@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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # @robohall/react-query-factory
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@robohall/react-query-factory)](https://www.npmjs.com/package/@robohall/react-query-factory)
4
- [![bundle size](https://img.shields.io/bundlephobia/minzip/@robohall/react-query-factory)](https://bundlephobia.com/package/@robohall/react-query-factory)
4
+ ![minified](https://img.shields.io/badge/minified-%3C_12_kB-blue)
5
+ ![gzipped](https://img.shields.io/badge/gzipped-%3C_3_kB-blue)
5
6
  [![license](https://img.shields.io/npm/l/@robohall/react-query-factory)](./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', 'find', { MaxResults: 20 }, { instanceId: 'i-0abc123def456' }]
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
- Because `findInstance`'s key is nested under `['ec2:DescribeInstances']`, a single invalidation call busts the parent and all children:
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
- // after a runInstances/terminateInstances mutationinvalidates everything in the namespace.
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 base = Array.isArray(namespace) ? namespace : [namespace];
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 base = Array.isArray(namespace) ? namespace : [namespace];
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": "1.2.0",
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",