- commit
- 2c42194
- parent
- db23e0e
- author
- Eric Bower
- date
- 2023-04-20 04:14:45 +0000 UTC
feat: `compose` function for middleware
4 files changed,
+85,
-12
+23,
-10
1@@ -1,10 +1,19 @@
2-import { safe } from "./fx/mod.ts";
3+import { call } from "./fx/mod.ts";
4+import type { Instruction, Result } from "./deps.ts";
5+import { Err } from "./deps.ts";
6
7-interface Middleware<Ctx> {}
8-type Next = () => void;
9-interface PipeCtx {}
10+export type MdwCtx = Record<string, any>;
11
12-export function compose<Ctx extends PipeCtx = PipeCtx>(
13+export type Next<T = unknown> = () => Generator<Instruction, Result<T>, void>;
14+export type Middleware<Ctx extends MdwCtx = MdwCtx> = (
15+ ctx: Ctx,
16+ next: Next,
17+) => any;
18+/* type MiddlewareCo<Ctx extends MdwCtx = MdwCtx> =
19+ | Middleware<Ctx>
20+ | Middleware<Ctx>[]; */
21+
22+export function compose<Ctx extends MdwCtx = MdwCtx>(
23 middleware: Middleware<Ctx>[],
24 ) {
25 if (!Array.isArray(middleware)) {
26@@ -17,20 +26,24 @@ export function compose<Ctx extends PipeCtx = PipeCtx>(
27 }
28 }
29
30- return function* composeFn(context: Ctx, next?: Next): SagaIterator<void> {
31+ return function* composeFn<T>(context: Ctx, next?: Next) {
32 // last called middleware #
33 let index = -1;
34- yield* safe(() => dispatch(0));
35
36- function* dispatch(i: number) {
37+ function* dispatch(i: number): Generator<Instruction, Result<T>, void> {
38 if (i <= index) {
39 throw new Error("next() called multiple times");
40 }
41 index = i;
42 let fn: any = middleware[i];
43 if (i === middleware.length) fn = next;
44- if (!fn) return;
45- yield* safe(() => fn(context, dispatch.bind(null, i + 1)));
46+ if (!fn) return Err(new Error("fn is falsy"));
47+ const nxt = dispatch.bind(null, i + 1);
48+ const result = yield* call(() => fn(context, nxt));
49+ return result;
50 }
51+
52+ yield* dispatch(0);
53+ return context;
54 };
55 }
M
mod.ts
+1,
-0
1@@ -2,3 +2,4 @@ export * from "./fx/mod.ts";
2 export * from "./types.ts";
3 export * from "./iter.ts";
4 export * from "./context.ts";
5+export * from "./compose.ts";
+2,
-2
1@@ -21,7 +21,7 @@ it(tests, "should call the generator function", async () => {
2 });
3
4 it(tests, "should return an Err()", async () => {
5- const err = new Error("bang!")
6+ const err = new Error("bang!");
7 function* me() {
8 throw err;
9 }
10@@ -29,7 +29,7 @@ it(tests, "should return an Err()", async () => {
11 await run(function* () {
12 const result = yield* call(me);
13 if (!result.ok) {
14- expect(result.error).toEqual(err)
15+ expect(result.error).toEqual(err);
16 }
17 });
18 });
+59,
-0
1@@ -0,0 +1,59 @@
2+import { describe, expect, it } from "../test.ts";
3+
4+import { run, sleep } from "../deps.ts";
5+import { compose } from "../compose.ts";
6+
7+const tests = describe("compose()");
8+
9+it(tests, "should compose middleware", async () => {
10+ const mdw = compose<{ one: string; three: string }>([
11+ function* (ctx, next) {
12+ ctx.one = "two";
13+ yield* next();
14+ },
15+ function* (ctx, next) {
16+ ctx.three = "four";
17+ yield* next();
18+ },
19+ ]);
20+ const actual = await run(function* () {
21+ return yield* mdw({ one: "", three: "" });
22+ });
23+
24+ const expected = {
25+ // we should see the mutation
26+ one: "two",
27+ three: "four",
28+ };
29+ expect(actual).toEqual(expected);
30+});
31+
32+it(tests, "order of execution", async () => {
33+ const mdw = compose<{ actual: string }>([
34+ function* (ctx, next) {
35+ ctx.actual += "a";
36+ yield* next();
37+ ctx.actual += "g";
38+ },
39+ function* (ctx, next) {
40+ yield* sleep(10);
41+ ctx.actual += "b";
42+ yield* next();
43+ yield* sleep(10);
44+ ctx.actual += "f";
45+ },
46+ function* (ctx, next) {
47+ ctx.actual += "c";
48+ yield* next();
49+ ctx.actual += "d";
50+ yield* sleep(30);
51+ ctx.actual += "e";
52+ },
53+ ]);
54+
55+ const actual = await run(function* () {
56+ return yield* mdw({ actual: "" });
57+ });
58+ const expected = { actual: "abcdefg" };
59+ expect(actual).toEqual(expected);
60+});