@kdeveloper/kvark 0.2.0 → 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.
Files changed (2) hide show
  1. package/README.md +66 -3
  2. 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, id: number) => id,
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, value: number) => value,
89
+ set: async (_ctx, _value: number) => {},
90
90
  });
91
91
 
92
92
  // Derived atom — reads from another atom
@@ -100,6 +100,69 @@ 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
+
103
166
  ### `onMount`
104
167
 
105
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`.
@@ -320,7 +383,7 @@ export function App() {
320
383
  ## Utility Types
321
384
 
322
385
  ```ts
323
- import type { AtomValue, AtomArgs, IsWritable } from "@kdeveloper/kvark";
386
+ import type { AtomValue, AtomArgs, IsWritable, WritableAtomContext } from "@kdeveloper/kvark";
324
387
 
325
388
  type UserData = AtomValue<typeof userAtom>; // → User
326
389
  type PostArgs = AtomArgs<typeof postAtom>; // → [postId: number]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kdeveloper/kvark",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Atomic state management with explicit dependency graphs",
5
5
  "license": "MIT",
6
6
  "files": [