feat(core): userContext hook function
MODIFY
packages/@integration/src/messaging/client.ts
packages/@integration/src/messaging/client.ts
@@ -15,7 +15,7 @@ import { merge, of, forkJoin, EMPTY } from 'rxjs';
import { tap, map, mergeMap } from 'rxjs/operators';
import { requestValidator$, t } from '@marblejs/middleware-io';
-const ClientToken = createContextToken<MessagingClient>();
+const ClientToken = createContextToken<MessagingClient>('MessagingClient');
const client = messagingClient({
transport: Transport.AMQP,

MODIFY
packages/@integration/src/websockets/http.server.ts
packages/@integration/src/websockets/http.server.ts
@@ -7,23 +7,27 @@ import {
ServerEvent,
httpListener,
HttpServerEffect,
+ useContext,
} from '@marblejs/core';
import { mapToServer, MarbleWebSocketServer } from '@marblejs/websockets';
import { logger$ } from '@marblejs/middleware-logger';
import { merge } from 'rxjs';
-import { tap, map, mapTo } from 'rxjs/operators';
+import { tap, map } from 'rxjs/operators';
import { websocketsServer } from './websockets.server';
-export const WsServerToken = createContextToken<MarbleWebSocketServer>();
+export const WsServerToken = createContextToken<MarbleWebSocketServer>('MarbleWebSocketServer');
const root$ = r.pipe(
r.matchPath('/'),
r.matchType('GET'),
- r.useEffect((req$, _, { ask }) => req$.pipe(
- mapTo(ask(WsServerToken)),
- tap(ws => ws.map(server => server.sendBroadcastResponse({ type: 'ROOT', payload: 'Hello' }))),
+ r.useEffect((req$, _, { ask }) => {
+ const wsServer = useContext(WsServerToken)(ask);
+
+ return req$.pipe(
+ tap(() => wsServer.sendBroadcastResponse({ type: 'ROOT', payload: 'Hello' })),
map(body => ({ body })),
- )));
+ );
+ }));
const upgrade$: HttpServerEffect = (event$, _, { ask }) =>
event$.pipe(

ADD
packages/core/src/context/context.hook.ts
+import { ContextToken } from './context.token.factory';
+import { ContextProvider } from './context.factory';
+import { ContextError } from '../error/error.model';
+import { coreErrorFactory, CoreErrorOptions } from '../error/error.factory';
+
+const coreErrorOptions: CoreErrorOptions = { contextMethod: 'useContext', offset: 2 };
+
+export const useContext = <T>(token: ContextToken<T>) => (ask: ContextProvider): T =>
+ ask(token).getOrElseL(() => {
+ const message = `Cannot resolve "${token.name || token._id}" context token.`;
+ const detail = `You've probably forgot to register the bound token in the app context.`;
+ const error = new ContextError(`${message} ${detail}`);
+ const coreError = coreErrorFactory(error.message, coreErrorOptions)
+
+ console.error(coreError.stack);
+
+ throw error;
+ });

MODIFY
packages/core/src/error/error.factory.ts
packages/core/src/error/error.factory.ts
@@ -4,6 +4,7 @@ import { CoreError } from './error.model';
export type CoreErrorOptions = {
contextMethod: string;
contextPackage?: string;
+ offset?: number;
};
export const stringifyStackTrace = (stackTrace: NodeJS.CallSite[]) =>
@@ -12,7 +13,13 @@ export const stringifyStackTrace = (stackTrace: NodeJS.CallSite[]) =>
.join('\n ');
export const coreErrorStackTraceFactory = (opts: CoreErrorOptions) => (message: string, stack: NodeJS.CallSite[]) => {
- const [method, file, ...restStack] = stack;
+ const methodPointer = opts.offset || 0;
+ const method = stack[methodPointer];
+
+ const filePointer = methodPointer + 1;
+ const file = stack[filePointer];
+
+ const restStack = stack.slice(filePointer, stack.length);
const [line, col] = [file.getLineNumber() || 0, file.getColumnNumber() || 0];
const packageName = opts.contextPackage || '@marblejs/core';
const methodName = opts.contextMethod + ' : ' + (method.getMethodName() || '-');

MODIFY
packages/core/src/error/error.model.ts
packages/core/src/error/error.model.ts
@@ -4,6 +4,7 @@ import { ExtendableError } from '../+internal/utils';
export enum ErrorType {
CORE_ERROR = 'CoreError',
+ CONTEXT_ERROR = 'ContextError',
HTTP_ERROR = 'HttpError',
EVENT_ERROR = 'EventError',
}
@@ -33,6 +34,14 @@ export class CoreError extends ExtendableError {
}
}
+export class ContextError extends ExtendableError {
+ constructor(
+ public readonly message: string,
+ ) {
+ super(ErrorType.CONTEXT_ERROR, message);
+ }
+}
+
export class EventError extends ExtendableError {
constructor(
public readonly event: Event,

MODIFY
packages/core/src/index.ts
packages/core/src/index.ts
@@ -14,6 +14,7 @@ export * from './http.interface';
export * from './event/event.factory';
export * from './event/event.interface';
export * from './context/context.factory';
+export * from './context/context.hook';
export * from './context/context.token.factory';
export * from './server/server.event';
export * from './server/server.interface';

feat(core): userContext hook function