##// END OF EJS Templates
sync
cin -
r15:3985e8405319 tip default
parent child
Show More
@@ -0,0 +1,61
1 import { ILifetimeSlot } from "../typings/interfaces";
2 import { isDestroyable } from "./traits";
3
4 const _destroy = (item: unknown) => () => isDestroyable(item) && item.destroy();
5
6 enum State {
7 New,
8
9 Initialized,
10
11 Destroyed
12 }
13
14 export class LifetimeSlot<T> implements ILifetimeSlot<T> {
15 private _value: NonNullable<T> | undefined = undefined;
16
17 private _state = State.New;
18
19 cleanup: () => void;
20
21 readonly remove: () => void;
22
23 constructor(remove: () => void) {
24 this.remove = this._finalizer(remove);
25 this.cleanup = this._finalizer(() => { });
26 }
27
28 has(): boolean {
29 this._assertNotDisposed();
30 return this._value !== undefined;
31 }
32 get(): NonNullable<T> {
33 this._assertNotDisposed();
34 if (this._value === undefined)
35 throw new Error("The slot doesn't have a value");
36 return this._value;
37 }
38
39 initialize(): boolean {
40 return this._state === State.New ?
41 (this._state = State.Initialized, true) :
42 false;
43 }
44
45 store(item: NonNullable<T>, cleanup: ((item: NonNullable<T>) => void) | undefined = _destroy): void {
46 this._assertNotDisposed();
47 this._value = item;
48 if (cleanup)
49 this.cleanup = this._finalizer(() => cleanup(item));
50 }
51
52 private _finalizer(cb: () => void) {
53 return () => this._state !== State.Destroyed ? (this._state = State.Destroyed, cb()) : void (0);
54 }
55
56 private _assertNotDisposed() {
57 if (this._state === State.Destroyed)
58 throw new Error("The slot is disposed");
59 }
60
61 } No newline at end of file
@@ -1,71 +1,70
1 plugins {
1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.4"
2 id "org.implab.gradle-typescript" version "1.3.4"
3 id "org.implab.gradle-mercurial" version "1.0.1"
3 id "org.implab.gradle-mercurial" version "1.0.1"
4 }
4 }
5
5
6 if (!symbols in ['local', 'pack', 'none'])
6 if (!symbols in ['local', 'pack', 'none'])
7 throw new Exception("The symbols property value is invalid: $symbols");
7 throw new Exception("The symbols property value is invalid: $symbols");
8
8
9 if (!flavour in ['browser', 'node'])
9 if (!flavour in ['browser', 'node'])
10 throw new Exception("The flavour property value is invalid: $flavour");
10 throw new Exception("The flavour property value is invalid: $flavour");
11
11
12 ext {
12 ext {
13 packageName = flavour == 'browser' ? "@$npmScope/$name-amd" : "@$npmScope/$name"
13 packageName = flavour == 'browser' ? "@$npmScope/$name-amd" : "@$npmScope/$name"
14 lint = project.hasProperty('lint') ? project.lint ?: true : false
14 lint = project.hasProperty('lint') ? project.lint ?: true : false
15 }
15 }
16
16
17 mercurial {
17 mercurial {
18 preReleasePolicy { it
18 preReleasePolicy { it
19 .addPatch(versionDistance)
19 .addPatch(versionDistance)
20 .withPreRelease('dev')
20 .withPreRelease('dev')
21 .withMeta(changeset)
21 .withMeta(changeset)
22 }
22 }
23
23
24 applyVersioningPolicy()
24 applyVersioningPolicy()
25 }
25 }
26
26
27 typescript {
27 typescript {
28 compilerOptions {
28 compilerOptions {
29 types = []
29 types = []
30 declaration = true
30 declaration = true
31 experimentalDecorators = true
31 experimentalDecorators = true
32 strict = true
32 strict = true
33 module = "commonjs"
33 module = "nodenext"
34 target = "es5"
34 target = "ESNext"
35 lib = ["es2015", "dom", "scripthost"]
36 }
35 }
37 tscCmd = "$projectDir/node_modules/.bin/tsc"
36 tscCmd = "$projectDir/node_modules/.bin/tsc"
38 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
37 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
39 esLintCmd = "$projectDir/node_modules/.bin/eslint"
38 esLintCmd = "$projectDir/node_modules/.bin/eslint"
40 }
39 }
41
40
42 tasks.matching{ it.name =~ /^lint/ }.configureEach {
41 tasks.matching{ it.name =~ /^lint/ }.configureEach {
43 onlyIf { lint }
42 onlyIf { lint }
44 }
43 }
45
44
46 if (symbols == 'local') {
45 if (symbols == 'local') {
47 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
48 compilerOptions {
47 compilerOptions {
49 sourceRoot = "file://" + it.rootDir
48 sourceRoot = "file://" + it.rootDir
50 }
49 }
51 }
50 }
52 }
51 }
53
52
54 task packSources {
53 task packSources {
55
54
56 }
55 }
57
56
58 printVersion {
57 printVersion {
59 doLast {
58 doLast {
60 println "packageName: $packageName";
59 println "packageName: $packageName";
61 println "flavour: $flavour";
60 println "flavour: $flavour";
62 println "target: $typescript.compilerOptions.target";
61 println "target: $typescript.compilerOptions.target";
63 println "module: $typescript.compilerOptions.module";
62 println "module: $typescript.compilerOptions.module";
64 println "lint: $lint";
63 println "lint: $lint";
65 println "symbols: $symbols";
64 println "symbols: $symbols";
66 }
65 }
67 }
66 }
68
67
69 test {
68 test {
70 commandLine "npx", "mocha", "${->typescript.testDir.get()}";
69 commandLine "npx", "mocha", "${->typescript.testDir.get()}";
71 } No newline at end of file
70 }
@@ -1,153 +1,111
1 import { ActivationError } from "./ActivationError";
1 import { ActivationError, ActivationItem } from "./ActivationError";
2 import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager, ILifetimeSlot } from "./interfaces";
2 import { IActivationContext, DescriptorMap, ILifetimeManager, ILifetimeSlot, ServiceLocator, IContainerBuilder } from "../typings/interfaces";
3 import { argumentNotNull, prototype } from "./traits";
3 import { argumentNotNull, each, prototype } from "./traits";
4
4 import { LifetimeManager } from "./LifetimeManager";
5 export interface ActivationContextInfo {
5 import { ContainerBuilder } from "./ContainerBuilder";
6 name: string;
7
8 service: string;
9
10 }
11
12 let nextId = 1;
13
6
14 /** This object is created once per `Container.resolve` method call and used to
7 /** This object is created once per `Container.resolve` method call and used to
15 * cache dependencies and to track created instances. The activation context
8 * cache dependencies and to track created instances. The activation context
16 * tracks services with `context` activation type.
9 * tracks services with `context` activation type.
17 *
10 *
18 * @template S The service map used in the activation context, services from
11 * @template S The service map used in the activation context, services from
19 * this map are available to resolution.
12 * this map are available to resolution.
20 * @template U A set of keys from the service map which can be overridden in
13 * @template U A set of keys from the service map which can be overridden in
21 * this activation context.
14 * this activation context.
22 */
15 */
23 export class ActivationContext<S> implements IActivationContext<S> {
16 export class ActivationContext<S> implements IActivationContext<S> {
24 private readonly _cache: Record<string, unknown>;
25
17
26 private readonly _services: DescriptorMap<S>;
18 private readonly _container: ServiceLocator<S>;
27
28 private readonly _name: string;
29
19
30 private readonly _service: Descriptor<S, unknown>;
20 private readonly _contextScope: ILifetimeManager;
31
21
32 private readonly _lifetimeManagers: ILifetimeManager[];
22 private _services: DescriptorMap<S>;
33
23
34 private readonly _parent: ActivationContext<S> | undefined;
24 private readonly _scope: ILifetimeManager[];
35
25
36 /** Creates a new activation context with the specified parameters.
26 /** Creates a new activation context with the specified parameters.
37 * @param containerLifetimeManager the container which starts the activation process
27 * @param containerLifetimeManager the container which starts the activation process
38 * @param services the initial service registrations
28 * @param services the initial service registrations
39 * @param name the name of the service being activated, this parameter is
29 * @param name the name of the service being activated, this parameter is
40 * used for the debug purpose.
30 * used for the debug purpose.
41 * @param service the service to activate, this parameter is used for the
31 * @param service the service to activate, this parameter is used for the
42 * debug purpose.
32 * debug purpose.
43 */
33 */
44 constructor(lifetimeManagers: ILifetimeManager[], services: DescriptorMap<S>, name: string, service: Descriptor<S, unknown>, cache = {}) {
34 constructor(container: ServiceLocator<S>, scope: ILifetimeManager[], services: DescriptorMap<S>, contextScope: ILifetimeManager = new LifetimeManager()) {
45 this._name = name;
35 this._container = container;
46 this._service = service;
36 this._contextScope = contextScope;
47 this._cache = cache;
48 this._services = services;
37 this._services = services;
49 this._lifetimeManagers = lifetimeManagers;
38 this._scope = scope;
50 }
51
52 /** the name of the current resolving dependency */
53 getName() {
54 return this._name;
55 }
39 }
56
40
57 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
41 resolve<K extends keyof S>(name: K, stack: ActivationItem[]): NonNullable<S[K]>;
58 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
42 resolve<K extends keyof S, T>(name: K, stack: ActivationItem[], def: T): NonNullable<S[K]> | T;
59 resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined {
43 resolve<K extends keyof S, T>(name: K, stack: ActivationItem[], def?: T): NonNullable<S[K]> | T | undefined {
60 const d = this._services[name];
44 const service = this._services[name];
61
45
62 if (d !== undefined) {
46 if (service !== undefined) {
63 return this.activate(d, name.toString());
47 return service.activate(
48 this,
49 stack.concat({
50 name: name.toString(),
51 descriptor: service.toString()
52 })
53 );
64 } else {
54 } else {
65 if (arguments.length > 1)
55 if (arguments.length > 2)
66 return def;
56 return def;
67 else
57 else
68 throw new Error(`Service ${String(name)} not found`);
58 throw new Error("Service not found");
69 }
59 }
70 }
60 }
71
61
72 /**
62 /**
73 * registers services local to the the activation context
63 * registers services local to the the activation context
74 *
64 *
75 * @name{string} the name of the service
65 * @name{string} the name of the service
76 * @service{string} the service descriptor to register
66 * @service{string} the service descriptor to register
77 */
67 */
78 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]) {
68 private _register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]) {
79 argumentNotNull(name, "name");
69 argumentNotNull(name, "name");
80
70
81 const d = this._services[name];
71 const d = this._services[name];
82 if (d !== undefined && !d.configurable)
72 if (d !== undefined && !d.configurable)
83 throw new Error(`Service ${String(name)} can't be overridden`);
73 throw new Error(`Service ${String(name)} can't be overridden`);
84
74
85 this._services[name] = service;
75 this._services[name] = service;
86 }
76 }
87
77
88 ownerSlot<T>(slotId: string | number): ILifetimeSlot<T> {
78 scopeSlot<T>(level: number, slotId: string | number): ILifetimeSlot<T> {
89 return this._lifetimeManagers[this._service.level].slot(slotId);
79 if (level < 0 || level >= this._scope.length)
80 throw new Error("The scope level is out of range");
81 return this._scope[level].slot(slotId);
90 }
82 }
91
83
92 containerSlot<T>(slotId: string | number): ILifetimeSlot<T> {
84 hierarchySlot<T>(slotId: string | number): ILifetimeSlot<T> {
93 return this._lifetimeManagers[this._lifetimeManagers.length - 1].slot(slotId);
85 return this._scope[this._scope.length - 1].slot(slotId);
86 }
87
88 selfContainer(): ServiceLocator<S> {
89 return this._container;
90 }
91
92 createChildContainer(): IContainerBuilder<S, keyof S> {
93 return new ContainerBuilder(this._services, this._scope);
94 }
94 }
95
95
96 contextSlot<T>(slotId: string | number): ILifetimeSlot<T> {
96 contextSlot<T>(slotId: string | number): ILifetimeSlot<T> {
97
97 return this._contextScope.slot(slotId);
98 }
99
100 createLifetime<T>(): ILifetime<T> {
101 const id = nextId++;
102 return {
103 initialize() { },
104 has: () => id in this._cache,
105 get: () => {
106 const v = this._cache[id] as T;
107 if (v === undefined || v === null)
108 throw new Error("The value isn't present in the activation context");
109 return v;
110 },
111 store: item => {
112 this._cache[id] = item;
113 }
114 };
115 }
98 }
116
99
117 activate<T>(d: Descriptor<S, T>, name: string) {
100 withOverrides<X>(overrides: DescriptorMap<S>, action: () => X) {
118 // TODO: add logging
101 const services = this._services;
119 // if (trace.isLogEnabled())
102 this._services = prototype(this._services);
120 // trace.log("enter {0} {1}", name, d);
103 try {
121
104 each(overrides, (v, k) => this._register(k, v));
122 const ctx = new ActivationContext(
105 return action();
123 this._containerLifetimeManager,
106 } finally {
124 d.hasOverrides ? prototype(this._services) : this._services,
107 this._services = services;
125 name,
108 }
126 d,
127 this._cache
128 );
129
130 const v = d.activate(ctx);
131
132 // if (trace.isLogEnabled())
133 // trace.log(`leave ${name}`);
134
135 return v;
136 }
137
138 getStack(): ActivationContextInfo[] {
139 const stack = [{
140 name: this._name,
141 service: this._service.toString()
142 }];
143
144 return this._parent ?
145 stack.concat(this._parent.getStack()) :
146 stack;
147 }
148
149 fail(innerException: unknown): never {
150 throw new ActivationError(this._name, this.getStack(), innerException);
151 }
109 }
152
110
153 }
111 }
@@ -1,60 +1,57
1 export interface ActivationItem {
1 export interface ActivationItem {
2 name: string;
2 name: string;
3 service: string;
3 descriptor: string;
4 }
4 }
5
5
6 /**
6 /**
7 * Contains information about the error which occurred during service activation.
7 * Contains information about the error which occurred during service activation.
8 *
8 *
9 * Information about activation error includes original exception which has
9 * Information about activation error includes original exception which has
10 * occurred, the name of the service being activated and activation stack of
10 * occurred, the name of the service being activated and activation stack of
11 * services.
11 * services.
12 */
12 */
13 export class ActivationError {
13 export class ActivationError {
14 /**
14 /**
15 * Stack of services being activating
15 * Stack of services being activating
16 */
16 */
17 readonly activationStack: ActivationItem[];
17 readonly activationStack: ActivationItem[];
18
18
19 /**
19 /**
20 * The name of the failed service
21 */
22 readonly service: string;
23
24 /**
25 * The exception which occurred during activation of the service
20 * The exception which occurred during activation of the service
26 */
21 */
27 readonly innerException: unknown;
22 readonly innerException: unknown;
28
23
29 /**
24 /**
30 * Error message
25 * Error message
31 */
26 */
32 readonly message: string;
27 readonly message: string;
33
28
34 constructor(service: string, activationStack: ActivationItem[], innerException: unknown) {
29 constructor(message: string, activationStack: ActivationItem[], innerException?: unknown) {
35 this.message = "Failed to activate the service";
30 this.message = message;
36 this.activationStack = activationStack;
31 this.activationStack = activationStack;
37 this.service = service;
38 this.innerException = innerException;
32 this.innerException = innerException;
39 }
33 }
40
34
41 toString() {
35 toString() {
42 const parts = [this.message];
36 const parts = [this.message];
43 if (this.service)
37
44 parts.push(`when activating: ${String(this.service)}`);
38 if (this.activationStack && this.activationStack.length) {
39 const [{ name, descriptor }, ...before] = this.activationStack;
40
41 parts.push(`when activating: ${name}, ${descriptor}`);
42
43 if (before) {
44 parts.push("at");
45 parts.push.apply(
46 null,
47 before.map(({ name: name, descriptor: service }) => ` ${name} ${service}`)
48 );
49 }
50 }
45
51
46 if (this.innerException)
52 if (this.innerException)
47 parts.push(`caused by: ${String(this.innerException)}`);
53 parts.push(`caused by: ${String(this.innerException)}`);
48
54
49 if (this.activationStack) {
50 parts.push("at");
51 parts.push.apply(null,
52 this.activationStack
53 .map(({ name, service }) => ` ${name} ${service}`)
54 );
55
56 }
57
58 return parts.join("\n");
55 return parts.join("\n");
59 }
56 }
60 }
57 }
@@ -1,64 +1,71
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ActivationError } from "./ActivationError";
3 import { ContainerBuilder } from "./ContainerBuilder";
2 import { ContainerBuilder } from "./ContainerBuilder";
4 import { LifetimeManager } from "./LifetimeManager";
3 import { LifetimeManager, emptySlot } from "./LifetimeManager";
5 import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces";
4 import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ILifetimeSlot, ServiceLocator } from "../typings/interfaces";
5
6 let nextId = 1;
6
7
7 export class Container<S> implements ServiceLocator<S>, IDestroyable {
8 export class Container<S> implements ServiceLocator<S>, IDestroyable {
8 private readonly _services: DescriptorMap<S>;
9 private readonly _services: DescriptorMap<S>;
9
10
10 private readonly _lifetimeManagers: ILifetimeManager[];
11 private readonly _scope: ILifetimeManager[];
12
13 private readonly _containerId = `container-${nextId++}`;
14
15 private readonly _slot: ILifetimeSlot<this>;
11
16
12 private _disposed: boolean;
17 private _disposed: boolean;
13
18
14 private readonly _onDestroyed: () => void;
19 constructor(services: DescriptorMap<S>, parentScope: ILifetimeManager[]) {
15
16 constructor(services: DescriptorMap<S>, lifetimeManagers: ILifetimeManager[], destroyed: () => void) {
17 this._services = services;
20 this._services = services;
18 this._disposed = false;
21 this._disposed = false;
19 this._lifetimeManagers = lifetimeManagers.concat(new LifetimeManager());
22 this._scope = parentScope.concat(new LifetimeManager());
20 this._onDestroyed = destroyed;
23
21
24 // If this container is created inside the parent container scope,
25 // allocated lifetime slot
26 this._slot = parentScope.length ?
27 parentScope[parentScope.length - 1].slot(this._containerId) :
28 emptySlot<this>();
29
30 // store the container reference in the lifetime slot
31 this._slot.store(this);
32
22 }
33 }
23
34
24 private _assertNotDestroyed() {
35 private _assertNotDestroyed() {
25 if (this._disposed)
36 if (this._disposed)
26 throw new Error("The container is destroyed");
37 throw new Error("The container is destroyed");
27 }
38 }
28
39
29 createChildBuilder(): IContainerBuilder<S, keyof S> {
40 createChildBuilder(): IContainerBuilder<S, keyof S> {
30 this._assertNotDestroyed();
41 this._assertNotDestroyed();
31
42
32 const lifetime = this._lifetimeManager.create<IDestroyable>();
43 return new ContainerBuilder(this._services, this._scope);
33
34 return new ContainerBuilder(this._services, lifetime);
35 }
44 }
36
45
37 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
46 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
38 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
47 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
39 resolve<K extends keyof S, T>(name: K, def?: T) {
48 resolve<K extends keyof S, T>(name: K, def?: T) {
40 this._assertNotDestroyed();
49 this._assertNotDestroyed();
41
50
42 const d = this._services[name];
51 const context = new ActivationContext(this, this._scope, this._services);
43 if (d === undefined) {
52
44 if (arguments.length > 1)
53 return arguments.length === 1 ?
45 return def;
54 context.resolve(name, []) :
46 else
55 context.resolve(name, [], def);
47 throw new Error(`Service '${String(name)}' isn't found`);
48 } else {
49 const context = new ActivationContext(this._lifetimeManagers, this._services, String(name), d);
50 try {
51 return d.activate(context);
52 } catch (error) {
53 throw new ActivationError(name.toString(), context.getStack(), error);
54 }
55 }
56 }
56 }
57
57 destroy() {
58 destroy() {
58 if (this._disposed)
59 if (this._disposed)
59 return;
60 return;
60 this._disposed = true;
61 this._disposed = true;
61 this._lifetimeManager.destroy();
62
62 (0,this._onDestroyed)();
63 // destroy own scope
64 this._scope[this._scope.length - 1].destroy();
65
66 // release lifetime slot if the container is destroyed before the parent
67 // container. If this container is destroyed during the parent container
68 // cleanup procedure this call will have no effect.
69 this._slot.remove();
63 }
70 }
64 }
71 }
@@ -1,66 +1,57
1 import { Container } from "./Container";
1 import { Container } from "./Container";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { containerSelfDescriptor } from "./DescriptorImpl";
3 import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetimeManager } from "../typings/interfaces";
4 import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable, ContainerServices, ContainerServicesConstraint } from "./interfaces";
5 import { emptyLifetime, LifetimeManager } from "./LifetimeManager";
6 import { isDestroyable, prototype } from "./traits";
7
4
8 /**
5 /**
9 * Container builder used to prepare service descriptors and create a IoC container
6 * Container builder used to prepare service descriptors and create a IoC container
10 */
7 */
11 export class ContainerBuilder<S, U extends keyof S> implements
8 export class ContainerBuilder<S, U extends keyof S> implements
12 IContainerBuilder<S, U> {
9 IContainerBuilder<S, U> {
13
10
14 private _pending = 1;
11 private _pending = 1;
15
12
16 private readonly _services: DescriptorMap<ContainerServices<S>>;
13 private readonly _services: DescriptorMap<S>;
17
14
18 private readonly _lifetimeManager = new LifetimeManager();
15 private readonly _scope: ILifetimeManager[];
19
16
20 private readonly _lifetime: ILifetime<IDestroyable>;
17 private readonly _level: number;
21
18
22 constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) {
19 constructor(parentServices: DescriptorMap<S> | null, scope: ILifetimeManager[] = []) {
23 this._services = { ...parentServices }; // create a copy
20 this._services = { ...parentServices }; // create a copy
24 this._lifetime = lifetime ?? emptyLifetime();
21 this._level = scope.length;
22 this._scope = scope;
25 }
23 }
26 createServiceBuilder<K extends U>(name: K):
24 createServiceBuilder<K extends U>(name: K): IDescriptorBuilder<S, S[K], Record<never, never>, U> {
27 IDescriptorBuilder<S, S[K], Record<never, never>, U> {
28
25
29 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
26 return new DescriptorBuilder(this._level, String(name), this._register(name), this._fail);
30
27
31 }
28 }
32
29
33 build(): ServiceLocator<S> {
30 build(): ServiceLocator<S> {
34 this._assertBuilding();
31 this._assertBuilding();
35 if (!this._complete())
32 if (!this._complete())
36 throw new Error("The configuration didn't complete.");
33 throw new Error("The configuration didn't complete.");
37
34
38 const {remove, store} = this._lifetime(null);
35 return new Container(this._services, this._scope);
39
40 const container = new Container(this._services, this._lifetimeManager, remove);
41
42 store(container);
43
44 return container;
45 }
36 }
46
37
47 private readonly _register = <K extends U>(name: K) =>
38 private readonly _register = <K extends U>(name: K) =>
48 (descriptor: Descriptor<S, S[K]>) => {
39 (descriptor: Descriptor<S, S[K]>) => {
49 this._complete();
40 this._complete();
50 this._services[name] = descriptor;
41 this._services[name] = descriptor;
51 };
42 };
52
43
53 private readonly _fail = (ex: unknown) => {
44 private readonly _fail = (ex: unknown) => {
54 throw ex;
45 throw ex;
55 };
46 };
56
47
57 private _assertBuilding() {
48 private _assertBuilding() {
58 if (!this._pending)
49 if (!this._pending)
59 throw new Error("The descriptor builder is finalized");
50 throw new Error("The descriptor builder is finalized");
60 }
51 }
61
52
62 private _complete() {
53 private _complete() {
63 return !(--this._pending);
54 return !(--this._pending);
64 }
55 }
65
56
66 } No newline at end of file
57 }
@@ -1,189 +1,197
1 import { BuildDescriptorFn, IDescriptorBuilder, DepsMap, Resolve, DescriptorMap } from "./interfaces";
1 import { BuildDescriptorFn, IDescriptorBuilder, DepsMap, Resolve, DescriptorMap } from "../typings/interfaces";
2 import { Descriptor, ILifetime, ActivationType } from "./interfaces";
2 import { Descriptor, ILifetime, ActivationType } from "../typings/interfaces";
3 import { DescriptorImpl } from "./DescriptorImpl";
3 import { DescriptorImpl } from "./DescriptorImpl";
4 import { containerLifetime, contextLifetime, emptyLifetime, hierarchyLifetime, LifetimeManager, singletonLifetime } from "./LifetimeManager";
4 import { contextLifetime, emptyLifetime, hierarchyLifetime, scopeLifetime, singletonLifetime } from "./LifetimeManager";
5 import { each, isPromise, isString, key, oid } from "./traits";
5 import { each, isPromise, isString, key, oid } from "./traits";
6
6
7 /**
7 /**
8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
8 * @template {S} Карта доступных зависимостей, как правило `ContainerServices`
9 * @template {T} Тип сервиса
9 * @template {T} Тип сервиса
10 */
10 */
11 export class DescriptorBuilder<S, T, R, U extends keyof S> implements IDescriptorBuilder<S, T, R, U> {
11 export class DescriptorBuilder<S, T, R, U extends keyof S> implements IDescriptorBuilder<S, T, R, U> {
12 private readonly _cb: (d: Descriptor<S, T>) => void;
12 private readonly _cb: (d: Descriptor<S, T>) => void;
13
13
14 private readonly _eb: (err: unknown) => void;
14 private readonly _eb: (err: unknown) => void;
15
15
16 private readonly _refs: DepsMap<R>;
16 private readonly _refs: DepsMap<R>;
17
17
18 private readonly _level: number;
19
20 private readonly _instanceId: string | number;
21
18 private _lifetime: ILifetime<T> = emptyLifetime<T>();
22 private _lifetime: ILifetime<T> = emptyLifetime<T>();
19
23
20 private _overrides: DescriptorMap<S>;
24 private _overrides: DescriptorMap<S>;
21
25
22 private _cleanup?: (item: T) => void;
26 private _cleanup?: (item: T) => void;
23
27
24 private _factory?: (refs: R) => NonNullable<T>;
28 private _factory?: (refs: R) => NonNullable<T>;
25
29
26 private _pending = 1;
30 private _pending = 1;
27
31
28 private _failed = false;
32 private _failed = false;
29
33
30 private _finalized = false;
34 private _finalized = false;
31
35
32 /**
36 /**
33 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
37 * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
34 * lifetime.
38 * lifetime.
35 *
39 *
36 * @param cb The callback to receive the built service descriptor
40 * @param cb The callback to receive the built service descriptor
37 * @param eb The callback to receive the error due
41 * @param eb The callback to receive the error due
38 */
42 */
39 constructor(cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
43 constructor(level: number, instanceId: string | number, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
40 this._cb = cb;
44 this._cb = cb;
41 this._eb = eb;
45 this._eb = eb;
42 this._overrides = {};
46 this._overrides = {};
43 this._refs = {};
47 this._refs = {};
48 this._level = level;
49 this._instanceId = instanceId;
44 }
50 }
45
51
46 /** Declares dependencies to be consumed in the factory method */
52 /** Declares dependencies to be consumed in the factory method */
47 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
53 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
48 IDescriptorBuilder<S, T, R & { [k in keyof X]: Resolve<S, X[k]>; }, U> {
54 IDescriptorBuilder<S, T, R & { [k in keyof X]: Resolve<S, X[k]>; }, U> {
49
55
50 each(refs, (v, k) => this._refs[k] = v);
56 each(refs, (v, k) => this._refs[k] = v);
51
57
52 return this as IDescriptorBuilder<S, T, R & {
58 return this as IDescriptorBuilder<S, T, R & {
53 [k in keyof X]: Resolve<S, X[k]>;
59 [k in keyof X]: Resolve<S, X[k]>;
54 }, U>;
60 }, U>;
55 }
61 }
56
62
57 /** Registers a factory method for the service */
63 /** Registers a factory method for the service */
58 factory(f: (refs: R) => NonNullable<T>): void {
64 factory(f: (refs: R) => NonNullable<T>): void {
59 this._assertBuilding();
65 this._assertBuilding();
60 this._factory = f;
66 this._factory = f;
61 this._finalize();
67 this._finalize();
62 this._complete();
68 this._complete();
63 }
69 }
64
70
65
71
66 private _assertBuilding() {
72 private _assertBuilding() {
67 if (this._finalized)
73 if (this._finalized)
68 throw new Error("The descriptor builder is finalized");
74 throw new Error("The descriptor builder is finalized");
69 }
75 }
70
76
71 private _finalize() {
77 private _finalize() {
72 this._finalized = true;
78 this._finalized = true;
73 }
79 }
74
80
75 override<K extends U>(name: K, builder: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this;
81 override<K extends U>(name: K, builder: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this;
76 override<K extends U>(services: { [k in K]: BuildDescriptorFn<S, NonNullable<S[k]>, U> }): this;
82 override<K extends U>(services: { [k in K]: BuildDescriptorFn<S, NonNullable<S[k]>, U> }): this;
77 override<K extends U>(nameOrServices: K | { [name in K]: BuildDescriptorFn<S, NonNullable<S[K]>, U> }, builder?: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this {
83 override<K extends U>(nameOrServices: K | { [name in K]: BuildDescriptorFn<S, NonNullable<S[K]>, U> }, builder?: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this {
78 this._assertBuilding();
84 this._assertBuilding();
79 const guard = (v: void | Promise<void>) => {
85 const guard = (v: void | Promise<void>) => {
80 if (isPromise(v))
86 if (isPromise(v))
81 v.catch(err => this._fail(err));
87 v.catch(err => this._fail(err));
82 };
88 };
83
89
84 if (typeof nameOrServices !== "object") {
90 if (typeof nameOrServices !== "object") {
85 if (builder) {
91 if (builder) {
86 this._defer();
92 this._defer();
87 const d = new DescriptorBuilder<S, NonNullable<S[K]>, object, U>(
93 const d = new DescriptorBuilder<S, NonNullable<S[K]>, object, U>(
94 this._level,
95 String(nameOrServices),
88 result => {
96 result => {
89 this._overrides[nameOrServices] = result;
97 this._overrides[nameOrServices] = result;
90 this._complete();
98 this._complete();
91 },
99 },
92 err => this._fail(err)
100 err => this._fail(err)
93 );
101 );
94
102
95 try {
103 try {
96 guard(builder(d));
104 guard(builder(d));
97 } catch (err) {
105 } catch (err) {
98 this._fail(err);
106 this._fail(err);
99 }
107 }
100 }
108 }
101 } else {
109 } else {
102 each(nameOrServices, (v, k) => this.override(k, v));
110 each(nameOrServices, (v, k) => this.override(k, v));
103 }
111 }
104 return this;
112 return this;
105 }
113 }
106
114
107 /** Specified the singleton lifetime for the service */
115 /** Specified the singleton lifetime for the service */
108 lifetime(lifetime: "singleton", typeId: string): this;
116 lifetime(lifetime: "singleton", typeId: string): this;
109 /**
117 /**
110 * Specifies the lifetime for the service, either {@linkcode ILifetime<T>}
118 * Specifies the lifetime for the service, either {@linkcode ILifetime<T>}
111 * object or {@linkcode ActivationType} literal.
119 * object or {@linkcode ActivationType} literal.
112 * @param lifetime
120 * @param lifetime
113 */
121 */
114 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
122 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
115 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
123 lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
116 this._assertBuilding();
124 this._assertBuilding();
117 if (isString(lifetime)) {
125 if (isString(lifetime)) {
118 this._lifetime = this._resolveLifetime(lifetime, typeId);
126 this._lifetime = this._resolveLifetime(lifetime, typeId);
119 } else {
127 } else {
120 this._lifetime = lifetime;
128 this._lifetime = lifetime;
121 }
129 }
122 return this;
130 return this;
123 }
131 }
124
132
125 /** Registers cleanup callback, used when lifetime of the instance is managed
133 /** Registers cleanup callback, used when lifetime of the instance is managed
126 * by the container or some external mechanism
134 * by the container or some external mechanism
127 */
135 */
128 cleanup(cb: (item: T) => void): this {
136 cleanup(cb: (item: T) => void): this {
129 this._assertBuilding();
137 this._assertBuilding();
130 this._cleanup = cb;
138 this._cleanup = cb;
131 return this;
139 return this;
132 }
140 }
133
141
134 /** Registers a value as the instance of the service */
142 /** Registers a value as the instance of the service */
135 value(v: NonNullable<T>): void {
143 value(v: NonNullable<T>): void {
136 this._assertBuilding();
144 this._assertBuilding();
137 this._cb({
145 this._cb({
138 activate() {
146 activate() {
139 return v;
147 return v;
140 }
148 }
141 });
149 });
142 this._finalize();
150 this._finalize();
143 }
151 }
144
152
145 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
153 _resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
146 switch (activation) {
154 switch (activation) {
147 case "container":
155 case "container":
148 return containerLifetime();
156 return scopeLifetime(this._level, this._instanceId);
149 case "hierarchy":
157 case "hierarchy":
150 return hierarchyLifetime();
158 return hierarchyLifetime(this._instanceId);
151 case "context":
159 case "context":
152 return contextLifetime();
160 return contextLifetime(this._instanceId);
153 case "singleton": {
161 case "singleton": {
154 if (!typeId)
162 if (!typeId)
155 throw Error("The singleton activation requires a typeId");
163 throw Error("The singleton activation requires a typeId");
156 const _oid = isString(typeId) ? typeId : oid(typeId);
164 const _oid = isString(typeId) ? typeId : oid(typeId);
157 return singletonLifetime(_oid);
165 return singletonLifetime(_oid);
158 }
166 }
159 default:
167 default:
160 return emptyLifetime();
168 return emptyLifetime();
161 }
169 }
162 }
170 }
163
171
164 _defer() {
172 _defer() {
165 this._pending++;
173 this._pending++;
166 }
174 }
167
175
168 _complete() {
176 _complete() {
169 if (--this._pending === 0) {
177 if (--this._pending === 0) {
170 if (!this._factory)
178 if (!this._factory)
171 throw new Error("The factory must be specified");
179 throw new Error("The factory must be specified");
172
180
173 this._cb(new DescriptorImpl<S, T>({
181 this._cb(new DescriptorImpl<S, T>({
174 lifetime: this._lifetime,
182 lifetime: this._lifetime,
175 factory: this._factory as (refs: Record<key, unknown>) => NonNullable<T>,
183 factory: this._factory as (refs: Record<key, unknown>) => NonNullable<T>,
176 overrides: this._overrides,
184 overrides: this._overrides,
177 cleanup: this._cleanup
185 cleanup: this._cleanup
178 }));
186 }));
179 }
187 }
180 }
188 }
181
189
182 _fail(err: unknown) {
190 _fail(err: unknown) {
183 if (!this._failed) {
191 if (!this._failed) {
184 this._failed = true;
192 this._failed = true;
185 this._eb.call(undefined, err);
193 this._eb.call(undefined, err);
186 }
194 }
187 }
195 }
188
196
189 }
197 }
@@ -1,105 +1,89
1 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
1 import { Descriptor, ILifetime, IActivationContext, DescriptorMap, DescriptorDepsMap } from "../typings/interfaces";
2 import { ActivationError, ActivationItem } from "./ActivationError";
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
6
6 readonly lifetime: ILifetime<NonNullable<T>>;
7 readonly lifetime: ILifetime<T>;
7
8
8 readonly factory: (refs: Record<key, unknown>) => NonNullable<T>;
9 readonly factory: (refs: Record<key, unknown>) => NonNullable<T>;
9
10
10 readonly cleanup?: (item: NonNullable<T>) => void;
11 readonly cleanup?: (item: NonNullable<T>) => void;
11
12
12 readonly overrides?: DescriptorMap<S>;
13 readonly overrides?: DescriptorMap<S>;
13
14
14 readonly dependencies?: DepsMap<S>;
15 readonly dependencies?: DescriptorDepsMap<S>;
15 }
16 }
16
17
17 export const containerSelfDescriptor = <S>() => Object.freeze({
18 level: 0,
19 activate(context: IActivationContext<S>) {
20 return context.createChildContainer();
21 }
22 });
23
24
25 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
18 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
26
19
27 private readonly _overrides?: DescriptorMap<S>;
20 private readonly _overrides: DescriptorMap<S> | undefined;
28
21
29 private readonly _lifetime: ILifetime<NonNullable<T>>;
22 private readonly _lifetime: ILifetime<T>;
30
23
31 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
24 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
32
25
33 private readonly _cleanup?: (item: NonNullable<T>) => void;
26 private readonly _cleanup: (item: NonNullable<T>) => void;
34
27
35 private readonly _deps?: DepsMap<S>;
28 private readonly _dependencies: DescriptorDepsMap<S> | undefined;
36
37 readonly hasOverrides: boolean;
38
29
39 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
30 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
40 this._lifetime = lifetime;
31 this._lifetime = lifetime;
41 this._factory = factory;
32 this._factory = factory;
42 if (cleanup)
33 this._cleanup = cleanup ? cleanup : () => { };
43 this._cleanup = cleanup;
34 this._overrides = overrides;
44 if (overrides)
35 this._dependencies = dependencies;
45 this._overrides = overrides;
46 if (dependencies)
47 this._deps = dependencies;
48
49 this.hasOverrides = !!overrides;
50 }
36 }
51
37
52 activate(context: IActivationContext<S>): NonNullable<T> {
38 activate(context: IActivationContext<S>, stack: ActivationItem[]): NonNullable<T> {
53
39
54 const { has, get, initialize, store } = this._lifetime(context);
40 const slot = this._lifetime(context);
41 if (slot.has())
42 return slot.get();
43
44 if (!slot.initialize())
45 throw new Error("Cyclic reference detected");
55
46
56 if (has())
47 const instance = this._overrides ?
57 return get();
48 context.withOverrides(this._overrides, () => this._activate(context, stack)) :
49 this._activate(context, stack);
58
50
59 initialize();
51 slot.store(instance, this._cleanup);
60
52
61 if (this._overrides)
53 return instance;
62 each(this._overrides, (v, k) => context.register(k, v));
54 }
63
55
64 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => {
56 private _activate(context: IActivationContext<S>, stack: ActivationItem[]) {
65 if (lazy) {
57 const refs: Record<key, unknown> = {};
66 return "default" in opts ?
58 if (this._dependencies) {
67 () => context.resolve(name, opts.default) :
59 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K]; }) => {
68 () => context.resolve(name);
60 if (lazy) {
69 } else {
61 return "default" in opts ?
70 return "default" in opts ?
62 () => context.resolve(name, stack, opts.default) :
71 context.resolve(name, opts.default) :
63 () => context.resolve(name, stack);
72 context.resolve(name);
64 } else {
73 }
65 return "default" in opts ?
74 };
66 context.resolve(name, stack, opts.default) :
67 context.resolve(name, stack);
68 }
69 };
75
70
76 const deps = this._deps;
71 // can throw activation exception
77
72 each(this._dependencies, (v, k) => {
78 const refs = deps ?
73 refs[k] = resolve(v);
79 Object.keys(deps)
74 });
80 .map(k => {
75 }
81 const ref = deps[k];
82 return typeof ref !== "object" ?
83 { [k]: resolve({ name: ref }) } :
84 { [k]: resolve(ref) };
85 })
86 .reduce((a, p) => ({ ...a, ...p }), {}) :
87 {};
88
76
89 try {
77 try {
90 // call the factory method
78 // call the factory method
91 const instance = (0, this._factory)(refs);
79 return (0, this._factory)(refs);
92
80 } catch (e) {
93 // store the instance
81 throw new ActivationError("Error creating instance", stack, e);
94 store(instance, this._cleanup);
95 return instance;
96 } catch (err) {
97 context.fail(err);
98 }
82 }
99 }
83 }
100
84
101
85
102 toString() {
86 toString() {
103 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
87 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
104 }
88 }
105 }
89 }
@@ -1,72 +1,72
1 import { ConfigurationMap, ConfigurationMapConstraint, ContainerServices, ContainerServicesConstraint, ExtractRequiredKeys, IContainerBuilder } from "./interfaces";
1 import { ConfigurationMap, ConfigurationMapConstraint, ContainerServices, ContainerServicesConstraint, ExtractRequiredKeys, IContainerBuilder } from "../typings/interfaces";
2 import { argumentNotNull, each, isKey } from "./traits";
2 import { argumentNotNull, each, isKey } from "./traits";
3
3
4 type ContainerExtensionConstraint<X, S> = ContainerServicesConstraint<Pick<S, keyof X & keyof S>>;
4 type ContainerExtensionConstraint<X, S> = ContainerServicesConstraint<Pick<S, keyof X & keyof S>>;
5
5
6 export class FluentConfiguration<S extends ContainerServicesConstraint<S>, Y extends keyof S = keyof S> {
6 export class FluentConfiguration<S extends ContainerServicesConstraint<S>, Y extends keyof S = keyof S> {
7
7
8 private _builders: Partial<ConfigurationMap<ContainerServices<S>, keyof S, keyof S>> = {};
8 private _builders: Partial<ConfigurationMap<ContainerServices<S>, keyof S, keyof S>> = {};
9
9
10 /** Adds a declaration of the services to the current config.
10 /** Adds a declaration of the services to the current config.
11 *
11 *
12 * @template D The map of the services
12 * @template D The map of the services
13 * @returns self
13 * @returns self
14 */
14 */
15 declare<D extends ContainerExtensionConstraint<D,S>>(): FluentConfiguration<S & D, Y | keyof D> {
15 declare<D extends ContainerExtensionConstraint<D,S>>(): FluentConfiguration<S & D, Y | keyof D> {
16 return this as unknown as FluentConfiguration<S & D, Y | keyof D>;
16 return this as unknown as FluentConfiguration<S & D, Y | keyof D>;
17 }
17 }
18
18
19 /** Adds compile-time information about the already provided services
19 /** Adds compile-time information about the already provided services
20 *
20 *
21 * @template P The map of the provided services
21 * @template P The map of the provided services
22 * @returns self
22 * @returns self
23 */
23 */
24 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
24 provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> {
25 return this as unknown as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
25 return this as unknown as FluentConfiguration<S & P, Exclude<Y, keyof P>>;
26 }
26 }
27
27
28 /** Register the service.
28 /** Register the service.
29 *
29 *
30 * @param name The name of the service
30 * @param name The name of the service
31 * @param builder The service builder
31 * @param builder The service builder
32 * @returns self
32 * @returns self
33 */
33 */
34 register<K extends Y>(name: K, builder: ConfigurationMap<ContainerServices<S>,Y, keyof S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
34 register<K extends Y>(name: K, builder: ConfigurationMap<ContainerServices<S>,Y, keyof S>[K]): FluentConfiguration<S, Exclude<Y, K>>;
35 /** Registers the collection of services
35 /** Registers the collection of services
36 * @param config The collection of services to register.
36 * @param config The collection of services to register.
37 * @returns self
37 * @returns self
38 */
38 */
39 register<X extends ConfigurationMapConstraint<ContainerServices<S>, Y, keyof X>>(config: X): FluentConfiguration<S, Exclude<Y, keyof X>>;
39 register<X extends ConfigurationMapConstraint<ContainerServices<S>, Y, keyof X>>(config: X): FluentConfiguration<S, Exclude<Y, keyof X>>;
40 register<K extends Y>(nameOrConfig: K | ConfigurationMap<ContainerServices<S>, K, keyof S>, builder?: ConfigurationMap<ContainerServices<S>, Y, keyof S>[K]) {
40 register<K extends Y>(nameOrConfig: K | ConfigurationMap<ContainerServices<S>, K, keyof S>, builder?: ConfigurationMap<ContainerServices<S>, Y, keyof S>[K]) {
41 if (isKey(nameOrConfig)) {
41 if (isKey(nameOrConfig)) {
42 argumentNotNull(builder, "builder");
42 argumentNotNull(builder, "builder");
43 this._builders[nameOrConfig] = builder;
43 this._builders[nameOrConfig] = builder;
44 } else {
44 } else {
45 each(nameOrConfig, (v, k) => this.register(k, v));
45 each(nameOrConfig, (v, k) => this.register(k, v));
46 }
46 }
47
47
48 return this; // as FluentConfiguration<S, Exclude<Y, K>>;
48 return this; // as FluentConfiguration<S, Exclude<Y, K>>;
49 }
49 }
50
50
51 /**
51 /**
52 * This method is used to enable a compile time check of the configuration.
52 * This method is used to enable a compile time check of the configuration.
53 * If there are not configured services in the configuration the compiler
53 * If there are not configured services in the configuration the compiler
54 * will trigger the error.
54 * will trigger the error.
55 *
55 *
56 * @param missing Empty object literal should always be specified.
56 * @param missing Empty object literal should always be specified.
57 * @returns self
57 * @returns self
58 */
58 */
59 // eslint-disable-next-line @typescript-eslint/no-unused-vars
59 // eslint-disable-next-line @typescript-eslint/no-unused-vars
60 done(...args: ExtractRequiredKeys<S, Y> extends never ? [] : [services: {[ k in ExtractRequiredKeys<S, Y>]: "required"}]) {
60 done(...args: ExtractRequiredKeys<S, Y> extends never ? [] : [services: {[ k in ExtractRequiredKeys<S, Y>]: "required"}]) {
61 //done() {
61 //done() {
62 return this;
62 return this;
63 }
63 }
64
64
65 configure<C extends IContainerBuilder<ContainerServices<S>, keyof S>>(builder: C) {
65 configure<C extends IContainerBuilder<ContainerServices<S>, keyof S>>(builder: C) {
66 each(this._builders, (v, k) => {
66 each(this._builders, (v, k) => {
67 v(builder.createServiceBuilder(k));
67 v(builder.createServiceBuilder(k));
68 });
68 });
69 return builder.build();
69 return builder.build();
70 }
70 }
71
71
72 }
72 }
@@ -1,174 +1,97
1 import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
1 import { ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "../typings/interfaces";
2 import { argumentNotNull, isDestroyable } from "./traits";
2 import { LifetimeSlot } from "./LifetimeSlot";
3
3 import { argumentNotNull } from "./traits";
4 const safeCall = (item: () => void) => {
5 try {
6 item();
7 } catch {
8 // silence!
9 }
10 };
11
4
12 const noop = () => { };
5 const noop = () => { };
13
6
14 const fail = (message: string) => (): never => {
7 const fail = (message: string) => (): never => {
15 throw new Error(message);
8 throw new Error(message);
16 };
9 };
17
10
18 const _emptySlot = Object.freeze({
11 const _emptySlot = Object.freeze({
19 has: () => false,
12 has: () => false,
20
13
21 initialize: noop,
14 initialize: () => false,
22
15
23 get: fail("The specified item isn't registered with a lifetime manager"),
16 get: fail("The specified item isn't registered with a lifetime manager"),
24
17
25 store: noop,
18 store: noop,
26
19
27 remove: noop,
20 remove: noop,
28
21
29 cleanup: noop,
22 cleanup: noop,
30 });
23 });
31
24
32 const _destroy = (item: unknown) => () => isDestroyable(item) && item.destroy() ;
33
34 const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) =>
35 cleanup ? () => cleanup(value) : _destroy(value);
36
37 const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
38 has: () => false,
39
40 initialize: () => put(pendingSlot(put, remove)),
41
42 get: fail("The slot doesn't hold a value"),
43
44 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
45
46 remove: noop,
47
48 cleanup: noop,
49 });
50
51 const pendingSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
52 has: () => false,
53
54 get: fail("The value in this slot doesn't exist"),
55
56 initialize: fail("Cyclic reference detected"),
57
58 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
59
60 remove,
61
62 cleanup: noop
63 });
64
65 const valueSlot = <T>(value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({
66 has: () => true,
67
68 get: () => value,
69
70 initialize: fail("The slot already has a value"),
71
72 store: fail("The slot already has a value"),
73
74 cleanup: _makeCleanup(value, cleanup),
75
76 remove: remove
77 });
78
79 export class LifetimeManager implements ILifetimeManager {
25 export class LifetimeManager implements ILifetimeManager {
80 private _destroyed = false;
26 private _destroyed = false;
81
27
82 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
28 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
83
29
84 slot<T>(cookie: string | number): ILifetimeSlot<T> {
30 slot<T>(slotId: string | number): ILifetimeSlot<T> {
85 if (cookie in this._slots)
86 return this._slots[cookie] as ILifetimeSlot<T>;
87
88 return newSlot<T>(this._put(cookie), this._remove(cookie));
89 }
90
91 private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => {
92 this._assertNotDestroyed();
93 this._slots[id] = slot as ILifetimeSlot<unknown>;
94 };
95
96 private readonly _remove = (id: string | number) => () => {
97 this._assertNotDestroyed();
98 delete this._slots[id];
99 };
100
101 private _assertNotDestroyed() {
102 if (this._destroyed)
31 if (this._destroyed)
103 throw new Error("The lifetime manager is destroyed");
32 throw new Error("The lifetime manager is destroyed");
104
33
34 if (slotId in this._slots)
35 return this._slots[slotId] as ILifetimeSlot<T>;
36
37 return this._slots[slotId] = new LifetimeSlot<T>(() => delete this._slots[slotId]);
105 }
38 }
106
39
40
107 destroy() {
41 destroy() {
108 if (!this._destroyed) {
42 if (!this._destroyed) {
109 this._destroyed = true;
43 this._destroyed = true;
110 Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup));
44 Object.values(this._slots).forEach(slot => {
45 try {
46 slot.cleanup();
47 } catch {
48 // ignore
49 }
50 });
111 }
51 }
112 }
52 }
113
53
114 }
54 }
115
55
116 export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>;
56 export const emptySlot = <T>() => _emptySlot as ILifetimeSlot<T>;
117
57
118 let nextId = 1;
58 export const emptyLifetime = <T>() => emptySlot as ILifetime<T>;
119
59
120 export const containerLifetime = <T>() => {
60 export const scopeLifetime = <T>(level: number, slotId: string | number) =>
121 const slotId = nextId ++;
61 (context: ILifetimeContext) =>
122 return (context: ILifetimeContext) =>
62 context.scopeSlot<T>(level, slotId);
123 context.ownerSlot<T>(slotId);
124 };
125
63
126 export const hierarchyLifetime = <T>() => {
64 export const hierarchyLifetime = <T>(slotId: string | number) =>
127 const slotId = nextId++;
65 (context: ILifetimeContext) =>
128 return (context: ILifetimeContext) =>
66 context.hierarchySlot<T>(slotId);
129 context.containerSlot<T>(slotId);
130 };
131
67
132 /**
68 /**
133 * Creates a lifetime instance bound to the current activation context. This
69 * Creates a lifetime instance bound to the current activation context. This
134 * lifetime will store the service instance per activation context. Every
70 * lifetime will store the service instance per activation context. Every
135 * top level service resolution will create a new activation context. This
71 * top level service resolution will create a new activation context. This
136 * context is propagated to subsequent service resolution thus all services
72 * context is propagated to subsequent service resolution thus all services
137 * with context lifetime will be shared among their consumers.
73 * with context lifetime will be shared among their consumers.
138 *
74 *
139 * @returns The instance of the lifetime.
75 * @returns The instance of the lifetime.
140 */
76 */
141 export const contextLifetime = <T>() => {
77 export const contextLifetime = <T>(slotId: string | number) =>
142 const slotId = nextId++;
78 (context: ILifetimeContext) =>
143
144 return (context: ILifetimeContext) =>
145 context.contextSlot<T>(slotId);
79 context.contextSlot<T>(slotId);
146 };
147
80
148 const singletons = new LifetimeManager();
81 const singletons = new LifetimeManager();
149
82
150 /**
83 /**
151 * Creates the lifetime for the service which will allow existence only one
84 * Creates the lifetime for the service which will allow existence only one
152 * instance with the specified {@linkcode typeId}. If there will be created
85 * instance with the specified {@linkcode typeId}. If there will be created
153 * several lifetime instances with same `typeId` in the runtime, they will
86 * several lifetime instances with same `typeId` in the runtime, they will
154 * share the same service instance.
87 * share the same service instance.
155 *
88 *
156 * @param typeId The identified for the global instance, usually this is a
89 * @param typeId The identified for the global instance, usually this is a
157 * fully qualified class name
90 * fully qualified class name
158 * @returns The lifetime instance
91 * @returns The lifetime instance
159 */
92 */
160 export const singletonLifetime = <T>(typeId: string) => {
93 export const singletonLifetime = <T>(typeId: string) => {
161 argumentNotNull(typeId, "typeId");
94 argumentNotNull(typeId, "typeId");
162
95
163 return () => singletons.slot<T>(typeId);
96 return () => singletons.slot<T>(typeId);
164 };
97 };
165
166 /** Creates a lifetime bound to the specified container. Using this lifetime
167 * will create a single service instance per the specified container.
168 *
169 * @param container The container which will manage the lifetime for the service
170 */
171 export const containerLifetime = <T>(manager: ILifetimeManager) => {
172 const slotId = nextId++;
173 return () => manager.slot<T>(slotId);
174 };
@@ -1,52 +1,52
1 import { FluentConfiguration } from "./FluentConfiguration";
1 import { FluentConfiguration } from "./FluentConfiguration";
2 import { ContainerServices, ContainerServicesConstraint, IDestroyable } from "./interfaces";
2 import { ContainerServicesConstraint, IDestroyable } from "../typings/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 };
49 };
50
50
51 export const prototype = <T extends object>(instance: T | null): T =>
51 export const prototype = <T extends object>(instance: T | null): T =>
52 Object.create(instance) as T; No newline at end of file
52 Object.create(instance) as T;
@@ -1,273 +1,292
1 import { ContainerBuilder } from "./ContainerBuilder";
1 import { ActivationItem } from "../ts/ActivationError";
2 import { key } from "./traits";
2 import { key } from "../ts/traits";
3
3
4 export interface IDestroyable {
4 export interface IDestroyable {
5 destroy(): void;
5 destroy(): void;
6 }
6 }
7
7
8 /**
8 /**
9 * @template S Карта доступных зависимостей
9 * @template S Карта доступных зависимостей
10 */
10 */
11 export interface Resolver<S> {
11 export interface Resolver<S> {
12 /**
12 /**
13 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
13 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
14 * отложенную активацию и значение по-умолчанию для сервисов
14 * отложенную активацию и значение по-умолчанию для сервисов
15 * @template K Ключ сервиса из {@linkcode S}
15 * @template K Ключ сервиса из {@linkcode S}
16 * @template O Тип параметра {@linkcode opts} используется для выведения типа
16 * @template O Тип параметра {@linkcode opts} используется для выведения типа
17 * возвращаемого значения.
17 * возвращаемого значения.
18 * @param name Ключ сервиса, который будет разрешен.
18 * @param name Ключ сервиса, который будет разрешен.
19 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
19 * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
20 * будет возвращен фабричный метод для получения зависимости. Если не указан,
20 * будет возвращен фабричный метод для получения зависимости. Если не указан,
21 * то считается `false`.
21 * то считается `false`.
22 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
22 * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
23 * сервис не зарегистрирован
23 * сервис не зарегистрирован
24 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
24 * @returns Либо фабричный метод для получения зависимости, либо значение зависимости
25 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
25 * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
26 */
26 */
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: true; }>(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 <K extends keyof S, O extends { lazy?: false; }>(name: K, opts?: O): NonNullable<S[K]> | InferDefault<O>;
29 }
29 }
30
30
31 export type DepsMap<S> = {
31 export type DepsMap<S> = {
32 [k in key]: Refs<S> | keyof S;
32 [k in key]: Refs<S> | keyof S;
33 };
33 };
34
34
35 export type DescriptorDepsMap<S> = {
36 [k in key]: Refs<S>;
37 };
38
35 export type Refs<S> = {
39 export type Refs<S> = {
36 [k in keyof S]: Ref<k, S[k]>;
40 [k in keyof S]: Ref<k, S[k]>;
37 }[keyof S];
41 }[keyof S];
38
42
39 export type Ref<K extends key, D> = {
43 export type Ref<K extends key, D> = {
40 /** The name of the service */
44 /** The name of the service */
41 name: K;
45 name: K;
42
46
43 /** Make a lazy reference, the resolved dependency will be a function */
47 /** Make a lazy reference, the resolved dependency will be a function */
44 lazy?: boolean;
48 lazy?: boolean;
45
49
46 /** The default value for the case where the service isn't defined.
50 /** The default value for the case where the service isn't defined.
47 * When specified the dependency becomes optional, the default value can be
51 * When specified the dependency becomes optional, the default value can be
48 * `null` or `undefined`
52 * `null` or `undefined`
49 */
53 */
50 default?: D | null
54 default?: D;
51 };
55 };
52
56
53 export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
57 export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
54
58
55 /** Возвращает тип свойства `default` в типе {@link T} */
59 /** Возвращает тип свойства `default` в типе {@link T} */
56 export type InferDefault<T> = T extends { default: infer D } ? D : never;
60 export type InferDefault<T> = T extends { default: infer D } ? D : never;
57
61
58 export type InferLazy<R> = R extends { lazy: infer L } ?
62 export type IsLazy<R> = R extends { lazy: infer L } ?
59 L extends true ? true : false :
63 L extends true ? true : false :
60 false;
64 false;
61 export type Resolve<S, R> =
65 export type Resolve<S, R> =
62 R extends keyof S ? NonNullable<S[R]> :
66 R extends keyof S ? NonNullable<S[R]> :
63 R extends Ref<infer K, unknown> ?
67 R extends Ref<infer K, unknown> ?
64 K extends keyof S ?
68 K extends keyof S ?
65 Lazy<NonNullable<S[K]> | InferDefault<R>, InferLazy<R>> :
69 Lazy<NonNullable<S[K]> | InferDefault<R>, IsLazy<R>> :
66 never :
70 never :
67 never;
71 never;
68
72
69 /**
73 /**
70 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
74 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
71 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
75 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
72 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
76 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
73 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
77 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
74 * {@linkcode value}.
78 * {@linkcode value}.
75 *
79 *
76 * @template S Карта сервисов контейнера, доступных при описании дескриптора
80 * @template S Карта сервисов контейнера, доступных при описании дескриптора
77 * @template T Тип сервиса
81 * @template T Тип сервиса
78 * @template R Карта зависимостей, которая передается параметром фабрике
82 * @template R Карта зависимостей, которая передается параметром фабрике
79 * @template U Имена пользовательских сервисов, доступных для переопределения
83 * @template U Имена пользовательских сервисов, доступных для переопределения
80 */
84 */
81 export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
85 export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
82
86
83 /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
87 /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
84 * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
88 * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
85 * которые были предварительно указаны вызовами метода `wants(...)`
89 * которые были предварительно указаны вызовами метода `wants(...)`
86 *
90 *
87 * Вызов данного метода завершает конфигурирование сервиса.
91 * Вызов данного метода завершает конфигурирование сервиса.
88 *
92 *
89 * @param f Фабрика для создания экземпляра сервиса.
93 * @param f Фабрика для создания экземпляра сервиса.
90 */
94 */
91 factory(f: (refs: R) => NonNullable<T>): void;
95 factory(f: (refs: R) => NonNullable<T>): void;
92
96
93 /**
97 /**
94 * Используется для указания зависимостей, которые потребуются фабричному
98 * Используется для указания зависимостей, которые потребуются фабричному
95 * методу при создании нового экземпляра сервиса. Данный метод может быть
99 * методу при создании нового экземпляра сервиса. Данный метод может быть
96 * вызван несколько раз подряд, при этом вызовы этого метода имеют
100 * вызван несколько раз подряд, при этом вызовы этого метода имеют
97 * кумулятивный эффект.
101 * кумулятивный эффект.
98 *
102 *
99 * @template X Тип объекта с зависимостями, которые требуется получить при
103 * @template X Тип объекта с зависимостями, которые требуется получить при
100 * создании экземпляра фабрики при помощи фабричного метода.
104 * создании экземпляра фабрики при помощи фабричного метода.
101 * @param refs Объект с описанием зависимостей
105 * @param refs Объект с описанием зависимостей
102 * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
106 * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
103 * зависимости
107 * зависимости
104 */
108 */
105 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
109 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
106 IDescriptorBuilder<S, T, R & {
110 IDescriptorBuilder<S, T, R & {
107 [k in keyof X]: Resolve<S, X[k]>;
111 [k in keyof X]: Resolve<S, X[k]>;
108 }, U>
112 }, U>
109
113
110 override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
114 override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
111 override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;
115 override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;
112
116
113 lifetime(lifetime: "singleton", typeId: string | number | object): this;
117 lifetime(lifetime: "singleton", typeId: string | number | object): this;
114 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
118 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
115
119
116 /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
120 /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
117 * время жизни привязано к контейнеру.
121 * время жизни привязано к контейнеру.
118 */
122 */
119 cleanup(cb: (item: T) => void): this;
123 cleanup(cb: (item: T) => void): this;
120
124
121 /**
125 /**
122 * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
126 * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
123 *
127 *
124 * @param v Экземпляр реализации сервиса.
128 * @param v Экземпляр реализации сервиса.
125 */
129 */
126 value(v: NonNullable<T>): void;
130 value(v: NonNullable<T>): void;
127 }
131 }
128
132
129 export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
133 export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
130
134
131 /**
135 /**
132 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
136 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
133 *
137 *
134 * Все параметры конфигурации являются обязательными, если требуется ввести
138 * Все параметры конфигурации являются обязательными, если требуется ввести
135 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
139 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
136 *
140 *
137 * @template S Сервисы доступные в контейнере
141 * @template S Сервисы доступные в контейнере
138 * @template K Сервисы участвующие в конфигурации
142 * @template K Сервисы участвующие в конфигурации
139 */
143 */
140 export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
144 export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
141 [k in K]-?: BuildDescriptorFn<S, S[k], U>
145 [k in K]-?: BuildDescriptorFn<S, S[k], U>
142 };
146 };
143
147
144 export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
148 export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
145 [k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
149 [k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
146 };
150 };
147
151
148 /**
152 /**
149 * The type constraint useful to restrict type parameters to prevent defining
153 * The type constraint useful to restrict type parameters to prevent defining
150 * the services with the {@link ContainerKeys} names.
154 * the services with the {@link ContainerKeys} names.
151 *
155 *
152 * The constraint doesn't exclude using this keys but declares them as `never`
156 * The constraint doesn't exclude using this keys but declares them as `never`
153 * which effectively will lead using this keys to the error.
157 * which effectively will lead using this keys to the error.
154 */
158 */
155 export type ContainerServicesConstraint<S> = {
159 export type ContainerServicesConstraint<S> = {
156 [k in keyof S]: k extends ContainerKeys ? never : S[k];
160 [k in keyof S]: k extends ContainerKeys ? never : S[k];
157 };
161 };
158
162
159 export interface Descriptor<S, T> {
163 export interface Descriptor<S, T> {
160
164
161 /** This flags indicates that this registration can be replaced or overridden. */
165 /** This flags indicates that this registration can be replaced or overridden. */
162 readonly configurable?: boolean;
166 readonly configurable?: boolean;
163
167
164 /** If specified signals the activation context that a new service scope
168 activate(context: IActivationContext<S>, stack: ActivationItem[]): NonNullable<T>;
165 * should be created to isolate service overrides.
166 */
167 readonly hasOverrides?: boolean;
168
169 activate(context: IActivationContext<S>): NonNullable<T>;
170 }
169 }
171
170
172 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
171 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
173 export interface ILifetimeContext {
172 export interface ILifetimeContext {
174
173
175 ownerSlot<T>(slotId: string | number): ILifetimeSlot<T>;
176
177 contextSlot<T>(slotId: string | number): ILifetimeSlot<T>;
174 contextSlot<T>(slotId: string | number): ILifetimeSlot<T>;
178
175
179 containerSlot<T>(slotId: string | number): ILifetimeSlot<T>;
176 /**
177 *
178 * @param slotId
179 */
180 scopeSlot<T>(level: number, slotId: string | number): ILifetimeSlot<T>;
180
181
182 hierarchySlot<T>(slotId: string | number): ILifetimeSlot<T>;
181 }
183 }
182
184
183 export interface IActivationContext<S> extends ILifetimeContext, ServiceLocator<S> {
185 export interface IActivationContext<S> extends ILifetimeContext {
184
186
185 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
187 /**
186
188 * @param overrides
187 fail(error: unknown): never;
189 * @param action
190 * @throws
191 */
192 withOverrides<X>(overrides: DescriptorMap<S>, action: () => X): X;
188
193
189 selfContainer(): ServiceLocator<S>;
194 selfContainer(): ServiceLocator<S>;
190
195
191 createChildContainer(): IContainerBuilder<S, Exclude<keyof S, ContainerKeys>>;
196 createChildContainer(): IContainerBuilder<S, keyof S>;
197
198 resolve<K extends keyof S>(name: K, stack: ActivationItem[]): NonNullable<S[K]>;
199 resolve<K extends keyof S, T>(name: K, stack: ActivationItem[], def: T): NonNullable<S[K]> | T;
192 }
200 }
193
201
194 /**
202 /**
195 * Descriptors map for the specified services {@linkcode S}. All entries are
203 * Descriptors map for the specified services {@linkcode S}. All entries are
196 * optional regardless the required or optional services in the original map.
204 * optional regardless the required or optional services in the original map.
197 *
205 *
198 * @template S Сервисы контекста активации
206 * @template S Сервисы контекста активации
199 * @template U Карта сервисов которые создаются дескрипторами
207 * @template U Карта сервисов которые создаются дескрипторами
200 */
208 */
201 export type DescriptorMap<S> = {
209 export type DescriptorMap<S> = {
202 [k in keyof S]?: Descriptor<S, S[k]>;
210 [k in keyof S]?: Descriptor<S, S[k]>;
203 };
211 };
204
212
205 type ContainerKeys = keyof ContainerProvided<object>;
213 export type ContainerKeys = keyof ContainerProvided<object>;
206
214
207 export type ContainerProvided<S, U extends keyof S = keyof S> = {
215 export type ContainerProvided<S, U extends keyof S = keyof S> = {
208 container: ServiceLocator<ContainerProvided<S>>;
216 container: ServiceLocator<ContainerProvided<S>>;
209
217
210 childContainer: IContainerBuilder<S, U>;
218 childContainer: IContainerBuilder<S, U>;
211 };
219 };
212
220
213
221
214 /**
222 /**
215 * Таблица сервисов, которые предоставляет контейнер.
223 * Таблица сервисов, которые предоставляет контейнер.
216 *
224 *
217 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
225 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
218 */
226 */
219 export type ContainerServices<S> = {
227 export type ContainerServices<S> = {
220 [k in (keyof S) | ContainerKeys]:
228 [k in (keyof S) | ContainerKeys]:
221 k extends ContainerKeys ? ContainerProvided<S>[k] :
229 k extends ContainerKeys ? ContainerProvided<S>[k] :
222 k extends keyof S ? S[k] : never
230 k extends keyof S ? S[k] : never
223 };
231 };
224
232
225
233
226 /**
234 /**
227 * Returns the service declared in the type map {@link S}.
235 * Returns the service declared in the type map {@link S}.
228 *
236 *
229 *
237 *
230 */
238 */
231 export interface ServiceLocator<S> {
239 export interface ServiceLocator<S> {
232 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
240 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
233 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
241 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
234 }
242 }
235
243
236
244
237 export interface IContainerBuilder<S, U extends keyof S> {
245 export interface IContainerBuilder<S, U extends keyof S> {
238 createServiceBuilder<K extends U>(name: K):
246 createServiceBuilder<K extends U>(name: K):
239 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
247 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
240
248
241 build(): ServiceLocator<S>;
249 build(): ServiceLocator<S>;
242 }
250 }
243
251
244
252
245 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
253 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
246
254
247 /**
255 /**
248 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
256 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
249 * свой собственный объект `ILifetime`, который создается при первой активации
257 * свой собственный объект `ILifetime`, который создается при первой активации
250 */
258 */
251 export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<T>;
259 export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<T>;
252
260
253 export interface ILifetimeSlot<T> {
261 export interface ILifetimeSlot<T> {
254 readonly has: () => boolean;
262 has(): boolean;
263
264 get(): NonNullable<T>;
255
265
256 readonly get: () => T;
266 /**
257
267 * Initializes the slot, if the slot is already initialized returns false,
258 readonly initialize: () => void;
268 * otherwise returns true. This method is used to detect cyclic references.
269 */
270 initialize(): boolean;
259
271
260 readonly store: (item: T, cleanup?: (item: T) => void) => void;
272 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
261
273
262 readonly remove: () => void;
274 /**
275 * Removes the slot from the lifetime manager. The inner value will not be
276 * disposed. If the slot is cleaned up calling this method has no effect.
277 */
278 remove(): void;
263
279
264 readonly cleanup: () => void;
280 /**
281 * Disposed inner value of this slot.
282 */
283 cleanup(): void;
265 }
284 }
266
285
267 export interface ILifetimeManager extends IDestroyable {
286 export interface ILifetimeManager extends IDestroyable {
268 slot<T>(id: string | number): ILifetimeSlot<T>;
287 slot<T>(id: string | number): ILifetimeSlot<T>;
269 }
288 }
270
289
271 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
290 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
272
291
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
292 export type ExtractRequiredKeys<T, K extends keyof T = keyof T> = { [p in K]-?: undefined extends T[p] ? never : p }[K];
@@ -1,12 +1,11
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": "ES2018",
9 "target": "ESNext"
10 "lib": ["ES2018", "DOM", "ScriptHost"]
11 }
10 }
12 } No newline at end of file
11 }
General Comments 0
You need to be logged in to leave comments. Login now