##// END OF EJS Templates
almost woking typings
cin -
r9:988f0f6aab67 default
parent child
Show More
@@ -1,3 +1,6
1 1 {
2 "java.configuration.updateBuildConfiguration": "automatic"
2 "java.configuration.updateBuildConfiguration": "automatic",
3 "cSpell.words": [
4 "linkcode"
5 ]
3 6 } No newline at end of file
@@ -1,139 +1,144
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { LifetimeManager } from "./LifetimeManager";
1 import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager } from "./interfaces";
3 2 import { argumentNotNull } from "./traits";
4 3
5 4 export interface ActivationContextInfo {
6 5 name: string;
7 6
8 7 service: string;
9 8
10 9 }
11 10
12 11 let nextId = 1;
13 12
14 /** This class is created once per `Container.resolve` method call and used to
13 /** This object is created once per `Container.resolve` method call and used to
15 14 * cache dependencies and to track created instances. The activation context
16 15 * tracks services with `context` activation type.
16 *
17 * @template S The service map used in the activation context, services from
18 * this map are available to resolution.
19 * @template U A set of keys from the service map which can be overridden in
20 * this activation context.
17 21 */
18 export class ActivationContext<S extends object> {
22 export class ActivationContext<S> implements IActivationContext<S> {
19 23 private readonly _cache: Record<string, unknown>;
20 24
21 private readonly _services: Partial<RegistrationMap<S>>;
25 private readonly _services: DescriptorMap<S>;
22 26
23 27 private readonly _name: string;
24 28
25 29 private readonly _service: Descriptor<S, unknown>;
26 30
27 private readonly _containerLifetimeManager: LifetimeManager;
31 private readonly _containerLifetimeManager: ILifetimeManager;
28 32
29 33 private readonly _parent: ActivationContext<S> | undefined;
30 34
31 35 /** Creates a new activation context with the specified parameters.
32 36 * @param containerLifetimeManager the container which starts the activation process
33 37 * @param services the initial service registrations
34 38 * @param name the name of the service being activated, this parameter is
35 39 * used for the debug purpose.
36 40 * @param service the service to activate, this parameter is used for the
37 41 * debug purpose.
38 42 */
39 constructor(containerLifetimeManager: LifetimeManager, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>, cache = {}) {
43 constructor(containerLifetimeManager: ILifetimeManager, services: DescriptorMap<S>, name: string, service: Descriptor<S, unknown>, cache = {}) {
40 44 this._name = name;
41 45 this._service = service;
42 46 this._cache = cache;
43 47 this._services = services;
44 48 this._containerLifetimeManager = containerLifetimeManager;
45 49 }
46 50
47 51 /** the name of the current resolving dependency */
48 52 getName() {
49 53 return this._name;
50 54 }
51 55
52 56 createContainerLifetime<T>() {
53 57 return this._containerLifetimeManager.create<T>();
54 58 }
55 59
56 /** Resolves the specified dependency in the current context
57 * @param name The name of the dependency being resolved
58 */
59 60 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
60 /** Resolves the specified dependency with the specified default value if
61 * the dependency is missing.
62 *
63 * @param name The name of the dependency being resolved
64 * @param def A default value to return in case of the specified dependency
65 * is missing.
66 */
67 61 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
68 resolve<K extends keyof S, T>(name: K, def?: T): S[K] | T | undefined {
62 resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined {
69 63 const d = this._services[name];
70 64
71 65 if (d !== undefined) {
72 66 return this.activate(d, name.toString());
73 67 } else {
74 68 if (arguments.length > 1)
75 69 return def;
76 70 else
77 71 throw new Error(`Service ${String(name)} not found`);
78 72 }
79 73 }
80 74
81 75 /**
82 76 * registers services local to the the activation context
83 77 *
84 78 * @name{string} the name of the service
85 79 * @service{string} the service descriptor to register
86 80 */
87 register<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
81 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]) {
88 82 argumentNotNull(name, "name");
89 83
84 const d = this._services[name];
85 if (d !== undefined && !d.configurable)
86 throw new Error(`Service ${String(name)} can't be overridden`);
87
90 88 this._services[name] = service;
91 89 }
92 90
93 91 createLifetime<T>(): ILifetime<T> {
94 92 const id = nextId++;
95 93 return {
96 initialize() {},
94 initialize() { },
97 95 has: () => id in this._cache,
98 get: () => this._cache[id] as T,
96 get: () => {
97 const v = this._cache[id] as T;
98 if (v === undefined || v === null)
99 throw new Error("The value isn't present in the activation context");
100 return v;
101 },
99 102 store: item => {
100 103 this._cache[id] = item;
101 104 }
102 105 };
103 106 }
104 107
105 108 activate<T>(d: Descriptor<S, T>, name: string) {
106 109 // TODO: add logging
107 110 // if (trace.isLogEnabled())
108 111 // trace.log("enter {0} {1}", name, d);
109 112
110 113 const ctx = this.enter(d, name);
111 114 const v = d.activate(ctx);
112 115
113 116 // if (trace.isLogEnabled())
114 117 // trace.log(`leave ${name}`);
115 118
116 119 return v;
117 120 }
118 121
119 122 getStack(): ActivationContextInfo[] {
120 123 const stack = [{
121 124 name: this._name,
122 125 service: this._service.toString()
123 126 }];
124 127
125 128 return this._parent ?
126 129 stack.concat(this._parent.getStack()) :
127 130 stack;
128 131 }
129 132
130 133 private enter(service: Descriptor<S, unknown>, name: string) {
131 134 return new ActivationContext(
132 135 this._containerLifetimeManager,
133 Object.create(this._services) as typeof this._services,
136 service.hasOverrides ?
137 Object.create(this._services) as typeof this._services :
138 this._services,
134 139 name,
135 140 service,
136 141 this._cache
137 142 );
138 143 }
139 144 }
@@ -1,41 +1,60
1 1 export interface ActivationItem {
2 2 name: string;
3 3 service: string;
4 4 }
5 5
6 /**
7 * Contains information about the error which occurred during service activation.
8 *
9 * Information about activation error includes original exception which has
10 * occurred, the name of the service being activated and activation stack of
11 * services.
12 */
6 13 export class ActivationError {
14 /**
15 * Stack of services being activating
16 */
7 17 readonly activationStack: ActivationItem[];
8 18
19 /**
20 * The name of the failed service
21 */
9 22 readonly service: string;
10 23
24 /**
25 * The exception which occurred during activation of the service
26 */
11 27 readonly innerException: unknown;
12 28
29 /**
30 * Error message
31 */
13 32 readonly message: string;
14 33
15 34 constructor(service: string, activationStack: ActivationItem[], innerException: unknown) {
16 35 this.message = "Failed to activate the service";
17 36 this.activationStack = activationStack;
18 37 this.service = service;
19 38 this.innerException = innerException;
20 39 }
21 40
22 41 toString() {
23 42 const parts = [this.message];
24 43 if (this.service)
25 44 parts.push(`when activating: ${String(this.service)}`);
26 45
27 46 if (this.innerException)
28 47 parts.push(`caused by: ${String(this.innerException)}`);
29 48
30 49 if (this.activationStack) {
31 50 parts.push("at");
32 51 parts.push.apply(null,
33 52 this.activationStack
34 53 .map(({ name, service }) => ` ${name} ${service}`)
35 54 );
36 55
37 56 }
38 57
39 58 return parts.join("\n");
40 59 }
41 60 }
@@ -1,54 +1,74
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ActivationError } from "./ActivationError";
3 3 import { ContainerBuilder } from "./ContainerBuilder";
4 import { RegistrationMap, ServiceContainer, ContainerServices, ServiceLocator, IContainerBuilder, Configurable, ContainerKeys} from "./interfaces";
5 import { LifetimeManager } from "./LifetimeManager";
4 import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces";
6 5
7 export class Container<S extends Configurable<S>> implements ServiceContainer<S> {
8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
6 export class Container<S> implements ServiceLocator<S>, IDestroyable {
7 private readonly _services: DescriptorMap<S>;
9 8
10 private readonly _lifetimeManager: LifetimeManager;
9 private readonly _lifetimeManager: ILifetimeManager;
11 10
12 11 private _disposed: boolean;
13 12
14 constructor(services: Partial<RegistrationMap<ContainerServices<S>>>, lifetimeManager: LifetimeManager) {
15 this._services = services;
16 this._services.container = { activate: () => this as ContainerServices<S>["container"]};
17 this._services.childContainer = { activate: () => this.createChildContainer() as ContainerServices<S>["childContainer"] };
13 private readonly _onDestroyed: () => void;
14
15 constructor(services: DescriptorMap<S>, lifetimeManager: ILifetimeManager, destroyed: () => void) {
16 this._services = {
17 ...services,
18 container: {
19 configurable: false,
20 activate: () => this
21 },
22 childContainer: {
23 configurable: false,
24 activate: () => this.createChildBuilder(),
25 }
26 };
27
18 28 this._disposed = false;
19 29 this._lifetimeManager = lifetimeManager;
30 this._onDestroyed = destroyed;
31
20 32 }
21 33
22 createChildContainer(): IContainerBuilder<S> {
23 return new ContainerBuilder(this._services);
34 private _assertNotDestroyed() {
35 if (this._disposed)
36 throw new Error("The container is destroyed");
24 37 }
25 38
26 resolve<K extends keyof ContainerServices<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
27 resolve<K extends keyof ContainerServices<S>, T>(name: K, def: T): NonNullable<ContainerServices<S>[K]> | T;
28 resolve<K extends keyof ContainerServices<S>, T>(name: K, def?: T) {
29 // TODO: add logging
30 // trace.debug("resolve {0}", name);
39 createChildBuilder(): IContainerBuilder<S, keyof S> {
40 this._assertNotDestroyed();
41
42 const lifetime = this._lifetimeManager.create<IDestroyable>();
43
44 return new ContainerBuilder(this._services, lifetime);
45 }
46
47 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
48 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
49 resolve<K extends keyof S, T>(name: K, def?: T) {
50 this._assertNotDestroyed();
51
31 52 const d = this._services[name];
32 53 if (d === undefined) {
33 54 if (arguments.length > 1)
34 55 return def;
35 56 else
36 57 throw new Error(`Service '${String(name)}' isn't found`);
37 58 } else {
38
39 59 const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d);
40 60 try {
41 61 return d.activate(context);
42 62 } catch (error) {
43 63 throw new ActivationError(name.toString(), context.getStack(), error);
44 64 }
45 65 }
46 66 }
47
48 67 destroy() {
49 68 if (this._disposed)
50 69 return;
51 70 this._disposed = true;
52 71 this._lifetimeManager.destroy();
72 (0,this._onDestroyed)();
53 73 }
54 74 }
@@ -1,46 +1,67
1 1 import { Container } from "./Container";
2 2 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { Configurable, ConfigurableKeys, ContainerServices, Descriptor, IContainerBuilder, IDescriptorBuilder, RegistrationMap, ServiceContainer } from "./interfaces";
4 import { LifetimeManager } from "./LifetimeManager";
3 import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces";
4 import { emptyLifetime, LifetimeManager } from "./LifetimeManager";
5 import { isDestroyable } from "./traits";
5 6
6 export class ContainerBuilder<S extends Configurable<S>> implements IContainerBuilder<S>{
7 /**
8 * Container builder used to prepare service descriptors and create a IoC container
9 */
10 export class ContainerBuilder<S, U extends keyof S> implements
11 IContainerBuilder<S, U> {
7 12
8 13 private _pending = 1;
9 14
10 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
15 private readonly _services: DescriptorMap<S>;
11 16
12 17 private readonly _lifetimeManager = new LifetimeManager();
13 18
14 constructor(parentServices?: object) {
15 this._services = Object.create(parentServices ? parentServices : null) as object;
19 private readonly _lifetime: ILifetime<IDestroyable>;
20
21 constructor(parentServices?: DescriptorMap<S>, lifetime?: ILifetime<IDestroyable>) {
22 this._services = Object.create(parentServices ? parentServices : null) as DescriptorMap<S>;
23 this._lifetimeManager = new LifetimeManager();
24 this._lifetime = lifetime ?? emptyLifetime();
16 25 }
26 createServiceBuilder<K extends U>(name: K):
27 IDescriptorBuilder<S, S[K], Record<never, never>, U> {
17 28
18 createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<ContainerServices<S>, NonNullable<S[K]>, object, keyof S> {
19 29 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
30
20 31 }
21 32
22 build(): ServiceContainer<S> {
33 build(): ServiceLocator<S> {
23 34 this._assertBuilding();
24 if(!this._complete())
35 if (!this._complete())
25 36 throw new Error("The configuration didn't complete.");
26 return new Container(this._services, this._lifetimeManager);
37
38 const lifetime = this._lifetime;
39
40 const detach = isDestroyable(lifetime) ? () => lifetime.destroy() : () => void (0);
41
42 const container = new Container(this._services, this._lifetimeManager, detach);
43 lifetime.store(container);
44
45 return container;
27 46 }
28 47
29 private readonly _register = <K extends ConfigurableKeys<S>>(name: K) => (descriptor: Descriptor<S, NonNullable<S[K]>>) => {
30 this._complete();
31 this._services[name] = descriptor;
32 };
48 private readonly _register = <K extends U>(name: K) =>
49 (descriptor: Descriptor<S, S[K]>) => {
50 this._complete();
51 this._services[name] = descriptor;
52 };
33 53
34 54 private readonly _fail = (ex: unknown) => {
35
55 throw ex;
36 56 };
37 57
38 58 private _assertBuilding() {
39 throw new Error("The descriptor builder is finalized");
59 if (!this._pending)
60 throw new Error("The descriptor builder is finalized");
40 61 }
41 62
42 63 private _complete() {
43 64 return !(--this._pending);
44 65 }
45 66
46 67 } No newline at end of file
@@ -1,189 +1,193
1 import { RegistrationBuilder, LifetimeContainer, ConfigurableKeys, IDescriptorBuilder, Ref, Resolved, DepsMap } from "./interfaces";
1 import { BuildDescriptorFn, IDescriptorBuilder, DepsMap, Resolve, DescriptorMap } from "./interfaces";
2 2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
3 import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
4 import { LifetimeManager } from "./LifetimeManager";
5 import { each, isKey, isPromise, isString, key, oid } from "./traits";
3 import { DescriptorImpl } from "./DescriptorImpl";
4 import { contextLifetime, emptyLifetime, hierarchyLifetime, LifetimeManager, singletonLifetime } from "./LifetimeManager";
5 import { each, isPromise, isString, key, oid } from "./traits";
6 6
7 7 /**
8 8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
9 9 * @template {T} Тип сервиса
10 10 */
11 export class DescriptorBuilder<S extends object, T, R extends object, O extends keyof S> implements IDescriptorBuilder<S, T, R, O> {
11 export class DescriptorBuilder<S, T, R, U extends keyof S> implements IDescriptorBuilder<S, T, R, U> {
12 12 private readonly _lifetimeManager: LifetimeManager;
13 13 private readonly _cb: (d: Descriptor<S, T>) => void;
14 14
15 15 private readonly _eb: (err: unknown) => void;
16 16
17 private readonly _refs: DepsMap<key, keyof S>;
17 private readonly _refs: DepsMap<R>;
18 18
19 private _lifetime = LifetimeManager.empty<T>();
19 private _lifetime = emptyLifetime<T>();
20 20
21 private _overrides: RegistrationOverridesMap<S>;
21 private _overrides: DescriptorMap<S>;
22 22
23 23 private _cleanup?: (item: T) => void;
24 24
25 private _factory?: (refs: R) => T;
25 private _factory?: (refs: R) => NonNullable<T>;
26 26
27 27 private _pending = 1;
28 28
29 29 private _failed = false;
30 30
31 31 private _finalized = false;
32 32
33 33 /**
34 34 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
35 35 * lifetime.
36 36 *
37 37 * @param lifetimeManager The lifetime container is the container where the service is to be registered.
38 38 * @param cb The callback to receive the built service descriptor
39 39 * @param eb The callback to receive the error due
40 40 */
41 41 constructor(lifetimeManager: LifetimeManager, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
42 42 this._lifetimeManager = lifetimeManager;
43 43 this._cb = cb;
44 44 this._eb = eb;
45 45 this._overrides = {};
46 46 this._refs = {};
47 47 }
48 wants<X extends { [k in Exclude<key, keyof R>]: keyof S | Ref<keyof S, boolean, unknown>; }>(refs: X):
49 IDescriptorBuilder<S, T, R & {
50 [k in keyof X]:
51 X[k] extends keyof S ? NonNullable<S[X[k]]> :
52 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
53 never;
54 }, O> {
48
49 /** Declares dependencies to be consumed in the factory method */
50 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
51 IDescriptorBuilder<S, T, R & { [k in keyof X]: Resolve<S, X[k]>; }, U> {
55 52
56 53 each(refs, (v, k) => this._refs[k] = v);
57 54
58 55 return this as IDescriptorBuilder<S, T, R & {
59 [k in keyof X]:
60 X[k] extends keyof S ? NonNullable<S[X[k]]> :
61 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
62 never;
63 }, O>;
56 [k in keyof X]: Resolve<S, X[k]>;
57 }, U>;
64 58 }
65 factory(f: (refs: R) => T): void {
59
60 /** Registers a factory method for the service */
61 factory(f: (refs: R) => NonNullable<T>): void {
66 62 this._assertBuilding();
67 63 this._factory = f;
68 64 this._finalize();
69 65 this._complete();
70 66 }
71 67
72 68
73 69 private _assertBuilding() {
74 70 if (this._finalized)
75 71 throw new Error("The descriptor builder is finalized");
76 72 }
77 73
78 74 private _finalize() {
79 75 this._finalized = true;
80 76 }
81 77
82 override<K extends O>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
83 override<K extends O>(services: { [k in K]: RegistrationBuilder<S, NonNullable<S[k]>> }): this;
84 override<K extends O>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }, builder?: RegistrationBuilder<S, NonNullable<S[K]>>): this {
78 override<K extends U>(name: K, builder: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this;
79 override<K extends U>(services: { [k in K]: BuildDescriptorFn<S, NonNullable<S[k]>, U> }): this;
80 override<K extends U>(nameOrServices: K | { [name in K]: BuildDescriptorFn<S, NonNullable<S[K]>, U> }, builder?: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this {
85 81 this._assertBuilding();
86 82 const guard = (v: void | Promise<void>) => {
87 83 if (isPromise(v))
88 84 v.catch(err => this._fail(err));
89 85 };
90 86
91 if (isKey(nameOrServices)) {
87 if (typeof nameOrServices !== "object") {
92 88 if (builder) {
93 89 this._defer();
94 const d = new DescriptorBuilder<S, NonNullable<S[K]>, object, O>(
90 const d = new DescriptorBuilder<S, NonNullable<S[K]>, object, U>(
95 91 this._lifetimeManager,
96 92 result => {
97 93 this._overrides[nameOrServices] = result;
98 94 this._complete();
99 95 },
100 96 err => this._fail(err)
101 97 );
102 98
103 99 try {
104 100 guard(builder(d));
105 101 } catch (err) {
106 102 this._fail(err);
107 103 }
108 104 }
109 105 } else {
110 106 each(nameOrServices, (v, k) => this.override(k, v));
111 107 }
112 108 return this;
113 109 }
114 110
111 /** Specified the singleton lifetime for the service */
115 112 lifetime(lifetime: "singleton", typeId: string): this;
113 /**
114 * Specifies the lifetime for the service, either {@linkcode ILifetime<T>}
115 * object or {@linkcode ActivationType} literal.
116 * @param lifetime
117 */
116 118 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
117 119 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
118 120 this._assertBuilding();
119 121 if (isString(lifetime)) {
120 122 this._lifetime = this._resolveLifetime(lifetime, typeId);
121 123 } else {
122 124 this._lifetime = lifetime;
123 125 }
124 126 return this;
125 127 }
126 128
129 /** Registers cleanup callback, used when lifetime of the instance is managed
130 * by the container or some external mechanism
131 */
127 132 cleanup(cb: (item: T) => void): this {
128 133 this._assertBuilding();
129 134 this._cleanup = cb;
130 135 return this;
131 136 }
132 137
133 value(v: T): void {
138 /** Registers a value as the instance of the service */
139 value(v: NonNullable<T>): void {
134 140 this._assertBuilding();
135 141 this._cb({
136 142 activate() {
137 143 return v;
138 144 }
139 145 });
140 146 this._finalize();
141 147 }
142 148
143 149 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
144 150 switch (activation) {
145 151 case "container":
146 152 return this._lifetimeManager.create();
147 153 case "hierarchy":
148 return LifetimeManager.hierarchyLifetime();
154 return hierarchyLifetime();
149 155 case "context":
150 return LifetimeManager.contextLifetime();
156 return contextLifetime();
151 157 case "singleton": {
152 158 if (!typeId)
153 159 throw Error("The singleton activation requires a typeId");
154
155 160 const _oid = isString(typeId) ? typeId : oid(typeId);
156
157 return LifetimeManager.singletonLifetime(_oid);
161 return singletonLifetime(_oid);
158 162 }
159 163 default:
160 return LifetimeManager.empty();
164 return emptyLifetime();
161 165 }
162 166 }
163 167
164 168 _defer() {
165 169 this._pending++;
166 170 }
167 171
168 172 _complete() {
169 173 if (--this._pending === 0) {
170 174 if (!this._factory)
171 175 throw new Error("The factory must be specified");
172 176
173 177 this._cb(new DescriptorImpl<S, T>({
174 178 lifetime: this._lifetime,
175 factory: this._factory,
179 factory: this._factory as (refs: Record<key, unknown>) => NonNullable<T>,
176 180 overrides: this._overrides,
177 181 cleanup: this._cleanup
178 182 }));
179 183 }
180 184 }
181 185
182 186 _fail(err: unknown) {
183 187 if (!this._failed) {
184 188 this._failed = true;
185 189 this._eb.call(undefined, err);
186 190 }
187 191 }
188 192
189 193 }
@@ -1,82 +1,84
1 import { Descriptor, ILifetime, DepsMap, Ref, IActivationContext } from "./interfaces";
2 import { each, isKey, key } from "./traits";
1 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
2 import { each, key } from "./traits";
3 3
4 export type RegistrationOverridesMap<S extends object> = { [k in keyof S]?: Descriptor<S, NonNullable<S[k]>> };
5
6 export interface DescriptorImplArgs<S extends object, T> {
4 export interface DescriptorImplArgs<S, T> {
7 5 lifetime: ILifetime<T>;
8 6
9 factory: (refs: Record<key, never>) => T;
7 factory: (refs: Record<key, unknown>) => NonNullable<T>;
10 8
11 cleanup?: (item: T) => void;
9 cleanup?: (item: NonNullable<T>) => void;
12 10
13 overrides?: RegistrationOverridesMap<S>;
11 overrides?: DescriptorMap<S>;
14 12
15 dependencies?: DepsMap<key, keyof S>;
13 dependencies?: DepsMap<S>;
16 14 }
17 15
18 16
19 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
17 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
20 18
21 private readonly _overrides?: RegistrationOverridesMap<S>;
19 private readonly _overrides?: DescriptorMap<S>;
22 20
23 21 private readonly _lifetime: ILifetime<T>;
24 22
25 private readonly _factory: (refs: Record<key, never>) => T;
23 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
24
25 private readonly _cleanup?: (item: NonNullable<T>) => void;
26 26
27 private readonly _cleanup?: (item: T) => void;
27 private readonly _deps?: DepsMap<S>;
28 28
29 private readonly _deps?: DepsMap<key, keyof S>;
29 readonly hasOverrides: boolean;
30 30
31 31 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
32 32 this._lifetime = lifetime;
33 33 this._factory = factory;
34 34 if (cleanup)
35 35 this._cleanup = cleanup;
36 36 if (overrides)
37 37 this._overrides = overrides;
38 38 if (dependencies)
39 39 this._deps = dependencies;
40
41 this.hasOverrides = !!overrides;
40 42 }
41 43
42 activate(context: IActivationContext<S>): T {
44 activate(context: IActivationContext<S>): NonNullable<T> {
43 45
44 46 if (this._lifetime.has())
45 47 return this._lifetime.get();
46 48
47 49 this._lifetime.initialize(context);
48 50
49 51 if (this._overrides)
50 52 each(this._overrides, (v, k) => context.register(k, v));
51 53
52 const resolve = <K extends keyof S, L extends boolean, D = never>({ name, lazy, ...opts }: Ref<K, L, D>) => {
54 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => {
53 55 if (lazy) {
54 56 return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
55 57 } else {
56 58 return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
57 59 }
58 60 };
59 61
60 62 const makeRefs = (deps: typeof this._deps) => deps ?
61 63 Object.keys(deps)
62 64 .map(k => {
63 65 const ref = deps[k];
64 return isKey(ref) ?
66 return typeof ref !== "object" ?
65 67 { [k]: resolve({ name: ref }) } :
66 68 { [k]: resolve(ref) };
67 69 })
68 .reduce((a, p) => ({ ...a, ...p }), {} ) as Record<key, never>:
69 {} as Record<key, never>;
70 .reduce((a, p) => ({ ...a, ...p }), {} ):
71 {};
70 72
71 73 const instance = this._factory.call(undefined, makeRefs(this._deps));
72 74
73 75 this._lifetime.store(instance, this._cleanup);
74 76
75 77 return instance;
76 78 }
77 79
78 80
79 81 toString() {
80 82 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
81 83 }
82 84 }
@@ -1,71 +1,72
1 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces";
3 import { ServiceContainer } from "./interfaces";
1 import { ConfigurationMap, ConfigurationMapConstraint, ContainerServices, ContainerServicesConstraint, ExtractRequiredKeys, IContainerBuilder } from "./interfaces";
4 2 import { argumentNotNull, each, isKey } from "./traits";
5 3
6 export class FluentConfiguration<S, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> {
4 type ContainerExtensionConstraint<X, S> = ContainerServicesConstraint<Pick<S, keyof X & keyof S>>;
7 5
8 private _builders: Partial<RegistrationBuildersMap<S>> = {};
6 export class FluentConfiguration<S extends ContainerServicesConstraint<S>, Y extends keyof S = keyof S> {
7
8 private _builders: Partial<ConfigurationMap<ContainerServices<S>, keyof S, keyof S>> = {};
9 9
10 10 /** Adds a declaration of the services to the current config.
11 11 *
12 12 * @template D The map of the services
13 13 * @returns self
14 14 */
15 declare<D extends Pick<S, keyof D & keyof S>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> {
16 return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>;
15 declare<D extends ContainerExtensionConstraint<D,S>>(): FluentConfiguration<S & D, Y | keyof D> {
16 return this as unknown as FluentConfiguration<S & D, Y | keyof D>;
17 17 }
18 18
19 19 /** Adds compile-time information about the already provided services
20 20 *
21 21 * @template P The map of the provided services
22 22 * @returns self
23 23 */
24 24 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
25 return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
25 return this as unknown as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
26 26 }
27 27
28 28 /** Register the service.
29 29 *
30 30 * @param name The name of the service
31 31 * @param builder The service builder
32 32 * @returns self
33 33 */
34 register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
34 register<K extends Y>(name: K, builder: ConfigurationMap<ContainerServices<S>,Y, keyof S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
35 35 /** Registers the collection of services
36 36 * @param config The collection of services to register.
37 37 * @returns self
38 38 */
39 register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>;
40 register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) {
39 register<X extends ConfigurationMapConstraint<ContainerServices<S>, Y, keyof X>>(config: X): FluentConfiguration<S, Exclude<Y, keyof X>>;
40 register<K extends Y>(nameOrConfig: K | ConfigurationMap<ContainerServices<S>, K, keyof S>, builder?: ConfigurationMap<ContainerServices<S>, Y, keyof S>[K]) {
41 41 if (isKey(nameOrConfig)) {
42 42 argumentNotNull(builder, "builder");
43 43 this._builders[nameOrConfig] = builder;
44 44 } else {
45 45 each(nameOrConfig, (v, k) => this.register(k, v));
46 46 }
47 47
48 return this as FluentConfiguration<S, Exclude<Y, K>>;
48 return this; // as FluentConfiguration<S, Exclude<Y, K>>;
49 49 }
50 50
51 51 /**
52 52 * This method is used to enable a compile time check of the configuration.
53 53 * If there are not configured services in the configuration the compiler
54 54 * will trigger the error.
55 55 *
56 56 * @param missing Empty object literal should always be specified.
57 57 * @returns self
58 58 */
59 59 // eslint-disable-next-line @typescript-eslint/no-unused-vars
60 done<M extends ExtractRequired<S,Y>>(missing: M) {
60 done(...args: ExtractRequiredKeys<S, Y> extends never ? [] : [services: {[ k in ExtractRequiredKeys<S, Y>]: "required"}]) {
61 //done() {
61 62 return this;
62 63 }
63 64
64 configure<T extends IContainerBuilder<S>>(builder: T) {
65 configure<C extends IContainerBuilder<ContainerServices<S>, keyof S>>(builder: C) {
65 66 each(this._builders, (v, k) => {
66 67 v(builder.createServiceBuilder(k));
67 68 });
68 builder.build() as T & ServiceContainer<S>;
69 return builder.build();
69 70 }
70 71
71 72 }
@@ -1,211 +1,217
1 import { IActivationContext, IDestroyable, ILifetime } from "./interfaces";
1 import { IActivationContext, ILifetime, ILifetimeManager } from "./interfaces";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { argumentNotNull, isDestroyable } from "./traits";
4 4
5 5 const safeCall = (item: () => void) => {
6 6 try {
7 7 item();
8 8 } catch {
9 9 // silence!
10 10 }
11 11 };
12 12
13 const emptyLifetime = Object.freeze({
13 const _emptyLifetime = Object.freeze({
14 14 has() {
15 15 return false;
16 16 },
17 17
18 18 initialize() {
19 19 },
20 20
21 21 get() {
22 22 throw new Error("The specified item isn't registered with this lifetime manager");
23 23 },
24 24
25 25 store() {
26 26 // does nothing
27 27 },
28 28
29 29 toString() {
30 30 return `[object EmptyLifetime]`;
31 31 }
32 32
33 33 });
34 34
35 const unknownLifetime = Object.freeze({
35 const _unknownLifetime = Object.freeze({
36 36 has() {
37 37 return false;
38 38 },
39 39 initialize() {
40 40 throw new Error("Can't call initialize on the unknown lifetime object");
41 41 },
42 42 get() {
43 43 throw new Error("The lifetime object isn't initialized");
44 44 },
45 45 store() {
46 46 throw new Error("Can't store a value in the unknown lifetime object");
47 47 },
48
48 49 toString() {
49 50 return `[object UnknownLifetime]`;
50 51 }
51 52 });
52 53
53 54 let nextId = 0;
54 55
55 const singletons: { [K:string]: unknown} = {};
56 const singletons: { [K: string]: unknown } = {};
56 57
57 export class LifetimeManager implements IDestroyable {
58 export class LifetimeManager implements ILifetimeManager {
58 59 private _cleanup: (() => void)[] = [];
59 private readonly _cache: {[K: string]: unknown} = {};
60 private readonly _cache: { [K: string]: unknown } = {};
60 61 private _destroyed = false;
61 62
62 private readonly _pending: {[K: string]: unknown} = {};
63 private readonly _pending: { [K: string]: unknown } = {};
63 64
64 create<T>(): ILifetime<T> {
65 create<T>(): ILifetime<T> & { remove(): void; } {
65 66 const id = ++nextId;
66 67 return {
67 68 has: () => id in this._cache,
68 69
69 70 get: () => {
70 const t = this._cache[id];
71 if (t === undefined)
71 const t = this._cache[id] as T;
72 if (t === undefined || t === null)
72 73 throw new Error(`The item with with the key ${id} isn't found`);
73 return t as T;
74 return t;
74 75 },
75 76
76 77 initialize: () => {
77 78 if (this._pending[id])
78 79 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
79 80 this._pending[id] = true;
80 81 },
81 82
82 store: (item: T, cleanup?: (item: T) => void) => {
83 argumentNotNull(id, "id");
84 argumentNotNull(item, "item");
85
83 store: (item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) => {
86 84 if (id in this._cache)
87 85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
86 if (this._destroyed)
87 throw new Error("Lifetime manager is destroyed");
88
88 89 delete this._pending[id];
89 90
90 91 this._cache[id] = item;
91 92
92 if (this._destroyed)
93 throw new Error("Lifetime manager is destroyed");
94 93 if (cleanup) {
95 94 this._cleanup.push(() => cleanup(item));
96 95 } else if (isDestroyable(item)) {
97 96 this._cleanup.push(() => item.destroy());
98 97 }
98 },
99
100 remove: () => {
101 if (this._pending[id])
102 throw new Error(`The item '${id}' can't be removed before it has been stored`);
103 delete this._cache[id];
99 104 }
100 105 };
101 106 }
102 107
103 108 destroy() {
104 109 if (!this._destroyed) {
105 110 this._destroyed = true;
106 111 this._cleanup.forEach(safeCall);
107 112 this._cleanup.length = 0;
108 113 }
109 114 }
110 115
111 static empty<T>(): ILifetime<T> {
112 return emptyLifetime;
113 }
116 }
117
118 export const emptyLifetime = <T>(): ILifetime<T> => {
119 return _emptyLifetime;
120 };
114 121
115 static hierarchyLifetime<T>() {
116 let _lifetime: ILifetime<T> = unknownLifetime;
117 return {
118 initialize(context: IActivationContext<object>) {
119 if (_lifetime !== unknownLifetime)
120 throw new Error("Cyclic reference activation detected");
122 export const hierarchyLifetime = <T>(): ILifetime<T> => {
123 let _lifetime: ILifetime<T> = _unknownLifetime;
124 return {
125 initialize(context: IActivationContext<object>) {
126 if (_lifetime !== _unknownLifetime)
127 throw new Error("Cyclic reference activation detected");
121 128
122 _lifetime = context.createContainerLifetime<T>();
123 },
124 get() {
125 return _lifetime.get();
126 },
127 has() {
128 return _lifetime.has();
129 },
130 store(item: T, cleanup?: (item: T) => void) {
131 return _lifetime.store(item, cleanup);
132 },
133 toString() {
134 return `[object HierarchyLifetime, has=${String(this.has())}]`;
135 }
136 };
137 }
138
139 static contextLifetime<T>() {
140 let _lifetime: ILifetime<T> = unknownLifetime;
141 return {
142 initialize(context: ActivationContext<object>) {
143 if (_lifetime !== unknownLifetime)
144 throw new Error("Cyclic reference detected");
145 _lifetime = context.createLifetime();
146 },
147 get() {
148 return _lifetime.get();
149 },
150 has() {
151 return _lifetime.has();
152 },
153 store(item: T) {
154 _lifetime.store(item);
155 },
156 toString() {
157 return `[object ContextLifetime, has=${String(this.has())}]`;
158 }
159 };
160 }
129 _lifetime = context.createContainerLifetime<T>();
130 },
131 get() {
132 return _lifetime.get();
133 },
134 has() {
135 return _lifetime.has();
136 },
137 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
138 return _lifetime.store(item, cleanup);
139 },
140 toString() {
141 return `[object HierarchyLifetime, has=${String(this.has())}]`;
142 }
143 };
144 };
161 145
162 static singletonLifetime<T>(typeId: string) {
163 argumentNotNull(typeId, "typeId");
164 let pending = false;
165 return {
166 has() {
167 return typeId in singletons;
168 },
169 get() {
170 if (!this.has())
171 throw new Error(`The instance ${typeId} doesn't exists`);
172 return singletons[typeId] as T;
173 },
174 initialize() {
175 if (pending)
176 throw new Error("Cyclic reference detected");
177 pending = true;
178 },
179 store(item: T) {
180 singletons[typeId] = item;
181 pending = false;
182 },
183 toString() {
184 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
185 }
186 };
187 }
146 export const contextLifetime = <T>(): ILifetime<T> => {
147 let _lifetime: ILifetime<T> = _unknownLifetime;
148 return {
149 initialize(context: ActivationContext<object>) {
150 if (_lifetime !== _unknownLifetime)
151 throw new Error("Cyclic reference detected");
152 _lifetime = context.createLifetime();
153 },
154 get() {
155 return _lifetime.get();
156 },
157 has() {
158 return _lifetime.has();
159 },
160 store(item: NonNullable<T>) {
161 _lifetime.store(item);
162 },
163 toString() {
164 return `[object ContextLifetime, has=${String(this.has())}]`;
165 }
166 };
167 };
188 168
189 static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) {
190 let _lifetime: ILifetime<T> = unknownLifetime;
191 return {
192 initialize() {
193 if (_lifetime !== unknownLifetime)
194 throw new Error("Cyclic reference detected");
195 _lifetime = container.createLifetime();
196 },
197 get() {
198 return _lifetime.get();
199 },
200 has() {
201 return _lifetime.has();
202 },
203 store(item: T) {
204 _lifetime.store(item);
205 },
206 toString() {
207 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
208 }
209 };
210 }
211 }
169 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
170 argumentNotNull(typeId, "typeId");
171 let pending = false;
172 return {
173 has() {
174 return typeId in singletons;
175 },
176 get() {
177 if (!this.has())
178 throw new Error(`The instance ${typeId} doesn't exists`);
179 return singletons[typeId] as NonNullable<T>;
180 },
181 initialize() {
182 if (pending)
183 throw new Error("Cyclic reference detected");
184 pending = true;
185 },
186 store(item: NonNullable<T>) {
187 singletons[typeId] = item;
188 pending = false;
189 },
190 toString() {
191 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
192 }
193 };
194 };
195
196 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
197 let _lifetime: ILifetime<T> = _unknownLifetime;
198 return {
199 initialize() {
200 if (_lifetime !== _unknownLifetime)
201 throw new Error("Cyclic reference detected");
202 _lifetime = container.createLifetime();
203 },
204 get() {
205 return _lifetime.get();
206 },
207 has() {
208 return _lifetime.has();
209 },
210 store(item: NonNullable<T>) {
211 _lifetime.store(item);
212 },
213 toString() {
214 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
215 }
216 };
217 };
@@ -1,183 +1,258
1 1 import { key } from "./traits";
2 2
3 3 export interface IDestroyable {
4 4 destroy(): void;
5 5 }
6 6
7 7 /**
8 8 * @template S Карта доступных зависимостей
9 9 */
10 10 export interface Resolver<S> {
11 11 /**
12 12 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
13 13 * отложенную активацию и значение по-умолчанию для сервисов
14 * @template K Ключ сервиса из {@link S}
15 * @template O Тип параметра {@link opts} используется для выведения типа
14 * @template K Ключ сервиса из {@linkcode S}
15 * @template O Тип параметра {@linkcode opts} используется для выведения типа
16 16 * возвращаемого значения.
17 17 * @param name Ключ сервиса, который будет разрешен.
18 18 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
19 19 * будет возвращен фабричный метод для получения зависимости. Если не указан,
20 20 * то считается `false`.
21 21 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
22 22 * сервис не зарегистрирован
23 23 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
24 24 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
25 25 */
26 <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
27 <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
26 <K extends keyof S, O extends { lazy: true; }>(name: K, opts?: O): () => NonNullable<S[K]> | InferDefault<O>;
27 <K extends keyof S, O extends { lazy?: false; }>(name: K, opts?: O): NonNullable<S[K]> | InferDefault<O>;
28 28 }
29 29
30 30 export type DepsMap<S> = {
31 31 [k in key]: Refs<S> | keyof S;
32 32 };
33 33
34 34 export type Refs<S> = {
35 [k in keyof S]: Ref<k, boolean, S[k]>;
35 [k in keyof S]: Ref<k, S[k]>;
36 36 }[keyof S];
37 37
38 export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L, default?: D | null };
38 export type Ref<K extends key, D> = {
39 /** The name of the service */
40 name: K;
41
42 /** Make a lazy reference, the resolved dependency will be a function */
43 lazy?: boolean;
44
45 /** The default value for the case where the service isn't defined.
46 * When specified the dependency becomes optional, the default value can be
47 * `null` or `undefined`
48 */
49 default?: D | null
50 };
39 51
40 52 export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
41 53
54 /** Возвращает тип свойства `default` в типе {@link T} */
42 55 export type InferDefault<T> = T extends { default: infer D } ? D : never;
43 56
57 export type InferLazy<R> = R extends { lazy: infer L } ?
58 L extends true ? true : false :
59 false;
44 60 export type Resolve<S, R> =
45 61 R extends keyof S ? NonNullable<S[R]> :
46 R extends Ref<infer K, infer L, unknown> ?
47 K extends keyof S ? Lazy<NonNullable<S[K]> | InferDefault<R>, L> :
62 R extends Ref<infer K, unknown> ?
63 K extends keyof S ?
64 Lazy<NonNullable<S[K]> | InferDefault<R>, InferLazy<R>> :
48 65 never :
49 66 never;
50 67
51 68 /**
52 * Интерфейс для конфигурирования сервиса в контейнере
69 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
70 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
71 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
72 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
73 * {@linkcode value}.
74 *
75 * @template S Карта сервисов контейнера, доступных при описании дескриптора
76 * @template T Тип сервиса
77 * @template R Карта зависимостей, которая передается параметром фабрике
78 * @template U Имена пользовательских сервисов, доступных для переопределения
53 79 */
54 export interface IDescriptorBuilder<S, T, R, O extends keyof S> {
80 export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
55 81
56 82 /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
57 83 * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
58 84 * которые были предварительно указаны вызовами метода `wants(...)`
59 85 *
60 86 * Вызов данного метода завершает конфигурирование сервиса.
61 87 *
62 * @param f Фабрика для создания экземпляра сервиса
88 * @param f Фабрика для создания экземпляра сервиса.
63 89 */
64 factory(f: (refs: R) => T): void;
90 factory(f: (refs: R) => NonNullable<T>): void;
65 91
66 92 /**
67 93 * Используется для указания зависимостей, которые потребуются фабричному
68 94 * методу при создании нового экземпляра сервиса. Данный метод может быть
69 95 * вызван несколько раз подряд, при этом вызовы этого метода имеют
70 96 * кумулятивный эффект.
71 97 *
72 98 * @template X Тип объекта с зависимостями, которые требуется получить при
73 99 * создании экземпляра фабрики при помощи фабричного метода.
74 100 * @param refs Объект с описанием зависимостей
75 101 * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
76 102 * зависимости
77 103 */
78 104 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
79 105 IDescriptorBuilder<S, T, R & {
80 106 [k in keyof X]: Resolve<S, X[k]>;
81 }, O>
107 }, U>
82 108
83 override<K extends O>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
84 override<K extends O>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this;
109 override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
110 override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;
85 111
86 112 lifetime(lifetime: "singleton", typeId: string | number | object): this;
87 113 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
88 114
89 115 /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
90 116 * время жизни привязано к контейнеру.
91 117 */
92 118 cleanup(cb: (item: T) => void): this;
93 119
94 120 /**
95 121 * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
96 122 *
97 123 * @param v Экземпляр реализации сервиса.
98 124 */
99 value(v: T): void;
125 value(v: NonNullable<T>): void;
100 126 }
101 127
102 export type RegistrationBuilder<S, T> = (d: IDescriptorBuilder<S, T, object, ConfigurableKeys<S>>) => void;
128 export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
103 129
104 export type RegistrationBuildersMap<S extends Configurable<S>, K extends keyof S = keyof S> = {
105 [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>>
130 /**
131 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
132 *
133 * Все параметры конфигурации являются обязательными, если требуется ввести
134 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
135 *
136 * @template S Сервисы доступные в контейнере
137 * @template K Сервисы участвующие в конфигурации
138 */
139 export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
140 [k in K]-?: BuildDescriptorFn<S, S[k], U>
141 };
142
143 export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
144 [k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
145 };
146
147 /**
148 * The type constraint useful to restrict type parameters to prevent defining
149 * the services with the {@link ContainerKeys} names.
150 *
151 * The constraint doesn't exclude using this keys but declares them as `never`
152 * which effectively will lead using this keys to the error.
153 */
154 export type ContainerServicesConstraint<S> = {
155 [k in keyof S]: k extends ContainerKeys ? never : S[k];
106 156 };
107 157
108 158 export interface Descriptor<S, T> {
109 activate(context: IActivationContext<S>): T;
159
160 /** This flags indicates that this registration can be replaced or overridden. */
161 readonly configurable?: boolean;
162
163 /** If specified signals the activation context that a new service scope
164 * should be created to isolate service overrides.
165 */
166 readonly hasOverrides?: boolean;
167
168 activate(context: IActivationContext<S>): NonNullable<T>;
110 169 }
111 170
112 171 export interface IActivationContext<S> extends ServiceLocator<S> {
113 172 createLifetime<T>(): ILifetime<T>;
114 173
115 174 createContainerLifetime<T>(): ILifetime<T>;
116 }
117 175
118 export type RegistrationMap<S, K extends keyof S = keyof S> = {
119 [k in K]-?: Descriptor<S, S[k]>;
120 };
121
122 export interface ContainerProvided<S> {
123 container: ServiceLocator<ContainerServices<S>>;
124
125 childContainer: IContainerBuilder<S>;
176 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
126 177 }
127 178
128 export type Configurable<S> = { [k in keyof S]: k extends ProvidedKeys ? never : S[k]; };
179 /**
180 * Descriptors map for the specified services {@linkcode S}. All entries are
181 * optional regardless the required or optional services in the original map.
182 *
183 * @template S Сервисы контекста активации
184 * @template U Карта сервисов которые создаются дескрипторами
185 */
186 export type DescriptorMap<S> = {
187 [k in keyof S]?: Descriptor<S, S[k]>;
188 };
129 189
130 export type ProvidedKeys = keyof ContainerProvided<never>;
190 type ContainerKeys = keyof ContainerProvided<object>;
191
192 export type ContainerProvided<S extends ContainerServicesConstraint<S>> = {
193 container: ServiceLocator<ContainerServices<S>>;
194
195 childContainer: IContainerBuilder<ContainerServices<S>, Exclude<keyof S, ContainerKeys>>;
196 };
131 197
132 export type ContainerServices<S> =
133 { [k in keyof S as k extends ProvidedKeys ? never: k]: S[k] } &
134 ContainerProvided<S>;
135 198
136 export type ConfigurableKeys<S> = Exclude<keyof S, ProvidedKeys>;
199 /**
200 * Таблица сервисов, которые предоставляет контейнер.
201 *
202 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
203 */
204 export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
205 [k in keyof S | ContainerKeys]:
206 k extends ContainerKeys ? ContainerProvided<S>[k] :
207 k extends keyof S ? S[k] : never
208 };
137 209
138 export type ConfigurableServices<S> = Pick<S, ConfigurableKeys<S>>;
139 210
140 export type ContainerKeys<S> = keyof S | ProvidedKeys;
141
211 /**
212 * Returns the service declared in the type map {@link S}.
213 *
214 *
215 */
142 216 export interface ServiceLocator<S> {
143 217 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
144 218 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
145 219 }
146 220
147 221 export interface LifetimeContainer {
148 222 createLifetime<T>(): ILifetime<T>;
149 223 }
150 224
151 export interface ServiceContainer<S> extends
152 ServiceLocator<ContainerServices<S>>,
153 IDestroyable {
225 export interface IContainerBuilder<S, U extends keyof S> {
226 createServiceBuilder<K extends U>(name: K):
227 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
154 228
155 createChildContainer(): IContainerBuilder<S>;
156 }
157
158 export interface IContainerBuilder<S> {
159 createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object, keyof S>;
160
161 build(): ServiceContainer<S>;
229 build(): ServiceLocator<S>;
162 230 }
163 231
164 232
165 233 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
166 234
167 235 /**
168 236 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
169 237 * свой собственный объект `ILifetime`, который создается при первой активации
170 238 */
171 239 export interface ILifetime<T> {
172 240 /** Проверяет, что уже создан экземпляр объекта */
173 241 has(): boolean;
174 242
175 get(): T;
243 get(): NonNullable<T>;
244
245 initialize(context: IActivationContext<unknown>): void;
246
247 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
176 248
177 initialize(context: IActivationContext<object>): void;
249 toString(): string;
250 }
178 251
179 store(item: T, cleanup?: (item: T) => void): void;
252 export interface ILifetimeManager extends IDestroyable {
253 create<T>(): ILifetime<T>;
180 254 }
181 255
182 256 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
183 257
258 export type ExtractRequiredKeys<T, K extends keyof T = keyof T> = { [p in K]-?: undefined extends T[p] ? never : p }[K]; No newline at end of file
@@ -1,49 +1,49
1 1 import { FluentConfiguration } from "./FluentConfiguration";
2 import { IDestroyable } from "./interfaces";
2 import { ContainerServices, ContainerServicesConstraint, IDestroyable } from "./interfaces";
3 3
4 export function fluent<S = {}>() {
4 export function fluent<S extends ContainerServicesConstraint<S>>() {
5 5 return new FluentConfiguration<S>();
6 6 }
7 7
8 8 export type key = string | number | symbol;
9 9
10 10 export const isKey = (v: unknown): v is key =>
11 11 typeof v === "string" || typeof v === "number" || typeof v === "symbol";
12 12
13 13 export const isString = (v: unknown): v is string =>
14 14 typeof v === "string";
15 15
16 16
17 17 export const isNotNull = <T>(v: T): v is NonNullable<T> => v !== null && v !== undefined;
18 18
19 19 export const each = <T extends object>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void) =>
20 20 (Object.keys(obj) as (Extract<keyof T, string>)[])
21 21 .forEach(k => {
22 22 const v = obj[k];
23 23 isNotNull(v) && cb(v, k);
24 24 });
25 25
26 26 export const argumentNotNull = (arg: unknown, name: string) => {
27 27 if (arg === null || arg === undefined)
28 28 throw new Error("The argument " + name + " can't be null or undefined");
29 29 };
30 30
31 31 export const isPromise = <T = unknown>(val: unknown): val is PromiseLike<T> =>
32 32 isNotNull(val) && typeof (val as PromiseLike<T>).then === "function";
33 33
34 34 export const isDestroyable = (d: unknown): d is IDestroyable =>
35 35 isNotNull(d) && typeof (d as IDestroyable).destroy === "function";
36 36
37 37 let _nextOid = 0;
38 38 const _oid = typeof Symbol === "function" ?
39 39 Symbol.for("__implab__oid__") :
40 40 "__implab__oid__";
41 41
42 42 type OidSlot = { [k in typeof _oid]?: string };
43 43
44 44 export const oid = <T extends object>(instance: T): string => {
45 45 argumentNotNull(instance, "instance");
46 46 const val = (instance as OidSlot)[_oid];
47 47
48 48 return val ? val : ((instance as OidSlot)[_oid] = `oid_${++_nextOid}`);
49 49 }; No newline at end of file
@@ -1,100 +1,107
1 1 /* eslint max-classes-per-file: ["error", 20] */
2 2 import { describe, it } from "mocha";
3 3 import { Container } from "../Container";
4 4 import { ContainerBuilder } from "../ContainerBuilder";
5 import { ConfigurableKeys, ContainerProvided, ContainerServices, DepsMap, Refs, Resolver } from "../interfaces";
5 import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver } from "../interfaces";
6 6 import { fluent } from "../traits";
7 7
8 8 class Foo {
9 9 foo = "foo";
10 10 }
11 11
12 12 class Bar {
13 13 bar = "bar";
14 14
15 15 constructor(foo?: () => Foo) { }
16 16 }
17 17
18 18 interface Services {
19 19 foo: Foo;
20 20
21 21 bar?: Bar;
22 22
23 23 baz: Foo;
24 24
25 container: string;
25 box?: Foo;
26
27 //container: string;
26 28 }
27 29
28 30 interface ServicesB {
29 31 // will give errors
30 32 // baz: Bar;
31 33
32 34 baz: Foo;
33 35
34 36 zoo?: Foo;
35 37 }
36 38
37 39 declare const resolver: Resolver<Services>;
38 40
39 const foo = resolver("foo", {lazy: true});
41 const foo = resolver("foo", { lazy: true, default: null });
40 42
41 const mmap = <X extends DepsMap<Services>>(m: X) => {};
43 const mmap = <X extends DepsMap<Services>>(m: X) => { };
42 44
43 45 declare const refs: Refs<Services>;
44 46
45 47 if (refs && refs.name === "foo") {
46 48 refs.default;
47 49 }
48 50
49 51 declare const x: ContainerServices<Services>;
50 52
51 53 x.container.resolve("container");
52 54
53 55
54 56 mmap({
55 fooz: {name: "foo", lazy: false, default: undefined },
56 ooz: "bar"
57 fooz: { name: "foo", lazy: false, default: undefined },
58 ooz: "bar"
57 59 });
58 60
59 61 interface SharedServices {
60 62 foo: Foo;
61 63
62 64 bar?: Bar;
63 65
64 66 baz: Bar;
65 67 }
66 68
67 const config = fluent()
68 .declare<Services>()
69 const config = fluent<Services>()
69 70 .declare<ServicesB>()
70 71 .register({
71 zoo: it => {},
72 zoo: it => it.value(new Foo()),
72 73 bar: it => it
73 74 .lifetime("context") // тип активации, время жизни
74 75 .wants({
75 zoo: "zoo", // зависимость
76 self: "container",
77 childContainer: "childContainer",
76 78 bar: "bar",
77
78 $zoo: { name: "foo", lazy: true, } // отложенная активация,
79 //фабричный метод
79 foo$: { name: "foo", lazy: true } // отложенная активация, фабричный метод
80 80 })
81 .wants({
82 zoom: "bar"
81 .override({ // переопределение сервиса
82 box: it => it.factory(() => new Foo())
83
83 84 })
84 .factory(({ $zoo, zoo }) => // фабрика получает объект с именованными зависимостями
85 // удобно для деструктурирования
86 new Bar($zoo) // создается экземпляр сервиса
85 .factory(({ foo$, bar, self, childContainer }) => // фабрика получает объект с именованными зависимостями
86 new Bar(foo$) // создается экземпляр сервиса
87 87 ),
88 88 foo: it => it.factory(() => new Foo()),
89 baz: it => it.value(new Foo())
89 baz: it => it.value(new Foo()),
90 //box: it => it.factory(() => new Foo())
90 91 })
91 .done({});
92 .done();
93
94 declare const container: IContainerBuilder<ContainerServices<Services>, keyof Services>;
92 95
93 declare const container: ContainerBuilder<{}>;
96 const v = container.build().resolve("foo");
97 if (v) {
98 // noop
99 }
100
94 101 const c2 = config.configure(container);
95 102
96 103 c2.resolve("foo");
97 104
98 declare const m :ContainerServices<{foo: Foo}>["container"];
105 declare const m: ContainerServices<{ foo?: Foo }>["container"];
99 106
100 107 m.resolve("container").resolve("container").resolve("foo"); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now