Skip to content
This repository was archived by the owner on Apr 21, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![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)
[![Security Score](https://snyk-widget.herokuapp.com/badge/npm/@coderspirit%2Flambda-ioc/badge.svg)](https://snyk.io/advisor/npm-package/@coderspirit/lambda-ioc)

> Pure functional (λ) dependency injection 💉 for TypeScript (inspired by Diddly)
> Super type safe dependency injection 💉 for TypeScript (inspired by Diddly)

**NOTE:** This is a "fork" of Tom Sherman's
**[Diddly library](https://github.com/tom-sherman/diddly)**, who deserves most
Expand Down Expand Up @@ -85,6 +85,14 @@ container.resolveGroup('group2') // ~ [3, 4], not necessarily in the same order
// up to date. This is useful if we want to use the container as a factory for
// some of your dependencies.
const resolvedContainer = container.resolve('$')

// If you want to indirectly resolve the container itself, it can be done only
// with the methods:
// - resolveConstructor
// - resolveAsyncConstructor
// This is because they have "privileged" information about the container's
// type, while relying on `register` or `registerAsync` plus "combinators" does
// not allow us to leverage that information.
```

It is also possible to register and resolve asynchronous factories and
Expand Down
10 changes: 9 additions & 1 deletion lambda-ioc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![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)
[![Security Score](https://snyk-widget.herokuapp.com/badge/npm/@coderspirit%2Flambda-ioc/badge.svg)](https://snyk.io/advisor/npm-package/@coderspirit/lambda-ioc)

> Pure functional (λ) dependency injection 💉 for TypeScript (inspired by Diddly)
> Super type safe dependency injection 💉 for TypeScript (inspired by Diddly)

**NOTE:** This is a "fork" of Tom Sherman's
**[Diddly library](https://github.com/tom-sherman/diddly)**, who deserves most
Expand Down Expand Up @@ -85,6 +85,14 @@ container.resolveGroup('group2') // ~ [3, 4], not necessarily in the same order
// up to date. This is useful if we want to use the container as a factory for
// some of your dependencies.
const resolvedContainer = container.resolve('$')

// If you want to indirectly resolve the container itself, it can be done only
// with the methods:
// - resolveConstructor
// - resolveAsyncConstructor
// This is because they have "privileged" information about the container's
// type, while relying on `register` or `registerAsync` plus "combinators" does
// not allow us to leverage that information.
```

It is also possible to register and resolve asynchronous factories and
Expand Down
3 changes: 2 additions & 1 deletion lambda-ioc/deno/combinators.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
AsyncDependencyFactory,
ContainerKey,
ReadableContainer,
ReadableSyncContainer,
SyncDependencyFactory,
} from './container.ts';
import { ContainerKey, ParamsToResolverKeys, TupleO, Zip } from './util.ts';
import { ParamsToResolverKeys, TupleO, Zip } from './util.ts';

/**
* Given a dependency factory, returns a new factory that will always resolve
Expand Down
178 changes: 169 additions & 9 deletions lambda-ioc/deno/container.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */

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

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

type ConstrainedKey = Exclude<ContainerKey, '$' | `$:${string}`>
type KeysMatching<Collection, Value> = {
[K in keyof Collection]-?: Collection[K] extends Value ? K : never
}[keyof Collection]

type ContextualParamsToSyncResolverKeys<
TSyncDependencies extends Record<ConstrainedKey, unknown>,
TAsyncDependencies extends Record<ConstrainedKey, unknown>,
TParams extends
| readonly (
| TSyncDependencies[keyof TSyncDependencies]
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
| ReadableContainer<
Partial<TSyncDependencies>,
Partial<TAsyncDependencies>
>
)[]
| [],
> = {
[K in keyof TParams]: TParams[K] extends
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
? '$'
: KeysMatching<TSyncDependencies, TParams[K]>
}

type ContextualParamsToAsyncResolverKeys<
TSyncDependencies extends Record<ConstrainedKey, unknown>,
TAsyncDependencies extends Record<ConstrainedKey, unknown>,
TParams extends
| readonly (
| TSyncDependencies[keyof TSyncDependencies]
| TAsyncDependencies[keyof TAsyncDependencies]
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
| ReadableContainer<
Partial<TSyncDependencies>,
Partial<TAsyncDependencies>
>
)[]
| [],
> = {
[K in keyof TParams]: TParams[K] extends
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
? '$'
:
| KeysMatching<TSyncDependencies, TParams[K]>
| KeysMatching<TAsyncDependencies, TParams[K]>
}

export interface SyncDependencyFactory<
T,
Expand Down Expand Up @@ -116,7 +166,9 @@ export interface WritableContainer<
TAsyncDependencies extends Record<ConstrainedKey, unknown>,
> {
/**
* Register a new synchronous dependency factory.
* Registers a new synchronous dependency factory.
* It cannot be used when self-resolution is needed. Use
* `registerConstructor` instead.
*
* @param name The "name" of the dependency (can be a symbol).
* @param dependency A dependency factory.
Expand Down Expand Up @@ -148,7 +200,9 @@ export interface WritableContainer<
>

/**
* Register a new asynchronous dependency factory.
* Registers a new asynchronous dependency factory.
* It cannot be used when self-resolution is needed. Use
* `registerAsyncConstructor` instead.
*
* @param name The "name" of the dependency (can be a symbol).
* @param dependency A dependency factory.
Expand Down Expand Up @@ -179,10 +233,49 @@ export interface WritableContainer<
}
>

registerConstructor<
TName extends ConstrainedKey,
TParams extends readonly (
| TSyncDependencies[keyof TSyncDependencies]
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
| ReadableContainer<
Partial<TSyncDependencies>,
Partial<TAsyncDependencies>
>
)[],
TClass extends TName extends '$' | keyof TAsyncDependencies
? never
: TName extends keyof TSyncDependencies
? TSyncDependencies[TName]
: unknown,
TDependencies extends ContextualParamsToSyncResolverKeys<
TSyncDependencies,
TAsyncDependencies,
TParams
>,
>(
name: TName,
constructor: new (...args: TParams) => TClass,
...args: TDependencies
): Container<
{
[TK in
| keyof TSyncDependencies
| TName]: TK extends keyof TSyncDependencies
? TName extends TK
? TClass
: TSyncDependencies[TK]
: TClass
},
TAsyncDependencies
>

/**
* Registers a new constructor that might have asynchronous-resolvable
* dependencies. This method is helpful when the constructor combinator is
* not powerful enough (as it's only able to resolve synchronously).
* not powerful enough (as it's only able to resolve synchronously, and it
* cannot take advantage of self-resolution either).
*
* @param name The "name" of the dependency (can be a symbol).
* @param constructor A class constructor, that will be use to resolve the
Expand All @@ -196,13 +289,19 @@ export interface WritableContainer<
TParams extends readonly (
| TSyncDependencies[keyof TSyncDependencies]
| TAsyncDependencies[keyof TAsyncDependencies]
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
| ReadableContainer<
Partial<TSyncDependencies>,
Partial<TAsyncDependencies>
>
)[],
TClass extends TName extends '$' | keyof TSyncDependencies
? never
: TName extends keyof TAsyncDependencies
? TAsyncDependencies[TName]
: unknown,
TDependencies extends ContextualParamsToResolverKeys<
TDependencies extends ContextualParamsToAsyncResolverKeys<
TSyncDependencies,
TAsyncDependencies,
TParams
Expand All @@ -225,7 +324,7 @@ export interface WritableContainer<
>

/**
* Register an already instantiated dependency.
* Registers an already instantiated dependency.
*
* @param name The "name" of the dependency (can be a symbol).
* @param dependency An already instantiated value.
Expand Down Expand Up @@ -430,18 +529,77 @@ function __createContainer<
}
},

registerConstructor<
TName extends ConstrainedKey,
TParams extends readonly (
| TSyncDependencies[keyof TSyncDependencies]
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
| ReadableContainer<
Partial<TSyncDependencies>,
Partial<TAsyncDependencies>
>
)[],
TClass extends TName extends '$' | keyof TAsyncDependencies
? never
: TName extends keyof TSyncDependencies
? TSyncDependencies[TName]
: unknown,
TDependencies extends ContextualParamsToSyncResolverKeys<
TSyncDependencies,
TAsyncDependencies,
TParams
>,
>(
name: TName,
constructor: new (...args: TParams) => TClass,
...args: TDependencies
): ContainerWithNewSyncDep<TName, TClass> {
const factory = (container: typeof this) => {
const resolvedParams = args.map((arg) => {
return arg === '$'
? this
: container.resolve(arg as keyof TSyncDependencies)
}) as unknown as TParams

return new constructor(...resolvedParams)
}

if (name in syncDependencies) {
return __createContainer(
{
...syncDependencies,
[name]: factory,
},
asyncDependencies,
) as ContainerWithNewSyncDep<TName, TClass>
} else {
;(syncDependencies as Record<TName, unknown>)[name] = factory
return __createContainer(
syncDependencies,
asyncDependencies,
) as ContainerWithNewSyncDep<TName, TClass>
}
},

registerAsyncConstructor<
TName extends ConstrainedKey,
TParams extends readonly (
| TSyncDependencies[keyof TSyncDependencies]
| TAsyncDependencies[keyof TAsyncDependencies]
| ReadableSyncContainer<Partial<TSyncDependencies>>
| ReadableAsyncContainer<Partial<TAsyncDependencies>>
| ReadableContainer<
Partial<TSyncDependencies>,
Partial<TAsyncDependencies>
>
)[],
TClass extends TName extends '$' | keyof TSyncDependencies
? never
: TName extends keyof TAsyncDependencies
? TAsyncDependencies[TName]
: unknown,
TDependencies extends ContextualParamsToResolverKeys<
TDependencies extends ContextualParamsToAsyncResolverKeys<
TSyncDependencies,
TAsyncDependencies,
TParams
Expand All @@ -453,7 +611,9 @@ function __createContainer<
): ContainerWithNewAsyncDep<TName, TClass> {
const factory = async (container: typeof this) => {
const argPromises = args.map((arg) => {
return (arg as string) in syncDependencies
return arg === '$'
? this
: (arg as string) in syncDependencies
? container.resolve(arg as keyof TSyncDependencies)
: container.resolveAsync(arg as keyof TAsyncDependencies)
})
Expand Down
21 changes: 1 addition & 20 deletions lambda-ioc/deno/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */

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

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

export type ContextualParamsToResolverKeys<
TSyncDependencies extends Record<ContainerKey, unknown>,
TAsyncDependencies extends Record<ContainerKey, unknown>,
TParams extends
| readonly (
| TSyncDependencies[keyof TSyncDependencies]
| TAsyncDependencies[keyof TAsyncDependencies]
)[]
| [],
> = {
[K in keyof TParams]:
| KeysMatching<TSyncDependencies, TParams[K]>
| KeysMatching<TAsyncDependencies, TParams[K]>
}

type KeysMatching<Collection, Value> = {
[K in keyof Collection]-?: Collection[K] extends Value ? K : never
}[keyof Collection]

export type MergeNoDuplicates<A extends {}, B extends {}> = {
[K in keyof A | keyof B]: K extends keyof B
? K extends keyof A
Expand Down
4 changes: 2 additions & 2 deletions lambda-ioc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coderspirit/lambda-ioc",
"version": "0.6.0",
"version": "0.7.0",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/cjs/index.d.ts",
Expand All @@ -9,7 +9,7 @@
"require": "./dist/cjs/index.js",
"node": "./dist/cjs/index.js"
},
"description": "Pure functional (λ) dependency injection 💉 for TypeScript (inspired by Diddly)",
"description": "Super type safe dependency injection 💉 for TypeScript (inspired by Diddly)",
"keywords": [
"typescript",
"functional",
Expand Down
Loading