@kdeveloper/kvark 0.1.1

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 ADDED
@@ -0,0 +1,329 @@
1
+ # @kdeveloper/kvark
2
+
3
+ Atomic state management for React with **explicit dependency graphs**, stale-while-revalidate, and first-class external invalidation.
4
+
5
+ Inspired by Jotai, but built around a key difference: dependencies are declared upfront rather than inferred at runtime. This makes the data graph statically analysable, enables parallel loading, and allows invalidation from anywhere — WebSocket handlers, SSE streams, timers, Service Workers — without any React hooks.
6
+
7
+ ```
8
+ npm install @kdeveloper/kvark
9
+ # peer dependency: react >=18
10
+ ```
11
+
12
+ ## Why Kvark?
13
+
14
+ | | Jotai | Kvark |
15
+ | ---------------------- | -------------------------------- | ----------------------------------------------- |
16
+ | Dependency declaration | Implicit (via `get(atom)` calls) | Explicit, via `dependencies` field |
17
+ | `atom()` signature | `atom(read, write?)` | `atom({ get, set?, dependencies? })` |
18
+ | Async model | Optional | `get`, `set`, `ctx.get` — always `async` |
19
+ | Parallel loading | Manual `Promise.all` | Built-in through `dependencies` |
20
+ | Stale-while-revalidate | Re-suspends on revalidation | Shows stale data; Suspense only on first load |
21
+ | `atomFamily` | External (`jotai/utils`) | Core, with LRU and invalidation |
22
+ | External invalidation | `store.set()` | Explicit `store.invalidate()` / `StoreClient` |
23
+ | TypeScript strictness | `strict: true` recommended | `strict: true` + 8 extra flags, `any` forbidden |
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pnpm add @kdeveloper/kvark
29
+ # react >=18 is a peer dependency
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```tsx
35
+ import { atom, createStore } from "@kdeveloper/kvark";
36
+ import { Provider, useAtomValue, useSetAtom } from "@kdeveloper/kvark/react";
37
+
38
+ // 1. Create atoms
39
+ const userIdAtom = atom({
40
+ debugLabel: "userId",
41
+ get: async () => 1,
42
+ set: async (_ctx, id: number) => id,
43
+ });
44
+
45
+ const userAtom = atom({
46
+ debugLabel: "user",
47
+ dependencies: { userId: userIdAtom },
48
+ get: async (ctx) => {
49
+ const id = await ctx.get("userId");
50
+ const res = await fetch(`/api/users/${id}`, { signal: ctx.signal });
51
+ return res.json();
52
+ },
53
+ });
54
+
55
+ // 2. Create a store
56
+ const store = createStore();
57
+
58
+ // 3. Wrap your app
59
+ function App() {
60
+ return (
61
+ <Provider store={store}>
62
+ <Suspense fallback="Loading...">
63
+ <UserCard />
64
+ </Suspense>
65
+ </Provider>
66
+ );
67
+ }
68
+
69
+ // 4. Use in components
70
+ function UserCard() {
71
+ const user = useAtomValue(userAtom);
72
+ return <div>{user.name}</div>;
73
+ }
74
+ ```
75
+
76
+ ## Core Concepts
77
+
78
+ ### Atoms
79
+
80
+ An atom is the smallest unit of state. Its `get` function is always `async`, and dependencies must be declared explicitly.
81
+
82
+ ```ts
83
+ import { atom } from "@kdeveloper/kvark";
84
+
85
+ // Primitive atom — no dependencies
86
+ const countAtom = atom({
87
+ debugLabel: "count",
88
+ get: async () => 0,
89
+ set: async (_ctx, value: number) => value,
90
+ });
91
+
92
+ // Derived atom — reads from another atom
93
+ const doubleAtom = atom({
94
+ debugLabel: "double",
95
+ dependencies: { count: countAtom },
96
+ get: async (ctx) => {
97
+ const n = await ctx.get("count");
98
+ return n * 2;
99
+ },
100
+ });
101
+ ```
102
+
103
+ ### Parallel loading
104
+
105
+ Declaring multiple dependencies causes the Store to resolve them in parallel before calling `get`. Inside `get` you control the parallelism explicitly.
106
+
107
+ ```ts
108
+ const dashboardAtom = atom({
109
+ dependencies: { user: userAtom, settings: settingsAtom },
110
+ get: async (ctx) => {
111
+ const [user, settings] = await Promise.all([ctx.get("user"), ctx.get("settings")]);
112
+ return { user, settings };
113
+ },
114
+ });
115
+ ```
116
+
117
+ ### Stale-while-revalidate
118
+
119
+ By default (`stalePolicy: 'keep'`), invalidating an atom marks it `stale` while preserving the last value. Components keep rendering the old data without re-suspending. Once revalidation completes the atom becomes `fresh` again.
120
+
121
+ ```
122
+ pending → fresh (first load)
123
+ fresh → stale (invalidated — old value kept)
124
+ stale → fresh (revalidation complete)
125
+ stale → error (revalidation failed — value preserved with 'keep')
126
+ ```
127
+
128
+ Use `{ observe: true }` to access the `isStale` flag in your component:
129
+
130
+ ```tsx
131
+ function UserCard() {
132
+ const { value: user, isStale, error } = useAtomValue(userAtom, { observe: true });
133
+
134
+ return (
135
+ <div style={{ opacity: isStale ? 0.6 : 1 }}>
136
+ <p>{user.name}</p>
137
+ {isStale && <Spinner />}
138
+ {error != null && <ErrorBanner error={error} />}
139
+ </div>
140
+ );
141
+ }
142
+ ```
143
+
144
+ Available `stalePolicy` values:
145
+
146
+ | Value | Behaviour |
147
+ | ------------------ | ---------------------------------------------- |
148
+ | `'keep'` (default) | Show stale data, revalidate in background |
149
+ | `'suspend'` | Re-throw Promise on revalidation (re-suspends) |
150
+ | `'reset'` | Clear value and re-suspend immediately |
151
+
152
+ ### `atomFamily`
153
+
154
+ Create a family of atoms parametrised by a key. Atoms are cached by param; supports LRU eviction.
155
+
156
+ ```ts
157
+ import { atomFamily } from "@kdeveloper/kvark/family";
158
+
159
+ const postFamily = atomFamily({
160
+ debugLabel: "post",
161
+ cachePolicy: "lru",
162
+ lruSize: 50,
163
+ dependencies: (_postId: number) => ({ user: userAtom }),
164
+ get: (postId) => async (ctx) => {
165
+ const user = await ctx.get("user");
166
+ const res = await fetch(`/api/posts/${postId}?userId=${user.id}`, {
167
+ signal: ctx.signal,
168
+ });
169
+ return res.json();
170
+ },
171
+ });
172
+
173
+ // Use in a component
174
+ const post = useAtomValue(postFamily(42));
175
+
176
+ // Invalidate after a mutation
177
+ await api.updatePost(42, data);
178
+ postFamily.invalidate(42);
179
+
180
+ // Invalidate everything (e.g. on logout)
181
+ postFamily.invalidateAll();
182
+ ```
183
+
184
+ ## React Hooks
185
+
186
+ All hooks must be used inside a `<Provider>`.
187
+
188
+ ### `useAtomValue`
189
+
190
+ Reads an atom value and subscribes to updates. Suspends on first load.
191
+
192
+ ```tsx
193
+ // Basic — throws if error, suspends if pending
194
+ const user = useAtomValue(userAtom);
195
+
196
+ // Observed — exposes isStale and error without throwing
197
+ const { value, isStale, error } = useAtomValue(userAtom, { observe: true });
198
+ ```
199
+
200
+ ### `useSetAtom`
201
+
202
+ Returns the setter for a writable atom, without subscribing to the value.
203
+
204
+ ```tsx
205
+ const setCount = useSetAtom(countAtom);
206
+ await setCount(42);
207
+ ```
208
+
209
+ ### `useAtom`
210
+
211
+ Combines `useAtomValue` and `useSetAtom` into a `[value, setter]` tuple.
212
+
213
+ ```tsx
214
+ const [count, setCount] = useAtom(countAtom);
215
+ ```
216
+
217
+ ### `useAtomCallback`
218
+
219
+ Imperative access to the `StoreClient` inside a callback. Does not subscribe.
220
+
221
+ ```tsx
222
+ const readBalance = useAtomCallback(async (client) => {
223
+ return client.get(balanceAtom);
224
+ });
225
+
226
+ // Call imperatively, e.g. in an event handler
227
+ const balance = await readBalance();
228
+ ```
229
+
230
+ ## External Invalidation
231
+
232
+ `StoreClient` exposes the store's full capabilities outside of React — useful for WebSocket handlers, SSE streams, polling timers, and Service Workers.
233
+
234
+ ```ts
235
+ import { createStore } from "@kdeveloper/kvark";
236
+
237
+ const store = createStore();
238
+ const client = store.getClient();
239
+
240
+ // WebSocket
241
+ const ws = new WebSocket("wss://api.example.com/events");
242
+ ws.addEventListener("message", (event) => {
243
+ const msg = JSON.parse(event.data);
244
+ if (msg.type === "post.updated") {
245
+ postFamily.invalidate(msg.postId);
246
+ }
247
+ if (msg.type === "user.updated") {
248
+ client.invalidate(userAtom);
249
+ }
250
+ });
251
+
252
+ // SSE
253
+ const sse = new EventSource("/api/stream");
254
+ sse.addEventListener("prices.updated", () => {
255
+ client.invalidate(pricesAtom);
256
+ });
257
+
258
+ // Polling
259
+ setInterval(() => client.invalidate(statusAtom), 30_000);
260
+
261
+ // Subscribe to state changes outside React
262
+ const unsub = client.subscribe(userAtom, (state) => {
263
+ if (state.status === "fresh") {
264
+ analytics.identify(state.value.id);
265
+ }
266
+ });
267
+ ```
268
+
269
+ ### `StoreClient` interface
270
+
271
+ ```ts
272
+ interface StoreClient {
273
+ get<V>(atom: Atom<V>): Promise<V>;
274
+ set<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>, ...args: A): Promise<void>;
275
+ invalidate(atom: Atom<unknown>): void;
276
+ invalidateMany(atoms: ReadonlyArray<Atom<unknown>>): void;
277
+ subscribe<V>(atom: Atom<V>, listener: (state: AtomState<V>) => void): () => void;
278
+ }
279
+ ```
280
+
281
+ ## Provider and Store Setup
282
+
283
+ ```tsx
284
+ // store.ts
285
+ import { createStore } from "@kdeveloper/kvark";
286
+ export const store = createStore();
287
+
288
+ // App.tsx
289
+ import { Provider } from "@kdeveloper/kvark/react";
290
+ import { store } from "./store";
291
+
292
+ export function App() {
293
+ return (
294
+ <Provider store={store}>
295
+ <Router />
296
+ </Provider>
297
+ );
298
+ }
299
+ ```
300
+
301
+ ## Utility Types
302
+
303
+ ```ts
304
+ import type { AtomValue, AtomArgs, IsWritable } from "@kdeveloper/kvark";
305
+
306
+ type UserData = AtomValue<typeof userAtom>; // → User
307
+ type PostArgs = AtomArgs<typeof postAtom>; // → [postId: number]
308
+ type Writable = IsWritable<typeof countAtom>; // → true | false
309
+ ```
310
+
311
+ ## Package Structure
312
+
313
+ | Import | Contents |
314
+ | -------------------------- | ---------------------------------------------------------------------- |
315
+ | `@kdeveloper/kvark` | `atom`, `createStore`, all types |
316
+ | `@kdeveloper/kvark/react` | `Provider`, `useAtomValue`, `useSetAtom`, `useAtom`, `useAtomCallback` |
317
+ | `@kdeveloper/kvark/family` | `atomFamily` |
318
+
319
+ The core (`@kdeveloper/kvark`) has **zero runtime dependencies**. React is a peer dependency only needed when using the `/react` subpath.
320
+
321
+ ## Requirements
322
+
323
+ - **Node.js** ≥ 20
324
+ - **TypeScript** ≥ 6 (strict mode recommended)
325
+ - **React** ≥ 18 (for `@kdeveloper/kvark/react`)
326
+
327
+ ## License
328
+
329
+ MIT
@@ -0,0 +1,405 @@
1
+ // src/internal/types.ts
2
+ var CONFIG = /* @__PURE__ */ Symbol("kvark.config");
3
+ var WRITABLE = /* @__PURE__ */ Symbol("kvark.writable");
4
+ var FAMILY_LINK = /* @__PURE__ */ Symbol("kvark.familyLink");
5
+
6
+ // src/internal/atom.ts
7
+ function atom(config) {
8
+ const internal = /* @__PURE__ */ Object.create(null);
9
+ internal.get = config.get;
10
+ if (config.debugLabel != null) {
11
+ internal.debugLabel = config.debugLabel;
12
+ }
13
+ if (config.dependencies != null) {
14
+ internal.dependencies = config.dependencies;
15
+ }
16
+ if (config.stalePolicy != null) {
17
+ internal.stalePolicy = config.stalePolicy;
18
+ }
19
+ if (config.set != null) {
20
+ internal.set = config.set;
21
+ }
22
+ if (config.set != null) {
23
+ return {
24
+ [CONFIG]: internal,
25
+ [WRITABLE]: [],
26
+ debugLabel: config.debugLabel
27
+ };
28
+ }
29
+ return {
30
+ [CONFIG]: internal,
31
+ debugLabel: config.debugLabel
32
+ };
33
+ }
34
+
35
+ // src/internal/family.ts
36
+ function getFamilyLink(target) {
37
+ return target[FAMILY_LINK];
38
+ }
39
+ function atomFamily(options) {
40
+ const cachePolicy = options.cachePolicy ?? "keep-all";
41
+ const lruSize = options.lruSize ?? 100;
42
+ const cache = /* @__PURE__ */ new Map();
43
+ const lruOrder = [];
44
+ const storeCallbacks = /* @__PURE__ */ new Set();
45
+ const link = {
46
+ invalidateAtom(target) {
47
+ for (const cb of storeCallbacks) {
48
+ cb(target);
49
+ }
50
+ },
51
+ registerStore(cb) {
52
+ storeCallbacks.add(cb);
53
+ return () => {
54
+ storeCallbacks.delete(cb);
55
+ };
56
+ }
57
+ };
58
+ function attachLink(target) {
59
+ target[FAMILY_LINK] = link;
60
+ }
61
+ function getOrCreate(param) {
62
+ const cached = cache.get(param);
63
+ if (cached != null) {
64
+ if (cachePolicy === "lru") {
65
+ touchLru(param);
66
+ }
67
+ return cached;
68
+ }
69
+ const deps = options.dependencies?.(param);
70
+ const getFn = options.get(param);
71
+ const setFn = options.set?.(param);
72
+ const label = options.debugLabel != null ? `${options.debugLabel}(${String(param)})` : void 0;
73
+ const created = createAtom(label, deps, getFn, setFn);
74
+ attachLink(created);
75
+ cache.set(param, created);
76
+ if (cachePolicy === "lru") {
77
+ lruOrder.push(param);
78
+ evictLru();
79
+ }
80
+ return created;
81
+ }
82
+ function createAtom(label, deps, getFn, setFn) {
83
+ const base = {
84
+ get: getFn
85
+ };
86
+ if (label != null) {
87
+ base.debugLabel = label;
88
+ }
89
+ if (deps != null) {
90
+ base.dependencies = deps;
91
+ }
92
+ if (options.stalePolicy != null) {
93
+ base.stalePolicy = options.stalePolicy;
94
+ }
95
+ if (setFn != null) {
96
+ base.set = setFn;
97
+ return atom(
98
+ base
99
+ );
100
+ }
101
+ return atom(base);
102
+ }
103
+ function touchLru(param) {
104
+ const idx = lruOrder.indexOf(param);
105
+ if (idx !== -1) {
106
+ lruOrder.splice(idx, 1);
107
+ }
108
+ lruOrder.push(param);
109
+ }
110
+ function evictLru() {
111
+ while (lruOrder.length > lruSize) {
112
+ const evicted = lruOrder.shift();
113
+ if (evicted !== void 0) {
114
+ cache.delete(evicted);
115
+ }
116
+ }
117
+ }
118
+ const family = getOrCreate;
119
+ family.invalidate = (param) => {
120
+ const cached = cache.get(param);
121
+ if (cached != null) {
122
+ link.invalidateAtom(cached);
123
+ }
124
+ };
125
+ family.invalidateAll = () => {
126
+ for (const cached of cache.values()) {
127
+ link.invalidateAtom(cached);
128
+ }
129
+ };
130
+ family.remove = (param) => {
131
+ cache.delete(param);
132
+ if (cachePolicy === "lru") {
133
+ const idx = lruOrder.indexOf(param);
134
+ if (idx !== -1) {
135
+ lruOrder.splice(idx, 1);
136
+ }
137
+ }
138
+ };
139
+ family.getCache = () => cache;
140
+ return family;
141
+ }
142
+
143
+ // src/internal/store.ts
144
+ var PENDING_STATE = {
145
+ status: "pending",
146
+ value: void 0,
147
+ error: void 0
148
+ };
149
+ var Store = class {
150
+ #atoms = /* @__PURE__ */ new WeakMap();
151
+ #rdeps = /* @__PURE__ */ new Map();
152
+ #pending = /* @__PURE__ */ new Set();
153
+ #controllers = /* @__PURE__ */ new WeakMap();
154
+ #familyUnsubs = /* @__PURE__ */ new Set();
155
+ #registeredFamilies = /* @__PURE__ */ new WeakSet();
156
+ #client = null;
157
+ #getOrCreate(atom2) {
158
+ let entry = this.#atoms.get(atom2);
159
+ if (entry == null) {
160
+ entry = {
161
+ state: PENDING_STATE,
162
+ version: 0,
163
+ promise: null,
164
+ listeners: /* @__PURE__ */ new Set()
165
+ };
166
+ this.#atoms.set(atom2, entry);
167
+ }
168
+ return entry;
169
+ }
170
+ resolve(atom2) {
171
+ const entry = this.#getOrCreate(atom2);
172
+ if (entry.promise != null) {
173
+ return entry.promise;
174
+ }
175
+ const promise = this.#runGet(atom2).finally(() => {
176
+ const e = this.#atoms.get(atom2);
177
+ if (e != null) {
178
+ e.promise = null;
179
+ }
180
+ });
181
+ entry.promise = promise;
182
+ return promise;
183
+ }
184
+ async #runGet(atom2) {
185
+ const config = atom2[CONFIG];
186
+ const dependencies = config.dependencies;
187
+ const stalePolicy = config.stalePolicy ?? "keep";
188
+ const entry = this.#getOrCreate(atom2);
189
+ if (dependencies != null) {
190
+ await Promise.all(Object.values(dependencies).map((dep) => this.resolve(dep)));
191
+ this.#registerRdeps(atom2, dependencies);
192
+ }
193
+ const ctx = this.#makeCtx(atom2, dependencies ?? {});
194
+ try {
195
+ const value = await config.get(ctx);
196
+ entry.state = { status: "fresh", value, error: void 0 };
197
+ entry.version++;
198
+ this.#scheduleNotify(atom2);
199
+ return value;
200
+ } catch (e) {
201
+ if (isAbortError(e)) {
202
+ throw e;
203
+ }
204
+ const prevValue = extractPreviousValue(entry.state);
205
+ entry.state = {
206
+ status: "error",
207
+ value: stalePolicy === "keep" ? prevValue : void 0,
208
+ error: e
209
+ };
210
+ entry.version++;
211
+ this.#scheduleNotify(atom2);
212
+ throw e;
213
+ }
214
+ }
215
+ invalidate(atom2) {
216
+ this.#markStale(atom2);
217
+ this.#pending.add(atom2);
218
+ if (this.#pending.size === 1) {
219
+ queueMicrotask(() => {
220
+ this.#flushPending();
221
+ });
222
+ }
223
+ }
224
+ invalidateMany(atoms) {
225
+ for (const a of atoms) {
226
+ this.#markStale(a);
227
+ this.#pending.add(a);
228
+ }
229
+ if (this.#pending.size > 0) {
230
+ queueMicrotask(() => {
231
+ this.#flushPending();
232
+ });
233
+ }
234
+ }
235
+ #markStale(atom2, visited = /* @__PURE__ */ new Set()) {
236
+ if (visited.has(atom2)) {
237
+ return;
238
+ }
239
+ visited.add(atom2);
240
+ const entry = this.#atoms.get(atom2);
241
+ if (entry != null) {
242
+ const stalePolicy = atom2[CONFIG].stalePolicy ?? "keep";
243
+ if (entry.state.status === "fresh") {
244
+ if (stalePolicy === "reset") {
245
+ entry.state = PENDING_STATE;
246
+ } else {
247
+ entry.state = { ...entry.state, status: "stale" };
248
+ }
249
+ }
250
+ entry.promise = null;
251
+ this.#controllers.get(atom2)?.abort();
252
+ }
253
+ const rdeps = this.#rdeps.get(atom2);
254
+ if (rdeps != null) {
255
+ for (const dep of rdeps) {
256
+ this.#markStale(dep, visited);
257
+ }
258
+ }
259
+ }
260
+ #flushPending() {
261
+ const batch = [...this.#pending];
262
+ this.#pending.clear();
263
+ const toNotify = /* @__PURE__ */ new Set();
264
+ for (const a of batch) {
265
+ const entry = this.#atoms.get(a);
266
+ if (entry != null) {
267
+ for (const l of entry.listeners) {
268
+ toNotify.add(l);
269
+ }
270
+ }
271
+ }
272
+ for (const l of toNotify) {
273
+ l();
274
+ }
275
+ }
276
+ #scheduleNotify(atom2) {
277
+ this.#pending.add(atom2);
278
+ if (this.#pending.size === 1) {
279
+ queueMicrotask(() => {
280
+ this.#flushPending();
281
+ });
282
+ }
283
+ }
284
+ #registerRdeps(atom2, deps) {
285
+ for (const dep of Object.values(deps)) {
286
+ let set = this.#rdeps.get(dep);
287
+ if (set == null) {
288
+ set = /* @__PURE__ */ new Set();
289
+ this.#rdeps.set(dep, set);
290
+ }
291
+ set.add(atom2);
292
+ }
293
+ }
294
+ #makeCtx(atom2, deps) {
295
+ this.#controllers.get(atom2)?.abort();
296
+ const controller = new AbortController();
297
+ this.#controllers.set(atom2, controller);
298
+ return {
299
+ signal: controller.signal,
300
+ get: async (key) => {
301
+ const dep = deps[key];
302
+ if (dep == null) {
303
+ throw new Error(`Unknown dependency key: "${String(key)}"`);
304
+ }
305
+ return this.resolve(dep);
306
+ }
307
+ };
308
+ }
309
+ subscribe(atom2, listener) {
310
+ const entry = this.#getOrCreate(atom2);
311
+ entry.listeners.add(listener);
312
+ this.#autoRegisterFamily(atom2);
313
+ if (entry.state.status === "pending" && entry.promise == null) {
314
+ void this.resolve(atom2);
315
+ }
316
+ return () => {
317
+ entry.listeners.delete(listener);
318
+ };
319
+ }
320
+ #autoRegisterFamily(atom2) {
321
+ const link = getFamilyLink(atom2);
322
+ if (link == null) {
323
+ return;
324
+ }
325
+ if (this.#registeredFamilies.has(link)) {
326
+ return;
327
+ }
328
+ this.#registeredFamilies.add(link);
329
+ const unsub = link.registerStore((target) => {
330
+ this.invalidate(target);
331
+ });
332
+ this.#familyUnsubs.add(unsub);
333
+ }
334
+ getSnapshot(atom2) {
335
+ return this.#getOrCreate(atom2).state;
336
+ }
337
+ getServerSnapshot(atom2) {
338
+ const entry = this.#atoms.get(atom2);
339
+ if (entry != null) {
340
+ return entry.state;
341
+ }
342
+ return PENDING_STATE;
343
+ }
344
+ async set(atom2, ...args) {
345
+ const config = atom2[CONFIG];
346
+ if (config.set == null) {
347
+ throw new Error(
348
+ `Atom${atom2.debugLabel != null ? ` "${atom2.debugLabel}"` : ""} is not writable`
349
+ );
350
+ }
351
+ const deps = config.dependencies ?? {};
352
+ const ctx = this.#makeCtx(atom2, deps);
353
+ await config.set(ctx, ...args);
354
+ this.invalidate(atom2);
355
+ }
356
+ getClient() {
357
+ if (this.#client != null) {
358
+ return this.#client;
359
+ }
360
+ const store = this;
361
+ this.#client = {
362
+ get(atom2) {
363
+ return store.resolve(atom2);
364
+ },
365
+ set(atom2, ...args) {
366
+ return store.set(atom2, ...args);
367
+ },
368
+ invalidate(atom2) {
369
+ store.invalidate(atom2);
370
+ },
371
+ invalidateMany(atoms) {
372
+ store.invalidateMany(atoms);
373
+ },
374
+ subscribe(atom2, listener) {
375
+ return store.subscribe(atom2, () => {
376
+ listener(store.getSnapshot(atom2));
377
+ });
378
+ }
379
+ };
380
+ return this.#client;
381
+ }
382
+ };
383
+ function createStore() {
384
+ return new Store();
385
+ }
386
+ function isAbortError(e) {
387
+ return e instanceof DOMException && e.name === "AbortError";
388
+ }
389
+ function extractPreviousValue(state) {
390
+ if (state.status === "stale" || state.status === "fresh") {
391
+ return state.value;
392
+ }
393
+ if (state.status === "error") {
394
+ return state.value;
395
+ }
396
+ return void 0;
397
+ }
398
+
399
+ export {
400
+ CONFIG,
401
+ atom,
402
+ atomFamily,
403
+ createStore
404
+ };
405
+ //# sourceMappingURL=chunk-6WIZ6MFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/types.ts","../src/internal/atom.ts","../src/internal/family.ts","../src/internal/store.ts"],"sourcesContent":["export const CONFIG = Symbol(\"kvark.config\");\nexport const WRITABLE = Symbol(\"kvark.writable\");\nexport const FAMILY_LINK = Symbol(\"kvark.familyLink\");\n\nexport type AtomState<Value> =\n | { status: \"pending\"; value: undefined; error: undefined }\n | { status: \"stale\"; value: Value; error: undefined }\n | { status: \"fresh\"; value: Value; error: undefined }\n | { status: \"error\"; value: Value | undefined; error: unknown };\n\nexport type StalePolicy = \"keep\" | \"suspend\" | \"reset\";\n\nexport type AtomContext<Deps extends Record<string, Atom<unknown>>> = {\n get: <K extends keyof Deps>(key: K) => Promise<AtomValue<Deps[K]>>;\n signal: AbortSignal;\n};\n\nexport type AtomConfig<\n Value,\n Deps extends Record<string, Atom<unknown>>,\n Args extends readonly unknown[],\n> = {\n dependencies?: Deps;\n stalePolicy?: StalePolicy;\n debugLabel?: string;\n get: (ctx: AtomContext<Deps>) => Promise<Value>;\n set?: (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;\n};\n\nexport interface InternalAtomConfig<Value> {\n dependencies?: Record<string, Atom<unknown>>;\n stalePolicy?: StalePolicy;\n debugLabel?: string;\n get: (ctx: AtomContext<Record<string, Atom<unknown>>>) => Promise<Value>;\n set?: (\n ctx: AtomContext<Record<string, Atom<unknown>>>,\n ...args: readonly unknown[]\n ) => Promise<void>;\n}\n\nexport interface Atom<out Value> {\n readonly [CONFIG]: InternalAtomConfig<Value>;\n readonly debugLabel: string | undefined;\n}\n\nexport interface WritableAtom<\n out Value,\n in out Args extends readonly unknown[],\n> extends Atom<Value> {\n readonly [WRITABLE]: Args;\n}\n\nexport type AtomValue<A> = A extends Atom<infer V> ? V : never;\nexport type IsWritable<A> = A extends WritableAtom<unknown, readonly unknown[]> ? true : false;\nexport type AtomArgs<A> = A extends WritableAtom<unknown, infer Args> ? Args : never;\n","import type { Atom, AtomConfig, AtomContext, InternalAtomConfig, WritableAtom } from \"./types.js\";\nimport { CONFIG, WRITABLE } from \"./types.js\";\n\ntype WritableConfig<\n Value,\n Deps extends Record<string, Atom<unknown>>,\n Args extends readonly unknown[],\n> = AtomConfig<Value, Deps, Args> & {\n set: (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;\n};\n\ntype ReadonlyConfig<Value, Deps extends Record<string, Atom<unknown>>> = Omit<\n AtomConfig<Value, Deps, readonly []>,\n \"set\"\n>;\n\nexport function atom<\n Value,\n Deps extends Record<string, Atom<unknown>> = Record<never, never>,\n Args extends readonly unknown[] = readonly [],\n>(config: WritableConfig<Value, Deps, Args>): WritableAtom<Value, Args>;\n\nexport function atom<Value, Deps extends Record<string, Atom<unknown>> = Record<never, never>>(\n config: ReadonlyConfig<Value, Deps>,\n): Atom<Value>;\n\nexport function atom<\n Value,\n Deps extends Record<string, Atom<unknown>> = Record<never, never>,\n Args extends readonly unknown[] = readonly [],\n>(config: AtomConfig<Value, Deps, Args>): Atom<Value> {\n const internal: InternalAtomConfig<Value> = Object.create(null) as InternalAtomConfig<Value>;\n internal.get = config.get as InternalAtomConfig<Value>[\"get\"];\n\n if (config.debugLabel != null) {\n internal.debugLabel = config.debugLabel;\n }\n if (config.dependencies != null) {\n internal.dependencies = config.dependencies as Record<string, Atom<unknown>>;\n }\n if (config.stalePolicy != null) {\n internal.stalePolicy = config.stalePolicy;\n }\n if (config.set != null) {\n internal.set = config.set as NonNullable<InternalAtomConfig<Value>[\"set\"]>;\n }\n\n if (config.set != null) {\n return {\n [CONFIG]: internal,\n [WRITABLE]: [] as unknown as Args,\n debugLabel: config.debugLabel,\n } as unknown as Atom<Value>;\n }\n\n return {\n [CONFIG]: internal,\n debugLabel: config.debugLabel,\n } as Atom<Value>;\n}\n","import type { Atom, AtomContext, StalePolicy } from \"./types.js\";\nimport { FAMILY_LINK } from \"./types.js\";\nimport { atom } from \"./atom.js\";\n\nexport type AtomFamilyOptions<\n Param,\n Value,\n Deps extends Record<string, Atom<unknown>>,\n Args extends readonly unknown[],\n> = {\n dependencies?: (param: Param) => Deps;\n stalePolicy?: StalePolicy;\n cachePolicy?: \"keep-all\" | \"lru\";\n lruSize?: number;\n debugLabel?: string;\n get: (param: Param) => (ctx: AtomContext<Deps>) => Promise<Value>;\n set?: (param: Param) => (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;\n};\n\nexport interface AtomFamily<Param, Value> {\n (param: Param): Atom<Value>;\n invalidate(param: Param): void;\n invalidateAll(): void;\n remove(param: Param): void;\n getCache(): ReadonlyMap<Param, Atom<Value>>;\n}\n\nexport interface FamilyLink {\n invalidateAtom: (atom: Atom<unknown>) => void;\n registerStore: (cb: (atom: Atom<unknown>) => void) => () => void;\n}\n\nexport function getFamilyLink(target: Atom<unknown>): FamilyLink | undefined {\n return (target as unknown as Record<symbol, FamilyLink | undefined>)[FAMILY_LINK];\n}\n\nexport function atomFamily<\n Param,\n Value,\n Deps extends Record<string, Atom<unknown>> = Record<never, never>,\n Args extends readonly unknown[] = readonly [],\n>(options: AtomFamilyOptions<Param, Value, Deps, Args>): AtomFamily<Param, Value> {\n const cachePolicy = options.cachePolicy ?? \"keep-all\";\n const lruSize = options.lruSize ?? 100;\n const cache = new Map<Param, Atom<Value>>();\n const lruOrder: Param[] = [];\n const storeCallbacks = new Set<(atom: Atom<unknown>) => void>();\n\n const link: FamilyLink = {\n invalidateAtom(target: Atom<unknown>): void {\n for (const cb of storeCallbacks) {\n cb(target);\n }\n },\n registerStore(cb: (atom: Atom<unknown>) => void): () => void {\n storeCallbacks.add(cb);\n return () => {\n storeCallbacks.delete(cb);\n };\n },\n };\n\n function attachLink(target: Atom<Value>): void {\n (target as unknown as Record<symbol, FamilyLink>)[FAMILY_LINK] = link;\n }\n\n function getOrCreate(param: Param): Atom<Value> {\n const cached = cache.get(param);\n if (cached != null) {\n if (cachePolicy === \"lru\") {\n touchLru(param);\n }\n return cached;\n }\n\n const deps = options.dependencies?.(param);\n const getFn = options.get(param);\n const setFn = options.set?.(param);\n\n const label =\n options.debugLabel != null ? `${options.debugLabel}(${String(param)})` : undefined;\n\n const created = createAtom(label, deps, getFn, setFn);\n attachLink(created);\n cache.set(param, created);\n\n if (cachePolicy === \"lru\") {\n lruOrder.push(param);\n evictLru();\n }\n\n return created;\n }\n\n function createAtom(\n label: string | undefined,\n deps: Deps | undefined,\n getFn: (ctx: AtomContext<Deps>) => Promise<Value>,\n setFn: ((ctx: AtomContext<Deps>, ...args: Args) => Promise<void>) | undefined,\n ): Atom<Value> {\n const base = {\n get: getFn as (ctx: AtomContext<Deps>) => Promise<Value>,\n } as {\n debugLabel?: string;\n dependencies?: Deps;\n stalePolicy?: StalePolicy;\n get: (ctx: AtomContext<Deps>) => Promise<Value>;\n set?: (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;\n };\n\n if (label != null) {\n base.debugLabel = label;\n }\n if (deps != null) {\n base.dependencies = deps;\n }\n if (options.stalePolicy != null) {\n base.stalePolicy = options.stalePolicy;\n }\n\n if (setFn != null) {\n base.set = setFn;\n return atom(\n base as typeof base & {\n set: (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;\n },\n ) as Atom<Value>;\n }\n\n return atom(base as Omit<typeof base, \"set\">);\n }\n\n function touchLru(param: Param): void {\n const idx = lruOrder.indexOf(param);\n if (idx !== -1) {\n lruOrder.splice(idx, 1);\n }\n lruOrder.push(param);\n }\n\n function evictLru(): void {\n while (lruOrder.length > lruSize) {\n const evicted = lruOrder.shift();\n if (evicted !== undefined) {\n cache.delete(evicted);\n }\n }\n }\n\n const family = getOrCreate as AtomFamily<Param, Value>;\n\n family.invalidate = (param: Param): void => {\n const cached = cache.get(param);\n if (cached != null) {\n link.invalidateAtom(cached);\n }\n };\n\n family.invalidateAll = (): void => {\n for (const cached of cache.values()) {\n link.invalidateAtom(cached);\n }\n };\n\n family.remove = (param: Param): void => {\n cache.delete(param);\n if (cachePolicy === \"lru\") {\n const idx = lruOrder.indexOf(param);\n if (idx !== -1) {\n lruOrder.splice(idx, 1);\n }\n }\n };\n\n family.getCache = (): ReadonlyMap<Param, Atom<Value>> => cache;\n\n return family;\n}\n","import type {\n Atom,\n AtomContext,\n AtomState,\n InternalAtomConfig,\n StalePolicy,\n WritableAtom,\n} from \"./types.js\";\nimport { CONFIG } from \"./types.js\";\nimport { getFamilyLink } from \"./family.js\";\n\ntype AtomEntry<V> = {\n state: AtomState<V>;\n version: number;\n promise: Promise<V> | null;\n listeners: Set<() => void>;\n};\n\nexport interface StoreClient {\n get<V>(atom: Atom<V>): Promise<V>;\n set<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>, ...args: A): Promise<void>;\n invalidate(atom: Atom<unknown>): void;\n invalidateMany(atoms: ReadonlyArray<Atom<unknown>>): void;\n subscribe<V>(atom: Atom<V>, listener: (state: AtomState<V>) => void): () => void;\n}\n\nconst PENDING_STATE: AtomState<never> = {\n status: \"pending\",\n value: undefined,\n error: undefined,\n} as AtomState<never>;\n\nexport class Store {\n readonly #atoms = new WeakMap<Atom<unknown>, AtomEntry<unknown>>();\n readonly #rdeps = new Map<Atom<unknown>, Set<Atom<unknown>>>();\n readonly #pending = new Set<Atom<unknown>>();\n readonly #controllers = new WeakMap<Atom<unknown>, AbortController>();\n readonly #familyUnsubs = new Set<() => void>();\n readonly #registeredFamilies = new WeakSet<object>();\n #client: StoreClient | null = null;\n\n #getOrCreate<V>(atom: Atom<V>): AtomEntry<V> {\n let entry = this.#atoms.get(atom) as AtomEntry<V> | undefined;\n if (entry == null) {\n entry = {\n state: PENDING_STATE as AtomState<V>,\n version: 0,\n promise: null,\n listeners: new Set(),\n };\n this.#atoms.set(atom, entry as AtomEntry<unknown>);\n }\n return entry;\n }\n\n resolve<V>(atom: Atom<V>): Promise<V> {\n const entry = this.#getOrCreate(atom);\n if (entry.promise != null) {\n return entry.promise;\n }\n\n const promise = this.#runGet(atom).finally(() => {\n const e = this.#atoms.get(atom) as AtomEntry<V> | undefined;\n if (e != null) {\n e.promise = null;\n }\n });\n entry.promise = promise;\n return promise;\n }\n\n async #runGet<V>(atom: Atom<V>): Promise<V> {\n const config: InternalAtomConfig<V> = atom[CONFIG];\n const dependencies = config.dependencies;\n const stalePolicy: StalePolicy = config.stalePolicy ?? \"keep\";\n const entry = this.#getOrCreate(atom);\n\n if (dependencies != null) {\n await Promise.all(Object.values(dependencies).map((dep) => this.resolve(dep)));\n this.#registerRdeps(atom, dependencies);\n }\n\n const ctx = this.#makeCtx(atom, dependencies ?? {});\n\n try {\n const value = await config.get(ctx);\n entry.state = { status: \"fresh\", value, error: undefined };\n entry.version++;\n this.#scheduleNotify(atom);\n return value;\n } catch (e: unknown) {\n if (isAbortError(e)) {\n throw e;\n }\n const prevValue = extractPreviousValue<V>(entry.state);\n entry.state = {\n status: \"error\",\n value: stalePolicy === \"keep\" ? prevValue : undefined,\n error: e,\n } as AtomState<V>;\n entry.version++;\n this.#scheduleNotify(atom);\n throw e;\n }\n }\n\n invalidate(atom: Atom<unknown>): void {\n this.#markStale(atom);\n this.#pending.add(atom);\n if (this.#pending.size === 1) {\n queueMicrotask(() => {\n this.#flushPending();\n });\n }\n }\n\n invalidateMany(atoms: ReadonlyArray<Atom<unknown>>): void {\n for (const a of atoms) {\n this.#markStale(a);\n this.#pending.add(a);\n }\n if (this.#pending.size > 0) {\n queueMicrotask(() => {\n this.#flushPending();\n });\n }\n }\n\n #markStale(atom: Atom<unknown>, visited = new Set<Atom<unknown>>()): void {\n if (visited.has(atom)) {\n return;\n }\n visited.add(atom);\n\n const entry = this.#atoms.get(atom);\n if (entry != null) {\n const stalePolicy: StalePolicy = atom[CONFIG].stalePolicy ?? \"keep\";\n\n if (entry.state.status === \"fresh\") {\n if (stalePolicy === \"reset\") {\n entry.state = PENDING_STATE;\n } else {\n entry.state = { ...entry.state, status: \"stale\" };\n }\n }\n\n entry.promise = null;\n this.#controllers.get(atom)?.abort();\n }\n\n const rdeps = this.#rdeps.get(atom);\n if (rdeps != null) {\n for (const dep of rdeps) {\n this.#markStale(dep, visited);\n }\n }\n }\n\n #flushPending(): void {\n const batch = [...this.#pending];\n this.#pending.clear();\n const toNotify = new Set<() => void>();\n for (const a of batch) {\n const entry = this.#atoms.get(a);\n if (entry != null) {\n for (const l of entry.listeners) {\n toNotify.add(l);\n }\n }\n }\n for (const l of toNotify) {\n l();\n }\n }\n\n #scheduleNotify(atom: Atom<unknown>): void {\n this.#pending.add(atom);\n if (this.#pending.size === 1) {\n queueMicrotask(() => {\n this.#flushPending();\n });\n }\n }\n\n #registerRdeps(atom: Atom<unknown>, deps: Record<string, Atom<unknown>>): void {\n for (const dep of Object.values(deps)) {\n let set = this.#rdeps.get(dep);\n if (set == null) {\n set = new Set();\n this.#rdeps.set(dep, set);\n }\n set.add(atom);\n }\n }\n\n #makeCtx(\n atom: Atom<unknown>,\n deps: Record<string, Atom<unknown>>,\n ): AtomContext<Record<string, Atom<unknown>>> {\n this.#controllers.get(atom)?.abort();\n const controller = new AbortController();\n this.#controllers.set(atom, controller);\n\n return {\n signal: controller.signal,\n get: async (key: string) => {\n const dep = deps[key];\n if (dep == null) {\n throw new Error(`Unknown dependency key: \"${String(key)}\"`);\n }\n return this.resolve(dep);\n },\n };\n }\n\n subscribe<V>(atom: Atom<V>, listener: () => void): () => void {\n const entry = this.#getOrCreate(atom);\n entry.listeners.add(listener);\n\n this.#autoRegisterFamily(atom);\n\n if (entry.state.status === \"pending\" && entry.promise == null) {\n void this.resolve(atom);\n }\n\n return () => {\n entry.listeners.delete(listener);\n };\n }\n\n #autoRegisterFamily(atom: Atom<unknown>): void {\n const link = getFamilyLink(atom);\n if (link == null) {\n return;\n }\n if (this.#registeredFamilies.has(link)) {\n return;\n }\n this.#registeredFamilies.add(link);\n const unsub = link.registerStore((target: Atom<unknown>) => {\n this.invalidate(target);\n });\n this.#familyUnsubs.add(unsub);\n }\n\n getSnapshot<V>(atom: Atom<V>): AtomState<V> {\n return this.#getOrCreate(atom).state;\n }\n\n getServerSnapshot<V>(atom: Atom<V>): AtomState<V> {\n const entry = this.#atoms.get(atom) as AtomEntry<V> | undefined;\n if (entry != null) {\n return entry.state;\n }\n return PENDING_STATE as AtomState<V>;\n }\n\n async set<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>, ...args: A): Promise<void> {\n const config: InternalAtomConfig<V> = atom[CONFIG];\n if (config.set == null) {\n throw new Error(\n `Atom${atom.debugLabel != null ? ` \"${atom.debugLabel}\"` : \"\"} is not writable`,\n );\n }\n\n const deps = config.dependencies ?? {};\n const ctx = this.#makeCtx(atom, deps);\n await config.set(ctx, ...args);\n this.invalidate(atom);\n }\n\n getClient(): StoreClient {\n if (this.#client != null) {\n return this.#client;\n }\n\n const store = this;\n this.#client = {\n get<V>(atom: Atom<V>): Promise<V> {\n return store.resolve(atom);\n },\n set<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>, ...args: A): Promise<void> {\n return store.set(atom, ...args);\n },\n invalidate(atom: Atom<unknown>): void {\n store.invalidate(atom);\n },\n invalidateMany(atoms: ReadonlyArray<Atom<unknown>>): void {\n store.invalidateMany(atoms);\n },\n subscribe<V>(atom: Atom<V>, listener: (state: AtomState<V>) => void): () => void {\n return store.subscribe(atom, () => {\n listener(store.getSnapshot(atom));\n });\n },\n };\n return this.#client;\n }\n}\n\nexport function createStore(): Store {\n return new Store();\n}\n\nfunction isAbortError(e: unknown): boolean {\n return e instanceof DOMException && e.name === \"AbortError\";\n}\n\nfunction extractPreviousValue<V>(state: AtomState<V>): V | undefined {\n if (state.status === \"stale\" || state.status === \"fresh\") {\n return state.value;\n }\n if (state.status === \"error\") {\n return state.value;\n }\n return undefined;\n}\n"],"mappings":"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","names":["atom"]}
@@ -0,0 +1,23 @@
1
+ import { A as Atom, S as StalePolicy, a as AtomContext } from './store-DwEjhc31.js';
2
+ export { b as AtomConfig, c as AtomState, d as AtomValue, W as WritableAtom, e as createStore } from './store-DwEjhc31.js';
3
+ export { atom } from './index.js';
4
+
5
+ type AtomFamilyOptions<Param, Value, Deps extends Record<string, Atom<unknown>>, Args extends readonly unknown[]> = {
6
+ dependencies?: (param: Param) => Deps;
7
+ stalePolicy?: StalePolicy;
8
+ cachePolicy?: "keep-all" | "lru";
9
+ lruSize?: number;
10
+ debugLabel?: string;
11
+ get: (param: Param) => (ctx: AtomContext<Deps>) => Promise<Value>;
12
+ set?: (param: Param) => (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;
13
+ };
14
+ interface AtomFamily<Param, Value> {
15
+ (param: Param): Atom<Value>;
16
+ invalidate(param: Param): void;
17
+ invalidateAll(): void;
18
+ remove(param: Param): void;
19
+ getCache(): ReadonlyMap<Param, Atom<Value>>;
20
+ }
21
+ declare function atomFamily<Param, Value, Deps extends Record<string, Atom<unknown>> = Record<never, never>, Args extends readonly unknown[] = readonly []>(options: AtomFamilyOptions<Param, Value, Deps, Args>): AtomFamily<Param, Value>;
22
+
23
+ export { Atom, AtomContext, type AtomFamily, type AtomFamilyOptions, StalePolicy, atomFamily };
package/dist/family.js ADDED
@@ -0,0 +1,11 @@
1
+ import {
2
+ atom,
3
+ atomFamily,
4
+ createStore
5
+ } from "./chunk-6WIZ6MFN.js";
6
+ export {
7
+ atom,
8
+ atomFamily,
9
+ createStore
10
+ };
11
+ //# sourceMappingURL=family.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,11 @@
1
+ import { A as Atom, b as AtomConfig, a as AtomContext, W as WritableAtom } from './store-DwEjhc31.js';
2
+ export { h as AtomArgs, c as AtomState, d as AtomValue, I as IsWritable, S as StalePolicy, g as StoreClient, e as createStore } from './store-DwEjhc31.js';
3
+
4
+ type WritableConfig<Value, Deps extends Record<string, Atom<unknown>>, Args extends readonly unknown[]> = AtomConfig<Value, Deps, Args> & {
5
+ set: (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;
6
+ };
7
+ type ReadonlyConfig<Value, Deps extends Record<string, Atom<unknown>>> = Omit<AtomConfig<Value, Deps, readonly []>, "set">;
8
+ declare function atom<Value, Deps extends Record<string, Atom<unknown>> = Record<never, never>, Args extends readonly unknown[] = readonly []>(config: WritableConfig<Value, Deps, Args>): WritableAtom<Value, Args>;
9
+ declare function atom<Value, Deps extends Record<string, Atom<unknown>> = Record<never, never>>(config: ReadonlyConfig<Value, Deps>): Atom<Value>;
10
+
11
+ export { Atom, AtomConfig, AtomContext, WritableAtom, atom };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import {
2
+ atom,
3
+ createStore
4
+ } from "./chunk-6WIZ6MFN.js";
5
+ export {
6
+ atom,
7
+ createStore
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { f as Store, A as Atom, W as WritableAtom, g as StoreClient } from '../store-DwEjhc31.js';
3
+
4
+ type ProviderProps = {
5
+ store: Store;
6
+ children: ReactNode;
7
+ };
8
+ declare function Provider({ store, children }: ProviderProps): ReactNode;
9
+ declare function useStore(): Store;
10
+
11
+ type ObservedValue<V> = {
12
+ value: V;
13
+ isStale: boolean;
14
+ error: unknown;
15
+ };
16
+ declare function useAtomValue<V>(atom: Atom<V>): V;
17
+ declare function useAtomValue<V>(atom: Atom<V>, opts: {
18
+ observe: true;
19
+ }): ObservedValue<V>;
20
+
21
+ declare function useSetAtom<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>): (...args: A) => Promise<void>;
22
+
23
+ declare function useAtom<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>): readonly [V, (...args: A) => Promise<void>];
24
+
25
+ declare function useAtomCallback<R>(callback: (ctx: StoreClient) => Promise<R>): () => Promise<R>;
26
+
27
+ export { Provider, useAtom, useAtomCallback, useAtomValue, useSetAtom, useStore };
@@ -0,0 +1,87 @@
1
+ import {
2
+ CONFIG
3
+ } from "../chunk-6WIZ6MFN.js";
4
+
5
+ // src/react/provider.tsx
6
+ import { createContext, useContext } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var StoreContext = createContext(null);
9
+ function Provider({ store, children }) {
10
+ return /* @__PURE__ */ jsx(StoreContext, { value: store, children });
11
+ }
12
+ function useStore() {
13
+ const store = useContext(StoreContext);
14
+ if (store == null) {
15
+ throw new Error("useStore must be used within a <Provider>");
16
+ }
17
+ return store;
18
+ }
19
+
20
+ // src/react/use-atom-value.ts
21
+ import { useSyncExternalStore } from "react";
22
+ function useAtomValue(atom, opts) {
23
+ const store = useStore();
24
+ const snapshot = useSyncExternalStore(
25
+ (notify) => store.subscribe(atom, notify),
26
+ () => store.getSnapshot(atom),
27
+ () => store.getServerSnapshot(atom)
28
+ );
29
+ const stalePolicy = atom[CONFIG].stalePolicy ?? "keep";
30
+ if (snapshot.status === "pending") {
31
+ throw store.resolve(atom);
32
+ }
33
+ if (snapshot.status === "stale") {
34
+ if (stalePolicy === "suspend") {
35
+ throw store.resolve(atom);
36
+ }
37
+ void store.resolve(atom);
38
+ if (opts?.observe === true) {
39
+ return { value: snapshot.value, isStale: true, error: void 0 };
40
+ }
41
+ return snapshot.value;
42
+ }
43
+ if (snapshot.status === "error") {
44
+ if (opts?.observe === true && snapshot.value !== void 0) {
45
+ return {
46
+ value: snapshot.value,
47
+ isStale: false,
48
+ error: snapshot.error
49
+ };
50
+ }
51
+ throw snapshot.error;
52
+ }
53
+ if (opts?.observe === true) {
54
+ return { value: snapshot.value, isStale: false, error: void 0 };
55
+ }
56
+ return snapshot.value;
57
+ }
58
+
59
+ // src/react/use-set-atom.ts
60
+ import { useCallback } from "react";
61
+ function useSetAtom(atom) {
62
+ const store = useStore();
63
+ return useCallback((...args) => store.set(atom, ...args), [store, atom]);
64
+ }
65
+
66
+ // src/react/use-atom.ts
67
+ function useAtom(atom) {
68
+ const value = useAtomValue(atom);
69
+ const setter = useSetAtom(atom);
70
+ return [value, setter];
71
+ }
72
+
73
+ // src/react/use-atom-callback.ts
74
+ import { useCallback as useCallback2 } from "react";
75
+ function useAtomCallback(callback) {
76
+ const store = useStore();
77
+ return useCallback2(() => callback(store.getClient()), [store, callback]);
78
+ }
79
+ export {
80
+ Provider,
81
+ useAtom,
82
+ useAtomCallback,
83
+ useAtomValue,
84
+ useSetAtom,
85
+ useStore
86
+ };
87
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/provider.tsx","../../src/react/use-atom-value.ts","../../src/react/use-set-atom.ts","../../src/react/use-atom.ts","../../src/react/use-atom-callback.ts"],"sourcesContent":["import { createContext, useContext, type ReactNode } from \"react\";\nimport { Store } from \"../internal/store.js\";\n\nconst StoreContext = createContext<Store | null>(null);\n\nexport type ProviderProps = {\n store: Store;\n children: ReactNode;\n};\n\nexport function Provider({ store, children }: ProviderProps): ReactNode {\n return <StoreContext value={store}>{children}</StoreContext>;\n}\n\nexport function useStore(): Store {\n const store = useContext(StoreContext);\n if (store == null) {\n throw new Error(\"useStore must be used within a <Provider>\");\n }\n return store;\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { Atom, AtomState, StalePolicy } from \"../internal/types.js\";\nimport { CONFIG } from \"../internal/types.js\";\nimport { useStore } from \"./provider.js\";\n\ntype ObservedValue<V> = {\n value: V;\n isStale: boolean;\n error: unknown;\n};\n\nexport function useAtomValue<V>(atom: Atom<V>): V;\nexport function useAtomValue<V>(atom: Atom<V>, opts: { observe: true }): ObservedValue<V>;\nexport function useAtomValue<V>(atom: Atom<V>, opts?: { observe: true }): V | ObservedValue<V> {\n const store = useStore();\n\n const snapshot: AtomState<V> = useSyncExternalStore(\n (notify) => store.subscribe(atom, notify),\n () => store.getSnapshot(atom),\n () => store.getServerSnapshot(atom),\n );\n\n const stalePolicy: StalePolicy = atom[CONFIG].stalePolicy ?? \"keep\";\n\n if (snapshot.status === \"pending\") {\n throw store.resolve(atom);\n }\n\n if (snapshot.status === \"stale\") {\n if (stalePolicy === \"suspend\") {\n throw store.resolve(atom);\n }\n // \"keep\" or \"reset\" while stale: trigger background revalidation\n void store.resolve(atom);\n\n if (opts?.observe === true) {\n return { value: snapshot.value, isStale: true, error: undefined };\n }\n return snapshot.value;\n }\n\n if (snapshot.status === \"error\") {\n if (opts?.observe === true && snapshot.value !== undefined) {\n return {\n value: snapshot.value as V,\n isStale: false,\n error: snapshot.error,\n };\n }\n throw snapshot.error;\n }\n\n // status === \"fresh\"\n if (opts?.observe === true) {\n return { value: snapshot.value, isStale: false, error: undefined };\n }\n return snapshot.value;\n}\n","import { useCallback } from \"react\";\nimport type { WritableAtom } from \"../internal/types.js\";\nimport { useStore } from \"./provider.js\";\n\nexport function useSetAtom<V, A extends readonly unknown[]>(\n atom: WritableAtom<V, A>,\n): (...args: A) => Promise<void> {\n const store = useStore();\n return useCallback((...args: A): Promise<void> => store.set(atom, ...args), [store, atom]);\n}\n","import type { WritableAtom } from \"../internal/types.js\";\nimport { useAtomValue } from \"./use-atom-value.js\";\nimport { useSetAtom } from \"./use-set-atom.js\";\n\nexport function useAtom<V, A extends readonly unknown[]>(\n atom: WritableAtom<V, A>,\n): readonly [V, (...args: A) => Promise<void>] {\n const value = useAtomValue(atom);\n const setter = useSetAtom(atom);\n return [value, setter] as const;\n}\n","import { useCallback } from \"react\";\nimport type { StoreClient } from \"../internal/store.js\";\nimport { useStore } from \"./provider.js\";\n\nexport function useAtomCallback<R>(callback: (ctx: StoreClient) => Promise<R>): () => Promise<R> {\n const store = useStore();\n return useCallback((): Promise<R> => callback(store.getClient()), [store, callback]);\n}\n"],"mappings":";;;;;AAAA,SAAS,eAAe,kBAAkC;AAWjD;AART,IAAM,eAAe,cAA4B,IAAI;AAO9C,SAAS,SAAS,EAAE,OAAO,SAAS,GAA6B;AACtE,SAAO,oBAAC,gBAAa,OAAO,OAAQ,UAAS;AAC/C;AAEO,SAAS,WAAkB;AAChC,QAAM,QAAQ,WAAW,YAAY;AACrC,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,SAAO;AACT;;;ACpBA,SAAS,4BAA4B;AAa9B,SAAS,aAAgB,MAAe,MAAgD;AAC7F,QAAM,QAAQ,SAAS;AAEvB,QAAM,WAAyB;AAAA,IAC7B,CAAC,WAAW,MAAM,UAAU,MAAM,MAAM;AAAA,IACxC,MAAM,MAAM,YAAY,IAAI;AAAA,IAC5B,MAAM,MAAM,kBAAkB,IAAI;AAAA,EACpC;AAEA,QAAM,cAA2B,KAAK,MAAM,EAAE,eAAe;AAE7D,MAAI,SAAS,WAAW,WAAW;AACjC,UAAM,MAAM,QAAQ,IAAI;AAAA,EAC1B;AAEA,MAAI,SAAS,WAAW,SAAS;AAC/B,QAAI,gBAAgB,WAAW;AAC7B,YAAM,MAAM,QAAQ,IAAI;AAAA,IAC1B;AAEA,SAAK,MAAM,QAAQ,IAAI;AAEvB,QAAI,MAAM,YAAY,MAAM;AAC1B,aAAO,EAAE,OAAO,SAAS,OAAO,SAAS,MAAM,OAAO,OAAU;AAAA,IAClE;AACA,WAAO,SAAS;AAAA,EAClB;AAEA,MAAI,SAAS,WAAW,SAAS;AAC/B,QAAI,MAAM,YAAY,QAAQ,SAAS,UAAU,QAAW;AAC1D,aAAO;AAAA,QACL,OAAO,SAAS;AAAA,QAChB,SAAS;AAAA,QACT,OAAO,SAAS;AAAA,MAClB;AAAA,IACF;AACA,UAAM,SAAS;AAAA,EACjB;AAGA,MAAI,MAAM,YAAY,MAAM;AAC1B,WAAO,EAAE,OAAO,SAAS,OAAO,SAAS,OAAO,OAAO,OAAU;AAAA,EACnE;AACA,SAAO,SAAS;AAClB;;;ACzDA,SAAS,mBAAmB;AAIrB,SAAS,WACd,MAC+B;AAC/B,QAAM,QAAQ,SAAS;AACvB,SAAO,YAAY,IAAI,SAA2B,MAAM,IAAI,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC;AAC3F;;;ACLO,SAAS,QACd,MAC6C;AAC7C,QAAM,QAAQ,aAAa,IAAI;AAC/B,QAAM,SAAS,WAAW,IAAI;AAC9B,SAAO,CAAC,OAAO,MAAM;AACvB;;;ACVA,SAAS,eAAAA,oBAAmB;AAIrB,SAAS,gBAAmB,UAA8D;AAC/F,QAAM,QAAQ,SAAS;AACvB,SAAOC,aAAY,MAAkB,SAAS,MAAM,UAAU,CAAC,GAAG,CAAC,OAAO,QAAQ,CAAC;AACrF;","names":["useCallback","useCallback"]}
@@ -0,0 +1,70 @@
1
+ declare const CONFIG: unique symbol;
2
+ declare const WRITABLE: unique symbol;
3
+ type AtomState<Value> = {
4
+ status: "pending";
5
+ value: undefined;
6
+ error: undefined;
7
+ } | {
8
+ status: "stale";
9
+ value: Value;
10
+ error: undefined;
11
+ } | {
12
+ status: "fresh";
13
+ value: Value;
14
+ error: undefined;
15
+ } | {
16
+ status: "error";
17
+ value: Value | undefined;
18
+ error: unknown;
19
+ };
20
+ type StalePolicy = "keep" | "suspend" | "reset";
21
+ type AtomContext<Deps extends Record<string, Atom<unknown>>> = {
22
+ get: <K extends keyof Deps>(key: K) => Promise<AtomValue<Deps[K]>>;
23
+ signal: AbortSignal;
24
+ };
25
+ type AtomConfig<Value, Deps extends Record<string, Atom<unknown>>, Args extends readonly unknown[]> = {
26
+ dependencies?: Deps;
27
+ stalePolicy?: StalePolicy;
28
+ debugLabel?: string;
29
+ get: (ctx: AtomContext<Deps>) => Promise<Value>;
30
+ set?: (ctx: AtomContext<Deps>, ...args: Args) => Promise<void>;
31
+ };
32
+ interface InternalAtomConfig<Value> {
33
+ dependencies?: Record<string, Atom<unknown>>;
34
+ stalePolicy?: StalePolicy;
35
+ debugLabel?: string;
36
+ get: (ctx: AtomContext<Record<string, Atom<unknown>>>) => Promise<Value>;
37
+ set?: (ctx: AtomContext<Record<string, Atom<unknown>>>, ...args: readonly unknown[]) => Promise<void>;
38
+ }
39
+ interface Atom<out Value> {
40
+ readonly [CONFIG]: InternalAtomConfig<Value>;
41
+ readonly debugLabel: string | undefined;
42
+ }
43
+ interface WritableAtom<out Value, in out Args extends readonly unknown[]> extends Atom<Value> {
44
+ readonly [WRITABLE]: Args;
45
+ }
46
+ type AtomValue<A> = A extends Atom<infer V> ? V : never;
47
+ type IsWritable<A> = A extends WritableAtom<unknown, readonly unknown[]> ? true : false;
48
+ type AtomArgs<A> = A extends WritableAtom<unknown, infer Args> ? Args : never;
49
+
50
+ interface StoreClient {
51
+ get<V>(atom: Atom<V>): Promise<V>;
52
+ set<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>, ...args: A): Promise<void>;
53
+ invalidate(atom: Atom<unknown>): void;
54
+ invalidateMany(atoms: ReadonlyArray<Atom<unknown>>): void;
55
+ subscribe<V>(atom: Atom<V>, listener: (state: AtomState<V>) => void): () => void;
56
+ }
57
+ declare class Store {
58
+ #private;
59
+ resolve<V>(atom: Atom<V>): Promise<V>;
60
+ invalidate(atom: Atom<unknown>): void;
61
+ invalidateMany(atoms: ReadonlyArray<Atom<unknown>>): void;
62
+ subscribe<V>(atom: Atom<V>, listener: () => void): () => void;
63
+ getSnapshot<V>(atom: Atom<V>): AtomState<V>;
64
+ getServerSnapshot<V>(atom: Atom<V>): AtomState<V>;
65
+ set<V, A extends readonly unknown[]>(atom: WritableAtom<V, A>, ...args: A): Promise<void>;
66
+ getClient(): StoreClient;
67
+ }
68
+ declare function createStore(): Store;
69
+
70
+ export { type Atom as A, type IsWritable as I, type StalePolicy as S, type WritableAtom as W, type AtomContext as a, type AtomConfig as b, type AtomState as c, type AtomValue as d, createStore as e, Store as f, type StoreClient as g, type AtomArgs as h };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@kdeveloper/kvark",
3
+ "version": "0.1.1",
4
+ "description": "Atomic state management with explicit dependency graphs",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react/index.d.ts",
17
+ "import": "./dist/react/index.js"
18
+ },
19
+ "./family": {
20
+ "types": "./dist/family.d.ts",
21
+ "import": "./dist/family.js"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "lint": "oxlint --deny-warnings",
27
+ "lint:fix": "oxlint --fix --deny-warnings",
28
+ "lint:types": "tsc --noEmit",
29
+ "fmt": "oxfmt .",
30
+ "fmt:check": "oxfmt --check .",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest"
33
+ },
34
+ "devDependencies": {
35
+ "@testing-library/dom": "^10.4.1",
36
+ "@testing-library/react": "^16.3.2",
37
+ "@types/react": "^19.2.14",
38
+ "@types/react-dom": "^19.2.3",
39
+ "jsdom": "^29.0.1",
40
+ "oxfmt": "^0.42.0",
41
+ "oxlint": "^1.57.0",
42
+ "react": ">=18",
43
+ "react-dom": "^19.2.4",
44
+ "tsup": "^8.5.1",
45
+ "typescript": "^6.0.2",
46
+ "vitest": "^4.1.2"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=18"
50
+ },
51
+ "engines": {
52
+ "node": ">=20"
53
+ },
54
+ "packageManager": "pnpm@10.33.0"
55
+ }