##// 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
@@ -26,6 +26,7
26 { "ignoreExpressions": true, "max": 1 }
26 { "ignoreExpressions": true, "max": 1 }
27 ],
27 ],
28 "@typescript-eslint/prefer-readonly": ["error"],
28 "@typescript-eslint/prefer-readonly": ["error"],
29 "semi": "off",
29 "@typescript-eslint/semi": ["error"]
30 "@typescript-eslint/semi": ["error"]
30
31
31 }
32 }
@@ -1,4 +1,5
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
1 import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
2 import { LifetimeManager } from "./LifetimeManager";
2 import { argumentNotNull } from "./traits";
3 import { argumentNotNull } from "./traits";
3
4
4 export interface ActivationContextInfo {
5 export interface ActivationContextInfo {
@@ -23,7 +24,7 export class ActivationContext<S extends
23
24
24 private readonly _service: Descriptor<S, unknown>;
25 private readonly _service: Descriptor<S, unknown>;
25
26
26 private readonly _containerLifetimeManager: LifetimeContainer;
27 private readonly _containerLifetimeManager: LifetimeManager;
27
28
28 private readonly _parent: ActivationContext<S> | undefined;
29 private readonly _parent: ActivationContext<S> | undefined;
29
30
@@ -35,7 +36,7 export class ActivationContext<S extends
35 * @param service the service to activate, this parameter is used for the
36 * @param service the service to activate, this parameter is used for the
36 * debug purpose.
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 this._name = name;
40 this._name = name;
40 this._service = service;
41 this._service = service;
41 this._cache = cache;
42 this._cache = cache;
@@ -49,7 +50,7 export class ActivationContext<S extends
49 }
50 }
50
51
51 createContainerLifetime<T>() {
52 createContainerLifetime<T>() {
52 return this._containerLifetimeManager.createLifetime<T>();
53 return this._containerLifetimeManager.create<T>();
53 }
54 }
54
55
55 /** Resolves the specified dependency in the current context
56 /** Resolves the specified dependency in the current context
@@ -1,47 +1,31
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ActivationError } from "./ActivationError";
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 import { LifetimeManager } from "./LifetimeManager";
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 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
8 private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
9
9
10 private readonly _lifetimeManager: LifetimeManager;
10 private readonly _lifetimeManager: LifetimeManager;
11
11
12 private readonly _cleanup: (() => void)[];
13
14 private _disposed: boolean;
12 private _disposed: boolean;
15
13
16 constructor(parent?: Container<S>) {
14 constructor(services: Partial<RegistrationMap<ContainerServices<S>>>, lifetimeManager: LifetimeManager) {
17 this._services = Object.create(parent ? parent._services : null) as typeof this._services;
15 this._services = services;
18 this._cleanup = [];
16 this._services.container = { activate: () => this as ContainerServices<S>["container"]};
19 this._services.container = { activate: () => this as ServiceLocator<ContainerServices<S>>};
17 this._services.childContainer = { activate: () => this.createChildContainer() as ContainerServices<S>["childContainer"] };
20 this._services.childContainer = { activate: () => this.createChildContainer() };
21 this._disposed = false;
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;
22 createChildContainer(): IContainerBuilder<S> {
26 register<K extends ConfigurableKeys<S>>(services: {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}): void;
23 return new ContainerBuilder(this._services);
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 }
36 }
24 }
37
25
38 createChildContainer(): ServiceContainer<S> {
26 resolve<K extends keyof ContainerServices<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
39 return new Container(this);
27 resolve<K extends keyof ContainerServices<S>, T>(name: K, def: T): NonNullable<ContainerServices<S>[K]> | T;
40 }
28 resolve<K extends keyof ContainerServices<S>, T>(name: K, def?: T) {
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 {
45 // TODO: add logging
29 // TODO: add logging
46 // trace.debug("resolve {0}", name);
30 // trace.debug("resolve {0}", name);
47 const d = this._services[name];
31 const d = this._services[name];
@@ -52,7 +36,7 export class Container<S extends object
52 throw new Error(`Service '${String(name)}' isn't found`);
36 throw new Error(`Service '${String(name)}' isn't found`);
53 } else {
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 try {
40 try {
57 return d.activate(context);
41 return d.activate(context);
58 } catch (error) {
42 } catch (error) {
@@ -61,19 +45,10 export class Container<S extends object
61 }
45 }
62 }
46 }
63
47
64 createLifetime<T>() {
65 return this._lifetimeManager.create<T>();
66 }
67
68 destroy() {
48 destroy() {
69 return this.dispose();
70 }
71 dispose() {
72 if (this._disposed)
49 if (this._disposed)
73 return;
50 return;
74 this._disposed = true;
51 this._disposed = true;
75 for (const f of this._cleanup)
76 f();
77 this._lifetimeManager.destroy();
52 this._lifetimeManager.destroy();
78 }
53 }
79 }
54 }
@@ -9,7 +9,7 import { each, isKey, isPromise, isStrin
9 * @template {T} Тип сервиса
9 * @template {T} Тип сервиса
10 */
10 */
11 export class DescriptorBuilder<S extends object, T, R extends object = object> implements IDescriptorBuilder<S, T, R> {
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 private readonly _cb: (d: Descriptor<S, T>) => void;
13 private readonly _cb: (d: Descriptor<S, T>) => void;
14
14
15 private readonly _eb: (err: unknown) => void;
15 private readonly _eb: (err: unknown) => void;
@@ -34,12 +34,12 export class DescriptorBuilder<S extends
34 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
34 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
35 * lifetime.
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 * @param cb The callback to receive the built service descriptor
38 * @param cb The callback to receive the built service descriptor
39 * @param eb The callback to receive the error due
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) {
41 constructor(lifetimeManager: LifetimeManager, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
42 this._lifetimeContainer = container;
42 this._lifetimeManager = lifetimeManager;
43 this._cb = cb;
43 this._cb = cb;
44 this._eb = eb;
44 this._eb = eb;
45 this._overrides = {};
45 this._overrides = {};
@@ -92,7 +92,7 export class DescriptorBuilder<S extends
92 if (builder) {
92 if (builder) {
93 this._defer();
93 this._defer();
94 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
94 const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
95 this._lifetimeContainer,
95 this._lifetimeManager,
96 result => {
96 result => {
97 this._overrides[nameOrServices] = result;
97 this._overrides[nameOrServices] = result;
98 this._complete();
98 this._complete();
@@ -130,13 +130,6 export class DescriptorBuilder<S extends
130 return this;
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 value(v: T): void {
133 value(v: T): void {
141 this._assertBuilding();
134 this._assertBuilding();
142 this._cb({
135 this._cb({
@@ -150,7 +143,7 export class DescriptorBuilder<S extends
150 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
143 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
151 switch (activation) {
144 switch (activation) {
152 case "container":
145 case "container":
153 return LifetimeManager.containerLifetime(this._lifetimeContainer);
146 return this._lifetimeManager.create();
154 case "hierarchy":
147 case "hierarchy":
155 return LifetimeManager.hierarchyLifetime();
148 return LifetimeManager.hierarchyLifetime();
156 case "context":
149 case "context":
@@ -1,4 +1,4
1 import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref } from "./interfaces";
1 import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref, IActivationContext } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { each, isKey, key } from "./traits";
3 import { each, isKey, key } from "./traits";
4
4
@@ -40,7 +40,7 export class DescriptorImpl<S extends ob
40 this._deps = dependencies;
40 this._deps = dependencies;
41 }
41 }
42
42
43 activate(context: ActivationContext<S>): T {
43 activate(context: IActivationContext<S>): T {
44
44
45 if (this._lifetime.has())
45 if (this._lifetime.has())
46 return this._lifetime.get();
46 return this._lifetime.get();
@@ -58,7 +58,7 export class DescriptorImpl<S extends ob
58 }
58 }
59 };
59 };
60
60
61 const makeRefs = (deps?: DepsMap<key, keyof S>) => deps ?
61 const makeRefs = (deps: typeof this._deps) => deps ?
62 Object.keys(deps)
62 Object.keys(deps)
63 .map(k => {
63 .map(k => {
64 const ref = deps[k];
64 const ref = deps[k];
@@ -1,5 +1,5
1 import { DescriptorBuilder } from "./DescriptorBuilder";
1 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, ExtractRequired } from "./interfaces";
2 import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces";
3 import { ServiceContainer } from "./interfaces";
3 import { ServiceContainer } from "./interfaces";
4 import { argumentNotNull, each, isKey } from "./traits";
4 import { argumentNotNull, each, isKey } from "./traits";
5
5
@@ -61,33 +61,11 export class FluentConfiguration<S exten
61 return this;
61 return this;
62 }
62 }
63
63
64
64 configure<T extends IContainerBuilder<S>>(builder: T) {
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
73 each(this._builders, (v, k) => {
65 each(this._builders, (v, k) => {
74 pending++;
66 v(builder.createServiceBuilder(k));
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);
86 });
67 });
87 if (!complete())
68 builder.build() as T & ServiceContainer<S>;
88 throw new Error("The configuration didn't complete.");
89
90 return target as T & ServiceContainer<S>;
91 }
69 }
92
70
93 }
71 }
@@ -1,4 +1,4
1 import { IDestroyable, ILifetime } from "./interfaces";
1 import { IActivationContext, IDestroyable, ILifetime } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { argumentNotNull, isDestroyable } from "./traits";
3 import { argumentNotNull, isDestroyable } from "./traits";
4
4
@@ -115,7 +115,7 export class LifetimeManager implements
115 static hierarchyLifetime<T>() {
115 static hierarchyLifetime<T>() {
116 let _lifetime: ILifetime<T> = unknownLifetime;
116 let _lifetime: ILifetime<T> = unknownLifetime;
117 return {
117 return {
118 initialize(context: ActivationContext<object>) {
118 initialize(context: IActivationContext<object>) {
119 if (_lifetime !== unknownLifetime)
119 if (_lifetime !== unknownLifetime)
120 throw new Error("Cyclic reference activation detected");
120 throw new Error("Cyclic reference activation detected");
121
121
@@ -1,8 +1,6
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { key } from "./traits";
2 import { key } from "./traits";
3
3
4 export type primitive = number | string | null | undefined | symbol;
5
6 export interface IDestroyable {
4 export interface IDestroyable {
7 destroy(): void;
5 destroy(): void;
8 }
6 }
@@ -67,37 +65,43 export interface IDescriptorBuilder<S ex
67 export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T>) => void;
65 export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T>) => void;
68
66
69 export type RegistrationBuildersMap<S extends object, K extends ConfigurableKeys<S> = ConfigurableKeys<S>> = {
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 export interface Descriptor<S extends object, T> {
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 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
83 export type RegistrationMap<S extends object, K extends keyof S = keyof S> = {
80 [k in K]-?: Descriptor<S, S[k]>;
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 container: ServiceLocator<ContainerServices<S>>;
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>;
93 export type Configurable<S> = { [k in keyof S & (keyof ContainerProvided<never>)]: never; };
90
91 export type ContainerKeys<S extends object> = keyof ContainerServices<S>;
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 export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>;
99 export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>;
98
100
99 export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>;
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 export interface ServiceLocator<S extends object> {
105 export interface ServiceLocator<S extends object> {
102 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
106 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
103 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
107 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
@@ -107,12 +111,17 export interface LifetimeContainer {
107 createLifetime<T>(): ILifetime<T>;
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;
118 createChildContainer(): IContainerBuilder<S>;
113 register<K extends ConfigurableKeys<S>>(services: { [k in K]: ConfigurableDescriptor<S, K> }): void;
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
@@ -128,7 +137,7 export interface ILifetime<T> {
128
137
129 get(): T;
138 get(): T;
130
139
131 initialize(context: ActivationContext<object>): void;
140 initialize(context: IActivationContext<object>): void;
132
141
133 store(item: T, cleanup?: (item: T) => void): void;
142 store(item: T, cleanup?: (item: T) => void): void;
134 }
143 }
@@ -1,6 +1,7
1 /* eslint max-classes-per-file: ["error", 20] */
1 /* eslint max-classes-per-file: ["error", 20] */
2 import { describe, it } from "mocha";
2 import { describe, it } from "mocha";
3 import { Container } from "../Container";
3 import { Container } from "../Container";
4 import { ContainerServices } from "../interfaces";
4 import { fluent } from "../traits";
5 import { fluent } from "../traits";
5
6
6 class Foo {
7 class Foo {
@@ -59,7 +60,11 const config = fluent()
59 })
60 })
60 .done({});
61 .done({});
61
62
62 declare const container: Container<Record<string, never>>;
63 declare const container: Container<object>;
63 const c2 = config.apply(container);
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