repos / starfx

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

commit
db23e0e
parent
a525a64
author
Eric Bower
date
2023-04-20 01:42:59 +0000 UTC
refactor: `call` returns `Result` now
5 files changed,  +119, -54
A compose.ts
+36, -0
 1@@ -0,0 +1,36 @@
 2+import { safe } from "./fx/mod.ts";
 3+
 4+interface Middleware<Ctx> {}
 5+type Next = () => void;
 6+interface PipeCtx {}
 7+
 8+export function compose<Ctx extends PipeCtx = PipeCtx>(
 9+  middleware: Middleware<Ctx>[],
10+) {
11+  if (!Array.isArray(middleware)) {
12+    throw new TypeError("Middleware stack must be an array!");
13+  }
14+
15+  for (const fn of middleware) {
16+    if (typeof fn !== "function") {
17+      throw new TypeError("Middleware must be composed of functions!");
18+    }
19+  }
20+
21+  return function* composeFn(context: Ctx, next?: Next): SagaIterator<void> {
22+    // last called middleware #
23+    let index = -1;
24+    yield* safe(() => dispatch(0));
25+
26+    function* dispatch(i: number) {
27+      if (i <= index) {
28+        throw new Error("next() called multiple times");
29+      }
30+      index = i;
31+      let fn: any = middleware[i];
32+      if (i === middleware.length) fn = next;
33+      if (!fn) return;
34+      yield* safe(() => fn(context, dispatch.bind(null, i + 1)));
35+    }
36+  };
37+}
M fx/call.ts
+14, -15
 1@@ -24,28 +24,16 @@ export function* toOperation<T>(opFn: OpFn<T>): Operation<T> {
 2   return result;
 3 }
 4 
 5-export function call<T>(op: OpFn<T>): Operation<T> {
 6+export function unsafeCall<T>(op: OpFn<T>): Operation<T> {
 7   return action(function* (resolve) {
 8     const result = yield* toOperation(op);
 9     resolve(result);
10   });
11 }
12 
13-export function* go<T>(op: OpFn<T>): Operation<Task<Result<T>>> {
14-  return yield* spawn(function* () {
15-    try {
16-      return Ok(yield* call(op));
17-    } catch (error) {
18-      const { input } = yield* ErrContext;
19-      yield* input.send(error);
20-      return Err(error);
21-    }
22-  });
23-}
24-
25-export function* safe<T>(opFn: OpFn<T>): Operation<Result<T>> {
26+export function* call<T>(opFn: OpFn<T>): Operation<Result<T>> {
27   try {
28-    const value = yield* call(opFn);
29+    const value = yield* unsafeCall(opFn);
30     return Ok(value);
31   } catch (error) {
32     const { input } = yield* ErrContext;
33@@ -53,3 +41,14 @@ export function* safe<T>(opFn: OpFn<T>): Operation<Result<T>> {
34     return Err(error);
35   }
36 }
37+
38+export function* go<T>(op: OpFn<T>): Operation<Task<Result<T>>> {
39+  return yield* spawn(function* () {
40+    const result = yield* call(op);
41+    if (!result.ok) {
42+      const { input } = yield* ErrContext;
43+      yield* input.send(result.error);
44+    }
45+    return result;
46+  });
47+}
M fx/parallel.ts
+2, -2
 1@@ -1,7 +1,7 @@
 2 import type { Channel, Operation, Result } from "../deps.ts";
 3 import type { Computation, OpFn } from "../types.ts";
 4 import { createChannel, resource, spawn } from "../deps.ts";
 5-import { safe } from "./call.ts";
 6+import { call } from "./call.ts";
 7 
 8 interface ParallelRet<T> extends Computation<Result<T>[]> {
 9   sequence: Channel<Result<T>, void>;
10@@ -19,7 +19,7 @@ export function parallel<T>(operations: OpFn<T>[]) {
11       for (const op of operations) {
12         tasks.push(
13           yield* spawn(function* () {
14-            const result = yield* safe(op);
15+            const result = yield* call(op);
16             yield* immediate.input.send(result);
17             return result;
18           }),
M fx/watch.ts
+2, -2
 1@@ -1,12 +1,12 @@
 2 import type { OpFn } from "../types.ts";
 3 
 4-import { safe } from "./call.ts";
 5+import { call } from "./call.ts";
 6 import { parallel } from "./parallel.ts";
 7 
 8 export function supervise<T>(op: OpFn<T>) {
 9   return function* () {
10     while (true) {
11-      yield* safe(op);
12+      yield* call(op);
13     }
14   };
15 }
M test/call.test.ts
+65, -35
  1@@ -3,51 +3,81 @@ import { describe, expect, it } from "../test.ts";
  2 import { run } from "../deps.ts";
  3 import { call } from "../mod.ts";
  4 
  5-describe("call()", () => {
  6-  it("should call the generator function", async () => {
  7-    function* me() {
  8-      return "valid";
  9-    }
 10+const tests = describe("call()");
 11 
 12-    await run(function* () {
 13-      const result = yield* call(me);
 14-      expect(result).toBe("valid");
 15-    });
 16-  });
 17+it(tests, "should call the generator function", async () => {
 18+  function* me() {
 19+    return "valid";
 20+  }
 21 
 22-  it("should call a normal function with no params", async () => {
 23-    function me() {
 24-      return "valid";
 25+  await run(function* () {
 26+    const result = yield* call(me);
 27+    if (!result.ok) {
 28+      expect(true).toBe(false);
 29+      return;
 30     }
 31+    expect(result.value).toBe("valid");
 32+  });
 33+});
 34 
 35-    await run(function* () {
 36-      const result = yield* call(me);
 37-      expect(result).toBe("valid");
 38-    });
 39+it(tests, "should return an Err()", async () => {
 40+  const err = new Error("bang!")
 41+  function* me() {
 42+    throw err;
 43+  }
 44+
 45+  await run(function* () {
 46+    const result = yield* call(me);
 47+    if (!result.ok) {
 48+      expect(result.error).toEqual(err)
 49+    }
 50   });
 51+});
 52 
 53-  it("should call a normal function with params", async () => {
 54-    function me(v: string) {
 55-      return "valid " + v;
 56+it(tests, "should call a normal function with no params", async () => {
 57+  function me() {
 58+    return "valid";
 59+  }
 60+
 61+  await run(function* () {
 62+    const result = yield* call(me);
 63+    if (!result.ok) {
 64+      expect(true).toBe(false);
 65+      return;
 66     }
 67+    expect(result.value).toBe("valid");
 68+  });
 69+});
 70 
 71-    await run(function* () {
 72-      const result = yield* call(() => me("fn"));
 73-      expect(result).toBe("valid fn");
 74-    });
 75+it(tests, "should call a normal function with params", async () => {
 76+  function me(v: string) {
 77+    return "valid " + v;
 78+  }
 79+
 80+  await run(function* () {
 81+    const result = yield* call(() => me("fn"));
 82+    if (!result.ok) {
 83+      expect(true).toBe(false);
 84+      return;
 85+    }
 86+    expect(result.value).toBe("valid fn");
 87   });
 88+});
 89 
 90-  it("should call a promise", async () => {
 91-    const me = () =>
 92-      new Promise((resolve) => {
 93-        setTimeout(() => {
 94-          resolve("valid");
 95-        }, 10);
 96-      });
 97-
 98-    await run(function* () {
 99-      const result = yield* call(me);
100-      expect(result).toBe("valid");
101+it(tests, "should call a promise", async () => {
102+  const me = () =>
103+    new Promise((resolve) => {
104+      setTimeout(() => {
105+        resolve("valid");
106+      }, 10);
107     });
108+
109+  await run(function* () {
110+    const result = yield* call(me);
111+    if (!result.ok) {
112+      expect(true).toBe(false);
113+      return;
114+    }
115+    expect(result.value).toBe("valid");
116   });
117 });