@robohall/react-query-factory 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -6
- package/dist/index.js +20 -5
- package/dist/index.mjs +20 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @robohall/react-query-factory
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@robohall/react-query-factory)
|
|
4
|
-
|
|
4
|
+

|
|
5
|
+

|
|
5
6
|
[](./LICENSE)
|
|
6
7
|
|
|
7
8
|
A factory function for TanStack Query configs. Instead of calling `useQuery` with ad-hoc options, you define a factory once and call it anywhere — getting consistent cache keys, automatic pagination crawling, and `useInfiniteQuery` support for free. TanStack's API stays fully exposed.
|
|
@@ -135,22 +136,41 @@ const findInstance = queryFactory(describeInstances, {
|
|
|
135
136
|
!instances.some(i => i.InstanceId === opts.instanceId),
|
|
136
137
|
});
|
|
137
138
|
|
|
138
|
-
// query key: ['ec2:DescribeInstances',
|
|
139
|
+
// query key: ['ec2:DescribeInstances', { MaxResults: 20 }, 'find', { instanceId: 'i-0abc123def456' }]
|
|
139
140
|
// crawls pages until the target instance appears, then stops
|
|
140
141
|
const { data } = useQuery(
|
|
141
142
|
findInstance({ MaxResults: 20 }, { instanceId: 'i-0abc123def456' })
|
|
142
143
|
);
|
|
143
144
|
```
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
**Invalidation — broad and scoped:**
|
|
147
|
+
|
|
148
|
+
Child keys follow the ordering `[...parentNS, params, ...childSegments]`, which means the parent key for a given set of params is always a strict prefix of every child key for those same params:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
describeInstances({ MaxResults: 20 })
|
|
152
|
+
→ ['ec2:DescribeInstances', { MaxResults: 20 }]
|
|
153
|
+
|
|
154
|
+
runningInstances({ MaxResults: 20 }) // select child, no own segments
|
|
155
|
+
→ ['ec2:DescribeInstances', { MaxResults: 20 }] (same entry — select is not in the key)
|
|
156
|
+
|
|
157
|
+
findInstance({ MaxResults: 20 }, { instanceId: 'i-abc' })
|
|
158
|
+
→ ['ec2:DescribeInstances', { MaxResults: 20 }, 'find', { instanceId: 'i-abc' }]
|
|
159
|
+
// └── params ──────┘ └── own segs ──────────────────┘
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This unlocks two invalidation granularities with no extra bookkeeping:
|
|
146
163
|
|
|
147
164
|
```typescript
|
|
148
|
-
//
|
|
149
|
-
// Calling the factory with no args produces just the namespace key; TanStack prefix-matches it
|
|
150
|
-
// against all entries, so describeInstances, runningInstances, and findInstance are all busted.
|
|
165
|
+
// Broad: zero-arg returns the namespace — busts every variant, every param set
|
|
151
166
|
await queryClient.invalidateQueries(describeInstances());
|
|
167
|
+
|
|
168
|
+
// Scoped: parent call with params — busts the parent and every child for those params only
|
|
169
|
+
await queryClient.invalidateQueries(describeInstances({ MaxResults: 20 }));
|
|
152
170
|
```
|
|
153
171
|
|
|
172
|
+
The scoped form is particularly useful after a targeted mutation: invalidate the one resource that changed without touching unrelated cache entries.
|
|
173
|
+
|
|
154
174
|
---
|
|
155
175
|
|
|
156
176
|
## Infinite queries
|
|
@@ -170,6 +190,8 @@ The `.infinite()` key includes an `'infinite'` segment to keep it separate from
|
|
|
170
190
|
- `describeInstances({ MaxResults: 20 })` → `['ec2:DescribeInstances', { MaxResults: 20 }]`
|
|
171
191
|
- `describeInstances.infinite({ MaxResults: 20 })` → `['ec2:DescribeInstances', 'infinite', { MaxResults: 20 }]`
|
|
172
192
|
|
|
193
|
+
Child factories place `params` before their own key segments so that the parent key is always a prefix of the child key for the same params — enabling per-call-site scoped invalidation.
|
|
194
|
+
|
|
173
195
|
---
|
|
174
196
|
|
|
175
197
|
## Public API
|
package/dist/index.js
CHANGED
|
@@ -29,8 +29,7 @@ var FACTORY_CONFIG = /* @__PURE__ */ Symbol("factoryConfig");
|
|
|
29
29
|
var getEnvelopeNextPageParam = (envelope) => envelope.nextPageParam;
|
|
30
30
|
var noNextPage = () => void 0;
|
|
31
31
|
function resolveKey(namespace, params, crawlOptions) {
|
|
32
|
-
const
|
|
33
|
-
const withParams = params === void 0 ? base : [...base, params];
|
|
32
|
+
const withParams = params === void 0 ? namespace : [...namespace, params];
|
|
34
33
|
if (!crawlOptions) return withParams;
|
|
35
34
|
let defined;
|
|
36
35
|
for (const key in crawlOptions) {
|
|
@@ -40,6 +39,19 @@ function resolveKey(namespace, params, crawlOptions) {
|
|
|
40
39
|
}
|
|
41
40
|
return defined ? [...withParams, defined] : withParams;
|
|
42
41
|
}
|
|
42
|
+
function buildChildKey(parentKey, ownSegments, params, crawlOptions, infinite) {
|
|
43
|
+
let defined;
|
|
44
|
+
for (const key in crawlOptions) {
|
|
45
|
+
if (crawlOptions[key] !== void 0) {
|
|
46
|
+
(defined != null ? defined : defined = {})[key] = crawlOptions[key];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (params === void 0 && !defined) return [...parentKey];
|
|
50
|
+
const withParams = params !== void 0 ? [...parentKey, params] : [...parentKey];
|
|
51
|
+
const withOwn = [...withParams, ...ownSegments];
|
|
52
|
+
const withInfinite = infinite ? [...withOwn, "infinite"] : withOwn;
|
|
53
|
+
return defined ? [...withInfinite, defined] : withInfinite;
|
|
54
|
+
}
|
|
43
55
|
function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions, select) {
|
|
44
56
|
return (lastPage, allPages, lastPageParam, allPageParams) => {
|
|
45
57
|
const combined = select ? select(lastPage) : lastPage;
|
|
@@ -106,6 +118,7 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
106
118
|
function buildFactory(cfg) {
|
|
107
119
|
const {
|
|
108
120
|
queryKey: namespace,
|
|
121
|
+
parentKey,
|
|
109
122
|
queryFn: rawQueryFn,
|
|
110
123
|
select,
|
|
111
124
|
getNextPageParam,
|
|
@@ -115,6 +128,8 @@ function buildFactory(cfg) {
|
|
|
115
128
|
reduce,
|
|
116
129
|
standardOptions
|
|
117
130
|
} = cfg;
|
|
131
|
+
const ownSegments = parentKey !== void 0 ? namespace.slice(parentKey.length) : namespace;
|
|
132
|
+
const infiniteNamespace = [...namespace, "infinite"];
|
|
118
133
|
const hasCrawling = rawQueryFn !== void 0 && getNextPageParam !== void 0 && shouldFetchNextPage !== void 0;
|
|
119
134
|
const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
|
|
120
135
|
const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
|
|
@@ -130,7 +145,6 @@ function buildFactory(cfg) {
|
|
|
130
145
|
shouldFetchNextPage,
|
|
131
146
|
reduce
|
|
132
147
|
) : void 0;
|
|
133
|
-
const infiniteNamespace = [...resolveKey(namespace, void 0), "infinite"];
|
|
134
148
|
const envelopeSelect = infiniteCrawlingFn ? (data) => ({
|
|
135
149
|
...data,
|
|
136
150
|
pages: data.pages.map((e) => select ? select(e.data) : e.data)
|
|
@@ -140,7 +154,7 @@ function buildFactory(cfg) {
|
|
|
140
154
|
pages: data.pages.map(select)
|
|
141
155
|
}) : void 0;
|
|
142
156
|
const factory = function(params, crawlOptions = {}) {
|
|
143
|
-
const queryKey = resolveKey(namespace, params, crawlOptions);
|
|
157
|
+
const queryKey = parentKey !== void 0 ? buildChildKey(parentKey, ownSegments, params, crawlOptions) : resolveKey(namespace, params, crawlOptions);
|
|
144
158
|
const resolvedQueryFn = crawlingFn ? (ctx) => crawlingFn(params, crawlOptions, ctx) : rawQueryFn ? (ctx) => rawQueryFn(params, ctx) : void 0;
|
|
145
159
|
return {
|
|
146
160
|
...standardOptions,
|
|
@@ -151,7 +165,7 @@ function buildFactory(cfg) {
|
|
|
151
165
|
};
|
|
152
166
|
};
|
|
153
167
|
factory.infinite = function(params, crawlOptions = {}) {
|
|
154
|
-
const queryKey = resolveKey(infiniteNamespace, params, crawlOptions);
|
|
168
|
+
const queryKey = parentKey !== void 0 ? buildChildKey(parentKey, ownSegments, params, crawlOptions, true) : resolveKey(infiniteNamespace, params, crawlOptions);
|
|
155
169
|
if (infiniteCrawlingFn) {
|
|
156
170
|
return {
|
|
157
171
|
...standardOptions,
|
|
@@ -236,6 +250,7 @@ function queryFactory(configOrParent, childConfig) {
|
|
|
236
250
|
} = childConfig;
|
|
237
251
|
return buildFactory({
|
|
238
252
|
queryKey: composedNamespace,
|
|
253
|
+
parentKey: parentCfg.queryKey,
|
|
239
254
|
queryFn: hasNewQueryFn ? childConfig.queryFn : parentCfg.queryFn,
|
|
240
255
|
select: resolvedSelect,
|
|
241
256
|
...crawling,
|
package/dist/index.mjs
CHANGED
|
@@ -3,8 +3,7 @@ var FACTORY_CONFIG = /* @__PURE__ */ Symbol("factoryConfig");
|
|
|
3
3
|
var getEnvelopeNextPageParam = (envelope) => envelope.nextPageParam;
|
|
4
4
|
var noNextPage = () => void 0;
|
|
5
5
|
function resolveKey(namespace, params, crawlOptions) {
|
|
6
|
-
const
|
|
7
|
-
const withParams = params === void 0 ? base : [...base, params];
|
|
6
|
+
const withParams = params === void 0 ? namespace : [...namespace, params];
|
|
8
7
|
if (!crawlOptions) return withParams;
|
|
9
8
|
let defined;
|
|
10
9
|
for (const key in crawlOptions) {
|
|
@@ -14,6 +13,19 @@ function resolveKey(namespace, params, crawlOptions) {
|
|
|
14
13
|
}
|
|
15
14
|
return defined ? [...withParams, defined] : withParams;
|
|
16
15
|
}
|
|
16
|
+
function buildChildKey(parentKey, ownSegments, params, crawlOptions, infinite) {
|
|
17
|
+
let defined;
|
|
18
|
+
for (const key in crawlOptions) {
|
|
19
|
+
if (crawlOptions[key] !== void 0) {
|
|
20
|
+
(defined != null ? defined : defined = {})[key] = crawlOptions[key];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (params === void 0 && !defined) return [...parentKey];
|
|
24
|
+
const withParams = params !== void 0 ? [...parentKey, params] : [...parentKey];
|
|
25
|
+
const withOwn = [...withParams, ...ownSegments];
|
|
26
|
+
const withInfinite = infinite ? [...withOwn, "infinite"] : withOwn;
|
|
27
|
+
return defined ? [...withInfinite, defined] : withInfinite;
|
|
28
|
+
}
|
|
17
29
|
function wrapGetNextPageParam(getNextPageParam, shouldFetchNextPage, crawlOptions, select) {
|
|
18
30
|
return (lastPage, allPages, lastPageParam, allPageParams) => {
|
|
19
31
|
const combined = select ? select(lastPage) : lastPage;
|
|
@@ -80,6 +92,7 @@ function buildInfiniteCrawlingQueryFn(queryFn, getNextPageParam, shouldFetchNext
|
|
|
80
92
|
function buildFactory(cfg) {
|
|
81
93
|
const {
|
|
82
94
|
queryKey: namespace,
|
|
95
|
+
parentKey,
|
|
83
96
|
queryFn: rawQueryFn,
|
|
84
97
|
select,
|
|
85
98
|
getNextPageParam,
|
|
@@ -89,6 +102,8 @@ function buildFactory(cfg) {
|
|
|
89
102
|
reduce,
|
|
90
103
|
standardOptions
|
|
91
104
|
} = cfg;
|
|
105
|
+
const ownSegments = parentKey !== void 0 ? namespace.slice(parentKey.length) : namespace;
|
|
106
|
+
const infiniteNamespace = [...namespace, "infinite"];
|
|
92
107
|
const hasCrawling = rawQueryFn !== void 0 && getNextPageParam !== void 0 && shouldFetchNextPage !== void 0;
|
|
93
108
|
const hasInfiniteCrawling = hasCrawling && reduce !== void 0;
|
|
94
109
|
const crawlingFn = hasCrawling ? buildCrawlingQueryFn(
|
|
@@ -104,7 +119,6 @@ function buildFactory(cfg) {
|
|
|
104
119
|
shouldFetchNextPage,
|
|
105
120
|
reduce
|
|
106
121
|
) : void 0;
|
|
107
|
-
const infiniteNamespace = [...resolveKey(namespace, void 0), "infinite"];
|
|
108
122
|
const envelopeSelect = infiniteCrawlingFn ? (data) => ({
|
|
109
123
|
...data,
|
|
110
124
|
pages: data.pages.map((e) => select ? select(e.data) : e.data)
|
|
@@ -114,7 +128,7 @@ function buildFactory(cfg) {
|
|
|
114
128
|
pages: data.pages.map(select)
|
|
115
129
|
}) : void 0;
|
|
116
130
|
const factory = function(params, crawlOptions = {}) {
|
|
117
|
-
const queryKey = resolveKey(namespace, params, crawlOptions);
|
|
131
|
+
const queryKey = parentKey !== void 0 ? buildChildKey(parentKey, ownSegments, params, crawlOptions) : resolveKey(namespace, params, crawlOptions);
|
|
118
132
|
const resolvedQueryFn = crawlingFn ? (ctx) => crawlingFn(params, crawlOptions, ctx) : rawQueryFn ? (ctx) => rawQueryFn(params, ctx) : void 0;
|
|
119
133
|
return {
|
|
120
134
|
...standardOptions,
|
|
@@ -125,7 +139,7 @@ function buildFactory(cfg) {
|
|
|
125
139
|
};
|
|
126
140
|
};
|
|
127
141
|
factory.infinite = function(params, crawlOptions = {}) {
|
|
128
|
-
const queryKey = resolveKey(infiniteNamespace, params, crawlOptions);
|
|
142
|
+
const queryKey = parentKey !== void 0 ? buildChildKey(parentKey, ownSegments, params, crawlOptions, true) : resolveKey(infiniteNamespace, params, crawlOptions);
|
|
129
143
|
if (infiniteCrawlingFn) {
|
|
130
144
|
return {
|
|
131
145
|
...standardOptions,
|
|
@@ -210,6 +224,7 @@ function queryFactory(configOrParent, childConfig) {
|
|
|
210
224
|
} = childConfig;
|
|
211
225
|
return buildFactory({
|
|
212
226
|
queryKey: composedNamespace,
|
|
227
|
+
parentKey: parentCfg.queryKey,
|
|
213
228
|
queryFn: hasNewQueryFn ? childConfig.queryFn : parentCfg.queryFn,
|
|
214
229
|
select: resolvedSelect,
|
|
215
230
|
...crawling,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robohall/react-query-factory",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A factory abstraction for TanStack Query (React Query) with composable keys, crawling support, and automatic infinite query generation",
|
|
5
5
|
"author": "Robert Hall",
|
|
6
6
|
"license": "MIT",
|