| @@ -1,67 +1,94 | |||
|
|
1 | 1 | import { DescriptorBuilder } from "./DescriptorBuilder"; |
|
|
2 |
import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, Required |
|
|
|
2 | import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, ExtractRequired } from "./interfaces"; | |
|
|
3 | 3 | import { ServiceContainer } from "./interfaces"; |
|
|
4 | 4 | import { argumentNotNull, each, isKey } from "./traits"; |
|
|
5 | 5 | |
|
|
6 | 6 | export class FluentConfiguration<S extends object, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> { |
|
|
7 | 7 | |
|
|
8 | _builders: Partial<RegistrationBuildersMap<S>> = {}; | |
|
|
8 | private _builders: Partial<RegistrationBuildersMap<S>> = {}; | |
|
|
9 | 9 | |
|
|
10 | /** Adds a declaration of the services to the current config. | |
|
|
11 | * | |
|
|
12 | * @template D The map of the services | |
|
|
13 | * @returns self | |
|
|
14 | */ | |
|
|
10 | 15 | declare<D extends Partial<Pick<S, keyof D & keyof S>>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> { |
|
|
11 | 16 | return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>; |
|
|
12 | 17 | } |
|
|
13 | 18 | |
|
|
19 | /** Adds compile-time information about the already provided services | |
|
|
20 | * | |
|
|
21 | * @template P The map of the provided services | |
|
|
22 | * @returns self | |
|
|
23 | */ | |
|
|
14 | 24 | provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> { |
|
|
15 | 25 | return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>; |
|
|
16 | 26 | } |
|
|
17 | 27 | |
|
|
28 | /** Register the service. | |
|
|
29 | * | |
|
|
30 | * @param name The name of the service | |
|
|
31 | * @param builder The service builder | |
|
|
32 | * @returns self | |
|
|
33 | */ | |
|
|
18 | 34 | register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>; |
|
|
35 | /** Registers the collection of services | |
|
|
36 | * @param config The collection of services to register. | |
|
|
37 | * @returns self | |
|
|
38 | */ | |
|
|
19 | 39 | register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>; |
|
|
20 | 40 | register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) { |
|
|
21 | 41 | if (isKey(nameOrConfig)) { |
|
|
22 | 42 | argumentNotNull(builder, "builder"); |
|
|
23 | 43 | this._builders[nameOrConfig] = builder; |
|
|
24 | 44 | } else { |
|
|
25 | 45 | each(nameOrConfig, (v, k) => this.register(k, v)); |
|
|
26 | 46 | } |
|
|
27 | 47 | |
|
|
28 | 48 | return this as FluentConfiguration<S, Exclude<Y, K>>; |
|
|
29 | 49 | } |
|
|
30 | 50 | |
|
|
31 | done(missing: RequiredKeys<S, Y> extends never ? void : RequiredKeys<S, Y>) { | |
|
|
32 | if (missing !== undefined) | |
|
|
33 | throw new Error("The configuration isn't complete"); | |
|
|
51 | /** | |
|
|
52 | * This method is used to enable a compile time check of the configuration. | |
|
|
53 | * If there are not configured services in the configuration the compiler | |
|
|
54 | * will trigger the error. | |
|
|
55 | * | |
|
|
56 | * @param missing Empty object literal should always be specified. | |
|
|
57 | * @returns self | |
|
|
58 | */ | |
|
|
59 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
|
|
60 | done<M extends ExtractRequired<S,Y>>(missing: M) { | |
|
|
34 | 61 | return this; |
|
|
35 | 62 | } |
|
|
36 | 63 | |
|
|
37 | 64 | |
|
|
38 | 65 | apply<A extends Partial<S>>(target: ServiceContainer<A>) { |
|
|
39 | 66 | |
|
|
40 | 67 | let pending = 1; |
|
|
41 | 68 | |
|
|
42 | 69 | const _t2 = target as ServiceContainer<S>; |
|
|
43 | 70 | |
|
|
44 | 71 | const reject = (ex: unknown) => { throw ex; }; |
|
|
45 | 72 | |
|
|
46 | 73 | const complete = () => !--pending; |
|
|
47 | 74 | |
|
|
48 | 75 | each(this._builders, (v, k) => { |
|
|
49 | 76 | pending++; |
|
|
50 | 77 | const d = new DescriptorBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[typeof k]>>(_t2, |
|
|
51 | 78 | result => { |
|
|
52 | 79 | _t2.register(k, result); |
|
|
53 | 80 | complete(); |
|
|
54 | 81 | }, |
|
|
55 | 82 | reject |
|
|
56 | 83 | ); |
|
|
57 | 84 | |
|
|
58 | 85 | |
|
|
59 | 86 | v(d); |
|
|
60 | 87 | }); |
|
|
61 | 88 | if (!complete()) |
|
|
62 | 89 | throw new Error("The configuration didn't complete."); |
|
|
63 | 90 | |
|
|
64 | 91 | return _t2 as ServiceContainer<A & S>; |
|
|
65 | 92 | } |
|
|
66 | 93 | |
|
|
67 | 94 | } |
| @@ -1,121 +1,121 | |||
|
|
1 | 1 | import { ActivationContext } from "./ActivationContext"; |
|
|
2 | 2 | |
|
|
3 | 3 | export type primitive = number | string | null | undefined | symbol; |
|
|
4 | 4 | |
|
|
5 | 5 | export interface IDestroyable { |
|
|
6 | 6 | destroy(): void; |
|
|
7 | 7 | } |
|
|
8 | 8 | |
|
|
9 | 9 | /** |
|
|
10 | 10 | * @template S ΠΠ°ΡΡΠ° Π΄ΠΎΡΡΡΠΏΠ½ΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ |
|
|
11 | 11 | */ |
|
|
12 | 12 | export interface Resolver<S extends object> { |
|
|
13 | 13 | /** |
|
|
14 | 14 | * Π€ΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠ°Π·ΡΠ΅ΡΠ΅Π½ΠΈΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ, ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ°Π±ΡΠΈΡΠ½ΡΡ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ², |
|
|
15 | 15 | * ΠΎΡΠ»ΠΎΠΆΠ΅Π½Π½ΡΡ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΡ ΠΈ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎ-ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ Π΄Π»Ρ ΡΠ΅ΡΠ²ΠΈΡΠΎΠ² |
|
|
16 | 16 | * @template K ΠΠ»ΡΡ ΡΠ΅ΡΠ²ΠΈΡΠ° ΠΈΠ· {@link S} |
|
|
17 | 17 | * @template O Π’ΠΈΠΏ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠ° {@link opts} ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π΄Π»Ρ Π²ΡΠ²Π΅Π΄Π΅Π½ΠΈΡ ΡΠΈΠΏΠ° |
|
|
18 | 18 | * Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠΎΠ³ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡ. |
|
|
19 | 19 | * @param name ΠΠ»ΡΡ ΡΠ΅ΡΠ²ΠΈΡΠ°, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΡΠ°Π·ΡΠ΅ΡΠ΅Π½. |
|
|
20 | 20 | * @param {boolean=} opts.lazy ΠΡΠΈΠ·Π½Π°ΠΊ ΡΠΎΠ³ΠΎ, ΡΡΠΎ ΡΡΠ΅Π±ΡΠ΅ΡΡΡ ΠΎΡΠ»ΠΎΠΆΠ΅Π½Π½Π°Ρ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΡ, |
|
|
21 | 21 | * Π±ΡΠ΄Π΅Ρ Π²ΠΎΠ·Π²ΡΠ°ΡΠ΅Π½ ΡΠ°Π±ΡΠΈΡΠ½ΡΠΉ ΠΌΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ. ΠΡΠ»ΠΈ Π½Π΅ ΡΠΊΠ°Π·Π°Π½, |
|
|
22 | 22 | * ΡΠΎ ΡΡΠΈΡΠ°Π΅ΡΡΡ `false`. |
|
|
23 | 23 | * @param {any=} opts.default ΠΠ½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ, Π΅ΡΠ»ΠΈ Π² ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ΅ ΡΠΊΠ°Π·Π°Π½Π½ΡΠΉ |
|
|
24 | 24 | * ΡΠ΅ΡΠ²ΠΈΡ Π½Π΅ Π·Π°ΡΠ΅Π³ΠΈΡΡΡΠΈΡΠΎΠ²Π°Π½ |
|
|
25 | 25 | * @returns ΠΠΈΠ±ΠΎ ΡΠ°Π±ΡΠΈΡΠ½ΡΠΉ ΠΌΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ, Π»ΠΈΠ±ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ |
|
|
26 | 26 | * @throws Error ΠΡΠ»ΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΡ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π° ΠΈ Π½Π΅ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»Π΅Π½ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎ-ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ |
|
|
27 | 27 | */ |
|
|
28 | 28 | <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>; |
|
|
29 | 29 | <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>; |
|
|
30 | 30 | } |
|
|
31 | 31 | |
|
|
32 | 32 | export interface DescriptorBuilder<S extends object, T> { |
|
|
33 | 33 | |
|
|
34 | 34 | /** |
|
|
35 | 35 | * |
|
|
36 | 36 | * @param f |
|
|
37 | 37 | */ |
|
|
38 | 38 | factory(f: (resolve: Resolver<S>) => T): void; |
|
|
39 | 39 | |
|
|
40 | 40 | override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this; |
|
|
41 | 41 | override<K extends ConfigurableKeys<S>>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this; |
|
|
42 | 42 | |
|
|
43 | 43 | lifetime(lifetime: "singleton", typeId: string | number | object): this; |
|
|
44 | 44 | lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this; |
|
|
45 | 45 | |
|
|
46 | 46 | cleanup(cb: (item: T) => void): this; |
|
|
47 | 47 | |
|
|
48 | 48 | value(v: T): void; |
|
|
49 | 49 | } |
|
|
50 | 50 | |
|
|
51 | 51 | export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>) => void; |
|
|
52 | 52 | |
|
|
53 | 53 | export type RegistrationBuildersMap<S extends object, K extends ConfigurableKeys<S> = ConfigurableKeys<S>> = { |
|
|
54 | 54 | [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[k]>> |
|
|
55 | 55 | }; |
|
|
56 | 56 | |
|
|
57 | 57 | export interface Descriptor<S extends object, T> { |
|
|
58 | 58 | activate(context: ActivationContext<S>): T; |
|
|
59 | 59 | } |
|
|
60 | 60 | |
|
|
61 | 61 | export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>; |
|
|
62 | 62 | |
|
|
63 | 63 | export type RegistrationMap<S extends object, K extends keyof S = keyof S> = { |
|
|
64 | 64 | [k in K]-?: Descriptor<S, S[k]>; |
|
|
65 | 65 | }; |
|
|
66 | 66 | |
|
|
67 | 67 | export interface ProvidedServices<S extends object> { |
|
|
68 | 68 | container: ServiceLocator<ContainerServices<S>>; |
|
|
69 | 69 | |
|
|
70 | 70 | childContainer: ServiceContainer<S>; |
|
|
71 | 71 | } |
|
|
72 | 72 | |
|
|
73 | 73 | export type ProvidedKeys = keyof ProvidedServices<object>; |
|
|
74 | 74 | |
|
|
75 | 75 | export type ContainerKeys<S extends object> = keyof ContainerServices<S>; |
|
|
76 | 76 | |
|
|
77 | 77 | export type Mix<S, X> = { [k in keyof (S & X)]: k extends keyof X ? X[k] : S[k & keyof S] }; |
|
|
78 | 78 | |
|
|
79 | 79 | export type ContainerServices<S extends object> = Mix<S, ProvidedServices<S>>; |
|
|
80 | 80 | |
|
|
81 | 81 | export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>; |
|
|
82 | 82 | |
|
|
83 | 83 | export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>; |
|
|
84 | 84 | |
|
|
85 | 85 | export interface ServiceLocator<S extends object> { |
|
|
86 | 86 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; |
|
|
87 | 87 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; |
|
|
88 | 88 | } |
|
|
89 | 89 | |
|
|
90 | 90 | export interface LifetimeContainer { |
|
|
91 | 91 | createLifetime<T>(): ILifetime<T>; |
|
|
92 | 92 | } |
|
|
93 | 93 | |
|
|
94 | 94 | export interface ServiceContainer<S extends object> extends ServiceLocator<ContainerServices<S>>, LifetimeContainer, IDestroyable { |
|
|
95 | 95 | |
|
|
96 | 96 | register<K extends ConfigurableKeys<S>>(name: K, service: ConfigurableDescriptor<S, K>): void; |
|
|
97 | 97 | register<K extends ConfigurableKeys<S>>(services: { [k in K]: ConfigurableDescriptor<S, K> }): void; |
|
|
98 | 98 | |
|
|
99 | 99 | createChildContainer(): ServiceContainer<S>; |
|
|
100 | 100 | } |
|
|
101 | 101 | |
|
|
102 | 102 | |
|
|
103 | 103 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; |
|
|
104 | 104 | |
|
|
105 | 105 | /** |
|
|
106 | 106 | * ΠΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ Π΄Π»Ρ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΠΆΠΈΠ·Π½ΡΡ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡΠ° ΠΎΠ±ΡΠ΅ΠΊΡΠ°. ΠΠ°ΠΆΠ΄Π°Ρ ΡΠ΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ ΠΈΠΌΠ΅Π΅Ρ |
|
|
107 | 107 | * ΡΠ²ΠΎΠΉ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ `ILifetime`, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠΎΠ·Π΄Π°Π΅ΡΡΡ ΠΏΡΠΈ ΠΏΠ΅ΡΠ²ΠΎΠΉ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΈ |
|
|
108 | 108 | */ |
|
|
109 | 109 | export interface ILifetime<T> { |
|
|
110 | 110 | /** ΠΡΠΎΠ²Π΅ΡΡΠ΅Ρ, ΡΡΠΎ ΡΠΆΠ΅ ΡΠΎΠ·Π΄Π°Π½ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡΠ° */ |
|
|
111 | 111 | has(): boolean; |
|
|
112 | 112 | |
|
|
113 | 113 | get(): T; |
|
|
114 | 114 | |
|
|
115 | 115 | initialize(context: ActivationContext<object>): void; |
|
|
116 | 116 | |
|
|
117 | 117 | store(item: T, cleanup?: (item: T) => void): void; |
|
|
118 | 118 | } |
|
|
119 | 119 | |
|
|
120 |
export type Required |
|
|
|
120 | export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] }; | |
|
|
121 | 121 | |
| @@ -1,58 +1,58 | |||
|
|
1 | 1 | /* eslint max-classes-per-file: ["error", 20] */ |
|
|
2 | 2 | import { describe, it } from "mocha"; |
|
|
3 | 3 | import {LifetimeManager} from "../LifetimeManager"; |
|
|
4 | 4 | import {Container} from "../Container"; |
|
|
5 | 5 | import { fluent } from "../traits"; |
|
|
6 | 6 | |
|
|
7 | 7 | class Foo { |
|
|
8 | 8 | foo = "foo"; |
|
|
9 | 9 | } |
|
|
10 | 10 | |
|
|
11 | 11 | class Bar { |
|
|
12 | 12 | bar = "bar"; |
|
|
13 | 13 | |
|
|
14 | 14 | constructor(foo?: () => Foo) {} |
|
|
15 | 15 | } |
|
|
16 | 16 | |
|
|
17 | 17 | interface Services { |
|
|
18 | 18 | foo: Foo; |
|
|
19 | 19 | |
|
|
20 | 20 | bar?: Bar; |
|
|
21 | 21 | |
|
|
22 | 22 | baz: Foo; |
|
|
23 | 23 | } |
|
|
24 | 24 | |
|
|
25 | 25 | interface ServicesB { |
|
|
26 | 26 | // will give errors |
|
|
27 | 27 | // baz: Bar; |
|
|
28 | 28 | |
|
|
29 | 29 | baz: Foo; |
|
|
30 | 30 | |
|
|
31 | 31 | zoo?: Foo; |
|
|
32 | 32 | } |
|
|
33 | 33 | |
|
|
34 | 34 | interface SharedServices { |
|
|
35 | 35 | foo: Foo; |
|
|
36 | 36 | |
|
|
37 | 37 | bar?: Bar; |
|
|
38 | 38 | |
|
|
39 | 39 | baz: Bar; |
|
|
40 | 40 | } |
|
|
41 | 41 | |
|
|
42 | 42 | const config = fluent() |
|
|
43 | 43 | .declare<Services>() |
|
|
44 | 44 | .declare<ServicesB>() |
|
|
45 | 45 | .register({ |
|
|
46 | 46 | bar: it => it |
|
|
47 | 47 | .lifetime("context") |
|
|
48 | 48 | .factory($ => new Bar($("zoo", {lazy: true, default: new Foo()}))), |
|
|
49 | 49 | foo: it => it.factory($ => new Foo()), |
|
|
50 | 50 | baz: it => it.value(new Foo()) |
|
|
51 | 51 | }) |
|
|
52 | .done(); | |
|
|
52 | .done({}); | |
|
|
53 | 53 | |
|
|
54 | 54 | declare const container: Container<SharedServices>; |
|
|
55 | 55 | |
|
|
56 | 56 | const c2 = config.apply(container); |
|
|
57 | 57 | |
|
|
58 | 58 | c2.resolve("baz"); No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now
