repos / starfx

supercharged async flow control library.
git clone https://github.com/neurosnap/starfx.git

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
M mod.ts
M compose.ts
+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";
M test/call.test.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 });
A test/compose.test.ts
+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+});