muya 2.4.96 → 2.5.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 CHANGED
@@ -1,271 +1,525 @@
1
- # **Muya 🌀**
1
+ # Muya
2
2
 
3
- *A tiny, type-safe state manager for React with a dead-simple mental model:*
4
- - **Create a state**
5
- - **Read it in components**
6
- - **Update it**
7
- - **Derive more states when you need to**
8
-
9
- ---
3
+ A tiny, type-safe state manager for React.
10
4
 
11
5
  [![Build](https://github.com/samuelgja/muya/actions/workflows/build.yml/badge.svg)](https://github.com/samuelgja/muya/actions/workflows/build.yml)
12
6
  [![Code Quality](https://github.com/samuelgja/muya/actions/workflows/code-check.yml/badge.svg)](https://github.com/samuelgja/muya/actions/workflows/code-check.yml)
13
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/muya?label=Bundle%20size)](https://bundlephobia.com/result?p=muya)
7
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/muya?label=size)](https://bundlephobia.com/result?p=muya)
8
+ [![npm](https://img.shields.io/npm/v/muya)](https://www.npmjs.com/package/muya)
9
+
10
+ ---
11
+
12
+ ## Why Muya?
14
13
 
15
- ## Highlights
16
- - **2-call API**: `create` and `select` (plus a tiny hook `useValue`)
17
- - **React-friendly**: internal batching; opt-in equality checks to skip renders
18
- - **Type-first**: full TypeScript support
19
- - **Lightweight**: built for small mental & bundle footprint
14
+ | Feature | useState + Context | Zustand | Jotai | Muya |
15
+ | -------------- | ------------------ | ------- | --------- | --------------- |
16
+ | Bundle size | 0kb (built-in) | ~2.9kb | ~2.4kb | **~1.5kb** |
17
+ | Boilerplate | High | Low | Low | **Minimal** |
18
+ | TypeScript | Manual | Good | Good | **First-class** |
19
+ | Async support | Manual | Manual | Built-in | **Built-in** |
20
+ | Derived state | Manual | Manual | Built-in | **Built-in** |
21
+ | React Suspense | No | No | Yes | **Yes** |
22
+ | Batching | React handles | Manual | Automatic | **Automatic** |
20
23
 
21
24
  ---
22
25
 
23
- ## 📦 Install
26
+ ## Installation
27
+
24
28
  ```bash
25
- bun add muya@latest
29
+ npm install muya
26
30
  # or
27
- npm i muya@latest
31
+ bun add muya
28
32
  # or
29
- yarn add muya@latest
33
+ yarn add muya
30
34
  ```
31
35
 
32
36
  ---
33
37
 
34
- ## 🏃 Quick Start
38
+ ## Quick Start
35
39
 
36
40
  ```tsx
37
41
  import { create } from 'muya'
38
42
 
39
- // 1) Make a state
40
43
  const counter = create(0)
41
44
 
42
- // 2) Read it inside React
43
- export function Counter() {
45
+ function Counter() {
44
46
  const count = counter() // state is a hook
45
- return (
46
- <div>
47
- <button onClick={() => counter.set(n => n + 1)}>+1</button>
48
- <p>Count: {count}</p>
49
- </div>
50
- )
47
+ return <button onClick={() => counter.set((n) => n + 1)}>Count: {count}</button>
51
48
  }
52
49
  ```
53
50
 
51
+ That's it. No providers, no setup, no boilerplate.
52
+
54
53
  ---
55
54
 
56
- ## 🧩 Selecting / Deriving
55
+ ## Comparison
57
56
 
58
- Create derived states from one or many sources.
57
+ ### vs useState + useContext
59
58
 
60
- ```ts
61
- import { create, select } from 'muya'
59
+ **Before** (React Context):
60
+
61
+ ```tsx
62
+ // 1. Create context
63
+ const CountContext = createContext(null)
64
+
65
+ // 2. Create provider component
66
+ function CountProvider({ children }) {
67
+ const [count, setCount] = useState(0)
68
+ return <CountContext.Provider value={{ count, setCount }}>{children}</CountContext.Provider>
69
+ }
70
+
71
+ // 3. Create custom hook
72
+ function useCount() {
73
+ const context = useContext(CountContext)
74
+ if (!context) throw new Error('Must be in provider')
75
+ return context
76
+ }
77
+
78
+ // 4. Wrap your app
79
+ function App() {
80
+ return (
81
+ <CountProvider>
82
+ <Counter />
83
+ </CountProvider>
84
+ )
85
+ }
62
86
 
63
- const a = create(1)
64
- const b = create(2)
87
+ // 5. Finally use it
88
+ function Counter() {
89
+ const { count, setCount } = useCount()
90
+ return <button onClick={() => setCount((n) => n + 1)}>{count}</button>
91
+ }
92
+ ```
65
93
 
66
- // Single source
67
- const doubleA = a.select(n => n * 2)
94
+ **After** (Muya):
95
+
96
+ ```tsx
97
+ const counter = create(0)
68
98
 
69
- // Multiple sources
70
- const sum = select([a, b], (x, y) => x + y)
99
+ function Counter() {
100
+ const count = counter()
101
+ return <button onClick={() => counter.set((n) => n + 1)}>{count}</button>
102
+ }
71
103
  ```
72
104
 
73
- **Equality checks** (to avoid re-emits):
105
+ ### vs Zustand
106
+
107
+ **Zustand**:
108
+
109
+ ```tsx
110
+ import { create } from 'zustand'
74
111
 
75
- ```ts
76
- const obj = create({ a: 1, b: 2 }, (prev, next) => prev.b === next.b)
77
- obj.set(p => ({ ...p, a: p.a + 1 })) // does not notify (b unchanged)
112
+ const useStore = create((set) => ({
113
+ count: 0,
114
+ increment: () => set((state) => ({ count: state.count + 1 })),
115
+ }))
116
+
117
+ function Counter() {
118
+ const count = useStore((state) => state.count)
119
+ const increment = useStore((state) => state.increment)
120
+ return <button onClick={increment}>{count}</button>
121
+ }
78
122
  ```
79
123
 
80
- You can also add an equality function on a `select`:
124
+ **Muya**:
125
+
126
+ ```tsx
127
+ import { create } from 'muya'
81
128
 
82
- ```ts
83
- const stable = select([a, b], (x, y) => x + y, (prev, next) => prev === next)
129
+ const counter = create(0)
130
+
131
+ function Counter() {
132
+ const count = counter()
133
+ return <button onClick={() => counter.set((n) => n + 1)}>{count}</button>
134
+ }
84
135
  ```
85
136
 
86
137
  ---
87
138
 
88
- ## 🎣 Using in Components
139
+ ## Core API
140
+
141
+ ### `create(initial, isEqual?)`
89
142
 
90
- Muya states are callable hooks. Prefer that.
91
- If you want a hook wrapper or slicing, use `useValue`.
143
+ Create a state. The state itself is a hook.
144
+
145
+ ```tsx
146
+ // Simple value
147
+ const name = create('Ada')
148
+
149
+ // Object
150
+ const user = create({ id: 1, name: 'Ada', role: 'admin' })
151
+
152
+ // Lazy (computed on first read)
153
+ const expensive = create(() => computeExpensiveValue())
154
+
155
+ // Async
156
+ const data = create(fetch('/api/data').then((r) => r.json()))
157
+ const lazyData = create(() => fetch('/api/data').then((r) => r.json()))
158
+
159
+ // With equality check (skip updates when equal)
160
+ const position = create({ x: 0, y: 0 }, (prev, next) => prev.x === next.x && prev.y === next.y)
161
+ ```
162
+
163
+ ### State Methods
164
+
165
+ ```tsx
166
+ const counter = create(0)
167
+
168
+ // Read (outside React)
169
+ counter.get() // 0
170
+
171
+ // Update
172
+ counter.set(5)
173
+ counter.set((prev) => prev + 1)
174
+
175
+ // Subscribe (outside React)
176
+ const unsubscribe = counter.listen((value) => console.log(value))
177
+
178
+ // Derive new state
179
+ const doubled = counter.select((n) => n * 2)
180
+
181
+ // Debug name (for DevTools)
182
+ counter.withName('counter')
183
+
184
+ // Cleanup
185
+ counter.destroy()
186
+ ```
187
+
188
+ ### `select([states], derive, isEqual?)`
189
+
190
+ Derive state from multiple sources.
191
+
192
+ ```tsx
193
+ import { create, select } from 'muya'
194
+
195
+ const firstName = create('Ada')
196
+ const lastName = create('Lovelace')
197
+
198
+ const fullName = select([firstName, lastName], (first, last) => `${first} ${last}`)
199
+
200
+ function Greeting() {
201
+ const name = fullName() // 'Ada Lovelace'
202
+ return <h1>Hello, {name}</h1>
203
+ }
204
+ ```
205
+
206
+ ### `useValue(state, selector?)`
207
+
208
+ Hook for reading state with optional selector.
92
209
 
93
210
  ```tsx
94
211
  import { create, useValue } from 'muya'
95
212
 
96
- const user = create({ id: 'u1', name: 'Ada', admin: false })
213
+ const user = create({ id: 1, name: 'Ada', role: 'admin' })
97
214
 
98
- // Option 1: call the state directly
99
- function Profile() {
100
- const u = user()
101
- return <div>{u.name}</div>
215
+ function UserName() {
216
+ // Only re-renders when name changes
217
+ const name = useValue(user, (u) => u.name)
218
+ return <span>{name}</span>
102
219
  }
220
+ ```
221
+
222
+ ### `useValueLoadable(state, selector?)`
223
+
224
+ Hook for async states without Suspense. Returns `[value, isLoading, isError, error]`.
225
+
226
+ ```tsx
227
+ import { create, useValueLoadable } from 'muya'
228
+
229
+ const data = create(() => fetch('/api/data').then((r) => r.json()))
103
230
 
104
- // Option 2: useValue for a selector
105
- function OnlyName() {
106
- const name = useValue(user, u => u.name)
107
- return <div>{name}</div>
231
+ function DataView() {
232
+ const [value, isLoading, isError, error] = useValueLoadable(data)
233
+
234
+ if (isLoading) return <Spinner />
235
+ if (isError) return <Error message={error.message} />
236
+ return <Display data={value} />
108
237
  }
109
238
  ```
110
239
 
111
240
  ---
112
241
 
113
- ## Async & Lazy (the 2-minute mental model)
242
+ ## Async States
243
+
244
+ ### Async Initialization
114
245
 
115
- **Immediate vs Lazy**
116
- ```ts
117
- create(0) // immediate: value exists now
118
- create(() => 0) // lazy: computed on first read (.get() / component render)
246
+ ```tsx
247
+ // Promise (loads immediately)
248
+ const user = create(fetch('/api/user').then((r) => r.json()))
249
+
250
+ // Lazy async (loads on first read)
251
+ const user = create(() => fetch('/api/user').then((r) => r.json()))
119
252
  ```
120
253
 
121
- **Async sources**
122
- ```ts
123
- async function fetchInitial() { return 0 }
124
- create(fetchInitial) // lazy + async
125
- create(Promise.resolve(0)) // immediate + async
254
+ ### Async Updates
255
+
256
+ ```tsx
257
+ const user = create(() => fetchUser())
258
+
259
+ // Override immediately (cancels pending)
260
+ user.set({ id: 1, name: 'New User' })
261
+
262
+ // Wait for current value, then update
263
+ user.set((prev) => ({ ...prev, name: 'Updated' }))
126
264
  ```
127
265
 
128
- **Setting with async state**
129
- - `state.set(2)` **overrides immediately** (cancels previous pending promise)
130
- - `state.set(prev => prev + 1)` **waits for the current promise to resolve** so `prev` is always the resolved value
266
+ ### Async Selectors
267
+
268
+ ```tsx
269
+ const userId = create(1)
131
270
 
132
- **Async selectors**
133
- ```ts
134
- const base = create(0)
135
- const plusOne = base.select(async n => {
136
- await doWork()
137
- return n + 1
271
+ const userDetails = userId.select(async (id) => {
272
+ const response = await fetch(`/api/users/${id}`)
273
+ return response.json()
138
274
  })
275
+
276
+ // Suspends on first read
277
+ function UserProfile() {
278
+ const details = userDetails()
279
+ return <Profile {...details} />
280
+ }
139
281
  ```
140
- - Async selects **suspend** the first time (and when their upstream async value requires it)
141
- - A sync selector reading an async parent will **suspend once** on initial load
142
282
 
143
- > Tip: Prefer keeping selectors **sync** and performing async work **before** calling `set`. It keeps render trees predictable.
283
+ ### With Suspense
144
284
 
145
- ---
285
+ ```tsx
286
+ const data = create(() => fetchData())
146
287
 
147
- ## 🧪 API (short and sweet)
288
+ function App() {
289
+ return (
290
+ <Suspense fallback={<Loading />}>
291
+ <DataView />
292
+ </Suspense>
293
+ )
294
+ }
148
295
 
149
- ### `create<T>(initial, isEqual?) => State<T>`
150
- **State methods**
151
- - `get(): T` — read current value (resolves lazy/async when needed)
152
- - `set(value | (prev) => next)` — update value (batched)
153
- - `listen(fn)` — subscribe, returns `unsubscribe`
154
- - `select(selector, isEqual?)` — derive new state from this state
155
- - `destroy()` — clear listeners and dispose
156
- - `withName(name)` — add a debug label (DevTools)
296
+ function DataView() {
297
+ const value = data() // suspends until resolved
298
+ return <div>{value}</div>
299
+ }
300
+ ```
157
301
 
158
- ### `select([states], derive, isEqual?) => State<R>`
159
- Derive a state from one or multiple states.
302
+ ### Without Suspense
160
303
 
161
- ### `useValue(state, selector?)`
162
- React hook to read a state or a slice.
304
+ ```tsx
305
+ const data = create(() => fetchData())
163
306
 
164
- ---
307
+ function DataView() {
308
+ const [value, isLoading, isError, error] = useValueLoadable(data)
165
309
 
166
- ## 🪵 DevTools
167
- In **dev**, Muya auto-connects to Redux DevTools if present and reports state updates by name.
168
- Use `state.withName('CartItems')` to make timelines readable.
310
+ if (isLoading) return <Loading />
311
+ if (isError) return <Error error={error} />
312
+ return <div>{value}</div>
313
+ }
314
+ ```
169
315
 
170
316
  ---
171
317
 
172
- ## 🤝 Patterns & Anti-patterns
318
+ ## Patterns
173
319
 
174
- **✅ Good**
175
- - Keep selectors pure and fast
176
- - Use equality checks to avoid unnecessary updates
177
- - Do async outside, then `set` synchronously
320
+ ### Computed Values
178
321
 
179
- **⚠️ Be cautious**
180
- - Deep async chains of selectors → harder to debug and may re-suspend often
181
- - Long-running async work inside selectors push it out of render path
322
+ ```tsx
323
+ const items = create([
324
+ { id: 1, name: 'Apple', price: 1.5, quantity: 2 },
325
+ { id: 2, name: 'Banana', price: 0.5, quantity: 5 },
326
+ ])
182
327
 
183
- ---
328
+ const total = items.select((list) => list.reduce((sum, item) => sum + item.price * item.quantity, 0))
329
+
330
+ const count = items.select((list) => list.length)
331
+ ```
184
332
 
185
- ## 🧭 Examples
333
+ ### Actions
186
334
 
187
- **Boolean flags derived from async state (suspends only once):**
188
335
  ```tsx
189
- const user = create(fetchUser) // async lazy
190
- const isAdmin = user.select(u => u.role === 'admin')
191
- // First mount suspends while user loads, subsequent updates are instant
336
+ const cart = create({ items: [], discount: 0 })
337
+
338
+ const cartActions = {
339
+ addItem: (item) =>
340
+ cart.set((state) => ({
341
+ ...state,
342
+ items: [...state.items, item],
343
+ })),
344
+
345
+ applyDiscount: (percent) =>
346
+ cart.set((state) => ({
347
+ ...state,
348
+ discount: percent,
349
+ })),
350
+
351
+ clear: () => cart.set({ items: [], discount: 0 }),
352
+ }
353
+
354
+ // Usage
355
+ cartActions.addItem({ id: 1, name: 'Book', price: 20 })
192
356
  ```
193
357
 
194
- **Batching inside events:**
195
- ```ts
196
- function onCheckout() {
197
- cart.set(c => applyDiscount(c))
198
- total.set(t => t - 10)
199
- // Muya batches internally; React sees one commit
358
+ ### Shallow Equality
359
+
360
+ ```tsx
361
+ import { create, shallow } from 'muya'
362
+
363
+ const list = create(
364
+ [1, 2, 3],
365
+ shallow, // built-in shallow comparison
366
+ )
367
+
368
+ // Won't notify if array contents are the same
369
+ list.set([1, 2, 3])
370
+ ```
371
+
372
+ ### Batching
373
+
374
+ Multiple updates in the same event are batched automatically:
375
+
376
+ ```tsx
377
+ function checkout() {
378
+ cart.set((c) => applyDiscount(c))
379
+ total.set((t) => t - 10)
380
+ inventory.set((i) => decrementStock(i))
381
+ // React sees one render
200
382
  }
201
383
  ```
202
384
 
203
385
  ---
204
386
 
205
- ## 🗃️ (Optional) Muya + SQLite Companion
387
+ ## DevTools
388
+
389
+ Muya auto-connects to Redux DevTools in development.
390
+
391
+ ```tsx
392
+ const counter = create(0).withName('counter')
393
+ const user = create({ name: 'Ada' }).withName('user')
394
+ ```
395
+
396
+ ---
397
+
398
+ ## SQLite Companion
206
399
 
207
- If you’re using the companion `muya/sqlite` package, you can manage large, queryable lists with React-friendly pagination:
400
+ For large, queryable lists with pagination. Works with expo-sqlite, better-sqlite3, or in-memory.
208
401
 
209
- ```ts
210
- import { createSqliteState } from 'muya/sqlite'
211
- import { useSqliteValue } from 'muya/sqlite'
402
+ ```tsx
403
+ import { createSqliteState, useSqliteValue } from 'muya/sqlite'
212
404
 
213
- type Person = { id: string; name: string; age: number }
405
+ type Task = { id: string; title: string; done: boolean; priority: number }
214
406
 
215
- const people = createSqliteState<Person>({
216
- backend, // e.g. expo-sqlite / bunMemoryBackend
217
- tableName: 'People',
407
+ const tasks = createSqliteState<Task>({
408
+ backend,
409
+ tableName: 'tasks',
218
410
  key: 'id',
219
- indexes: ['age'], // optional
411
+ indexes: ['priority', 'done'],
220
412
  })
413
+ ```
414
+
415
+ ### React Hook with Pagination
416
+
417
+ ```tsx
418
+ function TaskList() {
419
+ const [rows, actions] = useSqliteValue(tasks, { sortBy: 'priority', order: 'desc', limit: 20 }, [])
221
420
 
222
- // In React: stepwise fetching + where/order/limit
223
- function PeopleList() {
224
- const [rows, actions] = useSqliteValue(people, { sorBy: 'age', order: 'asc', limit: 50 }, [])
225
421
  return (
226
422
  <>
227
- <ul>{rows.map(p => <li key={p.id}>{p.name}</li>)}</ul>
228
- <button onClick={() => actions.next()}>Load more</button>
229
- <button onClick={() => actions.reset()}>Reset</button>
423
+ {rows.map((task) => (
424
+ <TaskItem key={task.id} task={task} />
425
+ ))}
426
+ <button onClick={actions.next}>Load more</button>
427
+ <button onClick={actions.reset}>Reset</button>
230
428
  </>
231
429
  )
232
430
  }
233
431
  ```
234
432
 
235
- **Quick ops**
236
- ```ts
237
- await people.batchSet([{ id:'1', name:'Alice', age:30 }])
238
- await people.set({ id:'2', name:'Bob', age:25 })
239
- await people.delete('1')
240
- const alice = await people.get('2', p => p.name) // 'Bob'
241
- const count = await people.count({ where: { age: { gt: 20 } } })
242
- for await (const p of people.search({ where: { name: { like: '%Ali%' }}})) { /* … */ }
433
+ ### CRUD Operations
434
+
435
+ ```tsx
436
+ // Create
437
+ await tasks.set({ id: '1', title: 'Buy milk', done: false, priority: 1 })
438
+
439
+ // Batch create
440
+ await tasks.batchSet([
441
+ { id: '2', title: 'Walk dog', done: false, priority: 2 },
442
+ { id: '3', title: 'Read book', done: true, priority: 3 },
443
+ ])
444
+
445
+ // Read
446
+ const task = await tasks.get('1')
447
+ const title = await tasks.get('1', (t) => t.title)
448
+
449
+ // Update
450
+ await tasks.set({ id: '1', title: 'Buy milk', done: true, priority: 1 })
451
+
452
+ // Delete
453
+ await tasks.delete('1')
454
+
455
+ // Batch delete
456
+ await tasks.batchDelete(['2', '3'])
457
+
458
+ // Count
459
+ const total = await tasks.count()
460
+ const pending = await tasks.count({ where: { done: false } })
243
461
  ```
244
462
 
245
- ---
463
+ ### Querying
246
464
 
247
- ## ❓ FAQ
465
+ ```tsx
466
+ // Search with where clause
467
+ for await (const task of tasks.search({
468
+ where: { done: false, priority: { gt: 1 } },
469
+ orderBy: 'priority',
470
+ order: 'desc',
471
+ })) {
472
+ console.log(task.title)
473
+ }
248
474
 
249
- **Is Muya a replacement for Redux/Zustand/Jotai?**
250
- No—Muya is intentionally tiny. If you need complex middleware, effects, or ecosystem plugins, those tools are great choices.
475
+ ```
251
476
 
252
- **Can I use Suspense?**
253
- Yes. Async states/selectors will suspend on first read (and when upstream requires it).
477
+ **Where clause operators:**
254
478
 
255
- **How do I avoid re-renders?**
256
- Provide an `isEqual(prev, next)` to `create` or `select`, or select a smaller slice in `useValue`.
479
+ | Operator | Example | Description |
480
+ | -------- | ------------------------------- | ---------------------- |
481
+ | equals | `{ done: false }` | Exact match |
482
+ | gt | `{ priority: { gt: 5 } }` | Greater than |
483
+ | gte | `{ priority: { gte: 5 } }` | Greater than or equal |
484
+ | lt | `{ priority: { lt: 5 } }` | Less than |
485
+ | lte | `{ priority: { lte: 5 } }` | Less than or equal |
486
+ | like | `{ title: { like: '%milk%' } }` | SQL LIKE pattern match |
257
487
 
258
488
  ---
259
489
 
260
- ## 🧪 Testing Tips
261
- - State reads/writes are synchronous, but **async sources/selectors** resolve over time. In tests, use `await waitFor(...)` around expectations that depend on async resolution.
490
+ ## TypeScript
491
+
492
+ Full type inference out of the box:
493
+
494
+ ```tsx
495
+ const user = create({ id: 1, name: 'Ada', role: 'admin' as const })
496
+ // Type: State<{ id: number; name: string; role: 'admin' }>
497
+
498
+ const role = user.select((u) => u.role)
499
+ // Type: State<'admin'>
500
+
501
+ const name = useValue(user, (u) => u.name)
502
+ // Type: string
503
+ ```
262
504
 
263
505
  ---
264
506
 
265
- ## 📜 License
266
- MIT — if you like Muya, a ⭐️ is always appreciated!
507
+ ## FAQ
508
+
509
+ **Is Muya a replacement for Redux/Zustand/Jotai?**
510
+ Muya is intentionally minimal. If you need middleware, devtools plugins, or large ecosystem, consider those alternatives.
511
+
512
+ **How do I avoid re-renders?**
513
+ Use `isEqual` function with `create`/`select`, or use a selector with `useValue` to subscribe to a slice.
514
+
515
+ **Can I use Suspense?**
516
+ Yes. Async states suspend on first read. Use `useValueLoadable` if you prefer loading states over Suspense.
517
+
518
+ **Does it work with React Native?**
519
+ Yes, Muya has no DOM dependencies.
267
520
 
268
521
  ---
269
522
 
270
- ### Changelog / Contributing
271
- See repo issues and PRs. Keep changes small and measured—Muya’s value is simplicity.
523
+ ## License
524
+
525
+ MIT
package/cjs/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var V=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var W=(e,t)=>{for(var o in t)V(e,o,{get:t[o],enumerable:!0})},J=(e,t,o,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of K(t))!Y.call(e,s)&&s!==o&&V(e,s,{get:()=>t[s],enumerable:!(a=N(t,s))||a.enumerable});return e};var Q=e=>J(V({},"__esModule",{value:!0}),e);var $={};W($,{EMPTY_SELECTOR:()=>D,create:()=>z,isAbortError:()=>I,isArray:()=>g,isEqualBase:()=>m,isError:()=>v,isFunction:()=>h,isMap:()=>x,isPromise:()=>f,isSet:()=>E,isSetValueFunction:()=>O,isState:()=>Z,isUndefined:()=>d,select:()=>k,shallow:()=>F,useValue:()=>C});module.exports=Q($);var T=class extends Error{static Error="AbortError"};function X(e,t){t&&t.abort();let o=new AbortController,{signal:a}=o;return{promise:new Promise((n,u)=>{a.addEventListener("abort",()=>{u(new T)}),e.then(n).catch(u)}),controller:o}}function y(e,t=m){if(!d(e.current)){if(!d(e.previous)&&t(e.current,e.previous))return!1;e.previous=e.current}return!0}function p(e,t){let{cache:o,emitter:{emit:a}}=e;if(!f(t))return t;o.abortController&&o.abortController.abort();let{promise:s,controller:n}=X(t,o.abortController);return o.abortController=n,s.then(u=>{o.current=u,a()}).catch(u=>{I(u)||(o.current=u,a())})}function f(e){return e instanceof Promise}function h(e){return typeof e=="function"}function x(e){return e instanceof Map}function E(e){return e instanceof Set}function g(e){return Array.isArray(e)}function m(e,t){return e===t?!0:!!Object.is(e,t)}function O(e){return typeof e=="function"}function I(e){return e instanceof T}function v(e){return e instanceof Error}function d(e){return e===void 0}function Z(e){return h(e)&&"get"in e&&"set"in e&&"isSet"in e&&e.isSet===!0}var D=e=>e;function U(){let e=new Map,t=new Set,o=performance.now(),a=!1;function s(){let u=performance.now(),r=u-o,{size:i}=t;if(r<.2&&i>0&&i<10){o=u,n();return}a||(a=!0,Promise.resolve().then(()=>{a=!1,o=performance.now(),n()}))}function n(){if(t.size===0)return;let u=new Set,r=new Map;for(let i of t){if(e.has(i.id)){u.add(i.id);let{onResolveItem:c}=e.get(i.id);c&&c(i.value),r.has(i.id)||r.set(i.id,[]),r.get(i.id).push(i.value)}t.delete(i)}if(t.size>0){s();return}for(let i of u){let c=r.get(i);e.get(i)?.onScheduleDone(c)}}return{add(u,r){return e.set(u,r),()=>{e.delete(u)}},schedule(u,r){t.add({value:r,id:u}),s()}}}function k(e,t,o){function a(){let c=!1,l=e.map(A=>{let w=A.get();return f(w)&&(c=!0),w});return c?new Promise((A,w)=>{Promise.all(l).then(G=>{if(G.some(_=>d(_)))return w(new T);let j=t(...G);A(j)})}):t(...l)}function s(){if(d(r.cache.current)){let c=a();r.cache.current=p(r,c)}return r.cache.current}function n(){if(d(r.cache.current)){let l=a();r.cache.current=p(r,l)}let{current:c}=r.cache;return f(c)?new Promise(l=>{c.then(S=>{if(d(S)){l(n());return}l(S)})}):r.cache.current}let u=[];for(let c of e){let l=c.emitter.subscribe(()=>{b.schedule(r.id,null)});u.push(l)}let r=P({destroy(){for(let c of u)c();i(),r.emitter.clear(),r.cache.current=void 0},get:n,getSnapshot:s}),i=b.add(r.id,{onScheduleDone(){let c=a();r.cache.current=p(r,c),y(r.cache,o)&&r.emitter.emit()}});return r}var R=require("react");var L=require("use-sync-external-store/shim/with-selector");function C(e,t=D){let{emitter:o}=e,a=(0,L.useSyncExternalStoreWithSelector)(e.emitter.subscribe,o.getSnapshot,o.getInitialSnapshot,t);if((0,R.useDebugValue)(a),f(a)||v(a))throw a;return a}function q(e,t){let o=new Set,a=[];return{clear:()=>{for(let s of a)s();o.clear()},subscribe:s=>(o.add(s),()=>{o.delete(s)}),emit:(...s)=>{for(let n of o)n(...s)},contains:s=>o.has(s),getSnapshot:e,getInitialSnapshot:t,getSize:()=>o.size,subscribeToOtherEmitter(s){let n=s.subscribe(()=>{this.emit()});a.push(n)}}}var M=0;function H(){return M++,M.toString(36)}function P(e){let{get:t,destroy:o,set:a,getSnapshot:s}=e,n=!!a,u={},r=function(i){return C(r,i)};return r.isSet=n,r.id=H(),r.emitter=q(s),r.destroy=o,r.listen=function(i){return this.emitter.subscribe(()=>{let c=t();f(c)||i(t())})},r.withName=function(i){return this.stateName=i,this},r.select=function(i,c=m){return k([r],i,c)},r.get=t,r.set=a,r.cache=u,r}var b=U();function z(e,t=m){function o(){try{if(d(n.cache.current)){let r=h(e)?e():e,i=p(n,r);return n.cache.current=i,n.cache.current}return n.cache.current}catch(r){n.cache.current=r}return n.cache.current}async function a(r,i){await r;let c=i(n.cache.current),l=p(n,c);n.cache.current=l}function s(r){let i=o(),c=O(r);if(c&&f(i)){a(i,r);return}n.cache.abortController&&n.cache.abortController.abort();let l=c?r(i):r,S=p(n,l);n.cache.current=S}let n=P({get:o,destroy(){o(),u(),n.emitter.clear(),n.cache.current=void 0},set(r){b.schedule(n.id,r)},getSnapshot:o}),u=b.add(n.id,{onScheduleDone(){n.cache.current=o(),y(n.cache,t)&&n.emitter.emit()},onResolveItem:s});return h(e)||o(),n}function F(e,t){if(e==t)return!0;if(typeof e!="object"||e==null||typeof t!="object"||t==null)return!1;if(x(e)&&x(t)){if(e.size!==t.size)return!1;for(let[s,n]of e)if(!Object.is(n,t.get(s)))return!1;return!0}if(E(e)&&E(t)){if(e.size!==t.size)return!1;for(let s of e)if(!t.has(s))return!1;return!0}if(g(e)&&g(t)){if(e.length!==t.length)return!1;for(let[s,n]of e.entries())if(!Object.is(n,t[s]))return!1;return!0}let o=Object.keys(e),a=Object.keys(t);if(o.length!==a.length)return!1;for(let s of o)if(!Object.prototype.hasOwnProperty.call(t,s)||!Object.is(e[s],t[s]))return!1;return!0}
1
+ "use strict";var O=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var Q=(e,t)=>{for(var n in t)O(e,n,{get:t[n],enumerable:!0})},X=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of W(t))!J.call(e,s)&&s!==n&&O(e,s,{get:()=>t[s],enumerable:!(a=Y(t,s))||a.enumerable});return e};var Z=e=>X(O({},"__esModule",{value:!0}),e);var ee={};Q(ee,{EMPTY_SELECTOR:()=>w,create:()=>F,isAbortError:()=>v,isArray:()=>k,isEqualBase:()=>S,isError:()=>b,isFunction:()=>h,isMap:()=>g,isPromise:()=>l,isSet:()=>P,isSetValueFunction:()=>D,isState:()=>B,isUndefined:()=>f,select:()=>V,shallow:()=>j,useValue:()=>C,useValueLoadable:()=>_});module.exports=Z(ee);var T=class extends Error{static Error="AbortError"};function $(e,t){t&&t.abort();let n=new AbortController,{signal:a}=n;return{promise:new Promise((o,c)=>{a.addEventListener("abort",()=>{c(new T)}),e.then(o).catch(c)}),controller:n}}function E(e,t=S){if(!f(e.current)){if(!f(e.previous)&&t(e.current,e.previous))return!1;e.previous=e.current}return!0}function p(e,t){let{cache:n,emitter:{emit:a}}=e;if(!l(t))return t;n.abortController&&n.abortController.abort();let{promise:s,controller:o}=$(t,n.abortController);return n.abortController=o,s.then(c=>{n.current=c,a()}).catch(c=>{v(c)||(n.current=c,a())})}function l(e){return e instanceof Promise}function h(e){return typeof e=="function"}function g(e){return e instanceof Map}function P(e){return e instanceof Set}function k(e){return Array.isArray(e)}function S(e,t){return e===t?!0:!!Object.is(e,t)}function D(e){return typeof e=="function"}function v(e){return e instanceof T}function b(e){return e instanceof Error}function f(e){return e===void 0}function B(e){return h(e)&&"get"in e&&"set"in e&&"isSet"in e&&e.isSet===!0}var w=e=>e;function G(){let e=new Map,t=new Set,n=performance.now(),a=!1;function s(){let c=performance.now(),r=c-n,{size:i}=t;if(r<.2&&i>0&&i<10){n=c,o();return}a||(a=!0,Promise.resolve().then(()=>{a=!1,n=performance.now(),o()}))}function o(){if(t.size===0)return;let c=new Set,r=new Map;for(let i of t){if(e.has(i.id)){c.add(i.id);let{onResolveItem:u}=e.get(i.id);u&&u(i.value),r.has(i.id)||r.set(i.id,[]),r.get(i.id).push(i.value)}t.delete(i)}if(t.size>0){s();return}for(let i of c){let u=r.get(i);e.get(i)?.onScheduleDone(u)}}return{add(c,r){return e.set(c,r),()=>{e.delete(c)}},schedule(c,r){t.add({value:r,id:c}),s()}}}function V(e,t,n){function a(){let u=!1,d=e.map(L=>{let x=L.get();return l(x)&&(u=!0),x});return u?new Promise((L,x)=>{Promise.all(d).then(R=>{if(R.some(K=>f(K)))return x(new T);let N=t(...R);L(N)})}):t(...d)}function s(){if(f(r.cache.current)){let u=a();r.cache.current=p(r,u)}return r.cache.current}function o(){if(f(r.cache.current)){let d=a();r.cache.current=p(r,d)}let{current:u}=r.cache;return l(u)?new Promise(d=>{u.then(m=>{if(f(m)){d(o());return}d(m)})}):r.cache.current}let c=[];for(let u of e){let d=u.emitter.subscribe(()=>{y.schedule(r.id,null)});c.push(d)}let r=A({destroy(){for(let u of c)u();i(),r.emitter.clear(),r.cache.current=void 0},get:o,getSnapshot:s}),i=y.add(r.id,{onScheduleDone(){let u=a();r.cache.current=p(r,u),E(r.cache,n)&&r.emitter.emit()}});return r}var U=require("react");var M=require("use-sync-external-store/shim/with-selector");function C(e,t=w){let{emitter:n}=e,a=(0,M.useSyncExternalStoreWithSelector)(e.emitter.subscribe,n.getSnapshot,n.getInitialSnapshot,t);if((0,U.useDebugValue)(a),l(a)||b(a))throw a;return a}function q(e,t){let n=new Set,a=[];return{clear:()=>{for(let s of a)s();n.clear()},subscribe:s=>(n.add(s),()=>{n.delete(s)}),emit:(...s)=>{for(let o of n)o(...s)},contains:s=>n.has(s),getSnapshot:e,getInitialSnapshot:t,getSize:()=>n.size,subscribeToOtherEmitter(s){let o=s.subscribe(()=>{this.emit()});a.push(o)}}}var H=0;function z(){return H++,H.toString(36)}function A(e){let{get:t,destroy:n,set:a,getSnapshot:s}=e,o=!!a,c={},r=function(i){return C(r,i)};return r.isSet=o,r.id=z(),r.emitter=q(s),r.destroy=n,r.listen=function(i){return this.emitter.subscribe(()=>{let u=t();l(u)||i(t())})},r.withName=function(i){return this.stateName=i,this},r.select=function(i,u=S){return V([r],i,u)},r.get=t,r.set=a,r.cache=c,r}var y=G();function F(e,t=S){function n(){try{if(f(o.cache.current)){let r=h(e)?e():e,i=p(o,r);return o.cache.current=i,o.cache.current}return o.cache.current}catch(r){o.cache.current=r}return o.cache.current}async function a(r,i){await r;let u=i(o.cache.current),d=p(o,u);o.cache.current=d}function s(r){let i=n(),u=D(r);if(u&&l(i)){a(i,r);return}o.cache.abortController&&o.cache.abortController.abort();let d=u?r(i):r,m=p(o,d);o.cache.current=m}let o=A({get:n,destroy(){n(),c(),o.emitter.clear(),o.cache.current=void 0},set(r){y.schedule(o.id,r)},getSnapshot:n}),c=y.add(o.id,{onScheduleDone(){o.cache.current=n(),E(o.cache,t)&&o.emitter.emit()},onResolveItem:s});return h(e)||n(),o}var I=require("react");function _(e,t=w){let{emitter:n}=e,a=(0,I.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getInitialSnapshot);return(0,I.useDebugValue)(a),l(a)?[void 0,!0,!1,void 0]:b(a)?[void 0,!1,!0,a]:[t(a),!1,!1,void 0]}function j(e,t){if(e==t)return!0;if(typeof e!="object"||e==null||typeof t!="object"||t==null)return!1;if(g(e)&&g(t)){if(e.size!==t.size)return!1;for(let[s,o]of e)if(!Object.is(o,t.get(s)))return!1;return!0}if(P(e)&&P(t)){if(e.size!==t.size)return!1;for(let s of e)if(!t.has(s))return!1;return!0}if(k(e)&&k(t)){if(e.length!==t.length)return!1;for(let[s,o]of e.entries())if(!Object.is(o,t[s]))return!1;return!0}let n=Object.keys(e),a=Object.keys(t);if(n.length!==a.length)return!1;for(let s of n)if(!Object.prototype.hasOwnProperty.call(t,s)||!Object.is(e[s],t[s]))return!1;return!0}
package/esm/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./utils/is";export*from"./types";import{create as f}from"./create";import{select as p}from"./select";import{useValue as l}from"./use-value";import{shallow as s}from"./utils/shallow";export{f as create,p as select,s as shallow,l as useValue};
1
+ export*from"./utils/is";export*from"./types";import{create as a}from"./create";import{select as p}from"./select";import{useValue as m}from"./use-value";import{useValueLoadable as s}from"./use-value-loadable";import{shallow as b}from"./utils/shallow";export{a as create,p as select,b as shallow,m as useValue,s as useValueLoadable};
@@ -0,0 +1 @@
1
+ import{useDebugValue as n,useSyncExternalStore as s}from"react";import{EMPTY_SELECTOR as u}from"./types";import{isError as r,isPromise as l}from"./utils/is";function b(t,d=u){const{emitter:a}=t,e=s(a.subscribe,a.getSnapshot,a.getInitialSnapshot);return n(e),l(e)?[void 0,!0,!1,void 0]:r(e)?[void 0,!1,!0,e]:[d(e),!1,!1,void 0]}export{b as useValueLoadable};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.4.96",
3
+ "version": "2.5.1",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -0,0 +1,135 @@
1
+ import { renderHook, act } from '@testing-library/react-hooks'
2
+ import { waitFor } from '@testing-library/react'
3
+ import { create } from '../create'
4
+ import { useValueLoadable } from '../use-value-loadable'
5
+ import { longPromise } from './test-utils'
6
+
7
+ describe('useValueLoadable', () => {
8
+ it('should return value immediately for sync state', () => {
9
+ const state = create(42)
10
+ const { result } = renderHook(() => useValueLoadable(state))
11
+
12
+ const [value, isLoading, isError, error] = result.current
13
+ expect(value).toBe(42)
14
+ expect(isLoading).toBe(false)
15
+ expect(isError).toBe(false)
16
+ expect(error).toBeUndefined()
17
+ })
18
+
19
+ it('should return loading state for async state', async () => {
20
+ const state = create(longPromise(50))
21
+ const { result } = renderHook(() => useValueLoadable(state))
22
+
23
+ // Initially loading
24
+ expect(result.current[0]).toBeUndefined()
25
+ expect(result.current[1]).toBe(true)
26
+ expect(result.current[2]).toBe(false)
27
+ expect(result.current[3]).toBeUndefined()
28
+
29
+ // After resolution
30
+ await waitFor(() => {
31
+ expect(result.current[0]).toBe(0)
32
+ expect(result.current[1]).toBe(false)
33
+ expect(result.current[2]).toBe(false)
34
+ expect(result.current[3]).toBeUndefined()
35
+ })
36
+ })
37
+
38
+ it('should return error state when state throws', () => {
39
+ const testError = new Error('Test error')
40
+ const state = create(() => {
41
+ throw testError
42
+ })
43
+ const { result } = renderHook(() => useValueLoadable(state))
44
+
45
+ const [value, isLoading, isError, error] = result.current
46
+ expect(value).toBeUndefined()
47
+ expect(isLoading).toBe(false)
48
+ expect(isError).toBe(true)
49
+ expect(error).toBe(testError)
50
+ })
51
+
52
+ it('should return error state when async state rejects', async () => {
53
+ const testError = new Error('Async error')
54
+ const state = create(Promise.reject(testError))
55
+ const { result } = renderHook(() => useValueLoadable(state))
56
+
57
+ await waitFor(() => {
58
+ expect(result.current[0]).toBeUndefined()
59
+ expect(result.current[1]).toBe(false)
60
+ expect(result.current[2]).toBe(true)
61
+ expect(result.current[3]).toBe(testError)
62
+ })
63
+ })
64
+
65
+ it('should update when sync state changes', async () => {
66
+ const state = create(1)
67
+ const { result } = renderHook(() => useValueLoadable(state))
68
+
69
+ expect(result.current[0]).toBe(1)
70
+
71
+ act(() => {
72
+ state.set(2)
73
+ })
74
+
75
+ await waitFor(() => {
76
+ expect(result.current[0]).toBe(2)
77
+ expect(result.current[1]).toBe(false)
78
+ expect(result.current[2]).toBe(false)
79
+ })
80
+ })
81
+
82
+ it('should work with selector', () => {
83
+ const state = create({ count: 10, name: 'test' })
84
+ const { result } = renderHook(() => useValueLoadable(state, (s) => s.count))
85
+
86
+ const [value, isLoading, isError] = result.current
87
+ expect(value).toBe(10)
88
+ expect(isLoading).toBe(false)
89
+ expect(isError).toBe(false)
90
+ })
91
+
92
+ it('should work with selector on async state', async () => {
93
+ const state = create(Promise.resolve({ count: 5, name: 'async' }))
94
+ const { result } = renderHook(() => useValueLoadable(state, (s) => s.count))
95
+
96
+ // Initially loading
97
+ expect(result.current[1]).toBe(true)
98
+
99
+ await waitFor(() => {
100
+ expect(result.current[0]).toBe(5)
101
+ expect(result.current[1]).toBe(false)
102
+ })
103
+ })
104
+
105
+ it('should not throw to suspense boundary', async () => {
106
+ const state = create(longPromise(50))
107
+ const renderCount = jest.fn()
108
+
109
+ const { result } = renderHook(() => {
110
+ renderCount()
111
+ return useValueLoadable(state)
112
+ })
113
+
114
+ // Should render without throwing
115
+ expect(renderCount).toHaveBeenCalled()
116
+ expect(result.current[1]).toBe(true)
117
+
118
+ await waitFor(() => {
119
+ expect(result.current[1]).toBe(false)
120
+ })
121
+ })
122
+
123
+ it('should provide type narrowing when isLoading is false', () => {
124
+ const state = create(42)
125
+ const { result } = renderHook(() => useValueLoadable(state))
126
+
127
+ const [value, isLoading, isError] = result.current
128
+
129
+ if (!isLoading && !isError) {
130
+ // TypeScript should know value is number here
131
+ const number_: number = value
132
+ expect(number_).toBe(42)
133
+ }
134
+ })
135
+ })
package/src/index.ts CHANGED
@@ -3,4 +3,5 @@ export * from './types'
3
3
  export { create } from './create'
4
4
  export { select } from './select'
5
5
  export { useValue } from './use-value'
6
+ export { useValueLoadable, type LoadableResult } from './use-value-loadable'
6
7
  export { shallow } from './utils/shallow'
@@ -0,0 +1,39 @@
1
+ import { useDebugValue, useSyncExternalStore } from 'react'
2
+ import { EMPTY_SELECTOR, type GetState } from './types'
3
+ import { isError, isPromise } from './utils/is'
4
+
5
+ type LoadableLoading = [undefined, true, false, undefined]
6
+ type LoadableSuccess<T> = [T, false, false, undefined]
7
+ type LoadableError = [undefined, false, true, Error]
8
+
9
+ export type LoadableResult<T> = LoadableLoading | LoadableSuccess<T> | LoadableError
10
+
11
+ /**
12
+ * React hook to subscribe to a state and get its value without throwing to Suspense.
13
+ * Returns a tuple of [value, isLoading, isError, error] for handling async states.
14
+ * @param state The state to subscribe to
15
+ * @param selector Optional function to derive a value from the state
16
+ * @returns Tuple of [value, isLoading, isError, error] with discriminated union types
17
+ */
18
+ export function useValueLoadable<T, S = undefined>(
19
+ state: GetState<T>,
20
+ selector: (stateValue: Awaited<T>) => S = EMPTY_SELECTOR,
21
+ ): LoadableResult<undefined extends S ? Awaited<T> : S> {
22
+ const { emitter } = state
23
+
24
+ const rawValue = useSyncExternalStore(emitter.subscribe, emitter.getSnapshot, emitter.getInitialSnapshot)
25
+
26
+ useDebugValue(rawValue)
27
+
28
+ if (isPromise(rawValue)) {
29
+ return [undefined, true, false, undefined] as LoadableResult<undefined extends S ? Awaited<T> : S>
30
+ }
31
+
32
+ if (isError(rawValue)) {
33
+ return [undefined, false, true, rawValue] as LoadableResult<undefined extends S ? Awaited<T> : S>
34
+ }
35
+
36
+ const selectedValue = selector(rawValue as Awaited<T>)
37
+
38
+ return [selectedValue, false, false, undefined] as LoadableResult<undefined extends S ? Awaited<T> : S>
39
+ }
package/types/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './types';
3
3
  export { create } from './create';
4
4
  export { select } from './select';
5
5
  export { useValue } from './use-value';
6
+ export { useValueLoadable, type LoadableResult } from './use-value-loadable';
6
7
  export { shallow } from './utils/shallow';
@@ -0,0 +1,14 @@
1
+ import { type GetState } from './types';
2
+ type LoadableLoading = [undefined, true, false, undefined];
3
+ type LoadableSuccess<T> = [T, false, false, undefined];
4
+ type LoadableError = [undefined, false, true, Error];
5
+ export type LoadableResult<T> = LoadableLoading | LoadableSuccess<T> | LoadableError;
6
+ /**
7
+ * React hook to subscribe to a state and get its value without throwing to Suspense.
8
+ * Returns a tuple of [value, isLoading, isError, error] for handling async states.
9
+ * @param state The state to subscribe to
10
+ * @param selector Optional function to derive a value from the state
11
+ * @returns Tuple of [value, isLoading, isError, error] with discriminated union types
12
+ */
13
+ export declare function useValueLoadable<T, S = undefined>(state: GetState<T>, selector?: (stateValue: Awaited<T>) => S): LoadableResult<undefined extends S ? Awaited<T> : S>;
14
+ export {};
@@ -1 +0,0 @@
1
- import{createState as l}from"../create-state";let n=0;function o(){return n++,`${n.toString(36)}-sql`}function S(r,a){const{subscribe:c,updateSearchOptions:s}=r,m=o();return(...u)=>{const e=o(),p=c(e,m,()=>{t.emitter.emit()}),i=a(...u),t=l({destroy(){p(),t.emitter.clear(),t.cache.current=void 0},get(){return r.getSnapshot(e)},getSnapshot(){return r.getSnapshot(e)}});return s(e,i),t}}export{S as selectSql};
@@ -1,65 +0,0 @@
1
- import { createState } from '../create-state'
2
- import type { GetState } from '../types'
3
- import type { SyncTable } from './create-sqlite'
4
- import type { DocType, DotPath } from './table/table.types'
5
- import type { Where } from './table/where'
6
-
7
- export type CreateState<Document, Params extends unknown[]> = (...params: Params) => GetState<Document[]>
8
-
9
- export interface SqlSeachOptions<Document extends DocType> {
10
- readonly sortBy?: DotPath<Document>
11
- readonly order?: 'asc' | 'desc'
12
- readonly limit?: number
13
- readonly offset?: number
14
- readonly where?: Where<Document>
15
- readonly stepSize?: number
16
- }
17
-
18
- let stateId = 0
19
- /**
20
- * Generate a unique state ID
21
- * @returns A unique state ID
22
- */
23
- function getStateId() {
24
- stateId++
25
- return `${stateId.toString(36)}-sql`
26
- }
27
-
28
- /**
29
- * Create a state that derives its value from a SyncTable using a compute function
30
- * @param state The SyncTable to derive from
31
- * @param compute A function that takes parameters and returns SqlSeachOptions to filter the SyncTable
32
- * @returns A function that takes parameters and returns a GetState of the derived documents
33
- */
34
- export function selectSql<Document extends DocType, Params extends unknown[] = []>(
35
- state: SyncTable<Document>,
36
- compute: (...args: Params) => SqlSeachOptions<Document>,
37
- ): CreateState<Document, Params> {
38
- const { subscribe, updateSearchOptions } = state
39
- const componentId = getStateId()
40
- const result: CreateState<Document, Params> = (...params) => {
41
- const searchId = getStateId()
42
- const destroy = subscribe(searchId, componentId, () => {
43
- getState.emitter.emit()
44
- })
45
-
46
- const options = compute(...params)
47
- const getState = createState<Document[]>({
48
- destroy() {
49
- destroy()
50
- getState.emitter.clear()
51
- getState.cache.current = undefined
52
- },
53
- get() {
54
- return state.getSnapshot(searchId)
55
- },
56
- getSnapshot() {
57
- return state.getSnapshot(searchId)
58
- },
59
- })
60
- updateSearchOptions<Document>(searchId, options)
61
-
62
- return getState
63
- }
64
- return result
65
- }
@@ -1,20 +0,0 @@
1
- import type { GetState } from '../types';
2
- import type { SyncTable } from './create-sqlite';
3
- import type { DocType, DotPath } from './table/table.types';
4
- import type { Where } from './table/where';
5
- export type CreateState<Document, Params extends unknown[]> = (...params: Params) => GetState<Document[]>;
6
- export interface SqlSeachOptions<Document extends DocType> {
7
- readonly sortBy?: DotPath<Document>;
8
- readonly order?: 'asc' | 'desc';
9
- readonly limit?: number;
10
- readonly offset?: number;
11
- readonly where?: Where<Document>;
12
- readonly stepSize?: number;
13
- }
14
- /**
15
- * Create a state that derives its value from a SyncTable using a compute function
16
- * @param state The SyncTable to derive from
17
- * @param compute A function that takes parameters and returns SqlSeachOptions to filter the SyncTable
18
- * @returns A function that takes parameters and returns a GetState of the derived documents
19
- */
20
- export declare function selectSql<Document extends DocType, Params extends unknown[] = []>(state: SyncTable<Document>, compute: (...args: Params) => SqlSeachOptions<Document>): CreateState<Document, Params>;