Skip to content

Commit

Permalink
Merge pull request #27 from jeffijoe/feature-infer-name
Browse files Browse the repository at this point in the history
Infer name from function/class (#26)
  • Loading branch information
jeffijoe committed Jul 12, 2017
2 parents 1fcd8aa + 11d51bb commit fa4a2c9
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 57 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Awilix Changelog

## 2.6

* **[NEW]**: infer function name for `registerClass`/`registerFunction` ([#26](https://github.com/jeffijoe/awilix/issues/26))
* **[FIXED]**: Corrected some TypeScript typings related to `registerClass` and `registerFunction`.

## 2.5.0

* **[NEW]**: Implemented per-module locals injection ([#24](https://github.com/jeffijoe/awilix/issues/24)).
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Extremely powerful **Inversion of Control** (IoC) container for Node with depend
+ [`container.cache`](#containercache)
+ [`container.options`](#containeroptions)
+ [`container.resolve()`](#containerresolve)
+ [`container.register()`](#containerregister)
+ [`container.registerValue()`](#containerregistervalue)
+ [`container.registerFunction()`](#containerregisterfunction)
+ [`container.registerClass()`](#containerregisterclass)
Expand Down Expand Up @@ -639,6 +640,11 @@ container.cradle.leet === 1337

### `container.register()`

**Signatures**

* `register(name: string, registration: Registration): AwilixContainer`
* `register(nameAndRegistrationPair: NameAndRegistrationPair): AwilixContainer`

Registers modules with the container. This function is used by the `registerValue`, `registerFunction` and `registerClass` functions.

Awilix needs to know how to resolve the modules, so let's pull out the
Expand Down Expand Up @@ -685,6 +691,11 @@ container.register('context', asClass(SessionContext).scoped())

### `container.registerValue()`

**Signatures**

* `registerValue(name: string, value: any): AwilixContainer`
* `registerValue(nameAndValuePairs: RegisterNameAndValuePair): AwilixContainer `

Registers a constant value in the container. Can be anything.

```js
Expand All @@ -705,6 +716,13 @@ container

### `container.registerFunction()`

**Signatures**

* `registerFunction(fn: Function, opts?: RegistrationOptions): AwilixContainer` (infers the name using `fn.name`)
* `registerFunction(name: string, fn: Function, opts?: RegistrationOptions): AwilixContainer`
* `registerFunction(name: string, funcAndOptionsPair: [Function, RegistrationOptions]): AwilixContainer`
* `registerFunction(nameAndFunctionPair: RegisterNameAndFunctionPair): AwilixContainer`

Registers a standard function to be called whenever being resolved. The factory function can return anything it wants, and whatever it returns is what is passed to dependents.

By default all registrations are `TRANSIENT`, meaning resolutions will **not** be cached. This is configurable on a per-registration level.
Expand Down Expand Up @@ -751,6 +769,14 @@ setTimeout(() => {

### `container.registerClass()`

**Signatures**

* `registerClass(ctor: Constructor<T>, opts?: RegistrationOptions): AwilixContainer` (infers the name using `ctor.name`)

* `registerClass<T>(name: string, ctor: Constructor<T>, opts?: RegistrationOptions): AwilixContainer`
* `registerClass<T>(name: string, ctorAndOptionsPair: [Constructor<T>, RegistrationOptions]): AwilixContainer`
* `registerClass(nameAndClassPair: RegisterNameAndClassPair): AwilixContainer`

Same as `registerFunction`, except it will use `new`.

By default all registrations are `TRANSIENT`, meaning resolutions will **not** be cached. This is configurable on a per-registration level.
Expand Down
36 changes: 25 additions & 11 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,31 @@ export declare interface AwilixContainer {
createScope(): AwilixContainer
loadModules(globPatterns: string[] | Array<[string, RegistrationOptions]>, options?: LoadModulesOptions): ModuleDescriptor[]
registrations: Registration[]
register(name: string, registration: Registration): AwilixContainer
register(nameAndRegistrationPair: NameAndRegistrationPair): AwilixContainer
registerClass<T>(name: string, instance: Object): AwilixContainer
registerClass<T>(nameAndClassPair: RegisterNameAndClassPair<T>): AwilixContainer
registerFunction(name: string, fn: Function): AwilixContainer
registerFunction(nameAndFunctionPair: RegisterNameAndFunctionPair): AwilixContainer
registerValue(name: string, value: any): AwilixContainer
registerValue(nameAndValuePairs: RegisterNameAndValuePair): AwilixContainer
register(name: string, registration: Registration): this
register(nameAndRegistrationPair: NameAndRegistrationPair): this
registerClass<T>(ctor: Constructor<T>, opts?: RegistrationOptions): this
registerClass<T>(name: string, ctor: Constructor<T>, opts?: RegistrationOptions): this
registerClass<T>(name: string, ctorAndOptionsPair: [Constructor<T>, RegistrationOptions]): this
registerClass(nameAndClassPair: RegisterNameAndClassPair): this
registerFunction(fn: Function, opts?: RegistrationOptions): this
registerFunction(name: string, fn: Function, opts?: RegistrationOptions): this
registerFunction(name: string, funcAndOptionsPair: [Function, RegistrationOptions]): this
registerFunction(nameAndFunctionPair: RegisterNameAndFunctionPair): this
registerValue(name: string, value: any): this
registerValue(nameAndValuePairs: RegisterNameAndValuePair): this
resolve<T>(name: string): T
}

/**
* A class constructor. For example:
*
* class MyClass {}
*
* container.registerClass('myClass', MyClass)
* ^^^^^^^
*/
export type Constructor<T> = { new (...args: any[]): T }

/**
* This is a special error thrown when Awilix is unable to resolve all dependencies
* (due to missing or cyclic dependencies).
Expand All @@ -40,7 +54,7 @@ export declare class AwilixResolutionError extends Error {
* @return {Registration}
*/
export declare function asClass<T>(
type: new (...args: any[]) => T,
type: Constructor<T>,
options?: RegistrationOptions
): FluidRegistration

Expand Down Expand Up @@ -193,8 +207,8 @@ export type BuiltInNameFormatters = 'camelCase'
* Register a class.
* @interface RegisterNameAndClassPair
*/
export interface RegisterNameAndClassPair<T> {
[key: string]: [T, RegistrationOptions] | T
export interface RegisterNameAndClassPair {
[key: string]: [Constructor<any>, RegistrationOptions] | Constructor<any>
}

/**
Expand Down
9 changes: 9 additions & 0 deletions lib/AwilixError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const ExtendableError = require('./ExtendableError')

/**
* Base error for all Awilix-specific errors.
*/
class AwilixError extends ExtendableError {
}

module.exports = AwilixError
4 changes: 2 additions & 2 deletions lib/AwilixNotAFunctionError.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const ExtendableError = require('./ExtendableError')
const AwilixError = require('./AwilixError')

/**
* Error thrown to indicate Awilix was expecting a function (or a class)
*/
class AwilixNotAFunctionError extends ExtendableError {
class AwilixNotAFunctionError extends AwilixError {
/**
* Constructor, takes the function name, expected and given
* type to produce an error.
Expand Down
4 changes: 2 additions & 2 deletions lib/AwilixResolutionError.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const EOL = require('os').EOL
const ExtendableError = require('./ExtendableError')
const AwilixError = require('./AwilixError')

/**
* Creates an error message so the developer knows what dependencies are missing.
Expand Down Expand Up @@ -31,7 +31,7 @@ const createErrorMessage = (name, resolutionStack, message) => {
/**
* A nice error class so we can do an instanceOf check.
*/
class AwilixResolutionError extends ExtendableError {
class AwilixResolutionError extends AwilixError {
/**
* Constructor, takes the registered modules and unresolved tokens
* to create a message.
Expand Down
10 changes: 10 additions & 0 deletions lib/createContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const listModules = require('./listModules')
const { asClass, asFunction, asValue } = require('./registrations')
const ResolutionMode = require('./ResolutionMode')
const AwilixResolutionError = require('./AwilixResolutionError')
const AwilixError = require('./AwilixError')
const nameValueToObject = require('./nameValueToObject')
const Lifetime = require('./Lifetime')
const last = require('./last')
Expand Down Expand Up @@ -184,6 +185,15 @@ module.exports = function createContainer (options, __parentContainer) {
* When set to true, treat the value as-is, don't check if its an value-opts-array.
*/
const makeRegister = (fn, verbatimValue) => function registerShortcut (name, value, opts) {
// Supports infering the class/function name.
if (typeof name === 'function' && !verbatimValue) {
if (!name.name) {
throw new AwilixError(`Attempted to use shorthand register function, but the specified function has no name.`)
}
opts = value
value = name
name = name.name
}
// This ensures that we can support name+value style and object style.
const obj = nameValueToObject(name, value)
for (const key in obj) {
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,27 @@
"homepage": "https://github.com/jeffijoe/awilix#readme",
"devDependencies": {
"@types/chai": "^4.0.1",
"@types/node": "^8.0.5",
"chai": "^4.0.2",
"@types/node": "^8.0.10",
"chai": "^4.1.0",
"coveralls": "^2.13.1",
"eslint": "^4.1.1",
"eslint": "^4.2.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.6.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-watch": "^3.1.2",
"istanbul": "^0.4.5",
"mocha": "^3.4.2",
"sinon": "^2.3.6",
"sinon": "^2.3.7",
"sinon-chai": "^2.11.0",
"typescript": "^2.4.1"
},
"dependencies": {
"camel-case": "^3.0.0",
"glob": "^7.1.2",
"is-class": "0.0.4",
"is-plain-object": "^2.0.3",
"is-plain-object": "^2.0.4",
"is-string": "^1.0.4"
}
}
12 changes: 11 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
AwilixContainer,
ContainerOptions,
listModules,
Lifetime
Lifetime,
ResolutionMode
} from '../index'

/**
Expand Down Expand Up @@ -59,11 +60,20 @@ testFunc2("")

container.register('_testValue', asValue(VALUE))

container.registerClass<TestClass>(TestClass)
container.registerClass<TestClass>('__testClass', TestClass)
container.registerClass('__testClass', TestClass)
container.registerClass('__testClass', TestClass, { lifetime: Lifetime.SCOPED })
container.registerClass('__testClass', [TestClass, { lifetime: Lifetime.SCOPED }])
container.registerClass({
__testClass: TestClass,
__testClass2: [TestClass, {}]
})

container.registerFunction(testFunction, { lifetime: Lifetime.SCOPED })
container.registerFunction('__testClass', testFunction)
container.registerFunction('__testClass', testFunction, { resolutionMode: ResolutionMode.CLASSIC })
container.registerFunction('__testClass', [testFunction, { resolutionMode: ResolutionMode.CLASSIC }])
container.registerFunction({
__testFunction: testFunction,
__testFunction2: [testFunction, { injector: (c) => ({ hehe: 42 })}]
Expand Down
19 changes: 19 additions & 0 deletions test/lib/createContainer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@ describe('createContainer', function () {
container.registrations.objWithLifetime.lifetime.should.equal(Lifetime.SCOPED)
})

it('can infer the registration name in registerFunction and registerClass', function () {
container.registerFunction(function plain () { return 1 }, { lifetime: Lifetime.SCOPED })

const arrow = () => 2
container.registerFunction(arrow)

container.registerClass(Repo)

expect(container.resolve('plain')).to.equal(1)
expect(container.resolve('arrow')).to.equal(2)
expect(container.resolve('Repo')).to.be.an.instanceOf(Repo)

expect(container.registrations.plain.lifetime).to.equal(Lifetime.SCOPED)
})

it('fails when it cannot read the name of the function', function () {
expect(() => container.registerFunction(() => 42)).to.throw(/name/)
})

it('supports registerValue', function () {
container.registerValue('nameValue', 1)
container.registerValue({
Expand Down
Loading

0 comments on commit fa4a2c9

Please sign in to comment.