Tiny TypeScript functional dependency injection, based on AsyncLocalStorage. Supports Node.js, Deno
MIT License
Tiny TypeScript functional dependency injection, based on AsyncLocalStorage. Supports Node.js, Deno
Firstly, need init DI container for each life cycle of your backend application (each HTTP request/response, handle MQ message, ...).
Example of middleware for typical Koa application, where on each HTTP request will be created particular DI container:
app.use(async (ctx, next) => {
await diInit(async () => return await next());
});
Further, simply use ts-fp-di API "as is" in code, it will consider particular DI scope.
const fn = di(() => 1);
fn() // call `fn` function inside DI scope, it's return 1
const fn = di(() => 1);
diSet(fn, () => 2); // Override `fn` function inside DI scope. Useful for unit tests.
fn() // returns 2, because it rewriten.
const fn = () => 1;
diSet(fn, () => 2); // Override `fn` function inside DI scope. Useful for unit tests.
diDep(fn)() // returns 2, because it rewriten.
diSet('user', {login: 'xxx'}); // Useful to setup current user in DI scope
diDep<User>('user') // Extract current user from anywhere
// setup Redux like state with reducer in DI scope
const inc = dis((sum, n: number) => sum + n, 0);
inc(1); // mutate state
inc(); // 1, "inc" without argument returns current state
const num = div<number>(); // alias to dis((sum, n: number) => n, void 0)
num(5); // mutate state
num(); // 5
// setup Redux like state with reducer in global scope (pass true as isGlobal flag)
const inc = dis((sum, n: number) => sum + n, 0, true);
inc(1); // mutate state
inc(); // 1, "inc" without argument returns current state
clearGlobalState(); // you can clear global state (useful in tests)
inc() // 0, "inc" returns default value now
let i = 0;
const fn = diOnce(() => { // <- setup Singleton function for DI scope
i += 1;
return i;
});
fn(); // 1
fn(); // also 1, because fn is singleton for DI scope
const cache = dic<number>()
cache(1)
cache() // 1
const fn = diOnce((n: number) => { // <- setup Singleton function for DI scope
return n + 1;
});
diOnceSet(fn, -1); // Override diOnceSet. For example, use this in your unit tests
fn(4) // -1 instead 5, because -1 set on prev line
diExists() // false
diInit(() => {
diExists() // true
});
const ctx = diContext()
diInit(() => {
// ctx will be considered here
}, ctx)
diInit(() => {
// same ctx will be considered here too
}, ctx)
const inc = dis((resp: number, n: number) => resp + n, 0)
const scope = diScope({ inc }, () => {
// optional "constructor" function
// some `diSet` can be used here
})
scope.inc(5) // this mutation occur only inside this scope
scope.inc() // 5
const cacheNumber = dic<number>()
const calcString = diMap(n => `string - ${n}`, cacheNumber)
cacheNumber(5)
calcString() // "string - 5"
const onceNumber = diOnce((n: number) => {
return n;
});
const calcString = diMap(n => `string - ${n}`, onceNumber)
onceNumber(5)
calcString() // "string - 5"
const inc = dis((sum, n: number) => sum + n, 0);
const calcString = diMap(s => `string - ${s}`, inc)
inc(1);
inc(4);
calcString() // "string - 5"
calcString.raw(1) // direct call of function, useful for unit tests
const cacheNumber = dic<number>()
let i = 0
const onceString = diMapOnce(n => ((i = i + 1), `string - ${n}`), cacheNumber)
cacheNumber(5)
onceString() // "string - 5"
onceString() // i = 1, because onceString is singleton for DI scope
const numberState = dic<number>()
numberState(5)
const stringState = diMap(n => `string - ${n}`, numberState)
const seState = div<string>() // will be populated via side effect
const se = dise(
async (n, s) => `${n} ${s}`, // side effect async function
seState, // this state will be populated via async response
numberState, // optional arg1 for effect function
stringState) // optional arg2 for effect function
await se()
seState() // "5 string - 5"
// dise function can be overriden for unit tests
diseSet(se, async (n, s) => n + parseInt(s.match(/\d/)))
await se()
seState() // 10
Internal AsyncLocalStorage instance exposed as als
property. You can implement your own plugin around it.
‼️ If you use ts-fp-di with plugins on your project, please consider, that you have only one ts-fp-di node_module
For example you can freeze as singleton you dependency via package.json overrides
{
"overrides": {
"ts-fp-di": "^x.x.x"
}
}