| @@ -29,7 +29,7 export class ActivationContext<S> implem | |||||
| 29 |
|
29 | |||
| 30 | private readonly _service: Descriptor<S, unknown>; |
|
30 | private readonly _service: Descriptor<S, unknown>; | |
| 31 |
|
31 | |||
| 32 |
private readonly _ |
|
32 | private readonly _lifetimeManagers: ILifetimeManager[]; | |
| 33 |
|
33 | |||
| 34 | private readonly _parent: ActivationContext<S> | undefined; |
|
34 | private readonly _parent: ActivationContext<S> | undefined; | |
| 35 |
|
35 | |||
| @@ -41,12 +41,12 export class ActivationContext<S> implem | |||||
| 41 | * @param service the service to activate, this parameter is used for the |
|
41 | * @param service the service to activate, this parameter is used for the | |
| 42 | * debug purpose. |
|
42 | * debug purpose. | |
| 43 | */ |
|
43 | */ | |
| 44 |
constructor( |
|
44 | constructor(lifetimeManagers: ILifetimeManager[], services: DescriptorMap<S>, name: string, service: Descriptor<S, unknown>, cache = {}) { | |
| 45 | this._name = name; |
|
45 | this._name = name; | |
| 46 | this._service = service; |
|
46 | this._service = service; | |
| 47 | this._cache = cache; |
|
47 | this._cache = cache; | |
| 48 | this._services = services; |
|
48 | this._services = services; | |
| 49 |
this._ |
|
49 | this._lifetimeManagers = lifetimeManagers; | |
| 50 | } |
|
50 | } | |
| 51 |
|
51 | |||
| 52 | /** the name of the current resolving dependency */ |
|
52 | /** the name of the current resolving dependency */ | |
| @@ -54,10 +54,6 export class ActivationContext<S> implem | |||||
| 54 | return this._name; |
|
54 | return this._name; | |
| 55 | } |
|
55 | } | |
| 56 |
|
56 | |||
| 57 | createContainerLifetime<T>() { |
|
|||
| 58 | return this._containerLifetimeManager.create<T>(); |
|
|||
| 59 | } |
|
|||
| 60 |
|
||||
| 61 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; |
|
57 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; | |
| 62 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; |
|
58 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; | |
| 63 | resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined { |
|
59 | resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined { | |
| @@ -1,32 +1,22 | |||||
| 1 | import { ActivationContext } from "./ActivationContext"; |
|
1 | import { ActivationContext } from "./ActivationContext"; | |
| 2 | import { ActivationError } from "./ActivationError"; |
|
2 | import { ActivationError } from "./ActivationError"; | |
| 3 | import { ContainerBuilder } from "./ContainerBuilder"; |
|
3 | import { ContainerBuilder } from "./ContainerBuilder"; | |
|
|
4 | import { LifetimeManager } from "./LifetimeManager"; | |||
| 4 | import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces"; |
|
5 | import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces"; | |
| 5 |
|
6 | |||
| 6 | export class Container<S> implements ServiceLocator<S>, IDestroyable { |
|
7 | export class Container<S> implements ServiceLocator<S>, IDestroyable { | |
| 7 | private readonly _services: DescriptorMap<S>; |
|
8 | private readonly _services: DescriptorMap<S>; | |
| 8 |
|
9 | |||
| 9 | private readonly _lifetimeManager: ILifetimeManager; |
|
10 | private readonly _lifetimeManagers: ILifetimeManager[]; | |
| 10 |
|
11 | |||
| 11 | private _disposed: boolean; |
|
12 | private _disposed: boolean; | |
| 12 |
|
13 | |||
| 13 | private readonly _onDestroyed: () => void; |
|
14 | private readonly _onDestroyed: () => void; | |
| 14 |
|
15 | |||
| 15 | constructor(services: DescriptorMap<S>, lifetimeManager: ILifetimeManager, destroyed: () => void) { |
|
16 | constructor(services: DescriptorMap<S>, lifetimeManagers: ILifetimeManager[], destroyed: () => void) { | |
| 16 |
this._services = |
|
17 | this._services = services; | |
| 17 | ...services, |
|
|||
| 18 | container: { |
|
|||
| 19 | configurable: false, |
|
|||
| 20 | activate: () => this |
|
|||
| 21 | }, |
|
|||
| 22 | childContainer: { |
|
|||
| 23 | configurable: false, |
|
|||
| 24 | activate: () => this.createChildBuilder(), |
|
|||
| 25 | } |
|
|||
| 26 | }; |
|
|||
| 27 |
|
||||
| 28 | this._disposed = false; |
|
18 | this._disposed = false; | |
| 29 | this._lifetimeManager = lifetimeManager; |
|
19 | this._lifetimeManagers = lifetimeManagers.concat(new LifetimeManager()); | |
| 30 | this._onDestroyed = destroyed; |
|
20 | this._onDestroyed = destroyed; | |
| 31 |
|
21 | |||
| 32 | } |
|
22 | } | |
| @@ -56,7 +46,7 export class Container<S> implements Ser | |||||
| 56 | else |
|
46 | else | |
| 57 | throw new Error(`Service '${String(name)}' isn't found`); |
|
47 | throw new Error(`Service '${String(name)}' isn't found`); | |
| 58 | } else { |
|
48 | } else { | |
| 59 | const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d); |
|
49 | const context = new ActivationContext(this._lifetimeManagers, this._services, String(name), d); | |
| 60 | try { |
|
50 | try { | |
| 61 | return d.activate(context); |
|
51 | return d.activate(context); | |
| 62 | } catch (error) { |
|
52 | } catch (error) { | |
| @@ -1,25 +1,30 | |||||
| 1 | import { Container } from "./Container"; |
|
1 | import { Container } from "./Container"; | |
| 2 | import { DescriptorBuilder } from "./DescriptorBuilder"; |
|
2 | import { DescriptorBuilder } from "./DescriptorBuilder"; | |
| 3 | import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces"; |
|
3 | import { containerSelfDescriptor } from "./DescriptorImpl"; | |
|
|
4 | import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable, ContainerServices, ContainerServicesConstraint } from "./interfaces"; | |||
| 4 | import { emptyLifetime, LifetimeManager } from "./LifetimeManager"; |
|
5 | import { emptyLifetime, LifetimeManager } from "./LifetimeManager"; | |
| 5 | import { isDestroyable, prototype } from "./traits"; |
|
6 | import { isDestroyable, prototype } from "./traits"; | |
| 6 |
|
7 | |||
| 7 | /** |
|
8 | /** | |
| 8 | * Container builder used to prepare service descriptors and create a IoC container |
|
9 | * Container builder used to prepare service descriptors and create a IoC container | |
| 9 | */ |
|
10 | */ | |
| 10 | export class ContainerBuilder<S, U extends keyof S> implements |
|
11 | export class ContainerBuilder<S extends ContainerServicesConstraint<S>, U extends keyof S> implements | |
| 11 | IContainerBuilder<S, U> { |
|
12 | IContainerBuilder<S, U> { | |
| 12 |
|
13 | |||
| 13 | private _pending = 1; |
|
14 | private _pending = 1; | |
| 14 |
|
15 | |||
| 15 | private readonly _services: DescriptorMap<S>; |
|
16 | private readonly _services: DescriptorMap<ContainerServices<S>>; | |
| 16 |
|
17 | |||
| 17 | private readonly _lifetimeManager = new LifetimeManager(); |
|
18 | private readonly _lifetimeManager = new LifetimeManager(); | |
| 18 |
|
19 | |||
| 19 | private readonly _lifetime: ILifetime<IDestroyable>; |
|
20 | private readonly _lifetime: ILifetime<IDestroyable>; | |
| 20 |
|
21 | |||
| 21 | constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) { |
|
22 | constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) { | |
| 22 |
this._services = |
|
23 | this._services = { | |
|
|
24 | ...parentServices, | |||
|
|
25 | container: containerSelfDescriptor as any, | |||
|
|
26 | childContainer: containerSelfDescriptor as any | |||
|
|
27 | }; | |||
| 23 | this._lifetimeManager = new LifetimeManager(); |
|
28 | this._lifetimeManager = new LifetimeManager(); | |
| 24 | this._lifetime = lifetime ?? emptyLifetime(); |
|
29 | this._lifetime = lifetime ?? emptyLifetime(); | |
| 25 | } |
|
30 | } | |
| @@ -13,6 +13,13 export interface DescriptorImplArgs<S, T | |||||
| 13 | dependencies?: DepsMap<S>; |
|
13 | dependencies?: DepsMap<S>; | |
| 14 | } |
|
14 | } | |
| 15 |
|
15 | |||
|
|
16 | export const containerSelfDescriptor = <S>() => Object.freeze({ | |||
|
|
17 | level: 0, | |||
|
|
18 | activate(context: IActivationContext<S>) { | |||
|
|
19 | return context.createChildContainer(); | |||
|
|
20 | } | |||
|
|
21 | }); | |||
|
|
22 | ||||
| 16 |
|
23 | |||
| 17 | export class DescriptorImpl<S, T> implements Descriptor<S, T> { |
|
24 | export class DescriptorImpl<S, T> implements Descriptor<S, T> { | |
| 18 |
|
25 | |||
| @@ -29,12 +29,10 const _emptySlot = Object.freeze({ | |||||
| 29 | cleanup: noop, |
|
29 | cleanup: noop, | |
| 30 | }); |
|
30 | }); | |
| 31 |
|
31 | |||
| 32 |
const _destroy = (item: |
|
32 | const _destroy = (item: unknown) => () => isDestroyable(item) && item.destroy() ; | |
| 33 |
|
33 | |||
| 34 | const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) => |
|
34 | const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) => | |
| 35 | cleanup ? () => cleanup(value) : |
|
35 | cleanup ? () => cleanup(value) : _destroy(value); | |
| 36 | isDestroyable(value) ? () => _destroy(value) : |
|
|||
| 37 | noop; |
|
|||
| 38 |
|
36 | |||
| 39 | const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({ |
|
37 | const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({ | |
| 40 | has: () => false, |
|
38 | has: () => false, | |
| @@ -119,6 +117,12 export const emptyLifetime = <T>() => () | |||||
| 119 |
|
117 | |||
| 120 | let nextId = 1; |
|
118 | let nextId = 1; | |
| 121 |
|
119 | |||
|
|
120 | export const containerLifetime = <T>() => { | |||
|
|
121 | const slotId = nextId ++; | |||
|
|
122 | return (context: ILifetimeContext) => | |||
|
|
123 | context.ownerSlot<T>(slotId); | |||
|
|
124 | }; | |||
|
|
125 | ||||
| 122 | export const hierarchyLifetime = <T>() => { |
|
126 | export const hierarchyLifetime = <T>() => { | |
| 123 | const slotId = nextId++; |
|
127 | const slotId = nextId++; | |
| 124 | return (context: ILifetimeContext) => |
|
128 | return (context: ILifetimeContext) => | |
| @@ -1,3 +1,4 | |||||
|
|
1 | import { ContainerBuilder } from "./ContainerBuilder"; | |||
| 1 | import { key } from "./traits"; |
|
2 | import { key } from "./traits"; | |
| 2 |
|
3 | |||
| 3 | export interface IDestroyable { |
|
4 | export interface IDestroyable { | |
| @@ -157,6 +158,10 export type ContainerServicesConstraint< | |||||
| 157 |
|
158 | |||
| 158 | export interface Descriptor<S, T> { |
|
159 | export interface Descriptor<S, T> { | |
| 159 |
|
160 | |||
|
|
161 | /** The level of the service in the containers chain. | |||
|
|
162 | */ | |||
|
|
163 | readonly level: number; | |||
|
|
164 | ||||
| 160 | /** This flags indicates that this registration can be replaced or overridden. */ |
|
165 | /** This flags indicates that this registration can be replaced or overridden. */ | |
| 161 | readonly configurable?: boolean; |
|
166 | readonly configurable?: boolean; | |
| 162 |
|
167 | |||
| @@ -170,6 +175,9 export interface Descriptor<S, T> { | |||||
| 170 |
|
175 | |||
| 171 | /** The context used to initialize lifetime instance {@linkcode ILifetime} */ |
|
176 | /** The context used to initialize lifetime instance {@linkcode ILifetime} */ | |
| 172 | export interface ILifetimeContext { |
|
177 | export interface ILifetimeContext { | |
|
|
178 | ||||
|
|
179 | ownerSlot<T>(slotId: string | number): ILifetimeSlot<T>; | |||
|
|
180 | ||||
| 173 | contextSlot<T>(slotId: string | number): ILifetimeSlot<T>; |
|
181 | contextSlot<T>(slotId: string | number): ILifetimeSlot<T>; | |
| 174 |
|
182 | |||
| 175 | containerSlot<T>(slotId: string | number): ILifetimeSlot<T>; |
|
183 | containerSlot<T>(slotId: string | number): ILifetimeSlot<T>; | |
| @@ -181,6 +189,10 export interface IActivationContext<S> e | |||||
| 181 | register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void; |
|
189 | register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void; | |
| 182 |
|
190 | |||
| 183 | fail(error: unknown): never; |
|
191 | fail(error: unknown): never; | |
|
|
192 | ||||
|
|
193 | selfContainer(): ServiceLocator<S>; | |||
|
|
194 | ||||
|
|
195 | createChildContainer(): IContainerBuilder<S, Exclude<keyof S, ContainerKeys>>; | |||
| 184 | } |
|
196 | } | |
| 185 |
|
197 | |||
| 186 | /** |
|
198 | /** | |
| @@ -196,10 +208,10 export type DescriptorMap<S> = { | |||||
| 196 |
|
208 | |||
| 197 | type ContainerKeys = keyof ContainerProvided<object>; |
|
209 | type ContainerKeys = keyof ContainerProvided<object>; | |
| 198 |
|
210 | |||
| 199 |
export type ContainerProvided<S extends |
|
211 | export type ContainerProvided<S, U extends keyof S = keyof S> = { | |
| 200 |
container: ServiceLocator<Container |
|
212 | container: ServiceLocator<ContainerProvided<S>>; | |
| 201 |
|
213 | |||
| 202 |
childContainer: IContainerBuilder< |
|
214 | childContainer: IContainerBuilder<S, U>; | |
| 203 | }; |
|
215 | }; | |
| 204 |
|
216 | |||
| 205 |
|
217 | |||
| @@ -208,8 +220,8 export type ContainerProvided<S extends | |||||
| 208 | * |
|
220 | * | |
| 209 | * Сервисы, предоставляемые контейнером не могут быть null или undefined. |
|
221 | * Сервисы, предоставляемые контейнером не могут быть null или undefined. | |
| 210 | */ |
|
222 | */ | |
| 211 |
export type ContainerServices<S |
|
223 | export type ContainerServices<S> = { | |
| 212 | [k in keyof S | ContainerKeys]: |
|
224 | [k in (keyof S) | ContainerKeys]: | |
| 213 | k extends ContainerKeys ? ContainerProvided<S>[k] : |
|
225 | k extends ContainerKeys ? ContainerProvided<S>[k] : | |
| 214 | k extends keyof S ? S[k] : never |
|
226 | k extends keyof S ? S[k] : never | |
| 215 | }; |
|
227 | }; | |
| @@ -2,7 +2,7 | |||||
| 2 | import { describe, it } from "mocha"; |
|
2 | import { describe, it } from "mocha"; | |
| 3 | import { Container } from "../Container"; |
|
3 | import { Container } from "../Container"; | |
| 4 | import { ContainerBuilder } from "../ContainerBuilder"; |
|
4 | import { ContainerBuilder } from "../ContainerBuilder"; | |
| 5 | import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver } from "../interfaces"; |
|
5 | import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver, ServiceLocator } from "../interfaces"; | |
| 6 | import { fluent } from "../traits"; |
|
6 | import { fluent } from "../traits"; | |
| 7 |
|
7 | |||
| 8 | class Foo { |
|
8 | class Foo { | |
| @@ -48,9 +48,9 if (refs && refs.name === "foo") { | |||||
| 48 | refs.default; |
|
48 | refs.default; | |
| 49 | } |
|
49 | } | |
| 50 |
|
50 | |||
| 51 | declare const x: ContainerServices<Services>; |
|
51 | declare const x: ServiceLocator<ContainerServices<Services>>; | |
| 52 |
|
52 | |||
| 53 | x.container.resolve("container"); |
|
53 | x.resolve("container").resolve("container"); | |
| 54 |
|
54 | |||
| 55 |
|
55 | |||
| 56 | mmap({ |
|
56 | mmap({ | |
General Comments 0
You need to be logged in to leave comments.
Login now
