feat(core): use middlewares with `combinedRoutes`
MODIFY
packages/core/src/effects/effects.combiner.spec.ts
packages/core/src/effects/effects.combiner.spec.ts
-import { mapTo, tap } from 'rxjs/operators';
+import { map, mapTo, tap } from 'rxjs/operators';
import { Marbles } from '../../../util/marbles.spec-util';
import { HttpRequest, HttpResponse } from '../http.interface';
import { combineEffects, combineMiddlewareEffects, combineRoutes } from './effects.combiner';
@@ -50,12 +50,11 @@ describe('Effects combiner', () => {
const c$: Effect = request$ => request$.pipe(mapTo({ status: 203 }));
const d$: Effect = request$ => request$.pipe(mapTo({ status: 204 }));
- const group1$: GroupedEffects = { path: '/test', effects: [c$, d$] };
- const group2$: GroupedEffects = { path: '/test/foo', effects: [c$, d$] };
+ const group1$: GroupedEffects = { path: '/test', effects: [c$, d$], middlewares: [] };
const req = createMockReq('/test');
const res = createMockRes();
- const combinedEffects = combineEffects([ a$, b$, group1$, group2$ ]);
+ const combinedEffects = combineEffects([ a$, b$, group1$ ]);
const http$ = combinedEffects(res)(req);
Marbles.assertCombinedEffects(http$, [
@@ -68,6 +67,36 @@ describe('Effects combiner', () => {
]);
});
+ it('#combineEffects combines effects for grouped effects with middlewares', () => {
+ // given
+ const a$: Effect = request$ => request$.pipe(map(req => ({ status: 201, mid: req.mid })));
+ const b$: Effect = request$ => request$.pipe(map(req => ({ status: 202, mid: req.mid })));
+ const c$: Effect = request$ => request$.pipe(map(req => ({ status: 203, mid: req.mid })));
+ const d$: Effect = request$ => request$.pipe(map(req => ({ status: 204, mid: req.mid })));
+
+ const m$: Effect<HttpRequest> = request$ => request$.pipe(tap(req => req.mid = (req.mid || 0) + 1));
+
+ const group1$: GroupedEffects = { path: '/test', effects: [c$, d$], middlewares: [m$, m$] };
+
+ // when
+ const req = createMockReq('/test/foo');
+ const res = createMockRes();
+ const combinedEffects = combineEffects([ a$, b$, group1$, c$, d$ ]);
+ const http$ = combinedEffects(res)(req);
+
+ // then
+ Marbles.assertCombinedEffects(http$, [
+ '(abcdef|)', {
+ a: { status: 201, mid: undefined },
+ b: { status: 202, mid: undefined },
+ c: { status: 203, mid: 2 },
+ d: { status: 204, mid: 2 },
+ e: { status: 203, mid: 2 },
+ f: { status: 204, mid: 2 },
+ }
+ ]);
+ });
+
it('#combineMiddlewareEffects combines chained middlewares', () => {
const a$: Effect<HttpRequest> = request$ => request$.pipe(tap(req => { req.test = 1; }));
const b$: Effect<HttpRequest> = request$ => request$.pipe(tap(req => { req.test = req.test + 1; }));
@@ -85,13 +114,59 @@ describe('Effects combiner', () => {
]);
});
- it('#combineRoutes factorizes route combiner', () => {
+ it('#combineRoutes factorizes combined routes for effects only', () => {
+ // given
const a$: Effect = request$ => request$.pipe(mapTo({}));
const b$: Effect = request$ => request$.pipe(mapTo({}));
- expect(combineRoutes('/test', [a$, b$])).toEqual({
+ // when
+ const combiner = combineRoutes('/test', [a$, b$]);
+
+ // then
+ expect(combiner).toEqual({
+ path: '/test',
+ effects: [a$, b$],
+ middlewares: [],
+ });
+ });
+
+ it('#combineRoutes factorizes combined routes for effects and middlewares', () => {
+ // given
+ const a$: Effect = request$ => request$.pipe(mapTo({}));
+ const b$: Effect = request$ => request$.pipe(mapTo({}));
+
+ const m1$: Effect<HttpRequest> = request$ => request$;
+ const m2$: Effect<HttpRequest> = request$ => request$;
+
+ // when
+ const combiner = combineRoutes('/test', {
+ effects: [a$, b$],
+ middlewares: [m1$, m2$],
+ });
+
+ // then
+ expect(combiner).toEqual({
+ path: '/test',
+ effects: [a$, b$],
+ middlewares: [m1$, m2$],
+ });
+ });
+
+ it('#combineRoutes factorizes combined routes for effects and empty middlewares', () => {
+ // given
+ const a$: Effect = request$ => request$.pipe(mapTo({}));
+ const b$: Effect = request$ => request$.pipe(mapTo({}));
+
+ // when
+ const combiner = combineRoutes('/test', {
+ effects: [a$, b$],
+ });
+
+ // then
+ expect(combiner).toEqual({
path: '/test',
effects: [a$, b$],
+ middlewares: [],
});
});

MODIFY
packages/core/src/effects/effects.combiner.ts
packages/core/src/effects/effects.combiner.ts
import { concat, of } from 'rxjs';
-import { concatMap, switchMap } from 'rxjs/operators';
+import { concatMap, mergeMap, switchMap } from 'rxjs/operators';
import { HttpRequest } from '../http.interface';
import { matchPath } from '../operators';
-import { isGroup } from './effects.helpers';
+import { isGroup, isRouteCombinerConfig } from './effects.helpers';
import { EffectCombiner, MiddlewareCombiner, RouteCombiner } from './effects.interface';
export const combineEffects: EffectCombiner = effects => res => req => {
@@ -10,6 +10,7 @@ export const combineEffects: EffectCombiner = effects => res => req => {
const mappedEffects = effects.map(effect => isGroup(effect)
? req$.pipe(
matchPath(effect.path, { suffix: '/:foo*', combiner: true }),
+ mergeMap(combineMiddlewareEffects(effect.middlewares)(res)),
concatMap(combineEffects(effect.effects)(res))
)
: effect(req$, res, undefined)
@@ -26,5 +27,8 @@ export const combineMiddlewareEffects: MiddlewareCombiner = effects => res => re
return req$.pipe(...mappedEffects);
};
-export const combineRoutes: RouteCombiner = (path, effects) =>
- ({ path, effects });
+export const combineRoutes: RouteCombiner = (path, configOrEffects) => ({
+ path,
+ effects: isRouteCombinerConfig(configOrEffects) ? configOrEffects.effects : configOrEffects,
+ middlewares: isRouteCombinerConfig(configOrEffects) ? (configOrEffects.middlewares || []) : [],
+});

MODIFY
packages/core/src/effects/effects.helpers.spec.ts
packages/core/src/effects/effects.helpers.spec.ts
import { mapTo } from 'rxjs/operators';
-import { isEffect, isGroup } from './effects.helpers';
-import { Effect, GroupedEffects } from './effects.interface';
+import { isEffect, isGroup, isRouteCombinerConfig, isRouteCombinerEffects } from './effects.helpers';
+import { Effect, GroupedEffects, RouteCombinerConfig } from './effects.interface';
describe('Effects helpers', () => {
- it('#isGroup checks if parameters is GroupedEffects type', () => {
- expect(isGroup({ path: '/test', effects: [] })).toBe(true);
- expect(isGroup({ path: '/test', effects: {} } as GroupedEffects)).toBe(false);
+ test('#isGroup checks if parameters is GroupedEffects type', () => {
+ expect(isGroup({ path: '/test', effects: [] } as GroupedEffects)).toBe(true);
expect(isGroup({ path: '/test', effects: {} } as GroupedEffects)).toBe(false);
expect(isGroup({ effects: [] } as any as GroupedEffects)).toBe(false);
});
- it('#isEffect checks if parameters is GroupedEffects type', () => {
+ test('#isEffect checks if parameters is GroupedEffects type', () => {
+ // given
const effect$: Effect = request$ => request$.pipe(mapTo({}));
- const groupedEffects = { path: '/test', effects: [effect$] };
+ const groupedEffects = { path: '/test', effects: [effect$], middlewares: [] };
+
+ // when
+ const effect = isEffect(effect$);
+ const group = isEffect(groupedEffects);
+
+ // then
+ expect(effect).toBe(true);
+ expect(group).toBe(false);
+ });
+
+ test('#isRouteCombinerConfig checks if parameter is a configuration object', () => {
+ // given
+ const effects = [];
+ const combinedRoutes: RouteCombinerConfig = { middlewares: [], effects: [] };
+
+ // when
+ const combinedRoutesEffects = isRouteCombinerConfig(effects);
+ const combinedRoutesConfig = isRouteCombinerConfig(combinedRoutes);
- expect(isEffect(effect$)).toBe(true);
- expect(isEffect(groupedEffects)).toBe(false);
+ // then
+ expect(combinedRoutesConfig).toBe(true);
+ expect(combinedRoutesEffects).toBe(false);
});
+
+ test('#isRouteCombinerEffects checks if parameters is a collection of effects', () => {
+ // given
+ const effects = [];
+ const combinedRoutes: RouteCombinerConfig = { middlewares: [], effects: [] };
+
+ // when
+ const combinedRoutesEffects = isRouteCombinerEffects(effects);
+ const combinedRoutesConfig = isRouteCombinerEffects(combinedRoutes);
+
+ // then
+ expect(combinedRoutesConfig).toBe(false);
+ expect(combinedRoutesEffects).toBe(true);
+ });
+
});

MODIFY
packages/core/src/effects/effects.helpers.ts
packages/core/src/effects/effects.helpers.ts
-import { Effect, EffectResponse, GroupedEffects } from './effects.interface';
+import { Effect, EffectResponse, Effects, GroupedEffects, RouteCombinerConfig } from './effects.interface';
export const isGroup = (item: Effect | GroupedEffects): item is GroupedEffects =>
typeof item === 'object'
@@ -10,3 +10,9 @@ export const isGroup = (item: Effect | GroupedEffects): item is GroupedEffects =
export const isEffect = (item: Effect | GroupedEffects): item is Effect<EffectResponse> =>
!isGroup(item)
&& typeof item === 'function';
+
+export const isRouteCombinerConfig = (item: RouteCombinerConfig | Effects): item is RouteCombinerConfig =>
+ !Array.isArray(item);
+
+export const isRouteCombinerEffects = (item: RouteCombinerConfig | Effects): item is Effects =>
+ !isRouteCombinerConfig(item);

MODIFY
packages/core/src/effects/effects.interface.ts
packages/core/src/effects/effects.interface.ts
@@ -10,6 +10,7 @@ export interface EffectResponse {
export interface GroupedEffects {
path: string;
effects: Effects;
+ middlewares: Effect<HttpRequest>[];
}
export interface EffectCombiner {
@@ -21,7 +22,12 @@ export interface MiddlewareCombiner {
}
export interface RouteCombiner {
- (path: string, effects: Effects): GroupedEffects;
+ (path: string, config: RouteCombinerConfig | Effects): GroupedEffects;
+}
+
+export interface RouteCombinerConfig {
+ middlewares?: Effect<HttpRequest>[];
+ effects: Effects;
}
export type Effect<T = EffectResponse, U = any> = (

feat(core): use middlewares with `combinedRoutes`