##// END OF EJS Templates
Working on IoC container configuration
cin -
r111:33420a95f637 ioc ts support
parent child
Show More
@@ -1,50 +1,37
1 1 import { Constructor } from "../interfaces";
2 2
3 3 export interface InjectOptions {
4 4 lazy?: boolean;
5 5 }
6 6
7 7 type Setter<T = any> = (v: T) => void;
8 8
9 type Injector<T> = {
10 [k in keyof T]: Setter;
11 };
12
13 9 type Compatible<T1, T2> = T1 extends T2 ? any : never;
14 10
15 11 type SetterType<T> = T extends (v: infer V) => void ? V : never;
16 12
17 type Tuple<T = any> = Parameters<(...args: T[]) => void>;
13 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
18 14
19 interface Newable<A extends Tuple, T> {
20 new (...params: A): T;
21 prototype: T;
22 }
15 type ExtractDependency<D, S> = D extends { $dependency: infer K } ? D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : VisitDependency<D, S>;
23 16
24 type MapTuple<T, A extends (keyof T)[]> = { [K in keyof A] : K extends number ? T[ A[K] ] : A[K] };
17 type VisitDependency<D, S> = D extends {} ? { [K in keyof D]: ExtractDependency<D[K], S> } : D;
25 18
26 19 export class Builder<T, S> {
27 provides() {
28 return <C extends Constructor<T>>(constructor: C) => {
29 return constructor;
20 consume<P extends any[]>(...args: P) {
21 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
22 // return constructor;
30 23 };
31 24 }
32 25
33 26 inject<K extends keyof S>(dependency: K) {
34 27 // K = "bar"
35 28 // M = "setValue"
36 29 // S[K] = Bar
37 30 // T[M] = (value: string) => void
38 31 // P[m] = (value: V) => void
39 32 return <P, M extends keyof (T | P)>(target: P, memberName: M, descriptor: TypedPropertyDescriptor<Compatible<T[M], Setter<S[K]>>>) => {
40 33
41 34 };
42 35 }
43 36
44 dependencies<D extends (keyof S)[]>(...deps: D) {
45 return <C extends Constructor<T>>(constructor: MapTuple<S, D> extends ConstructorParameters<C> ? C : never) => {
46 return constructor;
47 } ;
48 }
49
50 37 }
@@ -1,111 +1,111
1 1 export interface Constructor<T = {}> {
2 2 new(...args: any[]): T;
3 3 prototype: T;
4 4 }
5 5
6 6 export type Factory<T = {}> = (...args: any[]) => T;
7 7
8 8 export type Predicate<T = any> = (x: T) => boolean;
9 9
10 10 export interface MapOf<T> {
11 11 [key: string]: T;
12 12 }
13 13
14 14 export interface IDestroyable {
15 15 destroy(): void;
16 16 }
17 17
18 18 export interface IRemovable {
19 19 remove(): void;
20 20 }
21 21
22 22 export interface ICancellation {
23 23 throwIfRequested(): void;
24 24 isRequested(): boolean;
25 25 isSupported(): boolean;
26 26 register(cb: (e: any) => void): IDestroyable;
27 27 }
28 28
29 29 /**
30 30 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‰ΠΈΠΉ Π°ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½Π½ΡƒΡŽ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΡŽ
31 31 */
32 32 export interface IActivatable {
33 33 /**
34 34 * @returns Boolean indicates the current state
35 35 */
36 36 isActive(): boolean;
37 37
38 38 /**
39 39 * Starts the component activation
40 40 * @param ct cancellation token for this operation
41 41 */
42 42 activate(ct?: ICancellation): Promise<void>;
43 43
44 44 /**
45 45 * Starts the component deactivation
46 46 * @param ct cancellation token for this operation
47 47 */
48 48 deactivate(ct?: ICancellation): Promise<void>;
49 49
50 50 /**
51 51 * Sets the activation controller for this component
52 52 * @param controller The activation controller
53 53 *
54 54 * Activation controller checks whether this component
55 55 * can be activated and manages the active state of the
56 56 * component
57 57 */
58 setActivationController(controller: IActivationController);
58 setActivationController(controller: IActivationController): void;
59 59
60 60 /**
61 61 * Gets the current activation controller for this component
62 62 */
63 63 getActivationController(): IActivationController;
64 64 }
65 65
66 66 export interface IActivationController {
67 67 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
68 68
69 69 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
70 70
71 71 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
72 72
73 73 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
74 74
75 75 deactivate(ct?: ICancellation): Promise<void>;
76 76
77 77 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
78 78
79 79 getActive(): IActivatable;
80 80 }
81 81
82 82 export interface IAsyncComponent {
83 83 getCompletion(): Promise<void>;
84 84 }
85 85
86 86 export interface ICancellable {
87 87 cancel(reason?: any): void;
88 88 }
89 89
90 90 export interface IObservable<T> {
91 91 on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable;
92 92 next(ct?: ICancellation): Promise<T>;
93 93 }
94 94
95 95 export interface IObserver<T> {
96 96 next(event: T): void;
97 97
98 98 error(e: any): void;
99 99
100 100 complete(): void;
101 101 }
102 102
103 103 export interface TextWriter {
104 104 write(obj: any): void;
105 105 write(format: string, ...args: any[]): void;
106 106
107 107 writeLine(obj?: any): void;
108 108 writeLine(format: string, ...args: any[]): void;
109 109
110 110 writeValue(value: any, spec?: string): void;
111 111 }
@@ -1,18 +1,33
1 1 import { Foo } from "./Foo";
2 import { config } from "./config";
2 3
3 export class Bar {
4 name = "bar";
4 const service = config.build("bar");
5 5
6 foo: Foo | undefined;
6 @service.consume({
7 f: config.dependency("foo"),
8 nested: {
9 lazy: config.lazy("foo")
10 }
11 })
12 export class Bar {
13 barName = "bar";
7 14
8 constructor(_opts?: { foo?: Foo; }) {
9 if (_opts && _opts.foo)
10 this.foo = _opts.foo;
15 _v: Foo | undefined;
16
17 constructor(_opts: {
18 f: Foo;
19 nested: {
20 lazy: () => Foo
21 }
22 }) {
23
24 if (_opts && _opts.f)
25 this._v = _opts.f;
11 26 }
12 27
13 28 getFoo() {
14 if (this.foo === undefined)
29 if (this._v === undefined)
15 30 throw new Error("The foo isn't set");
16 return this.foo;
31 return this._v;
17 32 }
18 33 }
@@ -1,29 +1,28
1 1 import { config } from "./config";
2 2
3 const service = config.service("barBox");
3 const service = config.build("barBox");
4 4
5 @service.provides()
5 @service.consume(config.dependency("bar"))
6 6 export class Box<T> {
7 7 private _value: T | undefined;
8 8
9 9 constructor(value: T) {
10 10 this._value = value;
11 11 }
12 12
13 13 @service.inject("bar")
14 14 setValue(value: T) {
15 15 this._value = value;
16 16 }
17 17
18 @service.inject("foo")
19 18 setObj(value: object) {
20 19
21 20 }
22 21
23 22 getValue() {
24 23 if (this._value === undefined)
25 24 throw new Error("Trying to get a value from the empty box");
26 25
27 26 return this._value;
28 27 }
29 } No newline at end of file
28 }
@@ -1,3 +1,3
1 1 export class Foo {
2 name = "foo";
2 fooName = "foo";
3 3 }
@@ -1,29 +1,67
1 1 import { Foo } from "./Foo";
2 2 import { Bar } from "./Bar";
3 3 import { ActivationType } from "../di/interfaces";
4 4 import { Builder } from "../di/Annotations";
5 5 import { Box } from "./Box";
6 6
7 7 interface RegistrationOptions {
8 8 activation?: ActivationType;
9 9 }
10 10
11 interface Dependency<K extends keyof any> {
12 $dependency: K;
13
14 lazy?: boolean;
15 }
16
17 interface Lazy<K extends keyof any> extends Dependency<K> {
18 lazy: true;
19 }
20
21 type PromiseOrValue<T> = T | PromiseLike<T>;
22
11 23 interface ConfigBuilder<S> {
12 service<K extends keyof S>(name: K): Builder<S[K], S>;
24 build<K extends keyof S, T = S[K]>(name: K): Builder<T, S>;
25
26 dependency<K extends keyof S>(name: K): Dependency<K>;
27
28 lazy<K extends keyof S>(name: K): Lazy<K>;
29
30 mapTo<K extends keyof S>(name: K, ctor: () => PromiseOrValue<new (...args: any[]) => S[K]>): ConfigBuilder<S>;
31
13 32 }
14 33
15 34 interface ContainerServices {
16 35 barBox: Box<Bar>;
17 36
18 37 foo: Foo;
19 38
20 39 bar: Bar;
21 40
22 41 password: string;
23 42
24 43 user: string;
25 44
26 45 timeout: number;
27 46 }
28 47
48 declare function load<M, C extends keyof M>(m: PromiseLike<M>, name: C): () => PromiseLike<M[C]>;
49
50 const t = {
51 barBox: load(import("./Box"), "Box"),
52
53 foo: async () => (await import("./Bar")).Bar,
54
55 bar: Bar,
56
57 password: String,
58
59 user: String,
60
61 timeout: Number
62 };
63
29 64 export declare const config: ConfigBuilder<ContainerServices>;
65 config
66 .mapTo("bar", )
67 .mapTo("barBox", ;
@@ -1,93 +1,93
1 1 import { test } from "./TestTraits";
2 2 import { Container } from "../di/Container";
3 3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
4 4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
5 5 import { ValueDescriptor } from "../di/ValueDescriptor";
6 6 import { Foo } from "../mock/Foo";
7 7 import { Bar } from "../mock/Bar";
8 8 import { isNull } from "../safe";
9 9
10 10 test("Container register/resolve tests", async t => {
11 11 const container = new Container();
12 12
13 13 const connection1 = "db://localhost";
14 14
15 15 t.throws(
16 16 () => container.register("bla-bla", "bla-bla"),
17 17 "Do not allow to register anything other than descriptors"
18 18 );
19 19
20 20 t.doesNotThrow(
21 21 () => container.register("connection", new ValueDescriptor(connection1)),
22 22 "register ValueDescriptor"
23 23 );
24 24
25 25 t.equals(container.resolve("connection"), connection1, "resolve string value");
26 26
27 27 t.doesNotThrow(
28 28 () => container.register(
29 29 "dbParams",
30 30 new AggregateDescriptor({
31 31 timeout: 10,
32 32 connection: new ReferenceDescriptor({ name: "connection" })
33 33 })
34 34 ),
35 35 "register AggregateDescriptor"
36 36 );
37 37
38 38 const dbParams = container.resolve("dbParams");
39 39 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
40 40 });
41 41
42 42 test("Container configure/resolve tests", async t => {
43 43
44 44 const container = new Container();
45 45
46 46 await container.configure({
47 47 foo: {
48 48 $type: Foo
49 49 },
50 50
51 51 box: {
52 52 $type: Bar,
53 53 params: {
54 54 $dependency: "foo"
55 55 }
56 56 },
57 57
58 58 bar: {
59 59 $type: Bar,
60 60 params: {
61 61 db: {
62 62 provider: {
63 63 $dependency: "db"
64 64 }
65 65 }
66 66 }
67 67 }
68 68 });
69 69 t.pass("should configure from js object");
70 70
71 71 const f1 = container.resolve("foo");
72 72
73 73 t.assert(!isNull(f1), "foo should be not null");
74 74
75 75 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
76 76
77 77 });
78 78
79 79 test("Load configuration from module", async t => {
80 80 const container = new Container();
81 81
82 82 await container.configure("../mock/config1", { contextRequire: require });
83 83 t.pass("The configuration should load");
84 84
85 85 const f1 = container.resolve("foo");
86 86
87 87 t.assert(!isNull(f1), "foo should be not null");
88 88
89 89 const b1 = container.resolve("bar") as Bar;
90 90
91 91 t.assert(!isNull(b1), "bar should not be null");
92 t.assert(!isNull(b1.foo), "bar.foo should not be null");
92 t.assert(!isNull(b1._v), "bar.foo should not be null");
93 93 });
General Comments 0
You need to be logged in to leave comments. Login now