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