- commit
- fe13fff
- parent
- 6f04a24
- author
- Eric Bower
- date
- 2024-02-15 15:23:54 +0000 UTC
docs: copy
3 files changed,
+206,
-14
+13,
-3
1@@ -5,7 +5,11 @@ description: Our API for public consumption
2
3 WIP
4
5-# `parallel`
6+# query
7+
8+# fx
9+
10+## `parallel`
11
12 This is `Promise.all` on steroids and doesn't cancel all other tasks when one
13 fails.
14@@ -14,6 +18,12 @@ It can wait for all tasks to complete, receive them in the order in which they
15 were created, or receive them in the order in which they were completed. They
16 are safe, meaning one task won't crash another one -- or the parent task.
17
18-# `race`
19+## `race`
20+
21+## `safe`
22+
23+# store
24+
25+# schema
26
27-# `safe`
28+# react
+21,
-3
1@@ -46,11 +46,14 @@ import * as starfx from "https://deno.land/x/starfx@0.7.0/mod.ts";
2
3 # the simplest example
4
5-Here we demonstrate a complete example so you glimpse at how `starfx` works.
6+Here we demonstrate a complete example so you can glimpse at how `starfx` works.
7+In this example, we will fetch users from an API endpoint, cache the `Response`
8+json, and then ensure the endpoint only gets called at-most once every **5
9+minutes**, mimicking the basic features of `react-query`.
10
11 ```tsx
12 import ReactDOM from "react-dom/client";
13-import { createApi, mdw } from "starfx";
14+import { createApi, mdw, timer } from "starfx";
15 import { configureStore, createSchema, slice } from "starfx/store";
16 import { Provider, useCache } from "starfx/react";
17
18@@ -59,7 +62,11 @@ api.use(mdw.api());
19 api.use(api.routes());
20 api.use(mdw.fetch({ baseUrl: "https://jsonplaceholder.typicode.com" }));
21
22-const fetchUsers = api.get("/users", api.cache());
23+const fetchUsers = api.get(
24+ "/users",
25+ { supervisor: timer() },
26+ api.cache(),
27+);
28
29 const schema = createSchema({
30 loaders: slice.loader(),
31@@ -93,3 +100,14 @@ ReactDOM.createRoot(root).render(
32 </Provider>,
33 );
34 ```
35+
36+# `effection`
37+
38+In order to support structured concurrency, we are using `effection` to manage
39+that for us. As a result, in order to be successful using `starfx` developers
40+need to understand how `effection` works.
41+
42+We highly recommend reading through their docs at least once to get some
43+fundamental knowledge for how these libraries work.
44+
45+[https://frontside.com/effection](Read the `effection` docs)
+172,
-8
1@@ -10,8 +10,8 @@ A schema has two primary features:
2
3 A schema must be an object. It is composed of slices of state. Slices can
4 represent any data type, however, we recommend keeping it as JSON serializable
5-as possible. Slices not only hold a value, but associated with that value are
6-ways to:
7+as possible. Slices not only hold a value, but with it comes some handy
8+functions to:
9
10 - Update the value
11 - Query for data within the value
12@@ -21,12 +21,12 @@ ways to:
13 As a result, the following slices should cover the most common data types and
14 associated logic:
15
16-- `any`
17-- `loader`
18-- `num`
19-- `obj`
20-- `str`
21-- `table`
22+- [any](#any)
23+- [num](#num)
24+- [str](#str)
25+- [obj](#obj)
26+- [loader](#loader)
27+- [table](#table)
28
29 # Schema assumptions
30
31@@ -37,6 +37,170 @@ Why do we require those slices? Because if we can assume those exist, we can
32 build a lot of useful middleware and supervisors on top of that assumption. It's
33 a place for `starfx` and third-party functionality to hold their state.
34
35+# `slice.any`
36+
37+This is essentially a basic getter and setter slice. You can provide the type it
38+ought to be and it has a couple functions to manage and query the value stored
39+inside of it.
40+
41+```ts
42+const schema = createSchema({
43+ nav: slice.any<bool>(false),
44+});
45+
46+function*() {
47+ yield* schema.update(schema.nav.set(true)); // set the value
48+ const nav = yield* select(schema.nav.select); // grab the value
49+ yield* schema.update(schema.nav.reset()); // reset value back to inititial
50+}
51+```
52+
53+# `slice.num`
54+
55+This slice has some custom actions to manage a number value.
56+
57+```ts
58+const schema = createSchema({
59+ views: slice.num(0),
60+});
61+
62+function*() {
63+ yield* schema.update(schema.views.increment());
64+ yield* schema.update(schema.views.decrement());
65+ yield* schema.update(schema.views.set(100));
66+ const views = yield* select(schema.views.select);
67+ yield* schema.update(schema.views.reset()); // reset value back to inititial
68+}
69+```
70+
71+# `slice.str`
72+
73+This slice is probably not super useful since it is essentially the same as
74+`slice.any<string>` but we could add more actions to it in the future.
75+
76+```ts
77+const schema = createSchema({
78+ token: slice.str(""),
79+});
80+
81+function*() {
82+ yield* schema.update(schema.token.set("1234"));
83+ const token = yield* select(schema.token.select);
84+ yield* schema.update(schema.token.reset()); // reset value back to inititial
85+}
86+```
87+
88+# `slice.obj`
89+
90+This is a specialized slice with some custom actions to deal with javascript
91+objects.
92+
93+```ts
94+const schema = createSchema({
95+ settings: slice.obj({
96+ notifications: false,
97+ theme: "light",
98+ }),
99+});
100+
101+function*() {
102+ yield* schema.update(schema.settings.update(theme, "dark"));
103+ yield* schema.update(schema.settings.update(notifications, true));
104+ const settings = yield* select(schema.settings.select);
105+ yield* schema.update(schema.token.reset()); // reset value back to inititial
106+ yield* schema.update(
107+ schema.token.set({ notifications: true, theme: "dark" }),
108+ );
109+}
110+```
111+
112+# `slice.table`
113+
114+This is the more powerful and specialized slice we created. It attempts to
115+mimick a database table where it holds an object:
116+
117+```ts
118+type Table<Entity = any> = Record<string | number, Entity>;
119+```
120+
121+The key is the entity's primary id and the value is the entity itself.
122+
123+```ts
124+const schema = createSchema({
125+ users: slice.table({ empty: { id: "", name: "" } }),
126+});
127+
128+function*() {
129+ const user1 = { id: "1", name: "bbob" };
130+ const user2 = { id: "2", name: "tony" };
131+ const user3 = { id: "3", name: "jessica" };
132+ yield* schema.update(
133+ schema.users.add({
134+ [user1.id]: user1,
135+ [user2.id]: user2,
136+ [user3.id]: user3,
137+ }),
138+ );
139+ yield* schema.update(
140+ schema.users.patch({ [user1.id]: { name: "bob" } }),
141+ );
142+ yield* schema.update(
143+ schema.users.remove([user3.id]),
144+ );
145+
146+ // selectors
147+ yield* select(schema.users.selectTable());
148+ yield* select(schema.users.selectTableAsList());
149+ yield* select(schema.users.selectById({ id: user1.id }));
150+ yield* select(schema.users.selectByIds({ ids: [user1.id, user2.id] }));
151+
152+ yield* schema.update(schema.users.reset());
153+}
154+```
155+
156+## empty
157+
158+When `empty` is provided to `slice.table` and we use a selector like
159+`selectById` to find an entity that does **not** exist, we will return the
160+`empty` value.
161+
162+This mimicks golang's empty values but catered towards entities. When `empty` is
163+provided, we guarentee that `selectById` will return the correct state shape,
164+with all the empty values that the end-developer provides.
165+
166+By providing a "default" entity when none exists, it promotes safer code because
167+it creates stable assumptions about the data we have when performing lookups.
168+The last thing we want to do is litter our view layer with optional chaining,
169+because it sets up poor assumptions about the data we have.
170+
171+Read more about this design philosophy in my blog post:
172+[Death by a thousand existential checks](https://bower.sh/death-by-thousand-existential-checks).
173+
174+When creating table slices, we highly recommend providing an `empty` value.
175+
176+Further, we also recommend creating entity factories for each entity that exists
177+in your system.
178+
179+[Read more about entity factories.](https://bower.sh/entity-factories)
180+
181+# `slice.loader`
182+
183+This is a specialized database table specific to managing loaders in `starfx`.
184+[Read more about loaders here](/loader).
185+
186+```ts
187+const schema = createSchema({
188+ loaders: slice.loader(),
189+});
190+
191+function*() {
192+ yield* schema.update(schema.loaders.start({ id: "my-id" }));
193+ yield* schema.update(schema.loaders.success({ id: "my-id" }));
194+ const loader = yield* select(schema.loaders.selectById({ id: "my-id" }));
195+ console.log(loader);
196+}
197+```
198+
199 # Build your own slice
200
201 We will build a `counter` slice to demonstrate how to build your own slices.