feat(core): r.handle
MODIFY
packages/core/src/http/effects/http.effects.interface.ts
packages/core/src/http/effects/http.effects.interface.ts
@@ -3,6 +3,7 @@ import { Event } from '../../event/event.interface';
import { Effect } from '../../effects/effects.interface';
export interface HttpEffectResponse<T = any> {
+ request: HttpRequest;
status?: HttpStatus;
headers?: HttpHeaders;
body?: T;

MODIFY
packages/core/src/http/error/http.error.model.ts
packages/core/src/http/error/http.error.model.ts
-import { HttpStatus } from '../http.interface';
+import { HttpStatus, HttpRequest } from '../http.interface';
import { ExtendableError } from '../../+internal/utils';
export enum HttpErrorType {
HTTP_ERROR = 'HttpError',
+ HTTP_EFFECT_ERROR = 'HttpEffectError',
}
export class HttpError extends ExtendableError {
@@ -16,5 +17,14 @@ export class HttpError extends ExtendableError {
}
}
+export class HttpEffectError extends ExtendableError {
+ constructor(
+ public readonly error: Error,
+ public readonly request: HttpRequest,
+ ) {
+ super(HttpErrorType.HTTP_EFFECT_ERROR, 'An error occured in HttpEffect');
+ }
+}
+
export const isHttpError = (error: Error): error is HttpError =>
error.name === HttpErrorType.HTTP_ERROR;

MODIFY
packages/core/src/http/router/http.router.ixbuilder.ts
packages/core/src/http/router/http.router.ixbuilder.ts
+import { Observable, OperatorFunction, throwError, of } from 'rxjs';
+import { mergeMap, map, catchError } from 'rxjs/operators';
+import { pipeFromArray } from 'rxjs/internal/util/pipe';
import { combineMiddlewares } from '../../effects/effects.combiner';
import { IxBuilder, ichainCurry } from '../../+internal/fp/IxBuilder';
-import { HttpMiddlewareEffect, HttpEffect } from '../effects/http.effects.interface';
-import { HttpMethod } from '../http.interface';
+import { HttpMiddlewareEffect, HttpEffect, HttpEffectResponse } from '../effects/http.effects.interface';
+import { HttpMethod, HttpRequest } from '../http.interface';
+import { HttpEffectError } from '../error/http.error.model';
import { RouteEffect, RouteMeta } from './http.router.interface';
type Ready = 'Ready';
@@ -126,4 +130,83 @@ function pipe(
)(route).run();
}
-export const r = { matchPath, matchType, use, useEffect, applyMeta, pipe };
+type EffectFn = (input$: Observable<HttpRequest>) => Observable<HttpEffectResponse>;
+
+export function handle(): EffectFn;
+export function handle<A>(
+ op1: OperatorFunction<HttpRequest, A>,
+): EffectFn;
+export function handle<A, B>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+): EffectFn;
+export function handle<A, B, C>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+): EffectFn;
+export function handle<A, B, C, D>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+ op4: OperatorFunction<C, D>,
+): EffectFn;
+export function handle<A, B, C, D, E>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+ op4: OperatorFunction<C, D>,
+ op5: OperatorFunction<D, E>,
+): EffectFn;
+export function handle<A, B, C, D, E, F>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+ op4: OperatorFunction<C, D>,
+ op5: OperatorFunction<D, E>,
+ op6: OperatorFunction<E, F>,
+): EffectFn;
+export function handle<A, B, C, D, E, F, G>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+ op4: OperatorFunction<C, D>,
+ op5: OperatorFunction<D, E>,
+ op6: OperatorFunction<E, F>,
+ op7: OperatorFunction<F, G>,
+): EffectFn;
+export function handle<A, B, C, D, E, F, G, H>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+ op4: OperatorFunction<C, D>,
+ op5: OperatorFunction<D, E>,
+ op6: OperatorFunction<E, F>,
+ op7: OperatorFunction<F, G>,
+ op8: OperatorFunction<G, H>,
+): EffectFn;
+export function handle<A, B, C, D, E, F, G, H, I>(
+ op1: OperatorFunction<HttpRequest, A>,
+ op2: OperatorFunction<A, B>,
+ op3: OperatorFunction<B, C>,
+ op4: OperatorFunction<C, D>,
+ op5: OperatorFunction<D, E>,
+ op6: OperatorFunction<E, F>,
+ op7: OperatorFunction<F, G>,
+ op8: OperatorFunction<G, H>,
+ op9: OperatorFunction<H, I>,
+ ...operations: OperatorFunction<any, any>[]
+): EffectFn;
+
+export function handle(...operators: OperatorFunction<any, any>[]): EffectFn {
+ return input$ =>
+ input$.pipe(
+ mergeMap(request => pipeFromArray([
+ ...operators,
+ map(res => ({ ...res, request })),
+ catchError(error => throwError(new HttpEffectError(error, request))),
+ ])(of(request)) as Observable<HttpEffectResponse>),
+ );
+}
+
+export const r = { matchPath, matchType, use, useEffect, applyMeta, pipe, handle };

MODIFY
packages/core/src/http/server/specs/http.server.listener.spec.ts
packages/core/src/http/server/specs/http.server.listener.spec.ts
import * as http from 'http';
import { throwError } from 'rxjs';
-import { mapTo, mergeMap } from 'rxjs/operators';
+import { mapTo, mergeMap, map } from 'rxjs/operators';
import { r } from '../../router/http.router.ixbuilder';
import { httpListener } from '../http.server.listener';
import { HttpMiddlewareEffect, HttpEffectResponse } from '../../effects/http.effects.interface';
@@ -32,7 +32,12 @@ describe('Http listener', () => {
const effect$ = r.pipe(
r.matchPath('/'),
r.matchType('GET'),
- r.useEffect(req$ => req$.pipe(mapTo(effectResponse))),
+ r.useEffect(req$ => {
+
+ return r.handle(
+ mapTo(effectResponse),
+ )(req$);
+ }),
);
const middleware$: HttpMiddlewareEffect = req$ => req$;
@@ -46,7 +51,7 @@ describe('Http listener', () => {
})(context)(req, res);
// then
- expect(sender).toHaveBeenCalledWith(effectResponse);
+ expect(sender).toHaveBeenCalledWith({ ...effectResponse, request: { ...req, response: res }});
done();
});
@@ -54,13 +59,26 @@ describe('Http listener', () => {
// given
const req = { url: '/', method: 'GET' } as http.IncomingMessage;
const res = {} as http.OutgoingMessage;
- const effectResponse = { body: 'test' } as HttpEffectResponse;
const sender = jest.fn();
const effect$ = r.pipe(
r.matchPath('/'),
r.matchType('GET'),
- r.useEffect(req$ => req$.pipe(mapTo(effectResponse))),
+ r.useEffect(req$ => {
+
+ return r.handle(
+ mapTo(1),
+ map(x => x + 1),
+ map(x => x + 1),
+ map(x => x + 1),
+ map(x => x + 1),
+ map(x => x + 1),
+ map(x => x + 1),
+ map(x => ({
+ body: x + 1,
+ })),
+ )(req$);
+ }),
);
// when
@@ -72,7 +90,7 @@ describe('Http listener', () => {
})(context)(req, res);
// then
- expect(sender).toHaveBeenCalledWith(effectResponse);
+ expect(sender).toHaveBeenCalledWith({ body: 8, request: { ...req, response: res }});
done();
});

feat(core): r.handle