@travetto/di
Advanced tools
Comparing version 0.0.9 to 0.0.10
@@ -22,3 +22,3 @@ { | ||
}, | ||
"version": "0.0.9" | ||
"version": "0.0.10" | ||
} |
@@ -25,9 +25,27 @@ import { InjectableConfig, Dependency } from '../types'; | ||
export function Inject(config?: InjectConfig) { | ||
function extractSymbolOrConfig<T extends { qualifier?: Symbol }>(args: any[]) { | ||
const out = {} as T; | ||
if (args) { | ||
let extra = args[0]; | ||
if (typeof extra === 'symbol') { | ||
out.qualifier = extra; | ||
extra = args[1]; | ||
} | ||
Object.assign(out, extra); | ||
} | ||
return out; | ||
} | ||
export function Inject(symbol: symbol, config?: InjectConfig): ParameterDecorator & PropertyDecorator; | ||
export function Inject(config?: InjectConfig): ParameterDecorator & PropertyDecorator; | ||
export function Inject(...args: any[]): ParameterDecorator & PropertyDecorator { | ||
return (target: any, propertyKey: string | symbol, idx?: number) => { | ||
if (typeof idx !== 'number') { // Only register if on property | ||
const config: InjectConfig = extractSymbolOrConfig(args); | ||
DependencyRegistry.registerProperty( | ||
target.constructor, | ||
propertyKey as string, | ||
config as any as Dependency); | ||
(typeof config === 'symbol' ? { qualifier: config } : config) as any as Dependency); | ||
} | ||
@@ -37,6 +55,10 @@ }; | ||
export function InjectableFactory(config: InjectableFactoryConfig<any>): MethodDecorator { | ||
export function InjectableFactory(config: InjectableFactoryConfig<any>): MethodDecorator; | ||
export function InjectableFactory(symbol: symbol, config?: InjectableFactoryConfig<any>): MethodDecorator; | ||
export function InjectableFactory(...args: any[]): MethodDecorator { | ||
return (target: any, property: string | symbol, descriptor: TypedPropertyDescriptor<any>) => { | ||
const config: InjectableFactoryConfig<any> = extractSymbolOrConfig(args); | ||
DependencyRegistry.registerFactory({ ...config, fn: descriptor.value, id: `${target.__id}#${property}` }); | ||
}; | ||
} |
@@ -7,2 +7,4 @@ import { Dependency, InjectableConfig, ClassTarget, InjectableFactoryConfig } from '../types'; | ||
import * as _ from 'lodash'; | ||
export const DEFAULT_INSTANCE = Symbol('__default'); | ||
@@ -21,2 +23,14 @@ | ||
function mergeWithOptional<T extends { original?: symbol | object, qualifier?: symbol }>(o: T) { | ||
if (o.original) { | ||
if (typeof o.original === 'symbol') { | ||
o.qualifier = o.original; | ||
} else if (_.isPlainObject(o.original)) { | ||
_.merge(o, o.original) | ||
} | ||
o.original = undefined; | ||
} | ||
return o; | ||
} | ||
export class $DependencyRegistry extends MetadataRegistry<InjectableConfig> { | ||
@@ -78,14 +92,3 @@ private pendingFinalize: Class[] = []; | ||
async construct<T>(target: ClassTarget<T & ManagedExtra>, qualifier: symbol = DEFAULT_INSTANCE): Promise<T> { | ||
const targetId = target.__id; | ||
const aliasMap = this.aliases.get(targetId); | ||
if (!aliasMap || !aliasMap.has(qualifier)) { | ||
throw new InjectionError(`Dependency not found: ${targetId}[${getName(qualifier)}]`); | ||
} | ||
const clz = aliasMap.get(qualifier)!; | ||
const managed = this.get(clz)!; | ||
async computeDependencies(managed: InjectableConfig<any>) { | ||
const fieldKeys = Object.keys(managed.dependencies.fields!); | ||
@@ -96,2 +99,6 @@ | ||
for (const dep of allDeps) { | ||
mergeWithOptional(dep); | ||
} | ||
const promises = allDeps | ||
@@ -115,2 +122,30 @@ .map(async x => { | ||
const fields = new Map<string, any>(); | ||
for (let i = 0; i < fieldKeys.length; i++) { | ||
fields.set(fieldKeys[i], fieldValues[i]); | ||
} | ||
return { consValues, fields } | ||
} | ||
applyFieldDependencies(inst: any, fields: Map<string, any>) { | ||
for (const [key, value] of fields.entries()) { | ||
inst[key] = value; | ||
} | ||
} | ||
async construct<T>(target: ClassTarget<T & ManagedExtra>, qualifier: symbol = DEFAULT_INSTANCE): Promise<T> { | ||
const targetId = target.__id; | ||
const aliasMap = this.aliases.get(targetId); | ||
if (!aliasMap || !aliasMap.has(qualifier)) { | ||
throw new InjectionError(`Dependency not found: ${targetId}[${getName(qualifier)}]`); | ||
} | ||
const clz = aliasMap.get(qualifier)!; | ||
const managed = this.get(clz)!; | ||
const { consValues, fields } = await this.computeDependencies(managed); | ||
const inst = managed.factory ? | ||
@@ -120,5 +155,3 @@ managed.factory(...consValues) : | ||
for (let i = 0; i < fieldKeys.length; i++) { | ||
(inst as any)[fieldKeys[i]] = fieldValues[i]; | ||
} | ||
this.applyFieldDependencies(inst, fields); | ||
@@ -128,2 +161,3 @@ if (inst.postConstruct) { | ||
} | ||
return inst; | ||
@@ -211,2 +245,3 @@ } | ||
const conf = this.getOrCreatePending(cls); | ||
conf.dependencies!.fields[field] = dependency; | ||
@@ -242,5 +277,9 @@ dependency.qualifier = dependency.qualifier || DEFAULT_INSTANCE; | ||
const finalConfig: InjectableConfig<any> = {} as any; | ||
mergeWithOptional(config); | ||
if (typeof config.autoCreate === 'boolean') { | ||
finalConfig.autoCreate = { create: config.autoCreate } as any; | ||
} | ||
finalConfig.factory = config.fn; | ||
@@ -253,6 +292,16 @@ finalConfig.target = config.class; | ||
finalConfig.dependencies = { fields: {} }; | ||
if (config.dependencies) { | ||
finalConfig.dependencies = { | ||
cons: config.dependencies, | ||
fields: {} | ||
finalConfig.dependencies.cons = config.dependencies; | ||
} | ||
// Mirror target's field dependencies | ||
if (config.class) { | ||
const targetConfig = this.get(config.class); | ||
if (targetConfig && targetConfig.dependencies) { | ||
finalConfig.dependencies.fields = {}; | ||
for (const k of Object.keys(targetConfig.dependencies.fields)) { | ||
finalConfig.dependencies.fields[k] = targetConfig.dependencies.fields[k]; | ||
} | ||
} | ||
@@ -276,2 +325,3 @@ } | ||
// Allow for the factory to fulfill the target | ||
const parentClass = Object.getPrototypeOf(cls); | ||
@@ -278,0 +328,0 @@ const parentConfig = this.get(parentClass.__id); |
@@ -21,4 +21,23 @@ import * as ts from 'typescript'; | ||
const finalTarget = TransformUtil.importIfExternal(param.type!, state); | ||
const injectConfig = TransformUtil.getPrimaryArgument<ts.ObjectLiteralExpression>(injection); | ||
let injectConfig = TransformUtil.getPrimaryArgument<ts.ObjectLiteralExpression>(injection); | ||
let original = undefined; | ||
const callExpr = (injection && injection.expression as any as ts.CallExpression); | ||
if (callExpr) { | ||
const args = callExpr.arguments! || []; | ||
// Handle special case | ||
if (args.length && ts.isIdentifier(args[0])) { | ||
original = args[0]; | ||
injectConfig = args[1] as any; | ||
} | ||
} | ||
if (injectConfig === undefined) { | ||
injectConfig = TransformUtil.fromLiteral({}); | ||
} | ||
let optional = TransformUtil.getObjectValue(injectConfig, 'optional'); | ||
@@ -31,2 +50,3 @@ | ||
return TransformUtil.fromLiteral({ | ||
original, | ||
target: finalTarget, | ||
@@ -138,2 +158,3 @@ optional, | ||
let injectArgs: object[] = []; | ||
let original: any; | ||
@@ -149,30 +170,49 @@ try { | ||
if (injectArgs.length) { | ||
const foundExpr = (foundDec.expression as ts.CallExpression); | ||
let injectConfig = TransformUtil.getPrimaryArgument<ts.ObjectLiteralExpression>(foundDec); | ||
const args = TransformUtil.extendObjectLiteral({ | ||
dependencies: injectArgs | ||
}, foundExpr.arguments[0] as ts.ObjectLiteralExpression); | ||
const callExpr = (foundDec && foundDec.expression as any as ts.CallExpression); | ||
if (callExpr) { | ||
const args = callExpr.arguments! || []; | ||
// Handle special case | ||
if (args[0] && ts.isIdentifier(args[0])) { | ||
original = args[0]; | ||
injectConfig = args[1] as any; | ||
} | ||
} | ||
node = ts.createMethod( | ||
decls!.filter(x => x !== foundDec).concat([ | ||
ts.createDecorator( | ||
ts.createCall( | ||
foundExpr.expression, | ||
foundExpr.typeArguments, | ||
ts.createNodeArray([args]) | ||
) | ||
) | ||
]), | ||
node.modifiers, | ||
node.asteriskToken, | ||
node.name, | ||
node.questionToken, | ||
node.typeParameters, | ||
node.parameters, | ||
node.type, | ||
node.body | ||
) as any; | ||
if (injectConfig === undefined) { | ||
injectConfig = TransformUtil.fromLiteral({}); | ||
} | ||
// Handle when | ||
let target = TransformUtil.getObjectValue(injectConfig, 'target'); | ||
if (node.type && target === undefined) { // TODO: infer from typings, not just text? | ||
target = TransformUtil.importIfExternal(node.type!, state); | ||
} | ||
const args = TransformUtil.extendObjectLiteral({ | ||
dependencies: injectArgs, | ||
class: target, | ||
original | ||
}, injectConfig); | ||
node = ts.createMethod( | ||
decls!.filter(x => x !== foundDec).concat([ | ||
ts.createDecorator( | ||
ts.createCall( | ||
callExpr.expression, | ||
callExpr.typeArguments, | ||
ts.createNodeArray([args]) | ||
) | ||
) | ||
]), | ||
node.modifiers, | ||
node.asteriskToken, | ||
node.name, | ||
node.questionToken, | ||
node.typeParameters, | ||
node.parameters, | ||
node.type, | ||
node.body | ||
) as any; | ||
return node; | ||
@@ -179,0 +219,0 @@ } else { |
@@ -19,2 +19,3 @@ import { Class } from '@travetto/registry'; | ||
optional?: boolean; | ||
original?: Symbol | object; | ||
} | ||
@@ -27,2 +28,3 @@ | ||
autoCreate?: boolean | ||
original?: Symbol | object; | ||
} |
@@ -1,8 +0,21 @@ | ||
import { Injectable } from '../src/decorator'; | ||
import { Injectable, Inject } from '../src/decorator'; | ||
import { Util } from './util'; | ||
import { Config } from '@travetto/config'; | ||
@Injectable() | ||
export class Empty { | ||
public age = 10; | ||
} | ||
class Basic { | ||
@Inject() | ||
public empty: Empty; | ||
} | ||
@Config('a') | ||
export class DbConfig<A, B> { | ||
export class DbConfig<A, B> extends Basic { | ||
public temp: any; | ||
constructor() { | ||
super(); | ||
console.log('Creating dbconfigs'); | ||
@@ -9,0 +22,0 @@ } |
import { Injectable, Inject, InjectableFactory } from '../src'; | ||
import { DbConfig, AltConfig } from './config'; | ||
import { DbConfig, AltConfig, Empty } from './config'; | ||
@@ -47,8 +47,28 @@ @Injectable() | ||
export const CUSTOM_SERVICE_INHERIT = Symbol('Custom'); | ||
export const CUSTOM_DATABSE = Symbol('CUSTOM DB'); | ||
export const CUSTOM_EMPTY = Symbol('Custom EMPTY'); | ||
class TestConfig { | ||
@InjectableFactory({ class: ServiceInherit, qualifier: CUSTOM_SERVICE_INHERIT }) | ||
static getObject(@Inject({ qualifier: SERVICE_INHERIT_2 }) svc: ServiceInherit) { | ||
@InjectableFactory(CUSTOM_EMPTY) | ||
static getNewEmpty(): Empty { | ||
const out = new Empty(); | ||
out.age = 20; | ||
console.log('Custom EMPTY 1', out); | ||
return out; | ||
} | ||
@InjectableFactory(CUSTOM_SERVICE_INHERIT) | ||
static getObject(@Inject(SERVICE_INHERIT_2) svc: ServiceInherit): ServiceInherit { | ||
return new ServiceInherit2(svc.db); | ||
} | ||
@InjectableFactory(CUSTOM_DATABSE) | ||
static getCustomDB(config: DbConfig<any, any>, @Inject(CUSTOM_EMPTY) empty: Empty): Database { | ||
console.log('Custom EMPTY 2', empty); | ||
const ret = new Database(); | ||
config.temp = 'any'; | ||
ret.dbConfig = config; | ||
ret.dbConfig.empty = empty; | ||
return ret; | ||
} | ||
} |
import { DependencyRegistry } from '../src/service'; | ||
import { ServiceInherit, SERVICE_INHERIT_2, CUSTOM_SERVICE_INHERIT } from './deps'; | ||
import { Suite, Test } from '@travetto/test'; | ||
import { ServiceInherit, SERVICE_INHERIT_2, CUSTOM_SERVICE_INHERIT, CUSTOM_DATABSE, Database } from './deps'; | ||
import { Suite, Test, BeforeEach } from '@travetto/test'; | ||
import * as assert from 'assert'; | ||
@@ -9,3 +9,3 @@ | ||
function doWork() { | ||
// throw new Error('ahhh'); | ||
throw new Error('ahhh'); | ||
} | ||
@@ -38,5 +38,6 @@ | ||
assert(1 === 1); | ||
assert(2 + 2 === FOUR); | ||
doWork(); | ||
assert.throws(doWork, Error); | ||
} | ||
@@ -48,6 +49,10 @@ } | ||
@BeforeEach() | ||
async each() { | ||
await DependencyRegistry.init(); | ||
} | ||
@Test('run') | ||
async run() { | ||
console.log('starting'); | ||
await DependencyRegistry.init(); | ||
assert(30 === 30); | ||
@@ -76,8 +81,29 @@ | ||
async factory() { | ||
await DependencyRegistry.init(); | ||
assert(true); | ||
const inst = await DependencyRegistry.getInstance(ServiceInherit, CUSTOM_SERVICE_INHERIT); | ||
assert(inst); | ||
assert(inst.db.dbConfig); | ||
assert.ok(!inst.db.dbConfig.temp); | ||
assert(inst.db.dbConfig.empty.age === 10); | ||
} | ||
@Test('factory with autowire after') | ||
async factory2() { | ||
assert(true); | ||
const inst = await DependencyRegistry.getInstance(Database, CUSTOM_DATABSE); | ||
assert(inst); | ||
assert(inst.altConfig === undefined); | ||
assert.ok(inst.dbConfig); | ||
assert(inst.dbConfig.temp === 'any'); | ||
assert(inst.dbConfig.empty.age === 20); | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28420
775