@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.
@@ -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
+ });
@@ -14,8 +14,8 @@ import {
14
14
  Header,
15
15
  Res,
16
16
  Redirect,
17
- calyxFactory,
18
- calyxResponse,
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: calyxResponse) {
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 calyxFactory.create(AppModule);
83
+ app = await CalyxFactory.create(AppModule);
84
84
  await app.listen(PORT);
85
85
  baseUrl = `http://localhost:${PORT}`;
86
86
  });