feat(core): derived context - binding + lookup
MODIFY
packages/core/src/context/context.helper.ts
packages/core/src/context/context.helper.ts
@@ -5,8 +5,6 @@ import { registerAll, BoundDependency, createContext, bindTo, resolve, Context }
/**
* Constructs and resolves a new or derived context based on provided dependencies
- * @param context (optional) empty or derived context
- * @param dependencies list of bound dependencies to register in the context
* @since v3.4.0
*/
export const constructContext = (context?: Context) => (...dependencies: BoundDependency<any>[]) =>
@@ -21,7 +19,6 @@ export const constructContext = (context?: Context) => (...dependencies: BoundDe
/**
* Constructs and resolves a new context based on provided dependencies
- * @param dependencies list of bound dependencies to register in the context
* @since v3.2.0
*/
export const contextFactory = constructContext();

MODIFY
packages/core/src/context/context.ts
packages/core/src/context/context.ts
@@ -2,11 +2,12 @@ import * as R from 'fp-ts/lib/Reader';
import * as M from 'fp-ts/lib/Map';
import * as O from 'fp-ts/lib/Option';
import * as E from 'fp-ts/lib/Eq';
+import * as T from 'fp-ts/lib/Task';
import { pipe } from 'fp-ts/lib/pipeable';
import { contramap, ordString, Ord } from 'fp-ts/lib/Ord';
-import { ContextToken } from './context.token.factory';
+import { ContextToken, createContextToken } from './context.token.factory';
-export const ordContextToken: Ord<ContextToken<any>> = contramap((t: ContextToken) => t._id)(ordString);
+export const ordContextToken: Ord<ContextToken<unknown>> = contramap((t: ContextToken<unknown>) => t._id)(ordString);
export const setoidContextToken: E.Eq<ContextToken> = { equals: ordContextToken.equals };
export interface Context extends Map<ContextToken, ContextDependency | any> {}
@@ -37,6 +38,8 @@ export interface BoundDependency<T, U extends ContextDependency = ContextDepende
dependency: U;
}
+export const DerivedContextToken = createContextToken<Context>('DerivedContext');
+
const isEagerDependency = (x: any): x is ContextEagerReader => {
return x.eval && x.tag === ContextReaderTag.EAGER_READER;
};
@@ -59,45 +62,71 @@ export const registerAll = (boundDependencies: BoundDependency<any, any>[]) => (
context,
);
+/**
+ * Resolves eager dependencies within the context
+ * @since v2.0.0
+ */
export const resolve = async (context: Context): Promise<Context> => {
+ const resolveEagerDependency = ({ token, dependency }: BoundDependency<unknown, ContextEagerReader>): T.Task<Context> => pipe(
+ () => pipe(context, dependency.eval, d => Promise.resolve(d)),
+ T.chain(resolvedDependency => T.fromIO(() => context.set(token, resolvedDependency))));
+
for (const [token, dependency] of context) {
- context.set(
- token,
- isEagerDependency(dependency)
- ? await Promise.resolve(dependency.eval(context))
- : dependency,
- );
+ if (!isEagerDependency(dependency)) continue;
+ await resolveEagerDependency({ token, dependency })();
}
return context;
}
-export const lookup = (context: Context) => <T>(token: ContextToken<T>): O.Option<T> => pipe(
+/**
+ * Lookup the dependency for a token in a `Context`
+ * @since v2.0.0
+ */
+export const lookup = (context: Context) => <T>(token: ContextToken<T>): O.Option<T> =>
+ pipe(
M.lookup(ordContextToken)(token, context),
O.map(dependency => {
- if (!dependency.eval) {
- return dependency;
- }
+ if (!dependency.eval) return dependency;
const boostrapedDependency = isLazyDependency(dependency)
? dependency.eval()(context)
: dependency.eval(context);
context.set(token, boostrapedDependency);
+
return boostrapedDependency;
}),
+ O.fold(
+ () => pipe(
+ M.lookup(ordContextToken)(DerivedContextToken, context),
+ O.chain((derivedContext: Context) => lookup(derivedContext)(token))),
+ O.some),
);
+/**
+ * Binds context token to a lazy dependency.
+ * @since v3.0.0
+ */
export const bindLazilyTo =
<T>(token: ContextToken<T>) =>
<U extends ContextReader>(dependency: U): BoundDependency<T, ContextLazyReader> =>
({ token, dependency: { eval: () => dependency, tag: ContextReaderTag.LAZY_READER } });
+/**
+ * Binds context token to a eager dependency.
+ * @since v3.0.0
+ */
export const bindEagerlyTo =
<T>(token: ContextToken<T>) =>
<U extends ContextReader>(dependency: U): BoundDependency<T, ContextEagerReader> =>
({ token, dependency: { eval: dependency, tag: ContextReaderTag.EAGER_READER } });
+/**
+ * An alias to `bindLazilyTo`.
+ * Binds context token to a lazy dependency.
+ * @since v3.0.0
+ */
export const bindTo = bindLazilyTo;
export const reader = pipe(

MODIFY
packages/core/src/context/specs/context.spec.ts
packages/core/src/context/specs/context.spec.ts
@@ -16,8 +16,10 @@ import {
ContextReader,
ContextReaderTag,
resolve,
+ DerivedContextToken,
} from '../context';
import { createContextToken } from '../context.token.factory';
+import { contextFactory } from '../context.helper';
describe('#bindTo', () => {
test('binds lazy reader to token', () => {
@@ -231,4 +233,35 @@ describe('#reader', () => {
expect(lookup(context)(token)).toEqual(O.some('test'));
expect(spy).toHaveBeenCalledTimes(1);
});
+
+ test('asks context for a derived context dependency', async () => {
+ // given
+ const unknownToken = createContextToken<unknown>();
+
+ // given - derived context
+ const derivedDependency_1 = () => 'test_1';
+ const derivedDependency_2 = () => 'test_2';
+ const derivedDependencyToken_1 = createContextToken();
+ const derivedDependencyToken_2 = createContextToken();
+
+ const derivedContext = await contextFactory(
+ bindTo(derivedDependencyToken_1)(derivedDependency_1),
+ bindTo(derivedDependencyToken_2)(derivedDependency_2),
+ );
+
+ // given - new context
+ const dependency_3 = () => 'test_3';
+ const dependencyToken_3 = createContextToken();
+
+ const context = await contextFactory(
+ bindEagerlyTo(DerivedContextToken)(() => derivedContext),
+ bindTo(dependencyToken_3)(dependency_3),
+ );
+
+ // when, then
+ expect(lookup(context)(derivedDependencyToken_1)).toEqual(O.some('test_1'));
+ expect(lookup(context)(derivedDependencyToken_2)).toEqual(O.some('test_2'));
+ expect(lookup(context)(dependencyToken_3)).toEqual(O.some('test_3'));
+ expect(lookup(context)(unknownToken)).toEqual(O.none);
+ });
});

MODIFY
packages/messaging/src/eventbus/messaging.eventBus.reader.ts
packages/messaging/src/eventbus/messaging.eventBus.reader.ts
import { pipe } from 'fp-ts/lib/pipeable';
-import { deleteAt } from 'fp-ts/lib/Map';
import * as R from 'fp-ts/lib/Reader';
-import { Context, bindTo, setoidContextToken, createContextToken, constructContext } from '@marblejs/core';
+import { Context, bindTo, createContextToken, contextFactory, bindEagerlyTo, DerivedContextToken, logContext, LoggerTag } from '@marblejs/core';
import { TransportLayerToken } from '../server/messaging.server.tokens';
import { Transport, TransportLayerConnection } from '../transport/transport.interface';
import { provideTransportLayer } from '../transport/transport.provider';
@@ -19,17 +18,19 @@ export const EventBusClientToken = createContextToken<MessagingClient>('EventBus
export const eventBus = (config: EventBusConfig) => pipe(
R.ask<Context>(),
- R.map(async rootContext => {
+ R.map(async derivedContext => {
const { listener } = config;
- const derivedContext = deleteAt(setoidContextToken)(EventBusToken)(rootContext);
const transportLayer = provideTransportLayer(Transport.LOCAL);
const transportLayerConnection = await transportLayer.connect();
- const context = await constructContext(derivedContext)(
+ const context = await contextFactory(
+ bindEagerlyTo(DerivedContextToken)(() => derivedContext),
bindTo(TransportLayerToken)(() => transportLayer),
bindTo(EventTimerStoreToken)(EventTimerStore),
);
+ logContext(LoggerTag.EVENT_BUS)(context);
+
listener(context)(transportLayerConnection);
return transportLayerConnection;

feat(core): derived context - binding + lookup