@b08/injector-generator
v2.2.1
Published
generator for injector functions
Downloads
47
Readme
@b08/injector-generator, seeded from @b08/generator-seed, library type: generator
generator for injector functions
conceptual thoughts
- This is a static dependency injection mechanism. Static means that all resolving logic works in design time. Once it is generated and compiled, all dependencies are locked.
- Unresolved dependencies float up. So, if dependency of the service you want to resolve has an unresolved dependency, then your service has an unresolved dependency, thus, in order to call injector function for that service, you will have to provide a parameter of that type.
When several dependencies require the parameter of the same type, it will be collapsed into one parameter for higher level service.
what is an injector
It is a function, creating an instance of your class, calling corresponding injectors to create dependencies for your class Example:
export class Svc1 {
constructor(private svc2: Svc2) { }
}
export class Svc2 { }
For these 2 classes following injectors will be generated:
export function svc1(): Svc1 { return new Svc1(svc2()); }
export function svc2(): Svc2 { return new Svc2(); }
This is a simplified example of the code that is actually generated, only to demonstrate what those functions do. Actual code includes a passing "instances" object, so that only one instance would be generated per type. I.e. two different services, depending of third service will receive the same instance of the third service.
emerging parameters
if a dependency is not a class or not in parsed files at all, it acts as an external dependency and emerges on top of the injector chain Example:
export class Svc1 {
constructor(private svc2: Svc2) { }
}
export class Svc2 {
constructor(private name: string) { }
}
For these 2 classes following injectors will be generated:
export function svc1(name: string): Svc1 { return new Svc1(svc2(name)); }
export function svc2(name: string): Svc2 { return new Svc2(name); }
implementation replacements
Classes implementing interfaces will be automatically used in place of those interfaces. Example, source:
export interface IMyService { }
export class MyService implements IMyService { }
export class SecondService {
constructor(private svc: IMyService) { }
}
injectors to be generated:
export function myService(): MyService {
return new MyService();
}
export function secondService(): SecondService {
return new SecondService(myService());
}
No generics supported in all replacements.
mock replacements
Classes extending other classes and having same name with Mock suffix will be used in place of those classes.
// app folder
export class MyClass {}
// test folder
export class MyClassMock extends MyClass {}
Usage case: production build only affects "app" folder, so dependency will be used directly, test build also affects "test" folder, mocks in that folder will replace original classes.
named replacement via comment
// app folder
export class Dal1 { }
// test folder
// replacement for Dal1
export class Dal1Mock { }
Comment above Dal1Mock says that this mock is to replace "Dal1" class in injection chain.
configurable replacements
This config triggers the same replacement
const dalMock = { folder: "./folder-of-Dal1Mock", file: "file-name", name: "Dal1Mock", isModulesPath: false }
const options = {
nameReplacements:[{typeNameRegex: /^Dal1$/, replacement: dalMock }]
}
replacements, order of precedence
- Type replacements from options
- implementations and extensions
- Name(regex) replacements from options
- Comment replacements First rule to match wins.
context replacement
Second reason for a replacement is using a big context object for your application while services could be depending on fields of that context. Example:
export interface IContext {
db: IDb
}
export class Svc {
constructor(private db: IDb) { }
}
// this will be generated
export function svc(context: IContext) {
return new Svc(context.db);
}
You will need to include file with context into the sources. If you don't include that file for some reason, you can also specify the replacement in the options.
generating injectors
import { generateInjectors } from "@b08/injector-generator"; import { transformRange, transform } from "@b08/gulp-transform"; import * as changed from "gulp-changed";
const options = { lineFeed: "\n", quotes: """ };
export function injectors(): NodeJS.ReadWriteStream { // this is a gulp task
return gulp.src("./app/**/*.@(service|context).ts")
.pipe(transformRange(files => generateInjectors(files, options)))
.pipe(changed(dest, { hasChanged: changed.compareContents }))
.pipe(logWrittenFilesToConsole)
.pipe(gulp.dest("./app"));
}
const logWrittenFilesToConsole = transform(file => {
console.log(Writing ${file.folder}/${file.name}${file.extension}
);
return file;
});