@qiaopeng/tanstack-query-plus 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -968,7 +968,7 @@ const list7 = conditionalUpdateItems(
968
968
  )
969
969
  ```
970
970
 
971
- ### 7.9 完整示例:Todo 应用
971
+ ### 7.9 完整示例:Todo 应用
972
972
 
973
973
  ```tsx
974
974
  import { useEnhancedQuery, useMutation } from '@qiaopeng/tanstack-query-plus/hooks'
@@ -1038,54 +1038,55 @@ function TodoApp() {
1038
1038
  </ul>
1039
1039
  </div>
1040
1040
  )
1041
+ }
1041
1042
  }
1042
1043
  ```
1043
1044
 
1044
- ### 7.10 跨分页一致性:前缀失效与缓存对齐
1045
+ ### 7.10 分页家族一致性(避免分页切换回退)
1045
1046
 
1046
- 在分页列表中,乐观更新通常只更新“当前页”的缓存。为了保证切换到其他页时也看到最新数据,可以使用前缀级失效与跨页缓存对齐:
1047
+ 在带分页/筛选/排序的列表中,编辑、新增、删除、状态变更成功后切换 `page/pageSize` 时,可能命中同一资源的另一查询变体,从而短暂显示旧快照。本库提供可选的“家族一致性”能力,保障在成功后切换分页不回退。
1047
1048
 
1048
- 1. 使用统一的域前缀管理 Query Key(例如 `products` 域):
1049
-
1050
- ```tsx
1051
- import { createDomainKeyFactory } from '@qiaopeng/tanstack-query-plus/core'
1052
-
1053
- const productKeys = createDomainKeyFactory('products')
1054
- const listPrefix = productKeys.lists() // ['tanstack-query', 'products', 'list']
1055
- ```
1056
-
1057
- 2. 在 mutation 的乐观更新中启用跨页对齐与前缀失效:
1049
+ - 开启方式:在 `useMutation` 传入 `consistency` 配置(默认关闭,显式启用)
1050
+ - 安全默认:`mode: 'invalidate'` 只执行家族失效,确保最终与服务端一致
1051
+ - 进阶模式:`mode: 'sync+invalidate'` 先对缓存中已存在的变体按 `id` 合并更新,再统一失效
1052
+ - 形状适配:通过 `listSelector` 适配 `{items,total}` 结构;无法识别时自动降级为仅失效
1058
1053
 
1059
1054
  ```tsx
1060
1055
  import { useMutation } from '@qiaopeng/tanstack-query-plus/hooks'
1056
+ import { createPaginatedKey } from '@qiaopeng/tanstack-query-plus/core'
1061
1057
 
1062
- function UpdateProduct({ product }) {
1063
- const mutation = useMutation({
1064
- mutationFn: (patch) => api.updateProduct(product.id, patch),
1058
+ function useUpdateProduct({ page, pageSize }) {
1059
+ return useMutation({
1060
+ mutationFn: (updated) => api.updateProduct(updated.id, updated),
1061
+
1062
+ // 当前页的乐观更新:先更新 UI,再发请求,失败自动回滚
1065
1063
  optimistic: {
1066
- queryKey: productKeys.detail(product.id),
1067
- updater: (oldData, patch) => ({ ...oldData, ...patch }),
1068
- invalidateScope: 'prefix',
1069
- invalidatePrefixKey: listPrefix,
1070
- reconcileCachedPages: true
1071
- }
1072
- })
1064
+ queryKey: createPaginatedKey(['products', 'list'], page, pageSize),
1065
+ updater: (old, updated) => old?.map((p) => (p.id === updated.id ? { ...p, ...updated } : p)),
1066
+ },
1073
1067
 
1074
- return (
1075
- <button onClick={() => mutation.mutate({ title: '新标题' })}>
1076
- 更新商品
1077
- </button>
1078
- )
1068
+ // 家族一致性:编辑成功后,保障跨分页/筛选/排序的变体不回退
1069
+ consistency: {
1070
+ baseKey: ['products', 'list'],
1071
+ mode: 'sync+invalidate',
1072
+ idField: 'id',
1073
+ // 适配分页对象:提取 items;不确定时返回 null 将仅失效
1074
+ listSelector: (data) => {
1075
+ if (data && typeof data === 'object' && 'items' in (data as any)) {
1076
+ return { items: (data as any).items, total: (data as any).total }
1077
+ }
1078
+ if (Array.isArray(data)) return { items: data }
1079
+ return null
1080
+ },
1081
+ maxKeys: 50,
1082
+ },
1083
+ })
1079
1084
  }
1080
1085
  ```
1081
1086
 
1082
- 行为说明:
1083
- - 乐观阶段:先更新当前 key,对已缓存的所有分页列表也应用同样的更新,使切页立即看到新值。
1084
- - 成功后:对域前缀进行失效,后续展示从服务端获取最终一致数据。
1085
-
1086
- 注意事项:
1087
- - 请确保分页查询的 Key 都共享同一前缀(例如通过 `createDomainKeyFactory`),否则前缀失效无法覆盖所有分页。
1088
- - 在超大列表场景中,跨页对齐可能带来一定开销,可按需开启。
1087
+ 适用操作与行为说明:
1088
+ - 编辑/删除:在 `sync+invalidate` 模式下,会对已缓存的家族变体按 `id` 合并或移除;随后统一失效,最终以服务端为准
1089
+ - 新增/状态变更:默认不做跨页注入,仅当前页处理并家族失效;需要跨页放置时请在服务端裁决归属
1089
1090
 
1090
1091
  现在你已经掌握了数据变更和乐观更新。接下来,让我们学习如何处理无限滚动和分页场景。
1091
1092
 
@@ -3245,6 +3246,36 @@ function SearchResults({ query }) {
3245
3246
  }
3246
3247
  ```
3247
3248
 
3249
+ ### 14.11 家族一致性工具
3250
+
3251
+ 在某些高级场景下,你可能需要自行枚举并同步同一资源的家族查询变体(分页/筛选/排序等)。本库提供了工具函数用于匹配与安全同步:
3252
+
3253
+ ```tsx
3254
+ import { useQueryClient } from '@qiaopeng/tanstack-query-plus'
3255
+ import { findFamilyQueries, syncEntityAcrossFamily } from '@qiaopeng/tanstack-query-plus/utils'
3256
+
3257
+ function useManualFamilySync() {
3258
+ const queryClient = useQueryClient()
3259
+ const sync = (updated) => {
3260
+ const keys = findFamilyQueries(queryClient, { baseKey: ['products', 'list'], maxKeys: 50 })
3261
+ syncEntityAcrossFamily(queryClient, keys, updated, {
3262
+ idField: 'id',
3263
+ listSelector: (data) => {
3264
+ if (data && typeof data === 'object' && 'items' in (data as any)) {
3265
+ return { items: (data as any).items, total: (data as any).total }
3266
+ }
3267
+ if (Array.isArray(data)) return { items: data }
3268
+ return null
3269
+ },
3270
+ })
3271
+ keys.forEach((key) => queryClient.invalidateQueries({ queryKey: key }))
3272
+ }
3273
+ return { sync }
3274
+ }
3275
+ ```
3276
+
3277
+ 提示:上述工具已在 `useMutation` 的一致性配置中自动使用;仅在需要手动控制时使用它们。
3278
+
3248
3279
  现在你已经掌握了所有核心功能!最后,让我们看看一些最佳实践和常见问题。
3249
3280
 
3250
3281
  ---
@@ -5,4 +5,5 @@ export { isDev, isProd, isTest } from "./env.js";
5
5
  export { focusManager, type FocusManagerConfig, getSmartFocusManager, pauseFocusManager, resetSmartFocusManager, resumeFocusManager, setupFocusManager, SmartFocusManager } from "./focusManager.js";
6
6
  export { areKeysEqual, containsEntity, createComplexKey, createDomainKeyFactory, createFilteredKey, createMutationKeyFactory, createPaginatedKey, createSearchKey, createSortedKey, extractEntityId, matchesKeyPattern, normalizeQueryKey, queryKeys, validateQueryKey } from "./keys.js";
7
7
  export { createAppQueryOptions, createAppQueryOptionsWithSelect } from "./queryOptions.js";
8
+ export { createListQueryOptions, type ListQueryConfig } from "./queryOptions.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,EAC5B,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,2BAA2B,EAC3B,cAAc,EACd,cAAc,EACd,cAAc,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,KAAK,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,gBAAgB,EACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,qBAAqB,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,EAC5B,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,2BAA2B,EAC3B,cAAc,EACd,cAAc,EACd,cAAc,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,KAAK,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,gBAAgB,EACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,qBAAqB,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -5,3 +5,4 @@ export { isDev, isProd, isTest } from "./env.js";
5
5
  export { focusManager, getSmartFocusManager, pauseFocusManager, resetSmartFocusManager, resumeFocusManager, setupFocusManager, SmartFocusManager } from "./focusManager.js";
6
6
  export { areKeysEqual, containsEntity, createComplexKey, createDomainKeyFactory, createFilteredKey, createMutationKeyFactory, createPaginatedKey, createSearchKey, createSortedKey, extractEntityId, matchesKeyPattern, normalizeQueryKey, queryKeys, validateQueryKey } from "./keys.js";
7
7
  export { createAppQueryOptions, createAppQueryOptionsWithSelect } from "./queryOptions.js";
8
+ export { createListQueryOptions } from "./queryOptions.js";
@@ -11,4 +11,9 @@ export interface SelectQueryConfig<TData, TSelected = TData> extends Omit<BaseQu
11
11
  select: (data: TData) => TSelected;
12
12
  }
13
13
  export declare function createAppQueryOptionsWithSelect<TData, TSelected = TData>(config: SelectQueryConfig<TData, TSelected>): UseQueryOptions<TData, DefaultError, TSelected, QueryKey>;
14
+ export interface ListQueryConfig<TData> extends Omit<BaseQueryConfig<TData>, "staleTime" | "gcTime"> {
15
+ staleTime?: number;
16
+ gcTime?: number;
17
+ }
18
+ export declare function createListQueryOptions<TData>(config: ListQueryConfig<TData>): UseQueryOptions<TData, DefaultError, TData, QueryKey>;
14
19
  //# sourceMappingURL=queryOptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"queryOptions.d.ts","sourceRoot":"","sources":["../../src/core/queryOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIpG,MAAM,WAAW,eAAe,CAAC,KAAK;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,CAAC,CAalI;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAAE,SAAQ,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC1G,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC;CACpC;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,MAAM,EAAE,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC,CAahL"}
1
+ {"version":3,"file":"queryOptions.d.ts","sourceRoot":"","sources":["../../src/core/queryOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIpG,MAAM,WAAW,eAAe,CAAC,KAAK;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,CAAC,CAalI;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAAE,SAAQ,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC1G,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC;CACpC;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,MAAM,EAAE,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC,CAahL;AAED,MAAM,WAAW,eAAe,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,CAAC,CAanI"}
@@ -28,3 +28,17 @@ export function createAppQueryOptionsWithSelect(config) {
28
28
  refetchOnMount: true
29
29
  });
30
30
  }
31
+ export function createListQueryOptions(config) {
32
+ return queryOptions({
33
+ queryKey: config.queryKey,
34
+ queryFn: config.queryFn,
35
+ staleTime: config.staleTime ?? 0,
36
+ gcTime: config.gcTime ?? DEFAULT_GC_TIME,
37
+ enabled: config.enabled,
38
+ retry: defaultQueryRetryStrategy,
39
+ retryDelay: exponentialBackoff,
40
+ refetchOnWindowFocus: true,
41
+ refetchOnReconnect: true,
42
+ refetchOnMount: true
43
+ });
44
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useMutation.d.ts","sourceRoot":"","sources":["../../src/hooks/useMutation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useMutation.d.ts","sourceRoot":"","sources":["../../src/hooks/useMutation.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,25 @@
1
1
  import { useQueryClient, useMutation as useTanStackMutation } from "@tanstack/react-query";
2
2
  import { DEFAULT_MUTATION_CONFIG } from "../core/config.js";
3
+ function deriveFamilyKey(queryKey) {
4
+ const arr = Array.isArray(queryKey) ? [...queryKey] : [queryKey];
5
+ while (arr.length > 1) {
6
+ const last = arr[arr.length - 1];
7
+ if (last && typeof last === "object" && !Array.isArray(last)) {
8
+ arr.pop();
9
+ continue;
10
+ }
11
+ if (typeof last === "string" && (last === "paginated" || last === "filtered" || last === "sorted" || last === "search" || last === "complex")) {
12
+ arr.pop();
13
+ continue;
14
+ }
15
+ break;
16
+ }
17
+ return arr;
18
+ }
19
+ function isListFamilyKey(queryKey) {
20
+ const parts = Array.isArray(queryKey) ? queryKey : [queryKey];
21
+ return parts.includes("list") || parts.includes("paginated");
22
+ }
3
23
  export function useMutation(options) {
4
24
  const queryClient = useQueryClient();
5
25
  const { optimistic, onMutate, onError, onSuccess, onSettled, ...restOptions } = options;
@@ -31,7 +51,7 @@ export function useMutation(options) {
31
51
  return { userContext };
32
52
  }
33
53
  try {
34
- await queryClient.cancelQueries({ queryKey: optimistic.queryKey });
54
+ await queryClient.cancelQueries({ queryKey: optimistic.queryKey, exact: true });
35
55
  const previousData = queryClient.getQueryData(optimistic.queryKey);
36
56
  let mappedVariables = variables;
37
57
  if (optimistic.fieldMapping && typeof variables === "object" && variables !== null) {
@@ -44,14 +64,7 @@ export function useMutation(options) {
44
64
  }
45
65
  });
46
66
  }
47
- const safeUpdater = (oldData) => {
48
- const next = optimistic.updater(oldData, mappedVariables);
49
- return typeof next === "undefined" ? oldData : next;
50
- };
51
- queryClient.setQueryData(optimistic.queryKey, safeUpdater);
52
- if (optimistic.reconcileCachedPages && optimistic.invalidatePrefixKey) {
53
- queryClient.setQueriesData({ queryKey: optimistic.invalidatePrefixKey }, safeUpdater);
54
- }
67
+ queryClient.setQueryData(optimistic.queryKey, (oldData) => optimistic.updater(oldData, mappedVariables));
55
68
  const mutateCallback = onMutate;
56
69
  const userContext = onMutate ? await mutateCallback(variables) : undefined;
57
70
  return { previousData, userContext };
@@ -76,8 +89,38 @@ export function useMutation(options) {
76
89
  }
77
90
  };
78
91
  mutationConfig.onSuccess = (data, variables, context) => {
79
- const scope = optimistic.invalidateScope === "prefix" && optimistic.invalidatePrefixKey ? optimistic.invalidatePrefixKey : optimistic.queryKey;
80
- queryClient.invalidateQueries({ queryKey: scope });
92
+ const scope = optimistic.invalidateScope ?? (isListFamilyKey(optimistic.queryKey) ? "family" : "exact");
93
+ const invalidations = [];
94
+ if (scope !== "none") {
95
+ if (scope === "family") {
96
+ const familyKey = optimistic.familyKey ?? deriveFamilyKey(optimistic.queryKey);
97
+ invalidations.push({ queryKey: familyKey });
98
+ }
99
+ else {
100
+ invalidations.push({ queryKey: optimistic.queryKey });
101
+ }
102
+ }
103
+ if (Array.isArray(optimistic.relatedKeys) && optimistic.relatedKeys.length > 0) {
104
+ optimistic.relatedKeys.forEach((k) => invalidations.push({ queryKey: k }));
105
+ }
106
+ if (invalidations.length > 0) {
107
+ const seen = new Set();
108
+ const tasks = invalidations
109
+ .map((cfg) => ({ ...cfg, exact: false }))
110
+ .filter((cfg) => {
111
+ const key = JSON.stringify(cfg.queryKey);
112
+ if (seen.has(key))
113
+ return false;
114
+ seen.add(key);
115
+ return true;
116
+ });
117
+ if (tasks.length === 1) {
118
+ queryClient.invalidateQueries(tasks[0]);
119
+ }
120
+ else if (tasks.length > 1) {
121
+ invalidateQueriesBatch(queryClient, tasks);
122
+ }
123
+ }
81
124
  if (onSuccess) {
82
125
  const successCallback = onSuccess;
83
126
  successCallback(data, variables, context?.userContext);
@@ -97,7 +140,15 @@ export function setupMutationDefaults(queryClient, config) {
97
140
  }
98
141
  export function useListMutation(mutationFn, queryKey, options) {
99
142
  const queryClient = useQueryClient();
100
- return useTanStackMutation({ mutationFn, onSettled: () => { queryClient.invalidateQueries({ queryKey }); }, ...options, mutationKey: options?.mutationKey });
143
+ return useTanStackMutation({
144
+ mutationFn,
145
+ onSettled: () => {
146
+ const familyKey = deriveFamilyKey(queryKey);
147
+ queryClient.invalidateQueries({ queryKey: familyKey, exact: false });
148
+ },
149
+ ...options,
150
+ mutationKey: options?.mutationKey
151
+ });
101
152
  }
102
153
  export function useBatchMutation(mutationFn, options) {
103
154
  return useTanStackMutation({ mutationFn, ...options, mutationKey: options?.mutationKey });
@@ -115,7 +166,7 @@ export function useConditionalOptimisticMutation(mutationFn, condition, options)
115
166
  return { userContext, conditionMet: false };
116
167
  }
117
168
  try {
118
- await queryClient.cancelQueries({ queryKey: optimistic.queryKey });
169
+ await queryClient.cancelQueries({ queryKey: optimistic.queryKey, exact: true });
119
170
  const previousData = queryClient.getQueryData(optimistic.queryKey);
120
171
  queryClient.setQueryData(optimistic.queryKey, (oldData) => optimistic.updater(oldData, variables));
121
172
  const mutateCallback = onMutate;
@@ -149,7 +200,38 @@ export function useConditionalOptimisticMutation(mutationFn, condition, options)
149
200
  };
150
201
  mutationConfig.onSettled = (data, error, variables, context) => {
151
202
  if (context?.conditionMet) {
152
- queryClient.invalidateQueries({ queryKey: optimistic.queryKey });
203
+ const scope = optimistic.invalidateScope ?? (isListFamilyKey(optimistic.queryKey) ? "family" : "exact");
204
+ const invalidations = [];
205
+ if (scope !== "none") {
206
+ if (scope === "family") {
207
+ const familyKey = optimistic.familyKey ?? deriveFamilyKey(optimistic.queryKey);
208
+ invalidations.push({ queryKey: familyKey });
209
+ }
210
+ else {
211
+ invalidations.push({ queryKey: optimistic.queryKey });
212
+ }
213
+ }
214
+ if (Array.isArray(optimistic.relatedKeys) && optimistic.relatedKeys.length > 0) {
215
+ optimistic.relatedKeys.forEach((k) => invalidations.push({ queryKey: k }));
216
+ }
217
+ if (invalidations.length > 0) {
218
+ const seen = new Set();
219
+ const tasks = invalidations
220
+ .map((cfg) => ({ ...cfg, exact: false }))
221
+ .filter((cfg) => {
222
+ const key = JSON.stringify(cfg.queryKey);
223
+ if (seen.has(key))
224
+ return false;
225
+ seen.add(key);
226
+ return true;
227
+ });
228
+ if (tasks.length === 1) {
229
+ queryClient.invalidateQueries(tasks[0]);
230
+ }
231
+ else if (tasks.length > 1) {
232
+ invalidateQueriesBatch(queryClient, tasks);
233
+ }
234
+ }
153
235
  }
154
236
  if (onSettled) {
155
237
  const settledCallback = onSettled;
@@ -0,0 +1,13 @@
1
+ import type { QueryKey } from "@tanstack/react-query";
2
+ export type ConsistencyMode = "invalidate" | "sync+invalidate";
3
+ export interface FamilyConsistencyOptions {
4
+ baseKey: QueryKey;
5
+ mode?: ConsistencyMode;
6
+ idField?: string;
7
+ listSelector?: (data: unknown) => unknown[] | {
8
+ items: unknown[];
9
+ total?: number;
10
+ } | null;
11
+ maxKeys?: number;
12
+ }
13
+ //# sourceMappingURL=consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consistency.d.ts","sourceRoot":"","sources":["../../src/types/consistency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAC/D,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,QAAQ,CAAC;IAClB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,EAAE,GAAG;QAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -18,9 +18,9 @@ export interface MutationOptions<TData, TError, TVariables, TContext = unknown>
18
18
  enabled?: boolean;
19
19
  fieldMapping?: Record<string, string>;
20
20
  rollback?: <TQueryData = unknown>(previousData: TQueryData, error: Error) => void;
21
- invalidateScope?: "exact" | "prefix";
22
- invalidatePrefixKey?: QueryKey;
23
- reconcileCachedPages?: boolean;
21
+ invalidateScope?: "none" | "exact" | "family";
22
+ familyKey?: QueryKey;
23
+ relatedKeys?: QueryKey[];
24
24
  };
25
25
  }
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,MAAM,WAAW,eAAe,CAAC,KAAK,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO;IAAI,YAAY,CAAC,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,QAAQ,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE;AAC9I,MAAM,WAAW,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,GAAG,OAAO,CAAE,SAAQ,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC;IAC7I,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,SAAS,EAAE,SAAS,EAAE,UAAU,KAAK,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,GAAG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;QAAC,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;QAAC,mBAAmB,CAAC,EAAE,QAAQ,CAAC;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3Y"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,MAAM,WAAW,eAAe,CAAC,KAAK,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO;IAAI,YAAY,CAAC,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,QAAQ,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE;AAC9I,MAAM,WAAW,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,GAAG,OAAO,CAAE,SAAQ,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC;IAC7I,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,SAAS,EAAE,SAAS,EAAE,UAAU,KAAK,UAAU,GAAG,SAAS,CAAC;QAClH,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,QAAQ,CAAC,EAAE,CAAC,UAAU,GAAG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;QAClF,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;QAC9C,SAAS,CAAC,EAAE,QAAQ,CAAC;QACrB,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;KAC1B,CAAC;CACH"}
@@ -0,0 +1,14 @@
1
+ import type { QueryClient, QueryKey } from "@tanstack/react-query";
2
+ export interface FamilyQueryMatchOptions {
3
+ baseKey: QueryKey;
4
+ maxKeys?: number;
5
+ }
6
+ export declare function findFamilyQueries(queryClient: QueryClient, options: FamilyQueryMatchOptions): QueryKey[];
7
+ export declare function syncEntityAcrossFamily(queryClient: QueryClient, keys: QueryKey[], updatedEntity: unknown, options: {
8
+ idField?: string;
9
+ listSelector?: (data: unknown) => unknown[] | {
10
+ items: unknown[];
11
+ total?: number;
12
+ } | null;
13
+ }): void;
14
+ //# sourceMappingURL=consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consistency.d.ts","sourceRoot":"","sources":["../../src/utils/consistency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,uBAAuB,GAAG,QAAQ,EAAE,CAQxG;AAeD,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,EAAE,GAAG;QAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CA6BzO"}
@@ -0,0 +1,68 @@
1
+ function isObjectEqual(a, b) {
2
+ if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {
3
+ return JSON.stringify(a) === JSON.stringify(b);
4
+ }
5
+ return a === b;
6
+ }
7
+ function startsWithKey(key, prefix) {
8
+ if (prefix.length > key.length)
9
+ return false;
10
+ for (let i = 0; i < prefix.length; i++) {
11
+ if (!isObjectEqual(key[i], prefix[i]))
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+ export function findFamilyQueries(queryClient, options) {
17
+ const { baseKey, maxKeys } = options;
18
+ const queries = queryClient.getQueryCache().findAll({ predicate: (q) => startsWithKey(q.queryKey, baseKey) });
19
+ const keys = queries.map((q) => q.queryKey);
20
+ if (typeof maxKeys === "number" && maxKeys > 0) {
21
+ return keys.slice(0, maxKeys);
22
+ }
23
+ return keys;
24
+ }
25
+ function updateArrayById(items, updated, idField) {
26
+ const list = Array.isArray(items) ? items : [];
27
+ const id = updated[idField];
28
+ if (id === undefined)
29
+ return list;
30
+ return list.map((item) => {
31
+ const currentId = item[idField];
32
+ if (currentId === id) {
33
+ return { ...item, ...updated };
34
+ }
35
+ return item;
36
+ });
37
+ }
38
+ export function syncEntityAcrossFamily(queryClient, keys, updatedEntity, options) {
39
+ const idField = options.idField ?? "id";
40
+ keys.forEach((key) => {
41
+ const oldData = queryClient.getQueryData(key);
42
+ if (Array.isArray(oldData)) {
43
+ const next = updateArrayById(oldData, updatedEntity, idField);
44
+ queryClient.setQueryData(key, next);
45
+ return;
46
+ }
47
+ if (oldData && typeof oldData === "object") {
48
+ const selector = options.listSelector;
49
+ if (selector) {
50
+ const selected = selector(oldData);
51
+ if (selected === null)
52
+ return;
53
+ if (selected && Array.isArray(selected.items)) {
54
+ const nextItems = updateArrayById(selected.items, updatedEntity, idField);
55
+ const next = { ...oldData, items: nextItems };
56
+ queryClient.setQueryData(key, next);
57
+ return;
58
+ }
59
+ }
60
+ if (Object.prototype.hasOwnProperty.call(oldData, "items")) {
61
+ const items = oldData["items"];
62
+ const nextItems = updateArrayById(items, updatedEntity, idField);
63
+ const next = { ...oldData, items: nextItems };
64
+ queryClient.setQueryData(key, next);
65
+ }
66
+ }
67
+ });
68
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"optimisticUtils.d.ts","sourceRoot":"","sources":["../../src/utils/optimisticUtils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"optimisticUtils.d.ts","sourceRoot":"","sources":["../../src/utils/optimisticUtils.ts"],"names":[],"mappings":"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"}
@@ -45,7 +45,16 @@ export function createListOperationConfig(config) {
45
45
  return oldData || [];
46
46
  }
47
47
  },
48
- rollback: config.onRollback ? (previousData, error) => { config.onRollback(error, { previousData, timestamp: Date.now(), operationType: "update" }); } : undefined,
48
+ rollback: config.onRollback
49
+ ? (previousData, error) => {
50
+ const opType = config.operation === ListOperationType.ADD
51
+ ? "create"
52
+ : config.operation === ListOperationType.REMOVE
53
+ ? "delete"
54
+ : "update";
55
+ config.onRollback(error, { previousData, timestamp: Date.now(), operationType: opType });
56
+ }
57
+ : undefined,
49
58
  enabled: true
50
59
  };
51
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qiaopeng/tanstack-query-plus",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Enhanced TanStack Query toolkit: defaults, hooks, persistence, offline, utils",
5
5
  "author": "qiaopeng",
6
6
  "license": "MIT",