| @@ -0,0 +1,38 | |||
|
|
1 | import { primitive } from "../../safe"; | |
|
|
2 | import { ActivationType } from "../interfaces"; | |
|
|
3 | ||
|
|
4 | type ExtractService<K, S> = K extends keyof S ? S[K] : K; | |
|
|
5 | ||
|
|
6 | type ExtractDependency<D, S> = D extends { $dependency: infer K } ? | |
|
|
7 | D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : | |
|
|
8 | D extends { $type: new (...args: any[]) => infer I } ? I : | |
|
|
9 | D extends { $factory: (...args: any[]) => infer R } ? R : | |
|
|
10 | WalkDependencies<D, S>; | |
|
|
11 | ||
|
|
12 | type WalkDependencies<D, S> = D extends primitive ? D : | |
|
|
13 | { [K in keyof D]: ExtractDependency<D[K], S> }; | |
|
|
14 | ||
|
|
15 | export interface TypeBuilder<T, S extends object> { | |
|
|
16 | type<P extends any[], C extends new (...args: ExtractDependency<P, S>) => T>( | |
|
|
17 | target: C, ...params: P): ConstructorBuilder<C, S>; | |
|
|
18 | factory<P extends any[], F extends (...args: ExtractDependency<P, S>) => T>( | |
|
|
19 | target: F, ...params: P): FactoryBuilder<F, S>; | |
|
|
20 | } | |
|
|
21 | ||
|
|
22 | export interface ServiceBuilder<T, S extends object> { | |
|
|
23 | override<K extends keyof S>(name: K, builder: S[K] | ((t: TypeBuilder<S[K], S>) => any)): this; | |
|
|
24 | activate(activation: ActivationType): this; | |
|
|
25 | inject<M extends keyof T, P extends any[]>(member: T[M] extends (...params: ExtractDependency<P, S>) => any ? M : never, ...params: P): this; | |
|
|
26 | } | |
|
|
27 | ||
|
|
28 | export interface ConstructorBuilder<C extends new (...args: any[]) => any, S extends object> extends ServiceBuilder<InstanceType<C>, S> { | |
|
|
29 | $type: C; | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | export interface FactoryBuilder<F extends (...args: any[]) => any, S extends object> extends ServiceBuilder<ReturnType<F>, S> { | |
|
|
33 | $factory: F; | |
|
|
34 | } | |
|
|
35 | ||
|
|
36 | export interface ConfigBuilder<S extends object, Y extends keyof S = keyof S> { | |
|
|
37 | register<K extends Y>(name: K, builder: S[K] | ((t: TypeBuilder<S[K], S>) => any)): ConfigBuilder<S, Exclude<Y, K>>; | |
|
|
38 | } | |
| @@ -1,79 +1,79 | |||
|
|
1 | 1 | import { primitive } from "../safe"; |
|
|
2 | 2 | import { TypeRegistration, DependencyRegistration, LazyDependencyRegistration, Registration, StrictTypeRegistration } from "./Configuration"; |
|
|
3 | 3 | |
|
|
4 | 4 | export interface InjectOptions { |
|
|
5 | 5 | lazy?: boolean; |
|
|
6 | 6 | } |
|
|
7 | 7 | |
|
|
8 | 8 | type Compatible<T1, T2> = T2 extends T1 ? any : never; |
|
|
9 | 9 | |
|
|
10 | 10 | type ExtractService<K, S> = K extends keyof S ? S[K] : K; |
|
|
11 | 11 | |
|
|
12 | 12 | type ExtractDependency<D, S> = D extends { $dependency: infer K } ? |
|
|
13 | 13 | D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : |
|
|
14 | 14 | D extends { $type: new (...args: any[]) => infer I } ? I : |
|
|
15 | 15 | WalkDependencies<D, S>; |
|
|
16 | 16 | |
|
|
17 | 17 | type WalkDependencies<D, S> = D extends primitive ? D : |
|
|
18 | 18 | { [K in keyof D]: ExtractDependency<D[K], S> }; |
|
|
19 | 19 | |
|
|
20 | 20 | export class Builder<T, S extends object> { |
|
|
21 | 21 | declare<P extends any[]>(...args: P) { |
|
|
22 | 22 | return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => { |
|
|
23 | 23 | |
|
|
24 | 24 | }; |
|
|
25 | 25 | } |
|
|
26 | 26 | |
|
|
27 | 27 | inject<P extends any[]>(...args: P) { |
|
|
28 | 28 | return <X extends { [m in M]: (...args: any) => any }, M extends keyof (T | X)>( |
|
|
29 | 29 | target: X, |
|
|
30 | 30 | memberName: M, |
|
|
31 | 31 | descriptor: TypedPropertyDescriptor<Compatible<(...args: ExtractDependency<P, S>) => any, T[M]>> |
|
|
32 | 32 | ) => { |
|
|
33 | 33 | |
|
|
34 | 34 | }; |
|
|
35 | 35 | } |
|
|
36 | 36 | |
|
|
37 | 37 | getDescriptor(): TypeRegistration<new () => T, S> { |
|
|
38 | 38 | throw new Error(); |
|
|
39 | 39 | } |
|
|
40 | 40 | |
|
|
41 | 41 | } |
|
|
42 | 42 | |
|
|
43 | 43 | export interface DependencyOptions<T> { |
|
|
44 | 44 | optional?: boolean; |
|
|
45 | 45 | default?: T; |
|
|
46 | 46 | } |
|
|
47 | 47 | |
|
|
48 | 48 | export interface LazyDependencyOptions<T> extends DependencyOptions<T> { |
|
|
49 | 49 | lazy: true; |
|
|
50 | 50 | } |
|
|
51 | 51 | |
|
|
52 | 52 | interface Declaration<S extends object> { |
|
|
53 | 53 | define<T>(): Builder<T, S>; |
|
|
54 | 54 | |
|
|
55 | 55 | dependency<K extends keyof S>(name: K, opts: LazyDependencyOptions<S[K]>): LazyDependencyRegistration<S, K>; |
|
|
56 | 56 | dependency<K extends keyof S>(name: K, opts?: DependencyOptions<S[K]>): DependencyRegistration<S, K>; |
|
|
57 | 57 | |
|
|
58 |
$type<P extends any[], C extends new (...args: ExtractDependency<P, S>) => |
|
|
|
58 | $type<T, P extends any[], C extends new (...args: ExtractDependency<P, S>) => T>(target: C, ...params: P): StrictTypeRegistration<C, S>; | |
|
|
59 | 59 | |
|
|
60 | 60 | configure(): Config<S>; |
|
|
61 | 61 | } |
|
|
62 | 62 | |
|
|
63 | 63 | type ServiceModule<T, S extends object, M extends string = "service"> = { |
|
|
64 | 64 | [m in M]: Builder<T, S>; |
|
|
65 | 65 | }; |
|
|
66 | 66 | |
|
|
67 | 67 | type PromiseOrValue<T> = PromiseLike<T> | T; |
|
|
68 | 68 | |
|
|
69 | 69 | |
|
|
70 | 70 | export interface Config<S extends object, Y extends keyof S = keyof S> { |
|
|
71 | 71 | register<K extends Y>(name: K, m: { $from: Promise<ServiceModule<S[K], S>> }): Config<S, Exclude<Y, K>>; |
|
|
72 | 72 | register<K extends Y, M extends string>(name: K, m: { $from: Promise<ServiceModule<S[K], S, M>>, service: M }): Config<S, Exclude<Y, K>>; |
|
|
73 | 73 | |
|
|
74 | 74 | register<K extends Y>(name: K, m: Registration<S[K], S>): Config<S, Exclude<Y, K>>; |
|
|
75 | 75 | registerType<K extends Y, P extends any[]>( |
|
|
76 | 76 | name: K, $type: new (...args: ExtractDependency<P, S>) => S[K], ...params: P): Config<S, Exclude<Y, K>>; |
|
|
77 | 77 | } |
|
|
78 | 78 | |
|
|
79 | 79 | export declare function declare<S extends object>(): Declaration<S>; |
| @@ -1,35 +1,39 | |||
|
|
1 | 1 | import { Foo } from "./Foo"; |
|
|
2 | 2 | import { define, dependency } from "./services"; |
|
|
3 | 3 | |
|
|
4 | 4 | export const service = define<Bar>(); |
|
|
5 | 5 | |
|
|
6 | 6 | @service.declare({ |
|
|
7 | 7 | foo: dependency("foo"), |
|
|
8 | 8 | nested: { |
|
|
9 | 9 | lazy: dependency("foo", { lazy: true }) |
|
|
10 | 10 | }, |
|
|
11 | 11 | host: dependency("host") |
|
|
12 | 12 | }, "") |
|
|
13 | 13 | export class Bar { |
|
|
14 | 14 | barName = "bar"; |
|
|
15 | 15 | |
|
|
16 | 16 | _v: Foo | undefined; |
|
|
17 | 17 | |
|
|
18 | 18 | constructor(_opts: { |
|
|
19 | 19 | foo?: Foo; |
|
|
20 | 20 | nested?: { |
|
|
21 | 21 | lazy: () => Foo |
|
|
22 | 22 | }, |
|
|
23 | 23 | host: string |
|
|
24 | 24 | }, s: string) { |
|
|
25 | 25 | |
|
|
26 | 26 | if (_opts && _opts.foo) |
|
|
27 | 27 | this._v = _opts.foo; |
|
|
28 | 28 | } |
|
|
29 | 29 | |
|
|
30 | setName(name: string) { | |
|
|
31 | ||
|
|
32 | } | |
|
|
33 | ||
|
|
30 | 34 | getFoo() { |
|
|
31 | 35 | if (this._v === undefined) |
|
|
32 | 36 | throw new Error("The foo isn't set"); |
|
|
33 | 37 | return this._v; |
|
|
34 | 38 | } |
|
|
35 | 39 | } |
| @@ -1,23 +1,30 | |||
|
|
1 | 1 | import { configure, dependency, Services, $type } from "./services"; |
|
|
2 | 2 | import { Foo } from "./Foo"; |
|
|
3 | 3 | import { Bar } from "./Bar"; |
|
|
4 | 4 | import { Box } from "./Box"; |
|
|
5 | ||
|
|
5 | import { declare } from "../di/Annotations"; | |
|
|
6 | import { ConfigBuilder, TypeBuilder } from "../di/fluent/interfaces"; | |
|
|
6 | 7 | |
|
|
7 | export const config = configure() | |
|
|
8 | .register("bar", { $from: import("./Bar"), service: "service" }) | |
|
|
8 | export declare function build<T>(): TypeBuilder<T, Services>; | |
|
|
9 | ||
|
|
10 | export declare const config: ConfigBuilder<Services>; | |
|
|
11 | config | |
|
|
12 | //.register("bar", { $from: import("./Bar"), service: "service" }) | |
|
|
9 | 13 | // .register("box", { $from: import("./Box") }) |
|
|
10 | 14 |
|
|
|
11 | 15 | // .registerType("bar2", Bar, [{ foo: dependency("foo"), host: "" }]); |
|
|
12 |
.register("bar2", |
|
|
|
16 | .register("bar2", s => s.type(Bar, | |
|
|
13 | 17 | { |
|
|
14 |
foo: |
|
|
|
15 | .override("host", "foo.example.com") | |
|
|
16 | .inject("setName", dependency("host")) | |
|
|
18 | foo: build().type(Foo) | |
|
|
17 | 19 | .activate("context"), |
|
|
18 | host: "" | |
|
|
20 | nested: { lazy: dependency("foo", {lazy: true}) }, | |
|
|
21 | host: dependency("host") | |
|
|
19 | 22 | }, |
|
|
20 | 23 | "") |
|
|
24 | .inject("setName", dependency("host")) | |
|
|
21 | 25 | ) |
|
|
22 | .registerType("box", Box, dependency("bar")); | |
|
|
26 | .register("box", s => s | |
|
|
27 | .type(Box, dependency("bar")) | |
|
|
28 | .activate("context") | |
|
|
29 | ); | |
|
|
23 | 30 | |
| @@ -1,106 +1,107 | |||
|
|
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 | import { Box } from "ts/mock/Box"; |
|
|
10 | 10 | |
|
|
11 | 11 | test("Container register/resolve tests", async t => { |
|
|
12 | 12 | const container = new Container<{ |
|
|
13 | 13 | "bla-bla": string; |
|
|
14 | 14 | "connection": string; |
|
|
15 | 15 | "dbParams": { |
|
|
16 | 16 | timeout: number; |
|
|
17 | 17 | connection: string; |
|
|
18 | 18 | } |
|
|
19 | 19 | }>(); |
|
|
20 | 20 | |
|
|
21 | 21 | const connection1 = "db://localhost"; |
|
|
22 | 22 | |
|
|
23 | 23 | t.throws( |
|
|
24 | 24 | () => container.register("bla-bla", "bla-bla" as any), |
|
|
25 | 25 | "Do not allow to register anything other than descriptors" |
|
|
26 | 26 | ); |
|
|
27 | 27 | |
|
|
28 | 28 | t.doesNotThrow( |
|
|
29 | 29 | () => container.register("connection", new ValueDescriptor(connection1)), |
|
|
30 | 30 | "register ValueDescriptor" |
|
|
31 | 31 | ); |
|
|
32 | 32 | |
|
|
33 | 33 | t.equals(container.resolve("connection"), connection1, "resolve string value"); |
|
|
34 | 34 | |
|
|
35 | 35 | t.doesNotThrow( |
|
|
36 | 36 | () => container.register( |
|
|
37 | 37 | "dbParams", |
|
|
38 | 38 | new AggregateDescriptor({ |
|
|
39 | 39 | timeout: 10, |
|
|
40 | 40 | connection: new ReferenceDescriptor({ name: "connection" }) |
|
|
41 | 41 | }) |
|
|
42 | 42 | ), |
|
|
43 | 43 | "register AggregateDescriptor" |
|
|
44 | 44 | ); |
|
|
45 | 45 | |
|
|
46 | 46 | const dbParams = container.resolve("dbParams"); |
|
|
47 | 47 | t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'"); |
|
|
48 | 48 | }); |
|
|
49 | 49 | |
|
|
50 | 50 | test("Container configure/resolve tests", async t => { |
|
|
51 | 51 | |
|
|
52 | 52 | const container = new Container<{ |
|
|
53 | 53 | foo: Foo; |
|
|
54 | 54 | box: Bar; |
|
|
55 | 55 | bar: Bar; |
|
|
56 | db: any; | |
|
|
56 | 57 | }>(); |
|
|
57 | 58 | |
|
|
58 | 59 | await container.configure({ |
|
|
59 | 60 | foo: { |
|
|
60 | 61 | $type: Foo |
|
|
61 | 62 | }, |
|
|
62 | 63 | |
|
|
63 | 64 | box: { |
|
|
64 | 65 | $type: Bar, |
|
|
65 | 66 | params: { |
|
|
66 | 67 | $dependency: "foo" |
|
|
67 | 68 | } |
|
|
68 | 69 | }, |
|
|
69 | 70 | |
|
|
70 | 71 | bar: { |
|
|
71 | 72 | $type: Bar, |
|
|
72 | params: { | |
|
|
73 | params: [{ | |
|
|
73 | 74 | db: { |
|
|
74 | 75 | provider: { |
|
|
75 | 76 | $dependency: "db" |
|
|
76 | 77 | } |
|
|
77 | 78 | } |
|
|
78 | } | |
|
|
79 | }] | |
|
|
79 | 80 | } |
|
|
80 | 81 | }); |
|
|
81 | 82 | t.pass("should configure from js object"); |
|
|
82 | 83 | |
|
|
83 | 84 | const f1 = container.resolve("foo"); |
|
|
84 | 85 | |
|
|
85 | 86 | t.assert(!isNull(f1), "foo should be not null"); |
|
|
86 | 87 | |
|
|
87 | 88 | t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'"); |
|
|
88 | 89 | |
|
|
89 | 90 | }); |
|
|
90 | 91 | |
|
|
91 | 92 | test("Load configuration from module", async t => { |
|
|
92 | 93 | const container = new Container(); |
|
|
93 | 94 | |
|
|
94 | 95 | await container.configure("../mock/config1", { contextRequire: require }); |
|
|
95 | 96 | t.pass("The configuration should load"); |
|
|
96 | 97 | |
|
|
97 | 98 | const f1 = container.resolve("foo"); |
|
|
98 | 99 | |
|
|
99 | 100 | t.assert(!isNull(f1), "foo should be not null"); |
|
|
100 | 101 | |
|
|
101 | 102 | const b1 = container.resolve("bar") as Bar; |
|
|
102 | 103 | |
|
|
103 | 104 | t.assert(!isNull(b1), "bar should not be null"); |
|
|
104 | 105 | t.assert(!isNull(b1._v), "bar.foo should not be null"); |
|
|
105 | 106 | |
|
|
106 | 107 | }); |
General Comments 0
You need to be logged in to leave comments.
Login now
