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

Commit 47c892d

Browse files
authored
Merge pull request #12 from Coder-Spirit/indirect-self-resolution
Implement basic indirect self-resolution
2 parents 965f1b6 + 11b34fa commit 47c892d

File tree

10 files changed

+453
-65
lines changed

10 files changed

+453
-65
lines changed

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![Known Vulnerabilities](https://snyk.io//test/github/Coder-Spirit/lambda-ioc/badge.svg?targetFile=package.json)](https://snyk.io//test/github/Coder-Spirit/lambda-ioc?targetFile=package.json)
88
[![Security Score](https://snyk-widget.herokuapp.com/badge/npm/@coderspirit%2Flambda-ioc/badge.svg)](https://snyk.io/advisor/npm-package/@coderspirit/lambda-ioc)
99

10-
> Pure functional (λ) dependency injection 💉 for TypeScript (inspired by Diddly)
10+
> Super type safe dependency injection 💉 for TypeScript (inspired by Diddly)
1111
1212
**NOTE:** This is a "fork" of Tom Sherman's
1313
**[Diddly library](https://github.com/tom-sherman/diddly)**, who deserves most
@@ -85,6 +85,14 @@ container.resolveGroup('group2') // ~ [3, 4], not necessarily in the same order
8585
// up to date. This is useful if we want to use the container as a factory for
8686
// some of your dependencies.
8787
const resolvedContainer = container.resolve('$')
88+
89+
// If you want to indirectly resolve the container itself, it can be done only
90+
// with the methods:
91+
// - resolveConstructor
92+
// - resolveAsyncConstructor
93+
// This is because they have "privileged" information about the container's
94+
// type, while relying on `register` or `registerAsync` plus "combinators" does
95+
// not allow us to leverage that information.
8896
```
8997

9098
It is also possible to register and resolve asynchronous factories and

lambda-ioc/README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![Known Vulnerabilities](https://snyk.io//test/github/Coder-Spirit/lambda-ioc/badge.svg?targetFile=package.json)](https://snyk.io//test/github/Coder-Spirit/lambda-ioc?targetFile=package.json)
88
[![Security Score](https://snyk-widget.herokuapp.com/badge/npm/@coderspirit%2Flambda-ioc/badge.svg)](https://snyk.io/advisor/npm-package/@coderspirit/lambda-ioc)
99

10-
> Pure functional (λ) dependency injection 💉 for TypeScript (inspired by Diddly)
10+
> Super type safe dependency injection 💉 for TypeScript (inspired by Diddly)
1111
1212
**NOTE:** This is a "fork" of Tom Sherman's
1313
**[Diddly library](https://github.com/tom-sherman/diddly)**, who deserves most
@@ -85,6 +85,14 @@ container.resolveGroup('group2') // ~ [3, 4], not necessarily in the same order
8585
// up to date. This is useful if we want to use the container as a factory for
8686
// some of your dependencies.
8787
const resolvedContainer = container.resolve('$')
88+
89+
// If you want to indirectly resolve the container itself, it can be done only
90+
// with the methods:
91+
// - resolveConstructor
92+
// - resolveAsyncConstructor
93+
// This is because they have "privileged" information about the container's
94+
// type, while relying on `register` or `registerAsync` plus "combinators" does
95+
// not allow us to leverage that information.
8896
```
8997

9098
It is also possible to register and resolve asynchronous factories and

lambda-ioc/deno/combinators.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import {
22
AsyncDependencyFactory,
3+
ContainerKey,
34
ReadableContainer,
45
ReadableSyncContainer,
56
SyncDependencyFactory,
67
} from './container.ts';
7-
import { ContainerKey, ParamsToResolverKeys, TupleO, Zip } from './util.ts';
8+
import { ParamsToResolverKeys, TupleO, Zip } from './util.ts';
89

910
/**
1011
* Given a dependency factory, returns a new factory that will always resolve

lambda-ioc/deno/container.ts

+169-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22

3-
import { ContainerKey, ContextualParamsToResolverKeys } from './util.ts';
3+
export type ContainerKey = string | symbol
4+
type ConstrainedKey = Exclude<ContainerKey, '$' | `$:${string}`>
45

56
type ExtractPrefix<S extends ContainerKey> =
67
S extends `${infer Prefix}:${string}` ? Prefix : never
@@ -11,7 +12,56 @@ type ExtractPrefixedValues<
1112
BaseKeys extends keyof Struct = keyof Struct,
1213
> = BaseKeys extends `${Prefix}:${infer U}` ? Struct[`${Prefix}:${U}`] : never
1314

14-
type ConstrainedKey = Exclude<ContainerKey, '$' | `$:${string}`>
15+
type KeysMatching<Collection, Value> = {
16+
[K in keyof Collection]-?: Collection[K] extends Value ? K : never
17+
}[keyof Collection]
18+
19+
type ContextualParamsToSyncResolverKeys<
20+
TSyncDependencies extends Record<ConstrainedKey, unknown>,
21+
TAsyncDependencies extends Record<ConstrainedKey, unknown>,
22+
TParams extends
23+
| readonly (
24+
| TSyncDependencies[keyof TSyncDependencies]
25+
| ReadableSyncContainer<Partial<TSyncDependencies>>
26+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
27+
| ReadableContainer<
28+
Partial<TSyncDependencies>,
29+
Partial<TAsyncDependencies>
30+
>
31+
)[]
32+
| [],
33+
> = {
34+
[K in keyof TParams]: TParams[K] extends
35+
| ReadableSyncContainer<Partial<TSyncDependencies>>
36+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
37+
? '$'
38+
: KeysMatching<TSyncDependencies, TParams[K]>
39+
}
40+
41+
type ContextualParamsToAsyncResolverKeys<
42+
TSyncDependencies extends Record<ConstrainedKey, unknown>,
43+
TAsyncDependencies extends Record<ConstrainedKey, unknown>,
44+
TParams extends
45+
| readonly (
46+
| TSyncDependencies[keyof TSyncDependencies]
47+
| TAsyncDependencies[keyof TAsyncDependencies]
48+
| ReadableSyncContainer<Partial<TSyncDependencies>>
49+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
50+
| ReadableContainer<
51+
Partial<TSyncDependencies>,
52+
Partial<TAsyncDependencies>
53+
>
54+
)[]
55+
| [],
56+
> = {
57+
[K in keyof TParams]: TParams[K] extends
58+
| ReadableSyncContainer<Partial<TSyncDependencies>>
59+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
60+
? '$'
61+
:
62+
| KeysMatching<TSyncDependencies, TParams[K]>
63+
| KeysMatching<TAsyncDependencies, TParams[K]>
64+
}
1565

1666
export interface SyncDependencyFactory<
1767
T,
@@ -116,7 +166,9 @@ export interface WritableContainer<
116166
TAsyncDependencies extends Record<ConstrainedKey, unknown>,
117167
> {
118168
/**
119-
* Register a new synchronous dependency factory.
169+
* Registers a new synchronous dependency factory.
170+
* It cannot be used when self-resolution is needed. Use
171+
* `registerConstructor` instead.
120172
*
121173
* @param name The "name" of the dependency (can be a symbol).
122174
* @param dependency A dependency factory.
@@ -148,7 +200,9 @@ export interface WritableContainer<
148200
>
149201

150202
/**
151-
* Register a new asynchronous dependency factory.
203+
* Registers a new asynchronous dependency factory.
204+
* It cannot be used when self-resolution is needed. Use
205+
* `registerAsyncConstructor` instead.
152206
*
153207
* @param name The "name" of the dependency (can be a symbol).
154208
* @param dependency A dependency factory.
@@ -179,10 +233,49 @@ export interface WritableContainer<
179233
}
180234
>
181235

236+
registerConstructor<
237+
TName extends ConstrainedKey,
238+
TParams extends readonly (
239+
| TSyncDependencies[keyof TSyncDependencies]
240+
| ReadableSyncContainer<Partial<TSyncDependencies>>
241+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
242+
| ReadableContainer<
243+
Partial<TSyncDependencies>,
244+
Partial<TAsyncDependencies>
245+
>
246+
)[],
247+
TClass extends TName extends '$' | keyof TAsyncDependencies
248+
? never
249+
: TName extends keyof TSyncDependencies
250+
? TSyncDependencies[TName]
251+
: unknown,
252+
TDependencies extends ContextualParamsToSyncResolverKeys<
253+
TSyncDependencies,
254+
TAsyncDependencies,
255+
TParams
256+
>,
257+
>(
258+
name: TName,
259+
constructor: new (...args: TParams) => TClass,
260+
...args: TDependencies
261+
): Container<
262+
{
263+
[TK in
264+
| keyof TSyncDependencies
265+
| TName]: TK extends keyof TSyncDependencies
266+
? TName extends TK
267+
? TClass
268+
: TSyncDependencies[TK]
269+
: TClass
270+
},
271+
TAsyncDependencies
272+
>
273+
182274
/**
183275
* Registers a new constructor that might have asynchronous-resolvable
184276
* dependencies. This method is helpful when the constructor combinator is
185-
* not powerful enough (as it's only able to resolve synchronously).
277+
* not powerful enough (as it's only able to resolve synchronously, and it
278+
* cannot take advantage of self-resolution either).
186279
*
187280
* @param name The "name" of the dependency (can be a symbol).
188281
* @param constructor A class constructor, that will be use to resolve the
@@ -196,13 +289,19 @@ export interface WritableContainer<
196289
TParams extends readonly (
197290
| TSyncDependencies[keyof TSyncDependencies]
198291
| TAsyncDependencies[keyof TAsyncDependencies]
292+
| ReadableSyncContainer<Partial<TSyncDependencies>>
293+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
294+
| ReadableContainer<
295+
Partial<TSyncDependencies>,
296+
Partial<TAsyncDependencies>
297+
>
199298
)[],
200299
TClass extends TName extends '$' | keyof TSyncDependencies
201300
? never
202301
: TName extends keyof TAsyncDependencies
203302
? TAsyncDependencies[TName]
204303
: unknown,
205-
TDependencies extends ContextualParamsToResolverKeys<
304+
TDependencies extends ContextualParamsToAsyncResolverKeys<
206305
TSyncDependencies,
207306
TAsyncDependencies,
208307
TParams
@@ -225,7 +324,7 @@ export interface WritableContainer<
225324
>
226325

227326
/**
228-
* Register an already instantiated dependency.
327+
* Registers an already instantiated dependency.
229328
*
230329
* @param name The "name" of the dependency (can be a symbol).
231330
* @param dependency An already instantiated value.
@@ -430,18 +529,77 @@ function __createContainer<
430529
}
431530
},
432531

532+
registerConstructor<
533+
TName extends ConstrainedKey,
534+
TParams extends readonly (
535+
| TSyncDependencies[keyof TSyncDependencies]
536+
| ReadableSyncContainer<Partial<TSyncDependencies>>
537+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
538+
| ReadableContainer<
539+
Partial<TSyncDependencies>,
540+
Partial<TAsyncDependencies>
541+
>
542+
)[],
543+
TClass extends TName extends '$' | keyof TAsyncDependencies
544+
? never
545+
: TName extends keyof TSyncDependencies
546+
? TSyncDependencies[TName]
547+
: unknown,
548+
TDependencies extends ContextualParamsToSyncResolverKeys<
549+
TSyncDependencies,
550+
TAsyncDependencies,
551+
TParams
552+
>,
553+
>(
554+
name: TName,
555+
constructor: new (...args: TParams) => TClass,
556+
...args: TDependencies
557+
): ContainerWithNewSyncDep<TName, TClass> {
558+
const factory = (container: typeof this) => {
559+
const resolvedParams = args.map((arg) => {
560+
return arg === '$'
561+
? this
562+
: container.resolve(arg as keyof TSyncDependencies)
563+
}) as unknown as TParams
564+
565+
return new constructor(...resolvedParams)
566+
}
567+
568+
if (name in syncDependencies) {
569+
return __createContainer(
570+
{
571+
...syncDependencies,
572+
[name]: factory,
573+
},
574+
asyncDependencies,
575+
) as ContainerWithNewSyncDep<TName, TClass>
576+
} else {
577+
;(syncDependencies as Record<TName, unknown>)[name] = factory
578+
return __createContainer(
579+
syncDependencies,
580+
asyncDependencies,
581+
) as ContainerWithNewSyncDep<TName, TClass>
582+
}
583+
},
584+
433585
registerAsyncConstructor<
434586
TName extends ConstrainedKey,
435587
TParams extends readonly (
436588
| TSyncDependencies[keyof TSyncDependencies]
437589
| TAsyncDependencies[keyof TAsyncDependencies]
590+
| ReadableSyncContainer<Partial<TSyncDependencies>>
591+
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
592+
| ReadableContainer<
593+
Partial<TSyncDependencies>,
594+
Partial<TAsyncDependencies>
595+
>
438596
)[],
439597
TClass extends TName extends '$' | keyof TSyncDependencies
440598
? never
441599
: TName extends keyof TAsyncDependencies
442600
? TAsyncDependencies[TName]
443601
: unknown,
444-
TDependencies extends ContextualParamsToResolverKeys<
602+
TDependencies extends ContextualParamsToAsyncResolverKeys<
445603
TSyncDependencies,
446604
TAsyncDependencies,
447605
TParams
@@ -453,7 +611,9 @@ function __createContainer<
453611
): ContainerWithNewAsyncDep<TName, TClass> {
454612
const factory = async (container: typeof this) => {
455613
const argPromises = args.map((arg) => {
456-
return (arg as string) in syncDependencies
614+
return arg === '$'
615+
? this
616+
: (arg as string) in syncDependencies
457617
? container.resolve(arg as keyof TSyncDependencies)
458618
: container.resolveAsync(arg as keyof TAsyncDependencies)
459619
})

lambda-ioc/deno/util.ts

+1-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
33

4-
export type ContainerKey = string | symbol
4+
import { ContainerKey } from './container.ts';
55

66
export type Zip<A extends readonly unknown[], B extends readonly unknown[]> = {
77
[K in keyof A]: K extends keyof B ? [A[K], B[K]] : never
@@ -11,25 +11,6 @@ export type ParamsToResolverKeys<T extends readonly unknown[] | []> = {
1111
[K in keyof T]: ContainerKey
1212
}
1313

14-
export type ContextualParamsToResolverKeys<
15-
TSyncDependencies extends Record<ContainerKey, unknown>,
16-
TAsyncDependencies extends Record<ContainerKey, unknown>,
17-
TParams extends
18-
| readonly (
19-
| TSyncDependencies[keyof TSyncDependencies]
20-
| TAsyncDependencies[keyof TAsyncDependencies]
21-
)[]
22-
| [],
23-
> = {
24-
[K in keyof TParams]:
25-
| KeysMatching<TSyncDependencies, TParams[K]>
26-
| KeysMatching<TAsyncDependencies, TParams[K]>
27-
}
28-
29-
type KeysMatching<Collection, Value> = {
30-
[K in keyof Collection]-?: Collection[K] extends Value ? K : never
31-
}[keyof Collection]
32-
3314
export type MergeNoDuplicates<A extends {}, B extends {}> = {
3415
[K in keyof A | keyof B]: K extends keyof B
3516
? K extends keyof A

lambda-ioc/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coderspirit/lambda-ioc",
3-
"version": "0.6.0",
3+
"version": "0.7.0",
44
"main": "./dist/cjs/index.js",
55
"module": "./dist/esm/index.js",
66
"types": "./dist/cjs/index.d.ts",
@@ -9,7 +9,7 @@
99
"require": "./dist/cjs/index.js",
1010
"node": "./dist/cjs/index.js"
1111
},
12-
"description": "Pure functional (λ) dependency injection 💉 for TypeScript (inspired by Diddly)",
12+
"description": "Super type safe dependency injection 💉 for TypeScript (inspired by Diddly)",
1313
"keywords": [
1414
"typescript",
1515
"functional",

0 commit comments

Comments
 (0)