query-optimistic 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel-OFFEKFI1.d.mts +188 -0
- package/dist/channel-OFFEKFI1.d.ts +188 -0
- package/dist/core/index.d.mts +18 -108
- package/dist/core/index.d.ts +18 -108
- package/dist/core/index.js +77 -11
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +77 -11
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +92 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -20
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +5 -3
- package/dist/react/index.d.ts +5 -3
- package/dist/react/index.js +82 -11
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +82 -11
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/types-BOq5W_Qm.d.mts +0 -63
- package/dist/types-BOq5W_Qm.d.ts +0 -63
package/dist/react/index.js
CHANGED
|
@@ -10,6 +10,16 @@ var nanoid = require('nanoid');
|
|
|
10
10
|
var QueryRegistry = class {
|
|
11
11
|
constructor() {
|
|
12
12
|
this.entries = /* @__PURE__ */ new Map();
|
|
13
|
+
this.queryClient = null;
|
|
14
|
+
this.collectionDefs = /* @__PURE__ */ new Map();
|
|
15
|
+
}
|
|
16
|
+
/** Set the query client for direct cache access */
|
|
17
|
+
setQueryClient(client) {
|
|
18
|
+
this.queryClient = client;
|
|
19
|
+
}
|
|
20
|
+
/** Register a collection definition for direct cache updates */
|
|
21
|
+
registerDef(def) {
|
|
22
|
+
this.collectionDefs.set(def.name, def);
|
|
13
23
|
}
|
|
14
24
|
/** Register an active query */
|
|
15
25
|
register(entry) {
|
|
@@ -17,6 +27,9 @@ var QueryRegistry = class {
|
|
|
17
27
|
this.entries.set(entry.name, /* @__PURE__ */ new Set());
|
|
18
28
|
}
|
|
19
29
|
this.entries.get(entry.name).add(entry);
|
|
30
|
+
if (entry.kind === "collection" || entry.kind === "paginated") {
|
|
31
|
+
this.collectionDefs.set(entry.name, entry.def);
|
|
32
|
+
}
|
|
20
33
|
}
|
|
21
34
|
/** Unregister a query when component unmounts */
|
|
22
35
|
unregister(entry) {
|
|
@@ -32,8 +45,20 @@ var QueryRegistry = class {
|
|
|
32
45
|
getByName(name) {
|
|
33
46
|
return Array.from(this.entries.get(name) ?? []);
|
|
34
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if params partially match the given scope object.
|
|
50
|
+
* Returns true if all key-value pairs in scope exist in params.
|
|
51
|
+
*/
|
|
52
|
+
matchesScope(params, scope) {
|
|
53
|
+
if (!scope) return true;
|
|
54
|
+
if (!params) return false;
|
|
55
|
+
return Object.entries(scope).every(([key, value]) => params[key] === value);
|
|
56
|
+
}
|
|
35
57
|
/** Apply an optimistic update to all queries with given name */
|
|
36
|
-
applyUpdate(name, action, payload) {
|
|
58
|
+
applyUpdate(name, action, payload, scope) {
|
|
59
|
+
if (scope && this.queryClient) {
|
|
60
|
+
return this.applyDirectCacheUpdate(name, action, payload, scope);
|
|
61
|
+
}
|
|
37
62
|
const entries = this.getByName(name);
|
|
38
63
|
const rollbacks = [];
|
|
39
64
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -41,7 +66,8 @@ var QueryRegistry = class {
|
|
|
41
66
|
const key = JSON.stringify(entry.queryKey);
|
|
42
67
|
if (seenKeys.has(key)) return false;
|
|
43
68
|
seenKeys.add(key);
|
|
44
|
-
|
|
69
|
+
const params = entry.queryKey[1];
|
|
70
|
+
return this.matchesScope(params, scope);
|
|
45
71
|
});
|
|
46
72
|
for (const entry of uniqueEntries) {
|
|
47
73
|
if (entry.kind === "collection") {
|
|
@@ -78,6 +104,45 @@ var QueryRegistry = class {
|
|
|
78
104
|
}
|
|
79
105
|
return rollbacks;
|
|
80
106
|
}
|
|
107
|
+
/** Apply update directly to query cache (used when scope is provided) */
|
|
108
|
+
applyDirectCacheUpdate(name, action, payload, scope) {
|
|
109
|
+
if (!this.queryClient) return [];
|
|
110
|
+
const def = this.collectionDefs.get(name);
|
|
111
|
+
if (!def) return [];
|
|
112
|
+
const rollbacks = [];
|
|
113
|
+
const queries = this.queryClient.getQueriesData({
|
|
114
|
+
queryKey: [name]
|
|
115
|
+
});
|
|
116
|
+
for (const [queryKey, data] of queries) {
|
|
117
|
+
if (!data) continue;
|
|
118
|
+
const params = queryKey[1];
|
|
119
|
+
if (!this.matchesScope(params, scope)) continue;
|
|
120
|
+
const isPaginated = data && typeof data === "object" && "pages" in data;
|
|
121
|
+
if (isPaginated) {
|
|
122
|
+
const paginatedData = data;
|
|
123
|
+
const previous = paginatedData;
|
|
124
|
+
rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
|
|
125
|
+
this.queryClient.setQueryData(queryKey, (prev) => {
|
|
126
|
+
if (!prev) return prev;
|
|
127
|
+
return {
|
|
128
|
+
...prev,
|
|
129
|
+
pages: prev.pages.map(
|
|
130
|
+
(page, i) => i === 0 ? this.applyCollectionUpdate(page, action, payload, def.id) : page
|
|
131
|
+
)
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
const arrayData = data;
|
|
136
|
+
const previous = arrayData;
|
|
137
|
+
rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
|
|
138
|
+
this.queryClient.setQueryData(queryKey, (prev) => {
|
|
139
|
+
if (!prev) return prev;
|
|
140
|
+
return this.applyCollectionUpdate(prev, action, payload, def.id);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return rollbacks;
|
|
145
|
+
}
|
|
81
146
|
applyCollectionUpdate(items, action, payload, getId) {
|
|
82
147
|
switch (action) {
|
|
83
148
|
case "prepend":
|
|
@@ -117,6 +182,7 @@ var registry = new QueryRegistry();
|
|
|
117
182
|
function useQuery(def, options) {
|
|
118
183
|
const queryClient = reactQuery.useQueryClient();
|
|
119
184
|
const { params, paginated, getPageParams, queryKey: customQueryKey, syncInBackground, ...queryOptions } = options ?? {};
|
|
185
|
+
registry.setQueryClient(queryClient);
|
|
120
186
|
const queryKey = react.useMemo(
|
|
121
187
|
() => customQueryKey ?? [def.name, params].filter(Boolean),
|
|
122
188
|
[customQueryKey, def.name, params]
|
|
@@ -229,16 +295,18 @@ function useQuery(def, options) {
|
|
|
229
295
|
];
|
|
230
296
|
}
|
|
231
297
|
var BatchedCollectionChannel = class {
|
|
232
|
-
constructor(target, transactions) {
|
|
298
|
+
constructor(target, transactions, options) {
|
|
233
299
|
this.target = target;
|
|
234
300
|
this.transactions = transactions;
|
|
301
|
+
this.options = options;
|
|
235
302
|
}
|
|
236
303
|
prepend(data, options) {
|
|
237
304
|
this.transactions.push({
|
|
238
305
|
target: this.target,
|
|
239
306
|
action: "prepend",
|
|
240
307
|
data,
|
|
241
|
-
reconcile: options?.reconcile
|
|
308
|
+
reconcile: options?.reconcile,
|
|
309
|
+
scope: this.options?.scope
|
|
242
310
|
});
|
|
243
311
|
return this;
|
|
244
312
|
}
|
|
@@ -247,7 +315,8 @@ var BatchedCollectionChannel = class {
|
|
|
247
315
|
target: this.target,
|
|
248
316
|
action: "append",
|
|
249
317
|
data,
|
|
250
|
-
reconcile: options?.reconcile
|
|
318
|
+
reconcile: options?.reconcile,
|
|
319
|
+
scope: this.options?.scope
|
|
251
320
|
});
|
|
252
321
|
return this;
|
|
253
322
|
}
|
|
@@ -257,7 +326,8 @@ var BatchedCollectionChannel = class {
|
|
|
257
326
|
action: "update",
|
|
258
327
|
id,
|
|
259
328
|
update: updateFn,
|
|
260
|
-
reconcile: options?.reconcile
|
|
329
|
+
reconcile: options?.reconcile,
|
|
330
|
+
scope: this.options?.scope
|
|
261
331
|
});
|
|
262
332
|
return this;
|
|
263
333
|
}
|
|
@@ -265,7 +335,8 @@ var BatchedCollectionChannel = class {
|
|
|
265
335
|
this.transactions.push({
|
|
266
336
|
target: this.target,
|
|
267
337
|
action: "delete",
|
|
268
|
-
id
|
|
338
|
+
id,
|
|
339
|
+
scope: this.options?.scope
|
|
269
340
|
});
|
|
270
341
|
return this;
|
|
271
342
|
}
|
|
@@ -295,9 +366,9 @@ var BatchedEntityChannel = class {
|
|
|
295
366
|
}
|
|
296
367
|
};
|
|
297
368
|
function createBatchedChannel(transactions) {
|
|
298
|
-
function channel(target) {
|
|
369
|
+
function channel(target, options) {
|
|
299
370
|
if (target._type === "collection") {
|
|
300
|
-
return new BatchedCollectionChannel(target, transactions);
|
|
371
|
+
return new BatchedCollectionChannel(target, transactions, options);
|
|
301
372
|
} else {
|
|
302
373
|
return new BatchedEntityChannel(target, transactions);
|
|
303
374
|
}
|
|
@@ -316,7 +387,7 @@ function useMutation(def, options) {
|
|
|
316
387
|
const channel = createBatchedChannel(transactions);
|
|
317
388
|
options.optimistic(channel, params);
|
|
318
389
|
for (const tx of transactions) {
|
|
319
|
-
const { target, action, data, id, where, update } = tx;
|
|
390
|
+
const { target, action, data, id, where, update, scope } = tx;
|
|
320
391
|
const optimisticData = data ? {
|
|
321
392
|
...data,
|
|
322
393
|
_optimistic: { id: optimisticId, status: "pending" }
|
|
@@ -326,7 +397,7 @@ function useMutation(def, options) {
|
|
|
326
397
|
id,
|
|
327
398
|
where,
|
|
328
399
|
update: update ? (item) => update(item) : optimisticData ? (item) => ({ ...item, ...optimisticData }) : void 0
|
|
329
|
-
});
|
|
400
|
+
}, scope);
|
|
330
401
|
rollbacks.push(...updateRollbacks);
|
|
331
402
|
}
|
|
332
403
|
}
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/registry.ts","../../src/react/use-query.ts","../../src/react/use-mutation.ts"],"names":["useQueryClient","useMemo","query","useTanstackQuery","useEffect","useInfiniteQuery","useTanstackMutation","nanoid"],"mappings":"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","file":"index.js","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 // Deduplicate by queryKey to avoid updating the same cache entry multiple times\n const seenKeys = new Set<string>();\n const uniqueEntries = entries.filter((entry) => {\n const key = JSON.stringify(entry.queryKey);\n if (seenKeys.has(key)) return false;\n seenKeys.add(key);\n return true;\n });\n\n for (const entry of uniqueEntries) {\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 } from 'react';\nimport {\n useQuery as useTanstackQuery,\n useInfiniteQuery,\n useQueryClient,\n type UseQueryResult,\n type UseInfiniteQueryResult,\n} from '@tanstack/react-query';\nimport type {\n CollectionDef,\n EntityDef,\n Optimistic,\n QueryOptions,\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 * Keep this query registered for optimistic updates even when unmounted.\n * Useful when you want updates from other pages to sync to this query's cache.\n */\n syncInBackground?: boolean;\n}\n\n/** Return type for collection queries: [data, queryResult] */\nexport type QueryResult<T> = [\n Optimistic<T>[] | undefined,\n UseQueryResult<T[], Error>\n];\n\n/** Return type for paginated queries: [data, infiniteQueryResult] */\nexport type PaginatedQueryResult<T> = [\n Optimistic<T>[] | undefined,\n UseInfiniteQueryResult<{ pages: T[][]; pageParams: unknown[] }, Error>\n];\n\n/** Return type for entity queries: [data, queryResult] */\nexport type EntityResult<T> = [\n Optimistic<T> | undefined,\n UseQueryResult<T, Error>\n];\n\n/**\n * Unified query hook for fetching data\n *\n * Returns a tuple of [data, queryResult] where queryResult is the full\n * TanStack Query result object with all properties (isLoading, isError,\n * refetch, etc.)\n *\n * @example\n * // Simple collection query\n * const [data, query] = useQuery(postsQuery, { params: { limit: 10 } })\n * // query.isLoading, query.isError, query.refetch(), etc.\n *\n * @example\n * // Paginated query - returns full infinite query result\n * const [data, query] = useQuery(postsQuery, {\n * paginated: true,\n * getPageParams: ({ pageParam }) => ({ page: pageParam, limit: 10 })\n * })\n * // query.fetchNextPage(), query.hasNextPage, query.isFetchingNextPage\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, syncInBackground, ...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) {\n return;\n }\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\n // If syncInBackground is enabled, don't unregister on unmount\n if (!syncInBackground) {\n return () => registry.unregister(entry);\n }\n }, [def.name, queryKey, query.status, query.data, queryClient, syncInBackground]);\n\n return [\n query.data as Optimistic<TData> | undefined,\n query as UseQueryResult<TData, Error>,\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\n // If syncInBackground is enabled, don't unregister on unmount\n if (!syncInBackground) {\n return () => registry.unregister(entry);\n }\n }, [def.name, queryKey, infiniteQuery.status, infiniteQuery.data, queryClient, syncInBackground]);\n\n return [\n flatData,\n infiniteQuery as UseInfiniteQueryResult<{ pages: TData[][]; pageParams: unknown[] }, Error>,\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\n // If syncInBackground is enabled, don't unregister on unmount\n if (!syncInBackground) {\n return () => registry.unregister(entry);\n }\n }, [def.name, queryKey, query.status, query.data, queryClient, syncInBackground]);\n\n return [\n query.data as Optimistic<TData>[] | undefined,\n query as UseQueryResult<TData[], Error>,\n ];\n}\n","import {\n useMutation as useTanstackMutation,\n type UseMutationResult,\n} 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 reconcile with server response (replace optimistic with real data) */\n reconcile?: 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 reconcile?: 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?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'prepend',\n data,\n reconcile: options?.reconcile,\n });\n return this;\n }\n\n append(data: TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'append',\n data,\n reconcile: options?.reconcile,\n });\n return this;\n }\n\n update(id: string, updateFn: (item: TEntity) => TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'update',\n id,\n update: updateFn,\n reconcile: options?.reconcile,\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?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'update',\n update: updateFn,\n reconcile: options?.reconcile,\n });\n return this;\n }\n\n replace(data: TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'replace',\n data,\n reconcile: options?.reconcile,\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/**\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): UseMutationResult<TResponse, Error, TParams> {\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 reconcile is enabled, replace optimistic data with server response\n if (context?.transactions) {\n for (const tx of context.transactions) {\n if (tx.reconcile && 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 mutation;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/registry.ts","../../src/react/use-query.ts","../../src/react/use-mutation.ts"],"names":["useQueryClient","useMemo","query","useTanstackQuery","useEffect","useInfiniteQuery","useTanstackMutation","nanoid"],"mappings":";;;;;;;;;AA8CA,IAAM,gBAAN,MAAoB;AAAA,EAApB,WAAA,GAAA;AACE,IAAA,IAAA,CAAQ,OAAA,uBAAc,GAAA,EAAkC;AACxD,IAAA,IAAA,CAAQ,WAAA,GAAkC,IAAA;AAC1C,IAAA,IAAA,CAAQ,cAAA,uBAAqB,GAAA,EAAqC;AAAA,EAAA;AAAA;AAAA,EAGlE,eAAe,MAAA,EAA2B;AACxC,IAAA,IAAA,CAAK,WAAA,GAAc,MAAA;AAAA,EACrB;AAAA;AAAA,EAGA,YAAY,GAAA,EAAoC;AAC9C,IAAA,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAAA,EACvC;AAAA;AAAA,EAGA,SAAS,KAAA,EAA8B;AACrC,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,EAAG;AACjC,MAAA,IAAA,CAAK,QAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,kBAAM,IAAI,KAAK,CAAA;AAAA,IACxC;AACA,IAAA,IAAA,CAAK,QAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,CAAG,IAAI,KAAK,CAAA;AAGvC,IAAA,IAAI,KAAA,CAAM,IAAA,KAAS,YAAA,IAAgB,KAAA,CAAM,SAAS,WAAA,EAAa;AAC7D,MAAA,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,GAAG,CAAA;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA,EAGA,WAAW,KAAA,EAA8B;AACvC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAM,IAAI,CAAA;AACvC,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,GAAA,CAAI,OAAO,KAAK,CAAA;AAChB,MAAA,IAAI,GAAA,CAAI,SAAS,CAAA,EAAG;AAClB,QAAA,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,UAAU,IAAA,EAAiC;AACzC,IAAA,OAAO,KAAA,CAAM,KAAK,IAAA,CAAK,OAAA,CAAQ,IAAI,IAAI,CAAA,IAAK,EAAE,CAAA;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,YAAA,CAAa,QAA6C,KAAA,EAA0C;AAC1G,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,IAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AAGpB,IAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM,MAAA,CAAO,GAAG,MAAM,KAAK,CAAA;AAAA,EAC5E;AAAA;AAAA,EAGA,WAAA,CACE,IAAA,EACA,MAAA,EACA,OAAA,EAMA,KAAA,EACgB;AAEhB,IAAA,IAAI,KAAA,IAAS,KAAK,WAAA,EAAa;AAC7B,MAAA,OAAO,IAAA,CAAK,sBAAA,CAAuB,IAAA,EAAM,MAAA,EAAQ,SAAS,KAAK,CAAA;AAAA,IACjE;AAGA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AACnC,IAAA,MAAM,YAA4B,EAAC;AAGnC,IAAA,MAAM,QAAA,uBAAe,GAAA,EAAY;AACjC,IAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,MAAA,CAAO,CAAC,KAAA,KAAU;AAC9C,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,QAAQ,CAAA;AACzC,MAAA,IAAI,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,KAAA;AAC9B,MAAA,QAAA,CAAS,IAAI,GAAG,CAAA;AAEhB,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,QAAA,CAAS,CAAC,CAAA;AAC/B,MAAA,OAAO,IAAA,CAAK,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA;AAAA,IACxC,CAAC,CAAA;AAED,IAAA,KAAA,MAAW,SAAS,aAAA,EAAe;AACjC,MAAA,IAAI,KAAA,CAAM,SAAS,YAAA,EAAc;AAC/B,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,EAAQ;AAC/B,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAA,CAAQ,MAAM,QAAQ,CAAA;AACnD,QAAA,SAAA,CAAU,KAAK,QAAQ,CAAA;AAEvB,QAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,KAAS;AACtB,UAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,UAAA,OAAO,KAAK,qBAAA,CAAsB,IAAA,EAAM,QAAQ,OAAA,EAAS,KAAA,CAAM,IAAI,EAAE,CAAA;AAAA,QACvE,CAAC,CAAA;AAAA,MACH,CAAA,MAAA,IAAW,KAAA,CAAM,IAAA,KAAS,WAAA,EAAa;AACrC,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,EAAQ;AAC/B,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAA,CAAQ,MAAM,QAAQ,CAAA;AACnD,QAAA,SAAA,CAAU,KAAK,QAAQ,CAAA;AAEvB,QAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,KAAS;AACtB,UAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,UAAA,OAAO;AAAA,YACL,GAAG,IAAA;AAAA,YACH,KAAA,EAAO,KAAK,KAAA,CAAM,GAAA;AAAA,cAAI,CAAC,IAAA,EAAM,CAAA,KAC3B,CAAA,KAAM,CAAA,GACF,IAAA,CAAK,qBAAA,CAAsB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,KAAA,CAAM,GAAA,CAAI,EAAE,CAAA,GAC9D;AAAA;AACN,WACF;AAAA,QACF,CAAC,CAAA;AAAA,MACH,CAAA,MAAA,IAAW,KAAA,CAAM,IAAA,KAAS,QAAA,EAAU;AAClC,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,EAAQ;AAC/B,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,OAAA,CAAQ,MAAM,QAAQ,CAAA;AACnD,QAAA,SAAA,CAAU,KAAK,QAAQ,CAAA;AAEvB,QAAA,IAAI,MAAA,KAAW,QAAA,IAAY,OAAA,CAAQ,MAAA,EAAQ;AACzC,UAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,KAAU,IAAA,GAAO,QAAQ,MAAA,CAAQ,IAAS,IAAI,IAAK,CAAA;AAAA,QACpE,CAAA,MAAA,IAAW,MAAA,KAAW,SAAA,IAAa,OAAA,CAAQ,IAAA,EAAM;AAC/C,UAAA,KAAA,CAAM,OAAA,CAAQ,MAAM,OAAA,CAAQ,IAAS,CAAA;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO,SAAA;AAAA,EACT;AAAA;AAAA,EAGQ,sBAAA,CACN,IAAA,EACA,MAAA,EACA,OAAA,EAMA,KAAA,EACgB;AAChB,IAAA,IAAI,CAAC,IAAA,CAAK,WAAA,EAAa,OAAO,EAAC;AAE/B,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,IAAI,CAAA;AACxC,IAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAC;AAElB,IAAA,MAAM,YAA4B,EAAC;AAGnC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,WAAA,CAAY,cAAA,CAA8D;AAAA,MAC7F,QAAA,EAAU,CAAC,IAAI;AAAA,KAChB,CAAA;AAED,IAAA,KAAA,MAAW,CAAC,QAAA,EAAU,IAAI,CAAA,IAAK,OAAA,EAAS;AACtC,MAAA,IAAI,CAAC,IAAA,EAAM;AAGX,MAAA,MAAM,MAAA,GAAS,SAAS,CAAC,CAAA;AACzB,MAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA,EAAG;AAGvC,MAAA,MAAM,WAAA,GAAc,IAAA,IAAQ,OAAO,IAAA,KAAS,YAAY,OAAA,IAAW,IAAA;AAEnE,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,MAAM,aAAA,GAAgB,IAAA;AACtB,QAAA,MAAM,QAAA,GAAW,aAAA;AACjB,QAAA,SAAA,CAAU,KAAK,MAAM,IAAA,CAAK,YAAa,YAAA,CAAa,QAAA,EAAU,QAAQ,CAAC,CAAA;AAEvE,QAAA,IAAA,CAAK,WAAA,CAAY,YAAA,CAAsD,QAAA,EAAU,CAAC,IAAA,KAAS;AACzF,UAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,UAAA,OAAO;AAAA,YACL,GAAG,IAAA;AAAA,YACH,KAAA,EAAO,KAAK,KAAA,CAAM,GAAA;AAAA,cAAI,CAAC,IAAA,EAAM,CAAA,KAC3B,CAAA,KAAM,CAAA,GACF,IAAA,CAAK,qBAAA,CAAsB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,GAAA,CAAI,EAAE,CAAA,GACxD;AAAA;AACN,WACF;AAAA,QACF,CAAC,CAAA;AAAA,MACH,CAAA,MAAO;AACL,QAAA,MAAM,SAAA,GAAY,IAAA;AAClB,QAAA,MAAM,QAAA,GAAW,SAAA;AACjB,QAAA,SAAA,CAAU,KAAK,MAAM,IAAA,CAAK,YAAa,YAAA,CAAa,QAAA,EAAU,QAAQ,CAAC,CAAA;AAEvE,QAAA,IAAA,CAAK,WAAA,CAAY,YAAA,CAAkB,QAAA,EAAU,CAAC,IAAA,KAAS;AACrD,UAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,UAAA,OAAO,KAAK,qBAAA,CAAsB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,IAAI,EAAE,CAAA;AAAA,QACjE,CAAC,CAAA;AAAA,MACH;AAAA,IACF;AAEA,IAAA,OAAO,SAAA;AAAA,EACT;AAAA,EAEQ,qBAAA,CACN,KAAA,EACA,MAAA,EACA,OAAA,EAMA,KAAA,EACK;AACL,IAAA,QAAQ,MAAA;AAAQ,MACd,KAAK,SAAA;AACH,QAAA,OAAO,QAAQ,IAAA,GAAO,CAAC,QAAQ,IAAA,EAAW,GAAG,KAAK,CAAA,GAAI,KAAA;AAAA,MAExD,KAAK,QAAA;AACH,QAAA,OAAO,QAAQ,IAAA,GAAO,CAAC,GAAG,KAAA,EAAO,OAAA,CAAQ,IAAS,CAAA,GAAI,KAAA;AAAA,MAExD,KAAK,QAAA;AACH,QAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,KAAS;AACzB,UAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,EAAA,GACpB,KAAA,CAAM,IAAI,MAAM,OAAA,CAAQ,EAAA,GACxB,OAAA,CAAQ,KAAA,GAAQ,IAAI,CAAA;AACxB,UAAA,IAAI,OAAA,IAAW,QAAQ,MAAA,EAAQ;AAC7B,YAAA,OAAO,OAAA,CAAQ,OAAO,IAAI,CAAA;AAAA,UAC5B;AACA,UAAA,IAAI,OAAA,IAAW,QAAQ,IAAA,EAAM;AAC3B,YAAA,OAAO,EAAE,GAAG,IAAA,EAAM,GAAG,QAAQ,IAAA,EAAK;AAAA,UACpC;AACA,UAAA,OAAO,IAAA;AAAA,QACT,CAAC,CAAA;AAAA,MAEH,KAAK,QAAA;AACH,QAAA,OAAO,KAAA,CAAM,MAAA,CAAO,CAAC,IAAA,KAAS;AAC5B,UAAA,IAAI,QAAQ,EAAA,EAAI,OAAO,KAAA,CAAM,IAAI,MAAM,OAAA,CAAQ,EAAA;AAC/C,UAAA,IAAI,QAAQ,KAAA,EAAO,OAAO,CAAC,OAAA,CAAQ,MAAM,IAAI,CAAA;AAC7C,UAAA,OAAO,IAAA;AAAA,QACT,CAAC,CAAA;AAAA,MAEH,KAAK,SAAA;AACH,QAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,KAAS;AACzB,UAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,EAAA,GACpB,KAAA,CAAM,IAAI,MAAM,OAAA,CAAQ,EAAA,GACxB,OAAA,CAAQ,KAAA,GAAQ,IAAI,CAAA;AACxB,UAAA,OAAO,OAAA,IAAW,OAAA,CAAQ,IAAA,GAAQ,OAAA,CAAQ,IAAA,GAAa,IAAA;AAAA,QACzD,CAAC,CAAA;AAAA,MAEH;AACE,QAAA,OAAO,KAAA;AAAA;AACX,EACF;AACF,CAAA;AAGO,IAAM,QAAA,GAAW,IAAI,aAAA,EAAc;;;AC7MnC,SAAS,QAAA,CACd,KACA,OAAA,EACwE;AACxE,EAAA,MAAM,cAAcA,yBAAA,EAAe;AACnC,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAW,aAAA,EAAe,QAAA,EAAU,cAAA,EAAgB,gBAAA,EAAkB,GAAG,YAAA,EAAa,GAAI,OAAA,IAAW,EAAC;AAGtH,EAAA,QAAA,CAAS,eAAe,WAAW,CAAA;AAGnC,EAAA,MAAM,QAAA,GAAWC,aAAA;AAAA,IACf,MAAM,kBAAkB,CAAC,GAAA,CAAI,MAAM,MAAM,CAAA,CAAE,OAAO,OAAO,CAAA;AAAA,IACzD,CAAC,cAAA,EAAgB,GAAA,CAAI,IAAA,EAAM,MAAM;AAAA,GACnC;AAGA,EAAA,IAAI,GAAA,CAAI,UAAU,QAAA,EAAU;AAC1B,IAAA,MAAM,SAAA,GAAY,GAAA;AAClB,IAAA,MAAMC,SAAQC,mBAAA,CAAiB;AAAA,MAC7B,QAAA;AAAA,MACA,OAAA,EAAS,MAAM,SAAA,CAAU,KAAA,CAAM,MAAiB,CAAA;AAAA,MAChD,SAAS,YAAA,CAAa,OAAA;AAAA,MACtB,WAAW,YAAA,CAAa,SAAA;AAAA,MACxB,QAAQ,YAAA,CAAa,SAAA;AAAA,MACrB,gBAAgB,YAAA,CAAa,cAAA;AAAA,MAC7B,sBAAsB,YAAA,CAAa,oBAAA;AAAA,MACnC,iBAAiB,YAAA,CAAa;AAAA,KAC/B,CAAA;AAGD,IAAAC,eAAA,CAAU,MAAM;AACd,MAAA,IAAIF,MAAAA,CAAM,MAAA,KAAW,SAAA,IAAa,CAACA,OAAM,IAAA,EAAM;AAC7C,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,KAAA,GAAQ;AAAA,QACZ,IAAA,EAAM,QAAA;AAAA,QACN,MAAM,GAAA,CAAI,IAAA;AAAA,QACV,QAAA;AAAA,QACA,GAAA,EAAK,SAAA;AAAA,QACL,OAAA,EAAS,MAAM,WAAA,CAAY,YAAA,CAAoB,QAAQ,CAAA;AAAA,QACvD,SAAS,CAAC,OAAA,KACR,WAAA,CAAY,YAAA,CAAoB,UAAU,OAAO;AAAA,OACrD;AAEA,MAAA,QAAA,CAAS,SAAS,KAAK,CAAA;AAGvB,MAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,QAAA,OAAO,MAAM,QAAA,CAAS,UAAA,CAAW,KAAK,CAAA;AAAA,MACxC;AAAA,IACF,CAAA,EAAG,CAAC,GAAA,CAAI,IAAA,EAAM,QAAA,EAAUA,MAAAA,CAAM,MAAA,EAAQA,MAAAA,CAAM,IAAA,EAAM,WAAA,EAAa,gBAAgB,CAAC,CAAA;AAEhF,IAAA,OAAO;AAAA,MACLA,MAAAA,CAAM,IAAA;AAAA,MACNA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,aAAA,GAAgB,GAAA;AAGtB,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,MAAM,gBAAgBG,2BAAA,CAAiB;AAAA,MACrC,QAAA;AAAA,MACA,OAAA,EAAS,CAAC,EAAE,SAAA,EAAU,KAAM;AAC1B,QAAA,MAAM,UAAA,GAAa,gBACf,aAAA,CAAc,EAAE,WAAgC,CAAA,GAC/C,EAAE,SAAA,EAAU;AACjB,QAAA,OAAO,aAAA,CAAc,MAAM,UAAU,CAAA;AAAA,MACvC,CAAA;AAAA,MACA,gBAAA,EAAkB,CAAA;AAAA,MAClB,gBAAA,EAAkB,CAAC,QAAA,EAAU,QAAA,KAC3B,SAAS,MAAA,GAAS,CAAA,GAAI,QAAA,CAAS,MAAA,GAAS,CAAA,GAAI,MAAA;AAAA,MAC9C,SAAS,YAAA,CAAa,OAAA;AAAA,MACtB,WAAW,YAAA,CAAa,SAAA;AAAA,MACxB,QAAQ,YAAA,CAAa,SAAA;AAAA,MACrB,gBAAgB,YAAA,CAAa,cAAA;AAAA,MAC7B,sBAAsB,YAAA,CAAa,oBAAA;AAAA,MACnC,iBAAiB,YAAA,CAAa;AAAA,KAC/B,CAAA;AAED,IAAA,MAAM,QAAA,GAAWJ,aAAA;AAAA,MACf,MAAM,aAAA,CAAc,IAAA,EAAM,KAAA,CAAM,IAAA,EAAK;AAAA,MACrC,CAAC,cAAc,IAAI;AAAA,KACrB;AAGA,IAAAG,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,aAAA,CAAc,MAAA,KAAW,SAAA,IAAa,CAAC,cAAc,IAAA,EAAM;AAE/D,MAAA,MAAM,KAAA,GAAQ;AAAA,QACZ,IAAA,EAAM,WAAA;AAAA,QACN,MAAM,GAAA,CAAI,IAAA;AAAA,QACV,QAAA;AAAA,QACA,GAAA,EAAK,aAAA;AAAA,QACL,OAAA,EAAS,MACP,WAAA,CAAY,YAAA;AAAA,UACV;AAAA,SACF;AAAA,QACF,SAAS,CACP,OAAA,KAIA,WAAA,CAAY,YAAA,CAGT,UAAU,OAAO;AAAA,OACxB;AAEA,MAAA,QAAA,CAAS,SAAS,KAAK,CAAA;AAGvB,MAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,QAAA,OAAO,MAAM,QAAA,CAAS,UAAA,CAAW,KAAK,CAAA;AAAA,MACxC;AAAA,IACF,CAAA,EAAG,CAAC,GAAA,CAAI,IAAA,EAAM,QAAA,EAAU,aAAA,CAAc,MAAA,EAAQ,aAAA,CAAc,IAAA,EAAM,WAAA,EAAa,gBAAgB,CAAC,CAAA;AAEhG,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAGA,EAAA,MAAM,QAAQD,mBAAA,CAAiB;AAAA,IAC7B,QAAA;AAAA,IACA,OAAA,EAAS,MAAM,aAAA,CAAc,KAAA,CAAM,MAAiB,CAAA;AAAA,IACpD,SAAS,YAAA,CAAa,OAAA;AAAA,IACtB,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,QAAQ,YAAA,CAAa,SAAA;AAAA,IACrB,gBAAgB,YAAA,CAAa,cAAA;AAAA,IAC7B,sBAAsB,YAAA,CAAa,oBAAA;AAAA,IACnC,iBAAiB,YAAA,CAAa;AAAA,GAC/B,CAAA;AAGD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,SAAA,IAAa,CAAC,MAAM,IAAA,EAAM;AAE/C,IAAA,MAAM,KAAA,GAAQ;AAAA,MACZ,IAAA,EAAM,YAAA;AAAA,MACN,MAAM,GAAA,CAAI,IAAA;AAAA,MACV,QAAA;AAAA,MACA,GAAA,EAAK,aAAA;AAAA,MACL,OAAA,EAAS,MAAM,WAAA,CAAY,YAAA,CAAsB,QAAQ,CAAA;AAAA,MACzD,SAAS,CAAC,OAAA,KACR,WAAA,CAAY,YAAA,CAAsB,UAAU,OAAO;AAAA,KACvD;AAEA,IAAA,QAAA,CAAS,SAAS,KAAK,CAAA;AAGvB,IAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAO,MAAM,QAAA,CAAS,UAAA,CAAW,KAAK,CAAA;AAAA,IACxC;AAAA,EACF,CAAA,EAAG,CAAC,GAAA,CAAI,IAAA,EAAM,QAAA,EAAU,KAAA,CAAM,MAAA,EAAQ,KAAA,CAAM,IAAA,EAAM,WAAA,EAAa,gBAAgB,CAAC,CAAA;AAEhF,EAAA,OAAO;AAAA,IACL,KAAA,CAAM,IAAA;AAAA,IACN;AAAA,GACF;AACF;AC9IA,IAAM,2BAAN,MAAwC;AAAA,EACtC,WAAA,CACmB,MAAA,EACA,YAAA,EACA,OAAA,EACjB;AAHiB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAChB;AAAA,EAEH,OAAA,CAAQ,MAAe,OAAA,EAAyC;AAC9D,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK;AAAA,MACrB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,SAAA;AAAA,MACR,IAAA;AAAA,MACA,WAAW,OAAA,EAAS,SAAA;AAAA,MACpB,KAAA,EAAO,KAAK,OAAA,EAAS;AAAA,KACtB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAA,CAAO,MAAe,OAAA,EAAyC;AAC7D,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK;AAAA,MACrB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,QAAA;AAAA,MACR,IAAA;AAAA,MACA,WAAW,OAAA,EAAS,SAAA;AAAA,MACpB,KAAA,EAAO,KAAK,OAAA,EAAS;AAAA,KACtB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAA,CAAO,EAAA,EAAY,QAAA,EAAsC,OAAA,EAAyC;AAChG,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK;AAAA,MACrB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,QAAA;AAAA,MACR,EAAA;AAAA,MACA,MAAA,EAAQ,QAAA;AAAA,MACR,WAAW,OAAA,EAAS,SAAA;AAAA,MACpB,KAAA,EAAO,KAAK,OAAA,EAAS;AAAA,KACtB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,OAAO,EAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK;AAAA,MACrB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,QAAA;AAAA,MACR,EAAA;AAAA,MACA,KAAA,EAAO,KAAK,OAAA,EAAS;AAAA,KACtB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AAGA,IAAM,uBAAN,MAAoC;AAAA,EAClC,WAAA,CACmB,QACA,YAAA,EACjB;AAFiB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AAAA,EAChB;AAAA,EAEH,MAAA,CAAO,UAAsC,OAAA,EAAyC;AACpF,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK;AAAA,MACrB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,QAAA;AAAA,MACR,MAAA,EAAQ,QAAA;AAAA,MACR,WAAW,OAAA,EAAS;AAAA,KACrB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,OAAA,CAAQ,MAAe,OAAA,EAAyC;AAC9D,IAAA,IAAA,CAAK,aAAa,IAAA,CAAK;AAAA,MACrB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,MAAA,EAAQ,SAAA;AAAA,MACR,IAAA;AAAA,MACA,WAAW,OAAA,EAAS;AAAA,KACrB,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AASA,SAAS,qBAAqB,YAAA,EAAqD;AAGjF,EAAA,SAAS,OAAA,CACP,QACA,OAAA,EACmE;AACnE,IAAA,IAAI,MAAA,CAAO,UAAU,YAAA,EAAc;AACjC,MAAA,OAAO,IAAI,wBAAA,CAAyB,MAAA,EAAQ,YAAA,EAAc,OAAO,CAAA;AAAA,IACnE,CAAA,MAAO;AACL,MAAA,OAAO,IAAI,oBAAA,CAAqB,MAAA,EAAQ,YAAY,CAAA;AAAA,IACtD;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAuCO,SAAS,WAAA,CAId,KACA,OAAA,EAI8C;AAC9C,EAAA,MAAM,WAAWE,sBAAA,CAKf;AAAA,IACA,aAAa,GAAA,CAAI,IAAA,GAAO,CAAC,GAAA,CAAI,IAAI,CAAA,GAAI,MAAA;AAAA,IACrC,YAAY,GAAA,CAAI,MAAA;AAAA,IAEhB,QAAA,EAAU,OAAO,MAAA,KAAW;AAC1B,MAAA,MAAM,YAA4B,EAAC;AACnC,MAAA,MAAM,eAAeC,aAAA,EAAO;AAC5B,MAAA,MAAM,eAAsC,EAAC;AAG7C,MAAA,IAAI,SAAS,UAAA,EAAY;AACvB,QAAA,MAAM,OAAA,GAAU,qBAAqB,YAAY,CAAA;AACjD,QAAA,OAAA,CAAQ,UAAA,CAAW,SAAS,MAAM,CAAA;AAElC,QAAA,KAAA,MAAW,MAAM,YAAA,EAAc;AAC7B,UAAA,MAAM,EAAE,QAAQ,MAAA,EAAQ,IAAA,EAAM,IAAI,KAAA,EAAO,MAAA,EAAQ,OAAM,GAAI,EAAA;AAG3D,UAAA,MAAM,iBAAiB,IAAA,GACnB;AAAA,YACE,GAAG,IAAA;AAAA,YACH,WAAA,EAAa,EAAE,EAAA,EAAI,YAAA,EAAc,QAAQ,SAAA;AAAmB,WAC9D,GACA,MAAA;AAEJ,UAAA,MAAM,eAAA,GAAkB,QAAA,CAAS,WAAA,CAAY,MAAA,CAAO,MAAM,MAAA,EAAQ;AAAA,YAChE,IAAA,EAAM,cAAA;AAAA,YACN,EAAA;AAAA,YACA,KAAA;AAAA,YACA,MAAA,EAAQ,MAAA,GACJ,CAAC,IAAA,KAAc,OAAO,IAAI,CAAA,GAC1B,cAAA,GACE,CAAC,UAAe,EAAE,GAAG,IAAA,EAAM,GAAG,gBAAe,CAAA,GAC7C;AAAA,aACL,KAAK,CAAA;AAER,UAAA,SAAA,CAAU,IAAA,CAAK,GAAG,eAAe,CAAA;AAAA,QACnC;AAAA,MACF;AAEA,MAAA,OAAA,EAAS,WAAW,MAAM,CAAA;AAC1B,MAAA,OAAO,EAAE,SAAA,EAAW,YAAA,EAAc,YAAA,EAAa;AAAA,IACjD,CAAA;AAAA,IAEA,SAAA,EAAW,CAAC,IAAA,EAAM,MAAA,EAAQ,OAAA,KAAY;AAEpC,MAAA,IAAI,SAAS,YAAA,EAAc;AACzB,QAAA,KAAA,MAAW,EAAA,IAAM,QAAQ,YAAA,EAAc;AACrC,UAAA,IAAI,EAAA,CAAG,aAAa,IAAA,EAAM;AAExB,YAAA,QAAA,CAAS,WAAA,CAAY,EAAA,CAAG,MAAA,CAAO,IAAA,EAAM,QAAA,EAAU;AAAA,cAC7C,OAAO,CAAC,IAAA,KACN,IAAA,CAAK,WAAA,EAAa,OAAO,OAAA,EAAS,YAAA;AAAA,cACpC,QAAQ,MAAM;AAAA,aACf,CAAA;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAEA,MAAA,OAAA,EAAS,SAAA,GAAY,MAAM,MAAM,CAAA;AAAA,IACnC,CAAA;AAAA,IAEA,OAAA,EAAS,CAAC,KAAA,EAAO,MAAA,EAAQ,OAAA,KAAY;AAEnC,MAAA,OAAA,EAAS,SAAA,CAAU,OAAA,CAAQ,CAAC,QAAA,KAAa,UAAU,CAAA;AACnD,MAAA,OAAA,EAAS,OAAA,GAAU,OAAO,MAAM,CAAA;AAAA,IAClC;AAAA,GACD,CAAA;AAED,EAAA,OAAO,QAAA;AACT","file":"index.js","sourcesContent":["import type { QueryClient } from '@tanstack/react-query';\nimport 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 private queryClient: QueryClient | null = null;\n private collectionDefs = new Map<string, CollectionDef<any, any>>();\n\n /** Set the query client for direct cache access */\n setQueryClient(client: QueryClient): void {\n this.queryClient = client;\n }\n\n /** Register a collection definition for direct cache updates */\n registerDef(def: CollectionDef<any, any>): void {\n this.collectionDefs.set(def.name, def);\n }\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 // Also store the def for direct cache access\n if (entry.kind === 'collection' || entry.kind === 'paginated') {\n this.collectionDefs.set(entry.name, entry.def);\n }\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 /**\n * Check if params partially match the given scope object.\n * Returns true if all key-value pairs in scope exist in params.\n */\n private matchesScope(params: Record<string, unknown> | undefined, scope?: Record<string, unknown>): boolean {\n if (!scope) return true;\n if (!params) return false;\n\n // Check if all scope keys exist in params with same value\n return Object.entries(scope).every(([key, value]) => params[key] === value);\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 scope?: Record<string, unknown>\n ): (() => void)[] {\n // When scope is provided and we have a queryClient, update cache directly\n if (scope && this.queryClient) {\n return this.applyDirectCacheUpdate(name, action, payload, scope);\n }\n\n // Otherwise, use registry-based updates\n const entries = this.getByName(name);\n const rollbacks: (() => void)[] = [];\n\n // Deduplicate by queryKey to avoid updating the same cache entry multiple times\n const seenKeys = new Set<string>();\n const uniqueEntries = entries.filter((entry) => {\n const key = JSON.stringify(entry.queryKey);\n if (seenKeys.has(key)) return false;\n seenKeys.add(key);\n // Also filter by scope if provided\n const params = entry.queryKey[1] as Record<string, unknown> | undefined;\n return this.matchesScope(params, scope);\n });\n\n for (const entry of uniqueEntries) {\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 /** Apply update directly to query cache (used when scope is provided) */\n private applyDirectCacheUpdate<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 scope: Record<string, unknown>\n ): (() => void)[] {\n if (!this.queryClient) return [];\n\n const def = this.collectionDefs.get(name);\n if (!def) return [];\n\n const rollbacks: (() => void)[] = [];\n\n // Get all queries with this name from the cache\n const queries = this.queryClient.getQueriesData<T[] | { pages: T[][]; pageParams: unknown[] }>({\n queryKey: [name],\n });\n\n for (const [queryKey, data] of queries) {\n if (!data) continue;\n\n // Extract params from queryKey [name, params]\n const params = queryKey[1] as Record<string, unknown> | undefined;\n if (!this.matchesScope(params, scope)) continue;\n\n // Check if this is a paginated query\n const isPaginated = data && typeof data === 'object' && 'pages' in data;\n\n if (isPaginated) {\n const paginatedData = data as { pages: T[][]; pageParams: unknown[] };\n const previous = paginatedData;\n rollbacks.push(() => this.queryClient!.setQueryData(queryKey, previous));\n\n this.queryClient.setQueryData<{ pages: T[][]; pageParams: unknown[] }>(queryKey, (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, def.id)\n : page\n ),\n };\n });\n } else {\n const arrayData = data as T[];\n const previous = arrayData;\n rollbacks.push(() => this.queryClient!.setQueryData(queryKey, previous));\n\n this.queryClient.setQueryData<T[]>(queryKey, (prev) => {\n if (!prev) return prev;\n return this.applyCollectionUpdate(prev, action, payload, def.id);\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 } from 'react';\nimport {\n useQuery as useTanstackQuery,\n useInfiniteQuery,\n useQueryClient,\n type UseQueryResult,\n type UseInfiniteQueryResult,\n} from '@tanstack/react-query';\nimport type {\n CollectionDef,\n EntityDef,\n Optimistic,\n QueryOptions,\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 * Keep this query registered for optimistic updates even when unmounted.\n * Useful when you want updates from other pages to sync to this query's cache.\n */\n syncInBackground?: boolean;\n}\n\n/** Return type for collection queries: [data, queryResult] */\nexport type QueryResult<T> = [\n Optimistic<T>[] | undefined,\n UseQueryResult<T[], Error>\n];\n\n/** Return type for paginated queries: [data, infiniteQueryResult] */\nexport type PaginatedQueryResult<T> = [\n Optimistic<T>[] | undefined,\n UseInfiniteQueryResult<{ pages: T[][]; pageParams: unknown[] }, Error>\n];\n\n/** Return type for entity queries: [data, queryResult] */\nexport type EntityResult<T> = [\n Optimistic<T> | undefined,\n UseQueryResult<T, Error>\n];\n\n/**\n * Unified query hook for fetching data\n *\n * Returns a tuple of [data, queryResult] where queryResult is the full\n * TanStack Query result object with all properties (isLoading, isError,\n * refetch, etc.)\n *\n * @example\n * // Simple collection query\n * const [data, query] = useQuery(postsQuery, { params: { limit: 10 } })\n * // query.isLoading, query.isError, query.refetch(), etc.\n *\n * @example\n * // Paginated query - returns full infinite query result\n * const [data, query] = useQuery(postsQuery, {\n * paginated: true,\n * getPageParams: ({ pageParam }) => ({ page: pageParam, limit: 10 })\n * })\n * // query.fetchNextPage(), query.hasNextPage, query.isFetchingNextPage\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, syncInBackground, ...queryOptions } = options ?? {};\n\n // Set queryClient on registry for direct cache access\n registry.setQueryClient(queryClient);\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) {\n return;\n }\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\n // If syncInBackground is enabled, don't unregister on unmount\n if (!syncInBackground) {\n return () => registry.unregister(entry);\n }\n }, [def.name, queryKey, query.status, query.data, queryClient, syncInBackground]);\n\n return [\n query.data as Optimistic<TData> | undefined,\n query as UseQueryResult<TData, Error>,\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\n // If syncInBackground is enabled, don't unregister on unmount\n if (!syncInBackground) {\n return () => registry.unregister(entry);\n }\n }, [def.name, queryKey, infiniteQuery.status, infiniteQuery.data, queryClient, syncInBackground]);\n\n return [\n flatData,\n infiniteQuery as UseInfiniteQueryResult<{ pages: TData[][]; pageParams: unknown[] }, Error>,\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\n // If syncInBackground is enabled, don't unregister on unmount\n if (!syncInBackground) {\n return () => registry.unregister(entry);\n }\n }, [def.name, queryKey, query.status, query.data, queryClient, syncInBackground]);\n\n return [\n query.data as Optimistic<TData>[] | undefined,\n query as UseQueryResult<TData[], Error>,\n ];\n}\n","import {\n useMutation as useTanstackMutation,\n type UseMutationResult,\n} 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 ChannelOptions,\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 reconcile with server response (replace optimistic with real data) */\n reconcile?: 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 reconcile?: boolean;\n scope?: Record<string, unknown>;\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 private readonly options?: ChannelOptions\n ) {}\n\n prepend(data: TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'prepend',\n data,\n reconcile: options?.reconcile,\n scope: this.options?.scope,\n });\n return this;\n }\n\n append(data: TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'append',\n data,\n reconcile: options?.reconcile,\n scope: this.options?.scope,\n });\n return this;\n }\n\n update(id: string, updateFn: (item: TEntity) => TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'update',\n id,\n update: updateFn,\n reconcile: options?.reconcile,\n scope: this.options?.scope,\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 scope: this.options?.scope,\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?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'update',\n update: updateFn,\n reconcile: options?.reconcile,\n });\n return this;\n }\n\n replace(data: TEntity, options?: { reconcile?: boolean }): this {\n this.transactions.push({\n target: this.target,\n action: 'replace',\n data,\n reconcile: options?.reconcile,\n });\n return this;\n }\n}\n\n/** Internal batched channel type */\ninterface BatchedChannel {\n <TEntity>(target: CollectionDef<TEntity, any>, options?: ChannelOptions): 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>, options?: ChannelOptions): 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 options?: ChannelOptions\n ): BatchedCollectionChannel<TEntity> | BatchedEntityChannel<TEntity> {\n if (target._type === 'collection') {\n return new BatchedCollectionChannel(target, transactions, options);\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/**\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): UseMutationResult<TResponse, Error, TParams> {\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, scope } = 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 }, scope);\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 reconcile is enabled, replace optimistic data with server response\n if (context?.transactions) {\n for (const tx of context.transactions) {\n if (tx.reconcile && 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 mutation;\n}\n"]}
|
package/dist/react/index.mjs
CHANGED
|
@@ -8,6 +8,16 @@ import { nanoid } from 'nanoid';
|
|
|
8
8
|
var QueryRegistry = class {
|
|
9
9
|
constructor() {
|
|
10
10
|
this.entries = /* @__PURE__ */ new Map();
|
|
11
|
+
this.queryClient = null;
|
|
12
|
+
this.collectionDefs = /* @__PURE__ */ new Map();
|
|
13
|
+
}
|
|
14
|
+
/** Set the query client for direct cache access */
|
|
15
|
+
setQueryClient(client) {
|
|
16
|
+
this.queryClient = client;
|
|
17
|
+
}
|
|
18
|
+
/** Register a collection definition for direct cache updates */
|
|
19
|
+
registerDef(def) {
|
|
20
|
+
this.collectionDefs.set(def.name, def);
|
|
11
21
|
}
|
|
12
22
|
/** Register an active query */
|
|
13
23
|
register(entry) {
|
|
@@ -15,6 +25,9 @@ var QueryRegistry = class {
|
|
|
15
25
|
this.entries.set(entry.name, /* @__PURE__ */ new Set());
|
|
16
26
|
}
|
|
17
27
|
this.entries.get(entry.name).add(entry);
|
|
28
|
+
if (entry.kind === "collection" || entry.kind === "paginated") {
|
|
29
|
+
this.collectionDefs.set(entry.name, entry.def);
|
|
30
|
+
}
|
|
18
31
|
}
|
|
19
32
|
/** Unregister a query when component unmounts */
|
|
20
33
|
unregister(entry) {
|
|
@@ -30,8 +43,20 @@ var QueryRegistry = class {
|
|
|
30
43
|
getByName(name) {
|
|
31
44
|
return Array.from(this.entries.get(name) ?? []);
|
|
32
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if params partially match the given scope object.
|
|
48
|
+
* Returns true if all key-value pairs in scope exist in params.
|
|
49
|
+
*/
|
|
50
|
+
matchesScope(params, scope) {
|
|
51
|
+
if (!scope) return true;
|
|
52
|
+
if (!params) return false;
|
|
53
|
+
return Object.entries(scope).every(([key, value]) => params[key] === value);
|
|
54
|
+
}
|
|
33
55
|
/** Apply an optimistic update to all queries with given name */
|
|
34
|
-
applyUpdate(name, action, payload) {
|
|
56
|
+
applyUpdate(name, action, payload, scope) {
|
|
57
|
+
if (scope && this.queryClient) {
|
|
58
|
+
return this.applyDirectCacheUpdate(name, action, payload, scope);
|
|
59
|
+
}
|
|
35
60
|
const entries = this.getByName(name);
|
|
36
61
|
const rollbacks = [];
|
|
37
62
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -39,7 +64,8 @@ var QueryRegistry = class {
|
|
|
39
64
|
const key = JSON.stringify(entry.queryKey);
|
|
40
65
|
if (seenKeys.has(key)) return false;
|
|
41
66
|
seenKeys.add(key);
|
|
42
|
-
|
|
67
|
+
const params = entry.queryKey[1];
|
|
68
|
+
return this.matchesScope(params, scope);
|
|
43
69
|
});
|
|
44
70
|
for (const entry of uniqueEntries) {
|
|
45
71
|
if (entry.kind === "collection") {
|
|
@@ -76,6 +102,45 @@ var QueryRegistry = class {
|
|
|
76
102
|
}
|
|
77
103
|
return rollbacks;
|
|
78
104
|
}
|
|
105
|
+
/** Apply update directly to query cache (used when scope is provided) */
|
|
106
|
+
applyDirectCacheUpdate(name, action, payload, scope) {
|
|
107
|
+
if (!this.queryClient) return [];
|
|
108
|
+
const def = this.collectionDefs.get(name);
|
|
109
|
+
if (!def) return [];
|
|
110
|
+
const rollbacks = [];
|
|
111
|
+
const queries = this.queryClient.getQueriesData({
|
|
112
|
+
queryKey: [name]
|
|
113
|
+
});
|
|
114
|
+
for (const [queryKey, data] of queries) {
|
|
115
|
+
if (!data) continue;
|
|
116
|
+
const params = queryKey[1];
|
|
117
|
+
if (!this.matchesScope(params, scope)) continue;
|
|
118
|
+
const isPaginated = data && typeof data === "object" && "pages" in data;
|
|
119
|
+
if (isPaginated) {
|
|
120
|
+
const paginatedData = data;
|
|
121
|
+
const previous = paginatedData;
|
|
122
|
+
rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
|
|
123
|
+
this.queryClient.setQueryData(queryKey, (prev) => {
|
|
124
|
+
if (!prev) return prev;
|
|
125
|
+
return {
|
|
126
|
+
...prev,
|
|
127
|
+
pages: prev.pages.map(
|
|
128
|
+
(page, i) => i === 0 ? this.applyCollectionUpdate(page, action, payload, def.id) : page
|
|
129
|
+
)
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
const arrayData = data;
|
|
134
|
+
const previous = arrayData;
|
|
135
|
+
rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
|
|
136
|
+
this.queryClient.setQueryData(queryKey, (prev) => {
|
|
137
|
+
if (!prev) return prev;
|
|
138
|
+
return this.applyCollectionUpdate(prev, action, payload, def.id);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return rollbacks;
|
|
143
|
+
}
|
|
79
144
|
applyCollectionUpdate(items, action, payload, getId) {
|
|
80
145
|
switch (action) {
|
|
81
146
|
case "prepend":
|
|
@@ -115,6 +180,7 @@ var registry = new QueryRegistry();
|
|
|
115
180
|
function useQuery(def, options) {
|
|
116
181
|
const queryClient = useQueryClient();
|
|
117
182
|
const { params, paginated, getPageParams, queryKey: customQueryKey, syncInBackground, ...queryOptions } = options ?? {};
|
|
183
|
+
registry.setQueryClient(queryClient);
|
|
118
184
|
const queryKey = useMemo(
|
|
119
185
|
() => customQueryKey ?? [def.name, params].filter(Boolean),
|
|
120
186
|
[customQueryKey, def.name, params]
|
|
@@ -227,16 +293,18 @@ function useQuery(def, options) {
|
|
|
227
293
|
];
|
|
228
294
|
}
|
|
229
295
|
var BatchedCollectionChannel = class {
|
|
230
|
-
constructor(target, transactions) {
|
|
296
|
+
constructor(target, transactions, options) {
|
|
231
297
|
this.target = target;
|
|
232
298
|
this.transactions = transactions;
|
|
299
|
+
this.options = options;
|
|
233
300
|
}
|
|
234
301
|
prepend(data, options) {
|
|
235
302
|
this.transactions.push({
|
|
236
303
|
target: this.target,
|
|
237
304
|
action: "prepend",
|
|
238
305
|
data,
|
|
239
|
-
reconcile: options?.reconcile
|
|
306
|
+
reconcile: options?.reconcile,
|
|
307
|
+
scope: this.options?.scope
|
|
240
308
|
});
|
|
241
309
|
return this;
|
|
242
310
|
}
|
|
@@ -245,7 +313,8 @@ var BatchedCollectionChannel = class {
|
|
|
245
313
|
target: this.target,
|
|
246
314
|
action: "append",
|
|
247
315
|
data,
|
|
248
|
-
reconcile: options?.reconcile
|
|
316
|
+
reconcile: options?.reconcile,
|
|
317
|
+
scope: this.options?.scope
|
|
249
318
|
});
|
|
250
319
|
return this;
|
|
251
320
|
}
|
|
@@ -255,7 +324,8 @@ var BatchedCollectionChannel = class {
|
|
|
255
324
|
action: "update",
|
|
256
325
|
id,
|
|
257
326
|
update: updateFn,
|
|
258
|
-
reconcile: options?.reconcile
|
|
327
|
+
reconcile: options?.reconcile,
|
|
328
|
+
scope: this.options?.scope
|
|
259
329
|
});
|
|
260
330
|
return this;
|
|
261
331
|
}
|
|
@@ -263,7 +333,8 @@ var BatchedCollectionChannel = class {
|
|
|
263
333
|
this.transactions.push({
|
|
264
334
|
target: this.target,
|
|
265
335
|
action: "delete",
|
|
266
|
-
id
|
|
336
|
+
id,
|
|
337
|
+
scope: this.options?.scope
|
|
267
338
|
});
|
|
268
339
|
return this;
|
|
269
340
|
}
|
|
@@ -293,9 +364,9 @@ var BatchedEntityChannel = class {
|
|
|
293
364
|
}
|
|
294
365
|
};
|
|
295
366
|
function createBatchedChannel(transactions) {
|
|
296
|
-
function channel(target) {
|
|
367
|
+
function channel(target, options) {
|
|
297
368
|
if (target._type === "collection") {
|
|
298
|
-
return new BatchedCollectionChannel(target, transactions);
|
|
369
|
+
return new BatchedCollectionChannel(target, transactions, options);
|
|
299
370
|
} else {
|
|
300
371
|
return new BatchedEntityChannel(target, transactions);
|
|
301
372
|
}
|
|
@@ -314,7 +385,7 @@ function useMutation(def, options) {
|
|
|
314
385
|
const channel = createBatchedChannel(transactions);
|
|
315
386
|
options.optimistic(channel, params);
|
|
316
387
|
for (const tx of transactions) {
|
|
317
|
-
const { target, action, data, id, where, update } = tx;
|
|
388
|
+
const { target, action, data, id, where, update, scope } = tx;
|
|
318
389
|
const optimisticData = data ? {
|
|
319
390
|
...data,
|
|
320
391
|
_optimistic: { id: optimisticId, status: "pending" }
|
|
@@ -324,7 +395,7 @@ function useMutation(def, options) {
|
|
|
324
395
|
id,
|
|
325
396
|
where,
|
|
326
397
|
update: update ? (item) => update(item) : optimisticData ? (item) => ({ ...item, ...optimisticData }) : void 0
|
|
327
|
-
});
|
|
398
|
+
}, scope);
|
|
328
399
|
rollbacks.push(...updateRollbacks);
|
|
329
400
|
}
|
|
330
401
|
}
|