##// END OF EJS Templates
WIP service descriptors
cin -
r13:dc3d64c43573 default
parent child
Show More
@@ -1,145 +1,141
1 import { ActivationError } from "./ActivationError";
1 import { ActivationError } from "./ActivationError";
2 import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager } from "./interfaces";
2 import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager } from "./interfaces";
3 import { argumentNotNull, prototype } from "./traits";
3 import { argumentNotNull, prototype } from "./traits";
4
4
5 export interface ActivationContextInfo {
5 export interface ActivationContextInfo {
6 name: string;
6 name: string;
7
7
8 service: string;
8 service: string;
9
9
10 }
10 }
11
11
12 let nextId = 1;
12 let nextId = 1;
13
13
14 /** This object is created once per `Container.resolve` method call and used to
14 /** This object is created once per `Container.resolve` method call and used to
15 * cache dependencies and to track created instances. The activation context
15 * cache dependencies and to track created instances. The activation context
16 * tracks services with `context` activation type.
16 * tracks services with `context` activation type.
17 *
17 *
18 * @template S The service map used in the activation context, services from
18 * @template S The service map used in the activation context, services from
19 * this map are available to resolution.
19 * this map are available to resolution.
20 * @template U A set of keys from the service map which can be overridden in
20 * @template U A set of keys from the service map which can be overridden in
21 * this activation context.
21 * this activation context.
22 */
22 */
23 export class ActivationContext<S> implements IActivationContext<S> {
23 export class ActivationContext<S> implements IActivationContext<S> {
24 private readonly _cache: Record<string, unknown>;
24 private readonly _cache: Record<string, unknown>;
25
25
26 private readonly _services: DescriptorMap<S>;
26 private readonly _services: DescriptorMap<S>;
27
27
28 private readonly _name: string;
28 private readonly _name: string;
29
29
30 private readonly _service: Descriptor<S, unknown>;
30 private readonly _service: Descriptor<S, unknown>;
31
31
32 private readonly _containerLifetimeManager: ILifetimeManager;
32 private readonly _lifetimeManagers: ILifetimeManager[];
33
33
34 private readonly _parent: ActivationContext<S> | undefined;
34 private readonly _parent: ActivationContext<S> | undefined;
35
35
36 /** Creates a new activation context with the specified parameters.
36 /** Creates a new activation context with the specified parameters.
37 * @param containerLifetimeManager the container which starts the activation process
37 * @param containerLifetimeManager the container which starts the activation process
38 * @param services the initial service registrations
38 * @param services the initial service registrations
39 * @param name the name of the service being activated, this parameter is
39 * @param name the name of the service being activated, this parameter is
40 * used for the debug purpose.
40 * used for the debug purpose.
41 * @param service the service to activate, this parameter is used for the
41 * @param service the service to activate, this parameter is used for the
42 * debug purpose.
42 * debug purpose.
43 */
43 */
44 constructor(containerLifetimeManager: ILifetimeManager, services: DescriptorMap<S>, name: string, service: Descriptor<S, unknown>, cache = {}) {
44 constructor(lifetimeManagers: ILifetimeManager[], services: DescriptorMap<S>, name: string, service: Descriptor<S, unknown>, cache = {}) {
45 this._name = name;
45 this._name = name;
46 this._service = service;
46 this._service = service;
47 this._cache = cache;
47 this._cache = cache;
48 this._services = services;
48 this._services = services;
49 this._containerLifetimeManager = containerLifetimeManager;
49 this._lifetimeManagers = lifetimeManagers;
50 }
50 }
51
51
52 /** the name of the current resolving dependency */
52 /** the name of the current resolving dependency */
53 getName() {
53 getName() {
54 return this._name;
54 return this._name;
55 }
55 }
56
56
57 createContainerLifetime<T>() {
58 return this._containerLifetimeManager.create<T>();
59 }
60
61 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
57 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
62 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
58 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
63 resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined {
59 resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined {
64 const d = this._services[name];
60 const d = this._services[name];
65
61
66 if (d !== undefined) {
62 if (d !== undefined) {
67 return this.activate(d, name.toString());
63 return this.activate(d, name.toString());
68 } else {
64 } else {
69 if (arguments.length > 1)
65 if (arguments.length > 1)
70 return def;
66 return def;
71 else
67 else
72 throw new Error(`Service ${String(name)} not found`);
68 throw new Error(`Service ${String(name)} not found`);
73 }
69 }
74 }
70 }
75
71
76 /**
72 /**
77 * registers services local to the the activation context
73 * registers services local to the the activation context
78 *
74 *
79 * @name{string} the name of the service
75 * @name{string} the name of the service
80 * @service{string} the service descriptor to register
76 * @service{string} the service descriptor to register
81 */
77 */
82 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]) {
78 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]) {
83 argumentNotNull(name, "name");
79 argumentNotNull(name, "name");
84
80
85 const d = this._services[name];
81 const d = this._services[name];
86 if (d !== undefined && !d.configurable)
82 if (d !== undefined && !d.configurable)
87 throw new Error(`Service ${String(name)} can't be overridden`);
83 throw new Error(`Service ${String(name)} can't be overridden`);
88
84
89 this._services[name] = service;
85 this._services[name] = service;
90 }
86 }
91
87
92 createLifetime<T>(): ILifetime<T> {
88 createLifetime<T>(): ILifetime<T> {
93 const id = nextId++;
89 const id = nextId++;
94 return {
90 return {
95 initialize() { },
91 initialize() { },
96 has: () => id in this._cache,
92 has: () => id in this._cache,
97 get: () => {
93 get: () => {
98 const v = this._cache[id] as T;
94 const v = this._cache[id] as T;
99 if (v === undefined || v === null)
95 if (v === undefined || v === null)
100 throw new Error("The value isn't present in the activation context");
96 throw new Error("The value isn't present in the activation context");
101 return v;
97 return v;
102 },
98 },
103 store: item => {
99 store: item => {
104 this._cache[id] = item;
100 this._cache[id] = item;
105 }
101 }
106 };
102 };
107 }
103 }
108
104
109 activate<T>(d: Descriptor<S, T>, name: string) {
105 activate<T>(d: Descriptor<S, T>, name: string) {
110 // TODO: add logging
106 // TODO: add logging
111 // if (trace.isLogEnabled())
107 // if (trace.isLogEnabled())
112 // trace.log("enter {0} {1}", name, d);
108 // trace.log("enter {0} {1}", name, d);
113
109
114 const ctx = new ActivationContext(
110 const ctx = new ActivationContext(
115 this._containerLifetimeManager,
111 this._containerLifetimeManager,
116 d.hasOverrides ? prototype(this._services) : this._services,
112 d.hasOverrides ? prototype(this._services) : this._services,
117 name,
113 name,
118 d,
114 d,
119 this._cache
115 this._cache
120 );
116 );
121
117
122 const v = d.activate(ctx);
118 const v = d.activate(ctx);
123
119
124 // if (trace.isLogEnabled())
120 // if (trace.isLogEnabled())
125 // trace.log(`leave ${name}`);
121 // trace.log(`leave ${name}`);
126
122
127 return v;
123 return v;
128 }
124 }
129
125
130 getStack(): ActivationContextInfo[] {
126 getStack(): ActivationContextInfo[] {
131 const stack = [{
127 const stack = [{
132 name: this._name,
128 name: this._name,
133 service: this._service.toString()
129 service: this._service.toString()
134 }];
130 }];
135
131
136 return this._parent ?
132 return this._parent ?
137 stack.concat(this._parent.getStack()) :
133 stack.concat(this._parent.getStack()) :
138 stack;
134 stack;
139 }
135 }
140
136
141 fail(innerException: unknown): never {
137 fail(innerException: unknown): never {
142 throw new ActivationError(this._name, this.getStack(), innerException);
138 throw new ActivationError(this._name, this.getStack(), innerException);
143 }
139 }
144
140
145 }
141 }
@@ -1,74 +1,64
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ActivationError } from "./ActivationError";
2 import { ActivationError } from "./ActivationError";
3 import { ContainerBuilder } from "./ContainerBuilder";
3 import { ContainerBuilder } from "./ContainerBuilder";
4 import { LifetimeManager } from "./LifetimeManager";
4 import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces";
5 import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces";
5
6
6 export class Container<S> implements ServiceLocator<S>, IDestroyable {
7 export class Container<S> implements ServiceLocator<S>, IDestroyable {
7 private readonly _services: DescriptorMap<S>;
8 private readonly _services: DescriptorMap<S>;
8
9
9 private readonly _lifetimeManager: ILifetimeManager;
10 private readonly _lifetimeManagers: ILifetimeManager[];
10
11
11 private _disposed: boolean;
12 private _disposed: boolean;
12
13
13 private readonly _onDestroyed: () => void;
14 private readonly _onDestroyed: () => void;
14
15
15 constructor(services: DescriptorMap<S>, lifetimeManager: ILifetimeManager, destroyed: () => void) {
16 constructor(services: DescriptorMap<S>, lifetimeManagers: ILifetimeManager[], destroyed: () => void) {
16 this._services = {
17 this._services = services;
17 ...services,
18 container: {
19 configurable: false,
20 activate: () => this
21 },
22 childContainer: {
23 configurable: false,
24 activate: () => this.createChildBuilder(),
25 }
26 };
27
28 this._disposed = false;
18 this._disposed = false;
29 this._lifetimeManager = lifetimeManager;
19 this._lifetimeManagers = lifetimeManagers.concat(new LifetimeManager());
30 this._onDestroyed = destroyed;
20 this._onDestroyed = destroyed;
31
21
32 }
22 }
33
23
34 private _assertNotDestroyed() {
24 private _assertNotDestroyed() {
35 if (this._disposed)
25 if (this._disposed)
36 throw new Error("The container is destroyed");
26 throw new Error("The container is destroyed");
37 }
27 }
38
28
39 createChildBuilder(): IContainerBuilder<S, keyof S> {
29 createChildBuilder(): IContainerBuilder<S, keyof S> {
40 this._assertNotDestroyed();
30 this._assertNotDestroyed();
41
31
42 const lifetime = this._lifetimeManager.create<IDestroyable>();
32 const lifetime = this._lifetimeManager.create<IDestroyable>();
43
33
44 return new ContainerBuilder(this._services, lifetime);
34 return new ContainerBuilder(this._services, lifetime);
45 }
35 }
46
36
47 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
37 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;
38 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) {
39 resolve<K extends keyof S, T>(name: K, def?: T) {
50 this._assertNotDestroyed();
40 this._assertNotDestroyed();
51
41
52 const d = this._services[name];
42 const d = this._services[name];
53 if (d === undefined) {
43 if (d === undefined) {
54 if (arguments.length > 1)
44 if (arguments.length > 1)
55 return def;
45 return def;
56 else
46 else
57 throw new Error(`Service '${String(name)}' isn't found`);
47 throw new Error(`Service '${String(name)}' isn't found`);
58 } else {
48 } else {
59 const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d);
49 const context = new ActivationContext(this._lifetimeManagers, this._services, String(name), d);
60 try {
50 try {
61 return d.activate(context);
51 return d.activate(context);
62 } catch (error) {
52 } catch (error) {
63 throw new ActivationError(name.toString(), context.getStack(), error);
53 throw new ActivationError(name.toString(), context.getStack(), error);
64 }
54 }
65 }
55 }
66 }
56 }
67 destroy() {
57 destroy() {
68 if (this._disposed)
58 if (this._disposed)
69 return;
59 return;
70 this._disposed = true;
60 this._disposed = true;
71 this._lifetimeManager.destroy();
61 this._lifetimeManager.destroy();
72 (0,this._onDestroyed)();
62 (0,this._onDestroyed)();
73 }
63 }
74 }
64 }
@@ -1,66 +1,71
1 import { Container } from "./Container";
1 import { Container } from "./Container";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces";
3 import { containerSelfDescriptor } from "./DescriptorImpl";
4 import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable, ContainerServices, ContainerServicesConstraint } from "./interfaces";
4 import { emptyLifetime, LifetimeManager } from "./LifetimeManager";
5 import { emptyLifetime, LifetimeManager } from "./LifetimeManager";
5 import { isDestroyable, prototype } from "./traits";
6 import { isDestroyable, prototype } from "./traits";
6
7
7 /**
8 /**
8 * Container builder used to prepare service descriptors and create a IoC container
9 * Container builder used to prepare service descriptors and create a IoC container
9 */
10 */
10 export class ContainerBuilder<S, U extends keyof S> implements
11 export class ContainerBuilder<S extends ContainerServicesConstraint<S>, U extends keyof S> implements
11 IContainerBuilder<S, U> {
12 IContainerBuilder<S, U> {
12
13
13 private _pending = 1;
14 private _pending = 1;
14
15
15 private readonly _services: DescriptorMap<S>;
16 private readonly _services: DescriptorMap<ContainerServices<S>>;
16
17
17 private readonly _lifetimeManager = new LifetimeManager();
18 private readonly _lifetimeManager = new LifetimeManager();
18
19
19 private readonly _lifetime: ILifetime<IDestroyable>;
20 private readonly _lifetime: ILifetime<IDestroyable>;
20
21
21 constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) {
22 constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) {
22 this._services = prototype(parentServices);
23 this._services = {
24 ...parentServices,
25 container: containerSelfDescriptor as any,
26 childContainer: containerSelfDescriptor as any
27 };
23 this._lifetimeManager = new LifetimeManager();
28 this._lifetimeManager = new LifetimeManager();
24 this._lifetime = lifetime ?? emptyLifetime();
29 this._lifetime = lifetime ?? emptyLifetime();
25 }
30 }
26 createServiceBuilder<K extends U>(name: K):
31 createServiceBuilder<K extends U>(name: K):
27 IDescriptorBuilder<S, S[K], Record<never, never>, U> {
32 IDescriptorBuilder<S, S[K], Record<never, never>, U> {
28
33
29 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
34 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
30
35
31 }
36 }
32
37
33 build(): ServiceLocator<S> {
38 build(): ServiceLocator<S> {
34 this._assertBuilding();
39 this._assertBuilding();
35 if (!this._complete())
40 if (!this._complete())
36 throw new Error("The configuration didn't complete.");
41 throw new Error("The configuration didn't complete.");
37
42
38 const {remove, store} = this._lifetime(null);
43 const {remove, store} = this._lifetime(null);
39
44
40 const container = new Container(this._services, this._lifetimeManager, remove);
45 const container = new Container(this._services, this._lifetimeManager, remove);
41
46
42 store(container);
47 store(container);
43
48
44 return container;
49 return container;
45 }
50 }
46
51
47 private readonly _register = <K extends U>(name: K) =>
52 private readonly _register = <K extends U>(name: K) =>
48 (descriptor: Descriptor<S, S[K]>) => {
53 (descriptor: Descriptor<S, S[K]>) => {
49 this._complete();
54 this._complete();
50 this._services[name] = descriptor;
55 this._services[name] = descriptor;
51 };
56 };
52
57
53 private readonly _fail = (ex: unknown) => {
58 private readonly _fail = (ex: unknown) => {
54 throw ex;
59 throw ex;
55 };
60 };
56
61
57 private _assertBuilding() {
62 private _assertBuilding() {
58 if (!this._pending)
63 if (!this._pending)
59 throw new Error("The descriptor builder is finalized");
64 throw new Error("The descriptor builder is finalized");
60 }
65 }
61
66
62 private _complete() {
67 private _complete() {
63 return !(--this._pending);
68 return !(--this._pending);
64 }
69 }
65
70
66 } No newline at end of file
71 }
@@ -1,97 +1,104
1 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
1 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
2 import { each, key } from "./traits";
2 import { each, key } from "./traits";
3
3
4 export interface DescriptorImplArgs<S, T> {
4 export interface DescriptorImplArgs<S, T> {
5 lifetime: ILifetime<T>;
5 lifetime: ILifetime<T>;
6
6
7 factory: (refs: Record<key, unknown>) => NonNullable<T>;
7 factory: (refs: Record<key, unknown>) => NonNullable<T>;
8
8
9 cleanup?: (item: NonNullable<T>) => void;
9 cleanup?: (item: NonNullable<T>) => void;
10
10
11 overrides?: DescriptorMap<S>;
11 overrides?: DescriptorMap<S>;
12
12
13 dependencies?: DepsMap<S>;
13 dependencies?: DepsMap<S>;
14 }
14 }
15
15
16 export const containerSelfDescriptor = <S>() => Object.freeze({
17 level: 0,
18 activate(context: IActivationContext<S>) {
19 return context.createChildContainer();
20 }
21 });
22
16
23
17 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
24 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
18
25
19 private readonly _overrides?: DescriptorMap<S>;
26 private readonly _overrides?: DescriptorMap<S>;
20
27
21 private readonly _lifetime: ILifetime<T>;
28 private readonly _lifetime: ILifetime<T>;
22
29
23 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
30 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
24
31
25 private readonly _cleanup?: (item: NonNullable<T>) => void;
32 private readonly _cleanup?: (item: NonNullable<T>) => void;
26
33
27 private readonly _deps?: DepsMap<S>;
34 private readonly _deps?: DepsMap<S>;
28
35
29 readonly hasOverrides: boolean;
36 readonly hasOverrides: boolean;
30
37
31 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
38 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
32 this._lifetime = lifetime;
39 this._lifetime = lifetime;
33 this._factory = factory;
40 this._factory = factory;
34 if (cleanup)
41 if (cleanup)
35 this._cleanup = cleanup;
42 this._cleanup = cleanup;
36 if (overrides)
43 if (overrides)
37 this._overrides = overrides;
44 this._overrides = overrides;
38 if (dependencies)
45 if (dependencies)
39 this._deps = dependencies;
46 this._deps = dependencies;
40
47
41 this.hasOverrides = !!overrides;
48 this.hasOverrides = !!overrides;
42 }
49 }
43
50
44 activate(context: IActivationContext<S>): NonNullable<T> {
51 activate(context: IActivationContext<S>): NonNullable<T> {
45
52
46 const { has, get, initialize, store } = this._lifetime(context);
53 const { has, get, initialize, store } = this._lifetime(context);
47
54
48 if (has())
55 if (has())
49 return get();
56 return get();
50
57
51 initialize();
58 initialize();
52
59
53 if (this._overrides)
60 if (this._overrides)
54 each(this._overrides, (v, k) => context.register(k, v));
61 each(this._overrides, (v, k) => context.register(k, v));
55
62
56 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => {
63 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => {
57 if (lazy) {
64 if (lazy) {
58 return "default" in opts ?
65 return "default" in opts ?
59 () => context.resolve(name, opts.default) :
66 () => context.resolve(name, opts.default) :
60 () => context.resolve(name);
67 () => context.resolve(name);
61 } else {
68 } else {
62 return "default" in opts ?
69 return "default" in opts ?
63 context.resolve(name, opts.default) :
70 context.resolve(name, opts.default) :
64 context.resolve(name);
71 context.resolve(name);
65 }
72 }
66 };
73 };
67
74
68 const deps = this._deps;
75 const deps = this._deps;
69
76
70 const refs = deps ?
77 const refs = deps ?
71 Object.keys(deps)
78 Object.keys(deps)
72 .map(k => {
79 .map(k => {
73 const ref = deps[k];
80 const ref = deps[k];
74 return typeof ref !== "object" ?
81 return typeof ref !== "object" ?
75 { [k]: resolve({ name: ref }) } :
82 { [k]: resolve({ name: ref }) } :
76 { [k]: resolve(ref) };
83 { [k]: resolve(ref) };
77 })
84 })
78 .reduce((a, p) => ({ ...a, ...p }), {}) :
85 .reduce((a, p) => ({ ...a, ...p }), {}) :
79 {};
86 {};
80
87
81 try {
88 try {
82 // call the factory method
89 // call the factory method
83 const instance = (0,this._factory)(refs);
90 const instance = (0,this._factory)(refs);
84
91
85 // store the instance
92 // store the instance
86 store(instance, this._cleanup);
93 store(instance, this._cleanup);
87 return instance;
94 return instance;
88 } catch(err) {
95 } catch(err) {
89 context.fail(err);
96 context.fail(err);
90 }
97 }
91 }
98 }
92
99
93
100
94 toString() {
101 toString() {
95 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
102 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
96 }
103 }
97 }
104 }
@@ -1,170 +1,174
1 import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
1 import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
2 import { argumentNotNull, isDestroyable } from "./traits";
2 import { argumentNotNull, isDestroyable } from "./traits";
3
3
4 const safeCall = (item: () => void) => {
4 const safeCall = (item: () => void) => {
5 try {
5 try {
6 item();
6 item();
7 } catch {
7 } catch {
8 // silence!
8 // silence!
9 }
9 }
10 };
10 };
11
11
12 const noop = () => { };
12 const noop = () => { };
13
13
14 const fail = (message: string) => (): never => {
14 const fail = (message: string) => (): never => {
15 throw new Error(message);
15 throw new Error(message);
16 };
16 };
17
17
18 const _emptySlot = Object.freeze({
18 const _emptySlot = Object.freeze({
19 has: () => false,
19 has: () => false,
20
20
21 initialize: noop,
21 initialize: noop,
22
22
23 get: fail("The specified item isn't registered with a lifetime manager"),
23 get: fail("The specified item isn't registered with a lifetime manager"),
24
24
25 store: noop,
25 store: noop,
26
26
27 remove: noop,
27 remove: noop,
28
28
29 cleanup: noop,
29 cleanup: noop,
30 });
30 });
31
31
32 const _destroy = (item: IDestroyable) => item.destroy();
32 const _destroy = (item: unknown) => () => isDestroyable(item) && item.destroy() ;
33
33
34 const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) =>
34 const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) =>
35 cleanup ? () => cleanup(value) :
35 cleanup ? () => cleanup(value) : _destroy(value);
36 isDestroyable(value) ? () => _destroy(value) :
37 noop;
38
36
39 const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
37 const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
40 has: () => false,
38 has: () => false,
41
39
42 initialize: () => put(pendingSlot(put, remove)),
40 initialize: () => put(pendingSlot(put, remove)),
43
41
44 get: fail("The slot doesn't hold a value"),
42 get: fail("The slot doesn't hold a value"),
45
43
46 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
44 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
47
45
48 remove: noop,
46 remove: noop,
49
47
50 cleanup: noop,
48 cleanup: noop,
51 });
49 });
52
50
53 const pendingSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
51 const pendingSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
54 has: () => false,
52 has: () => false,
55
53
56 get: fail("The value in this slot doesn't exist"),
54 get: fail("The value in this slot doesn't exist"),
57
55
58 initialize: fail("Cyclic reference detected"),
56 initialize: fail("Cyclic reference detected"),
59
57
60 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
58 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
61
59
62 remove,
60 remove,
63
61
64 cleanup: noop
62 cleanup: noop
65 });
63 });
66
64
67 const valueSlot = <T>(value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({
65 const valueSlot = <T>(value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({
68 has: () => true,
66 has: () => true,
69
67
70 get: () => value,
68 get: () => value,
71
69
72 initialize: fail("The slot already has a value"),
70 initialize: fail("The slot already has a value"),
73
71
74 store: fail("The slot already has a value"),
72 store: fail("The slot already has a value"),
75
73
76 cleanup: _makeCleanup(value, cleanup),
74 cleanup: _makeCleanup(value, cleanup),
77
75
78 remove: remove
76 remove: remove
79 });
77 });
80
78
81 export class LifetimeManager implements ILifetimeManager {
79 export class LifetimeManager implements ILifetimeManager {
82 private _destroyed = false;
80 private _destroyed = false;
83
81
84 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
82 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
85
83
86 slot<T>(cookie: string | number): ILifetimeSlot<T> {
84 slot<T>(cookie: string | number): ILifetimeSlot<T> {
87 if (cookie in this._slots)
85 if (cookie in this._slots)
88 return this._slots[cookie] as ILifetimeSlot<T>;
86 return this._slots[cookie] as ILifetimeSlot<T>;
89
87
90 return newSlot<T>(this._put(cookie), this._remove(cookie));
88 return newSlot<T>(this._put(cookie), this._remove(cookie));
91 }
89 }
92
90
93 private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => {
91 private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => {
94 this._assertNotDestroyed();
92 this._assertNotDestroyed();
95 this._slots[id] = slot as ILifetimeSlot<unknown>;
93 this._slots[id] = slot as ILifetimeSlot<unknown>;
96 };
94 };
97
95
98 private readonly _remove = (id: string | number) => () => {
96 private readonly _remove = (id: string | number) => () => {
99 this._assertNotDestroyed();
97 this._assertNotDestroyed();
100 delete this._slots[id];
98 delete this._slots[id];
101 };
99 };
102
100
103 private _assertNotDestroyed() {
101 private _assertNotDestroyed() {
104 if (this._destroyed)
102 if (this._destroyed)
105 throw new Error("The lifetime manager is destroyed");
103 throw new Error("The lifetime manager is destroyed");
106
104
107 }
105 }
108
106
109 destroy() {
107 destroy() {
110 if (!this._destroyed) {
108 if (!this._destroyed) {
111 this._destroyed = true;
109 this._destroyed = true;
112 Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup));
110 Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup));
113 }
111 }
114 }
112 }
115
113
116 }
114 }
117
115
118 export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>;
116 export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>;
119
117
120 let nextId = 1;
118 let nextId = 1;
121
119
120 export const containerLifetime = <T>() => {
121 const slotId = nextId ++;
122 return (context: ILifetimeContext) =>
123 context.ownerSlot<T>(slotId);
124 };
125
122 export const hierarchyLifetime = <T>() => {
126 export const hierarchyLifetime = <T>() => {
123 const slotId = nextId++;
127 const slotId = nextId++;
124 return (context: ILifetimeContext) =>
128 return (context: ILifetimeContext) =>
125 context.containerSlot<T>(slotId);
129 context.containerSlot<T>(slotId);
126 };
130 };
127
131
128 /**
132 /**
129 * Creates a lifetime instance bound to the current activation context. This
133 * Creates a lifetime instance bound to the current activation context. This
130 * lifetime will store the service instance per activation context. Every
134 * lifetime will store the service instance per activation context. Every
131 * top level service resolution will create a new activation context. This
135 * top level service resolution will create a new activation context. This
132 * context is propagated to subsequent service resolution thus all services
136 * context is propagated to subsequent service resolution thus all services
133 * with context lifetime will be shared among their consumers.
137 * with context lifetime will be shared among their consumers.
134 *
138 *
135 * @returns The instance of the lifetime.
139 * @returns The instance of the lifetime.
136 */
140 */
137 export const contextLifetime = <T>() => {
141 export const contextLifetime = <T>() => {
138 const slotId = nextId++;
142 const slotId = nextId++;
139
143
140 return (context: ILifetimeContext) =>
144 return (context: ILifetimeContext) =>
141 context.contextSlot<T>(slotId);
145 context.contextSlot<T>(slotId);
142 };
146 };
143
147
144 const singletons = new LifetimeManager();
148 const singletons = new LifetimeManager();
145
149
146 /**
150 /**
147 * Creates the lifetime for the service which will allow existence only one
151 * Creates the lifetime for the service which will allow existence only one
148 * instance with the specified {@linkcode typeId}. If there will be created
152 * instance with the specified {@linkcode typeId}. If there will be created
149 * several lifetime instances with same `typeId` in the runtime, they will
153 * several lifetime instances with same `typeId` in the runtime, they will
150 * share the same service instance.
154 * share the same service instance.
151 *
155 *
152 * @param typeId The identified for the global instance, usually this is a
156 * @param typeId The identified for the global instance, usually this is a
153 * fully qualified class name
157 * fully qualified class name
154 * @returns The lifetime instance
158 * @returns The lifetime instance
155 */
159 */
156 export const singletonLifetime = <T>(typeId: string) => {
160 export const singletonLifetime = <T>(typeId: string) => {
157 argumentNotNull(typeId, "typeId");
161 argumentNotNull(typeId, "typeId");
158
162
159 return () => singletons.slot<T>(typeId);
163 return () => singletons.slot<T>(typeId);
160 };
164 };
161
165
162 /** Creates a lifetime bound to the specified container. Using this lifetime
166 /** Creates a lifetime bound to the specified container. Using this lifetime
163 * will create a single service instance per the specified container.
167 * will create a single service instance per the specified container.
164 *
168 *
165 * @param container The container which will manage the lifetime for the service
169 * @param container The container which will manage the lifetime for the service
166 */
170 */
167 export const containerLifetime = <T>(manager: ILifetimeManager) => {
171 export const containerLifetime = <T>(manager: ILifetimeManager) => {
168 const slotId = nextId++;
172 const slotId = nextId++;
169 return () => manager.slot<T>(slotId);
173 return () => manager.slot<T>(slotId);
170 };
174 };
@@ -1,265 +1,277
1 import { ContainerBuilder } from "./ContainerBuilder";
1 import { key } from "./traits";
2 import { key } from "./traits";
2
3
3 export interface IDestroyable {
4 export interface IDestroyable {
4 destroy(): void;
5 destroy(): void;
5 }
6 }
6
7
7 /**
8 /**
8 * @template S Карта доступных зависимостей
9 * @template S Карта доступных зависимостей
9 */
10 */
10 export interface Resolver<S> {
11 export interface Resolver<S> {
11 /**
12 /**
12 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
13 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
13 * отложенную активацию и значение по-умолчанию для сервисов
14 * отложенную активацию и значение по-умолчанию для сервисов
14 * @template K Ключ сервиса из {@linkcode S}
15 * @template K Ключ сервиса из {@linkcode S}
15 * @template O Тип параметра {@linkcode opts} используется для выведения типа
16 * @template O Тип параметра {@linkcode opts} используется для выведения типа
16 * возвращаемого значения.
17 * возвращаемого значения.
17 * @param name Ключ сервиса, который будет разрешен.
18 * @param name Ключ сервиса, который будет разрешен.
18 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
19 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
19 * будет возвращен фабричный метод для получения зависимости. Если не указан,
20 * будет возвращен фабричный метод для получения зависимости. Если не указан,
20 * то считается `false`.
21 * то считается `false`.
21 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
22 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
22 * сервис не зарегистрирован
23 * сервис не зарегистрирован
23 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
24 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
24 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
25 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
25 */
26 */
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: 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 <K extends keyof S, O extends { lazy?: false; }>(name: K, opts?: O): NonNullable<S[K]> | InferDefault<O>;
28 }
29 }
29
30
30 export type DepsMap<S> = {
31 export type DepsMap<S> = {
31 [k in key]: Refs<S> | keyof S;
32 [k in key]: Refs<S> | keyof S;
32 };
33 };
33
34
34 export type Refs<S> = {
35 export type Refs<S> = {
35 [k in keyof S]: Ref<k, S[k]>;
36 [k in keyof S]: Ref<k, S[k]>;
36 }[keyof S];
37 }[keyof S];
37
38
38 export type Ref<K extends key, D> = {
39 export type Ref<K extends key, D> = {
39 /** The name of the service */
40 /** The name of the service */
40 name: K;
41 name: K;
41
42
42 /** Make a lazy reference, the resolved dependency will be a function */
43 /** Make a lazy reference, the resolved dependency will be a function */
43 lazy?: boolean;
44 lazy?: boolean;
44
45
45 /** The default value for the case where the service isn't defined.
46 /** 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 * When specified the dependency becomes optional, the default value can be
47 * `null` or `undefined`
48 * `null` or `undefined`
48 */
49 */
49 default?: D | null
50 default?: D | null
50 };
51 };
51
52
52 export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
53 export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
53
54
54 /** Возвращает тип свойства `default` в типе {@link T} */
55 /** Возвращает тип свойства `default` в типе {@link T} */
55 export type InferDefault<T> = T extends { default: infer D } ? D : never;
56 export type InferDefault<T> = T extends { default: infer D } ? D : never;
56
57
57 export type InferLazy<R> = R extends { lazy: infer L } ?
58 export type InferLazy<R> = R extends { lazy: infer L } ?
58 L extends true ? true : false :
59 L extends true ? true : false :
59 false;
60 false;
60 export type Resolve<S, R> =
61 export type Resolve<S, R> =
61 R extends keyof S ? NonNullable<S[R]> :
62 R extends keyof S ? NonNullable<S[R]> :
62 R extends Ref<infer K, unknown> ?
63 R extends Ref<infer K, unknown> ?
63 K extends keyof S ?
64 K extends keyof S ?
64 Lazy<NonNullable<S[K]> | InferDefault<R>, InferLazy<R>> :
65 Lazy<NonNullable<S[K]> | InferDefault<R>, InferLazy<R>> :
65 never :
66 never :
66 never;
67 never;
67
68
68 /**
69 /**
69 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
70 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
70 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
71 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
71 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
72 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
72 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
73 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
73 * {@linkcode value}.
74 * {@linkcode value}.
74 *
75 *
75 * @template S Карта сервисов контейнера, доступных при описании дескриптора
76 * @template S Карта сервисов контейнера, доступных при описании дескриптора
76 * @template T Тип сервиса
77 * @template T Тип сервиса
77 * @template R Карта зависимостей, которая передается параметром фабрике
78 * @template R Карта зависимостей, которая передается параметром фабрике
78 * @template U Имена пользовательских сервисов, доступных для переопределения
79 * @template U Имена пользовательских сервисов, доступных для переопределения
79 */
80 */
80 export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
81 export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
81
82
82 /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
83 /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
83 * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
84 * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
84 * которые были предварительно указаны вызовами метода `wants(...)`
85 * которые были предварительно указаны вызовами метода `wants(...)`
85 *
86 *
86 * Вызов данного метода завершает конфигурирование сервиса.
87 * Вызов данного метода завершает конфигурирование сервиса.
87 *
88 *
88 * @param f Фабрика для создания экземпляра сервиса.
89 * @param f Фабрика для создания экземпляра сервиса.
89 */
90 */
90 factory(f: (refs: R) => NonNullable<T>): void;
91 factory(f: (refs: R) => NonNullable<T>): void;
91
92
92 /**
93 /**
93 * Используется для указания зависимостей, которые потребуются фабричному
94 * Используется для указания зависимостей, которые потребуются фабричному
94 * методу при создании нового экземпляра сервиса. Данный метод может быть
95 * методу при создании нового экземпляра сервиса. Данный метод может быть
95 * вызван несколько раз подряд, при этом вызовы этого метода имеют
96 * вызван несколько раз подряд, при этом вызовы этого метода имеют
96 * кумулятивный эффект.
97 * кумулятивный эффект.
97 *
98 *
98 * @template X Тип объекта с зависимостями, которые требуется получить при
99 * @template X Тип объекта с зависимостями, которые требуется получить при
99 * создании экземпляра фабрики при помощи фабричного метода.
100 * создании экземпляра фабрики при помощи фабричного метода.
100 * @param refs Объект с описанием зависимостей
101 * @param refs Объект с описанием зависимостей
101 * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
102 * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
102 * зависимости
103 * зависимости
103 */
104 */
104 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
105 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
105 IDescriptorBuilder<S, T, R & {
106 IDescriptorBuilder<S, T, R & {
106 [k in keyof X]: Resolve<S, X[k]>;
107 [k in keyof X]: Resolve<S, X[k]>;
107 }, U>
108 }, U>
108
109
109 override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
110 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;
111 override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;
111
112
112 lifetime(lifetime: "singleton", typeId: string | number | object): this;
113 lifetime(lifetime: "singleton", typeId: string | number | object): this;
113 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
114 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
114
115
115 /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
116 /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
116 * время жизни привязано к контейнеру.
117 * время жизни привязано к контейнеру.
117 */
118 */
118 cleanup(cb: (item: T) => void): this;
119 cleanup(cb: (item: T) => void): this;
119
120
120 /**
121 /**
121 * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
122 * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
122 *
123 *
123 * @param v Экземпляр реализации сервиса.
124 * @param v Экземпляр реализации сервиса.
124 */
125 */
125 value(v: NonNullable<T>): void;
126 value(v: NonNullable<T>): void;
126 }
127 }
127
128
128 export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
129 export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
129
130
130 /**
131 /**
131 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
132 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
132 *
133 *
133 * Все параметры конфигурации являются обязательными, если требуется ввести
134 * Все параметры конфигурации являются обязательными, если требуется ввести
134 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
135 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
135 *
136 *
136 * @template S Сервисы доступные в контейнере
137 * @template S Сервисы доступные в контейнере
137 * @template K Сервисы участвующие в конфигурации
138 * @template K Сервисы участвующие в конфигурации
138 */
139 */
139 export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
140 export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
140 [k in K]-?: BuildDescriptorFn<S, S[k], U>
141 [k in K]-?: BuildDescriptorFn<S, S[k], U>
141 };
142 };
142
143
143 export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
144 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 [k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
145 };
146 };
146
147
147 /**
148 /**
148 * The type constraint useful to restrict type parameters to prevent defining
149 * The type constraint useful to restrict type parameters to prevent defining
149 * the services with the {@link ContainerKeys} names.
150 * the services with the {@link ContainerKeys} names.
150 *
151 *
151 * The constraint doesn't exclude using this keys but declares them as `never`
152 * 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 * which effectively will lead using this keys to the error.
153 */
154 */
154 export type ContainerServicesConstraint<S> = {
155 export type ContainerServicesConstraint<S> = {
155 [k in keyof S]: k extends ContainerKeys ? never : S[k];
156 [k in keyof S]: k extends ContainerKeys ? never : S[k];
156 };
157 };
157
158
158 export interface Descriptor<S, T> {
159 export interface Descriptor<S, T> {
159
160
161 /** The level of the service in the containers chain.
162 */
163 readonly level: number;
164
160 /** This flags indicates that this registration can be replaced or overridden. */
165 /** This flags indicates that this registration can be replaced or overridden. */
161 readonly configurable?: boolean;
166 readonly configurable?: boolean;
162
167
163 /** If specified signals the activation context that a new service scope
168 /** If specified signals the activation context that a new service scope
164 * should be created to isolate service overrides.
169 * should be created to isolate service overrides.
165 */
170 */
166 readonly hasOverrides?: boolean;
171 readonly hasOverrides?: boolean;
167
172
168 activate(context: IActivationContext<S>): NonNullable<T>;
173 activate(context: IActivationContext<S>): NonNullable<T>;
169 }
174 }
170
175
171 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
176 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
172 export interface ILifetimeContext {
177 export interface ILifetimeContext {
178
179 ownerSlot<T>(slotId: string | number): ILifetimeSlot<T>;
180
173 contextSlot<T>(slotId: string | number): ILifetimeSlot<T>;
181 contextSlot<T>(slotId: string | number): ILifetimeSlot<T>;
174
182
175 containerSlot<T>(slotId: string | number): ILifetimeSlot<T>;
183 containerSlot<T>(slotId: string | number): ILifetimeSlot<T>;
176
184
177 }
185 }
178
186
179 export interface IActivationContext<S> extends ILifetimeContext, ServiceLocator<S> {
187 export interface IActivationContext<S> extends ILifetimeContext, ServiceLocator<S> {
180
188
181 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
189 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
182
190
183 fail(error: unknown): never;
191 fail(error: unknown): never;
192
193 selfContainer(): ServiceLocator<S>;
194
195 createChildContainer(): IContainerBuilder<S, Exclude<keyof S, ContainerKeys>>;
184 }
196 }
185
197
186 /**
198 /**
187 * Descriptors map for the specified services {@linkcode S}. All entries are
199 * Descriptors map for the specified services {@linkcode S}. All entries are
188 * optional regardless the required or optional services in the original map.
200 * optional regardless the required or optional services in the original map.
189 *
201 *
190 * @template S Сервисы контекста активации
202 * @template S Сервисы контекста активации
191 * @template U Карта сервисов которые создаются дескрипторами
203 * @template U Карта сервисов которые создаются дескрипторами
192 */
204 */
193 export type DescriptorMap<S> = {
205 export type DescriptorMap<S> = {
194 [k in keyof S]?: Descriptor<S, S[k]>;
206 [k in keyof S]?: Descriptor<S, S[k]>;
195 };
207 };
196
208
197 type ContainerKeys = keyof ContainerProvided<object>;
209 type ContainerKeys = keyof ContainerProvided<object>;
198
210
199 export type ContainerProvided<S extends ContainerServicesConstraint<S>> = {
211 export type ContainerProvided<S, U extends keyof S = keyof S> = {
200 container: ServiceLocator<ContainerServices<S>>;
212 container: ServiceLocator<ContainerProvided<S>>;
201
213
202 childContainer: IContainerBuilder<ContainerServices<S>, Exclude<keyof S, ContainerKeys>>;
214 childContainer: IContainerBuilder<S, U>;
203 };
215 };
204
216
205
217
206 /**
218 /**
207 * Таблица сервисов, которые предоставляет контейнер.
219 * Таблица сервисов, которые предоставляет контейнер.
208 *
220 *
209 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
221 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
210 */
222 */
211 export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
223 export type ContainerServices<S> = {
212 [k in keyof S | ContainerKeys]:
224 [k in (keyof S) | ContainerKeys]:
213 k extends ContainerKeys ? ContainerProvided<S>[k] :
225 k extends ContainerKeys ? ContainerProvided<S>[k] :
214 k extends keyof S ? S[k] : never
226 k extends keyof S ? S[k] : never
215 };
227 };
216
228
217
229
218 /**
230 /**
219 * Returns the service declared in the type map {@link S}.
231 * Returns the service declared in the type map {@link S}.
220 *
232 *
221 *
233 *
222 */
234 */
223 export interface ServiceLocator<S> {
235 export interface ServiceLocator<S> {
224 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
236 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
225 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
237 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
226 }
238 }
227
239
228
240
229 export interface IContainerBuilder<S, U extends keyof S> {
241 export interface IContainerBuilder<S, U extends keyof S> {
230 createServiceBuilder<K extends U>(name: K):
242 createServiceBuilder<K extends U>(name: K):
231 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
243 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
232
244
233 build(): ServiceLocator<S>;
245 build(): ServiceLocator<S>;
234 }
246 }
235
247
236
248
237 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
249 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
238
250
239 /**
251 /**
240 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
252 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
241 * свой собственный объект `ILifetime`, который создается при первой активации
253 * свой собственный объект `ILifetime`, который создается при первой активации
242 */
254 */
243 export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<NonNullable<T>>;
255 export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<NonNullable<T>>;
244
256
245 export interface ILifetimeSlot<T> {
257 export interface ILifetimeSlot<T> {
246 readonly has: () => boolean;
258 readonly has: () => boolean;
247
259
248 readonly get: () => T;
260 readonly get: () => T;
249
261
250 readonly initialize: () => void;
262 readonly initialize: () => void;
251
263
252 readonly store: (item: T, cleanup?: (item: T) => void) => void;
264 readonly store: (item: T, cleanup?: (item: T) => void) => void;
253
265
254 readonly remove: () => void;
266 readonly remove: () => void;
255
267
256 readonly cleanup: () => void;
268 readonly cleanup: () => void;
257 }
269 }
258
270
259 export interface ILifetimeManager extends IDestroyable {
271 export interface ILifetimeManager extends IDestroyable {
260 slot<T>(id: string | number): ILifetimeSlot<T>;
272 slot<T>(id: string | number): ILifetimeSlot<T>;
261 }
273 }
262
274
263 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
275 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
264
276
265 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
277 export type ExtractRequiredKeys<T, K extends keyof T = keyof T> = { [p in K]-?: undefined extends T[p] ? never : p }[K];
@@ -1,107 +1,107
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 { ContainerBuilder } from "../ContainerBuilder";
4 import { ContainerBuilder } from "../ContainerBuilder";
5 import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver } from "../interfaces";
5 import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver, ServiceLocator } from "../interfaces";
6 import { fluent } from "../traits";
6 import { fluent } from "../traits";
7
7
8 class Foo {
8 class Foo {
9 foo = "foo";
9 foo = "foo";
10 }
10 }
11
11
12 class Bar {
12 class Bar {
13 bar = "bar";
13 bar = "bar";
14
14
15 constructor(foo?: () => Foo) { }
15 constructor(foo?: () => Foo) { }
16 }
16 }
17
17
18 interface Services {
18 interface Services {
19 foo: Foo;
19 foo: Foo;
20
20
21 bar?: Bar;
21 bar?: Bar;
22
22
23 baz: Foo;
23 baz: Foo;
24
24
25 box?: Foo;
25 box?: Foo;
26
26
27 //container: string;
27 //container: string;
28 }
28 }
29
29
30 interface ServicesB {
30 interface ServicesB {
31 // will give errors
31 // will give errors
32 // baz: Bar;
32 // baz: Bar;
33
33
34 baz: Foo;
34 baz: Foo;
35
35
36 zoo?: Foo;
36 zoo?: Foo;
37 }
37 }
38
38
39 declare const resolver: Resolver<Services>;
39 declare const resolver: Resolver<Services>;
40
40
41 const foo = resolver("foo", { lazy: true, default: null });
41 const foo = resolver("foo", { lazy: true, default: null });
42
42
43 const mmap = <X extends DepsMap<Services>>(m: X) => { };
43 const mmap = <X extends DepsMap<Services>>(m: X) => { };
44
44
45 declare const refs: Refs<Services>;
45 declare const refs: Refs<Services>;
46
46
47 if (refs && refs.name === "foo") {
47 if (refs && refs.name === "foo") {
48 refs.default;
48 refs.default;
49 }
49 }
50
50
51 declare const x: ContainerServices<Services>;
51 declare const x: ServiceLocator<ContainerServices<Services>>;
52
52
53 x.container.resolve("container");
53 x.resolve("container").resolve("container");
54
54
55
55
56 mmap({
56 mmap({
57 fooz: { name: "foo", lazy: false, default: undefined },
57 fooz: { name: "foo", lazy: false, default: undefined },
58 ooz: "bar"
58 ooz: "bar"
59 });
59 });
60
60
61 interface SharedServices {
61 interface SharedServices {
62 foo: Foo;
62 foo: Foo;
63
63
64 bar?: Bar;
64 bar?: Bar;
65
65
66 baz: Bar;
66 baz: Bar;
67 }
67 }
68
68
69 const config = fluent<Services>()
69 const config = fluent<Services>()
70 .declare<ServicesB>()
70 .declare<ServicesB>()
71 .register({
71 .register({
72 zoo: it => it.value(new Foo()),
72 zoo: it => it.value(new Foo()),
73 bar: it => it
73 bar: it => it
74 .lifetime("context") // тип активации, время жизни
74 .lifetime("context") // тип активации, время жизни
75 .wants({
75 .wants({
76 self: "container",
76 self: "container",
77 childContainer: "childContainer",
77 childContainer: "childContainer",
78 bar: "bar",
78 bar: "bar",
79 foo$: { name: "foo", lazy: true } // отложенная активация, фабричный метод
79 foo$: { name: "foo", lazy: true } // отложенная активация, фабричный метод
80 })
80 })
81 .override({ // переопределение сервиса
81 .override({ // переопределение сервиса
82 box: it => it.factory(() => new Foo())
82 box: it => it.factory(() => new Foo())
83
83
84 })
84 })
85 .factory(({ foo$, bar, self, childContainer }) => // фабрика получает объект с именованными зависимостями
85 .factory(({ foo$, bar, self, childContainer }) => // фабрика получает объект с именованными зависимостями
86 new Bar(foo$) // создается экземпляр сервиса
86 new Bar(foo$) // создается экземпляр сервиса
87 ),
87 ),
88 foo: it => it.factory(() => new Foo()),
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 //box: it => it.factory(() => new Foo())
91 })
91 })
92 .done();
92 .done();
93
93
94 declare const container: IContainerBuilder<ContainerServices<Services>, keyof Services>;
94 declare const container: IContainerBuilder<ContainerServices<Services>, keyof Services>;
95
95
96 const v = container.build().resolve("foo");
96 const v = container.build().resolve("foo");
97 if (v) {
97 if (v) {
98 // noop
98 // noop
99 }
99 }
100
100
101 const c2 = config.configure(container);
101 const c2 = config.configure(container);
102
102
103 c2.resolve("foo");
103 c2.resolve("foo");
104
104
105 declare const m: ContainerServices<{ foo?: Foo }>["container"];
105 declare const m: ContainerServices<{ foo?: Foo }>["container"];
106
106
107 m.resolve("container").resolve("container").resolve("foo"); No newline at end of file
107 m.resolve("container").resolve("container").resolve("foo");
General Comments 0
You need to be logged in to leave comments. Login now