diff --git a/README.md b/README.md index cb316a6e..4b8ec78b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Dependency Injection container (IoC) for TypeScript. * [Lazy dependency injection (default)](#lazy-injection) * [Lifecycle listeners](#lifecycle-listeners) * [Eager components](#eager-components) +* [Debug logging](#debug-logging) # Usage @@ -286,6 +287,11 @@ const tsdi: TSDI = new TSDI(); tsdi.register(A); // <-- here the class A is instantiated ``` +### Debug logging + +To inspect which component is created when and injected where one can enable debug logging by either +set the environment variable `DEBUG` (node) or a localStorage key (browser) `debug` to `tsdi`. + ## Future ideas / Roadmap * Static factories diff --git a/lib/component.ts b/lib/component.ts index 1a7879d2..f1e4aa66 100644 --- a/lib/component.ts +++ b/lib/component.ts @@ -2,11 +2,14 @@ import { IComponentOptions } from './decorators'; import { addKnownComponent } from './global-state'; import { getNamedOptions } from './helper'; +import * as debug from 'debug'; +const log = debug('tsdi'); + export function Component(target: TFunction): TFunction; export function Component(optionsOrString?: IComponentOptions | string): ClassDecorator; export function Component(...args: any[]): ClassDecorator| TFunction { const decorate = (target: TFunction, optionsOrString: IComponentOptions | string = {}) => { - // console.log(`@Component ${(target as any).name}`); + log(`@Component ${(target as any).name}`); const options = getNamedOptions(optionsOrString); addKnownComponent({ fn: target as any, diff --git a/lib/decorators.ts b/lib/decorators.ts index 72f1c0df..27debd0c 100644 --- a/lib/decorators.ts +++ b/lib/decorators.ts @@ -10,6 +10,9 @@ import { removeElement } from './helper'; +import * as debug from 'debug'; +const log = debug('tsdi'); + export type Constructable = { new(...args: any[]): T; }; export type IComponentOptions = ComponentOptions; @@ -136,9 +139,14 @@ export class TSDI { console.warn(`Component with name '${componentMetadata.options.name}' already registered.`); } + log('registerComponent %o', isFactoryMetadata(componentMetadata) ? + (componentMetadata.rtti as any).name : (componentMetadata.fn as any).name); this.components.push(componentMetadata); if (componentMetadata.options.eager) { - this.getOrCreate(componentMetadata, this.components.length - 1); + const idx = this.components.length - 1; + setTimeout(() => { + this.getOrCreate(componentMetadata, idx); + }, 0); } } } @@ -204,13 +212,16 @@ export class TSDI { } private getOrCreate(metadata: ComponentOrFactoryMetadata, idx: number): T { + log('> getOrCreate %o', metadata); // todo: Use T here let instance: any = this.instances[idx]; if (!instance || !this.isSingleton(metadata)) { if (isFactoryMetadata(metadata)) { + log('create %o from factory with %o', (metadata.rtti as any).name, metadata.options); instance = this.get(metadata.target.constructor as Constructable)[metadata.property](); this.instances[idx] = instance; } else { + log('create %o with %o', (metadata.fn as any).name, metadata.options); const constructor: Constructable = metadata.fn as any; const parameters = this.getConstructorParameters(metadata); instance = new constructor(...parameters); @@ -225,6 +236,7 @@ export class TSDI { } this.notifyOnCreate(instance); } + log('< getOrCreate %o -> %o', metadata, instance); return instance; } @@ -243,6 +255,7 @@ export class TSDI { const injects: InjectMetadata[] = Reflect.getMetadata('component:injects', componentMetadata.fn.prototype); if (injects) { for (const inject of injects) { + log('injecting %s.%s', instance.constructor.name, inject.property); if (inject.options.name && typeof this.properties[inject.options.name] !== 'undefined') { instance[inject.property] = this.properties[inject.options.name]; } else { @@ -254,8 +267,10 @@ export class TSDI { get(): any { let value = instance[`tsdi$${inject.property}`]; if (!value) { + log('lazy-resolve injected property %s.%s', instance.constructor.name, inject.property); value = tsdi.getComponentDependency(inject); instance[`tsdi$${inject.property}`] = value; + log('lazy-resolved to %o', value); } return value; } @@ -284,12 +299,18 @@ export class TSDI { private checkAndThrowDependencyError(inject: InjectMetadata): void { if (inject.type && inject.options.name) { - throw new Error(`Injecting undefined type on ${(inject.target.constructor as any).name}` + const e = new Error(`Injecting undefined type on ${(inject.target.constructor as any).name}` + `#${inject.property}: Component named '${inject.options.name}' not found`); + log(e); + log('Known Components: %o', this.components.map(component => + isFactoryMetadata(component) ? (component.rtti as any).name : (component.fn as any).name)); + throw e; } if (!inject.type || inject.options.name) { - throw new Error(`Injecting undefined type on ${(inject.target.constructor as any).name}` + const e = new Error(`Injecting undefined type on ${(inject.target.constructor as any).name}` + `#${inject.property}: Probably a cyclic dependency, switch to name based injection`); + log(e); + throw e; } } diff --git a/lib/external.ts b/lib/external.ts index e9140800..9efb9c7d 100644 --- a/lib/external.ts +++ b/lib/external.ts @@ -1,17 +1,20 @@ import { addKnownExternal } from './global-state'; +import * as debug from 'debug'; +const log = debug('tsdi'); + export function External(target: TFunction): TFunction; export function External(): ClassDecorator; export function External(...args: any[]): ClassDecorator | TFunction { const decorate = (target: TFunction) => { - // console.log(`@External ${(target as any).name}`); + log(`@External ${(target as any).name}`); addKnownExternal(target); const constructor = function InjectedConstructor(this: any, ...args: any[]): any { return (target as any).__tsdi__.configureExternal(args, target); }; (constructor as any).displayName = (target as any).name; Object.getOwnPropertyNames(target) - .filter(prop => !(constructor as any)[prop] && prop !== 'length') + .filter(prop => !(constructor as any)[prop] && prop !== 'name' && prop !== 'length') .forEach(prop => (constructor as any)[prop] = (target as any)[prop]); constructor.prototype = target.prototype; return constructor as any; diff --git a/lib/factory.ts b/lib/factory.ts index 93c9071e..44fa9f49 100644 --- a/lib/factory.ts +++ b/lib/factory.ts @@ -1,11 +1,17 @@ import { IFactoryOptions } from './decorators'; import { addKnownComponent } from './global-state'; +import * as debug from 'debug'; +const log = debug('tsdi'); + export function Factory(target: Object, propertyKey: string): void; export function Factory(options?: IFactoryOptions): MethodDecorator; export function Factory(...args: any[]): MethodDecorator | void { const decorate = (target: Object, propertyKey: string, options: IFactoryOptions) => { - // console.log(`@Factory ${(target as any)[propertyKey].name}`); + if (log.enabled) { + log('@Factory %s#%s({name: "%s"})', (target.constructor as any).name, propertyKey, + (target as any)[propertyKey].name); + } addKnownComponent({ target, property: propertyKey, @@ -19,13 +25,7 @@ export function Factory(...args: any[]): MethodDecorator | void { } const options = args[0] || {}; return function(target: Object, propertyKey: string): void { - // console.log(`@Factory ${(target as any)[propertyKey].name}`); - addKnownComponent({ - target, - property: propertyKey, - options, - rtti: Reflect.getMetadata('design:returntype', target, propertyKey) - }); + decorate(target, propertyKey, options); }; } export const factory = Factory; diff --git a/lib/initialize.ts b/lib/initialize.ts index 6a0190fc..678a412f 100644 --- a/lib/initialize.ts +++ b/lib/initialize.ts @@ -1,8 +1,11 @@ +import * as debug from 'debug'; +const log = debug('tsdi'); + export function Initialize(target: Object, propertyKey: string): void; export function Initialize(): MethodDecorator; export function Initialize(...args: any[]): MethodDecorator | void { const decorate = (target: Object, propertyKey: string) => { - // console.log(`@Initialize ${(target as any)[propertyKey].name}`); + log('@Initialize %s#%s', (target.constructor as any).name, propertyKey); Reflect.defineMetadata('component:init', propertyKey, target); }; if (args.length > 0) { diff --git a/lib/inject.ts b/lib/inject.ts index 92e7e38e..e58247ac 100644 --- a/lib/inject.ts +++ b/lib/inject.ts @@ -6,6 +6,9 @@ import { } from './decorators'; import { getNamedOptions } from './helper'; +import * as debug from 'debug'; +const log = debug('tsdi'); + export function Inject(target: Object, propertyKey: string | symbol, parameterIndex?: number): void; export function Inject(optionsOrString?: IInjectOptions | string): PropertyDecorator & ParameterDecorator; export function Inject(...args: any[]): PropertyDecorator & ParameterDecorator | void { @@ -18,7 +21,7 @@ export function Inject(...args: any[]): PropertyDecorator & ParameterDecorator | }; const decorateProperty = (target: Object, propertyKey: string, options: IInjectOptions) => { - // console.log(`@Inject ${(target.constructor as any).name}#${propertyKey}`); + log(`@Inject ${(target.constructor as any).name}#${propertyKey}`); const type: Constructable = Reflect.getMetadata('design:type', target, propertyKey); let injects: InjectMetadata[] = Reflect.getMetadata('component:injects', target); if (!injects) { @@ -34,7 +37,7 @@ export function Inject(...args: any[]): PropertyDecorator & ParameterDecorator | }; const decorateParameter = (target: Object, propertyKey: string | symbol, parameterIndex: number, options: IInjectOptions) => { - // console.log(`@Inject ${propertyKey}`); + log(`@Inject ${propertyKey}`); let parameters: ParameterMetadata[] = Reflect.getMetadata('component:parameters', target); if (!parameters) { parameters = []; diff --git a/package-lock.json b/package-lock.json index fcd525a8..6722bb32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,13 @@ "@types/chai": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.2.tgz", - "integrity": "sha512-0pHNZTD0SpQhz1kUM4Muhgdn4acxq21kp726pZfWMmKYGbmmv8XkGTt3k/0QDklhTUYBD6hknZ/1YFokyP/G7Q==", + "integrity": "sha1-zOlmb17i+if/1QXS369TuwLtJYQ=", + "dev": true + }, + "@types/debug": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.30.tgz", + "integrity": "sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ==", "dev": true }, "@types/mocha": { @@ -25,7 +31,7 @@ "@types/node": { "version": "8.0.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.19.tgz", - "integrity": "sha512-VRQB+Q0L3YZWs45uRdpN9oWr82meL/8TrJ6faoKT5tp0uub2l/aRMhtm5fo68h7kjYKH60f9/bay1nF7ZpTW5g==", + "integrity": "sha1-5G4rAkPefQPxWya0XFnruE9lek4=", "dev": true }, "align-text": { @@ -340,7 +346,7 @@ "conventional-changelog-angular": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.4.0.tgz", - "integrity": "sha512-ukKX22lJl9ewogze1hKbBuff/dGMG2uyGpOhhw0ehhlv6GtdeCxj51YfGOZ5qC89WwsHT7SDXFzBKidwH3pwmQ==", + "integrity": "sha1-EYuffUGj2ZUAv7a+ofNSXgVei5s=", "dev": true, "requires": { "compare-func": "1.3.2", @@ -352,7 +358,7 @@ "conventional-changelog-atom": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-0.1.1.tgz", - "integrity": "sha512-6Nlu/+MiD4gi7k3Z+N1vMJWpaPSdvFPWzPGnH4OXewHAxiAl0L/TT9CGgA01fosPxmYr4hMNtD7kyN0tkg8vIA==", + "integrity": "sha1-1AqbKXlhtTx0Xl0XGP0aM3n2qS8=", "dev": true, "requires": { "q": "1.5.0" @@ -570,7 +576,7 @@ "conventional-recommended-bump": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-1.0.1.tgz", - "integrity": "sha512-2FrE8UJwt2EKpICi/E7P+xqyYUysdfMnFYiGV+7ANzJsixkBOrBKrKlCEV2NllCjR0XOmNfnA/mgozC5jAhhGQ==", + "integrity": "sha1-VriuVTqKEVL6Bp52dZnh9pSL02w=", "dev": true, "requires": { "concat-stream": "1.6.0", @@ -585,7 +591,7 @@ "conventional-commits-parser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.0.0.tgz", - "integrity": "sha512-8od6g684Fhi5Vpp4ABRv/RBsW1AY6wSHbJHEK6FGTv+8jvAAnlABniZu/FVmX9TcirkHepaEsa1QGkRvbg0CKw==", + "integrity": "sha1-cdAZEMsKma6yDBROUPgfTfMXhEc=", "dev": true, "requires": { "is-text-path": "1.0.1", @@ -704,10 +710,9 @@ } }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.0.1.tgz", + "integrity": "sha512-6nVc6S36qbt/mutyt+UGMnawAMrPDZUPQjRZI3FS9tCtDRhvxJbK79unYBLPi+z5SLXQ3ftoVBFCblQtNSls8w==", "requires": { "ms": "2.0.0" } @@ -985,7 +990,7 @@ "git-semver-tags": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.2.1.tgz", - "integrity": "sha512-fFyxtzTHCTQKwB4clA2AInVrlflBbVbbJD4NWwmxKXHUgsU/K9kmHNlkPLqFiuy9xu9q3lNopghR4VXeQwZbTQ==", + "integrity": "sha1-bM0qUuc1tzZ0jcdiRE/NlYjidJA=", "dev": true, "requires": { "meow": "3.7.0", @@ -1712,7 +1717,7 @@ "mocha": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.0.tgz", - "integrity": "sha512-pIU2PJjrPYvYRqVpjXzj76qltO9uBYI7woYAMoxbSefsa+vqAfptjoeevd6bUgwD0mPIO+hv9f7ltvsNreL2PA==", + "integrity": "sha1-EyhWfScX+ZcDD4AGI0vOm4zXJGU=", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -1737,6 +1742,15 @@ "graceful-readlink": "1.0.1" } }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "supports-color": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", @@ -1767,8 +1781,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "nave": { "version": "0.5.3", @@ -3764,7 +3777,7 @@ "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=", "dev": true }, "set-blocking": { @@ -4200,7 +4213,7 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "integrity": "sha1-YQY29rH3A4kb00dxzLF/uTtHB5w=", "dev": true }, "wordwrap": { diff --git a/package.json b/package.json index c59b3863..de6ebaf7 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "devDependencies": { "@knisterpeter/standard-tslint": "^1.5.1", "@types/chai": "^4.0.2", + "@types/debug": "0.0.30", "@types/mocha": "^2.2.41", "@types/node": "^8.0.19", "chai": "^4.1.0", @@ -52,6 +53,7 @@ "typescript": "^2.3.2" }, "dependencies": { + "debug": "^3.0.1", "reflect-metadata": "^0.1.10" }, "config": { diff --git a/tests/index-test.ts b/tests/index-test.ts index ecf87779..12974dc2 100644 --- a/tests/index-test.ts +++ b/tests/index-test.ts @@ -360,7 +360,7 @@ describe('TSDI', () => { assert.isDefined(component.dependency); }); - it('should create eager components as soon as possible', () => { + it('should create eager components as soon as possible', (done: MochaDone) => { tsdi.enableComponentScanner(); let count = 0; @@ -372,15 +372,22 @@ describe('TSDI', () => { } } - assert.equal(count, 1); + setTimeout(() => { + assert.equal(count, 1); + done(); + }, 1); }); - it('should respect dependency tree for eager creation', () => { + it('should respect dependency tree for eager creation', (done: MochaDone) => { tsdi.enableComponentScanner(); const eager1 = tsdi.get(EagerComponent1); const eager2 = tsdi.get(EagerComponent2); - assert.strictEqual(eager1.dependency, eager2); + + setTimeout(() => { + assert.strictEqual(eager1.dependency, eager2); + done(); + }, 1); }); it('should call lifecycle listener on component creation', () => {