@kdeveloper/kvark 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ import { Provider, useAtomValue, useSetAtom } from "@kdeveloper/kvark/react";
|
|
|
39
39
|
const userIdAtom = atom({
|
|
40
40
|
debugLabel: "userId",
|
|
41
41
|
get: async () => 1,
|
|
42
|
-
set: async (_ctx,
|
|
42
|
+
set: async (_ctx, _id: number) => {},
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
const userAtom = atom({
|
|
@@ -86,7 +86,7 @@ import { atom } from "@kdeveloper/kvark";
|
|
|
86
86
|
const countAtom = atom({
|
|
87
87
|
debugLabel: "count",
|
|
88
88
|
get: async () => 0,
|
|
89
|
-
set: async (_ctx,
|
|
89
|
+
set: async (_ctx, _value: number) => {},
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
// Derived atom — reads from another atom
|
|
@@ -100,6 +100,88 @@ const doubleAtom = atom({
|
|
|
100
100
|
});
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### Writable atoms
|
|
104
|
+
|
|
105
|
+
An atom is writable when its config includes a `set` function. The return type becomes `WritableAtom<Value, Args>`, where `Args` is the tuple of arguments the setter accepts.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const profileAtom = atom({
|
|
109
|
+
debugLabel: "profile",
|
|
110
|
+
get: async () => {
|
|
111
|
+
const res = await fetch("/api/profile");
|
|
112
|
+
return res.json() as Promise<Profile>;
|
|
113
|
+
},
|
|
114
|
+
set: async (ctx, patch: Partial<Profile>) => {
|
|
115
|
+
await fetch("/api/profile", {
|
|
116
|
+
method: "PATCH",
|
|
117
|
+
body: JSON.stringify(patch),
|
|
118
|
+
signal: ctx.signal,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Lifecycle after `set`:** the store calls `config.set(ctx, ...args)`, waits for the returned promise, then calls `invalidate` on the atom. This marks it `stale` and triggers a background refetch via `get` — the same stale-while-revalidate flow described [above](#stale-while-revalidate).
|
|
125
|
+
|
|
126
|
+
The `ctx` passed to `set` provides:
|
|
127
|
+
|
|
128
|
+
- **`ctx.get(key)`** — read any declared dependency (same as in `get`).
|
|
129
|
+
- **`ctx.signal`** — an `AbortSignal` tied to the atom's lifecycle, useful for cancelling in-flight requests.
|
|
130
|
+
- **`ctx.setOptimisticValue(value)`** — synchronously update the atom's cached value before the async work completes (see below).
|
|
131
|
+
|
|
132
|
+
#### Optimistic updates
|
|
133
|
+
|
|
134
|
+
Call `ctx.setOptimisticValue(value)` inside `set` to immediately reflect the new value in the UI while the mutation runs in the background. If the mutation throws (or the signal aborts), the store automatically rolls back to the state captured before the first `setOptimisticValue` call. Derived atoms that depend on this atom are marked `stale` so they re-render too.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const todoAtom = atom({
|
|
138
|
+
debugLabel: "todo",
|
|
139
|
+
get: async () => {
|
|
140
|
+
const res = await fetch("/api/todo");
|
|
141
|
+
return res.json() as Promise<Todo>;
|
|
142
|
+
},
|
|
143
|
+
set: async (ctx, title: string) => {
|
|
144
|
+
ctx.setOptimisticValue({ title, done: false });
|
|
145
|
+
await fetch("/api/todo", {
|
|
146
|
+
method: "PUT",
|
|
147
|
+
body: JSON.stringify({ title }),
|
|
148
|
+
signal: ctx.signal,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If the `PUT` fails, the atom reverts to whatever value `get` had loaded before the optimistic update — no manual rollback needed.
|
|
155
|
+
|
|
156
|
+
#### Writable atoms vs `onMount`
|
|
157
|
+
|
|
158
|
+
Both can update an atom's cached value, but they serve different purposes:
|
|
159
|
+
|
|
160
|
+
| | `set` | `onMount` |
|
|
161
|
+
|-|-------|-----------|
|
|
162
|
+
| **Triggered by** | Explicit call (`store.set`, `useSetAtom`) | First subscriber mounts |
|
|
163
|
+
| **After update** | `invalidate` → refetch via `get` | No refetch — value stays as-is |
|
|
164
|
+
| **Use case** | Mutations, API calls, optimistic updates | Timers, subscriptions, imperative push |
|
|
165
|
+
|
|
166
|
+
### `onMount`
|
|
167
|
+
|
|
168
|
+
Optional lifecycle hook that runs when the atom **first gains a subscriber** in a store (for example when a React component using `useAtomValue` mounts). It receives a synchronous `set(value)` that marks the atom `fresh` and notifies listeners — useful for timers, subscriptions, or imperative updates that should not go through `get`.
|
|
169
|
+
|
|
170
|
+
You may return a cleanup function; it runs when the **last** subscriber unsubscribes (for example when the last mounted consumer unmounts). If several components subscribe to the same atom, `onMount` runs once and the cleanup runs once after all of them unsubscribe.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const clockAtom = atom({
|
|
174
|
+
debugLabel: "clock",
|
|
175
|
+
get: async () => new Date().toISOString(),
|
|
176
|
+
onMount: (set) => {
|
|
177
|
+
const id = setInterval(() => {
|
|
178
|
+
set(new Date().toISOString());
|
|
179
|
+
}, 1000);
|
|
180
|
+
return () => clearInterval(id);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
103
185
|
### Parallel loading
|
|
104
186
|
|
|
105
187
|
Declaring multiple dependencies causes the Store to resolve them in parallel before calling `get`. Inside `get` you control the parallelism explicitly.
|
|
@@ -301,7 +383,7 @@ export function App() {
|
|
|
301
383
|
## Utility Types
|
|
302
384
|
|
|
303
385
|
```ts
|
|
304
|
-
import type { AtomValue, AtomArgs, IsWritable } from "@kdeveloper/kvark";
|
|
386
|
+
import type { AtomValue, AtomArgs, IsWritable, WritableAtomContext } from "@kdeveloper/kvark";
|
|
305
387
|
|
|
306
388
|
type UserData = AtomValue<typeof userAtom>; // → User
|
|
307
389
|
type PostArgs = AtomArgs<typeof postAtom>; // → [postId: number]
|