@qiaopeng/tanstack-query-plus 0.2.3 → 0.2.4
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 +66 -35
- package/dist/hooks/useMutation.d.ts.map +1 -1
- package/dist/hooks/useMutation.js +17 -10
- package/dist/types/consistency.d.ts +13 -0
- package/dist/types/consistency.d.ts.map +1 -0
- package/dist/types/consistency.js +1 -0
- package/dist/types/index.d.ts +11 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/utils/consistency.d.ts +14 -0
- package/dist/utils/consistency.d.ts.map +1 -0
- package/dist/utils/consistency.js +68 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
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
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
|
1063
|
-
|
|
1064
|
-
mutationFn: (
|
|
1058
|
+
function useUpdateProduct({ page, pageSize }) {
|
|
1059
|
+
return useMutation({
|
|
1060
|
+
mutationFn: (updated) => api.updateProduct(updated.id, updated),
|
|
1061
|
+
|
|
1062
|
+
// 当前页的乐观更新:先更新 UI,再发请求,失败自动回滚
|
|
1065
1063
|
optimistic: {
|
|
1066
|
-
queryKey:
|
|
1067
|
-
updater: (
|
|
1068
|
-
|
|
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
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
-
-
|
|
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
|
---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMutation.d.ts","sourceRoot":"","sources":["../../src/hooks/useMutation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,kBAAkB,IAAI,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACvK,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"useMutation.d.ts","sourceRoot":"","sources":["../../src/hooks/useMutation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findFamilyQueries, syncEntityAcrossFamily } from "../utils/consistency.js";
|
|
1
2
|
import { useQueryClient, useMutation as useTanStackMutation } from "@tanstack/react-query";
|
|
2
3
|
import { DEFAULT_MUTATION_CONFIG } from "../core/config.js";
|
|
3
4
|
export function useMutation(options) {
|
|
@@ -44,14 +45,7 @@ export function useMutation(options) {
|
|
|
44
45
|
}
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
|
-
|
|
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
|
-
}
|
|
48
|
+
queryClient.setQueryData(optimistic.queryKey, (oldData) => optimistic.updater(oldData, mappedVariables));
|
|
55
49
|
const mutateCallback = onMutate;
|
|
56
50
|
const userContext = onMutate ? await mutateCallback(variables) : undefined;
|
|
57
51
|
return { previousData, userContext };
|
|
@@ -76,8 +70,21 @@ export function useMutation(options) {
|
|
|
76
70
|
}
|
|
77
71
|
};
|
|
78
72
|
mutationConfig.onSuccess = (data, variables, context) => {
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
queryClient.invalidateQueries({ queryKey: optimistic.queryKey });
|
|
74
|
+
const consistency = options.consistency;
|
|
75
|
+
if (consistency && Array.isArray(consistency.baseKey) && consistency.baseKey.length > 0) {
|
|
76
|
+
try {
|
|
77
|
+
const keys = findFamilyQueries(queryClient, { baseKey: consistency.baseKey, maxKeys: consistency.maxKeys });
|
|
78
|
+
if (consistency.mode === "sync+invalidate") {
|
|
79
|
+
const updatedEntity = (typeof data === "object" && data !== null) ? data : (typeof variables === "object" && variables !== null ? variables : undefined);
|
|
80
|
+
if (updatedEntity) {
|
|
81
|
+
syncEntityAcrossFamily(queryClient, keys, updatedEntity, { idField: consistency.idField, listSelector: consistency.listSelector });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
keys.forEach((key) => { queryClient.invalidateQueries({ queryKey: key }); });
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
}
|
|
81
88
|
if (onSuccess) {
|
|
82
89
|
const successCallback = onSuccess;
|
|
83
90
|
successCallback(data, variables, context?.userContext);
|
|
@@ -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 {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./optimistic.js";
|
|
|
6
6
|
export * from "./persistence.js";
|
|
7
7
|
export * from "./selectors.js";
|
|
8
8
|
export * from "./suspense.js";
|
|
9
|
+
export * from "./consistency.js";
|
|
9
10
|
export interface MutationContext<TData = unknown, TContext = unknown> {
|
|
10
11
|
previousData?: TData;
|
|
11
12
|
userContext?: TContext;
|
|
@@ -18,9 +19,16 @@ export interface MutationOptions<TData, TError, TVariables, TContext = unknown>
|
|
|
18
19
|
enabled?: boolean;
|
|
19
20
|
fieldMapping?: Record<string, string>;
|
|
20
21
|
rollback?: <TQueryData = unknown>(previousData: TQueryData, error: Error) => void;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
};
|
|
23
|
+
consistency?: {
|
|
24
|
+
baseKey: QueryKey;
|
|
25
|
+
mode?: "invalidate" | "sync+invalidate";
|
|
26
|
+
idField?: string;
|
|
27
|
+
listSelector?: (data: unknown) => unknown[] | {
|
|
28
|
+
items: unknown[];
|
|
29
|
+
total?: number;
|
|
30
|
+
} | null;
|
|
31
|
+
maxKeys?: number;
|
|
24
32
|
};
|
|
25
33
|
}
|
|
26
34
|
//# 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;
|
|
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,cAAc,kBAAkB,CAAC;AACjC,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,CAAA;KAAE,CAAC;IACpS,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,YAAY,GAAG,iBAAiB,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,EAAE,GAAG;YAAE,KAAK,EAAE,OAAO,EAAE,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7M"}
|
package/dist/types/index.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export { getPrefetchManager, type InteractionRecord, type NetworkSpeed, type Pre
|
|
|
6
6
|
export { createQueryKeyFactory, createSimpleQueryKeyFactory, extractParamsFromKey, isQueryKeyEqual, type NormalizeConfig, normalizeQueryParams, type QueryKeyFactory, type QueryKeyFactoryConfig } from "./queryKey.js";
|
|
7
7
|
export { compose, selectById, selectByIds, selectCount, selectField, selectFields, selectFirst, selectItems, selectLast, selectMap, selectors, selectTotal, selectWhere } from "./selectors.js";
|
|
8
8
|
export { deepClone, formatBytes, getStorageUsage, isStorageAvailable } from "./storage.js";
|
|
9
|
+
export { findFamilyQueries, syncEntityAcrossFamily, type FamilyQueryMatchOptions } from "./consistency.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACpH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC3H,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC1P,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpO,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACxN,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChM,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACpH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC3H,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC1P,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpO,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACxN,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChM,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export { getPrefetchManager, resetPrefetchManager, SmartPrefetchManager } from "
|
|
|
6
6
|
export { createQueryKeyFactory, createSimpleQueryKeyFactory, extractParamsFromKey, isQueryKeyEqual, normalizeQueryParams } from "./queryKey.js";
|
|
7
7
|
export { compose, selectById, selectByIds, selectCount, selectField, selectFields, selectFirst, selectItems, selectLast, selectMap, selectors, selectTotal, selectWhere } from "./selectors.js";
|
|
8
8
|
export { deepClone, formatBytes, getStorageUsage, isStorageAvailable } from "./storage.js";
|
|
9
|
+
export { findFamilyQueries, syncEntityAcrossFamily } from "./consistency.js";
|