@livequery/react 2.0.137 → 2.0.139
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 +512 -58
- package/dist/useAction.d.ts.map +1 -1
- package/dist/useAction.js +3 -1
- package/dist/useAction.js.map +1 -1
- package/dist/useDocument.d.ts +5 -4
- package/dist/useDocument.d.ts.map +1 -1
- package/dist/useDocument.js +3 -2
- package/dist/useDocument.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -31,11 +31,18 @@ npm install @livequery/react @livequery/client react rxjs
|
|
|
31
31
|
Create one `LivequeryClient` for your app or data boundary, provide it once, then use hooks inside descendant components.
|
|
32
32
|
|
|
33
33
|
```tsx
|
|
34
|
-
import { LivequeryClient } from '@livequery/client'
|
|
34
|
+
import { LivequeryClient, LivequeryMemoryStorage } from '@livequery/client'
|
|
35
|
+
import { RestTransporter } from '@livequery/rest'
|
|
35
36
|
import { LivequeryClientProvider } from '@livequery/react'
|
|
36
37
|
|
|
37
38
|
const client = new LivequeryClient({
|
|
38
|
-
|
|
39
|
+
storage: new LivequeryMemoryStorage(),
|
|
40
|
+
transporters: {
|
|
41
|
+
rest: new RestTransporter({
|
|
42
|
+
api: 'https://your-livequery-server',
|
|
43
|
+
ws: 'wss://your-livequery-server/ws',
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
39
46
|
})
|
|
40
47
|
|
|
41
48
|
export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
@@ -67,6 +74,10 @@ Use it when:
|
|
|
67
74
|
|
|
68
75
|
The provider currently expects a `core` prop. Passing `client` will not work unless the implementation is changed.
|
|
69
76
|
|
|
77
|
+
### SharedWorker
|
|
78
|
+
|
|
79
|
+
If your app uses a SharedWorker via `@livequery/rpc`, the setup inside the worker is different — but from React's perspective nothing changes. You still construct a `LivequeryClient` and pass it to `LivequeryClientProvider` exactly as shown above. Read the `@livequery/rpc` documentation for how to expose the client from a SharedWorker; the React layer stays the same.
|
|
80
|
+
|
|
70
81
|
## `useLivequeryClient`
|
|
71
82
|
|
|
72
83
|
`useLivequeryClient()` reads the nearest `LivequeryClient` from `LivequeryClientProvider`.
|
|
@@ -91,7 +102,6 @@ The hook must be used under a matching provider. If it is called outside the pro
|
|
|
91
102
|
Use it when a component needs the full collection API: reactive state plus methods such as querying or mutations.
|
|
92
103
|
|
|
93
104
|
```tsx
|
|
94
|
-
import { useEffect } from 'react'
|
|
95
105
|
import { useCollection, useObservable } from '@livequery/react'
|
|
96
106
|
|
|
97
107
|
type Todo = {
|
|
@@ -101,15 +111,12 @@ type Todo = {
|
|
|
101
111
|
}
|
|
102
112
|
|
|
103
113
|
export function TodoList() {
|
|
114
|
+
// lazy: false — collection queries automatically on initialization
|
|
104
115
|
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
105
116
|
const items = useObservable(collection.items, [])
|
|
106
117
|
const loading = useObservable(collection.loading, false)
|
|
107
118
|
const error = useObservable(collection.error)
|
|
108
119
|
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
collection.query()
|
|
111
|
-
}, [collection])
|
|
112
|
-
|
|
113
120
|
if (loading) return <p>Loading...</p>
|
|
114
121
|
if (error) return <p>Could not load todos.</p>
|
|
115
122
|
|
|
@@ -123,6 +130,8 @@ export function TodoList() {
|
|
|
123
130
|
}
|
|
124
131
|
```
|
|
125
132
|
|
|
133
|
+
When `lazy: false`, the collection queries automatically when initialized — no `useEffect` or manual `collection.query()` call is needed. Use `lazy: true` (the default) when you need to control when the query fires, such as after user interaction or after other async setup completes.
|
|
134
|
+
|
|
126
135
|
Behavior notes:
|
|
127
136
|
|
|
128
137
|
- `ref` may be `undefined`, `null`, `false`, or an empty string. Falsy refs skip initialization.
|
|
@@ -135,30 +144,39 @@ Behavior notes:
|
|
|
135
144
|
|
|
136
145
|
`useDocument<T>(ref, options)` is a document-focused convenience wrapper over `useCollection()`.
|
|
137
146
|
|
|
138
|
-
It initializes a collection for a document ref, subscribes to collection items and
|
|
147
|
+
It initializes a collection for a document ref, subscribes to collection items, loading state, and error state, then returns `[items[0], loading, error]`.
|
|
139
148
|
|
|
140
|
-
Use it when a component only needs one document
|
|
149
|
+
Use it when a component only needs one document, a loading flag, and basic error handling.
|
|
141
150
|
|
|
142
151
|
```tsx
|
|
143
152
|
import { useDocument } from '@livequery/react'
|
|
144
153
|
|
|
145
154
|
type Todo = {
|
|
146
|
-
|
|
155
|
+
id: string
|
|
147
156
|
title: string
|
|
148
157
|
done: boolean
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
export function TodoDetail({ id }: { id: string }) {
|
|
152
|
-
const [todo, loading] = useDocument<Todo>(`todos/${id}`)
|
|
161
|
+
const [todo, loading, error] = useDocument<Todo>(`todos/${id}`)
|
|
153
162
|
|
|
154
163
|
if (loading) return <p>Loading...</p>
|
|
164
|
+
if (error) return <p>Error: {error.message}</p>
|
|
155
165
|
if (!todo) return <p>Not found</p>
|
|
156
166
|
|
|
157
|
-
return <h1>{todo.title}</h1>
|
|
167
|
+
return <h1>{todo.value.title}</h1>
|
|
158
168
|
}
|
|
159
169
|
```
|
|
160
170
|
|
|
161
|
-
|
|
171
|
+
The return tuple:
|
|
172
|
+
|
|
173
|
+
| Index | Type | Value |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `0` | `LivequeryDocument<DocState<T>> \| undefined` | The first document in the collection, or `undefined` when not yet loaded |
|
|
176
|
+
| `1` | `LivequeryLoadingState \| null` | Loading state: `null` when idle, `"all"` while the query is in flight |
|
|
177
|
+
| `2` | `{ code: string; message: string } \| null` | Error from the last query, or `null` when no error |
|
|
178
|
+
|
|
179
|
+
Use `useCollection()` instead when you need collection methods, multiple documents, or more control over subscriptions.
|
|
162
180
|
|
|
163
181
|
## `useObservable`
|
|
164
182
|
|
|
@@ -189,101 +207,305 @@ const lazyValue = useObservable(() => source$)
|
|
|
189
207
|
Behavior notes:
|
|
190
208
|
|
|
191
209
|
- `BehaviorSubject` is treated specially. Its initial value is read with `getValue()` so the first render can use the current value.
|
|
192
|
-
- Lazy sources are resolved once for the hook lifetime.
|
|
210
|
+
- Lazy sources are resolved once for the hook lifetime. The source function is called only on the first render and is not re-called if the function reference changes later. If you need a different source, the component must remount.
|
|
193
211
|
- If the source is `undefined`, the hook returns the default value, or `undefined` if no default was provided.
|
|
194
212
|
- Reading `.value` or `.getValue()` manually in render is not a replacement for `useObservable()` because it will not subscribe the component to future emissions.
|
|
195
213
|
|
|
196
|
-
##
|
|
214
|
+
## 7 Rules for Using @livequery/react
|
|
215
|
+
|
|
216
|
+
These rules are mandatory. Breaking any one of them causes unnecessary re-renders, unhandled errors, or hard-to-debug state bugs.
|
|
197
217
|
|
|
198
|
-
|
|
218
|
+
### Why two levels?
|
|
199
219
|
|
|
200
|
-
|
|
220
|
+
`collection.items` is a `BehaviorSubject<LivequeryDocument<T>[]>`.
|
|
201
221
|
|
|
202
|
-
- The outer `BehaviorSubject` emits a new array only when items are added, removed, or reordered.
|
|
203
|
-
- Each element
|
|
204
|
-
- A field update on one
|
|
222
|
+
- The **outer** `BehaviorSubject` emits a new array only when items are added, removed, or reordered.
|
|
223
|
+
- Each **element** is a `LivequeryDocument<T>` — itself a `BehaviorSubject<DocState<T>>` — that emits when that specific document's fields change.
|
|
224
|
+
- A field update on one document does **not** cause the outer array to emit. Only that document's own subject emits.
|
|
205
225
|
|
|
206
|
-
This means
|
|
226
|
+
This means re-renders can be scoped to exactly the component that owns the changed data — but only if you follow the rules below.
|
|
207
227
|
|
|
208
|
-
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### Rule 1 — `LivequeryClientProvider` is required
|
|
231
|
+
|
|
232
|
+
Every hook in this package reads the nearest `LivequeryClient` from context. There is no fallback. Using any hook outside a provider throws `Context provider is missing`.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
// app root or route boundary
|
|
236
|
+
<LivequeryClientProvider core={client}>
|
|
237
|
+
<YourApp />
|
|
238
|
+
</LivequeryClientProvider>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Create one client per data boundary, not one per component. The client holds the connection and cache — recreating it on every render loses all state.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### Rule 2 — SharedWorker: setup differs, React API stays the same
|
|
246
|
+
|
|
247
|
+
If your app runs `@livequery/client` inside a SharedWorker via `@livequery/rpc`, the worker setup is different. From React's perspective nothing changes — you still receive a `LivequeryClient` and pass it to `LivequeryClientProvider` exactly as normal. Read the `@livequery/rpc` docs for the worker side.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Rule 3 — Place `useCollection` in the component that owns the list
|
|
252
|
+
|
|
253
|
+
`useCollection` belongs in the component that renders the list with `.map()`. Do not call it in a parent and pass the collection down as a prop — the collection is created once for that component's lifetime.
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
// ✓ correct — useCollection lives where the list is rendered
|
|
257
|
+
export function TodoList() {
|
|
258
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
259
|
+
const items = useObservable(collection.items, [])
|
|
260
|
+
return <ul>{items.map(item => <TodoItem key={item.value.id} item={item} />)}</ul>
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ✗ wrong — collection created in parent, passed as prop
|
|
264
|
+
export function Parent() {
|
|
265
|
+
const collection = useCollection<Todo>('todos')
|
|
266
|
+
return <TodoList collection={collection} />
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### Rule 4 — Unwrap `items` with `useObservable`
|
|
273
|
+
|
|
274
|
+
`collection.items` is a `BehaviorSubject`. You must subscribe to it with `useObservable` to get the current array and re-render when items are added, removed, or reordered.
|
|
209
275
|
|
|
210
276
|
```tsx
|
|
211
277
|
const items = useObservable(collection.items, [])
|
|
212
|
-
// items
|
|
213
|
-
//
|
|
278
|
+
// items: LivequeryDocument<Todo>[]
|
|
279
|
+
// re-renders ONLY when count or order changes — not on field updates
|
|
214
280
|
```
|
|
215
281
|
|
|
216
|
-
|
|
282
|
+
Never read `collection.items.value` directly in render. It gives a snapshot that does not update.
|
|
283
|
+
|
|
284
|
+
---
|
|
217
285
|
|
|
218
|
-
|
|
286
|
+
### Rule 5 — Never call `.value` or `.getValue()` inside `.map()` — delegate to a child component
|
|
287
|
+
|
|
288
|
+
Each element of `items` is a `LivequeryDocument<T>` (a `BehaviorSubject`). Calling `.value` or `.getValue()` inside the parent map reads the value once — it does not subscribe, so field changes will not re-render.
|
|
289
|
+
|
|
290
|
+
Pass the document to a child component and call `useObservable` inside:
|
|
219
291
|
|
|
220
292
|
```tsx
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
293
|
+
// ✗ wrong — reads once, misses future field updates
|
|
294
|
+
{items.map(item => <li key={item.value.id}>{item.value.title}</li>)}
|
|
295
|
+
|
|
296
|
+
// ✗ also wrong — useObservable in parent map re-renders the whole list on any field change
|
|
297
|
+
{items.map(item => {
|
|
298
|
+
const todo = useObservable(item)
|
|
299
|
+
return <li key={todo.id}>{todo.title}</li>
|
|
300
|
+
})}
|
|
301
|
+
|
|
302
|
+
// ✓ correct — field updates re-render only TodoItem, not the list
|
|
303
|
+
{items.map(item => <TodoItem key={item.value.id} item={item} />)}
|
|
304
|
+
|
|
305
|
+
function TodoItem({ item }: { item: LivequeryDocument<Todo> }) {
|
|
306
|
+
const todo = useObservable(item) // subscribes inside the child
|
|
307
|
+
return <li>{todo.title}</li>
|
|
224
308
|
}
|
|
225
309
|
```
|
|
226
310
|
|
|
227
|
-
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Rule 6 — `loading`, `paging`, and `summary` also belong in separate child components
|
|
228
314
|
|
|
229
|
-
`collection.loading`
|
|
315
|
+
`collection.loading`, `collection.paging`, and `collection.summary` are all `BehaviorSubject`s. Calling `useObservable` on them in the same component as `items` means every loading toggle or paging update re-renders the entire list.
|
|
230
316
|
|
|
231
317
|
```tsx
|
|
232
|
-
|
|
318
|
+
// ✗ wrong — loading change re-renders the full list
|
|
319
|
+
export function TodoList() {
|
|
320
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
321
|
+
const items = useObservable(collection.items, [])
|
|
322
|
+
const loading = useObservable(collection.loading) // ← causes full re-render on change
|
|
323
|
+
const paging = useObservable(collection.paging) // ← same
|
|
324
|
+
...
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ✓ correct — each subject in its own component
|
|
328
|
+
function TodoLoading({ loading$ }: { loading$: LivequeryCollection<Todo>['loading'] }) {
|
|
233
329
|
const loading = useObservable(loading$)
|
|
234
330
|
if (!loading) return null
|
|
235
331
|
return <p>Loading...</p>
|
|
236
332
|
}
|
|
333
|
+
|
|
334
|
+
function TodoPaging({ paging$ }: { paging$: LivequeryCollection<Todo>['paging'] }) {
|
|
335
|
+
const paging = useObservable(paging$)
|
|
336
|
+
return <p>{paging.current} / {paging.total}</p>
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function TodoSummary({ summary$ }: { summary$: LivequeryCollection<Todo>['summary'] }) {
|
|
340
|
+
const summary = useObservable(summary$)
|
|
341
|
+
return <p>Open: {summary.open}</p>
|
|
342
|
+
}
|
|
237
343
|
```
|
|
238
344
|
|
|
239
|
-
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
### Full compliant example
|
|
240
348
|
|
|
241
349
|
```tsx
|
|
242
|
-
import {
|
|
243
|
-
import { BehaviorSubject } from 'rxjs'
|
|
350
|
+
import { LivequeryDocument } from '@livequery/client'
|
|
244
351
|
import { useCollection, useObservable } from '@livequery/react'
|
|
245
352
|
|
|
246
|
-
type Todo = {
|
|
353
|
+
type Todo = { id: string; title: string; done: boolean }
|
|
354
|
+
|
|
355
|
+
function TodoItem({ item }: { item: LivequeryDocument<Todo> }) {
|
|
356
|
+
const todo = useObservable(item)
|
|
357
|
+
return (
|
|
358
|
+
<li>
|
|
359
|
+
<input
|
|
360
|
+
type="checkbox"
|
|
361
|
+
checked={todo.done}
|
|
362
|
+
onChange={() => item.update({ done: !todo.done })}
|
|
363
|
+
/>
|
|
364
|
+
{todo.title}
|
|
365
|
+
{todo._updating && ' Saving…'}
|
|
366
|
+
</li>
|
|
367
|
+
)
|
|
368
|
+
}
|
|
247
369
|
|
|
248
|
-
function TodoLoading({ loading$ }: { loading$:
|
|
370
|
+
function TodoLoading({ loading$ }: { loading$: LivequeryCollection<Todo>['loading'] }) {
|
|
249
371
|
const loading = useObservable(loading$)
|
|
250
|
-
|
|
251
|
-
return <p>Loading...</p>
|
|
372
|
+
return loading ? <p>Loading…</p> : null
|
|
252
373
|
}
|
|
253
374
|
|
|
254
|
-
function
|
|
255
|
-
const
|
|
256
|
-
return
|
|
375
|
+
function TodoPaging({ paging$, onMore }: { paging$: LivequeryCollection<Todo>['paging'], onMore: () => void }) {
|
|
376
|
+
const paging = useObservable(paging$)
|
|
377
|
+
if (!paging.next) return null
|
|
378
|
+
return <button onClick={onMore}>Load more ({paging.total - paging.current} left)</button>
|
|
257
379
|
}
|
|
258
380
|
|
|
259
381
|
export function TodoList() {
|
|
260
|
-
const collection = useCollection<Todo>('todos')
|
|
382
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
261
383
|
const items = useObservable(collection.items, [])
|
|
262
384
|
|
|
263
|
-
useEffect(() => {
|
|
264
|
-
collection.query()
|
|
265
|
-
}, [collection])
|
|
266
|
-
|
|
267
385
|
return (
|
|
268
386
|
<>
|
|
269
387
|
<TodoLoading loading$={collection.loading} />
|
|
270
388
|
<ul>
|
|
271
|
-
{items.map(
|
|
272
|
-
<TodoItem key={item
|
|
389
|
+
{items.map(item => (
|
|
390
|
+
<TodoItem key={item.value.id} item={item} />
|
|
273
391
|
))}
|
|
274
392
|
</ul>
|
|
393
|
+
<TodoPaging paging$={collection.paging} onMore={() => collection.loadMore()} />
|
|
275
394
|
</>
|
|
276
395
|
)
|
|
277
396
|
}
|
|
278
397
|
```
|
|
279
398
|
|
|
280
|
-
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### Rule 7 — Wrap every action in `useAction`
|
|
402
|
+
|
|
403
|
+
Any call to `collection.add()`, `collection.update()`, `collection.delete()`, `collection.trigger()`, `item.update()`, `item.del()`, or `item.trigger()` is an async operation that can fail. Calling these directly in an event handler means:
|
|
404
|
+
|
|
405
|
+
- No loading state — UI has no way to show a spinner or disable the button
|
|
406
|
+
- No error state — a rejected promise becomes an unhandled exception that can crash the component
|
|
407
|
+
- Race conditions — two rapid clicks fire two concurrent calls with no guard
|
|
281
408
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
409
|
+
Wrap every action in `useAction` to get `loading`, `data`, and `error` as React state, with automatic race protection (only the latest call updates state).
|
|
410
|
+
|
|
411
|
+
`useAction` holds state (`loading`, `data`, `error`) that changes every time the action is called. Place it in the **same component as the button**, never in the component that owns `items`. If `useAction` lives in the list parent, every action call re-renders the entire list.
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
// ✗ wrong — unhandled rejection, no loading state, can crash
|
|
415
|
+
function AddButton({ collection }: { collection: LivequeryCollection<Todo> }) {
|
|
416
|
+
return (
|
|
417
|
+
<button onClick={() => collection.add({ title: 'New', done: false })}>
|
|
418
|
+
Add
|
|
419
|
+
</button>
|
|
420
|
+
)
|
|
421
|
+
}
|
|
285
422
|
|
|
286
|
-
|
|
423
|
+
// ✗ also wrong — useAction in the list parent re-renders the whole list on every call
|
|
424
|
+
function TodoList() {
|
|
425
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
426
|
+
const add = useAction(() => collection.add({ title: 'New', done: false })) // ← wrong place
|
|
427
|
+
const items = useObservable(collection.items, [])
|
|
428
|
+
return (
|
|
429
|
+
<>
|
|
430
|
+
<button onClick={() => void add()}>Add</button>
|
|
431
|
+
<ul>{items.map(item => <TodoItem key={item.value.id} item={item} />)}</ul>
|
|
432
|
+
</>
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ✓ correct — useAction lives in its own button component, list never re-renders for it
|
|
437
|
+
function AddButton({ collection }: { collection: LivequeryCollection<Todo> }) {
|
|
438
|
+
const add = useAction(() => collection.add({ title: 'New', done: false }))
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
<>
|
|
442
|
+
<button disabled={add.loading} onClick={() => void add()}>
|
|
443
|
+
{add.loading ? 'Adding…' : 'Add'}
|
|
444
|
+
</button>
|
|
445
|
+
{add.error && <p>Error: {add.error.message}</p>}
|
|
446
|
+
</>
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function TodoList() {
|
|
451
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
452
|
+
const items = useObservable(collection.items, [])
|
|
453
|
+
return (
|
|
454
|
+
<>
|
|
455
|
+
<AddButton collection={collection} />
|
|
456
|
+
<ul>{items.map(item => <TodoItem key={item.value.id} item={item} />)}</ul>
|
|
457
|
+
</>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The same applies to document-level actions:
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
function TodoItem({ item }: { item: LivequeryDocument<Todo> }) {
|
|
466
|
+
const todo = useObservable(item)
|
|
467
|
+
|
|
468
|
+
const toggle = useAction(() => item.update({ done: !todo.done }))
|
|
469
|
+
const remove = useAction(() => item.del())
|
|
470
|
+
const archive = useAction(() => item.trigger('archive'))
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<li>
|
|
474
|
+
<input type="checkbox" checked={todo.done} disabled={toggle.loading} onChange={() => void toggle()} />
|
|
475
|
+
{todo.title}
|
|
476
|
+
<button disabled={remove.loading} onClick={() => void remove()}>
|
|
477
|
+
{remove.loading ? 'Deleting…' : 'Delete'}
|
|
478
|
+
</button>
|
|
479
|
+
{toggle.error && <span>Save failed: {toggle.error.code}</span>}
|
|
480
|
+
{remove.error && <span>Delete failed: {remove.error.code}</span>}
|
|
481
|
+
</li>
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
And for collection triggers:
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
function ArchiveAllButton({ collection }: { collection: LivequeryCollection<Todo> }) {
|
|
490
|
+
const archive = useAction(
|
|
491
|
+
() => collection.trigger<{ count: number }>('archive-done'),
|
|
492
|
+
{ onError: (e) => console.error('Archive failed', e) }
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
return (
|
|
496
|
+
<>
|
|
497
|
+
<button disabled={archive.loading} onClick={() => void archive()}>
|
|
498
|
+
{archive.loading ? 'Archiving…' : 'Archive done'}
|
|
499
|
+
</button>
|
|
500
|
+
{archive.data && <p>Archived {archive.data.count} items</p>}
|
|
501
|
+
</>
|
|
502
|
+
)
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
`useAction` accepts any async function, so it works for non-Livequery async operations too (form submissions, file uploads, etc.).
|
|
507
|
+
|
|
508
|
+
---
|
|
287
509
|
|
|
288
510
|
## `useAction`
|
|
289
511
|
|
|
@@ -357,14 +579,245 @@ Behavior notes:
|
|
|
357
579
|
|
|
358
580
|
`LivequeryClientProvider` and `useLivequeryClient` are built with this helper.
|
|
359
581
|
|
|
582
|
+
## `useCollection` vs `useDocument`
|
|
583
|
+
|
|
584
|
+
**Use `useCollection` when you need a list (plural).**
|
|
585
|
+
**Use `useDocument` when you need one item (singular).**
|
|
586
|
+
|
|
587
|
+
```tsx
|
|
588
|
+
// list — collection ref has an odd number of segments
|
|
589
|
+
const collection = useCollection<Todo>('todos')
|
|
590
|
+
const collection = useCollection<Post>('users/u1/posts')
|
|
591
|
+
|
|
592
|
+
// single document — document ref has an even number of segments
|
|
593
|
+
const [todo, loading, error] = useDocument<Todo>('todos/todo-1')
|
|
594
|
+
const [post, loading, error] = useDocument<Post>('users/u1/posts/post-1')
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
`useDocument` is a thin wrapper over `useCollection`. Under the hood it creates the same collection but exposes only `[firstItem, loading, error]`. Use `useCollection` when you need methods like `add`, `update`, `delete`, `sort`, or `loadMore`. Use `useDocument` when you only need to read or mutate one document through `item.update()` and `item.del()`.
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## TypeScript
|
|
602
|
+
|
|
603
|
+
### Define your document type with `Doc`
|
|
604
|
+
|
|
605
|
+
Import `Doc` from `@livequery/client` and extend it. Every document must have an `id: string` field — `Doc<T>` adds it automatically.
|
|
606
|
+
|
|
607
|
+
```tsx
|
|
608
|
+
import type { Doc } from '@livequery/client'
|
|
609
|
+
|
|
610
|
+
type Todo = Doc<{
|
|
611
|
+
title: string
|
|
612
|
+
done: boolean
|
|
613
|
+
createdAt: number
|
|
614
|
+
}>
|
|
615
|
+
|
|
616
|
+
// use the type with hooks
|
|
617
|
+
const collection = useCollection<Todo>('todos')
|
|
618
|
+
const [todo] = useDocument<Todo>('todos/todo-1')
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Import types, not values
|
|
622
|
+
|
|
623
|
+
Only import types from `@livequery/client` in component files — the runtime objects (`LivequeryClient`, `LivequeryCollection`) live in your setup files, not in every component.
|
|
624
|
+
|
|
625
|
+
```tsx
|
|
626
|
+
// ✓ correct — type-only imports in components
|
|
627
|
+
import type { Doc, DocState, LivequeryDocument, LivequeryCollection } from '@livequery/client'
|
|
628
|
+
|
|
629
|
+
// ✗ wrong — importing runtime values you don't construct here
|
|
630
|
+
import { LivequeryClient, LivequeryCollection } from '@livequery/client'
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Type props that receive collection or document
|
|
634
|
+
|
|
635
|
+
```tsx
|
|
636
|
+
import type { LivequeryCollection, LivequeryDocument, Doc } from '@livequery/client'
|
|
637
|
+
|
|
638
|
+
type Todo = Doc<{ title: string; done: boolean }>
|
|
639
|
+
|
|
640
|
+
// list component receives a typed collection
|
|
641
|
+
function TodoList({ collection }: { collection: LivequeryCollection<Todo> }) { ... }
|
|
642
|
+
|
|
643
|
+
// item component receives a typed document subject
|
|
644
|
+
function TodoItem({ item }: { item: LivequeryDocument<Todo> }) { ... }
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## Document States
|
|
650
|
+
|
|
651
|
+
Every document in `items` is a `DocState<T>` which includes internal fields set by the client during optimistic mutations. Use these to show pending and error states in the UI.
|
|
652
|
+
|
|
653
|
+
| Field | When set | What to show |
|
|
654
|
+
|---|---|---|
|
|
655
|
+
| `_adding` | `local-first` add in progress | "Saving…", spinner, disable form |
|
|
656
|
+
| `_adding_error` | Server rejected the add | Error message, retry button |
|
|
657
|
+
| `_updating` | `local-first` update in progress | "Saving…", field disabled |
|
|
658
|
+
| `_updating_error` | Server rejected the update | Error message next to the field |
|
|
659
|
+
| `_deleting` | `local-first` delete in progress | Row dimmed, "Deleting…" |
|
|
660
|
+
| `_deleting_error` | Server rejected the delete | Error message, undo button |
|
|
661
|
+
| `_local_only` | Created with `local-only` mode | "Draft", "Unsaved" badge |
|
|
662
|
+
|
|
663
|
+
```tsx
|
|
664
|
+
function TodoItem({ item }: { item: LivequeryDocument<Todo> }) {
|
|
665
|
+
const todo = useObservable(item)
|
|
666
|
+
|
|
667
|
+
const toggle = useAction(() => item.update({ done: !todo.done }))
|
|
668
|
+
const remove = useAction(() => item.del())
|
|
669
|
+
|
|
670
|
+
return (
|
|
671
|
+
<li style={{ opacity: todo._deleting ? 0.4 : 1 }}>
|
|
672
|
+
<input
|
|
673
|
+
type="checkbox"
|
|
674
|
+
checked={todo.done}
|
|
675
|
+
disabled={toggle.loading || !!todo._updating}
|
|
676
|
+
onChange={() => void toggle()}
|
|
677
|
+
/>
|
|
678
|
+
|
|
679
|
+
<span>{todo.title}</span>
|
|
680
|
+
|
|
681
|
+
{todo._local_only && <span className="badge">Draft</span>}
|
|
682
|
+
{todo._updating && <span>Saving…</span>}
|
|
683
|
+
{todo._updating_error && <span>Save failed: {todo._updating_error.message}</span>}
|
|
684
|
+
|
|
685
|
+
<button disabled={remove.loading} onClick={() => void remove()}>
|
|
686
|
+
{todo._deleting ? 'Deleting…' : 'Delete'}
|
|
687
|
+
</button>
|
|
688
|
+
{todo._deleting_error && <span>Delete failed — {todo._deleting_error.message}</span>}
|
|
689
|
+
</li>
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Note: `_adding`, `_updating`, `_deleting` are set by `@livequery/client` during optimistic mutations. They are cleared automatically when the server responds. You do not need to manage them manually.
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## Common Patterns
|
|
699
|
+
|
|
700
|
+
### Conditional ref — wait for ID or auth before initializing
|
|
701
|
+
|
|
702
|
+
Pass a falsy value as `ref` to skip initialization. The collection stays empty with no loading state until `ref` becomes truthy.
|
|
703
|
+
|
|
704
|
+
```tsx
|
|
705
|
+
function UserTodos({ userId }: { userId: string | null }) {
|
|
706
|
+
// does not initialize until userId is available
|
|
707
|
+
const collection = useCollection<Todo>(userId && `users/${userId}/todos`, { lazy: false })
|
|
708
|
+
const items = useObservable(collection.items, [])
|
|
709
|
+
return <ul>{items.map(item => <TodoItem key={item.value.id} item={item} />)}</ul>
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
Accepted falsy values: `undefined`, `null`, `false`, `''`. Any of these skips `initialize()`.
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
### Search with debounce — filter without calling server on every keystroke
|
|
718
|
+
|
|
719
|
+
Create the collection with a `debounce` value (milliseconds), then call `debounceQuery` on every input change. The actual query fires only after the user stops typing.
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
function TodoSearch() {
|
|
723
|
+
const collection = useCollection<Todo>('todos', { debounce: 300 })
|
|
724
|
+
const items = useObservable(collection.items, [])
|
|
725
|
+
|
|
726
|
+
return (
|
|
727
|
+
<>
|
|
728
|
+
<input
|
|
729
|
+
placeholder="Search…"
|
|
730
|
+
onChange={e => collection.debounceQuery({ 'title:like': e.target.value })}
|
|
731
|
+
/>
|
|
732
|
+
<ul>{items.map(item => <TodoItem key={item.value.id} item={item} />)}</ul>
|
|
733
|
+
</>
|
|
734
|
+
)
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
`debounceQuery` does nothing unless the collection was created with `debounce: <ms>`.
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
### Pagination — load more / infinite scroll
|
|
743
|
+
|
|
744
|
+
Use `collection.paging` to check whether more pages are available, then call `loadMore()`. Put the button and paging state in their own component so page changes do not re-render the list.
|
|
745
|
+
|
|
746
|
+
```tsx
|
|
747
|
+
function TodoPaging({ collection }: { collection: LivequeryCollection<Todo> }) {
|
|
748
|
+
const paging = useObservable(collection.paging)
|
|
749
|
+
const loadMore = useAction(() => collection.loadMore())
|
|
750
|
+
|
|
751
|
+
if (!paging?.next) return null
|
|
752
|
+
|
|
753
|
+
return (
|
|
754
|
+
<button disabled={loadMore.loading} onClick={() => void loadMore()}>
|
|
755
|
+
{loadMore.loading ? 'Loading…' : `Load more (${paging.total - paging.current} left)`}
|
|
756
|
+
</button>
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function TodoList() {
|
|
761
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
762
|
+
const items = useObservable(collection.items, [])
|
|
763
|
+
|
|
764
|
+
return (
|
|
765
|
+
<>
|
|
766
|
+
<ul>{items.map(item => <TodoItem key={item.value.id} item={item} />)}</ul>
|
|
767
|
+
<TodoPaging collection={collection} />
|
|
768
|
+
</>
|
|
769
|
+
)
|
|
770
|
+
}
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
`loadMore()` appends results — it does not replace `items`. Use `loadPrev()` for the previous page.
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
### Mutations without querying (lazy collection)
|
|
778
|
+
|
|
779
|
+
Use `lazy: true` (the default) when you only need `add`, `update`, or `delete` and do not need the query results in this component. The collection is initialized but does not fire a query, so no network request is made and no items are loaded.
|
|
780
|
+
|
|
781
|
+
```tsx
|
|
782
|
+
// A floating "Add" button that writes to a collection without reading it
|
|
783
|
+
function AddTodoButton() {
|
|
784
|
+
const collection = useCollection<Todo>('todos') // lazy: true by default — no query
|
|
785
|
+
const add = useAction(() => collection.add({ title: 'New todo', done: false }))
|
|
786
|
+
|
|
787
|
+
return (
|
|
788
|
+
<button disabled={add.loading} onClick={() => void add()}>
|
|
789
|
+
{add.loading ? 'Adding…' : 'Add Todo'}
|
|
790
|
+
</button>
|
|
791
|
+
)
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
This avoids an unnecessary fetch. The collection is still connected to the client, so any local-only items you add will be visible to other collection instances on the same client that _do_ query the same ref.
|
|
796
|
+
|
|
797
|
+
> **Always wrap mutations in `useAction`** — it gives you `loading`, `error`, and race protection with no extra code.
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
### Automatic cleanup
|
|
802
|
+
|
|
803
|
+
`useCollection` registers a subscription with the client on mount and unsubscribes automatically on unmount. You do not need to write any cleanup code.
|
|
804
|
+
|
|
805
|
+
```tsx
|
|
806
|
+
// this is enough — no useEffect cleanup needed
|
|
807
|
+
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
808
|
+
// ↑ subscribes on mount, unsubscribes on unmount automatically
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
360
813
|
## Choosing The Right API
|
|
361
814
|
|
|
362
815
|
- Use `LivequeryClientProvider` once near the app or data boundary.
|
|
363
816
|
- Use `useLivequeryClient()` only when you need direct client access.
|
|
364
|
-
- Use `useCollection()` when
|
|
365
|
-
- Use `useDocument()` when you
|
|
817
|
+
- Use `useCollection()` when rendering a list or when you need collection methods.
|
|
818
|
+
- Use `useDocument()` when you need a single document — it returns `[doc, loading, error]`.
|
|
366
819
|
- Use `useObservable()` whenever an RxJS source should drive rendering.
|
|
367
|
-
- Use `useAction()` for async
|
|
820
|
+
- Use `useAction()` for every async action — never call mutations directly in event handlers.
|
|
368
821
|
- Use `createContextFromHook()` for package or app utilities that should expose provider plus hook pairs.
|
|
369
822
|
|
|
370
823
|
## Common Mistakes
|
|
@@ -373,7 +826,8 @@ Behavior notes:
|
|
|
373
826
|
- Calling collection mutations directly during render.
|
|
374
827
|
- Reading `BehaviorSubject` values manually and expecting rerenders.
|
|
375
828
|
- Passing changing `useCollection()` options and expecting the existing collection instance to rebuild.
|
|
376
|
-
- Using `
|
|
829
|
+
- Using `useCollection()` when `useDocument()` is sufficient — `useDocument` now returns `[doc, loading, error]` and handles the common case.
|
|
830
|
+
- Using `useDocument()` when you need collection methods (`add`, `update`, `delete`, `sort`, `loadMore`). For mutations, use `useCollection()` and get the document from `collection.items.value[0]`.
|
|
377
831
|
- Importing APIs not listed in the `Exports` section.
|
|
378
832
|
- Calling `useObservable(item$)` for each item inside the parent `.map()` instead of delegating to a child component — this causes the entire list to re-render on every field change of any single item.
|
|
379
833
|
- Observing `collection.loading` in the same component as the item list — loading state changes then re-render the full list.
|
package/dist/useAction.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,UAAS,OAAO,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAM;aAGxG,OAAO;WACT,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACrB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;
|
|
1
|
+
{"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,UAAS,OAAO,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAM;aAGxG,OAAO;WACT,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACrB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAsBhD,CAAA"}
|
package/dist/useAction.js
CHANGED
|
@@ -15,7 +15,9 @@ export const useAction = (fn, options = {}) => {
|
|
|
15
15
|
catch (error) {
|
|
16
16
|
options.onError?.(error);
|
|
17
17
|
if (currentRequestId === requestId.current) {
|
|
18
|
-
|
|
18
|
+
const code = error?.code ?? 'error';
|
|
19
|
+
const message = error instanceof Error ? error.message : (error?.message ?? String(error));
|
|
20
|
+
set_state({ loading: false, error: { code, message } });
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
};
|
package/dist/useAction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAction.js","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExC,MAAM,CAAC,MAAM,SAAS,GAAG,CAA6C,EAAK,EAAE,UAA0C,EAAE,EAAE,EAAE;IACzH,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC3B,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,QAAQ,CAIhC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAEtB,MAAM,CAAC,GAAG,KAAK,EAAE,GAAG,IAAS,EAAE,EAAE;QAC7B,MAAM,gBAAgB,GAAG,EAAE,SAAS,CAAC,OAAO,CAAA;QAC5C,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;YAC9B,IAAI,gBAAgB,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;gBACzC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YACvC,CAAC;YACD,OAAO,IAAI,CAAA;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;YACxB,IAAI,gBAAgB,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;gBACzC,
|
|
1
|
+
{"version":3,"file":"useAction.js","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExC,MAAM,CAAC,MAAM,SAAS,GAAG,CAA6C,EAAK,EAAE,UAA0C,EAAE,EAAE,EAAE;IACzH,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC3B,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,QAAQ,CAIhC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAEtB,MAAM,CAAC,GAAG,KAAK,EAAE,GAAG,IAAS,EAAE,EAAE;QAC7B,MAAM,gBAAgB,GAAG,EAAE,SAAS,CAAC,OAAO,CAAA;QAC5C,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;YAC9B,IAAI,gBAAgB,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;gBACzC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YACvC,CAAC;YACD,OAAO,IAAI,CAAA;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;YACxB,IAAI,gBAAgB,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;gBACzC,MAAM,IAAI,GAAI,KAAa,EAAE,IAAI,IAAI,OAAO,CAAA;gBAC5C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,KAAa,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;gBACnG,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YAC3D,CAAC;QACL,CAAC;IACL,CAAC,CAAA;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAM,EAAE,KAAK,CAAC,CAAA;AACvC,CAAC,CAAA"}
|
package/dist/useDocument.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type Doc } from "@livequery/client";
|
|
2
|
-
export declare const useDocument: <T extends Doc>(ref: string | undefined | "" | null | false, options?: {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { type Doc, type LivequeryCollectionOptions } from "@livequery/client";
|
|
2
|
+
export declare const useDocument: <T extends Doc>(ref: string | undefined | "" | null | false, options?: Pick<Partial<LivequeryCollectionOptions<T>>, "lazy" | "mode" | "seed">) => readonly [import("@livequery/client").LivequeryDocument<import("@livequery/client").DocState<T>> | undefined, import("@livequery/client").LivequeryLoadingState, {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
} | null];
|
|
5
6
|
//# sourceMappingURL=useDocument.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDocument.d.ts","sourceRoot":"","sources":["../src/useDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"useDocument.d.ts","sourceRoot":"","sources":["../src/useDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAK7E,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,GAAG,EAAE,KAAK,MAAM,GAAG,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,KAAK,EAAE,UAAS,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAM;;;SAM3K,CAAA"}
|
package/dist/useDocument.js
CHANGED
|
@@ -2,9 +2,10 @@ import {} from "@livequery/client";
|
|
|
2
2
|
import { useObservable } from "./useObservable.js";
|
|
3
3
|
import { useCollection } from "./useCollection.js";
|
|
4
4
|
export const useDocument = (ref, options = {}) => {
|
|
5
|
-
const collection = useCollection(ref,
|
|
5
|
+
const collection = useCollection(ref, options);
|
|
6
6
|
const items = useObservable(collection.items);
|
|
7
7
|
const loading = useObservable(collection.loading);
|
|
8
|
-
|
|
8
|
+
const error = useObservable(collection.error);
|
|
9
|
+
return [items[0], loading, error];
|
|
9
10
|
};
|
|
10
11
|
//# sourceMappingURL=useDocument.js.map
|
package/dist/useDocument.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDocument.js","sourceRoot":"","sources":["../src/useDocument.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"useDocument.js","sourceRoot":"","sources":["../src/useDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,MAAM,mBAAmB,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAgB,GAA2C,EAAE,UAAkF,EAAE,EAAE,EAAE;IAC5K,MAAM,UAAU,GAAG,aAAa,CAAI,GAAG,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IAC7C,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAU,CAAA;AAC9C,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"url": "https://github.com/livequery/react"
|
|
5
5
|
},
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "2.0.
|
|
7
|
+
"version": "2.0.139",
|
|
8
8
|
"description": "",
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
"dist/**/*"
|
|
50
50
|
],
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@livequery/client": "^2.0.
|
|
52
|
+
"@livequery/client": "^2.0.139",
|
|
53
53
|
"@types/bun": "^1.3.14",
|
|
54
54
|
"@types/react": "^19.2.14",
|
|
55
55
|
"@types/react-test-renderer": "^19.1.0",
|
|
56
56
|
"react-test-renderer": "^19.2.6"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@livequery/client": "^2.0.
|
|
59
|
+
"@livequery/client": "^2.0.139",
|
|
60
60
|
"react": "^19.2.5",
|
|
61
61
|
"rxjs": "^7.8.2"
|
|
62
62
|
},
|