query-optimistic 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.
- package/README.md +371 -0
- package/dist/core/index.d.mts +211 -0
- package/dist/core/index.d.ts +211 -0
- package/dist/core/index.js +245 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +237 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +513 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +503 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +213 -0
- package/dist/react/index.d.ts +213 -0
- package/dist/react/index.js +378 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +375 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/types-BRxQA1mR.d.mts +63 -0
- package/dist/types-BRxQA1mR.d.ts +63 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# query-optimistic
|
|
2
|
+
|
|
3
|
+
Simple, type-safe data fetching and optimistic updates for React.
|
|
4
|
+
|
|
5
|
+
A lightweight wrapper around TanStack Query that provides a cleaner API for defining queries, mutations, and handling optimistic updates with full TypeScript inference.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Type-safe definitions** - Define queries and mutations once, get full type inference everywhere
|
|
10
|
+
- **Simplified optimistic updates** - Imperative channel API for intuitive UI updates
|
|
11
|
+
- **Automatic rollback** - Failed mutations automatically revert optimistic changes
|
|
12
|
+
- **Multiple query sync** - Update multiple queries/entities in a single mutation
|
|
13
|
+
- **Framework agnostic core** - Core utilities work without React
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install query-optimistic @tanstack/react-query
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { defineCollection, defineMutation, useQuery, useMutation } from 'query-toolkit'
|
|
25
|
+
|
|
26
|
+
// Define a collection
|
|
27
|
+
const usersCollection = defineCollection({
|
|
28
|
+
name: 'users',
|
|
29
|
+
id: (user) => user.id,
|
|
30
|
+
fetch: () => api.get('/users')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Define a mutation
|
|
34
|
+
const createUser = defineMutation({
|
|
35
|
+
mutate: (params: { name: string }) => api.post('/users', params)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Use in components
|
|
39
|
+
function UserList() {
|
|
40
|
+
const [users, { isLoading }] = useQuery(usersCollection)
|
|
41
|
+
|
|
42
|
+
const { mutate } = useMutation(createUser, {
|
|
43
|
+
optimistic: (channel, params) => {
|
|
44
|
+
channel(usersCollection).prepend({
|
|
45
|
+
id: 'temp-' + Date.now(),
|
|
46
|
+
name: params.name,
|
|
47
|
+
}, { sync: true })
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<button onClick={() => mutate({ name: 'New User' })}>Add User</button>
|
|
54
|
+
{users?.map(user => <div key={user.id}>{user.name}</div>)}
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Defining Data Sources
|
|
61
|
+
|
|
62
|
+
### Collections (Arrays)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
interface User {
|
|
66
|
+
id: string
|
|
67
|
+
name: string
|
|
68
|
+
email: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const usersCollection = defineCollection<User, { page?: number }>({
|
|
72
|
+
name: 'users',
|
|
73
|
+
id: (user) => user.id, // Required: how to identify items
|
|
74
|
+
fetch: ({ page = 1 }) => api.get(`/users?page=${page}`)
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Entities (Single Items)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
interface Profile {
|
|
82
|
+
id: string
|
|
83
|
+
name: string
|
|
84
|
+
avatar: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const profileEntity = defineEntity<Profile, string>({
|
|
88
|
+
name: 'profile',
|
|
89
|
+
fetch: (userId) => api.get(`/users/${userId}/profile`)
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Mutations
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
interface CreateUserParams {
|
|
97
|
+
name: string
|
|
98
|
+
email: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const createUser = defineMutation<CreateUserParams, User>({
|
|
102
|
+
name: 'createUser', // Optional: used as mutation key
|
|
103
|
+
mutate: (params) => api.post('/users', params)
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Using Queries
|
|
108
|
+
|
|
109
|
+
### Basic Query
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
function UserList() {
|
|
113
|
+
const [users, { isLoading, error, refetch }] = useQuery(usersCollection)
|
|
114
|
+
|
|
115
|
+
if (isLoading) return <Loading />
|
|
116
|
+
if (error) return <Error error={error} />
|
|
117
|
+
|
|
118
|
+
return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### With Parameters
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const [users] = useQuery(usersCollection, {
|
|
126
|
+
params: { page: 2 }
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Entity Query
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const [profile] = useQuery(profileEntity, {
|
|
134
|
+
params: userId
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Paginated (Infinite) Query
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const [posts, query, pagination] = useQuery(postsCollection, {
|
|
142
|
+
paginated: true,
|
|
143
|
+
getPageParams: ({ pageParam = 1 }) => ({ page: pageParam, limit: 10 })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Load more
|
|
147
|
+
<button onClick={pagination.fetchNextPage} disabled={!pagination.hasNextPage}>
|
|
148
|
+
Load More
|
|
149
|
+
</button>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Optimistic Updates with Channel API
|
|
153
|
+
|
|
154
|
+
The channel API provides an imperative way to apply optimistic updates:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const { mutate } = useMutation(createUser, {
|
|
158
|
+
optimistic: (channel, params) => {
|
|
159
|
+
// Add to collection immediately
|
|
160
|
+
channel(usersCollection).prepend({
|
|
161
|
+
id: 'temp-' + Date.now(),
|
|
162
|
+
name: params.name,
|
|
163
|
+
email: params.email,
|
|
164
|
+
}, { sync: true }) // sync: replace with server response
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Collection Operations
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
optimistic: (channel, params) => {
|
|
173
|
+
const ch = channel(usersCollection)
|
|
174
|
+
|
|
175
|
+
// Add items
|
|
176
|
+
ch.prepend(newItem, { sync: true })
|
|
177
|
+
ch.append(newItem, { sync: true })
|
|
178
|
+
|
|
179
|
+
// Update by ID
|
|
180
|
+
ch.update(params.id, user => ({
|
|
181
|
+
...user,
|
|
182
|
+
name: params.newName
|
|
183
|
+
}))
|
|
184
|
+
|
|
185
|
+
// Delete by ID
|
|
186
|
+
ch.delete(params.id)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Entity Operations
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
optimistic: (channel, params) => {
|
|
194
|
+
channel(profileEntity).update(profile => ({
|
|
195
|
+
...profile,
|
|
196
|
+
name: params.newName
|
|
197
|
+
}))
|
|
198
|
+
|
|
199
|
+
// Or replace entirely
|
|
200
|
+
channel(profileEntity).replace(newProfile)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Multiple Updates
|
|
205
|
+
|
|
206
|
+
Update multiple collections/entities in a single mutation:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const { mutate } = useMutation(deleteUser, {
|
|
210
|
+
optimistic: (channel, params) => {
|
|
211
|
+
// Remove from users list
|
|
212
|
+
channel(usersCollection).delete(params.userId)
|
|
213
|
+
|
|
214
|
+
// Update stats
|
|
215
|
+
channel(statsEntity).update(stats => ({
|
|
216
|
+
...stats,
|
|
217
|
+
userCount: stats.userCount - 1
|
|
218
|
+
}))
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Standalone Channel (Outside Mutations)
|
|
224
|
+
|
|
225
|
+
Use the channel directly for immediate updates with manual rollback:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { channel } from 'query-toolkit'
|
|
229
|
+
|
|
230
|
+
async function handleLike(postId: string) {
|
|
231
|
+
// Apply optimistic update immediately
|
|
232
|
+
const rollback = channel(postsCollection).update(postId, post => ({
|
|
233
|
+
...post,
|
|
234
|
+
likes: post.likes + 1
|
|
235
|
+
}))
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await api.post(`/posts/${postId}/like`)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
// Undo on failure
|
|
241
|
+
rollback()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Optimistic Status
|
|
247
|
+
|
|
248
|
+
Items with pending optimistic updates include metadata for UI feedback:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
{users?.map(user => (
|
|
252
|
+
<div
|
|
253
|
+
key={user.id}
|
|
254
|
+
style={{ opacity: user._optimistic?.status === 'pending' ? 0.5 : 1 }}
|
|
255
|
+
>
|
|
256
|
+
{user.name}
|
|
257
|
+
{user._optimistic?.status === 'error' && (
|
|
258
|
+
<span className="error">Failed to save</span>
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
))}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## API Reference
|
|
265
|
+
|
|
266
|
+
### `defineCollection<TData, TParams>(config)`
|
|
267
|
+
|
|
268
|
+
| Property | Type | Description |
|
|
269
|
+
|----------|------|-------------|
|
|
270
|
+
| `name` | `string` | Unique identifier |
|
|
271
|
+
| `id` | `(item: TData) => string` | Extract ID from item |
|
|
272
|
+
| `fetch` | `(params: TParams) => Promise<TData[]>` | Fetch function |
|
|
273
|
+
|
|
274
|
+
### `defineEntity<TData, TParams>(config)`
|
|
275
|
+
|
|
276
|
+
| Property | Type | Description |
|
|
277
|
+
|----------|------|-------------|
|
|
278
|
+
| `name` | `string` | Unique identifier |
|
|
279
|
+
| `fetch` | `(params: TParams) => Promise<TData>` | Fetch function |
|
|
280
|
+
|
|
281
|
+
### `defineMutation<TParams, TResponse>(config)`
|
|
282
|
+
|
|
283
|
+
| Property | Type | Description |
|
|
284
|
+
|----------|------|-------------|
|
|
285
|
+
| `name?` | `string` | Optional mutation key |
|
|
286
|
+
| `mutate` | `(params: TParams) => Promise<TResponse>` | Mutation function |
|
|
287
|
+
|
|
288
|
+
### `useQuery(def, options?)`
|
|
289
|
+
|
|
290
|
+
Returns `[data, queryState]` or `[data, queryState, paginationState]` for paginated queries.
|
|
291
|
+
|
|
292
|
+
**Options:**
|
|
293
|
+
- `params` - Parameters for fetch function
|
|
294
|
+
- `enabled` - Enable/disable query
|
|
295
|
+
- `staleTime` - Time before data is stale (ms)
|
|
296
|
+
- `refetchOnMount` - Refetch on component mount
|
|
297
|
+
- `refetchOnWindowFocus` - Refetch when window focuses
|
|
298
|
+
- `refetchInterval` - Polling interval (ms)
|
|
299
|
+
- `paginated` - Enable infinite query mode
|
|
300
|
+
- `getPageParams` - Transform page context to params
|
|
301
|
+
|
|
302
|
+
### `useMutation(def, options?)`
|
|
303
|
+
|
|
304
|
+
Returns `{ mutate, mutateAsync, isPending, isError, isSuccess, error, data, reset }`.
|
|
305
|
+
|
|
306
|
+
**Options:**
|
|
307
|
+
- `optimistic` - `(channel, params) => void` - Apply optimistic updates
|
|
308
|
+
- `onMutate` - Called when mutation starts
|
|
309
|
+
- `onSuccess` - Called on success
|
|
310
|
+
- `onError` - Called on error
|
|
311
|
+
|
|
312
|
+
### `channel(target)`
|
|
313
|
+
|
|
314
|
+
Standalone channel for immediate optimistic updates.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// For collections
|
|
318
|
+
channel(collection).prepend(item, { sync?: boolean })
|
|
319
|
+
channel(collection).append(item, { sync?: boolean })
|
|
320
|
+
channel(collection).update(id, updateFn, { sync?: boolean })
|
|
321
|
+
channel(collection).updateWhere(predicate, updateFn)
|
|
322
|
+
channel(collection).delete(id)
|
|
323
|
+
channel(collection).deleteWhere(predicate)
|
|
324
|
+
|
|
325
|
+
// For entities
|
|
326
|
+
channel(entity).update(updateFn, { sync?: boolean })
|
|
327
|
+
channel(entity).replace(data, { sync?: boolean })
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
All methods return a rollback function.
|
|
331
|
+
|
|
332
|
+
## Submodule Imports
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// Core only (no React dependency)
|
|
336
|
+
import { defineCollection, defineEntity, defineMutation, channel } from 'query-toolkit/core'
|
|
337
|
+
|
|
338
|
+
// React hooks only
|
|
339
|
+
import { useQuery, useMutation } from 'query-toolkit/react'
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## TypeScript
|
|
343
|
+
|
|
344
|
+
Full type inference from definitions:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const usersCollection = defineCollection<User, { page: number }>({...})
|
|
348
|
+
const createUser = defineMutation<CreateUserParams, User>({...})
|
|
349
|
+
|
|
350
|
+
// Types flow automatically
|
|
351
|
+
const [users] = useQuery(usersCollection, { params: { page: 1 } })
|
|
352
|
+
// users: User[] | undefined
|
|
353
|
+
|
|
354
|
+
const { mutate } = useMutation(createUser, {
|
|
355
|
+
optimistic: (channel, params) => {
|
|
356
|
+
// params: CreateUserParams
|
|
357
|
+
channel(usersCollection).prepend({...})
|
|
358
|
+
// Type-checks that data matches User
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
// mutate: (params: CreateUserParams) => void
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Peer Dependencies
|
|
365
|
+
|
|
366
|
+
- `react` >= 18.0.0
|
|
367
|
+
- `@tanstack/react-query` >= 5.0.0
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
MIT
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { I as IdGetter, C as CollectionDef, E as EntityDef, M as MutationDef, a as Optimistic } from '../types-BRxQA1mR.mjs';
|
|
2
|
+
export { A as AnyDef, b as OptimisticAction, c as OptimisticInstruction, O as OptimisticStatus, P as PaginatedOptions, Q as QueryOptions } from '../types-BRxQA1mR.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Define a collection query for fetching arrays of items
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const postsQuery = defineCollection({
|
|
9
|
+
* name: 'posts',
|
|
10
|
+
* id: (post) => post._id,
|
|
11
|
+
* fetch: ({ page }) => api.get(`/posts?page=${page}`).json()
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
14
|
+
declare function defineCollection<TData, TParams = void>(config: {
|
|
15
|
+
name: string;
|
|
16
|
+
id: IdGetter<TData>;
|
|
17
|
+
fetch: (params: TParams) => Promise<TData[]>;
|
|
18
|
+
}): CollectionDef<TData, TParams>;
|
|
19
|
+
/**
|
|
20
|
+
* Define an entity for fetching single items
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const userEntity = defineEntity({
|
|
24
|
+
* name: 'user',
|
|
25
|
+
* fetch: (userId) => api.get(`/users/${userId}`).json()
|
|
26
|
+
* })
|
|
27
|
+
*/
|
|
28
|
+
declare function defineEntity<TData, TParams = void>(config: {
|
|
29
|
+
name: string;
|
|
30
|
+
fetch: (params: TParams) => Promise<TData>;
|
|
31
|
+
}): EntityDef<TData, TParams>;
|
|
32
|
+
/**
|
|
33
|
+
* Define a mutation for writing data
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const createPost = defineMutation({
|
|
37
|
+
* name: 'createPost',
|
|
38
|
+
* mutate: (data) => api.post('/posts', { json: data }).json()
|
|
39
|
+
* })
|
|
40
|
+
*/
|
|
41
|
+
declare function defineMutation<TParams, TResponse = void>(config: {
|
|
42
|
+
name?: string;
|
|
43
|
+
mutate: (params: TParams) => Promise<TResponse>;
|
|
44
|
+
}): MutationDef<TParams, TResponse>;
|
|
45
|
+
|
|
46
|
+
/** Registered collection entry */
|
|
47
|
+
interface RegisteredCollection<T = any> {
|
|
48
|
+
kind: 'collection';
|
|
49
|
+
name: string;
|
|
50
|
+
queryKey: readonly unknown[];
|
|
51
|
+
def: CollectionDef<T, any>;
|
|
52
|
+
getData: () => T[] | undefined;
|
|
53
|
+
setData: (updater: (prev: T[] | undefined) => T[] | undefined) => void;
|
|
54
|
+
}
|
|
55
|
+
/** Registered entity entry */
|
|
56
|
+
interface RegisteredEntity<T = any> {
|
|
57
|
+
kind: 'entity';
|
|
58
|
+
name: string;
|
|
59
|
+
queryKey: readonly unknown[];
|
|
60
|
+
def: EntityDef<T, any>;
|
|
61
|
+
getData: () => T | undefined;
|
|
62
|
+
setData: (updater: (prev: T | undefined) => T | undefined) => void;
|
|
63
|
+
}
|
|
64
|
+
/** Registered paginated collection entry */
|
|
65
|
+
interface RegisteredPaginatedCollection<T = any> {
|
|
66
|
+
kind: 'paginated';
|
|
67
|
+
name: string;
|
|
68
|
+
queryKey: readonly unknown[];
|
|
69
|
+
def: CollectionDef<T, any>;
|
|
70
|
+
getData: () => {
|
|
71
|
+
pages: T[][];
|
|
72
|
+
pageParams: unknown[];
|
|
73
|
+
} | undefined;
|
|
74
|
+
setData: (updater: (prev: {
|
|
75
|
+
pages: T[][];
|
|
76
|
+
pageParams: unknown[];
|
|
77
|
+
} | undefined) => {
|
|
78
|
+
pages: T[][];
|
|
79
|
+
pageParams: unknown[];
|
|
80
|
+
} | undefined) => void;
|
|
81
|
+
}
|
|
82
|
+
type RegisteredEntry = RegisteredCollection | RegisteredEntity | RegisteredPaginatedCollection;
|
|
83
|
+
/**
|
|
84
|
+
* Internal registry for tracking active queries
|
|
85
|
+
* Used by optimistic updates to broadcast changes
|
|
86
|
+
*/
|
|
87
|
+
declare class QueryRegistry {
|
|
88
|
+
private entries;
|
|
89
|
+
/** Register an active query */
|
|
90
|
+
register(entry: RegisteredEntry): void;
|
|
91
|
+
/** Unregister a query when component unmounts */
|
|
92
|
+
unregister(entry: RegisteredEntry): void;
|
|
93
|
+
/** Get all registered entries for a query name */
|
|
94
|
+
getByName(name: string): RegisteredEntry[];
|
|
95
|
+
/** Apply an optimistic update to all queries with given name */
|
|
96
|
+
applyUpdate<T>(name: string, action: 'prepend' | 'append' | 'update' | 'delete' | 'replace', payload: {
|
|
97
|
+
data?: Partial<Optimistic<T>>;
|
|
98
|
+
id?: string;
|
|
99
|
+
where?: (item: T) => boolean;
|
|
100
|
+
update?: (item: T) => T;
|
|
101
|
+
}): (() => void)[];
|
|
102
|
+
private applyCollectionUpdate;
|
|
103
|
+
}
|
|
104
|
+
/** Singleton registry instance */
|
|
105
|
+
declare const registry: QueryRegistry;
|
|
106
|
+
|
|
107
|
+
/** Transaction returned from channel methods */
|
|
108
|
+
interface OptimisticTransaction {
|
|
109
|
+
target: CollectionDef<any, any> | EntityDef<any, any>;
|
|
110
|
+
action: 'prepend' | 'append' | 'update' | 'delete' | 'replace';
|
|
111
|
+
data?: any;
|
|
112
|
+
id?: string;
|
|
113
|
+
where?: (item: any) => boolean;
|
|
114
|
+
update?: (item: any) => any;
|
|
115
|
+
sync?: boolean;
|
|
116
|
+
rollback: () => void;
|
|
117
|
+
}
|
|
118
|
+
/** Channel for a collection - provides typed optimistic mutation methods */
|
|
119
|
+
declare class CollectionChannel<TEntity> {
|
|
120
|
+
private readonly target;
|
|
121
|
+
private readonly optimisticId;
|
|
122
|
+
constructor(target: CollectionDef<TEntity, any>);
|
|
123
|
+
/**
|
|
124
|
+
* Prepend an item to the collection
|
|
125
|
+
* @returns Rollback function to undo the change
|
|
126
|
+
*/
|
|
127
|
+
prepend(data: TEntity, options?: {
|
|
128
|
+
sync?: boolean;
|
|
129
|
+
}): () => void;
|
|
130
|
+
/**
|
|
131
|
+
* Append an item to the collection
|
|
132
|
+
* @returns Rollback function to undo the change
|
|
133
|
+
*/
|
|
134
|
+
append(data: TEntity, options?: {
|
|
135
|
+
sync?: boolean;
|
|
136
|
+
}): () => void;
|
|
137
|
+
/**
|
|
138
|
+
* Update an item in the collection by ID
|
|
139
|
+
* @returns Rollback function to undo the change
|
|
140
|
+
*/
|
|
141
|
+
update(id: string, updateFn: (item: TEntity) => TEntity, options?: {
|
|
142
|
+
sync?: boolean;
|
|
143
|
+
}): () => void;
|
|
144
|
+
/**
|
|
145
|
+
* Update items matching a predicate
|
|
146
|
+
* @returns Rollback function to undo the change
|
|
147
|
+
*/
|
|
148
|
+
updateWhere(where: (item: TEntity) => boolean, updateFn: (item: TEntity) => TEntity): () => void;
|
|
149
|
+
/**
|
|
150
|
+
* Delete an item from the collection by ID
|
|
151
|
+
* @returns Rollback function to undo the change
|
|
152
|
+
*/
|
|
153
|
+
delete(id: string): () => void;
|
|
154
|
+
/**
|
|
155
|
+
* Delete items matching a predicate
|
|
156
|
+
* @returns Rollback function to undo the change
|
|
157
|
+
*/
|
|
158
|
+
deleteWhere(where: (item: TEntity) => boolean): () => void;
|
|
159
|
+
}
|
|
160
|
+
/** Channel for an entity - provides typed optimistic mutation methods */
|
|
161
|
+
declare class EntityChannel<TEntity> {
|
|
162
|
+
private readonly target;
|
|
163
|
+
constructor(target: EntityDef<TEntity, any>);
|
|
164
|
+
/**
|
|
165
|
+
* Update the entity
|
|
166
|
+
* @returns Rollback function to undo the change
|
|
167
|
+
*/
|
|
168
|
+
update(updateFn: (item: TEntity) => TEntity, options?: {
|
|
169
|
+
sync?: boolean;
|
|
170
|
+
}): () => void;
|
|
171
|
+
/**
|
|
172
|
+
* Replace the entity with new data
|
|
173
|
+
* @returns Rollback function to undo the change
|
|
174
|
+
*/
|
|
175
|
+
replace(data: TEntity, options?: {
|
|
176
|
+
sync?: boolean;
|
|
177
|
+
}): () => void;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Channel function for optimistic mutations.
|
|
181
|
+
* Call with a collection or entity to get typed mutation methods.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* // Standalone usage
|
|
185
|
+
* const rollback = channel(usersCollection).prepend({ id: '1', name: 'John' });
|
|
186
|
+
* // Later, to undo:
|
|
187
|
+
* rollback();
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* // Update an entity
|
|
191
|
+
* channel(userEntity).update(user => ({ ...user, name: 'Jane' }));
|
|
192
|
+
*/
|
|
193
|
+
interface Channel {
|
|
194
|
+
<TEntity>(target: CollectionDef<TEntity, any>): CollectionChannel<TEntity>;
|
|
195
|
+
<TEntity>(target: EntityDef<TEntity, any>): EntityChannel<TEntity>;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create a channel for optimistic mutations.
|
|
199
|
+
* Use this to apply immediate UI updates that can be rolled back.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* const rollback = channel(usersCollection).prepend(newUser);
|
|
203
|
+
* try {
|
|
204
|
+
* await api.createUser(newUser);
|
|
205
|
+
* } catch (error) {
|
|
206
|
+
* rollback(); // Undo the optimistic update
|
|
207
|
+
* }
|
|
208
|
+
*/
|
|
209
|
+
declare const channel: Channel;
|
|
210
|
+
|
|
211
|
+
export { type Channel, CollectionChannel, CollectionDef, EntityChannel, EntityDef, IdGetter, MutationDef, Optimistic, type OptimisticTransaction, type RegisteredCollection, type RegisteredEntity, type RegisteredEntry, type RegisteredPaginatedCollection, channel, defineCollection, defineEntity, defineMutation, registry };
|