@vladstudio/kstate 0.1.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 (74) hide show
  1. package/README.md +488 -0
  2. package/dist/adapters/api.d.ts +18 -0
  3. package/dist/adapters/api.d.ts.map +1 -0
  4. package/dist/adapters/api.js +31 -0
  5. package/dist/adapters/api.js.map +1 -0
  6. package/dist/adapters/index.d.ts +5 -0
  7. package/dist/adapters/index.d.ts.map +1 -0
  8. package/dist/adapters/index.js +5 -0
  9. package/dist/adapters/index.js.map +1 -0
  10. package/dist/adapters/local.d.ts +20 -0
  11. package/dist/adapters/local.d.ts.map +1 -0
  12. package/dist/adapters/local.js +48 -0
  13. package/dist/adapters/local.js.map +1 -0
  14. package/dist/adapters/queuedApi.d.ts +18 -0
  15. package/dist/adapters/queuedApi.d.ts.map +1 -0
  16. package/dist/adapters/queuedApi.js +41 -0
  17. package/dist/adapters/queuedApi.js.map +1 -0
  18. package/dist/adapters/sse.d.ts +15 -0
  19. package/dist/adapters/sse.d.ts.map +1 -0
  20. package/dist/adapters/sse.js +50 -0
  21. package/dist/adapters/sse.js.map +1 -0
  22. package/dist/config.d.ts +4 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +8 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/core/cache.d.ts +8 -0
  27. package/dist/core/cache.d.ts.map +1 -0
  28. package/dist/core/cache.js +27 -0
  29. package/dist/core/cache.js.map +1 -0
  30. package/dist/core/network.d.ts +16 -0
  31. package/dist/core/network.d.ts.map +1 -0
  32. package/dist/core/network.js +51 -0
  33. package/dist/core/network.js.map +1 -0
  34. package/dist/core/proxy.d.ts +11 -0
  35. package/dist/core/proxy.d.ts.map +1 -0
  36. package/dist/core/proxy.js +146 -0
  37. package/dist/core/proxy.js.map +1 -0
  38. package/dist/core/subscribers.d.ts +3 -0
  39. package/dist/core/subscribers.d.ts.map +1 -0
  40. package/dist/core/subscribers.js +50 -0
  41. package/dist/core/subscribers.js.map +1 -0
  42. package/dist/hooks/useStore.d.ts +2 -0
  43. package/dist/hooks/useStore.d.ts.map +1 -0
  44. package/dist/hooks/useStore.js +64 -0
  45. package/dist/hooks/useStore.js.map +1 -0
  46. package/dist/hooks/useStoreStatus.d.ts +8 -0
  47. package/dist/hooks/useStoreStatus.d.ts.map +1 -0
  48. package/dist/hooks/useStoreStatus.js +13 -0
  49. package/dist/hooks/useStoreStatus.js.map +1 -0
  50. package/dist/index.d.ts +9 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +12 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/stores/computed.d.ts +14 -0
  55. package/dist/stores/computed.d.ts.map +1 -0
  56. package/dist/stores/computed.js +39 -0
  57. package/dist/stores/computed.js.map +1 -0
  58. package/dist/stores/createSetStore.d.ts +5 -0
  59. package/dist/stores/createSetStore.d.ts.map +1 -0
  60. package/dist/stores/createSetStore.js +130 -0
  61. package/dist/stores/createSetStore.js.map +1 -0
  62. package/dist/stores/createStore.d.ts +3 -0
  63. package/dist/stores/createStore.d.ts.map +1 -0
  64. package/dist/stores/createStore.js +98 -0
  65. package/dist/stores/createStore.js.map +1 -0
  66. package/dist/sync/api.d.ts +16 -0
  67. package/dist/sync/api.d.ts.map +1 -0
  68. package/dist/sync/api.js +106 -0
  69. package/dist/sync/api.js.map +1 -0
  70. package/dist/types.d.ts +116 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -0
  73. package/dist/types.js.map +1 -0
  74. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,488 @@
1
+ # KState
2
+
3
+ Minimal, type-safe state management for React SPAs with fine-grained reactivity, optimistic updates, and flexible adapters.
4
+
5
+ ## Features
6
+
7
+ - **Tiny bundle** — No external dependencies except React
8
+ - **Fine-grained reactivity** — Components re-render only when subscribed values change
9
+ - **Optimistic updates** — UI updates instantly, auto-rollback on failure
10
+ - **Flexible adapters** — Mix REST API, localStorage, and SSE in any combination
11
+ - **TypeScript-native** — Full type inference, zero manual annotations
12
+
13
+ ## Getting Started
14
+
15
+ ```bash
16
+ bun add kstate
17
+ ```
18
+
19
+ ```tsx
20
+ import { configureKState } from 'kstate'
21
+
22
+ configureKState({
23
+ baseUrl: 'https://api.example.com',
24
+ getHeaders: async () => {
25
+ const token = localStorage.getItem('token')
26
+ return token ? { Authorization: `Bearer ${token}` } : {}
27
+ },
28
+ onError: (error, operation, meta) => {
29
+ console.error(`[KState] ${operation} failed:`, error.message)
30
+ }
31
+ })
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Stores
37
+
38
+ ### `createSetStore` — Array of items
39
+
40
+ For managing arrays of objects with `id`:
41
+
42
+ ```tsx
43
+ import { createSetStore, api } from 'kstate'
44
+
45
+ interface User {
46
+ id: string
47
+ name: string
48
+ email: string
49
+ }
50
+
51
+ const users = createSetStore<User>({
52
+ ...api({ list: '/users', item: '/users/:id' }),
53
+ })
54
+ ```
55
+
56
+ **Operations:**
57
+
58
+ ```tsx
59
+ await users.get() // Fetch all
60
+ await users.get({ role: 'admin' }) // With query params
61
+ await users.getOne({ id: '123' }) // Fetch one
62
+ await users.create({ name: 'Jane' }) // Create (returns new item)
63
+ await users.patch({ id: '123', name: 'Jo' }) // Partial update (optimistic)
64
+ await users.delete({ id: '123' }) // Delete (optimistic)
65
+ users.clear() // Clear local state
66
+ users.dispose() // Cleanup listeners
67
+ ```
68
+
69
+ ### `createStore` — Single value
70
+
71
+ For managing a single object:
72
+
73
+ ```tsx
74
+ import { createStore, local } from 'kstate'
75
+
76
+ interface Settings {
77
+ theme: 'light' | 'dark'
78
+ language: string
79
+ }
80
+
81
+ const settings = createStore<Settings>(local('settings', { theme: 'light', language: 'en' }))
82
+ ```
83
+
84
+ **Operations:**
85
+
86
+ ```tsx
87
+ await profile.get() // Fetch
88
+ await profile.set({ ... }) // Full replace (optimistic)
89
+ await profile.patch({ name: 'Jane' }) // Partial update (optimistic)
90
+ profile.clear() // Clear
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Adapters
96
+
97
+ Adapters provide sync logic. Mix them per-operation:
98
+
99
+ ### `api({ list, item?, dataKey?, requestKey? })` — REST API
100
+
101
+ ```tsx
102
+ import { api } from 'kstate'
103
+
104
+ // RESTful API - item defaults to list + '/:id'
105
+ const users = createSetStore<User>({
106
+ ...api({ list: '/users' }), // item: '/users/:id' inferred
107
+ })
108
+
109
+ // Explicit item endpoint
110
+ const posts = createSetStore<Post>({
111
+ ...api({ list: '/posts', item: '/posts/:id' }),
112
+ })
113
+
114
+ // Query-based API
115
+ const products = createSetStore<Product>({
116
+ ...api({ list: '/products', item: '/products?id=:id' }),
117
+ })
118
+
119
+ // Nested resources
120
+ const comments = createSetStore<Comment>({
121
+ ...api({ list: '/posts/:postId/comments', item: '/comments/:id' }),
122
+ })
123
+
124
+ // Response/request wrappers
125
+ const items = createSetStore<Item>({
126
+ ...api({ list: '/items', dataKey: 'data', requestKey: 'item' }),
127
+ })
128
+ ```
129
+
130
+ **Config:**
131
+ - `list` — Collection endpoint (GET all, POST create)
132
+ - `item` — Single item endpoint (GET one, PUT, PATCH, DELETE). Defaults to `list + '/:id'`
133
+ - `dataKey` — Extract data from response wrapper
134
+ - `requestKey` — Wrap request body
135
+
136
+ ### `queuedApi({ list, item?, dataKey?, requestKey? })` — Sequential REST API
137
+
138
+ Like `api`, but requests execute one at a time. Use for low-priority batch operations:
139
+
140
+ ```tsx
141
+ import { queuedApi } from 'kstate'
142
+
143
+ const logs = createSetStore<Log>({
144
+ ...queuedApi({ list: '/logs' }),
145
+ })
146
+
147
+ // 50 calls → executed sequentially, continues on errors
148
+ for (const log of items) {
149
+ logs.patch({ id: log.id, synced: true })
150
+ }
151
+ ```
152
+
153
+ **Note:** All `queuedApi` instances share a single global queue. This ensures low-priority operations across your app don't compete with each other.
154
+
155
+ ### `local(key, defaultValue?)` — localStorage
156
+
157
+ ```tsx
158
+ import { local } from 'kstate'
159
+
160
+ // Shorthand for local-only store
161
+ const favorites = createSetStore<Favorite>(local('favorites'))
162
+
163
+ // Add persistence to API store
164
+ const users = createSetStore<User>({
165
+ ...api({ list: '/users' }),
166
+ persist: local('users-cache').persist,
167
+ })
168
+ ```
169
+
170
+ ### `sse(url, opts?)` — Server-Sent Events
171
+
172
+ ```tsx
173
+ import { sse } from 'kstate'
174
+
175
+ const jobs = createSetStore<Job>({
176
+ ...api({ list: '/jobs' }),
177
+ subscribe: sse('/jobs/stream', {
178
+ mode: 'upsert', // 'replace' | 'append' | 'upsert'
179
+ dataKey: 'items',
180
+ maxItems: 100, // For 'append' mode
181
+ }),
182
+ })
183
+ ```
184
+
185
+ ### Custom Adapter
186
+
187
+ Pass custom functions directly for any data source:
188
+
189
+ ```tsx
190
+ // Inline functions
191
+ const users = createSetStore<User>({
192
+ get: async () => mySource.getUsers(),
193
+ create: async (data) => mySource.createUser(data),
194
+ patch: async (data) => mySource.updateUser(data.id, data),
195
+ delete: async ({ id }) => mySource.deleteUser(id),
196
+ })
197
+
198
+ // Reusable adapter factory
199
+ function myAdapter<T extends { id: string }>(source: MySource<T>) {
200
+ return {
201
+ get: async () => source.list(),
202
+ getOne: async ({ id }) => source.find(id),
203
+ create: async (data) => source.create(data),
204
+ patch: async (data) => source.update(data.id, data),
205
+ delete: async ({ id }) => source.remove(id),
206
+ }
207
+ }
208
+
209
+ const posts = createSetStore<Post>({
210
+ ...myAdapter(postsSource),
211
+ persist: local('posts-cache').persist, // Mix with other adapters
212
+ })
213
+ ```
214
+
215
+ ### Hybrid Example
216
+
217
+ ```tsx
218
+ const jobs = createSetStore<Job>({
219
+ ...api({ list: '/jobs' }),
220
+ subscribe: sse('/jobs/stream', { mode: 'upsert' }),
221
+ persist: local('jobs-cache').persist,
222
+ ttl: 60_000, // Cache for 1 minute
223
+ })
224
+ ```
225
+
226
+ ---
227
+
228
+ ## React Hooks
229
+
230
+ ### `useStore(store)`
231
+
232
+ Subscribe to store data:
233
+
234
+ ```tsx
235
+ import { useStore } from 'kstate'
236
+
237
+ function UserList() {
238
+ const items = useStore(users)
239
+ return items.map(u => <div key={u.id}>{u.name}</div>)
240
+ }
241
+ ```
242
+
243
+ ### `useStoreStatus(store)`
244
+
245
+ Subscribe to loading/error state:
246
+
247
+ ```tsx
248
+ import { useStoreStatus } from 'kstate'
249
+
250
+ function UserList() {
251
+ const items = useStore(users)
252
+ const { isLoading, isRevalidating, error, isOffline } = useStoreStatus(users)
253
+
254
+ if (isLoading) return <Spinner />
255
+ if (error) return <Error message={error.message} />
256
+
257
+ return (
258
+ <>
259
+ {isRevalidating && <RefreshIndicator />}
260
+ {items.map(u => <div key={u.id}>{u.name}</div>)}
261
+ </>
262
+ )
263
+ }
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Fine-Grained Reactivity
269
+
270
+ Subscribe to specific paths — components only re-render when that exact value changes:
271
+
272
+ ```tsx
273
+ function UserName({ index }: { index: number }) {
274
+ const name = useStore(users[index].name) // Only re-renders on name change
275
+ return <span>{name}</span>
276
+ }
277
+
278
+ function UserEmail({ index }: { index: number }) {
279
+ const email = useStore(users[index].email) // Only re-renders on email change
280
+ return <span>{email}</span>
281
+ }
282
+ ```
283
+
284
+ **How paths work:**
285
+
286
+ ```tsx
287
+ useStore(users) // Path: [] - All changes
288
+ useStore(users[0]) // Path: [0] - First item changes
289
+ useStore(users[0].name) // Path: [0, 'name'] - First item's name only
290
+ ```
291
+
292
+ When `users.patch({ id: '1', name: 'New' })` is called:
293
+ - `useStore(users)` — re-renders (ancestor)
294
+ - `useStore(users[0])` — re-renders (ancestor)
295
+ - `useStore(users[0].name)` — re-renders (exact match)
296
+ - `useStore(users[0].email)` — **does NOT** re-render (sibling)
297
+
298
+ ---
299
+
300
+ ## Optimistic Updates
301
+
302
+ `patch` and `delete` update local state immediately, then sync with server. On error, auto-rollback:
303
+
304
+ ```tsx
305
+ const handleNameChange = (name: string) => {
306
+ users.patch({ id: userId, name }) // UI updates instantly
307
+ // If API fails, rolls back automatically
308
+ }
309
+
310
+ const handleDelete = async () => {
311
+ try {
312
+ await users.delete({ id: userId }) // UI removes instantly
313
+ navigate('/users')
314
+ } catch (error) {
315
+ // Item restored, show error
316
+ toast.error('Failed to delete')
317
+ }
318
+ }
319
+ ```
320
+
321
+ **Note:** `create` is NOT optimistic — it waits for server response to get the real ID.
322
+
323
+ ---
324
+
325
+ ## Computed Stores
326
+
327
+ Derived stores that auto-update when sources change:
328
+
329
+ ```tsx
330
+ import { computed } from 'kstate'
331
+
332
+ // Filter
333
+ const activeUsers = computed(users, items => items.filter(u => u.isActive))
334
+
335
+ // Transform
336
+ const userStats = computed(users, items => ({
337
+ total: items.length,
338
+ active: items.filter(u => u.isActive).length,
339
+ }))
340
+
341
+ // Multiple sources
342
+ const dashboard = computed(
343
+ [users, orders],
344
+ ([userList, orderList]) => ({
345
+ userCount: userList.length,
346
+ orderCount: orderList.length,
347
+ })
348
+ )
349
+ ```
350
+
351
+ Fine-grained reactivity works with computed too:
352
+
353
+ ```tsx
354
+ const total = useStore(userStats.total) // Only re-renders on total change
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Complete Examples
360
+
361
+ ### User Management
362
+
363
+ ```tsx
364
+ // stores/users.ts
365
+ import { createSetStore, api, computed } from 'kstate'
366
+
367
+ interface User {
368
+ id: string
369
+ name: string
370
+ email: string
371
+ role: 'admin' | 'user'
372
+ isActive: boolean
373
+ }
374
+
375
+ export const users = createSetStore<User>({
376
+ ...api({ list: '/users', dataKey: 'items' }),
377
+ })
378
+
379
+ export const activeUsers = computed(users, items => items.filter(u => u.isActive))
380
+ ```
381
+
382
+ ```tsx
383
+ // components/UserList.tsx
384
+ import { useStore, useStoreStatus } from 'kstate'
385
+ import { users, activeUsers } from '../stores/users'
386
+
387
+ export function UserList() {
388
+ const items = useStore(activeUsers)
389
+ const { isLoading, error } = useStoreStatus(users)
390
+
391
+ useEffect(() => { users.get() }, [])
392
+
393
+ if (isLoading) return <Spinner />
394
+ if (error) return <Error message={error.message} />
395
+
396
+ return (
397
+ <ul>
398
+ {items.map(user => (
399
+ <li key={user.id}>
400
+ {user.name}
401
+ <button onClick={() => users.delete({ id: user.id })}>Delete</button>
402
+ </li>
403
+ ))}
404
+ </ul>
405
+ )
406
+ }
407
+ ```
408
+
409
+ ### Shopping Cart (localStorage)
410
+
411
+ ```tsx
412
+ // stores/cart.ts
413
+ import { createSetStore, local, computed } from 'kstate'
414
+
415
+ interface CartItem {
416
+ id: string
417
+ productId: string
418
+ name: string
419
+ price: number
420
+ quantity: number
421
+ }
422
+
423
+ export const cart = createSetStore<CartItem>(local('cart'))
424
+
425
+ export const cartTotal = computed(cart, items =>
426
+ items.reduce((sum, i) => sum + i.price * i.quantity, 0)
427
+ )
428
+ ```
429
+
430
+ ```tsx
431
+ // components/Cart.tsx
432
+ function CartSummary() {
433
+ const total = useStore(cartTotal)
434
+ return <span>Total: ${total.toFixed(2)}</span>
435
+ }
436
+
437
+ function AddToCart({ product }: { product: Product }) {
438
+ const handleAdd = () => {
439
+ const existing = cart.value.find(i => i.productId === product.id)
440
+ if (existing) {
441
+ cart.patch({ id: existing.id, quantity: existing.quantity + 1 })
442
+ } else {
443
+ cart.create({
444
+ id: crypto.randomUUID(),
445
+ productId: product.id,
446
+ name: product.name,
447
+ price: product.price,
448
+ quantity: 1,
449
+ })
450
+ }
451
+ }
452
+ return <button onClick={handleAdd}>Add to Cart</button>
453
+ }
454
+ ```
455
+
456
+ ### Realtime Jobs (SSE + API)
457
+
458
+ ```tsx
459
+ // stores/jobs.ts
460
+ import { createSetStore, api, sse, local } from 'kstate'
461
+
462
+ interface Job {
463
+ id: string
464
+ status: 'pending' | 'running' | 'complete'
465
+ url: string
466
+ }
467
+
468
+ export const jobs = createSetStore<Job>({
469
+ ...api({ list: '/jobs', dataKey: 'items' }),
470
+ subscribe: sse('/jobs/stream', { mode: 'upsert' }),
471
+ persist: local('jobs-cache').persist,
472
+ })
473
+ ```
474
+
475
+ ```tsx
476
+ // components/Jobs.tsx
477
+ function JobList() {
478
+ const items = useStore(jobs)
479
+
480
+ useEffect(() => { jobs.get() }, [])
481
+
482
+ return items.map(job => (
483
+ <div key={job.id}>
484
+ {job.status}: {job.url}
485
+ </div>
486
+ ))
487
+ }
488
+ ```
@@ -0,0 +1,18 @@
1
+ import type { ApiAdapterConfig } from '../types';
2
+ export declare function api<T extends {
3
+ id: string;
4
+ }>(config: ApiAdapterConfig): {
5
+ get: (params?: Record<string, unknown>) => Promise<T[]>;
6
+ getOne: (params: {
7
+ id: string;
8
+ } & Record<string, unknown>) => Promise<T>;
9
+ create: (body: Omit<T, "id"> | T) => Promise<T>;
10
+ set: (body: T) => Promise<T>;
11
+ patch: (body: Partial<T> & {
12
+ id: string;
13
+ }) => Promise<T>;
14
+ delete: (params: {
15
+ id: string;
16
+ }) => Promise<void>;
17
+ };
18
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/adapters/api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAIhD,wBAAgB,GAAG,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,gBAAgB;mBAK7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;qBAIrB;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;mBAI1C,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC;gBAIpB,CAAC;kBAIC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;qBAIxB;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;EAIxC"}
@@ -0,0 +1,31 @@
1
+ import { apiFetch } from '../sync/api';
2
+ export function api(config) {
3
+ const { list, item = `${list}/:id`, dataKey, requestKey } = config;
4
+ const toParams = (p) => p;
5
+ return {
6
+ get: async (params) => {
7
+ const { data } = await apiFetch({ method: 'GET', endpoint: list, params: toParams(params), dataKey });
8
+ return data;
9
+ },
10
+ getOne: async (params) => {
11
+ const { data } = await apiFetch({ method: 'GET', endpoint: item, params: params, dataKey });
12
+ return data;
13
+ },
14
+ create: async (body) => {
15
+ const { data } = await apiFetch({ method: 'POST', endpoint: list, body, dataKey, requestKey });
16
+ return data;
17
+ },
18
+ set: async (body) => {
19
+ const { data } = await apiFetch({ method: 'PUT', endpoint: item, params: { id: body.id }, body, dataKey, requestKey });
20
+ return data;
21
+ },
22
+ patch: async (body) => {
23
+ const { data } = await apiFetch({ method: 'PATCH', endpoint: item, params: { id: body.id }, body, dataKey, requestKey });
24
+ return data;
25
+ },
26
+ delete: async (params) => {
27
+ await apiFetch({ method: 'DELETE', endpoint: item, params: params });
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/adapters/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAKtC,MAAM,UAAU,GAAG,CAA2B,MAAwB;IACpE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,IAAI,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAAA;IAClE,MAAM,QAAQ,GAAG,CAAC,CAA2B,EAAE,EAAE,CAAC,CAAuB,CAAA;IAEzE,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,MAAgC,EAAE,EAAE;YAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;YAC1G,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,MAAgD,EAAE,EAAE;YACjE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAgB,EAAE,OAAO,EAAE,CAAC,CAAA;YACxG,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,IAAuB,EAAE,EAAE;YACxC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAA;YACjG,OAAO,IAAI,CAAA;QACb,CAAC;QACD,GAAG,EAAE,KAAK,EAAE,IAAO,EAAE,EAAE;YACrB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAA;YACzH,OAAO,IAAI,CAAA;QACb,CAAC;QACD,KAAK,EAAE,KAAK,EAAE,IAAiC,EAAE,EAAE;YACjD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAA;YAC3H,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,MAAsB,EAAE,EAAE;YACvC,MAAM,QAAQ,CAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAgB,EAAE,CAAC,CAAA;QACtF,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { api } from './api';
2
+ export { queuedApi } from './queuedApi';
3
+ export { local } from './local';
4
+ export { sse } from './sse';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { api } from './api';
2
+ export { queuedApi } from './queuedApi';
3
+ export { local } from './local';
4
+ export { sse } from './sse';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA"}
@@ -0,0 +1,20 @@
1
+ export declare function local<T extends {
2
+ id: string;
3
+ }>(key: string, defaultValue?: T[]): {
4
+ get: () => T[];
5
+ getOne: (params: {
6
+ id: string;
7
+ }) => T;
8
+ create: (data: Omit<T, "id"> | T) => T;
9
+ patch: (data: Partial<T> & {
10
+ id: string;
11
+ }) => T;
12
+ delete: (params: {
13
+ id: string;
14
+ }) => void;
15
+ persist: {
16
+ load: () => T[];
17
+ save: (data: T[]) => void;
18
+ };
19
+ };
20
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/adapters/local.ts"],"names":[],"mappings":"AAEA,wBAAgB,KAAK,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,EAAE;;qBAkB1D;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;mBAChB,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC;kBAOlB,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;qBAOxB;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;;oBAhChB,CAAC,EAAE;qBAUA,CAAC,EAAE;;EA2BxB"}
@@ -0,0 +1,48 @@
1
+ const getStorage = () => typeof localStorage !== 'undefined' ? localStorage : globalThis.localStorage;
2
+ export function local(key, defaultValue) {
3
+ const load = () => {
4
+ try {
5
+ const storage = getStorage();
6
+ if (!storage)
7
+ return defaultValue ?? [];
8
+ const raw = storage.getItem(key);
9
+ if (!raw)
10
+ return defaultValue ?? [];
11
+ const parsed = JSON.parse(raw);
12
+ return Array.isArray(parsed) ? parsed : (defaultValue ?? []);
13
+ }
14
+ catch {
15
+ return defaultValue ?? [];
16
+ }
17
+ };
18
+ const save = (data) => {
19
+ const storage = getStorage();
20
+ if (storage)
21
+ storage.setItem(key, JSON.stringify(data));
22
+ };
23
+ return {
24
+ get: () => load(),
25
+ getOne: (params) => load().find(i => i.id === params.id) ?? (() => { throw new Error(`Item ${params.id} not found`); })(),
26
+ create: (data) => {
27
+ const items = load();
28
+ const item = { ...data, id: data.id ?? crypto.randomUUID() };
29
+ items.push(item);
30
+ save(items);
31
+ return item;
32
+ },
33
+ patch: (data) => {
34
+ const items = load();
35
+ const idx = items.findIndex(i => i.id === data.id);
36
+ if (idx < 0)
37
+ throw new Error(`Item ${data.id} not found`);
38
+ items[idx] = { ...items[idx], ...data };
39
+ save(items);
40
+ return items[idx];
41
+ },
42
+ delete: (params) => {
43
+ save(load().filter(i => i.id !== params.id));
44
+ },
45
+ persist: { load, save },
46
+ };
47
+ }
48
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/adapters/local.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { ApiAdapterConfig } from '../types';
2
+ export declare function queuedApi<T extends {
3
+ id: string;
4
+ }>(config: ApiAdapterConfig): {
5
+ get: (params?: Record<string, unknown>) => Promise<T[]>;
6
+ getOne: (params: {
7
+ id: string;
8
+ } & Record<string, unknown>) => Promise<T>;
9
+ create: (body: Omit<T, "id"> | T) => Promise<T>;
10
+ set: (body: T) => Promise<T>;
11
+ patch: (body: Partial<T> & {
12
+ id: string;
13
+ }) => Promise<T>;
14
+ delete: (params: {
15
+ id: string;
16
+ }) => Promise<void>;
17
+ };
18
+ //# sourceMappingURL=queuedApi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queuedApi.d.ts","sourceRoot":"","sources":["../../src/adapters/queuedApi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AA0BhD,wBAAgB,SAAS,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,gBAAgB;mBAGzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;qBACrB;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;mBAC1C,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC;gBACpB,CAAC;kBACC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;qBACxB;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE;EAElC"}