Skip to content

Commit

Permalink
Infer registration name from function/class + updated typings
Browse files Browse the repository at this point in the history
Closes #26
  • Loading branch information
jeffijoe committed Jul 12, 2017
1 parent fe7e3a5 commit 39cd5a1
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 16 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
23 changes: 23 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,12 @@ container

### `container.registerFunction()`

**Signatures**

* `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 +768,12 @@ setTimeout(() => {

### `container.registerClass()`

**Signatures**

* `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
34 changes: 23 additions & 11 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,29 @@ 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>(name: string, ctor: Constructor<T>, opts?: RegistrationOptions): this
registerClass<T>(name: string, ctorAndOptionsPair: [Constructor<T>, RegistrationOptions]): this
registerClass(nameAndClassPair: RegisterNameAndClassPair): 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 +52,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 +205,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
10 changes: 9 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,18 @@ testFunc2("")

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

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('__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
15 changes: 15 additions & 0 deletions test/lib/createContainer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ 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 })
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)
})

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

0 comments on commit 39cd5a1

Please sign in to comment.