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

Commit d606ace

Browse files
committed
feat: introduce cc2ic
1 parent 8408706 commit d606ace

File tree

6 files changed

+115
-3
lines changed

6 files changed

+115
-3
lines changed

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { ... } from 'https://deno.land/x/lambda_ioc@[VERSION]/lambda-ioc/deno/in
3737

3838
```ts
3939
import {
40+
cc2ic, // Stands for "class-constructor to interface-constructor"
4041
constructor,
4142
createContainer,
4243
func
@@ -46,7 +47,12 @@ function printNameAndAge(name: string, age: number) {
4647
console.log(`${name} is aged ${age}`)
4748
}
4849

49-
class Person {
50+
interface Human {
51+
age: number
52+
name: readonly string
53+
}
54+
55+
class Person implements Human {
5056
constructor(
5157
public readonly age: number,
5258
public readonly name: string
@@ -60,6 +66,11 @@ const container = createContainer()
6066
.register('fn', func(printNameAndAge, 'someName', 'someAge'))
6167
// And constructors too
6268
.register('Person', constructor(Person, 'someAge', 'someName'))
69+
// We can do that directly, without having import `constructor`:
70+
.registerConstructor('AnotherPerson', Person, 'someAge', 'someName')
71+
// In case we want to register a "concrete" constructor to provide an
72+
// abstract interface, we'll have to apply a small hack, using `cc2ic`:
73+
.registerConstructor('Human', cc2ic<Human>()(Person), 'someAge', 'someName')
6374
// We can "define groups" by using `:` as an infix, the group's name will be
6475
// the first part of the string before `:`.
6576
// Groups can be used in all "register" methods.

lambda-ioc/deno/combinators.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ export function constructor<
113113
}
114114
}
115115

116+
// Class-Constructor to Interface-Constructor
117+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
118+
export function cc2ic<I>(): <CC extends new (...args: any[]) => I>(cc: CC) => AsInterfaceCtor<I, CC> {
119+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
120+
return identity as <C extends I, CC extends new (...args: any[]) => C>(
121+
cc: CC,
122+
) => AsInterfaceCtor<I, CC>
123+
}
124+
116125
// -----------------------------------------------------------------------------
117126
// Private Types
118127
// -----------------------------------------------------------------------------
@@ -125,3 +134,22 @@ type SyncFuncContainer<
125134
Extract<Zip<TSyncDependencies, TParams>, readonly [ContainerKey, any][]>
126135
>
127136
>
137+
138+
// Converts a "class constructor" type to an "interface constructor" type
139+
type AsInterfaceCtor<
140+
I, // The interface we are interested on
141+
// We have to pass CC to know its constructor parameters
142+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143+
CC extends new (...args: any[]) => I = new () => I,
144+
> = CC extends new (...args: infer A) => infer C
145+
? C extends I
146+
? new (...args: A) => I
147+
: never
148+
: never
149+
150+
// -----------------------------------------------------------------------------
151+
// Private Functions
152+
// -----------------------------------------------------------------------------
153+
154+
// Helper function, here to avoid creating too many anonymous lambdas
155+
const identity = (x: unknown) => x

lambda-ioc/deno/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ export {
88
type WritableContainer,
99
createContainer,
1010
} from './container.ts';
11-
export { asyncSingleton, constructor, func, singleton } from './combinators.ts';
11+
export {
12+
asyncSingleton,
13+
cc2ic,
14+
constructor,
15+
func,
16+
singleton,
17+
} from './combinators.ts';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { cc2ic } from '../combinators'
2+
import { createContainer } from '..'
3+
4+
interface IA {
5+
f: () => string
6+
}
7+
8+
class A implements IA {
9+
constructor(private readonly s: string) {}
10+
public f(): string {
11+
return this.s
12+
}
13+
}
14+
15+
class B {
16+
constructor(private readonly a: IA) {}
17+
public g(): IA {
18+
return this.a
19+
}
20+
}
21+
22+
describe('container regressions', () => {
23+
describe('registerConstructor', () => {
24+
it('allows to register constructors they are depended upon through interfaces', () => {
25+
const container = createContainer()
26+
.registerValue('greeting', 'hello')
27+
.registerConstructor('A', cc2ic<IA>()(A), 'greeting') // We have to rely on cc2ic
28+
.registerConstructor('B', B, 'A')
29+
30+
expect(container.resolve('B').g().f()).toBe('hello')
31+
})
32+
})
33+
})

lambda-ioc/src/combinators.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ export function constructor<
113113
}
114114
}
115115

116+
// Class-Constructor to Interface-Constructor
117+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
118+
export function cc2ic<I>(): <CC extends new (...args: any[]) => I>(cc: CC) => AsInterfaceCtor<I, CC> {
119+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
120+
return identity as <C extends I, CC extends new (...args: any[]) => C>(
121+
cc: CC,
122+
) => AsInterfaceCtor<I, CC>
123+
}
124+
116125
// -----------------------------------------------------------------------------
117126
// Private Types
118127
// -----------------------------------------------------------------------------
@@ -125,3 +134,22 @@ type SyncFuncContainer<
125134
Extract<Zip<TSyncDependencies, TParams>, readonly [ContainerKey, any][]>
126135
>
127136
>
137+
138+
// Converts a "class constructor" type to an "interface constructor" type
139+
type AsInterfaceCtor<
140+
I, // The interface we are interested on
141+
// We have to pass CC to know its constructor parameters
142+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143+
CC extends new (...args: any[]) => I = new () => I,
144+
> = CC extends new (...args: infer A) => infer C
145+
? C extends I
146+
? new (...args: A) => I
147+
: never
148+
: never
149+
150+
// -----------------------------------------------------------------------------
151+
// Private Functions
152+
// -----------------------------------------------------------------------------
153+
154+
// Helper function, here to avoid creating too many anonymous lambdas
155+
const identity = (x: unknown) => x

lambda-ioc/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ export {
88
type WritableContainer,
99
createContainer,
1010
} from './container'
11-
export { asyncSingleton, constructor, func, singleton } from './combinators'
11+
export {
12+
asyncSingleton,
13+
cc2ic,
14+
constructor,
15+
func,
16+
singleton,
17+
} from './combinators'

0 commit comments

Comments
 (0)