repos / starfx

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

commit
09eba9f
parent
cfcc818
author
Eric Bower
date
2023-12-12 02:44:39 +0000 UTC
docs: readme
1 files changed,  +87, -15
M README.md
+87, -15
  1@@ -32,13 +32,13 @@ Read my introductory blog post:
  2 # example: thunks are tasks for business logic
  3 
  4 Thunks are the foundational central processing units. They have access to all
  5-the actions being dispatched from thew view -- or other thunks. They also wield
  6-the full power of structured concurrency.
  7+the actions being dispatched from the view as well as your global state. They
  8+also wield the full power of structured concurrency.
  9 
 10 Every thunk that is created requires a unique id -- user provided string. This
 11 provides us with a handful of benefits:
 12 
 13-- User hand-labels each task created (intention)
 14+- User hand-labels each thunk created
 15 - Better tracability (via labels)
 16 - Easier to debug async and side-effects in general (via labels)
 17 - Build abstractions off naming conventions (e.g. creating routers
 18@@ -51,25 +51,22 @@ thunks and endpoints.
 19 Each run of a thunk gets its own `ctx` object which provides a substrate to
 20 communicate between middleware.
 21 
 22-You didn't know you wanted express middleware for the front-end, but let me get
 23-you excited, it's powerful.
 24-
 25 ```ts
 26-import { createThunks, mdw, call } from "starfx";
 27+import { call, createThunks, mdw } from "starfx";
 28 
 29 const thunks = createThunks();
 30 // catch errors from task and logs them with extra info
 31 thunks.use(mdw.err);
 32 // where all the thunks get called in the middleware stack
 33 thunks.use(thunks.routes());
 34-thunks.use(function*(ctx, next) {
 35+thunks.use(function* (ctx, next) {
 36   console.log("last mdw in the stack");
 37   yield* next();
 38 });
 39 
 40 // create a thunk
 41 const log = thunks.create("log", function* (ctx, next) {
 42-  const resp = yield* call(fetch("https"//bower.sh"));;
 43+  const resp = yield* call(fetch("https://bower.sh"));
 44   const data = yield* call(resp.json());
 45   console.log("before calling next middleware");
 46   yield* next();
 47@@ -97,7 +94,7 @@ api.use(api.routes());
 48 // calls `window.fetch` with `ctx.request` and sets to `ctx.response`
 49 api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));
 50 
 51-// automatically cache results in datastore
 52+// automatically cache Response json in datastore as-is
 53 export const fetchUsers = api.get("/users", api.cache());
 54 
 55 store.dispatch(fetchUsers());
 56@@ -170,7 +167,7 @@ function App() {
 57     <div>
 58       {users.map((u) => <div key={u.id}>{u.name}</div>)}
 59       <div>
 60-        <button onClick={api.trigger()}>fetch users</button>
 61+        <button onClick={() => api.trigger()}>fetch users</button>
 62         {api.isLoading ? <div>Loading ...</div> : null}
 63       </div>
 64     </div>
 65@@ -184,9 +181,9 @@ Please see [examples repo](https://github.com/neurosnap/starfx-examples).
 66 
 67 # when to use this library?
 68 
 69-This primary target for this library are single-page apps (SPAs). This is for an
 70-app that might live inside an object store or as a simple web server that serves
 71-files and that's it.
 72+The primary target for this library are single-page apps (SPAs). This is for an
 73+app that might be hosted inside an object store (like s3) or with a simple web
 74+server that serves files and that's it.
 75 
 76 Is your app highly interactive, requiring it to persist data across pages? This
 77 is the sweet spot for `starfx`.
 78@@ -199,7 +196,7 @@ cannot claim it'll work well.
 79 
 80 # what is structured concurrency?
 81 
 82-This is a board term so I'll make this specific to how `starfx` works.
 83+This is a broadd term so I'll make this specific to how `starfx` works.
 84 
 85 Under-the-hood, thunks and endpoints are registered under the root task. Every
 86 thunk and endpoint has their own supervisor that manages them. As a result, what
 87@@ -223,6 +220,81 @@ In review:
 88 - Supervisor tasks are designed to monitor task health (and automatically
 89   recover in many cases)
 90 
 91+# what is a supervisor task?
 92+
 93+A supervisor task is a way to monitor children tasks and probably most
 94+importantly, manage their health. By structuring your side-effects and business
 95+logic around supervisor tasks, we gain very interesting coding paradigms that
 96+result is easier to read and manage code.
 97+
 98+The most basic version of a supervisor is simply an infinite loop that calls a
 99+child task:
100+
101+```ts
102+function* supervisor() {
103+  while (true) {
104+    try {
105+      yield* call(someTask);
106+    } catch (err) {
107+      console.error(err);
108+    }
109+  }
110+}
111+```
112+
113+Here we call some task that should always be in a running and in a healthy
114+state. If it raises an exception, we log it and try to run the task again.
115+
116+All thunks and endpoints do is listen for particular actions being emitted onto
117+the `ActionContext`, which is just an event emitter.
118+
119+```ts
120+import { parallel, run, takeEvery } from "starfx";
121+
122+function* watchFetch() {
123+  const task = yield* takeEvery("FETCH_USERS", function* (action) {
124+    console.log(action);
125+  });
126+  yield* task;
127+}
128+
129+function* send() {
130+  yield* put({ type: "FETCH_USERS" });
131+  yield* put({ type: "FETCH_USERS" });
132+  yield* put({ type: "FETCH_USERS" });
133+}
134+
135+await run(
136+  parallel([watchFetch, send]),
137+);
138+```
139+
140+Here we create a supervisor function using a helper `takeEvery` to call a
141+function for every `FETCH_USERS` event emitted.
142+
143+However, this means that we are going to make the same request 3 times, we need
144+a throttle or debounce to prevent this issue.
145+
146+```ts
147+import { parallel, run, takeLeading } from "starfx";
148+
149+function* watchFetch() {
150+  const task = yield* takeLeading("FETCH_USERS", function* (action) {
151+    console.log(action);
152+  });
153+  yield* task;
154+}
155+```
156+
157+That's better, now only one task can be alive at one time.
158+
159+Both thunks and endpoints support overriding the default `takeEvery` supervisor
160+for either our officially supported supervisors `takeLatest` and `takeLeading`
161+or even a custom one!
162+
163+That's it. We are just leveraging the same tiny API that we are already using in
164+`starfx`.
165+
166 # talk
167 
168 I recently gave a talk about deliminited continuations where I also discuss this