fix(core): context - unset redundant token if present in DerivedContext
MODIFY
packages/core/src/context/context.helper.ts
packages/core/src/context/context.helper.ts
+import * as T from 'fp-ts/lib/Task';
+import * as O from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
+import { constant } from 'fp-ts/lib/function';
import { logger, LoggerToken, mockLogger } from '../logger';
import { isTestEnv } from '../+internal/utils';
-import { registerAll, BoundDependency, createContext, bindTo, resolve, Context } from './context';
+import { registerAll, BoundDependency, createContext, bindTo, resolve, Context, lookup, DerivedContextToken, unregister } from './context';
+import { ContextToken } from './context.token.factory';
+
+/**
+ * `INTERNAL` - unregisters redundant token if available in DerivedContext
+ * @since v3.4.0
+ */
+const unregisterRedundantToken = (token: ContextToken) => (context: Context): Context =>
+ pipe(
+ lookup(context)(DerivedContextToken),
+ O.chain(() => lookup(context)(token)),
+ O.fold(
+ constant(context),
+ () => unregister(token)(context)),
+ );
/**
* Constructs and resolves a new or derived context based on provided dependencies
* @since v3.4.0
*/
-export const constructContext = (context?: Context) => (...dependencies: BoundDependency<any>[]) =>
+export const constructContext = (context?: Context) => (...dependencies: BoundDependency<any>[]): Promise<Context> =>
pipe(
context ?? createContext(),
registerAll([
bindTo(LoggerToken)(isTestEnv() ? mockLogger : logger),
...dependencies,
]),
- resolve,
- );
+ context => () => resolve(context),
+ T.map(unregisterRedundantToken(LoggerToken)),
+ )();
/**
* Constructs and resolves a new context based on provided dependencies

MODIFY
packages/core/src/context/context.ts
packages/core/src/context/context.ts
@@ -62,6 +62,13 @@ export const registerAll = (boundDependencies: BoundDependency<any, any>[]) => (
context,
);
+/**
+ * Unregisters given token from the context
+ * @since v3.4.0
+ */
+export const unregister = (token: ContextToken) => (context: Context): Context =>
+ M.deleteAt(setoidContextToken)(token)(context);
+
/**
* Resolves eager dependencies within the context
* @since v2.0.0

MODIFY
packages/core/src/context/specs/context.helper.spec.ts
packages/core/src/context/specs/context.helper.spec.ts
@@ -3,7 +3,7 @@ import { size } from 'fp-ts/lib/Map';
import { createContextToken } from '../context.token.factory';
import { createReader } from '../context.reader.factory';
import { contextFactory, constructContext } from '../context.helper';
-import { bindTo, lookup } from '../context';
+import { bindTo, lookup, DerivedContextToken, bindEagerlyTo } from '../context';
import { LoggerToken } from '../../logger';
describe('#contextFactory', () => {
@@ -51,4 +51,28 @@ describe('#constructContext', () => {
expect(lookup(context)(token1)).toEqual(O.some('test_1'));
expect(lookup(context)(token2)).toEqual(O.some('test_2'));
});
+
+ test('doesn\'t register redundant LoggerToken if derived context has it already bound ', async () => {
+ // given
+ const customLogger = jest.fn(() => 'custom_logger');
+ const derivedContext = await contextFactory(
+ bindEagerlyTo(LoggerToken)(() => customLogger()),
+ );
+
+ // when
+ const context = await constructContext(derivedContext)(
+ bindEagerlyTo(DerivedContextToken)(() => derivedContext),
+ );
+
+ // then
+ expect(
+ lookup(derivedContext)(LoggerToken)
+ ).toEqual(
+ lookup(context)(LoggerToken)
+ );
+
+ expect(size(context)).toEqual(1); // Context[DerivedContextToken]
+ expect(size(derivedContext)).toEqual(1); // Context[Loggertoken]
+ expect(customLogger).toHaveBeenCalledTimes(1);
+ });
});

MODIFY
packages/core/src/context/specs/context.spec.ts
packages/core/src/context/specs/context.spec.ts
@@ -17,6 +17,7 @@ import {
ContextReaderTag,
resolve,
DerivedContextToken,
+ unregister,
} from '../context';
import { createContextToken } from '../context.token.factory';
import { contextFactory } from '../context.helper';
@@ -97,6 +98,28 @@ describe('#registerAll', () => {
});
});
+describe('#unregister', () => {
+ test('unregisters specified token', () => {
+ // given
+ const token = createContextToken<string>();
+ const token_to_delete = createContextToken<string>();
+ const dependency = pipe(R.ask<Context>(), R.map(_ => 'test'));
+ const dependency_to_delete = pipe(R.ask<Context>(), R.map(_ => 'test_to_delete'));
+
+ const contextBefore = registerAll([
+ bindTo(token)(dependency),
+ bindTo(token_to_delete)(dependency_to_delete),
+ ])(createContext());
+
+ // when
+ const contextAfter = unregister(token_to_delete)(contextBefore);
+
+ // then
+ expect(size(contextAfter)).toEqual(1);
+ expect(lookup(contextAfter)(token_to_delete)).toEqual(O.none);
+ });
+});
+
describe('#resolve', () => {
test('resolves asynchronous eager reader', async () => {
// given

fix(core): context - unset redundant token if present in DerivedContext