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
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { useMemo, useEffect } from 'react';
|
|
2
|
+
import { useQueryClient, useQuery as useQuery$1, useInfiniteQuery, useMutation as useMutation$1 } from '@tanstack/react-query';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
|
|
5
|
+
// src/react/use-query.ts
|
|
6
|
+
|
|
7
|
+
// src/core/registry.ts
|
|
8
|
+
var QueryRegistry = class {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
11
|
+
}
|
|
12
|
+
/** Register an active query */
|
|
13
|
+
register(entry) {
|
|
14
|
+
if (!this.entries.has(entry.name)) {
|
|
15
|
+
this.entries.set(entry.name, /* @__PURE__ */ new Set());
|
|
16
|
+
}
|
|
17
|
+
this.entries.get(entry.name).add(entry);
|
|
18
|
+
}
|
|
19
|
+
/** Unregister a query when component unmounts */
|
|
20
|
+
unregister(entry) {
|
|
21
|
+
const set = this.entries.get(entry.name);
|
|
22
|
+
if (set) {
|
|
23
|
+
set.delete(entry);
|
|
24
|
+
if (set.size === 0) {
|
|
25
|
+
this.entries.delete(entry.name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Get all registered entries for a query name */
|
|
30
|
+
getByName(name) {
|
|
31
|
+
return Array.from(this.entries.get(name) ?? []);
|
|
32
|
+
}
|
|
33
|
+
/** Apply an optimistic update to all queries with given name */
|
|
34
|
+
applyUpdate(name, action, payload) {
|
|
35
|
+
const entries = this.getByName(name);
|
|
36
|
+
const rollbacks = [];
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (entry.kind === "collection") {
|
|
39
|
+
const previous = entry.getData();
|
|
40
|
+
const rollback = () => entry.setData(() => previous);
|
|
41
|
+
rollbacks.push(rollback);
|
|
42
|
+
entry.setData((prev) => {
|
|
43
|
+
if (!prev) return prev;
|
|
44
|
+
return this.applyCollectionUpdate(prev, action, payload, entry.def.id);
|
|
45
|
+
});
|
|
46
|
+
} else if (entry.kind === "paginated") {
|
|
47
|
+
const previous = entry.getData();
|
|
48
|
+
const rollback = () => entry.setData(() => previous);
|
|
49
|
+
rollbacks.push(rollback);
|
|
50
|
+
entry.setData((prev) => {
|
|
51
|
+
if (!prev) return prev;
|
|
52
|
+
return {
|
|
53
|
+
...prev,
|
|
54
|
+
pages: prev.pages.map(
|
|
55
|
+
(page, i) => i === 0 ? this.applyCollectionUpdate(page, action, payload, entry.def.id) : page
|
|
56
|
+
)
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
} else if (entry.kind === "entity") {
|
|
60
|
+
const previous = entry.getData();
|
|
61
|
+
const rollback = () => entry.setData(() => previous);
|
|
62
|
+
rollbacks.push(rollback);
|
|
63
|
+
if (action === "update" && payload.update) {
|
|
64
|
+
entry.setData((prev) => prev ? payload.update(prev) : prev);
|
|
65
|
+
} else if (action === "replace" && payload.data) {
|
|
66
|
+
entry.setData(() => payload.data);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return rollbacks;
|
|
71
|
+
}
|
|
72
|
+
applyCollectionUpdate(items, action, payload, getId) {
|
|
73
|
+
switch (action) {
|
|
74
|
+
case "prepend":
|
|
75
|
+
return payload.data ? [payload.data, ...items] : items;
|
|
76
|
+
case "append":
|
|
77
|
+
return payload.data ? [...items, payload.data] : items;
|
|
78
|
+
case "update":
|
|
79
|
+
return items.map((item) => {
|
|
80
|
+
const matches = payload.id ? getId(item) === payload.id : payload.where?.(item);
|
|
81
|
+
if (matches && payload.update) {
|
|
82
|
+
return payload.update(item);
|
|
83
|
+
}
|
|
84
|
+
if (matches && payload.data) {
|
|
85
|
+
return { ...item, ...payload.data };
|
|
86
|
+
}
|
|
87
|
+
return item;
|
|
88
|
+
});
|
|
89
|
+
case "delete":
|
|
90
|
+
return items.filter((item) => {
|
|
91
|
+
if (payload.id) return getId(item) !== payload.id;
|
|
92
|
+
if (payload.where) return !payload.where(item);
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
case "replace":
|
|
96
|
+
return items.map((item) => {
|
|
97
|
+
const matches = payload.id ? getId(item) === payload.id : payload.where?.(item);
|
|
98
|
+
return matches && payload.data ? payload.data : item;
|
|
99
|
+
});
|
|
100
|
+
default:
|
|
101
|
+
return items;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var registry = new QueryRegistry();
|
|
106
|
+
|
|
107
|
+
// src/react/use-query.ts
|
|
108
|
+
function useQuery(def, options) {
|
|
109
|
+
const queryClient = useQueryClient();
|
|
110
|
+
const { params, paginated, getPageParams, queryKey: customQueryKey, ...queryOptions } = options ?? {};
|
|
111
|
+
const queryKey = useMemo(
|
|
112
|
+
() => customQueryKey ?? [def.name, params].filter(Boolean),
|
|
113
|
+
[customQueryKey, def.name, params]
|
|
114
|
+
);
|
|
115
|
+
if (def._type === "entity") {
|
|
116
|
+
const entityDef = def;
|
|
117
|
+
const query2 = useQuery$1({
|
|
118
|
+
queryKey,
|
|
119
|
+
queryFn: () => entityDef.fetch(params),
|
|
120
|
+
enabled: queryOptions.enabled,
|
|
121
|
+
staleTime: queryOptions.staleTime,
|
|
122
|
+
gcTime: queryOptions.cacheTime,
|
|
123
|
+
refetchOnMount: queryOptions.refetchOnMount,
|
|
124
|
+
refetchOnWindowFocus: queryOptions.refetchOnWindowFocus,
|
|
125
|
+
refetchInterval: queryOptions.refetchInterval
|
|
126
|
+
});
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (query2.status !== "success" || !query2.data) return;
|
|
129
|
+
const entry = {
|
|
130
|
+
kind: "entity",
|
|
131
|
+
name: def.name,
|
|
132
|
+
queryKey,
|
|
133
|
+
def: entityDef,
|
|
134
|
+
getData: () => queryClient.getQueryData(queryKey),
|
|
135
|
+
setData: (updater) => queryClient.setQueryData(queryKey, updater)
|
|
136
|
+
};
|
|
137
|
+
registry.register(entry);
|
|
138
|
+
return () => registry.unregister(entry);
|
|
139
|
+
}, [def.name, queryKey, query2.status, query2.data, queryClient]);
|
|
140
|
+
return [
|
|
141
|
+
query2.data,
|
|
142
|
+
{
|
|
143
|
+
isLoading: query2.isLoading,
|
|
144
|
+
isFetching: query2.isFetching,
|
|
145
|
+
isError: query2.isError,
|
|
146
|
+
error: query2.error,
|
|
147
|
+
refetch: query2.refetch
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
const collectionDef = def;
|
|
152
|
+
if (paginated) {
|
|
153
|
+
const infiniteQuery = useInfiniteQuery({
|
|
154
|
+
queryKey,
|
|
155
|
+
queryFn: ({ pageParam }) => {
|
|
156
|
+
const pageParams = getPageParams ? getPageParams({ pageParam }) : { pageParam };
|
|
157
|
+
return collectionDef.fetch(pageParams);
|
|
158
|
+
},
|
|
159
|
+
initialPageParam: 1,
|
|
160
|
+
getNextPageParam: (lastPage, allPages) => lastPage.length > 0 ? allPages.length + 1 : void 0,
|
|
161
|
+
enabled: queryOptions.enabled,
|
|
162
|
+
staleTime: queryOptions.staleTime,
|
|
163
|
+
gcTime: queryOptions.cacheTime,
|
|
164
|
+
refetchOnMount: queryOptions.refetchOnMount,
|
|
165
|
+
refetchOnWindowFocus: queryOptions.refetchOnWindowFocus,
|
|
166
|
+
refetchInterval: queryOptions.refetchInterval
|
|
167
|
+
});
|
|
168
|
+
const flatData = useMemo(
|
|
169
|
+
() => infiniteQuery.data?.pages.flat(),
|
|
170
|
+
[infiniteQuery.data]
|
|
171
|
+
);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (infiniteQuery.status !== "success" || !infiniteQuery.data) return;
|
|
174
|
+
const entry = {
|
|
175
|
+
kind: "paginated",
|
|
176
|
+
name: def.name,
|
|
177
|
+
queryKey,
|
|
178
|
+
def: collectionDef,
|
|
179
|
+
getData: () => queryClient.getQueryData(
|
|
180
|
+
queryKey
|
|
181
|
+
),
|
|
182
|
+
setData: (updater) => queryClient.setQueryData(queryKey, updater)
|
|
183
|
+
};
|
|
184
|
+
registry.register(entry);
|
|
185
|
+
return () => registry.unregister(entry);
|
|
186
|
+
}, [def.name, queryKey, infiniteQuery.status, infiniteQuery.data, queryClient]);
|
|
187
|
+
return [
|
|
188
|
+
flatData,
|
|
189
|
+
{
|
|
190
|
+
isLoading: infiniteQuery.isLoading,
|
|
191
|
+
isFetching: infiniteQuery.isFetching,
|
|
192
|
+
isError: infiniteQuery.isError,
|
|
193
|
+
error: infiniteQuery.error,
|
|
194
|
+
refetch: infiniteQuery.refetch
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
hasNextPage: infiniteQuery.hasNextPage ?? false,
|
|
198
|
+
fetchNextPage: infiniteQuery.fetchNextPage,
|
|
199
|
+
isFetchingNextPage: infiniteQuery.isFetchingNextPage
|
|
200
|
+
}
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
const query = useQuery$1({
|
|
204
|
+
queryKey,
|
|
205
|
+
queryFn: () => collectionDef.fetch(params),
|
|
206
|
+
enabled: queryOptions.enabled,
|
|
207
|
+
staleTime: queryOptions.staleTime,
|
|
208
|
+
gcTime: queryOptions.cacheTime,
|
|
209
|
+
refetchOnMount: queryOptions.refetchOnMount,
|
|
210
|
+
refetchOnWindowFocus: queryOptions.refetchOnWindowFocus,
|
|
211
|
+
refetchInterval: queryOptions.refetchInterval
|
|
212
|
+
});
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (query.status !== "success" || !query.data) return;
|
|
215
|
+
const entry = {
|
|
216
|
+
kind: "collection",
|
|
217
|
+
name: def.name,
|
|
218
|
+
queryKey,
|
|
219
|
+
def: collectionDef,
|
|
220
|
+
getData: () => queryClient.getQueryData(queryKey),
|
|
221
|
+
setData: (updater) => queryClient.setQueryData(queryKey, updater)
|
|
222
|
+
};
|
|
223
|
+
registry.register(entry);
|
|
224
|
+
return () => registry.unregister(entry);
|
|
225
|
+
}, [def.name, queryKey, query.status, query.data, queryClient]);
|
|
226
|
+
return [
|
|
227
|
+
query.data,
|
|
228
|
+
{
|
|
229
|
+
isLoading: query.isLoading,
|
|
230
|
+
isFetching: query.isFetching,
|
|
231
|
+
isError: query.isError,
|
|
232
|
+
error: query.error,
|
|
233
|
+
refetch: query.refetch
|
|
234
|
+
}
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
var BatchedCollectionChannel = class {
|
|
238
|
+
constructor(target, transactions) {
|
|
239
|
+
this.target = target;
|
|
240
|
+
this.transactions = transactions;
|
|
241
|
+
}
|
|
242
|
+
prepend(data, options) {
|
|
243
|
+
this.transactions.push({
|
|
244
|
+
target: this.target,
|
|
245
|
+
action: "prepend",
|
|
246
|
+
data,
|
|
247
|
+
sync: options?.sync
|
|
248
|
+
});
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
append(data, options) {
|
|
252
|
+
this.transactions.push({
|
|
253
|
+
target: this.target,
|
|
254
|
+
action: "append",
|
|
255
|
+
data,
|
|
256
|
+
sync: options?.sync
|
|
257
|
+
});
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
update(id, updateFn, options) {
|
|
261
|
+
this.transactions.push({
|
|
262
|
+
target: this.target,
|
|
263
|
+
action: "update",
|
|
264
|
+
id,
|
|
265
|
+
update: updateFn,
|
|
266
|
+
sync: options?.sync
|
|
267
|
+
});
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
delete(id) {
|
|
271
|
+
this.transactions.push({
|
|
272
|
+
target: this.target,
|
|
273
|
+
action: "delete",
|
|
274
|
+
id
|
|
275
|
+
});
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var BatchedEntityChannel = class {
|
|
280
|
+
constructor(target, transactions) {
|
|
281
|
+
this.target = target;
|
|
282
|
+
this.transactions = transactions;
|
|
283
|
+
}
|
|
284
|
+
update(updateFn, options) {
|
|
285
|
+
this.transactions.push({
|
|
286
|
+
target: this.target,
|
|
287
|
+
action: "update",
|
|
288
|
+
update: updateFn,
|
|
289
|
+
sync: options?.sync
|
|
290
|
+
});
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
replace(data, options) {
|
|
294
|
+
this.transactions.push({
|
|
295
|
+
target: this.target,
|
|
296
|
+
action: "replace",
|
|
297
|
+
data,
|
|
298
|
+
sync: options?.sync
|
|
299
|
+
});
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
function createBatchedChannel(transactions) {
|
|
304
|
+
function channel(target) {
|
|
305
|
+
if (target._type === "collection") {
|
|
306
|
+
return new BatchedCollectionChannel(target, transactions);
|
|
307
|
+
} else {
|
|
308
|
+
return new BatchedEntityChannel(target, transactions);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return channel;
|
|
312
|
+
}
|
|
313
|
+
function useMutation(def, options) {
|
|
314
|
+
const mutation = useMutation$1({
|
|
315
|
+
mutationKey: def.name ? [def.name] : void 0,
|
|
316
|
+
mutationFn: def.mutate,
|
|
317
|
+
onMutate: async (params) => {
|
|
318
|
+
const rollbacks = [];
|
|
319
|
+
const optimisticId = nanoid();
|
|
320
|
+
const transactions = [];
|
|
321
|
+
if (options?.optimistic) {
|
|
322
|
+
const channel = createBatchedChannel(transactions);
|
|
323
|
+
options.optimistic(channel, params);
|
|
324
|
+
for (const tx of transactions) {
|
|
325
|
+
const { target, action, data, id, where, update } = tx;
|
|
326
|
+
const optimisticData = data ? {
|
|
327
|
+
...data,
|
|
328
|
+
_optimistic: { id: optimisticId, status: "pending" }
|
|
329
|
+
} : void 0;
|
|
330
|
+
const updateRollbacks = registry.applyUpdate(target.name, action, {
|
|
331
|
+
data: optimisticData,
|
|
332
|
+
id,
|
|
333
|
+
where,
|
|
334
|
+
update: update ? (item) => update(item) : optimisticData ? (item) => ({ ...item, ...optimisticData }) : void 0
|
|
335
|
+
});
|
|
336
|
+
rollbacks.push(...updateRollbacks);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
options?.onMutate?.(params);
|
|
340
|
+
return { rollbacks, optimisticId, transactions };
|
|
341
|
+
},
|
|
342
|
+
onSuccess: (data, params, context) => {
|
|
343
|
+
if (context?.transactions) {
|
|
344
|
+
for (const tx of context.transactions) {
|
|
345
|
+
if (tx.sync && data) {
|
|
346
|
+
registry.applyUpdate(tx.target.name, "update", {
|
|
347
|
+
where: (item) => item._optimistic?.id === context?.optimisticId,
|
|
348
|
+
update: () => data
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
options?.onSuccess?.(data, params);
|
|
354
|
+
},
|
|
355
|
+
onError: (error, params, context) => {
|
|
356
|
+
context?.rollbacks.forEach((rollback) => rollback());
|
|
357
|
+
options?.onError?.(error, params);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
mutate: mutation.mutate,
|
|
362
|
+
mutateAsync: mutation.mutateAsync,
|
|
363
|
+
isLoading: mutation.isPending,
|
|
364
|
+
isPending: mutation.isPending,
|
|
365
|
+
isError: mutation.isError,
|
|
366
|
+
isSuccess: mutation.isSuccess,
|
|
367
|
+
error: mutation.error,
|
|
368
|
+
data: mutation.data,
|
|
369
|
+
reset: mutation.reset
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export { useMutation, useQuery };
|
|
374
|
+
//# sourceMappingURL=index.mjs.map
|
|
375
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/registry.ts","../../src/react/use-query.ts","../../src/react/use-mutation.ts"],"names":["query","useTanstackQuery","useTanstackMutation"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { CollectionDef, EntityDef, Optimistic } from './types';\n\n/** Registered collection entry */\nexport interface RegisteredCollection<T = any> {\n kind: 'collection';\n name: string;\n queryKey: readonly unknown[];\n def: CollectionDef<T, any>;\n getData: () => T[] | undefined;\n setData: (updater: (prev: T[] | undefined) => T[] | undefined) => void;\n}\n\n/** Registered entity entry */\nexport interface RegisteredEntity<T = any> {\n kind: 'entity';\n name: string;\n queryKey: readonly unknown[];\n def: EntityDef<T, any>;\n getData: () => T | undefined;\n setData: (updater: (prev: T | undefined) => T | undefined) => void;\n}\n\n/** Registered paginated collection entry */\nexport interface RegisteredPaginatedCollection<T = any> {\n kind: 'paginated';\n name: string;\n queryKey: readonly unknown[];\n def: CollectionDef<T, any>;\n getData: () => { pages: T[][]; pageParams: unknown[] } | undefined;\n setData: (\n updater: (\n prev: { pages: T[][]; pageParams: unknown[] } | undefined\n ) => { pages: T[][]; pageParams: unknown[] } | undefined\n ) => void;\n}\n\nexport type RegisteredEntry =\n | RegisteredCollection\n | RegisteredEntity\n | RegisteredPaginatedCollection;\n\n/**\n * Internal registry for tracking active queries\n * Used by optimistic updates to broadcast changes\n */\nclass QueryRegistry {\n private entries = new Map<string, Set<RegisteredEntry>>();\n\n /** Register an active query */\n register(entry: RegisteredEntry): void {\n if (!this.entries.has(entry.name)) {\n this.entries.set(entry.name, new Set());\n }\n this.entries.get(entry.name)!.add(entry);\n }\n\n /** Unregister a query when component unmounts */\n unregister(entry: RegisteredEntry): void {\n const set = this.entries.get(entry.name);\n if (set) {\n set.delete(entry);\n if (set.size === 0) {\n this.entries.delete(entry.name);\n }\n }\n }\n\n /** Get all registered entries for a query name */\n getByName(name: string): RegisteredEntry[] {\n return Array.from(this.entries.get(name) ?? []);\n }\n\n /** Apply an optimistic update to all queries with given name */\n applyUpdate<T>(\n name: string,\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace',\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n }\n ): (() => void)[] {\n const entries = this.getByName(name);\n const rollbacks: (() => void)[] = [];\n\n for (const entry of entries) {\n if (entry.kind === 'collection') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n entry.setData((prev) => {\n if (!prev) return prev;\n return this.applyCollectionUpdate(prev, action, payload, entry.def.id);\n });\n } else if (entry.kind === 'paginated') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n entry.setData((prev) => {\n if (!prev) return prev;\n return {\n ...prev,\n pages: prev.pages.map((page, i) =>\n i === 0\n ? this.applyCollectionUpdate(page, action, payload, entry.def.id)\n : page\n ),\n };\n });\n } else if (entry.kind === 'entity') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n if (action === 'update' && payload.update) {\n entry.setData((prev) => (prev ? payload.update!(prev as T) : prev));\n } else if (action === 'replace' && payload.data) {\n entry.setData(() => payload.data as T);\n }\n }\n }\n\n return rollbacks;\n }\n\n private applyCollectionUpdate<T>(\n items: T[],\n action: string,\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n },\n getId: (item: T) => string\n ): T[] {\n switch (action) {\n case 'prepend':\n return payload.data ? [payload.data as T, ...items] : items;\n\n case 'append':\n return payload.data ? [...items, payload.data as T] : items;\n\n case 'update':\n return items.map((item) => {\n const matches = payload.id\n ? getId(item) === payload.id\n : payload.where?.(item);\n if (matches && payload.update) {\n return payload.update(item);\n }\n if (matches && payload.data) {\n return { ...item, ...payload.data };\n }\n return item;\n });\n\n case 'delete':\n return items.filter((item) => {\n if (payload.id) return getId(item) !== payload.id;\n if (payload.where) return !payload.where(item);\n return true;\n });\n\n case 'replace':\n return items.map((item) => {\n const matches = payload.id\n ? getId(item) === payload.id\n : payload.where?.(item);\n return matches && payload.data ? (payload.data as T) : item;\n });\n\n default:\n return items;\n }\n }\n}\n\n/** Singleton registry instance */\nexport const registry = new QueryRegistry();\n","import { useEffect, useMemo, useCallback } from 'react';\nimport {\n useQuery as useTanstackQuery,\n useInfiniteQuery,\n useQueryClient,\n type UseQueryOptions,\n type UseInfiniteQueryOptions,\n} from '@tanstack/react-query';\nimport type {\n CollectionDef,\n EntityDef,\n Optimistic,\n QueryOptions,\n PaginatedOptions,\n} from '../core/types';\nimport { registry } from '../core/registry';\n\n/** Options for useQuery hook */\nexport interface UseQueryHookOptions<TParams> extends QueryOptions {\n /** Parameters to pass to the fetch function */\n params?: TParams;\n /** Enable pagination mode (infinite query) */\n paginated?: boolean;\n /** For paginated: get params for each page */\n getPageParams?: (context: { pageParam: number }) => TParams;\n /** Custom query key (defaults to [def.name, params]) */\n queryKey?: readonly unknown[];\n}\n\n/** Query state object (second element of tuple) */\nexport interface QueryState {\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n/** Pagination state object (third element of paginated tuple) */\nexport interface PaginationState {\n hasNextPage: boolean;\n fetchNextPage: () => void;\n isFetchingNextPage: boolean;\n}\n\n/** Return type for collection queries: [data, queryState] */\nexport type QueryResult<T> = [\n Optimistic<T>[] | undefined,\n QueryState\n];\n\n/** Return type for paginated queries: [data, queryState, paginationState] */\nexport type PaginatedQueryResult<T> = [\n Optimistic<T>[] | undefined,\n QueryState,\n PaginationState\n];\n\n/** Return type for entity queries: [data, queryState] */\nexport type EntityResult<T> = [\n Optimistic<T> | undefined,\n QueryState\n];\n\n/**\n * Unified query hook for fetching data\n *\n * @example\n * // Simple collection query\n * const [data, query] = useQuery(postsQuery, { params: { limit: 10 } })\n *\n * @example\n * // Paginated query\n * const [data, query, pagination] = useQuery(postsQuery, {\n * paginated: true,\n * getPageParams: ({ pageParam }) => ({ page: pageParam, limit: 10 })\n * })\n * // pagination.fetchNextPage(), pagination.hasNextPage\n *\n * @example\n * // Entity query\n * const [user, query] = useQuery(userEntity, { params: userId })\n */\nexport function useQuery<TData, TParams>(\n def: CollectionDef<TData, TParams>,\n options?: UseQueryHookOptions<TParams> & { paginated: true }\n): PaginatedQueryResult<TData>;\n\nexport function useQuery<TData, TParams>(\n def: CollectionDef<TData, TParams>,\n options?: UseQueryHookOptions<TParams>\n): QueryResult<TData>;\n\nexport function useQuery<TData, TParams>(\n def: EntityDef<TData, TParams>,\n options?: UseQueryHookOptions<TParams>\n): EntityResult<TData>;\n\nexport function useQuery<TData, TParams>(\n def: CollectionDef<TData, TParams> | EntityDef<TData, TParams>,\n options?: UseQueryHookOptions<TParams>\n): QueryResult<TData> | PaginatedQueryResult<TData> | EntityResult<TData> {\n const queryClient = useQueryClient();\n const { params, paginated, getPageParams, queryKey: customQueryKey, ...queryOptions } = options ?? {};\n\n // Build query key\n const queryKey = useMemo(\n () => customQueryKey ?? [def.name, params].filter(Boolean),\n [customQueryKey, def.name, params]\n );\n\n // Entity query\n if (def._type === 'entity') {\n const entityDef = def as EntityDef<TData, TParams>;\n const query = useTanstackQuery({\n queryKey,\n queryFn: () => entityDef.fetch(params as TParams),\n enabled: queryOptions.enabled,\n staleTime: queryOptions.staleTime,\n gcTime: queryOptions.cacheTime,\n refetchOnMount: queryOptions.refetchOnMount,\n refetchOnWindowFocus: queryOptions.refetchOnWindowFocus,\n refetchInterval: queryOptions.refetchInterval,\n });\n\n // Register for optimistic updates\n useEffect(() => {\n if (query.status !== 'success' || !query.data) return;\n\n const entry = {\n kind: 'entity' as const,\n name: def.name,\n queryKey,\n def: entityDef,\n getData: () => queryClient.getQueryData<TData>(queryKey),\n setData: (updater: (prev: TData | undefined) => TData | undefined) =>\n queryClient.setQueryData<TData>(queryKey, updater),\n };\n\n registry.register(entry);\n return () => registry.unregister(entry);\n }, [def.name, queryKey, query.status, query.data, queryClient]);\n\n return [\n query.data as Optimistic<TData> | undefined,\n {\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n error: query.error,\n refetch: query.refetch,\n },\n ];\n }\n\n const collectionDef = def as CollectionDef<TData, TParams>;\n\n // Paginated collection\n if (paginated) {\n const infiniteQuery = useInfiniteQuery({\n queryKey,\n queryFn: ({ pageParam }) => {\n const pageParams = getPageParams\n ? getPageParams({ pageParam: pageParam as number })\n : ({ pageParam } as TParams);\n return collectionDef.fetch(pageParams);\n },\n initialPageParam: 1,\n getNextPageParam: (lastPage, allPages) =>\n lastPage.length > 0 ? allPages.length + 1 : undefined,\n enabled: queryOptions.enabled,\n staleTime: queryOptions.staleTime,\n gcTime: queryOptions.cacheTime,\n refetchOnMount: queryOptions.refetchOnMount,\n refetchOnWindowFocus: queryOptions.refetchOnWindowFocus,\n refetchInterval: queryOptions.refetchInterval,\n });\n\n const flatData = useMemo(\n () => infiniteQuery.data?.pages.flat() as Optimistic<TData>[] | undefined,\n [infiniteQuery.data]\n );\n\n // Register for optimistic updates\n useEffect(() => {\n if (infiniteQuery.status !== 'success' || !infiniteQuery.data) return;\n\n const entry = {\n kind: 'paginated' as const,\n name: def.name,\n queryKey,\n def: collectionDef,\n getData: () =>\n queryClient.getQueryData<{ pages: TData[][]; pageParams: unknown[] }>(\n queryKey\n ),\n setData: (\n updater: (\n prev: { pages: TData[][]; pageParams: unknown[] } | undefined\n ) => { pages: TData[][]; pageParams: unknown[] } | undefined\n ) =>\n queryClient.setQueryData<{\n pages: TData[][];\n pageParams: unknown[];\n }>(queryKey, updater),\n };\n\n registry.register(entry);\n return () => registry.unregister(entry);\n }, [def.name, queryKey, infiniteQuery.status, infiniteQuery.data, queryClient]);\n\n return [\n flatData,\n {\n isLoading: infiniteQuery.isLoading,\n isFetching: infiniteQuery.isFetching,\n isError: infiniteQuery.isError,\n error: infiniteQuery.error,\n refetch: infiniteQuery.refetch,\n },\n {\n hasNextPage: infiniteQuery.hasNextPage ?? false,\n fetchNextPage: infiniteQuery.fetchNextPage,\n isFetchingNextPage: infiniteQuery.isFetchingNextPage,\n },\n ];\n }\n\n // Simple collection query\n const query = useTanstackQuery({\n queryKey,\n queryFn: () => collectionDef.fetch(params as TParams),\n enabled: queryOptions.enabled,\n staleTime: queryOptions.staleTime,\n gcTime: queryOptions.cacheTime,\n refetchOnMount: queryOptions.refetchOnMount,\n refetchOnWindowFocus: queryOptions.refetchOnWindowFocus,\n refetchInterval: queryOptions.refetchInterval,\n });\n\n // Register for optimistic updates\n useEffect(() => {\n if (query.status !== 'success' || !query.data) return;\n\n const entry = {\n kind: 'collection' as const,\n name: def.name,\n queryKey,\n def: collectionDef,\n getData: () => queryClient.getQueryData<TData[]>(queryKey),\n setData: (updater: (prev: TData[] | undefined) => TData[] | undefined) =>\n queryClient.setQueryData<TData[]>(queryKey, updater),\n };\n\n registry.register(entry);\n return () => registry.unregister(entry);\n }, [def.name, queryKey, query.status, query.data, queryClient]);\n\n return [\n query.data as Optimistic<TData>[] | undefined,\n {\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n error: query.error,\n refetch: query.refetch,\n },\n ];\n}\n","import { useMutation as useTanstackMutation } from '@tanstack/react-query';\nimport { nanoid } from 'nanoid';\nimport type {\n MutationDef,\n CollectionDef,\n EntityDef,\n} from '../core/types';\nimport { registry } from '../core/registry';\nimport {\n channel as coreChannel,\n type Channel,\n type CollectionChannel,\n type EntityChannel,\n} from '../core/channel';\n\n/** Extract entity type from a collection or entity definition */\ntype InferEntity<T> = T extends CollectionDef<infer TData, any>\n ? TData & {}\n : T extends EntityDef<infer TData, any>\n ? TData & {}\n : never;\n\n/** Base optimistic config with shared properties */\ninterface OptimisticConfigBase<\n TTarget extends CollectionDef<any, any> | EntityDef<any, any>,\n TParams\n> {\n /** Target collection/entity to update */\n target: TTarget;\n /** Whether to sync with server response (replace optimistic with real data) */\n sync?: boolean;\n}\n\n/** Config for prepend/append actions - requires full entity */\ninterface OptimisticPrependAppendConfig<\n TTarget extends CollectionDef<any, any> | EntityDef<any, any>,\n TParams\n> extends OptimisticConfigBase<TTarget, TParams> {\n action: 'prepend' | 'append';\n /** Data to prepend/append - must be a full entity */\n data: (params: TParams) => NoInfer<InferEntity<TTarget>>;\n}\n\n/** Config for replace action */\ninterface OptimisticReplaceConfig<\n TTarget extends CollectionDef<any, any> | EntityDef<any, any>,\n TParams\n> extends OptimisticConfigBase<TTarget, TParams> {\n action: 'replace';\n /** Data for replacement */\n data: (params: TParams) => NoInfer<InferEntity<TTarget>>;\n /** ID of item to replace */\n id?: string | ((params: TParams) => string);\n /** Filter function to find items to replace */\n where?: (item: NoInfer<InferEntity<TTarget>>) => boolean;\n}\n\n/** Config for update action */\ninterface OptimisticUpdateConfig<\n TTarget extends CollectionDef<any, any> | EntityDef<any, any>,\n TParams\n> extends OptimisticConfigBase<TTarget, TParams> {\n action: 'update';\n /** Partial data for update */\n data?: (params: TParams) => Partial<NoInfer<InferEntity<TTarget>>>;\n /** ID of item to update */\n id?: string | ((params: TParams) => string);\n /** Filter function to find items to update */\n where?: (item: NoInfer<InferEntity<TTarget>>) => boolean;\n /** Update function */\n update?: (item: NoInfer<InferEntity<TTarget>>, params: TParams) => NoInfer<InferEntity<TTarget>>;\n}\n\n/** Config for delete action */\ninterface OptimisticDeleteConfig<\n TTarget extends CollectionDef<any, any> | EntityDef<any, any>,\n TParams\n> extends OptimisticConfigBase<TTarget, TParams> {\n action: 'delete';\n /** ID of item to delete */\n id?: string | ((params: TParams) => string);\n /** Filter function to find items to delete */\n where?: (item: NoInfer<InferEntity<TTarget>>) => boolean;\n}\n\n/** Optimistic update configuration - type inferred from target */\nexport type OptimisticConfig<\n TTarget extends CollectionDef<any, any> | EntityDef<any, any>,\n TParams\n> =\n | OptimisticPrependAppendConfig<TTarget, TParams>\n | OptimisticReplaceConfig<TTarget, TParams>\n | OptimisticUpdateConfig<TTarget, TParams>\n | OptimisticDeleteConfig<TTarget, TParams>;\n\n/** Internal transaction type for batched mutations */\ninterface MutationTransaction {\n target: CollectionDef<any, any> | EntityDef<any, any>;\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace';\n data?: any;\n id?: string;\n where?: (item: any) => boolean;\n update?: (item: any) => any;\n sync?: boolean;\n}\n\n/** Internal collection channel for batched mutations */\nclass BatchedCollectionChannel<TEntity> {\n constructor(\n private readonly target: CollectionDef<TEntity, any>,\n private readonly transactions: MutationTransaction[]\n ) {}\n\n prepend(data: TEntity, options?: { sync?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'prepend',\n data,\n sync: options?.sync,\n });\n return this;\n }\n\n append(data: TEntity, options?: { sync?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'append',\n data,\n sync: options?.sync,\n });\n return this;\n }\n\n update(id: string, updateFn: (item: TEntity) => TEntity, options?: { sync?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'update',\n id,\n update: updateFn,\n sync: options?.sync,\n });\n return this;\n }\n\n delete(id: string): this {\n this.transactions.push({\n target: this.target,\n action: 'delete',\n id,\n });\n return this;\n }\n}\n\n/** Internal entity channel for batched mutations */\nclass BatchedEntityChannel<TEntity> {\n constructor(\n private readonly target: EntityDef<TEntity, any>,\n private readonly transactions: MutationTransaction[]\n ) {}\n\n update(updateFn: (item: TEntity) => TEntity, options?: { sync?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'update',\n update: updateFn,\n sync: options?.sync,\n });\n return this;\n }\n\n replace(data: TEntity, options?: { sync?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'replace',\n data,\n sync: options?.sync,\n });\n return this;\n }\n}\n\n/** Internal batched channel type */\ninterface BatchedChannel {\n <TEntity>(target: CollectionDef<TEntity, any>): BatchedCollectionChannel<TEntity>;\n <TEntity>(target: EntityDef<TEntity, any>): BatchedEntityChannel<TEntity>;\n}\n\n/** Creates a batched channel for collecting mutations */\nfunction createBatchedChannel(transactions: MutationTransaction[]): BatchedChannel {\n function channel<TEntity>(target: CollectionDef<TEntity, any>): BatchedCollectionChannel<TEntity>;\n function channel<TEntity>(target: EntityDef<TEntity, any>): BatchedEntityChannel<TEntity>;\n function channel<TEntity>(\n target: CollectionDef<TEntity, any> | EntityDef<TEntity, any>\n ): BatchedCollectionChannel<TEntity> | BatchedEntityChannel<TEntity> {\n if (target._type === 'collection') {\n return new BatchedCollectionChannel(target, transactions);\n } else {\n return new BatchedEntityChannel(target, transactions);\n }\n }\n return channel;\n}\n\n\n/** Options for useMutation hook */\nexport interface UseMutationOptions<TParams, TResponse> {\n /** Called when mutation starts */\n onMutate?: (params: TParams) => void;\n /** Called on success */\n onSuccess?: (data: TResponse, params: TParams) => void;\n /** Called on error */\n onError?: (error: Error, params: TParams) => void;\n}\n\n/** Return type for useMutation */\nexport interface MutationResult<TParams, TResponse> {\n mutate: (params: TParams) => void;\n mutateAsync: (params: TParams) => Promise<TResponse>;\n isLoading: boolean;\n isPending: boolean;\n isError: boolean;\n isSuccess: boolean;\n error: Error | null;\n data: TResponse | undefined;\n reset: () => void;\n}\n\n/**\n * Mutation hook with simplified optimistic updates\n *\n * @example\n * // Simple mutation\n * const { mutate } = useMutation(createPost)\n *\n * @example\n * // With optimistic update\n * const { mutate } = useMutation(createPost, {\n * optimistic: {\n * target: postsQuery,\n * action: 'prepend',\n * data: (params) => ({ ...params, _id: 'temp-id' })\n * }\n * })\n *\n * @example\n * // Multiple optimistic updates\n * const { mutate } = useMutation(deletePost, {\n * optimistic: [\n * { target: postsQuery, action: 'delete', id: (p) => p.postId },\n * { target: userStatsEntity, action: 'update', update: (stats) => ({ ...stats, postCount: stats.postCount - 1 }) }\n * ]\n * })\n */\nexport function useMutation<\n TParams,\n TResponse,\n>(\n def: MutationDef<TParams, TResponse>,\n options?: UseMutationOptions<TParams, TResponse> & {\n /** Optimistic update configuration - receives channel and params */\n optimistic?: (channel: BatchedChannel, params: TParams) => void;\n }\n): MutationResult<TParams, TResponse> {\n const mutation = useTanstackMutation<\n TResponse,\n Error,\n TParams,\n { rollbacks: (() => void)[]; optimisticId: string; transactions: MutationTransaction[] }\n >({\n mutationKey: def.name ? [def.name] : undefined,\n mutationFn: def.mutate,\n\n onMutate: async (params) => {\n const rollbacks: (() => void)[] = [];\n const optimisticId = nanoid();\n const transactions: MutationTransaction[] = [];\n\n // Apply optimistic updates via channel\n if (options?.optimistic) {\n const channel = createBatchedChannel(transactions);\n options.optimistic(channel, params);\n\n for (const tx of transactions) {\n const { target, action, data, id, where, update } = tx;\n\n // Add optimistic metadata to data\n const optimisticData = data\n ? {\n ...data,\n _optimistic: { id: optimisticId, status: 'pending' as const },\n }\n : undefined;\n\n const updateRollbacks = registry.applyUpdate(target.name, action, {\n data: optimisticData,\n id,\n where,\n update: update\n ? (item: any) => update(item)\n : optimisticData\n ? (item: any) => ({ ...item, ...optimisticData })\n : undefined,\n });\n\n rollbacks.push(...updateRollbacks);\n }\n }\n\n options?.onMutate?.(params);\n return { rollbacks, optimisticId, transactions };\n },\n\n onSuccess: (data, params, context) => {\n // If sync is enabled, replace optimistic data with server response\n if (context?.transactions) {\n for (const tx of context.transactions) {\n if (tx.sync && data) {\n // Replace optimistic item with real server data\n registry.applyUpdate(tx.target.name, 'update', {\n where: (item: any) =>\n item._optimistic?.id === context?.optimisticId,\n update: () => data as any,\n });\n }\n }\n }\n\n options?.onSuccess?.(data, params);\n },\n\n onError: (error, params, context) => {\n // Rollback all optimistic updates\n context?.rollbacks.forEach((rollback) => rollback());\n options?.onError?.(error, params);\n },\n });\n\n return {\n mutate: mutation.mutate,\n mutateAsync: mutation.mutateAsync,\n isLoading: mutation.isPending,\n isPending: mutation.isPending,\n isError: mutation.isError,\n isSuccess: mutation.isSuccess,\n error: mutation.error,\n data: mutation.data,\n reset: mutation.reset,\n };\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for query-toolkit
|
|
3
|
+
* Framework-agnostic definitions
|
|
4
|
+
*/
|
|
5
|
+
/** Extract the ID from an entity */
|
|
6
|
+
type IdGetter<T> = (item: T) => string;
|
|
7
|
+
/** Optimistic status for pending mutations */
|
|
8
|
+
type OptimisticStatus = 'pending' | 'success' | 'error';
|
|
9
|
+
/** Wraps an entity with optimistic metadata */
|
|
10
|
+
type Optimistic<T> = T & {
|
|
11
|
+
_optimistic?: {
|
|
12
|
+
id: string;
|
|
13
|
+
status: OptimisticStatus;
|
|
14
|
+
error?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/** Collection definition for arrays of items */
|
|
18
|
+
interface CollectionDef<TData, TParams = void> {
|
|
19
|
+
readonly _type: 'collection';
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly id: IdGetter<TData>;
|
|
22
|
+
readonly fetch: (params: TParams) => Promise<TData[]>;
|
|
23
|
+
}
|
|
24
|
+
/** Entity definition for single items */
|
|
25
|
+
interface EntityDef<TData, TParams = void> {
|
|
26
|
+
readonly _type: 'entity';
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly fetch: (params: TParams) => Promise<TData>;
|
|
29
|
+
}
|
|
30
|
+
/** Mutation definition */
|
|
31
|
+
interface MutationDef<TParams, TResponse = void> {
|
|
32
|
+
readonly _type: 'mutation';
|
|
33
|
+
readonly name?: string;
|
|
34
|
+
readonly mutate: (params: TParams) => Promise<TResponse>;
|
|
35
|
+
}
|
|
36
|
+
/** Unified definition type */
|
|
37
|
+
type AnyDef = CollectionDef<any, any> | EntityDef<any, any>;
|
|
38
|
+
/** Optimistic action types */
|
|
39
|
+
type OptimisticAction = 'prepend' | 'append' | 'update' | 'delete' | 'replace';
|
|
40
|
+
/** Optimistic update instruction */
|
|
41
|
+
interface OptimisticInstruction<T = any> {
|
|
42
|
+
target: CollectionDef<T, any> | EntityDef<T, any>;
|
|
43
|
+
action: OptimisticAction;
|
|
44
|
+
data?: Partial<T>;
|
|
45
|
+
id?: string;
|
|
46
|
+
where?: (item: T) => boolean;
|
|
47
|
+
update?: (item: T) => T;
|
|
48
|
+
}
|
|
49
|
+
/** Options for query execution */
|
|
50
|
+
interface QueryOptions {
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
staleTime?: number;
|
|
53
|
+
cacheTime?: number;
|
|
54
|
+
refetchOnMount?: boolean;
|
|
55
|
+
refetchOnWindowFocus?: boolean;
|
|
56
|
+
refetchInterval?: number | false;
|
|
57
|
+
}
|
|
58
|
+
/** Options for paginated queries */
|
|
59
|
+
interface PaginatedOptions extends QueryOptions {
|
|
60
|
+
getNextPageParam?: (lastPage: any[], allPages: any[][]) => unknown | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type { AnyDef as A, CollectionDef as C, EntityDef as E, IdGetter as I, MutationDef as M, OptimisticStatus as O, PaginatedOptions as P, QueryOptions as Q, Optimistic as a, OptimisticAction as b, OptimisticInstruction as c };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for query-toolkit
|
|
3
|
+
* Framework-agnostic definitions
|
|
4
|
+
*/
|
|
5
|
+
/** Extract the ID from an entity */
|
|
6
|
+
type IdGetter<T> = (item: T) => string;
|
|
7
|
+
/** Optimistic status for pending mutations */
|
|
8
|
+
type OptimisticStatus = 'pending' | 'success' | 'error';
|
|
9
|
+
/** Wraps an entity with optimistic metadata */
|
|
10
|
+
type Optimistic<T> = T & {
|
|
11
|
+
_optimistic?: {
|
|
12
|
+
id: string;
|
|
13
|
+
status: OptimisticStatus;
|
|
14
|
+
error?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/** Collection definition for arrays of items */
|
|
18
|
+
interface CollectionDef<TData, TParams = void> {
|
|
19
|
+
readonly _type: 'collection';
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly id: IdGetter<TData>;
|
|
22
|
+
readonly fetch: (params: TParams) => Promise<TData[]>;
|
|
23
|
+
}
|
|
24
|
+
/** Entity definition for single items */
|
|
25
|
+
interface EntityDef<TData, TParams = void> {
|
|
26
|
+
readonly _type: 'entity';
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly fetch: (params: TParams) => Promise<TData>;
|
|
29
|
+
}
|
|
30
|
+
/** Mutation definition */
|
|
31
|
+
interface MutationDef<TParams, TResponse = void> {
|
|
32
|
+
readonly _type: 'mutation';
|
|
33
|
+
readonly name?: string;
|
|
34
|
+
readonly mutate: (params: TParams) => Promise<TResponse>;
|
|
35
|
+
}
|
|
36
|
+
/** Unified definition type */
|
|
37
|
+
type AnyDef = CollectionDef<any, any> | EntityDef<any, any>;
|
|
38
|
+
/** Optimistic action types */
|
|
39
|
+
type OptimisticAction = 'prepend' | 'append' | 'update' | 'delete' | 'replace';
|
|
40
|
+
/** Optimistic update instruction */
|
|
41
|
+
interface OptimisticInstruction<T = any> {
|
|
42
|
+
target: CollectionDef<T, any> | EntityDef<T, any>;
|
|
43
|
+
action: OptimisticAction;
|
|
44
|
+
data?: Partial<T>;
|
|
45
|
+
id?: string;
|
|
46
|
+
where?: (item: T) => boolean;
|
|
47
|
+
update?: (item: T) => T;
|
|
48
|
+
}
|
|
49
|
+
/** Options for query execution */
|
|
50
|
+
interface QueryOptions {
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
staleTime?: number;
|
|
53
|
+
cacheTime?: number;
|
|
54
|
+
refetchOnMount?: boolean;
|
|
55
|
+
refetchOnWindowFocus?: boolean;
|
|
56
|
+
refetchInterval?: number | false;
|
|
57
|
+
}
|
|
58
|
+
/** Options for paginated queries */
|
|
59
|
+
interface PaginatedOptions extends QueryOptions {
|
|
60
|
+
getNextPageParam?: (lastPage: any[], allPages: any[][]) => unknown | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type { AnyDef as A, CollectionDef as C, EntityDef as E, IdGetter as I, MutationDef as M, OptimisticStatus as O, PaginatedOptions as P, QueryOptions as Q, Optimistic as a, OptimisticAction as b, OptimisticInstruction as c };
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "query-optimistic",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Simple, type-safe data fetching and optimistic updates for React with TanStack Query",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./core": {
|
|
16
|
+
"types": "./dist/core/index.d.ts",
|
|
17
|
+
"import": "./dist/core/index.mjs",
|
|
18
|
+
"require": "./dist/core/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./react": {
|
|
21
|
+
"types": "./dist/react/index.d.ts",
|
|
22
|
+
"import": "./dist/react/index.mjs",
|
|
23
|
+
"require": "./dist/react/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "npm run build && npm run typecheck"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"react",
|
|
38
|
+
"query",
|
|
39
|
+
"data-fetching",
|
|
40
|
+
"optimistic-updates",
|
|
41
|
+
"tanstack-query",
|
|
42
|
+
"react-query",
|
|
43
|
+
"state-management",
|
|
44
|
+
"typescript",
|
|
45
|
+
"hooks",
|
|
46
|
+
"mutations",
|
|
47
|
+
"cache"
|
|
48
|
+
],
|
|
49
|
+
"author": "avkulpin",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/avkulpin/query-optimistic.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/avkulpin/query-optimistic/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/avkulpin/query-optimistic#readme",
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@tanstack/react-query": ">=5.0.0",
|
|
61
|
+
"react": ">=18.0.0"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"nanoid": "^5.0.0"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@tanstack/react-query": "^5.0.0",
|
|
68
|
+
"@types/react": "^18.0.0",
|
|
69
|
+
"react": "^18.0.0",
|
|
70
|
+
"tsup": "^8.0.0",
|
|
71
|
+
"typescript": "^5.0.0"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=18.0.0"
|
|
75
|
+
}
|
|
76
|
+
}
|