Skip to content
This repository was archived by the owner on Apr 21, 2024. It is now read-only.

Commit 116c674

Browse files
authored
Merge pull request #6 from Coder-Spirit/i0005-writer-container-type-refinement
writer container type refinement
2 parents b27fa22 + 54de00e commit 116c674

File tree

4 files changed

+288
-44
lines changed

4 files changed

+288
-44
lines changed

lambda-ioc/deno/container.ts

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,14 @@ export interface WritableContainer<
7777
* @param name The "name" of the dependency (can be a symbol).
7878
* @param dependency A dependency factory.
7979
*/
80-
register<TName extends ContainerKey, TDependency>(
80+
register<
81+
TName extends ContainerKey,
82+
TDependency extends TName extends keyof TSyncDependencies
83+
? TSyncDependencies[TName]
84+
: TName extends keyof TAsyncDependencies
85+
? never
86+
: unknown,
87+
>(
8188
name: TName,
8289
dependency: SyncDependencyFactory<
8390
TDependency,
@@ -102,7 +109,14 @@ export interface WritableContainer<
102109
* @param name The "name" of the dependency (can be a symbol).
103110
* @param dependency A dependency factory.
104111
*/
105-
registerAsync<TName extends ContainerKey, TDependency>(
112+
registerAsync<
113+
TName extends ContainerKey,
114+
TDependency extends TName extends keyof TSyncDependencies
115+
? never
116+
: TName extends keyof TAsyncDependencies
117+
? TAsyncDependencies[TName]
118+
: unknown,
119+
>(
106120
name: TName,
107121
dependency: AsyncDependencyFactory<
108122
TDependency,
@@ -127,7 +141,14 @@ export interface WritableContainer<
127141
* @param name The "name" of the dependency (can be a symbol).
128142
* @param dependency An already instantiated value.
129143
*/
130-
registerValue<TName extends ContainerKey, TDependency>(
144+
registerValue<
145+
TName extends ContainerKey,
146+
TDependency extends TName extends keyof TSyncDependencies
147+
? TSyncDependencies[TName]
148+
: TName extends keyof TAsyncDependencies
149+
? never
150+
: unknown,
151+
>(
131152
name: TName,
132153
dependency: TDependency,
133154
): Container<
@@ -266,13 +287,21 @@ function __createContainer<
266287
Container<TSyncDependencies, {}>
267288
>,
268289
): ContainerWithNewSyncDep<TName, TDependency> {
269-
return __createContainer(
270-
{
271-
...syncDependencies,
272-
[name]: dependency,
273-
},
274-
asyncDependencies,
275-
) as ContainerWithNewSyncDep<TName, TDependency>
290+
if (name in syncDependencies) {
291+
return __createContainer(
292+
{
293+
...syncDependencies,
294+
[name]: dependency,
295+
},
296+
asyncDependencies,
297+
) as ContainerWithNewSyncDep<TName, TDependency>
298+
} else {
299+
;(syncDependencies as Record<TName, unknown>)[name] = dependency
300+
return __createContainer(
301+
syncDependencies,
302+
asyncDependencies,
303+
) as ContainerWithNewSyncDep<TName, TDependency>
304+
}
276305
},
277306

278307
registerAsync<TName extends ContainerKey, TDependency>(
@@ -282,23 +311,39 @@ function __createContainer<
282311
Container<TSyncDependencies, TAsyncDependencies>
283312
>,
284313
): ContainerWithNewAsyncDep<TName, TDependency> {
285-
return __createContainer(syncDependencies, {
286-
...asyncDependencies,
287-
[name]: dependency,
288-
}) as ContainerWithNewAsyncDep<TName, TDependency>
314+
if (name in asyncDependencies) {
315+
return __createContainer(syncDependencies, {
316+
...asyncDependencies,
317+
[name]: dependency,
318+
}) as ContainerWithNewAsyncDep<TName, TDependency>
319+
} else {
320+
;(asyncDependencies as Record<TName, unknown>)[name] = dependency
321+
return __createContainer(
322+
syncDependencies,
323+
asyncDependencies,
324+
) as ContainerWithNewAsyncDep<TName, TDependency>
325+
}
289326
},
290327

291328
registerValue<TName extends ContainerKey, TDependency>(
292329
name: TName,
293330
dependency: TDependency,
294331
): ContainerWithNewSyncDep<TName, TDependency> {
295-
return __createContainer(
296-
{
297-
...syncDependencies,
298-
[name]: () => dependency,
299-
},
300-
asyncDependencies,
301-
) as ContainerWithNewSyncDep<TName, TDependency>
332+
if (name in syncDependencies) {
333+
return __createContainer(
334+
{
335+
...syncDependencies,
336+
[name]: () => dependency,
337+
},
338+
asyncDependencies,
339+
) as ContainerWithNewSyncDep<TName, TDependency>
340+
} else {
341+
;(syncDependencies as Record<TName, unknown>)[name] = () => dependency
342+
return __createContainer(
343+
syncDependencies,
344+
asyncDependencies,
345+
) as ContainerWithNewSyncDep<TName, TDependency>
346+
}
302347
},
303348

304349
resolve<TName extends keyof TSyncDependencies>(

lambda-ioc/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coderspirit/lambda-ioc",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"main": "./dist/cjs/index.js",
55
"module": "./dist/esm/index.js",
66
"types": "./dist/cjs/index.d.ts",

lambda-ioc/src/__tests__/container.test.ts

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { createContainer } from '..'
1+
/* eslint-disable @typescript-eslint/ban-types */
2+
import { Container, createContainer } from '..'
23

4+
// Behavioural tests
35
describe('container', () => {
46
it('can register simple values', () => {
57
const container = createContainer()
@@ -41,4 +43,156 @@ describe('container', () => {
4143

4244
expect(await container.resolveAsync('ab')).toBe(15)
4345
})
46+
47+
it('can re-register sync dependencies (without changing the "original" container)', async () => {
48+
const c1 = createContainer()
49+
.register('a', () => 3)
50+
.register('b', () => 5)
51+
.registerAsync('c', async () => await Promise.resolve(11))
52+
.register('ab', (c) => c.resolve('a') * c.resolve('b'))
53+
.registerAsync(
54+
'ac',
55+
async (c) => c.resolve('a') * (await c.resolveAsync('c')),
56+
)
57+
const c2 = c1.register('a', () => 7)
58+
const c3 = c2.registerValue('a', 13)
59+
60+
expect(c1.resolve('ab')).toBe(15)
61+
expect(c2.resolve('ab')).toBe(35)
62+
expect(c3.resolve('ab')).toBe(65)
63+
64+
expect(await c1.resolveAsync('ac')).toBe(33)
65+
expect(await c2.resolveAsync('ac')).toBe(77)
66+
expect(await c3.resolveAsync('ac')).toBe(143)
67+
})
68+
69+
it('can re-register async dependencies (without changing the "original" container)', async () => {
70+
const c1 = createContainer()
71+
.register('a', () => 3)
72+
.registerAsync('b', async () => await Promise.resolve(5))
73+
.registerAsync(
74+
'ab',
75+
async (c) => c.resolve('a') * (await c.resolveAsync('b')),
76+
)
77+
const c2 = c1.registerAsync('b', async () => await Promise.resolve(7))
78+
79+
expect(await c1.resolveAsync('ab')).toBe(15)
80+
expect(await c2.resolveAsync('ab')).toBe(21)
81+
})
82+
})
83+
84+
// Type tests
85+
describe('@types/container', () => {
86+
it('has the correct type when created for the first time', () => {
87+
const c = createContainer()
88+
type C = typeof c
89+
type C_extends_Container = C extends Container<{}, {}> ? true : false
90+
type Container_extends_C = Container<{}, {}> extends C ? true : false
91+
type C_is_Container = C_extends_Container extends true
92+
? Container_extends_C extends true
93+
? true
94+
: false
95+
: false
96+
const c_is_Container: C_is_Container = true
97+
expect(c_is_Container).toBe(true)
98+
})
99+
100+
it('has the correct type after register call', () => {
101+
const c = createContainer().register('a', () => 1)
102+
103+
type C = typeof c
104+
type TargetType = Container<{ a: number }, {}>
105+
106+
type C_extends_TargetType = C extends TargetType ? true : false
107+
type TargetType_extends_C = TargetType extends C ? true : false
108+
type C_is_TargetType = C_extends_TargetType extends true
109+
? TargetType_extends_C extends true
110+
? true
111+
: false
112+
: false
113+
const c_is_TargetType: C_is_TargetType = true
114+
expect(c_is_TargetType).toBe(true)
115+
})
116+
117+
it('has the correct type after registerAsync call', () => {
118+
const c = createContainer().registerAsync(
119+
'b',
120+
async () => await Promise.resolve(1),
121+
)
122+
123+
type C = typeof c
124+
type TargetType = Container<{}, { b: number }>
125+
126+
type C_extends_TargetType = C extends TargetType ? true : false
127+
type TargetType_extends_C = TargetType extends C ? true : false
128+
type C_is_TargetType = C_extends_TargetType extends true
129+
? TargetType_extends_C extends true
130+
? true
131+
: false
132+
: false
133+
const c_is_TargetType: C_is_TargetType = true
134+
expect(c_is_TargetType).toBe(true)
135+
})
136+
137+
it('resolves nothing when the container is empty', () => {
138+
type C = Container<{}, {}>
139+
140+
// Checking what can be synchronously resolved (it should be nothing)
141+
type C_resolve_Parameters = Parameters<C['resolve']>
142+
type C_resolve_Parameters_is_never = C_resolve_Parameters extends [never]
143+
? true
144+
: false
145+
const c_cannot_resolve_anything: C_resolve_Parameters_is_never = true
146+
expect(c_cannot_resolve_anything).toBe(true)
147+
148+
// Checking what can be asynchronously resolved (it should be nothing)
149+
type C_resolveAsync_Parameters = Parameters<C['resolveAsync']>
150+
type C_resolveAsync_Parameters_is_never =
151+
C_resolveAsync_Parameters extends [never] ? true : false
152+
const c_cannot_resolveAsync_anything: C_resolveAsync_Parameters_is_never =
153+
true
154+
expect(c_cannot_resolveAsync_anything).toBe(true)
155+
})
156+
157+
it('only resolves the sync registered dependency', () => {
158+
type C = Container<{ a: number }, {}>
159+
160+
// Checking what can be synchronously resolved (it should be just 'a')
161+
type C_resolve_Parameters = Parameters<C['resolve']>
162+
type C_resolve_Parameters_is_a = C_resolve_Parameters extends ['a']
163+
? true
164+
: false
165+
const c_can_only_resolve_a: C_resolve_Parameters_is_a = true
166+
expect(c_can_only_resolve_a).toBe(true)
167+
168+
// Checking what can be asynchronously resolved (it should be nothing)
169+
type C_resolveAsync_Parameters = Parameters<C['resolveAsync']>
170+
type C_resolveAsync_Parameters_is_never =
171+
C_resolveAsync_Parameters extends [never] ? true : false
172+
const c_cannot_resolveAsync_anything: C_resolveAsync_Parameters_is_never =
173+
true
174+
expect(c_cannot_resolveAsync_anything).toBe(true)
175+
})
176+
177+
it('only resolves the async registered dependency', () => {
178+
type C = Container<{}, { b: number }>
179+
180+
// Checking what can be synchronously resolved (it should be nothing)
181+
type C_resolve_Parameters = Parameters<C['resolve']>
182+
type C_resolve_Parameters_is_never = C_resolve_Parameters extends [never]
183+
? true
184+
: false
185+
const c_cannot_resolve_anything: C_resolve_Parameters_is_never = true
186+
expect(c_cannot_resolve_anything).toBe(true)
187+
188+
// Checking what can be asynchronously resolved (it should be just 'b)
189+
type C_resolveAsync_Parameters = Parameters<C['resolveAsync']>
190+
type C_resolveAsync_Parameters_is_b = C_resolveAsync_Parameters extends [
191+
'b',
192+
]
193+
? true
194+
: false
195+
const c_can_only_resolve_b: C_resolveAsync_Parameters_is_b = true
196+
expect(c_can_only_resolve_b).toBe(true)
197+
})
44198
})

0 commit comments

Comments
 (0)