Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): derived context #288

Merged
merged 1 commit into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions packages/core/src/context/context.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>[]) =>
Expand All @@ -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();
75 changes: 52 additions & 23 deletions packages/core/src/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {}
Expand Down Expand Up @@ -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;
};
Expand All @@ -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)),
JozefFlakus marked this conversation as resolved.
Show resolved Hide resolved
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;
JozefFlakus marked this conversation as resolved.
Show resolved Hide resolved
await resolveEagerDependency({ token, dependency })();
}

return context;
}

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;
}

const boostrapedDependency = isLazyDependency(dependency)
? dependency.eval()(context)
: dependency.eval(context);

context.set(token, boostrapedDependency);
return boostrapedDependency;
}),
);
/**
* 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;

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(
Expand Down
33 changes: 33 additions & 0 deletions packages/core/src/context/specs/context.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
11 changes: 6 additions & 5 deletions packages/messaging/src/eventbus/messaging.eventBus.reader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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';
Expand All @@ -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;
Expand Down