README.md
# ![Jpex](https://jpex-js.github.io/dist/jpex.svg)
## Easy Dependency Injection
[![Build Status](https://travis-ci.org/jpex-js/jpex.svg?branch=master)](https://travis-ci.org/jackmellis/jpex)
[![npm version](https://badge.fury.io/js/jpex.svg)](https://badge.fury.io/js/jpex)
[![Code Climate](https://codeclimate.com/github/jackmellis/jpex/badges/gpa.svg)](https://codeclimate.com/github/jackmellis/jpex)
[![Test Coverage](https://codeclimate.com/github/jackmellis/jpex/badges/coverage.svg)](https://codeclimate.com/github/jackmellis/jpex/coverage)
Jpex is an Inversion of Control framework. Register dependencies on a container, then resolve them anywhere in your application. The real magic of jpex is its ability to infer dependencies using the magic of babel and typescript...
## Contents
- [Getting Started](#getting-started)
- [Registering Dependencies](#registering-dependencies)
- [Consuming Dependencies](#consuming-dependencies)
- [API](#api)
- [jpex](#jpex)
- [constant](#jpexconstant)
- [factory](#jpexfactory)
- [lifecycle](#lifecycle)
- [precedence](#precedence)
- [bindToInstance](#bindtoinstance)
- [alias](#alias)
- [service](#jpexservice)
- [alias](#jpexalias)
- [resolve](#jpexresolve)
- [optional](#optional)
- [with](#with)
- [resolveWith](#jpexresolvewith)
- [encase](#jpexencase)
- [extend](#jpexextend)
- [inherit](#inherit)
- [lifecycle](#lifecycle-1)
- [precedence](#precedence-1)
- [optional](#optional-1)
- [nodeModules](#nodemodules)
- [globals](#globals)
- [raw](#jpexraw)
- [clearCache](#jpexclearcache)
- [infer](#jpexinfer)
- [Types](#types)
- [Jpex](#jpex)
- [NodeModule](#nodemodule)
- [Global](#global)
- [caveats](#caveats)
- [react](#react)
- [Vanilla JS mode](#vanilla-js-mode)
## Getting Started
### Install
```
npm install jpex
```
### Plugin
Jpex uses babel to infer type interfaces at build time. You can do this with one of several methods:
[@jpex-js/babel-plugin](https://github.com/jpex-js/babel-plugin)
[@jpex-js/rollup-plugin](https://github.com/jpex-js/rollup-plugin)
[@jpex-js/webpack-plugin](https://github.com/jpex-js/webpack-loader)
Jpex comes bundled with the `@jpex-js/babel-plugin` so you can easily get started with a `.babelrc` like this:
```js
// .bablerc
{
presets: [ '@babel/preset-typescript' ],
plugins: [ 'jpex/babel-plugin' ]
}
```
### Usage
```ts
import jpex from 'jpex';
import { Foo, Bah } from './types';
jpex.factory<Foo>((bah: Bah) => bah.baz);
const foo = jpex.resolve<Foo>();
```
---
## Registering Dependencies
Services and factories are small modules or functions that provide a common piece of functionality.
### factories
```ts
type MyFactory = {};
jpex.factory<MyFactory>(() => {
return {};
});
```
### services
```ts
class MyService = {
method: (): any {
// ...
}
};
jpex.service(MyService);
```
### constants
```ts
type MyConstant = string;
jpex.constant<MyConstant>('foo');
```
---
## Consuming Dependencies
### resolve
You can then resolve a dependency anywhere in your app:
```ts
const value = jpex.resolve<MyFactory>();
```
### dependent factories
A factory can request another dependency and jpex will resolve it on the fly:
```ts
jpex.constant<MyConstant>('foo');
jpex.factory<MyFactory>((myConstant: MyConstant) => {
return `my constant is ${myConstant}`;
});
jpex.resolve<MyFactory>(); // "my constant is foo"
```
### encase
Or you can _encase_ a regular function so that dependencies are injected into it when called:
```ts
const fn = jpex.encase((value: MyFactory) => (arg1, arg2) => {
return value + arg1 + arg2;
});
fn(1, 2);
```
---
## API
### jpex
#### jpex.constant
```ts
<T>(obj: T): void
```
Registers a constant value.
#### jpex.factory
```ts
<T>(fn: (...deps: any[] => T), opts?: object): void
```
Registers a factory function against the given type. Jpex works out the types of `deps` and injects them at resolution time, then returns the resulting value `T`.
```ts
type GetStuff = () => Promise<string>;
jpex.factory<GetStuff>((window: Window) => () => window.fetch('/stuff));
```
> By default jpex will automatically resolve global types like Window or Document. In a node environment it will also be able to resolve node_modules.
The following options can be provided for both factories and services:
##### lifecycle
```ts
'application' | 'class' | 'instance' | 'none';
```
Determines how long the factory is cached for once resolved.
- `application` is resolved forever across all containers
- `class` is resolved for the current jpex container, if you `.extend()` the new container will resolve it again
- `instance` if you request the same dependency multiple times in the same `resolve` call, this will use the same value, but the next time you call `resolve` it will start again
- `none` never caches anything
The default lifecycle is `class`
##### precedence
```ts
'active' | 'passive';
```
Determines the behavior when the same factory is registered multiple times.
- `active` overwrites the existing factory
- `passive` prefers the existing factory
Defaults to `active`
##### bindToInstance
```ts
boolean;
```
Specifically for services, automatically binds all of the dependencies to the service instance.
##### alias
```ts
string | string[]
```
Creates aliases for the factory. This is essentially just shorthand for writing `jpex.factory(...); jpex.alias(...);`
#### jpex.service
```ts
<T>(class: ClassWithConstructor, opts?: object): void
```
Registers a service. A service is like a factory but instantiates a class instead.
```ts
class Foo {
constructor(window: Window) {
// ...
}
}
jpex.service(Foo);
```
If a class `implements` an interface, you can actually use it to resolve the class:
```ts
interface IFoo {}
class Foo implements IFoo {}
jpex.service(Foo);
const foo = jpex.resolve<IFoo>();
```
#### jpex.alias
```ts
<T>(alias: string): void
```
Creates an alias to another factory
#### jpex.resolve
```ts
<T>(opts?: object): T
```
Locates and resolves the desired factory.
```ts
const foo = jpex.resolve<Foo>();
```
The following options can be provided for both `resolve` and `resolveWith`:
##### optional
```ts
boolean;
```
When `true` if the dependency cannot be found or resolved, it will just return `undefined` rather than throwing an error.
#### jpex.resolveWith
```ts
<T, ...Rest[]>(values: Rest, opts?: object): T
```
Resolves a factory while substituting dependencies for the given values
```ts
const foo = jpex.resolveWith<Foo, Bah, Baz>(['bah', 'baz']);
```
#### jpex.encase
```ts
(...deps: any[]): (...args: any[]) => any
```
Wraps a function and injects values into it, it then returns the inner function for use.
```ts
const getStuff = jpex.encase((http: Http) => (thing: string) => {
return http(`api/app/${thing}`);
});
await getStuff('my-thing');
```
To help with testing, the returned function also has an `encased` property containng the outer function
```ts
getStuff.encased(fakeHttp)('my-thing');
```
#### jpex.extend
```ts
(config?: object): Jpex
```
creates a new container, using the current one as a base.
This is useful for creating isolated contexts or not poluting the global container.
The default behavior is to pass down all config options and factories to the new container.
##### inherit
`boolean`
Whether or not to inherit config and factories from its parent
##### lifecycle
`'application' | 'class' | 'instance' | 'none'`
The default lifecycle for factories. `class` by default
##### precedence
`'active' | 'passive'`
The default precedence for factories. `active` by default
##### optional
`boolean`
Whether factories should be optional by default
##### nodeModules
`boolean`
When trying to resolve a dependency, should it attempt to import the it from node modules?
##### globals
`boolean`
When trying to resolve a dependency, should it check for it on the global object?
#### jpex.raw
```ts
<T>() => (...deps: any[]) => T;
```
Returns the raw factory function, useful for testing.
#### jpex.clearCache
```ts
() => void
<T>() => void
```
Clears the cache of resolved factories. If you provide a type, that specific factory will be cleared, otherwise it will clear all factories.
#### jpex.infer
```ts
<T>() => string;
```
Under the hood jpex converts types into strings for runtime resolution. If you want to get that calculated string for whatever reason, you can use `jpex.infer`
### Types
#### Jpex
This is the type definition for the jpex container
#### NodeModule
This is a special type that lets you automatically inject a node module with type inference.
For example:
```ts
import jpex, { NodeModule } from 'jpex';
// this will resolve to the fs module without you having to explicitly register it as a dependency
const fs = jpex.resolve<NodeModule<'fs'>>();
```
The default return type will be `any` but you can specify one explicitly with the second type parameter:
```ts
import type fstype from 'fs';
import jpex, { NodeModule } from 'jpex';
const fs = jpex.resolve<NodeModule<'fs', typeof fstype>>();
```
#### Global
This is another special type that lets you automatically inject a global property with type inference.
For built-in types you can do this without any helpers:
```ts
import jpex from 'jpex';
const navigator = jpex.resolve<Navigator>();
```
But for custom globals, or properties that don't have built-in types, you can use the `Global` type:
```ts
import jpex, { Global } from 'jpex';
const analytics = jpex.resolve<Global<'ga', Function>>();
```
## caveats
There are a few caveats to be aware of:
- Only named types/interfaces are supported so you can't do `jpex.factory<{}>()`
- There is not yet a concept of extending types, so if you do `interface Bah extends Foo {}` you can't then try to resolve `Foo` and expect to be given `Bah`, they are treated as 2 separate things
- The check for a jpex instance is based on the variable name, so you can't do `const jpex2 = jpex; jpex2.constant<Foo>(foo);` without explicitly adding `jpex2` to the plugin config
- Similiarly you can't do `const { factory } = jpex`
## react
Jpex is a really good fit with React as it offers a good way to inject impure effects into pure components. There is a `react-jpex` library that exposes a few hooks.
```tsx
import React from 'react';
import { useResolve } from 'react-jpex';
import { SaveData } from '../types';
const MyComponent = (props) => {
const saveData = useResolve<SaveData>();
const onSubmit = () => saveData(props.values);
return (
<div>
<MyForm />
<button onClick={onSubmit}>Submit</button>
</div>
);
};
```
And this pattern also makes it really easy to isolate a component from its side effects when writing tests:
```tsx
import { Provider } from 'react-jpex';
// create a stub for the SaveData dependency
const saveData = stub();
render(
<Provider
inherit={false}
// register our stub dependency on an isolated container
onMount={(jpex) => jpex.constant<SaveData>(saveData)}
>
{/* when we render MyComponent, it will be given our stubbed dependency */}
<MyComponent />
</Provider>,
);
// trigger the compnent's onClick
doOnClick();
expect(saveData.called).to.be.true;
```
## Vanilla JS mode
Perhaps you hate typescript, or babel, or both. Or perhaps you don't have the luxury of a build pipeline in your application. That's fine because jpex supports vanilla js as well, you just have to explicitly state your dependencies up front:
```ts
const { jpex } = require('jpex');
jpex.constant('foo', 'foo');
jpex.factory('bah', ['foo'], (foo) => foo + 'bah');
const value = jpex.resolve('bah');
```
Jpex uses language features supported by the latest browsers, but if you need to support IE11 et al. you can import from 'jpex/dist/es5` (or create an alias in your build process)