##// END OF EJS Templates
Working on container builder
cin -
r5:83fa5814462d default
parent child
Show More
@@ -0,0 +1,46
1 import { Container } from "./Container";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { Configurable, ConfigurableKeys, ContainerServices, Descriptor, IContainerBuilder, IDescriptorBuilder, RegistrationMap, ServiceContainer } from "./interfaces";
4 import { LifetimeManager } from "./LifetimeManager";
5
6 export class ContainerBuilder<S extends Configurable<S>> implements IContainerBuilder<S>{
7
8 private _pending = 1;
9
10 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
11
12 private readonly _lifetimeManager = new LifetimeManager();
13
14 constructor(parentServices?: object) {
15 this._services = Object.create(parentServices ? parentServices : null) as object;
16 }
17
18 createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object> {
19 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
20 }
21
22 build(): ServiceContainer<S> {
23 this._assertBuilding();
24 if(!this._complete())
25 throw new Error("The configuration didn't complete.");
26 return new Container(this._services, this._lifetimeManager);
27 }
28
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 };
33
34 private readonly _fail = (ex: unknown) => {
35
36 };
37
38 private _assertBuilding() {
39 throw new Error("The descriptor builder is finalized");
40 }
41
42 private _complete() {
43 return !(--this._pending);
44 }
45
46 } No newline at end of file
@@ -1,32 +1,33
1 1 {
2 2 "root": true,
3 3 "extends": [
4 4 "plugin:react/recommended",
5 5 "eslint:recommended",
6 6 "plugin:@typescript-eslint/eslint-recommended",
7 7 "plugin:@typescript-eslint/recommended",
8 8 "plugin:@typescript-eslint/recommended-requiring-type-checking"
9 9 ],
10 10 "parser": "@typescript-eslint/parser",
11 11 "parserOptions": {
12 12 "ecmaFeatures": {
13 13 "jsx": true
14 14 },
15 15 "ecmaVersion": 5,
16 16 "tsconfigRootDir": "src",
17 17 "project": ["tsconfig.eslint.json", "*/tsconfig.json"]
18 18 },
19 19 "plugins": [
20 20 "@typescript-eslint"
21 21 ],
22 22 "rules": {
23 23 "@typescript-eslint/no-empty-function": "off",
24 24 "max-classes-per-file": [
25 25 "error",
26 26 { "ignoreExpressions": true, "max": 1 }
27 27 ],
28 28 "@typescript-eslint/prefer-readonly": ["error"],
29 "semi": "off",
29 30 "@typescript-eslint/semi": ["error"]
30 31
31 32 }
32 33 }
@@ -1,138 +1,139
1 1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { LifetimeManager } from "./LifetimeManager";
2 3 import { argumentNotNull } from "./traits";
3 4
4 5 export interface ActivationContextInfo {
5 6 name: string;
6 7
7 8 service: string;
8 9
9 10 }
10 11
11 12 let nextId = 1;
12 13
13 14 /** This class is created once per `Container.resolve` method call and used to
14 15 * cache dependencies and to track created instances. The activation context
15 16 * tracks services with `context` activation type.
16 17 */
17 18 export class ActivationContext<S extends object> {
18 19 private readonly _cache: Record<string, unknown>;
19 20
20 21 private readonly _services: Partial<RegistrationMap<S>>;
21 22
22 23 private readonly _name: string;
23 24
24 25 private readonly _service: Descriptor<S, unknown>;
25 26
26 private readonly _containerLifetimeManager: LifetimeContainer;
27 private readonly _containerLifetimeManager: LifetimeManager;
27 28
28 29 private readonly _parent: ActivationContext<S> | undefined;
29 30
30 31 /** Creates a new activation context with the specified parameters.
31 32 * @param containerLifetimeManager the container which starts the activation process
32 33 * @param services the initial service registrations
33 34 * @param name the name of the service being activated, this parameter is
34 35 * used for the debug purpose.
35 36 * @param service the service to activate, this parameter is used for the
36 37 * debug purpose.
37 38 */
38 constructor(containerLifetimeManager: LifetimeContainer, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>, cache = {}) {
39 constructor(containerLifetimeManager: LifetimeManager, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>, cache = {}) {
39 40 this._name = name;
40 41 this._service = service;
41 42 this._cache = cache;
42 43 this._services = services;
43 44 this._containerLifetimeManager = containerLifetimeManager;
44 45 }
45 46
46 47 /** the name of the current resolving dependency */
47 48 getName() {
48 49 return this._name;
49 50 }
50 51
51 52 createContainerLifetime<T>() {
52 return this._containerLifetimeManager.createLifetime<T>();
53 return this._containerLifetimeManager.create<T>();
53 54 }
54 55
55 56 /** Resolves the specified dependency in the current context
56 57 * @param name The name of the dependency being resolved
57 58 */
58 59 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
59 60 /** Resolves the specified dependency with the specified default value if
60 61 * the dependency is missing.
61 62 *
62 63 * @param name The name of the dependency being resolved
63 64 * @param def A default value to return in case of the specified dependency
64 65 * is missing.
65 66 */
66 67 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
67 68 resolve<K extends keyof S, T>(name: K, def?: T): S[K] | T | undefined {
68 69 const d = this._services[name];
69 70
70 71 if (d !== undefined) {
71 72 return this.activate(d, name.toString());
72 73 } else {
73 74 if (arguments.length > 1)
74 75 return def;
75 76 else
76 77 throw new Error(`Service ${String(name)} not found`);
77 78 }
78 79 }
79 80
80 81 /**
81 82 * registers services local to the the activation context
82 83 *
83 84 * @name{string} the name of the service
84 85 * @service{string} the service descriptor to register
85 86 */
86 87 register<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
87 88 argumentNotNull(name, "name");
88 89
89 90 this._services[name] = service;
90 91 }
91 92
92 93 createLifetime<T>(): ILifetime<T> {
93 94 const id = nextId++;
94 95 return {
95 96 initialize() {},
96 97 has: () => id in this._cache,
97 98 get: () => this._cache[id] as T,
98 99 store: item => {
99 100 this._cache[id] = item;
100 101 }
101 102 };
102 103 }
103 104
104 105 activate<T>(d: Descriptor<S, T>, name: string) {
105 106 // TODO: add logging
106 107 // if (trace.isLogEnabled())
107 108 // trace.log("enter {0} {1}", name, d);
108 109
109 110 const ctx = this.enter(d, name);
110 111 const v = d.activate(ctx);
111 112
112 113 // if (trace.isLogEnabled())
113 114 // trace.log(`leave ${name}`);
114 115
115 116 return v;
116 117 }
117 118
118 119 getStack(): ActivationContextInfo[] {
119 120 const stack = [{
120 121 name: this._name,
121 122 service: this._service.toString()
122 123 }];
123 124
124 125 return this._parent ?
125 126 stack.concat(this._parent.getStack()) :
126 127 stack;
127 128 }
128 129
129 130 private enter(service: Descriptor<S, unknown>, name: string) {
130 131 return new ActivationContext(
131 132 this._containerLifetimeManager,
132 133 Object.create(this._services) as typeof this._services,
133 134 name,
134 135 service,
135 136 this._cache
136 137 );
137 138 }
138 139 }
@@ -1,79 +1,54
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ActivationError } from "./ActivationError";
3 import { RegistrationMap, ContainerKeys, ServiceContainer, ContainerServices, ConfigurableServices, ConfigurableKeys, ServiceLocator, Descriptor} from "./interfaces";
3 import { ContainerBuilder } from "./ContainerBuilder";
4 import { RegistrationMap, ServiceContainer, ContainerServices, ServiceLocator, IContainerBuilder, Configurable, ContainerKeys} from "./interfaces";
4 5 import { LifetimeManager } from "./LifetimeManager";
5 import { each, isKey } from "./traits";
6 6
7 export class Container<S extends object > implements ServiceContainer<S> {
7 export class Container<S extends Configurable<S>> implements ServiceContainer<S> {
8 8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
9 9
10 10 private readonly _lifetimeManager: LifetimeManager;
11 11
12 private readonly _cleanup: (() => void)[];
13
14 12 private _disposed: boolean;
15 13
16 constructor(parent?: Container<S>) {
17 this._services = Object.create(parent ? parent._services : null) as typeof this._services;
18 this._cleanup = [];
19 this._services.container = { activate: () => this as ServiceLocator<ContainerServices<S>>};
20 this._services.childContainer = { activate: () => this.createChildContainer() };
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"] };
21 18 this._disposed = false;
22 this._lifetimeManager = new LifetimeManager();
19 this._lifetimeManager = lifetimeManager;
23 20 }
24 21
25 register<K extends ConfigurableKeys<S>>(name: K, service: Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>): void;
26 register<K extends ConfigurableKeys<S>>(services: {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}): void;
27 register<K extends ConfigurableKeys<S>>(nameOrCollection: K | {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}, service?: RegistrationMap<ContainerServices<S>>[K]) {
28 if (!isKey(nameOrCollection)) {
29 each(nameOrCollection, (v, k) => this.register(k, v));
30 } else {
31 if (!service)
32 throw new Error("The service parameter must be a descriptor");
33
34 this._services[nameOrCollection] = service;
35 }
22 createChildContainer(): IContainerBuilder<S> {
23 return new ContainerBuilder(this._services);
36 24 }
37 25
38 createChildContainer(): ServiceContainer<S> {
39 return new Container(this);
40 }
41
42 resolve<K extends ContainerKeys<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
43 resolve<K extends ContainerKeys<S>>(name: K, def: ContainerServices<S>[K]): ContainerServices<S>[K];
44 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerServices<S>[K]): ContainerServices<S>[K] | undefined {
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) {
45 29 // TODO: add logging
46 30 // trace.debug("resolve {0}", name);
47 31 const d = this._services[name];
48 32 if (d === undefined) {
49 33 if (arguments.length > 1)
50 34 return def;
51 35 else
52 36 throw new Error(`Service '${String(name)}' isn't found`);
53 37 } else {
54 38
55 const context = new ActivationContext(this, this._services, String(name), d);
39 const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d);
56 40 try {
57 41 return d.activate(context);
58 42 } catch (error) {
59 43 throw new ActivationError(name.toString(), context.getStack(), error);
60 44 }
61 45 }
62 46 }
63 47
64 createLifetime<T>() {
65 return this._lifetimeManager.create<T>();
66 }
67
68 48 destroy() {
69 return this.dispose();
70 }
71 dispose() {
72 49 if (this._disposed)
73 50 return;
74 51 this._disposed = true;
75 for (const f of this._cleanup)
76 f();
77 52 this._lifetimeManager.destroy();
78 53 }
79 54 }
@@ -1,196 +1,189
1 1 import { RegistrationBuilder, LifetimeContainer, ConfigurableKeys, IDescriptorBuilder, Ref, Resolved, DepsMap } from "./interfaces";
2 2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
3 3 import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
4 4 import { LifetimeManager } from "./LifetimeManager";
5 5 import { each, isKey, isPromise, isString, key, oid } from "./traits";
6 6
7 7 /**
8 8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
9 9 * @template {T} Тип сервиса
10 10 */
11 11 export class DescriptorBuilder<S extends object, T, R extends object = object> implements IDescriptorBuilder<S, T, R> {
12 private readonly _lifetimeContainer: LifetimeContainer;
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 17 private readonly _refs: DepsMap<key, keyof S>;
18 18
19 19 private _lifetime = LifetimeManager.empty<T>();
20 20
21 21 private _overrides: RegistrationOverridesMap<S>;
22 22
23 23 private _cleanup?: (item: T) => void;
24 24
25 25 private _factory?: (refs: R) => 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 * @param container The lifetime container is the container where the service is to be registered.
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 constructor(container: LifetimeContainer, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
42 this._lifetimeContainer = container;
41 constructor(lifetimeManager: LifetimeManager, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
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 48 wants<X extends { [k in Exclude<key, keyof R>]: keyof S | Ref<keyof S, boolean, unknown>; }>(refs: X):
49 49 IDescriptorBuilder<S, T, R & {
50 50 [k in keyof X]:
51 51 X[k] extends keyof S ? NonNullable<S[X[k]]> :
52 52 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
53 53 never;
54 54 }> {
55 55
56 56 each(refs, (v, k) => this._refs[k] = v);
57 57
58 58 return this as IDescriptorBuilder<S, T, R & {
59 59 [k in keyof X]:
60 60 X[k] extends keyof S ? NonNullable<S[X[k]]> :
61 61 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
62 62 never;
63 63 }>;
64 64 }
65 65 factory(f: (refs: R) => T): void {
66 66 this._assertBuilding();
67 67 this._factory = f;
68 68 this._finalize();
69 69 this._complete();
70 70 }
71 71
72 72
73 73 private _assertBuilding() {
74 74 if (this._finalized)
75 75 throw new Error("The descriptor builder is finalized");
76 76 }
77 77
78 78 private _finalize() {
79 79 this._finalized = true;
80 80 }
81 81
82 82 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
83 83 override<K extends ConfigurableKeys<S>>(services: { [k in K]: RegistrationBuilder<S, NonNullable<S[k]>> }): this;
84 84 override<K extends ConfigurableKeys<S>>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }, builder?: RegistrationBuilder<S, NonNullable<S[K]>>): this {
85 85 this._assertBuilding();
86 86 const guard = (v: void | Promise<void>) => {
87 87 if (isPromise(v))
88 88 v.catch(err => this._fail(err));
89 89 };
90 90
91 91 if (isKey(nameOrServices)) {
92 92 if (builder) {
93 93 this._defer();
94 94 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
95 this._lifetimeContainer,
95 this._lifetimeManager,
96 96 result => {
97 97 this._overrides[nameOrServices] = result;
98 98 this._complete();
99 99 },
100 100 err => this._fail(err)
101 101 );
102 102
103 103 try {
104 104 guard(builder(d));
105 105 } catch (err) {
106 106 this._fail(err);
107 107 }
108 108 }
109 109 } else {
110 110 each(nameOrServices, (v, k) => this.override(k, v));
111 111 }
112 112 return this;
113 113 }
114 114
115 115 lifetime(lifetime: "singleton", typeId: string): this;
116 116 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
117 117 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
118 118 this._assertBuilding();
119 119 if (isString(lifetime)) {
120 120 this._lifetime = this._resolveLifetime(lifetime, typeId);
121 121 } else {
122 122 this._lifetime = lifetime;
123 123 }
124 124 return this;
125 125 }
126 126
127 127 cleanup(cb: (item: T) => void): this {
128 128 this._assertBuilding();
129 129 this._cleanup = cb;
130 130 return this;
131 131 }
132 132
133 /*factory(f: (resolve: Resolver<S>) => T): void {
134 this._assertBuilding();
135 this._factory = f;
136 this._finalize();
137 this._complete();
138 }*/
139
140 133 value(v: T): void {
141 134 this._assertBuilding();
142 135 this._cb({
143 136 activate() {
144 137 return v;
145 138 }
146 139 });
147 140 this._finalize();
148 141 }
149 142
150 143 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
151 144 switch (activation) {
152 145 case "container":
153 return LifetimeManager.containerLifetime(this._lifetimeContainer);
146 return this._lifetimeManager.create();
154 147 case "hierarchy":
155 148 return LifetimeManager.hierarchyLifetime();
156 149 case "context":
157 150 return LifetimeManager.contextLifetime();
158 151 case "singleton": {
159 152 if (!typeId)
160 153 throw Error("The singleton activation requires a typeId");
161 154
162 155 const _oid = isString(typeId) ? typeId : oid(typeId);
163 156
164 157 return LifetimeManager.singletonLifetime(_oid);
165 158 }
166 159 default:
167 160 return LifetimeManager.empty();
168 161 }
169 162 }
170 163
171 164 _defer() {
172 165 this._pending++;
173 166 }
174 167
175 168 _complete() {
176 169 if (--this._pending === 0) {
177 170 if (!this._factory)
178 171 throw new Error("The factory must be specified");
179 172
180 173 this._cb(new DescriptorImpl<S, T>({
181 174 lifetime: this._lifetime,
182 175 factory: this._factory,
183 176 overrides: this._overrides,
184 177 cleanup: this._cleanup
185 178 }));
186 179 }
187 180 }
188 181
189 182 _fail(err: unknown) {
190 183 if (!this._failed) {
191 184 this._failed = true;
192 185 this._eb.call(undefined, err);
193 186 }
194 187 }
195 188
196 189 }
@@ -1,83 +1,83
1 import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref } from "./interfaces";
1 import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref, IActivationContext } from "./interfaces";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { each, isKey, key } from "./traits";
4 4
5 5 export type RegistrationOverridesMap<S extends object> = { [k in ConfigurableKeys<S>]?: Descriptor<S, NonNullable<S[k]>> };
6 6
7 7 export interface DescriptorImplArgs<S extends object, T> {
8 8 lifetime: ILifetime<T>;
9 9
10 10 factory: (refs: Record<key, never>) => T;
11 11
12 12 cleanup?: (item: T) => void;
13 13
14 14 overrides?: RegistrationOverridesMap<S>;
15 15
16 16 dependencies?: DepsMap<key, keyof S>;
17 17 }
18 18
19 19
20 20 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
21 21
22 22 private readonly _overrides?: RegistrationOverridesMap<S>;
23 23
24 24 private readonly _lifetime: ILifetime<T>;
25 25
26 26 private readonly _factory: (refs: Record<key, never>) => T;
27 27
28 28 private readonly _cleanup?: (item: T) => void;
29 29
30 30 private readonly _deps?: DepsMap<key, keyof S>;
31 31
32 32 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
33 33 this._lifetime = lifetime;
34 34 this._factory = factory;
35 35 if (cleanup)
36 36 this._cleanup = cleanup;
37 37 if (overrides)
38 38 this._overrides = overrides;
39 39 if (dependencies)
40 40 this._deps = dependencies;
41 41 }
42 42
43 activate(context: ActivationContext<S>): T {
43 activate(context: IActivationContext<S>): T {
44 44
45 45 if (this._lifetime.has())
46 46 return this._lifetime.get();
47 47
48 48 this._lifetime.initialize(context);
49 49
50 50 if (this._overrides)
51 51 each(this._overrides, (v, k) => context.register(k, v));
52 52
53 53 const resolve = <K extends keyof S, L extends boolean, D = never>({ name, lazy, ...opts }: Ref<K, L, D>) => {
54 54 if (lazy) {
55 55 return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
56 56 } else {
57 57 return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
58 58 }
59 59 };
60 60
61 const makeRefs = (deps?: DepsMap<key, keyof S>) => deps ?
61 const makeRefs = (deps: typeof this._deps) => deps ?
62 62 Object.keys(deps)
63 63 .map(k => {
64 64 const ref = deps[k];
65 65 return isKey(ref) ?
66 66 { [k]: resolve({ name: ref }) } :
67 67 { [k]: resolve(ref) };
68 68 })
69 69 .reduce((a, p) => ({ ...a, ...p }), {} ) as Record<key, never>:
70 70 {} as Record<key, never>;
71 71
72 72 const instance = this._factory.call(undefined, makeRefs(this._deps));
73 73
74 74 this._lifetime.store(instance, this._cleanup);
75 75
76 76 return instance;
77 77 }
78 78
79 79
80 80 toString() {
81 81 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
82 82 }
83 83 }
@@ -1,93 +1,71
1 1 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, ExtractRequired } from "./interfaces";
2 import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces";
3 3 import { ServiceContainer } from "./interfaces";
4 4 import { argumentNotNull, each, isKey } from "./traits";
5 5
6 6 export class FluentConfiguration<S extends object, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> {
7 7
8 8 private _builders: Partial<RegistrationBuildersMap<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 15 declare<D extends Partial<Pick<S, keyof D & keyof S>>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> {
16 16 return this as FluentConfiguration<S & D, Y | ConfigurableKeys<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 25 return this 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 34 register<K extends Y>(name: K, builder: RegistrationBuildersMap<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 39 register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>;
40 40 register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<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 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 60 done<M extends ExtractRequired<S,Y>>(missing: M) {
61 61 return this;
62 62 }
63 63
64
65 apply<T extends ServiceContainer<S>>(target: T) {
66
67 let pending = 1;
68
69 const reject = (ex: unknown) => { throw ex; };
70
71 const complete = () => !--pending;
72
64 configure<T extends IContainerBuilder<S>>(builder: T) {
73 65 each(this._builders, (v, k) => {
74 pending++;
75 const d = new DescriptorBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[typeof k]>>(
76 target,
77 result => {
78 target.register(k, result);
79 complete();
80 },
81 reject
82 );
83
84
85 v(d);
66 v(builder.createServiceBuilder(k));
86 67 });
87 if (!complete())
88 throw new Error("The configuration didn't complete.");
89
90 return target as T & ServiceContainer<S>;
68 builder.build() as T & ServiceContainer<S>;
91 69 }
92 70
93 71 }
@@ -1,211 +1,211
1 import { IDestroyable, ILifetime } from "./interfaces";
1 import { IActivationContext, IDestroyable, ILifetime } 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 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 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 toString() {
49 49 return `[object UnknownLifetime]`;
50 50 }
51 51 });
52 52
53 53 let nextId = 0;
54 54
55 55 const singletons: { [K:string]: unknown} = {};
56 56
57 57 export class LifetimeManager implements IDestroyable {
58 58 private _cleanup: (() => void)[] = [];
59 59 private readonly _cache: {[K: string]: unknown} = {};
60 60 private _destroyed = false;
61 61
62 62 private readonly _pending: {[K: string]: unknown} = {};
63 63
64 64 create<T>(): ILifetime<T> {
65 65 const id = ++nextId;
66 66 return {
67 67 has: () => id in this._cache,
68 68
69 69 get: () => {
70 70 const t = this._cache[id];
71 71 if (t === undefined)
72 72 throw new Error(`The item with with the key ${id} isn't found`);
73 73 return t as T;
74 74 },
75 75
76 76 initialize: () => {
77 77 if (this._pending[id])
78 78 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
79 79 this._pending[id] = true;
80 80 },
81 81
82 82 store: (item: T, cleanup?: (item: T) => void) => {
83 83 argumentNotNull(id, "id");
84 84 argumentNotNull(item, "item");
85 85
86 86 if (id in this._cache)
87 87 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
88 88 delete this._pending[id];
89 89
90 90 this._cache[id] = item;
91 91
92 92 if (this._destroyed)
93 93 throw new Error("Lifetime manager is destroyed");
94 94 if (cleanup) {
95 95 this._cleanup.push(() => cleanup(item));
96 96 } else if (isDestroyable(item)) {
97 97 this._cleanup.push(() => item.destroy());
98 98 }
99 99 }
100 100 };
101 101 }
102 102
103 103 destroy() {
104 104 if (!this._destroyed) {
105 105 this._destroyed = true;
106 106 this._cleanup.forEach(safeCall);
107 107 this._cleanup.length = 0;
108 108 }
109 109 }
110 110
111 111 static empty<T>(): ILifetime<T> {
112 112 return emptyLifetime;
113 113 }
114 114
115 115 static hierarchyLifetime<T>() {
116 116 let _lifetime: ILifetime<T> = unknownLifetime;
117 117 return {
118 initialize(context: ActivationContext<object>) {
118 initialize(context: IActivationContext<object>) {
119 119 if (_lifetime !== unknownLifetime)
120 120 throw new Error("Cyclic reference activation detected");
121 121
122 122 _lifetime = context.createContainerLifetime<T>();
123 123 },
124 124 get() {
125 125 return _lifetime.get();
126 126 },
127 127 has() {
128 128 return _lifetime.has();
129 129 },
130 130 store(item: T, cleanup?: (item: T) => void) {
131 131 return _lifetime.store(item, cleanup);
132 132 },
133 133 toString() {
134 134 return `[object HierarchyLifetime, has=${String(this.has())}]`;
135 135 }
136 136 };
137 137 }
138 138
139 139 static contextLifetime<T>() {
140 140 let _lifetime: ILifetime<T> = unknownLifetime;
141 141 return {
142 142 initialize(context: ActivationContext<object>) {
143 143 if (_lifetime !== unknownLifetime)
144 144 throw new Error("Cyclic reference detected");
145 145 _lifetime = context.createLifetime();
146 146 },
147 147 get() {
148 148 return _lifetime.get();
149 149 },
150 150 has() {
151 151 return _lifetime.has();
152 152 },
153 153 store(item: T) {
154 154 _lifetime.store(item);
155 155 },
156 156 toString() {
157 157 return `[object ContextLifetime, has=${String(this.has())}]`;
158 158 }
159 159 };
160 160 }
161 161
162 162 static singletonLifetime<T>(typeId: string) {
163 163 argumentNotNull(typeId, "typeId");
164 164 let pending = false;
165 165 return {
166 166 has() {
167 167 return typeId in singletons;
168 168 },
169 169 get() {
170 170 if (!this.has())
171 171 throw new Error(`The instance ${typeId} doesn't exists`);
172 172 return singletons[typeId] as T;
173 173 },
174 174 initialize() {
175 175 if (pending)
176 176 throw new Error("Cyclic reference detected");
177 177 pending = true;
178 178 },
179 179 store(item: T) {
180 180 singletons[typeId] = item;
181 181 pending = false;
182 182 },
183 183 toString() {
184 184 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
185 185 }
186 186 };
187 187 }
188 188
189 189 static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) {
190 190 let _lifetime: ILifetime<T> = unknownLifetime;
191 191 return {
192 192 initialize() {
193 193 if (_lifetime !== unknownLifetime)
194 194 throw new Error("Cyclic reference detected");
195 195 _lifetime = container.createLifetime();
196 196 },
197 197 get() {
198 198 return _lifetime.get();
199 199 },
200 200 has() {
201 201 return _lifetime.has();
202 202 },
203 203 store(item: T) {
204 204 _lifetime.store(item);
205 205 },
206 206 toString() {
207 207 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
208 208 }
209 209 };
210 210 }
211 211 }
@@ -1,137 +1,146
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { key } from "./traits";
3 3
4 export type primitive = number | string | null | undefined | symbol;
5
6 4 export interface IDestroyable {
7 5 destroy(): void;
8 6 }
9 7
10 8 /**
11 9 * @template S Карта доступных зависимостей
12 10 */
13 11 export interface Resolver<S extends object> {
14 12 /**
15 13 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
16 14 * отложенную активацию и значение по-умолчанию для сервисов
17 15 * @template K Ключ сервиса из {@link S}
18 16 * @template O Тип параметра {@link opts} используется для выведения типа
19 17 * возвращаемого значения.
20 18 * @param name Ключ сервиса, который будет разрешен.
21 19 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
22 20 * будет возвращен фабричный метод для получения зависимости. Если не указан,
23 21 * то считается `false`.
24 22 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
25 23 * сервис не зарегистрирован
26 24 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
27 25 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
28 26 */
29 27 <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
30 28 <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>;
31 29 }
32 30
33 31 export type DepsMap<K extends key, SK extends key> = { [k in K]: SK | Ref<SK, boolean, unknown> };
34 32
35 export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L} | { name: K, lazy?: L, default: D };
33 export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L } | { name: K, lazy?: L, default: D };
36 34
37 35 export type Resolved<S, K extends keyof S, L, D> =
38 L extends true ? () => NonNullable<S[K]> | (unknown extends D ? never : D) : NonNullable<S[K]> | (unknown extends D ? never : D);
36 L extends true ? () => NonNullable<S[K]> | (unknown extends D ? never : D) : NonNullable<S[K]> | (unknown extends D ? never : D);
39 37
40 38 export interface IDescriptorBuilder<S extends object, T, R extends object = object> {
41 39
42 40 /**
43 41 *
44 42 * @param f
45 43 */
46 44 factory(f: (refs: R) => T): void;
47 45
48 46 wants<X extends DepsMap<Exclude<key, keyof R>, keyof S>>(refs: X):
49 47 IDescriptorBuilder<S, T, R & {
50 48 [k in keyof X]:
51 49 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>:
50 X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> :
53 51 never
54 52 }>
55 53
56 54 override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
57 55 override<K extends ConfigurableKeys<S>>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this;
58 56
59 57 lifetime(lifetime: "singleton", typeId: string | number | object): this;
60 58 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
61 59
62 60 cleanup(cb: (item: T) => void): this;
63 61
64 62 value(v: T): void;
65 63 }
66 64
67 65 export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T>) => void;
68 66
69 67 export type RegistrationBuildersMap<S extends object, K extends ConfigurableKeys<S> = ConfigurableKeys<S>> = {
70 [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<ConfigurableServices<S>[k]>>
68 [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>>
71 69 };
72 70
73 71 export interface Descriptor<S extends object, T> {
74 activate(context: ActivationContext<S>): T;
72 activate(context: IActivationContext<S>): T;
75 73 }
76 74
77 export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>;
75 export interface IActivationContext<S extends object> extends ServiceLocator<S> {
76 createLifetime<T>(): ILifetime<T>;
77
78 createContainerLifetime<T>(): ILifetime<T>;
79 }
80
81 export type ConfigurableDescriptor<S extends object, K extends ConfigurableKeys<S>> = Descriptor<ContainerServices<S>, S[K]>;
78 82
79 83 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
80 84 [k in K]-?: Descriptor<S, S[k]>;
81 85 };
82 86
83 export interface ProvidedServices<S extends object> {
87 export interface ContainerProvided<S extends Configurable<S>> {
84 88 container: ServiceLocator<ContainerServices<S>>;
85 89
86 childContainer: ServiceContainer<S>;
90 childContainer: IContainerBuilder<S>;
87 91 }
88 92
89 export type ProvidedKeys = keyof ProvidedServices<object>;
90
91 export type ContainerKeys<S extends object> = keyof ContainerServices<S>;
93 export type Configurable<S> = { [k in keyof S & (keyof ContainerProvided<never>)]: never; };
92 94
93 export type Mix<S, X> = { [k in keyof (S & X)]: k extends keyof X ? X[k] : S[k & keyof S] };
95 export type ProvidedKeys = keyof ContainerProvided<never>;
94 96
95 export type ContainerServices<S extends object> = Mix<S, ProvidedServices<S>>;
97 export type ContainerServices<S extends Configurable<S>> = S & ContainerProvided<S>;
96 98
97 99 export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>;
98 100
99 101 export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>;
100 102
103 export type ContainerKeys<S extends Configurable<S>> = keyof S | keyof ContainerProvided<never>;
104
101 105 export interface ServiceLocator<S extends object> {
102 106 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
103 107 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
104 108 }
105 109
106 110 export interface LifetimeContainer {
107 111 createLifetime<T>(): ILifetime<T>;
108 112 }
109 113
110 export interface ServiceContainer<S extends object> extends ServiceLocator<ContainerServices<S>>, LifetimeContainer, IDestroyable {
114 export interface ServiceContainer<S extends Configurable<S>> extends
115 ServiceLocator<ContainerServices<S>>,
116 IDestroyable {
111 117
112 register<K extends ConfigurableKeys<S>>(name: K, service: ConfigurableDescriptor<S, K>): void;
113 register<K extends ConfigurableKeys<S>>(services: { [k in K]: ConfigurableDescriptor<S, K> }): void;
118 createChildContainer(): IContainerBuilder<S>;
119 }
114 120
115 createChildContainer(): ServiceContainer<S>;
121 export interface IContainerBuilder<S extends Configurable<S>> {
122 createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>>;
123
124 build(): ServiceContainer<S>;
116 125 }
117 126
118 127
119 128 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
120 129
121 130 /**
122 131 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
123 132 * свой собственный объект `ILifetime`, который создается при первой активации
124 133 */
125 134 export interface ILifetime<T> {
126 135 /** Проверяет, что уже создан экземпляр объекта */
127 136 has(): boolean;
128 137
129 138 get(): T;
130 139
131 initialize(context: ActivationContext<object>): void;
140 initialize(context: IActivationContext<object>): void;
132 141
133 142 store(item: T, cleanup?: (item: T) => void): void;
134 143 }
135 144
136 145 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
137 146
@@ -1,65 +1,70
1 1 /* eslint max-classes-per-file: ["error", 20] */
2 2 import { describe, it } from "mocha";
3 3 import { Container } from "../Container";
4 import { ContainerServices } from "../interfaces";
4 5 import { fluent } from "../traits";
5 6
6 7 class Foo {
7 8 foo = "foo";
8 9 }
9 10
10 11 class Bar {
11 12 bar = "bar";
12 13
13 14 constructor(foo?: () => Foo) { }
14 15 }
15 16
16 17 interface Services {
17 18 foo: Foo;
18 19
19 20 bar?: Bar;
20 21
21 22 baz: Foo;
22 23 }
23 24
24 25 interface ServicesB {
25 26 // will give errors
26 27 // baz: Bar;
27 28
28 29 baz: Foo;
29 30
30 31 zoo?: Foo;
31 32 }
32 33
33 34 interface SharedServices {
34 35 foo: Foo;
35 36
36 37 bar?: Bar;
37 38
38 39 baz: Bar;
39 40 }
40 41
41 42 const config = fluent()
42 43 .declare<Services>()
43 44 .declare<ServicesB>()
44 45 .register({
45 46 bar: it => it
46 47 .lifetime("context") // тип активации, время жизни
47 48 .wants({
48 49 zoo: "zoo", // зависимость
49 50
50 51 zoo$: { name: "zoo", lazy: true } // отложенная активация,
51 52 //фабричный метод
52 53 })
53 54 .factory(({ zoo$ }) => // фабрика получает объект с именованными зависимостями
54 55 // удобно для деструктурирования
55 56 new Bar(zoo$) // создается экземпляр сервиса
56 57 ),
57 58 foo: it => it.factory(() => new Foo()),
58 59 baz: it => it.value(new Foo())
59 60 })
60 61 .done({});
61 62
62 declare const container: Container<Record<string, never>>;
63 const c2 = config.apply(container);
63 declare const container: Container<object>;
64 const c2 = config.configure(container);
64 65
65 c2.resolve("foo"); No newline at end of file
66 c2.resolve("foo");
67
68 declare const m :ContainerServices<{foo: Foo}>["container"];
69
70 m.resolve("container").resolve("container"); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now