@martel/calyx 0.1.0 → 1.0.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.
- package/.github/workflows/release.yml +6 -2
- package/CHANGELOG.md +6 -0
- package/README.md +130 -0
- package/benchmarks/di-benchmark.ts +15 -15
- package/benchmarks/run-calyx-lifecycle.ts +2 -2
- package/benchmarks/run-calyx.ts +2 -2
- package/bun.lock +1261 -0
- package/docs/controllers.md +2 -2
- package/docs/dependency-injection.md +1 -1
- package/docs/lifecycle.md +2 -2
- package/docs/migration.md +5 -5
- package/package.json +6 -5
- package/src/core/container.ts +252 -27
- package/src/core/decorators.ts +34 -10
- package/src/core/metadata.ts +13 -0
- package/src/core/module-ref.ts +3 -1
- package/src/http/application.ts +316 -150
- package/src/http/decorators.ts +29 -8
- package/src/http/factory.ts +4 -4
- package/src/http/router.ts +12 -0
- package/src/lifecycle/context.ts +2 -2
- package/src/lifecycle/interfaces.ts +20 -0
- package/tests/di.test.ts +11 -11
- package/tests/dynamic-module.test.ts +2 -2
- package/tests/lifecycle.test.ts +4 -4
- package/tests/phase1.test.ts +143 -0
- package/tests/phase2.test.ts +107 -0
- package/tests/phase3.test.ts +203 -0
- package/tests/routing.test.ts +4 -4
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
Module,
|
|
4
|
+
Injectable,
|
|
5
|
+
Inject,
|
|
6
|
+
Controller,
|
|
7
|
+
Get,
|
|
8
|
+
CalyxContainer,
|
|
9
|
+
CalyxFactory,
|
|
10
|
+
Scope,
|
|
11
|
+
REQUEST,
|
|
12
|
+
ModuleRef,
|
|
13
|
+
} from '../src/index.ts';
|
|
14
|
+
|
|
15
|
+
describe('Phase 3: Dependency Injection Scopes (Request & Transient)', () => {
|
|
16
|
+
|
|
17
|
+
// 1. Transient Scope Test
|
|
18
|
+
test('should resolve transient provider as new instances every time', () => {
|
|
19
|
+
let transientInstantiations = 0;
|
|
20
|
+
|
|
21
|
+
@Injectable({ scope: Scope.TRANSIENT })
|
|
22
|
+
class TransientService {
|
|
23
|
+
id = ++transientInstantiations;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Injectable()
|
|
27
|
+
class ConsumerService {
|
|
28
|
+
constructor(public t1: TransientService, public t2: TransientService) {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Module({
|
|
32
|
+
providers: [TransientService, ConsumerService],
|
|
33
|
+
})
|
|
34
|
+
class RootModule {}
|
|
35
|
+
|
|
36
|
+
const container = new CalyxContainer();
|
|
37
|
+
container.bootstrap(RootModule);
|
|
38
|
+
|
|
39
|
+
const consumer = container.getGlobalOrAnyInstance(ConsumerService);
|
|
40
|
+
expect(consumer.t1.id).toBe(1);
|
|
41
|
+
expect(consumer.t2.id).toBe(2);
|
|
42
|
+
expect(consumer.t1).not.toBe(consumer.t2);
|
|
43
|
+
|
|
44
|
+
const t3 = container.resolveTokenGlobally(TransientService);
|
|
45
|
+
expect(t3.id).toBe(3);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 2. Request Scope and Bubble Up Test
|
|
49
|
+
test('should bubble up Request scope and instantiate once per request', async () => {
|
|
50
|
+
let requestScopedInstantiations = 0;
|
|
51
|
+
let controllerInstantiations = 0;
|
|
52
|
+
|
|
53
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
54
|
+
class ScopedService {
|
|
55
|
+
id = ++requestScopedInstantiations;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Injectable() // implicitly request-scoped because of dependency ScopedService
|
|
59
|
+
class DependentService {
|
|
60
|
+
constructor(public scoped: ScopedService) {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Controller('scoped')
|
|
64
|
+
class ScopedController {
|
|
65
|
+
id = ++controllerInstantiations;
|
|
66
|
+
constructor(public dep: DependentService, public scoped: ScopedService) {}
|
|
67
|
+
|
|
68
|
+
@Get()
|
|
69
|
+
getIds() {
|
|
70
|
+
return {
|
|
71
|
+
controllerId: this.id,
|
|
72
|
+
depScopedId: this.dep.scoped.id,
|
|
73
|
+
scopedId: this.scoped.id,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Module({
|
|
79
|
+
controllers: [ScopedController],
|
|
80
|
+
providers: [ScopedService, DependentService],
|
|
81
|
+
})
|
|
82
|
+
class RootModule {}
|
|
83
|
+
|
|
84
|
+
const app = await CalyxFactory.create(RootModule);
|
|
85
|
+
await app.listen(3994);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// First request
|
|
89
|
+
const res1 = await fetch('http://localhost:3994/scoped');
|
|
90
|
+
expect(res1.status).toBe(200);
|
|
91
|
+
const data1 = await res1.json();
|
|
92
|
+
expect(data1).toEqual({
|
|
93
|
+
controllerId: 1,
|
|
94
|
+
depScopedId: 1,
|
|
95
|
+
scopedId: 1,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Second request
|
|
99
|
+
const res2 = await fetch('http://localhost:3994/scoped');
|
|
100
|
+
expect(res2.status).toBe(200);
|
|
101
|
+
const data2 = await res2.json();
|
|
102
|
+
expect(data2).toEqual({
|
|
103
|
+
controllerId: 2,
|
|
104
|
+
depScopedId: 2,
|
|
105
|
+
scopedId: 2,
|
|
106
|
+
});
|
|
107
|
+
} finally {
|
|
108
|
+
await app.close();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 3. REQUEST Object Injection Test
|
|
113
|
+
test('should inject the current Request object into request-scoped providers', async () => {
|
|
114
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
115
|
+
class ReqService {
|
|
116
|
+
constructor(@Inject(REQUEST) public request: Request) {}
|
|
117
|
+
|
|
118
|
+
getUrl() {
|
|
119
|
+
return this.request.url;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Controller('request-inject')
|
|
124
|
+
class ReqController {
|
|
125
|
+
constructor(public reqService: ReqService) {}
|
|
126
|
+
|
|
127
|
+
@Get()
|
|
128
|
+
testInject() {
|
|
129
|
+
return { url: this.reqService.getUrl() };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Module({
|
|
134
|
+
controllers: [ReqController],
|
|
135
|
+
providers: [ReqService],
|
|
136
|
+
})
|
|
137
|
+
class RootModule {}
|
|
138
|
+
|
|
139
|
+
const app = await CalyxFactory.create(RootModule);
|
|
140
|
+
await app.listen(3995);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const res = await fetch('http://localhost:3995/request-inject?query=123');
|
|
144
|
+
expect(res.status).toBe(200);
|
|
145
|
+
const body = await res.json();
|
|
146
|
+
expect(body.url).toContain('/request-inject?query=123');
|
|
147
|
+
} finally {
|
|
148
|
+
await app.close();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// 4. ModuleRef resolve() and create() Test
|
|
153
|
+
test('should support ModuleRef.resolve() and ModuleRef.create()', async () => {
|
|
154
|
+
let transientCount = 0;
|
|
155
|
+
|
|
156
|
+
@Injectable({ scope: Scope.TRANSIENT })
|
|
157
|
+
class TransService {
|
|
158
|
+
id = ++transientCount;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Injectable()
|
|
162
|
+
class ConsumerService {
|
|
163
|
+
constructor(public moduleRef: ModuleRef) {}
|
|
164
|
+
|
|
165
|
+
async resolveTransient() {
|
|
166
|
+
return this.moduleRef.resolve(TransService);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async createDynamicClass() {
|
|
170
|
+
class DynamicClass {
|
|
171
|
+
constructor(public t: TransService) {}
|
|
172
|
+
}
|
|
173
|
+
Reflect.defineMetadata('design:paramtypes', [TransService], DynamicClass);
|
|
174
|
+
Reflect.defineMetadata('calyx:injectable', true, DynamicClass);
|
|
175
|
+
return this.moduleRef.create(DynamicClass);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Module({
|
|
180
|
+
providers: [TransService, ConsumerService],
|
|
181
|
+
})
|
|
182
|
+
class RootModule {}
|
|
183
|
+
|
|
184
|
+
const container = new CalyxContainer();
|
|
185
|
+
container.bootstrap(RootModule);
|
|
186
|
+
|
|
187
|
+
const consumer = container.getGlobalOrAnyInstance(ConsumerService);
|
|
188
|
+
|
|
189
|
+
// Test resolve
|
|
190
|
+
const t1 = await consumer.resolveTransient();
|
|
191
|
+
const t2 = await consumer.resolveTransient();
|
|
192
|
+
expect(t1).toBeInstanceOf(TransService);
|
|
193
|
+
expect(t2).toBeInstanceOf(TransService);
|
|
194
|
+
expect(t1.id).toBe(1);
|
|
195
|
+
expect(t2.id).toBe(2);
|
|
196
|
+
expect(t1).not.toBe(t2);
|
|
197
|
+
|
|
198
|
+
// Test create
|
|
199
|
+
const dynamic = await consumer.createDynamicClass();
|
|
200
|
+
expect(dynamic.t).toBeInstanceOf(TransService);
|
|
201
|
+
expect(dynamic.t.id).toBe(3);
|
|
202
|
+
});
|
|
203
|
+
});
|
package/tests/routing.test.ts
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
Header,
|
|
15
15
|
Res,
|
|
16
16
|
Redirect,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
CalyxFactory,
|
|
18
|
+
CalyxResponse,
|
|
19
19
|
NotFoundException,
|
|
20
20
|
BadRequestException,
|
|
21
21
|
} from '../src/index.ts';
|
|
@@ -59,7 +59,7 @@ class UsersController {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
@Get('manual')
|
|
62
|
-
manual(@Res() res:
|
|
62
|
+
manual(@Res() res: CalyxResponse) {
|
|
63
63
|
res.status(202).json({ manual: true });
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -80,7 +80,7 @@ describe('HTTP Routing and Controller System', () => {
|
|
|
80
80
|
const PORT = 3847;
|
|
81
81
|
|
|
82
82
|
beforeAll(async () => {
|
|
83
|
-
app = await
|
|
83
|
+
app = await CalyxFactory.create(AppModule);
|
|
84
84
|
await app.listen(PORT);
|
|
85
85
|
baseUrl = `http://localhost:${PORT}`;
|
|
86
86
|
});
|