repos / starfx

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

commit
a360879
parent
3f5d0aa
author
Eric Bower
date
2023-12-19 01:46:16 +0000 UTC
docs: readme
1 files changed,  +92, -0
M README.md
+92, -0
  1@@ -110,8 +110,22 @@ api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));
  2 // automatically cache Response json in datastore as-is
  3 export const fetchUsers = api.get("/users", api.cache());
  4 
  5+// create a POST HTTP request
  6+export const updateUser = api.post<{ id: string; name: string }>(
  7+  "/users/:id",
  8+  function* (ctx, next) {
  9+    ctx.request = ctx.req({
 10+      body: JSON.stringify({ name: ctx.payload.name }),
 11+    });
 12+    yield* next();
 13+  },
 14+);
 15+
 16 store.dispatch(fetchUsers());
 17 // now accessible with useCache(fetchUsers)
 18+
 19+// lets update a user record
 20+store.dispatch(updateUser({ id: "1", name: "bobby" }));
 21 ```
 22 
 23 # example: an immutable store that acts like a reactive, in-memory database
 24@@ -346,6 +360,84 @@ or a user-defined supervisor.
 25 That's it. We are just leveraging the same tiny API that we are already using in
 26 `starfx`.
 27 
 28+# example: test that doesn't need an http interceptor
 29+
 30+Need to write tests? Users to libraries like `msw` or `nock`? Well you don't
 31+need them with `starfx`. If the `mdw.fetch()` middleware detects `ctx.response`
 32+is already filled then it skips making the request. Let's take the update user
 33+endpoint example and provide stubbed data for our tests.
 34+
 35+```ts
 36+import { fireEvent, render, screen } from "@testing-library/react";
 37+import { useDispatch, useSelector } from "starfx/react";
 38+import { db } from "./schema.ts";
 39+import { updateUser } from "./user.ts";
 40+
 41+function UserSettingsPage() {
 42+  const id = "1";
 43+  const dispatch = useDispatch();
 44+  const user = useSelector((state) => db.users.selectById(state, { id }));
 45+
 46+  return (
 47+    <div>
 48+      <div>Name: {user.name}</div>
 49+      <button onClick={() => dispatch(updateUser({ id, name: "bobby" }))}>
 50+        Update User
 51+      </button>
 52+    </div>
 53+  );
 54+}
 55+
 56+describe("UserSettingsPage", () => {
 57+  it("should update the user", async () => {
 58+    // just for this test -- inject a new middleware into the endpoint stack
 59+    updateUser.use(function* (ctx, next) {
 60+      ctx.response = new Response(
 61+        JSON.stringify({ id: ctx.payload.id, name: ctx.payload.name }),
 62+      );
 63+      yield* next();
 64+    });
 65+
 66+    render(<UserSettingsPage />);
 67+
 68+    const btn = await screen.findByRole("button", { name: /Update User/ });
 69+    fireEvent.click(btn);
 70+
 71+    await screen.findByText(/Name: bobby/);
 72+  });
 73+});
 74+```
 75+
 76+That's it. No need for http interceptors and the core functionality works
 77+exactly the same, we just skip making the fetch request for our tests.
 78+
 79+What if we don't have an API endpoint yet and want to stub the data? We use the
 80+same concept but inline inside the `updateUser` endpoint:
 81+
 82+```ts
 83+export const updateUser = api.post<{ id: string; name: string }>(
 84+  "/users/:id",
 85+  [
 86+    function* (ctx, next) {
 87+      ctx.request = ctx.req({
 88+        body: JSON.stringify({ name: ctx.payload.name }),
 89+      });
 90+      yield* next();
 91+    },
 92+    function* (ctx, next) {
 93+      ctx.response = new Response(
 94+        JSON.stringify({ id: ctx.payload.id, name: ctx.payload.name }),
 95+      );
 96+      yield* next();
 97+    },
 98+  ],
 99+);
100+```
101+
102+Wow! Our stubbed data is now colocated next to our actual endpoint we are trying
103+to mock! Once we have a real API we want to hit, we can just remove that second
104+middleware function and everything will work exactly the same.
105+
106 # talk
107 
108 I recently gave a talk about delimited continuations where I also discuss this