##// END OF EJS Templates
improved interfaces and more tight type checking
cin -
r120:1b124b65514a ioc ts support
parent child
Show More
@@ -1,23 +1,23
1 1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
3 import { Descriptor, ServiceMap, PartialServiceMap } from "./interfaces";
2 import { argumentNotNull, argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerProvided, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
4 4 import { Container } from "./Container";
5 5 import { MapOf } from "../interfaces";
6 6
7 7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8 8
9 export interface ActivationContextInfo<S> {
9 export interface ActivationContextInfo<S extends object> {
10 10 name: string;
11 11
12 12 service: string;
13 13
14 scope: PartialServiceMap<S>;
14 scope: ContainerServiceMap<S>;
15 15 }
16 16
17 export class ActivationContext<S> {
17 export class ActivationContext<S extends object> {
18 18 _cache: MapOf<any>;
19 19
20 _services: PartialServiceMap<S>;
20 _services: ContainerServiceMap<S>;
21 21
22 22 _stack: ActivationContextInfo<S>[];
23 23
@@ -29,7 +29,7 export class ActivationContext<S> {
29 29
30 30 container: Container<S>;
31 31
32 constructor(container: Container<S>, services: PartialServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
32 constructor(container: Container<S>, services: ContainerServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
33 33 argumentNotNull(container, "container");
34 34 argumentNotNull(services, "services");
35 35
@@ -45,11 +45,11 export class ActivationContext<S> {
45 45 return this._name;
46 46 }
47 47
48 resolve<K extends keyof S, T extends S[K]>(name: K, def?: T): T {
48 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
49 49 const d = this._services[name];
50 50
51 51 if (d !== undefined) {
52 return this.activate(d as Descriptor<S, T>, name.toString());
52 return this.activate(d, name.toString());
53 53 } else {
54 54 if (def !== undefined && def !== null)
55 55 return def;
@@ -67,7 +67,7 export class ActivationContext<S> {
67 67 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
68 68 argumentNotEmptyString(name, "name");
69 69
70 this._services[name] = service;
70 this._services[name] = service as any;
71 71 }
72 72
73 73 clone() {
@@ -3,7 +3,7 import { ActivationContext } from "./Act
3 3 import { isPrimitive } from "../safe";
4 4 import { isDescriptor } from "./traits";
5 5
6 export class AggregateDescriptor<S, T> implements Descriptor<S, T> {
6 export class AggregateDescriptor<S extends object, T> implements Descriptor<S, T> {
7 7 _value: any;
8 8
9 9 constructor(value: any) {
@@ -26,10 +26,10 type ExtractDependency<D, S> = D extends
26 26 type WalkDependencies<D, S> = D extends primitive ? D :
27 27 { [K in keyof D]: ExtractDependency<D[K], S> };
28 28
29 export class Builder<T, S> {
29 export class Builder<T, S extends object> {
30 30 declare<P extends any[]>(...args: P) {
31 31 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
32 return constructor as C & { service: Builder<T, S> };
32
33 33 };
34 34 }
35 35
@@ -54,7 +54,7 export class Builder<T, S> {
54 54
55 55 }
56 56
57 interface Declaration<S> {
57 interface Declaration<S extends object> {
58 58 define<T>(): Builder<T, S>;
59 59
60 60 dependency<K extends keyof S>(name: K, opts: { lazy: true }): Lazy<K>;
@@ -63,13 +63,15 interface Declaration<S> {
63 63 config(): Config<S>;
64 64 }
65 65
66 interface ServiceModule<T, S> {
67 service: Builder<T, S>;
68 }
66 type ServiceModule<T, S extends object, M extends string = "service"> = {
67 [m in M]: Builder<T, S>;
68 };
69 69
70 interface Config<S> {
71 register<K extends keyof S>(name: K, builder: Builder<S[K], S>): Config<Omit<S, K>>;
72 register<K extends keyof S>(name: K, m: Promise<ServiceModule<S[K], S>>): Config<Omit<S, K>>;
70 export interface Config<S extends object, Y extends keyof S = keyof S> {
71 register<K extends Y>(name: K, builder: Builder<S[K], S>): Config<S, Exclude<Y, K>>;
72 register<K extends Y>(name: K, m: Promise<ServiceModule<S[K], S>>): Config<S, Exclude<Y, K>>;
73 register<K extends Y, M extends string>(name: K, m: Promise<ServiceModule<S[K], S, M>>, x: M): Config<S, Exclude<Y, K>>;
74
73 75 }
74 76
75 77 export declare function declare<S extends object>(): Declaration<S>;
@@ -1,9 +1,11
1 1 import {
2 2 PartialServiceMap,
3 ActivationType
3 ActivationType,
4 ContainerKeys,
5 ContainerResolve
4 6 } from "./interfaces";
5 7
6 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
8 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
7 9 import { AggregateDescriptor } from "./AggregateDescriptor";
8 10 import { ValueDescriptor } from "./ValueDescriptor";
9 11 import { Container } from "./Container";
@@ -14,23 +16,23 import { TraceSource } from "../log/Trac
14 16 import { ConfigError } from "./ConfigError";
15 17 import { Cancellation } from "../Cancellation";
16 18 import { makeResolver } from "./ResolverHelper";
17 import { ICancellation, Constructor, Factory } from "../interfaces";
19 import { ICancellation } from "../interfaces";
18 20 import { isDescriptor } from "./traits";
19 21
20 export interface RegistrationScope<S> {
22 export interface RegistrationScope<S extends object> {
21 23
22 24 /** сервисы, которые регистрируются в контексте активации и таким образом
23 25 * могут переопределять ранее зарегистрированные сервисы. за это свойство
24 26 * нужно платить, кроме того порядок активации будет влиять на результат
25 27 * разрешения зависимостей.
26 28 */
27 services?: PartialServiceMap<S>;
29 services?: RegistrationMap<S>;
28 30 }
29 31
30 32 /**
31 33 * Базовый интефейс конфигурации сервисов
32 34 */
33 export interface ServiceRegistration<T, P, S> extends RegistrationScope<S> {
35 export interface ServiceRegistration<T, P, S extends object> extends RegistrationScope<S> {
34 36
35 37 activation?: ActivationType;
36 38
@@ -41,13 +43,13 export interface ServiceRegistration<T,
41 43 cleanup?: ((instance: T) => void) | string;
42 44 }
43 45
44 export interface TypeRegistration<T, P extends any[], S> extends ServiceRegistration<T, P, S> {
46 export interface TypeRegistration<T, P extends any[], S extends object> extends ServiceRegistration<T, P, S> {
45 47 $type: string | (new (...params: P) => T);
46 48
47 49 }
48 50
49 export interface FactoryRegistration<T, P extends any[], S> extends ServiceRegistration<T, P, S> {
50 $factory: string | ( (...params: P) => T);
51 export interface FactoryRegistration<T, P extends any[], S extends object> extends ServiceRegistration<T, P, S> {
52 $factory: string | ((...params: P) => T);
51 53 }
52 54
53 55 export interface ValueRegistration<T> {
@@ -55,13 +57,27 export interface ValueRegistration<T> {
55 57 parse?: boolean;
56 58 }
57 59
58 export interface DependencyRegistration<S, K extends keyof S> extends RegistrationScope<S> {
60 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
59 61 $dependency: K;
60 62 lazy?: boolean;
61 63 optional?: boolean;
62 default?: S[K];
64 default?: ContainerResolve<S, K>;
63 65 }
64 66
67 export type Registration<T, S extends object> = T extends primitive ? T :
68 (
69 T |
70 { [k in keyof T]: Registration<T[k], S> } |
71 TypeRegistration<T, any, S> |
72 FactoryRegistration<T, any, S> |
73 ValueRegistration<any> |
74 DependencyRegistration<S, keyof S>
75 );
76
77 export type RegistrationMap<S extends object> = {
78 [k in keyof S]?: Registration<S[k], S>;
79 };
80
65 81 const _activationTypes: { [k in ActivationType]: number; } = {
66 82 singleton: 1,
67 83 container: 2,
@@ -82,7 +98,7 export function isValueRegistration(x: a
82 98 return (!isPrimitive(x)) && ("$value" in x);
83 99 }
84 100
85 export function isDependencyRegistration<S>(x: any): x is DependencyRegistration<S, keyof S> {
101 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
86 102 return (!isPrimitive(x)) && ("$dependency" in x);
87 103 }
88 104
@@ -112,11 +128,11 async function mapAll(data: any, map?: (
112 128
113 129 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
114 130
115 export class Configuration<S> {
131 export class Configuration<S extends object> {
116 132
117 133 _hasInnerDescriptors = false;
118 134
119 _container: Container<S>;
135 readonly _container: Container<S>;
120 136
121 137 _path: Array<string>;
122 138
@@ -152,13 +168,13 export class Configuration<S> {
152 168 );
153 169 }
154 170
155 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
171 async applyConfiguration(data: RegistrationMap<S>, contextRequire?: any, ct = Cancellation.none) {
156 172 argumentNotNull(data, "data");
157 173
158 174 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
159 175 }
160 176
161 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
177 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
162 178 trace.log("applyConfiguration");
163 179
164 180 this._configName = "$";
@@ -213,16 +229,13 export class Configuration<S> {
213 229 return this._require(moduleName);
214 230 }
215 231
216 async _visitRegistrations(data: any, name: string) {
232 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
217 233 this._enter(name);
218 234
219 235 if (data.constructor &&
220 236 data.constructor.prototype !== Object.prototype)
221 237 throw new Error("Configuration must be a simple object");
222 238
223 const o: PartialServiceMap<S> = {};
224 const keys = Object.keys(data);
225
226 239 const services = await mapAll(data, async (v, k) => {
227 240 const d = await this._visit(v, k.toString());
228 241 return isDescriptor(d) ? d : new AggregateDescriptor(d);
@@ -298,7 +311,7 export class Configuration<S> {
298 311 return v;
299 312 }
300 313
301 _makeServiceParams<T, P>(data: ServiceRegistration<T, P, S>) {
314 _makeServiceParams(data: ServiceRegistration<any, any, S>) {
302 315 const opts: any = {
303 316 owner: this._container
304 317 };
@@ -1,17 +1,17
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration } from "./Configuration";
6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 8 import { MapOf } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 10
11 11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12 12
13 export class Container<S = any> implements Resolver<S> {
14 readonly _services: PartialServiceMap<ContainerServices<S>>;
13 export class Container<S extends object = any> implements Resolver<S> {
14 readonly _services: ContainerServiceMap<S>;
15 15
16 16 readonly _cache: MapOf<any>;
17 17
@@ -41,7 +41,7 export class Container<S = any> implemen
41 41 return this._parent;
42 42 }
43 43
44 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T {
44 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K> {
45 45 trace.debug("resolve {0}", name);
46 46 const d = this._services[name];
47 47 if (d === undefined) {
@@ -53,7 +53,7 export class Container<S = any> implemen
53 53
54 54 const context = new ActivationContext<S>(this, this._services);
55 55 try {
56 return context.activate(d as Descriptor<S, T>, name.toString());
56 return context.activate(d, name.toString());
57 57 } catch (error) {
58 58 throw new ActivationError(name.toString(), context.getStack(), error);
59 59 }
@@ -63,7 +63,7 export class Container<S = any> implemen
63 63 /**
64 64 * @deprecated use resolve() method
65 65 */
66 getService<K extends keyof S, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T) {
66 getService<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
67 67 return this.resolve(name, def);
68 68 }
69 69
@@ -111,7 +111,7 export class Container<S = any> implemen
111 111 * The function which will be used to load a configuration or types for services.
112 112 *
113 113 */
114 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
114 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
115 115 const c = new Configuration<S>(this);
116 116
117 117 if (typeof (config) === "string") {
@@ -121,7 +121,7 export class Container<S = any> implemen
121 121 }
122 122 }
123 123
124 createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> {
124 createChildContainer<S2 extends object = S>(): Container<S & S2> {
125 125 return new Container<S & S2>(this as any);
126 126 }
127 127
@@ -1,11 +1,11
1 1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 2 import { argumentNotNull, oid } from "../safe";
3 3
4 export interface FactoryServiceDescriptorParams<S, T, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
4 export interface FactoryServiceDescriptorParams<S extends object, T, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
5 5 factory: (...args: P) => T;
6 6 }
7 7
8 export class FactoryServiceDescriptor<S, T, P extends any[]> extends ServiceDescriptor<S, T, P> {
8 export class FactoryServiceDescriptor<S extends object, T, P extends any[]> extends ServiceDescriptor<S, T, P> {
9 9 constructor(opts: FactoryServiceDescriptorParams<S, T, P>) {
10 10 super(opts);
11 11
@@ -1,30 +1,26
1 import { isNull, argumentNotEmptyString, each, keys } from "../safe";
1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 import { ServiceMap, Descriptor, PartialServiceMap } from "./interfaces";
3 import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces";
4 4 import { ActivationError } from "./ActivationError";
5 5
6 export interface ReferenceDescriptorParams<S, K extends keyof S> {
6 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 7 name: K;
8 8 lazy?: boolean;
9 9 optional?: boolean;
10 default?: S[K];
10 default?: ContainerResolve<S, K>;
11 11 services?: PartialServiceMap<S>;
12 12 }
13 13
14 function defined<T>(v: T | undefined) {
15 if (v === undefined)
16 throw Error();
17 return v;
18 }
14 export class ReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
15 implements Descriptor<S, ContainerResolve<S, K> | ((args?: PartialServiceMap<S>) => ContainerResolve<S, K>)> {
19 16
20 export class ReferenceDescriptor<S = any, K extends keyof S = keyof S> implements Descriptor<S, S[K] | ((args?: PartialServiceMap<S> ) => S[K])> {
21 17 _name: K;
22 18
23 19 _lazy = false;
24 20
25 21 _optional = false;
26 22
27 _default: S[K] | undefined;
23 _default: ContainerResolve<S, K> | undefined;
28 24
29 25 _services: PartialServiceMap<S>;
30 26
@@ -35,7 +31,7 export class ReferenceDescriptor<S = any
35 31 this._optional = !!opts.optional;
36 32 this._default = opts.default;
37 33
38 this._services = (opts.services || {}) as ServiceMap<S>;
34 this._services = (opts.services || {}) as PartialServiceMap<S>;
39 35 }
40 36
41 37 activate(context: ActivationContext<S>) {
@@ -9,7 +9,7 let cacheId = 0;
9 9
10 10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11 11
12 function injectMethod<T, M extends keyof T, S, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
12 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
13 13
14 14 const m = target[method];
15 15 if (!m || typeof m !== "function")
@@ -59,7 +59,7 export type InjectionSpec<T> = {
59 59 [m in keyof T]?: any;
60 60 };
61 61
62 export interface ServiceDescriptorParams<S, T, P extends any[]> {
62 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
63 63 activation?: ActivationType;
64 64
65 65 owner: Container<S>;
@@ -73,7 +73,7 export interface ServiceDescriptorParams
73 73 cleanup?: Cleaner<T>;
74 74 }
75 75
76 export class ServiceDescriptor<S, T, P extends any[]> implements Descriptor<S, T> {
76 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
77 77 _instance: T | undefined;
78 78
79 79 _hasInstance = false;
@@ -2,11 +2,11 import { ServiceDescriptor, ServiceDescr
2 2 import { Constructor, Factory } from "../interfaces";
3 3 import { argumentNotNull, isPrimitive } from "../safe";
4 4
5 export interface TypeServiceDescriptorParams<S, T extends object, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
5 export interface TypeServiceDescriptorParams<S extends object, T extends object, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
6 6 type: Constructor<T>;
7 7 }
8 8
9 export class TypeServiceDescriptor<S, T extends object, P extends any[]> extends ServiceDescriptor<S, T, P> {
9 export class TypeServiceDescriptor<S extends object, T extends object, P extends any[]> extends ServiceDescriptor<S, T, P> {
10 10 _type: Constructor;
11 11
12 12 constructor(opts: TypeServiceDescriptorParams<S, T, P>) {
@@ -1,21 +1,38
1 1 import { ActivationContext } from "./ActivationContext";
2 2
3 export interface Descriptor<S = any, T = any> {
3 export interface Descriptor<S extends object = any, T = any> {
4 4 activate(context: ActivationContext<S>): T;
5 5 }
6 6
7 export type ServiceMap<S> = {
7 export type ServiceMap<S extends object> = {
8 8 [k in keyof S]: Descriptor<S, S[k]>;
9 9 };
10 10
11 export type PartialServiceMap<S> = {
11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12
13 export type ContainerResolve<S extends object, K> =
14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 K extends keyof S ? S[K] : never;
16
17 export type ContainerServiceMap<S extends object> = {
18 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
19 };
20
21 export type PartialServiceMap<S extends object> = {
12 22 [k in keyof S]?: Descriptor<S, S[k]>;
13 23 };
14 24
15 export interface Resolver<S> {
16 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T;
25 export interface Resolver<S extends object> {
26 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
27 }
28
29 export interface ContainerProvided<S extends object> {
30 container: Resolver<S>;
17 31 }
18 export type ContainerServices<S> = S & {
19 container: Resolver<S>;
20 };
32
33 export type ContainerRegistered<S extends object> = /*{
34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
35 };*/
36 Exclude<S, ContainerProvided<S>>;
37
21 38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
@@ -4,4 +4,4 import { Descriptor } from "./interfaces
4 4 export function isDescriptor(x: any): x is Descriptor {
5 5 return (!isPrimitive(x)) &&
6 6 (x.activate instanceof Function);
7 }
7 } No newline at end of file
@@ -2,5 +2,5 import { config } from "./services";
2 2
3 3 config()
4 4 .register("bar", import("./Bar"))
5 .register("box", import("./Box"))
6 .register("foo", import("./Foo"), "Foo");
5 .register("box", import("./Box"), "service");
6 //.register("foo", import("./Foo"), "Foo");
@@ -6,10 +6,17 import { ValueDescriptor } from "../di/V
6 6 import { Foo } from "../mock/Foo";
7 7 import { Bar } from "../mock/Bar";
8 8 import { isNull } from "../safe";
9 import { Descriptor } from "../di/interfaces";
9 import { Box } from "ts/mock/Box";
10 10
11 11 test("Container register/resolve tests", async t => {
12 const container = new Container();
12 const container = new Container<{
13 "bla-bla": string;
14 "connection": string;
15 "dbParams": {
16 timeout: number;
17 connection: string;
18 }
19 }>();
13 20
14 21 const connection1 = "db://localhost";
15 22
@@ -42,7 +49,11 test("Container register/resolve tests",
42 49
43 50 test("Container configure/resolve tests", async t => {
44 51
45 const container = new Container();
52 const container = new Container<{
53 foo: Foo;
54 box: Bar;
55 bar: Bar;
56 }>();
46 57
47 58 await container.configure({
48 59 foo: {
General Comments 0
You need to be logged in to leave comments. Login now