- commit
- 5b6cce2
- parent
- 2a61af0
- author
- Eric Bower
- date
- 2024-08-16 19:32:28 +0000 UTC
docs: more content
7 files changed,
+90,
-20
+4,
-0
1@@ -11,5 +11,9 @@ of business logic. This could be as simple as making a single API endpoint and
2 caching the results or as complex as making multiple dependent API calls and
3 combinatory logic.
4
5+Not only do have a centralized place for handling complex business logic,
6+fetching API data, and updating our FE global state, but we also have a robust
7+middleware system similar to `express` or `koa`!
8+
9 In the following sections we will discuss how to create controllers and the
10 different use cases for them inside `starfx`.
+4,
-0
1@@ -41,6 +41,10 @@ of hooks. Then entire system is designed, from the ground up, to not need
2 subscribing to and publishing events. Those events could come from `react`, but
3 they could also come from anywhere.
4
5+Thirdly, we have taken the best part about `express` and `koa` and applied it to
6+fetching API data on the front-end. What this means is that we have a powerful
7+middleware system that we can leverage on the front-end.
8+
9 # Why does `starfx` use js generators?
10
11 Generators give us -- the library authors -- more control over how side-effects
+22,
-5
1@@ -13,11 +13,8 @@ want.
2
3 # Usage
4
5-For endpoints, when you use `storeMdw.store()`, loaders automatically track
6-fetch requests.
7-
8-For thunks you can use `storeMdw.loader()` which will track the status of a
9-thunk.
10+For endpoints, loaders are installed automatically and track fetch requests.
11+Loader success is determined by `Response.ok` or if `fetch` throws an error.
12
13 You can also use loaders manually:
14
15@@ -33,6 +30,26 @@ function* fn() {
16 }
17 ```
18
19+For thunks you can use `mdw.loader()` which will track the status of a thunk.
20+
21+```ts
22+import { createThunks, mdw } from "starfx";
23+// imaginary schema
24+import { initialState, schema } from "./schema";
25+
26+const thunks = createThunks();
27+thunks.use(mdw.loader(schema));
28+thunks.use(thunks.routes());
29+
30+const go = thunks.create("go", function* (ctx, next) {
31+ throw new Error("boom!");
32+});
33+
34+const store = createStore({ initialState });
35+store.dispatch(go());
36+schema.loaders.selectById(store.getState(), { id: `${go}` }); // status = "error"; message = "boom!"
37+```
38+
39 # Shape
40
41 ```ts
+4,
-4
1@@ -6,8 +6,8 @@ description: State management in starfx
2 Once core component of an MVC framework is the Model.
3
4 Since data normalization is a first-class citizen inside `starfx`, we built a
5-custom react database for front-end web apps. Like a backend MVC framework, we
6-want to think of managing the FE store like managing a database. So while
7+custom, reactive database for front-end web apps. Like a backend MVC framework,
8+we want to think of managing the FE store like managing a database. So while
9 thinking about models as separate entities, you create all your models by
10 creating a single schema.
11
12@@ -15,7 +15,7 @@ Managing models in `starfx` leverages two primary concepts: schema and store.
13
14 The store is a single, global, and reactive object that was built to make
15 updating views easy. It is essentially an event emitter with a javascript object
16-attached to it.
17+that is updated in a very particular way (via `schema.update`).
18
19 Because the goal of this library is to create scalable web apps, we want users
20-to create all their models at the same time inside of a single schema.
21+to create all their models at the same time inside a single schema.
+2,
-0
1@@ -17,6 +17,8 @@ functions to:
2 - Update the value
3 - Query for data within the value
4
5+Our schema implementation was heavily inspired by [zod](https://zod.dev/).
6+
7 # Schema assumptions
8
9 `createSchema` requires two slices by default in order for it and everything
+11,
-4
1@@ -3,10 +3,17 @@ title: Store
2 Description: An immutable store that acts like a reactive, in-memory database
3 ---
4
5-I love `redux`. I know it gets sniped for having too much boilerplate when
6+Features:
7+
8+- A single, global javascript object
9+- Reactive
10+- Normalized
11+- Acts like a database
12+
13+We love `redux`. We know it gets sniped for having too much boilerplate when
14 alternatives like `zustand` and `react-query` exist that cut through the
15 ceremony of managing state. However, `redux` was never designed to be easy to
16-use, it was designed to be scalable, debuggable, and maintainable. Yes, setting
17+use; it was designed to be scalable, debuggable, and maintainable. Yes, setting
18 up a `redux` store is work, but that is in an effort to serve its
19 maintainability.
20
21@@ -19,13 +26,13 @@ Fast forward to `redux-toolkit` and we have `createSlice` which leverages
22 `immer` under-the-hood to ensure immutability. So we no longer need reducers for
23 immutability.
24
25-Further, I argue, placing the business logic for updating state inside reducers
26+Further, we argue, placing the business logic for updating state inside reducers
27 (via switch-cases) makes understanding business logic harder. Instead of having
28 a single function that updates X state slices, we have X functions (reducers)
29 that we need to piece together in our heads to understand what is being updated
30 when an action is dispatched.
31
32-With all of this in mind, `starfx/store` takes all the good parts of `redux` and
33+With all of this in mind, `starfx` takes all the good parts of `redux` and
34 removes the need for reducers entirely. We still have a single state object that
35 contains everything from API data, UX, and a way to create memoized functions
36 (e.g. selectors). We maintain immutability (using `immer`) and also have a
+43,
-7
1@@ -7,16 +7,18 @@ Thunks are the foundational central processing units. They have access to all
2 the actions being dispatched from the view as well as your global state. They
3 also wield the full power of structured concurrency.
4
5-As I've been developing these specialized thunks, I'm starting to think of them
6-more like micro-controllers. Only thunks and endpoints have the ability to
7-update state (think MVC). However, thunks are not tied to any particular view
8-and in that way are more composable. Thunks can call other thunks and you have
9-the async flow control tools from `effection` to facilitate coordination.
10+> Endpoints are specialized thunks as you will see later in the docs
11+
12+Think of thunks as micro-controllers. Only thunks and endpoints have the ability
13+to update state (or a model in MVC terms). However, thunks are not tied to any
14+particular view and in that way are more composable. Thunks can call other
15+thunks and you have the async flow control tools from `effection` to facilitate
16+coordination and cleanup.
17
18 Every thunk that's created requires a unique id -- user provided string. This
19-provides us with a handful of benefits:
20+provides us with some benefits:
21
22-- User hand-labels each thunk created
23+- User hand-labels each thunk
24 - Better traceability
25 - Easier to debug async and side-effects
26 - Build abstractions off naming conventions (e.g. creating routers
27@@ -62,6 +64,34 @@ store.dispatch(log("sending log message"));
28 // after all remaining middleware have run
29 ```
30
31+# Anatomy of thunk middleware
32+
33+Thunks are a composition of middleware functions in a stack. Therefore, every
34+single middleware function shares the exact same type signature:
35+
36+```ts
37+// for demonstration purposes we are copy/pasting these types which can
38+// normally be imported from:
39+// import type { ThunkCtx, Next } from "starfx";
40+type Next = () => Operation<void>;
41+
42+interface ThunkCtx<P = any> extends Payload<P> {
43+ name: string;
44+ key: string;
45+ action: ActionWithPayload<CreateActionPayload<P>>;
46+ actionFn: IfAny<
47+ P,
48+ CreateAction<ThunkCtx>,
49+ CreateActionWithPayload<ThunkCtx<P>, P>
50+ >;
51+ result: Result<void>;
52+}
53+
54+function* myMiddleware(ctx: ThunkCtx, next: Next) {
55+ yield* next();
56+}
57+```
58+
59 # Thunk action
60
61 When creating a thunk, the return value is just an action creator:
62@@ -74,9 +104,15 @@ console.log(log("sending log message"));
63 }
64 ```
65
66+An action is the "event" being emitted from `startfx` and subscribes to a very
67+particular type signature.
68+
69 A thunk action adheres to the
70 [flux standard action spec](https://github.com/redux-utilities/flux-standard-action).
71
72+> While not strictly necessary, it is highly recommended to keep actions JSON
73+> serializable
74+
75 # Thunk payload
76
77 When calling a thunk, the user can provide a payload that is strictly enforced