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

Commit 0eddbde

Browse files
committed
feat: asyncSingleton
1 parent 0185576 commit 0eddbde

File tree

7 files changed

+157
-47
lines changed

7 files changed

+157
-47
lines changed

lambda-ioc/deno/combinators.ts

+42-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
2+
AsyncDependencyFactory,
23
ContainerKey,
34
ReadableContainer,
5+
ReadableSyncContainer,
46
SyncDependencyFactory,
57
} from './container.ts';
68
import { ParamsToResolverKeys, TupleO, Zip } from './util.ts';
@@ -11,12 +13,13 @@ import { ParamsToResolverKeys, TupleO, Zip } from './util.ts';
1113
*/
1214
export function singleton<
1315
TVal,
14-
TDependencies extends Record<ContainerKey, unknown>,
16+
TSyncDependencies extends Record<ContainerKey, unknown>,
1517
>(
16-
// eslint-disable-next-line @typescript-eslint/ban-types
17-
factory: SyncDependencyFactory<TVal, ReadableContainer<TDependencies, {}>>,
18-
// eslint-disable-next-line @typescript-eslint/ban-types
19-
): SyncDependencyFactory<TVal, ReadableContainer<TDependencies, {}>> {
18+
factory: SyncDependencyFactory<
19+
TVal,
20+
ReadableSyncContainer<TSyncDependencies>
21+
>,
22+
): SyncDependencyFactory<TVal, ReadableSyncContainer<TSyncDependencies>> {
2023
let result: Awaited<TVal> | undefined
2124

2225
return (container) => {
@@ -27,6 +30,33 @@ export function singleton<
2730
}
2831
}
2932

33+
/**
34+
* Given a dependency factory, returns a new asynchronous factory that will
35+
* always resolve the same instance of the dependency.
36+
*/
37+
export function asyncSingleton<
38+
TVal,
39+
TSyncDependencies extends Record<ContainerKey, unknown>,
40+
TAsyncDependencies extends Record<ContainerKey, unknown>,
41+
>(
42+
factory: AsyncDependencyFactory<
43+
TVal,
44+
ReadableContainer<TSyncDependencies, TAsyncDependencies>
45+
>,
46+
): AsyncDependencyFactory<
47+
TVal,
48+
ReadableContainer<TSyncDependencies, TAsyncDependencies>
49+
> {
50+
let result: TVal | undefined
51+
52+
return async (container) => {
53+
if (!result) {
54+
result = await factory(container)
55+
}
56+
return result
57+
}
58+
}
59+
3060
/**
3161
* Given a function, and a list of named dependencies, creates a new dependency
3262
* factory that will resolve a parameterless function wrapping the original
@@ -35,20 +65,20 @@ export function singleton<
3565
export function func<
3666
TParams extends readonly unknown[],
3767
TReturn,
38-
TDependencies extends ParamsToResolverKeys<TParams>,
68+
TSyncDependencies extends ParamsToResolverKeys<TParams>,
3969
>(
4070
fn: (...args: TParams) => Awaited<TReturn>,
41-
...args: TDependencies
71+
...args: TSyncDependencies
4272
): SyncDependencyFactory<
4373
() => TReturn,
44-
SyncFuncContainer<TParams, TDependencies>
74+
SyncFuncContainer<TParams, TSyncDependencies>
4575
> {
46-
return (container: SyncFuncContainer<TParams, TDependencies>) => {
76+
return (container: SyncFuncContainer<TParams, TSyncDependencies>) => {
4777
const resolvedArgs = args.map((arg) =>
4878
container.resolve(
4979
// This is ugly as hell, but I did not want to apply ts-ignore
5080
arg as Parameters<
51-
SyncFuncContainer<TParams, TDependencies>['resolve']
81+
SyncFuncContainer<TParams, TSyncDependencies>['resolve']
5282
>[0],
5383
),
5484
) as unknown as TParams
@@ -89,11 +119,9 @@ export function constructor<
89119
type SyncFuncContainer<
90120
TParams extends readonly unknown[],
91121
TSyncDependencies extends ParamsToResolverKeys<TParams>,
92-
> = ReadableContainer<
122+
> = ReadableSyncContainer<
93123
TupleO<
94124
// eslint-disable-next-line @typescript-eslint/no-explicit-any
95125
Extract<Zip<TSyncDependencies, TParams>, readonly [ContainerKey, any][]>
96-
>,
97-
// eslint-disable-next-line @typescript-eslint/ban-types
98-
{}
126+
>
99127
>

lambda-ioc/deno/container.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type ExtractPrefixedValues<
1313

1414
export interface SyncDependencyFactory<
1515
T,
16-
TContainer extends ReadableContainer<Record<ContainerKey, unknown>, {}>,
16+
TContainer extends ReadableSyncContainer<Record<ContainerKey, unknown>>,
1717
> {
1818
(container: TContainer): Awaited<T>
1919
}
@@ -41,13 +41,8 @@ export interface DependencyFactory<
4141
> extends SyncDependencyFactory<T, TContainer>,
4242
AsyncDependencyFactory<T, TContainer> {}
4343

44-
/**
45-
* Represents a read-only version of a type-safe IoC container with "auto-wired"
46-
* dependencies resolution.
47-
*/
48-
export interface ReadableContainer<
44+
export interface ReadableSyncContainer<
4945
TSyncDependencies extends Record<ContainerKey, unknown>,
50-
TAsyncDependencies extends Record<ContainerKey, unknown>,
5146
> {
5247
/**
5348
* Resolve a "synchronous" dependency from the container.
@@ -57,7 +52,11 @@ export interface ReadableContainer<
5752
resolve<TName extends keyof TSyncDependencies>(
5853
name: TName,
5954
): TSyncDependencies[TName]
55+
}
6056

57+
export interface ReadableAsyncContainer<
58+
TAsyncDependencies extends Record<ContainerKey, unknown>,
59+
> {
6160
/**
6261
* Resolve an "asynchronous" dependency from the container.
6362
*
@@ -68,6 +67,16 @@ export interface ReadableContainer<
6867
): Promise<TAsyncDependencies[TName]>
6968
}
7069

70+
/**
71+
* Represents a read-only version of a type-safe IoC container with "auto-wired"
72+
* dependencies resolution.
73+
*/
74+
export interface ReadableContainer<
75+
TSyncDependencies extends Record<ContainerKey, unknown>,
76+
TAsyncDependencies extends Record<ContainerKey, unknown>,
77+
> extends ReadableSyncContainer<TSyncDependencies>,
78+
ReadableAsyncContainer<TAsyncDependencies> {}
79+
7180
export interface RedableGroupContainer<
7281
TSyncDependencies extends Record<ContainerKey, unknown>,
7382
TAsyncDependencies extends Record<ContainerKey, unknown>,
@@ -120,7 +129,7 @@ export interface WritableContainer<
120129
name: TName,
121130
dependency: SyncDependencyFactory<
122131
TDependency,
123-
ReadableContainer<TSyncDependencies, {}>
132+
ReadableSyncContainer<TSyncDependencies>
124133
>,
125134
): Container<
126135
{

lambda-ioc/deno/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export {
22
type Container,
33
type DependencyFactory,
4+
type ReadableAsyncContainer,
45
type ReadableContainer,
6+
type ReadableSyncContainer,
57
type WritableContainer,
68
createContainer,
79
} from './container.ts';
8-
export { constructor, func, singleton } from './combinators.ts';
10+
export { asyncSingleton, constructor, func, singleton } from './combinators.ts';

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

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContainer, func, singleton } from '..'
1+
import { asyncSingleton, createContainer, func, singleton } from '..'
22

33
describe('singleton', () => {
44
it('resolves the same instance each time (no dependencies)', () => {
@@ -29,3 +29,35 @@ describe('singleton', () => {
2929
expect(f1).toBe(f2)
3030
})
3131
})
32+
33+
describe('asyncSingleton', () => {
34+
it('resolves the same instance each time (no dependencies)', async () => {
35+
const container = createContainer().registerAsync(
36+
'foo',
37+
asyncSingleton(() => Promise.resolve({ hello: 'world' })),
38+
)
39+
40+
const f1 = await container.resolveAsync('foo')
41+
const f2 = await container.resolveAsync('foo')
42+
43+
expect(f1).toBe(f2)
44+
})
45+
46+
it('resolves the same instance each time (multiple dependencies)', async () => {
47+
const container = createContainer()
48+
.registerValue('a', 3)
49+
.registerValue('b', 5)
50+
.registerAsync(
51+
'ab',
52+
asyncSingleton(async (c) =>
53+
Promise.resolve({ value: c.resolve('a') * c.resolve('b') }),
54+
),
55+
)
56+
57+
const ab1 = await container.resolveAsync('ab')
58+
const ab2 = await container.resolveAsync('ab')
59+
60+
expect(ab1).toEqual({ value: 15 })
61+
expect(ab1).toBe(ab2)
62+
})
63+
})

lambda-ioc/src/combinators.ts

+42-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
2+
AsyncDependencyFactory,
23
ContainerKey,
34
ReadableContainer,
5+
ReadableSyncContainer,
46
SyncDependencyFactory,
57
} from './container'
68
import { ParamsToResolverKeys, TupleO, Zip } from './util'
@@ -11,12 +13,13 @@ import { ParamsToResolverKeys, TupleO, Zip } from './util'
1113
*/
1214
export function singleton<
1315
TVal,
14-
TDependencies extends Record<ContainerKey, unknown>,
16+
TSyncDependencies extends Record<ContainerKey, unknown>,
1517
>(
16-
// eslint-disable-next-line @typescript-eslint/ban-types
17-
factory: SyncDependencyFactory<TVal, ReadableContainer<TDependencies, {}>>,
18-
// eslint-disable-next-line @typescript-eslint/ban-types
19-
): SyncDependencyFactory<TVal, ReadableContainer<TDependencies, {}>> {
18+
factory: SyncDependencyFactory<
19+
TVal,
20+
ReadableSyncContainer<TSyncDependencies>
21+
>,
22+
): SyncDependencyFactory<TVal, ReadableSyncContainer<TSyncDependencies>> {
2023
let result: Awaited<TVal> | undefined
2124

2225
return (container) => {
@@ -27,6 +30,33 @@ export function singleton<
2730
}
2831
}
2932

33+
/**
34+
* Given a dependency factory, returns a new asynchronous factory that will
35+
* always resolve the same instance of the dependency.
36+
*/
37+
export function asyncSingleton<
38+
TVal,
39+
TSyncDependencies extends Record<ContainerKey, unknown>,
40+
TAsyncDependencies extends Record<ContainerKey, unknown>,
41+
>(
42+
factory: AsyncDependencyFactory<
43+
TVal,
44+
ReadableContainer<TSyncDependencies, TAsyncDependencies>
45+
>,
46+
): AsyncDependencyFactory<
47+
TVal,
48+
ReadableContainer<TSyncDependencies, TAsyncDependencies>
49+
> {
50+
let result: TVal | undefined
51+
52+
return async (container) => {
53+
if (!result) {
54+
result = await factory(container)
55+
}
56+
return result
57+
}
58+
}
59+
3060
/**
3161
* Given a function, and a list of named dependencies, creates a new dependency
3262
* factory that will resolve a parameterless function wrapping the original
@@ -35,20 +65,20 @@ export function singleton<
3565
export function func<
3666
TParams extends readonly unknown[],
3767
TReturn,
38-
TDependencies extends ParamsToResolverKeys<TParams>,
68+
TSyncDependencies extends ParamsToResolverKeys<TParams>,
3969
>(
4070
fn: (...args: TParams) => Awaited<TReturn>,
41-
...args: TDependencies
71+
...args: TSyncDependencies
4272
): SyncDependencyFactory<
4373
() => TReturn,
44-
SyncFuncContainer<TParams, TDependencies>
74+
SyncFuncContainer<TParams, TSyncDependencies>
4575
> {
46-
return (container: SyncFuncContainer<TParams, TDependencies>) => {
76+
return (container: SyncFuncContainer<TParams, TSyncDependencies>) => {
4777
const resolvedArgs = args.map((arg) =>
4878
container.resolve(
4979
// This is ugly as hell, but I did not want to apply ts-ignore
5080
arg as Parameters<
51-
SyncFuncContainer<TParams, TDependencies>['resolve']
81+
SyncFuncContainer<TParams, TSyncDependencies>['resolve']
5282
>[0],
5383
),
5484
) as unknown as TParams
@@ -89,11 +119,9 @@ export function constructor<
89119
type SyncFuncContainer<
90120
TParams extends readonly unknown[],
91121
TSyncDependencies extends ParamsToResolverKeys<TParams>,
92-
> = ReadableContainer<
122+
> = ReadableSyncContainer<
93123
TupleO<
94124
// eslint-disable-next-line @typescript-eslint/no-explicit-any
95125
Extract<Zip<TSyncDependencies, TParams>, readonly [ContainerKey, any][]>
96-
>,
97-
// eslint-disable-next-line @typescript-eslint/ban-types
98-
{}
126+
>
99127
>

lambda-ioc/src/container.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type ExtractPrefixedValues<
1313

1414
export interface SyncDependencyFactory<
1515
T,
16-
TContainer extends ReadableContainer<Record<ContainerKey, unknown>, {}>,
16+
TContainer extends ReadableSyncContainer<Record<ContainerKey, unknown>>,
1717
> {
1818
(container: TContainer): Awaited<T>
1919
}
@@ -41,13 +41,8 @@ export interface DependencyFactory<
4141
> extends SyncDependencyFactory<T, TContainer>,
4242
AsyncDependencyFactory<T, TContainer> {}
4343

44-
/**
45-
* Represents a read-only version of a type-safe IoC container with "auto-wired"
46-
* dependencies resolution.
47-
*/
48-
export interface ReadableContainer<
44+
export interface ReadableSyncContainer<
4945
TSyncDependencies extends Record<ContainerKey, unknown>,
50-
TAsyncDependencies extends Record<ContainerKey, unknown>,
5146
> {
5247
/**
5348
* Resolve a "synchronous" dependency from the container.
@@ -57,7 +52,11 @@ export interface ReadableContainer<
5752
resolve<TName extends keyof TSyncDependencies>(
5853
name: TName,
5954
): TSyncDependencies[TName]
55+
}
6056

57+
export interface ReadableAsyncContainer<
58+
TAsyncDependencies extends Record<ContainerKey, unknown>,
59+
> {
6160
/**
6261
* Resolve an "asynchronous" dependency from the container.
6362
*
@@ -68,6 +67,16 @@ export interface ReadableContainer<
6867
): Promise<TAsyncDependencies[TName]>
6968
}
7069

70+
/**
71+
* Represents a read-only version of a type-safe IoC container with "auto-wired"
72+
* dependencies resolution.
73+
*/
74+
export interface ReadableContainer<
75+
TSyncDependencies extends Record<ContainerKey, unknown>,
76+
TAsyncDependencies extends Record<ContainerKey, unknown>,
77+
> extends ReadableSyncContainer<TSyncDependencies>,
78+
ReadableAsyncContainer<TAsyncDependencies> {}
79+
7180
export interface RedableGroupContainer<
7281
TSyncDependencies extends Record<ContainerKey, unknown>,
7382
TAsyncDependencies extends Record<ContainerKey, unknown>,
@@ -120,7 +129,7 @@ export interface WritableContainer<
120129
name: TName,
121130
dependency: SyncDependencyFactory<
122131
TDependency,
123-
ReadableContainer<TSyncDependencies, {}>
132+
ReadableSyncContainer<TSyncDependencies>
124133
>,
125134
): Container<
126135
{

lambda-ioc/src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export {
22
type Container,
33
type DependencyFactory,
4+
type ReadableAsyncContainer,
45
type ReadableContainer,
6+
type ReadableSyncContainer,
57
type WritableContainer,
68
createContainer,
79
} from './container'
8-
export { constructor, func, singleton } from './combinators'
10+
export { asyncSingleton, constructor, func, singleton } from './combinators'

0 commit comments

Comments
 (0)