@signalium/query 1.0.4 → 1.0.6

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/MemoryEvictionManager.js +56 -0
  3. package/dist/cjs/MemoryEvictionManager.js.map +1 -0
  4. package/dist/cjs/NetworkManager.js +21 -1
  5. package/dist/cjs/NetworkManager.js.map +1 -1
  6. package/dist/cjs/QueryClient.js +12 -600
  7. package/dist/cjs/QueryClient.js.map +1 -1
  8. package/dist/cjs/QueryResult.js +491 -0
  9. package/dist/cjs/QueryResult.js.map +1 -0
  10. package/dist/cjs/QueryStore.js +2 -6
  11. package/dist/cjs/QueryStore.js.map +1 -1
  12. package/dist/cjs/RefetchManager.js +88 -0
  13. package/dist/cjs/RefetchManager.js.map +1 -0
  14. package/dist/cjs/errors.js +13 -12
  15. package/dist/cjs/errors.js.map +1 -1
  16. package/dist/cjs/index.js +8 -11
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/parseEntities.js +1 -1
  19. package/dist/cjs/parseEntities.js.map +1 -1
  20. package/dist/cjs/proxy.js +2 -2
  21. package/dist/cjs/proxy.js.map +1 -1
  22. package/dist/cjs/query.js +60 -19
  23. package/dist/cjs/query.js.map +1 -1
  24. package/dist/cjs/stores/async.js +290 -2
  25. package/dist/cjs/stores/async.js.map +1 -1
  26. package/dist/cjs/stores/shared.js +19 -0
  27. package/dist/cjs/stores/shared.js.map +1 -0
  28. package/dist/cjs/stores/sync.js +201 -4
  29. package/dist/cjs/stores/sync.js.map +1 -1
  30. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  31. package/dist/cjs/typeDefs.js +221 -116
  32. package/dist/cjs/typeDefs.js.map +1 -1
  33. package/dist/cjs/utils.js +0 -42
  34. package/dist/cjs/utils.js.map +1 -1
  35. package/dist/esm/MemoryEvictionManager.d.ts +19 -0
  36. package/dist/esm/MemoryEvictionManager.d.ts.map +1 -0
  37. package/dist/esm/MemoryEvictionManager.js +51 -0
  38. package/dist/esm/MemoryEvictionManager.js.map +1 -0
  39. package/dist/esm/NetworkManager.d.ts +8 -0
  40. package/dist/esm/NetworkManager.d.ts.map +1 -1
  41. package/dist/esm/NetworkManager.js +19 -0
  42. package/dist/esm/NetworkManager.js.map +1 -1
  43. package/dist/esm/QueryClient.d.ts +33 -86
  44. package/dist/esm/QueryClient.d.ts.map +1 -1
  45. package/dist/esm/QueryClient.js +11 -599
  46. package/dist/esm/QueryClient.js.map +1 -1
  47. package/dist/esm/QueryResult.d.ts +66 -0
  48. package/dist/esm/QueryResult.d.ts.map +1 -0
  49. package/dist/esm/QueryResult.js +487 -0
  50. package/dist/esm/QueryResult.js.map +1 -0
  51. package/dist/esm/QueryStore.d.ts.map +1 -1
  52. package/dist/esm/QueryStore.js +2 -6
  53. package/dist/esm/QueryStore.js.map +1 -1
  54. package/dist/esm/RefetchManager.d.ts +18 -0
  55. package/dist/esm/RefetchManager.d.ts.map +1 -0
  56. package/dist/esm/RefetchManager.js +83 -0
  57. package/dist/esm/RefetchManager.js.map +1 -0
  58. package/dist/esm/errors.d.ts.map +1 -1
  59. package/dist/esm/errors.js +13 -12
  60. package/dist/esm/errors.js.map +1 -1
  61. package/dist/esm/index.d.ts +4 -5
  62. package/dist/esm/index.d.ts.map +1 -1
  63. package/dist/esm/index.js +4 -3
  64. package/dist/esm/index.js.map +1 -1
  65. package/dist/esm/parseEntities.js +2 -2
  66. package/dist/esm/parseEntities.js.map +1 -1
  67. package/dist/esm/proxy.js +3 -3
  68. package/dist/esm/proxy.js.map +1 -1
  69. package/dist/esm/query.d.ts +2 -0
  70. package/dist/esm/query.d.ts.map +1 -1
  71. package/dist/esm/query.js +59 -20
  72. package/dist/esm/query.js.map +1 -1
  73. package/dist/esm/stores/async.d.ts +63 -1
  74. package/dist/esm/stores/async.d.ts.map +1 -1
  75. package/dist/esm/stores/async.js +289 -1
  76. package/dist/esm/stores/async.js.map +1 -1
  77. package/dist/esm/stores/shared.d.ts +8 -0
  78. package/dist/esm/stores/shared.d.ts.map +1 -0
  79. package/dist/esm/stores/shared.js +11 -0
  80. package/dist/esm/stores/shared.js.map +1 -0
  81. package/dist/esm/stores/sync.d.ts +37 -1
  82. package/dist/esm/stores/sync.d.ts.map +1 -1
  83. package/dist/esm/stores/sync.js +198 -1
  84. package/dist/esm/stores/sync.js.map +1 -1
  85. package/dist/esm/typeDefs.d.ts +25 -8
  86. package/dist/esm/typeDefs.d.ts.map +1 -1
  87. package/dist/esm/typeDefs.js +220 -116
  88. package/dist/esm/typeDefs.js.map +1 -1
  89. package/dist/esm/types.d.ts +2 -1
  90. package/dist/esm/types.d.ts.map +1 -1
  91. package/dist/esm/utils.d.ts +1 -4
  92. package/dist/esm/utils.d.ts.map +1 -1
  93. package/dist/esm/utils.js +0 -40
  94. package/dist/esm/utils.js.map +1 -1
  95. package/package.json +3 -3
@@ -12,607 +12,18 @@
12
12
  * - Self-contained validator (no external dependencies except Signalium)
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.QueryClientContext = exports.QueryClient = exports.QueryResultImpl = void 0;
15
+ exports.QueryClientContext = exports.QueryClient = exports.queryKeyFor = void 0;
16
16
  const signalium_1 = require("signalium");
17
17
  const utils_1 = require("signalium/utils");
18
- const types_js_1 = require("./types.js");
19
- const proxy_js_1 = require("./proxy.js");
20
- const parseEntities_js_1 = require("./parseEntities.js");
21
18
  const EntityMap_js_1 = require("./EntityMap.js");
22
- const typeDefs_js_1 = require("./typeDefs.js");
23
19
  const NetworkManager_js_1 = require("./NetworkManager.js");
20
+ const QueryResult_js_1 = require("./QueryResult.js");
21
+ const RefetchManager_js_1 = require("./RefetchManager.js");
22
+ const MemoryEvictionManager_js_1 = require("./MemoryEvictionManager.js");
24
23
  const queryKeyFor = (queryDef, params) => {
25
- return (0, utils_1.hashValue)([queryDef.id, params]);
24
+ return (0, utils_1.hashValue)([queryDef.id, queryDef.shapeKey, params]);
26
25
  };
27
- const BASE_TICK_INTERVAL = 1000; // 1 second
28
- // Refetch interval manager - uses a fixed 1-second tick
29
- class RefetchManager {
30
- multiplier;
31
- intervalId;
32
- clock = 0; // Increments by 1000ms on each tick
33
- // Buckets: Map of actual interval -> Set of query instances
34
- buckets = new Map();
35
- constructor(multiplier = 1) {
36
- this.multiplier = multiplier;
37
- // Start the timer immediately and keep it running
38
- const tickInterval = BASE_TICK_INTERVAL * this.multiplier;
39
- this.intervalId = setTimeout(() => this.tick(), tickInterval);
40
- }
41
- addQuery(instance) {
42
- if (instance.def.type === "stream" /* QueryType.Stream */) {
43
- return; // Streams don't have refetch intervals
44
- }
45
- const interval = instance.def.cache?.refetchInterval;
46
- if (!interval) {
47
- return;
48
- }
49
- const actualInterval = interval * this.multiplier;
50
- // Add to bucket by actual interval
51
- let bucket = this.buckets.get(actualInterval);
52
- if (!bucket) {
53
- bucket = new Set();
54
- this.buckets.set(actualInterval, bucket);
55
- }
56
- bucket.add(instance);
57
- }
58
- removeQuery(query) {
59
- if (query.def.type === "stream" /* QueryType.Stream */) {
60
- return; // Streams don't have refetch intervals
61
- }
62
- const interval = query.def.cache?.refetchInterval;
63
- if (!interval) {
64
- return;
65
- }
66
- const actualInterval = interval * this.multiplier;
67
- // Remove from bucket
68
- const bucket = this.buckets.get(actualInterval);
69
- if (bucket) {
70
- bucket.delete(query);
71
- if (bucket.size === 0) {
72
- this.buckets.delete(actualInterval);
73
- }
74
- }
75
- }
76
- tick() {
77
- this.clock += BASE_TICK_INTERVAL * this.multiplier;
78
- // Only process buckets where clock is aligned with the interval
79
- for (const [interval, bucket] of this.buckets.entries()) {
80
- if (this.clock % interval === 0) {
81
- // Process all queries in this bucket
82
- for (const query of bucket) {
83
- // Skip if already fetching - let the current fetch complete
84
- if (query && !query.isFetching) {
85
- query.refetch();
86
- }
87
- }
88
- }
89
- }
90
- const tickInterval = BASE_TICK_INTERVAL * this.multiplier;
91
- this.intervalId = setTimeout(() => this.tick(), tickInterval);
92
- }
93
- destroy() {
94
- clearTimeout(this.intervalId);
95
- }
96
- }
97
- const EVICTION_INTERVAL = 60 * 1000; // 1 minute
98
- // Memory eviction manager - uses a single interval with rotating sets to avoid timeout overhead
99
- class MemoryEvictionManager {
100
- queryClient;
101
- multiplier;
102
- intervalId;
103
- currentFlush = new Set(); // Queries to evict on next tick
104
- nextFlush = new Set(); // Queries to evict on tick after next
105
- constructor(queryClient, multiplier = 1) {
106
- this.queryClient = queryClient;
107
- this.multiplier = multiplier;
108
- this.intervalId = setInterval(this.tick, EVICTION_INTERVAL * this.multiplier);
109
- }
110
- scheduleEviction(queryKey) {
111
- // Add to nextFlush so it waits at least one full interval
112
- // This prevents immediate eviction if scheduled right before a tick
113
- this.nextFlush.add(queryKey);
114
- }
115
- cancelEviction(queryKey) {
116
- // Remove from both sets to handle reactivation
117
- this.currentFlush.delete(queryKey);
118
- this.nextFlush.delete(queryKey);
119
- }
120
- tick = () => {
121
- if (!this.queryClient)
122
- return;
123
- // Evict all queries in currentFlush
124
- for (const queryKey of this.currentFlush) {
125
- this.queryClient.queryInstances.delete(queryKey);
126
- }
127
- // Rotate: currentFlush becomes nextFlush, nextFlush becomes empty
128
- this.currentFlush = this.nextFlush;
129
- this.nextFlush = new Set();
130
- };
131
- destroy() {
132
- clearInterval(this.intervalId);
133
- }
134
- }
135
- /**
136
- * QueryResult wraps a DiscriminatedReactivePromise and adds additional functionality
137
- * like refetch, while forwarding all the base relay properties.
138
- * This class combines the old QueryInstance and QueryResultImpl into a single entity.
139
- */
140
- class QueryResultImpl {
141
- def;
142
- queryKey;
143
- queryClient;
144
- initialized = false;
145
- isRefetchingSignal = (0, signalium_1.signal)(false);
146
- isFetchingMoreSignal = (0, signalium_1.signal)(false);
147
- updatedAt = undefined;
148
- params = undefined;
149
- refIds = undefined;
150
- refetchPromise = undefined;
151
- fetchMorePromise = undefined;
152
- attemptCount = 0;
153
- unsubscribe = undefined;
154
- relay;
155
- _relayState = undefined;
156
- wasOffline = false;
157
- get relayState() {
158
- const relayState = this._relayState;
159
- if (!relayState) {
160
- throw new Error('Relay state not initialized');
161
- }
162
- return relayState;
163
- }
164
- _nextPageParams = undefined;
165
- get nextPageParams() {
166
- // Streams don't have pagination
167
- if (this.def.type === "stream" /* QueryType.Stream */) {
168
- return null;
169
- }
170
- let params = this._nextPageParams;
171
- const value = this.relayState.value;
172
- if (params === undefined && value !== undefined) {
173
- if (!Array.isArray(value)) {
174
- throw new Error('Query result is not an array, this is a bug');
175
- }
176
- const infiniteDef = this.def;
177
- const nextParams = infiniteDef.pagination?.getNextPageParams?.(value[value.length - 1]);
178
- if (nextParams === undefined) {
179
- // store null to indicate that there is no next page, but we've already calculated
180
- params = null;
181
- }
182
- else {
183
- // Clone current params
184
- let hasDefinedParams = false;
185
- const clonedParams = { ...this.params };
186
- // iterate over the next page params and copy any defined values to the
187
- for (const [key, value] of Object.entries(nextParams)) {
188
- if (value !== undefined && value !== null) {
189
- clonedParams[key] = value;
190
- hasDefinedParams = true;
191
- }
192
- }
193
- this._nextPageParams = params = hasDefinedParams ? clonedParams : null;
194
- }
195
- }
196
- return params ?? null;
197
- }
198
- constructor(def, queryClient, queryKey, params) {
199
- (0, utils_1.setReactivePromise)(this);
200
- this.def = def;
201
- this.queryClient = queryClient;
202
- this.queryKey = queryKey;
203
- this.params = params;
204
- // Create the relay and handle activation/deactivation
205
- this.relay = (0, signalium_1.relay)(state => {
206
- this._relayState = state;
207
- // Load from cache first, then fetch fresh data
208
- this.queryClient.activateQuery(this);
209
- // Track network status for reconnect handling
210
- const networkManager = this.queryClient.networkManager;
211
- const isOnline = networkManager.getOnlineSignal().value;
212
- // Store initial offline state
213
- this.wasOffline = !isOnline;
214
- if (this.initialized) {
215
- if (this.def.type === "stream" /* QueryType.Stream */) {
216
- this.setupSubscription();
217
- }
218
- else {
219
- // Check if we just came back online
220
- if (!this.wasOffline && isOnline) {
221
- // We're back online - check if we should refresh
222
- const refreshStaleOnReconnect = this.def.cache?.refreshStaleOnReconnect ?? true;
223
- if (refreshStaleOnReconnect && this.isStale) {
224
- this.refetch();
225
- }
226
- // Reset attempt count on reconnect
227
- this.attemptCount = 0;
228
- }
229
- else if (this.isStale && !this.isPaused) {
230
- this.refetch();
231
- }
232
- }
233
- // Update wasOffline for next check
234
- this.wasOffline = !isOnline;
235
- }
236
- else {
237
- this.initialize();
238
- }
239
- // Return deactivation callback
240
- return {
241
- update: () => {
242
- // For streams, unsubscribe and resubscribe to re-establish connection
243
- if (this.def.type === "stream" /* QueryType.Stream */) {
244
- this.setupSubscription();
245
- return;
246
- }
247
- // Network status changed - check if we should react
248
- const currentlyOnline = networkManager.getOnlineSignal().value;
249
- // If we just came back online
250
- if (this.wasOffline && currentlyOnline) {
251
- const refreshStaleOnReconnect = this.def.cache?.refreshStaleOnReconnect ?? true;
252
- if (refreshStaleOnReconnect && this.isStale) {
253
- state.setPromise(this.runQuery(this.params, true));
254
- }
255
- // Reset attempt count on reconnect
256
- this.attemptCount = 0;
257
- }
258
- // Update wasOffline for next check
259
- this.wasOffline = !currentlyOnline;
260
- },
261
- deactivate: () => {
262
- // Last subscriber left, deactivate refetch and schedule memory eviction
263
- if (this.def.type === "stream" /* QueryType.Stream */) {
264
- // Unsubscribe from stream
265
- if (this.unsubscribe) {
266
- this.unsubscribe();
267
- this.unsubscribe = undefined;
268
- }
269
- }
270
- else if (this.def.cache?.refetchInterval) {
271
- this.queryClient.refetchManager.removeQuery(this);
272
- }
273
- // Schedule removal from memory using the global eviction manager
274
- // This allows quick reactivation from memory if needed again soon
275
- // Disk cache (if configured) will still be available after eviction
276
- this.queryClient.memoryEvictionManager.scheduleEviction(this.queryKey);
277
- },
278
- };
279
- });
280
- }
281
- // ======================================================
282
- // ReactivePromise properties
283
- // =====================================================
284
- get value() {
285
- return this.relay.value;
286
- }
287
- get error() {
288
- return this.relay.error;
289
- }
290
- get isPending() {
291
- return this.relay.isPending;
292
- }
293
- get isRejected() {
294
- return this.relay.isRejected;
295
- }
296
- get isResolved() {
297
- return this.relay.isResolved;
298
- }
299
- get isSettled() {
300
- return this.relay.isSettled;
301
- }
302
- get isReady() {
303
- return this.relay.isReady;
304
- }
305
- // TODO: Intimate APIs needed for `useReactive`, this is a code smell and
306
- // we should find a better way to entangle these more generically
307
- get _version() {
308
- return this.relay._version;
309
- }
310
- get _signal() {
311
- return this.relay._signal;
312
- }
313
- get _flags() {
314
- return this.relay._flags;
315
- }
316
- // Forward Promise methods
317
- then(onfulfilled, onrejected) {
318
- return this.relay.then(onfulfilled, onrejected);
319
- }
320
- catch(onrejected) {
321
- return this.relay.catch(onrejected);
322
- }
323
- finally(onfinally) {
324
- return this.relay.finally(onfinally);
325
- }
326
- get [Symbol.toStringTag]() {
327
- return 'QueryResult';
328
- }
329
- // ======================================================
330
- // Internal fetch methods
331
- // ======================================================
332
- /**
333
- * Initialize the query by loading from cache and fetching if stale
334
- */
335
- async initialize() {
336
- const state = this.relayState;
337
- try {
338
- this.initialized = true;
339
- // Load from cache first
340
- const cached = await this.queryClient.loadCachedQuery(this.def, this.queryKey);
341
- if (cached !== undefined) {
342
- const shape = this.def.shape;
343
- state.value =
344
- shape instanceof typeDefs_js_1.ValidatorDef
345
- ? (0, parseEntities_js_1.parseEntities)(cached.value, shape, this.queryClient, new Set())
346
- : (0, proxy_js_1.parseValue)(cached.value, shape, this.def.id);
347
- // Set the cached timestamp
348
- this.updatedAt = cached.updatedAt;
349
- // Set the cached reference IDs
350
- this.refIds = cached.refIds;
351
- }
352
- if (this.def.type === "stream" /* QueryType.Stream */) {
353
- this.setupSubscription();
354
- }
355
- else {
356
- if (cached !== undefined) {
357
- // Check if data is stale
358
- if (this.isStale) {
359
- // Data is stale, trigger background refetch
360
- this.refetch();
361
- }
362
- }
363
- else {
364
- // No cached data, fetch fresh
365
- state.setPromise(this.runQuery(this.params, true));
366
- }
367
- }
368
- }
369
- catch (error) {
370
- // Relay will handle the error state automatically
371
- state.setError(error);
372
- }
373
- }
374
- /**
375
- * Handle stream updates by merging with existing entity.
376
- * Deep merging is handled automatically by parseEntities/setEntity.
377
- */
378
- setupSubscription() {
379
- this.unsubscribe?.();
380
- const streamDef = this.def;
381
- this.unsubscribe = streamDef.subscribeFn(this.queryClient.getContext(), this.params, update => {
382
- const shapeDef = this.def.shape;
383
- const entityRefs = this.refIds ?? new Set();
384
- const parsedData = (0, parseEntities_js_1.parseEntities)(update, shapeDef, this.queryClient, entityRefs);
385
- // Update the relay state
386
- this.relayState.value = parsedData;
387
- this.updatedAt = Date.now();
388
- this.refIds = entityRefs;
389
- // Cache the data
390
- this.queryClient.saveQueryData(this.def, this.queryKey, parsedData, this.updatedAt, entityRefs);
391
- });
392
- }
393
- /**
394
- * Fetches fresh data, updates the cache, and updates updatedAt timestamp
395
- */
396
- async runQuery(params, reset = false) {
397
- // Check if paused before attempting fetch
398
- if (this.isPaused) {
399
- throw new Error('Query is paused due to network status');
400
- }
401
- const { retries, retryDelay } = this.getRetryConfig();
402
- let lastError;
403
- // Attempt fetch with retries
404
- for (let attempt = 0; attempt <= retries; attempt++) {
405
- try {
406
- const queryDef = this.def;
407
- const freshData = await queryDef.fetchFn(this.queryClient.getContext(), params);
408
- // Success! Reset attempt count
409
- this.attemptCount = 0;
410
- // Parse and cache the fresh data
411
- let entityRefs;
412
- const isInfinite = this.def.type === "infiniteQuery" /* QueryType.InfiniteQuery */;
413
- if (isInfinite && !reset && this.refIds !== undefined) {
414
- entityRefs = this.refIds;
415
- }
416
- else {
417
- entityRefs = new Set();
418
- }
419
- const shape = this.def.shape;
420
- const parsedData = shape instanceof typeDefs_js_1.ValidatorDef
421
- ? (0, parseEntities_js_1.parseEntities)(freshData, shape, this.queryClient, entityRefs)
422
- : (0, proxy_js_1.parseValue)(freshData, shape, this.def.id);
423
- let queryData;
424
- if (isInfinite) {
425
- const prevQueryData = this.relayState.value;
426
- queryData = reset || prevQueryData === undefined ? [parsedData] : [...prevQueryData, parsedData];
427
- }
428
- else {
429
- queryData = parsedData;
430
- }
431
- let updatedAt;
432
- if (reset) {
433
- updatedAt = this.updatedAt = Date.now();
434
- }
435
- else {
436
- updatedAt = this.updatedAt ??= Date.now();
437
- }
438
- this._nextPageParams = undefined;
439
- // Cache the data (synchronous, fire-and-forget)
440
- this.queryClient.saveQueryData(this.def, this.queryKey, queryData, updatedAt, entityRefs);
441
- // Update the timestamp
442
- this.updatedAt = Date.now();
443
- return queryData;
444
- }
445
- catch (error) {
446
- lastError = error;
447
- this.attemptCount = attempt + 1;
448
- // If we've exhausted retries, throw the error
449
- if (attempt >= retries) {
450
- throw error;
451
- }
452
- // Wait before retrying (unless paused)
453
- const delay = retryDelay(attempt);
454
- await new Promise(resolve => setTimeout(resolve, delay));
455
- // Check if paused during retry delay
456
- if (this.isPaused) {
457
- throw new Error('Query is paused due to network status');
458
- }
459
- }
460
- }
461
- // Should never reach here, but TypeScript needs it
462
- throw lastError;
463
- }
464
- // ======================================================
465
- // Public methods
466
- // ======================================================
467
- refetch() {
468
- if (this.def.type === "stream" /* QueryType.Stream */) {
469
- throw new Error('Cannot refetch a stream query');
470
- }
471
- if (this.fetchMorePromise) {
472
- throw new Error('Query is fetching more, cannot refetch');
473
- }
474
- if (this.refetchPromise) {
475
- return this.refetchPromise;
476
- }
477
- // Clear memoized nextPageParams so it's recalculated after refetch
478
- this._nextPageParams = undefined;
479
- // Set the signal before any async operations so it's immediately visible
480
- // Use untrack to avoid reactive violations when called from reactive context
481
- this.isRefetchingSignal.value = true;
482
- this._version.update(v => v + 1);
483
- const promise = this.runQuery(this.params, true)
484
- .then(result => {
485
- this.relayState.value = result;
486
- return result;
487
- })
488
- .catch((error) => {
489
- this.relayState.setError(error);
490
- return Promise.reject(error);
491
- })
492
- .finally(() => {
493
- this._version.update(v => v + 1);
494
- this.isRefetchingSignal.value = false;
495
- this.refetchPromise = undefined;
496
- });
497
- this.refetchPromise = promise;
498
- return promise;
499
- }
500
- fetchNextPage() {
501
- if (this.def.type === "stream" /* QueryType.Stream */) {
502
- throw new Error('Cannot fetch next page on a stream query');
503
- }
504
- if (this.refetchPromise) {
505
- return Promise.reject(new Error('Query is refetching, cannot fetch next page'));
506
- }
507
- if (this.fetchMorePromise) {
508
- return this.fetchMorePromise;
509
- }
510
- // Read nextPageParams in untracked context to avoid reactive violations
511
- const nextPageParams = this.nextPageParams;
512
- if (!nextPageParams) {
513
- return Promise.reject(new Error('No next page params'));
514
- }
515
- // Set the signal before any async operations so it's immediately visible
516
- // Use untrack to avoid reactive violations when called from reactive context
517
- this.isFetchingMoreSignal.value = true;
518
- this._version.update(v => v + 1);
519
- const promise = this.runQuery(nextPageParams, false)
520
- .then(result => {
521
- this.relayState.value = result;
522
- return result;
523
- })
524
- .catch((error) => {
525
- this.relayState.setError(error);
526
- return Promise.reject(error);
527
- })
528
- .finally(() => {
529
- this._version.update(v => v + 1);
530
- this.isFetchingMoreSignal.value = false;
531
- this.fetchMorePromise = undefined;
532
- });
533
- this.fetchMorePromise = promise;
534
- return promise;
535
- }
536
- // ======================================================
537
- // Public properties
538
- // ======================================================
539
- get isRefetching() {
540
- return this.isRefetchingSignal.value;
541
- }
542
- get isFetchingMore() {
543
- return this.isFetchingMoreSignal.value;
544
- }
545
- get isFetching() {
546
- return this.relay.isPending || this.isRefetching || this.isFetchingMore;
547
- }
548
- get hasNextPage() {
549
- return this.nextPageParams !== null;
550
- }
551
- get isStale() {
552
- // Streams are never stale - they're always receiving updates
553
- if (this.def.type === "stream" /* QueryType.Stream */) {
554
- return false;
555
- }
556
- if (this.updatedAt === undefined) {
557
- return true; // No data yet, needs fetch
558
- }
559
- const staleTime = this.def.cache?.staleTime ?? 0;
560
- return Date.now() - this.updatedAt >= staleTime;
561
- }
562
- get isPaused() {
563
- // Streams handle their own connection state
564
- if (this.def.type === "stream" /* QueryType.Stream */) {
565
- return false;
566
- }
567
- const networkMode = this.def.cache?.networkMode ?? types_js_1.NetworkMode.Online;
568
- const networkManager = this.queryClient.networkManager;
569
- // Read the online signal to make this reactive
570
- const isOnline = networkManager.getOnlineSignal().value;
571
- switch (networkMode) {
572
- case types_js_1.NetworkMode.Always:
573
- return false;
574
- case types_js_1.NetworkMode.Online:
575
- return !isOnline;
576
- case types_js_1.NetworkMode.OfflineFirst:
577
- // Only paused if we have no cached data AND we're offline
578
- return !isOnline && this.updatedAt === undefined;
579
- default:
580
- return false;
581
- }
582
- }
583
- getRetryConfig() {
584
- // Streams don't have retry config
585
- if (this.def.type === "stream" /* QueryType.Stream */) {
586
- return { retries: 0, retryDelay: () => 0 };
587
- }
588
- const retryOption = this.def.cache?.retry;
589
- const isServer = this.queryClient.isServer;
590
- // Default retry count: 3 on client, 0 on server
591
- let retries;
592
- let retryDelay;
593
- if (retryOption === false) {
594
- retries = 0;
595
- }
596
- else if (retryOption === undefined) {
597
- retries = isServer ? 0 : 3;
598
- }
599
- else if (typeof retryOption === 'number') {
600
- retries = retryOption;
601
- }
602
- else {
603
- retries = retryOption.retries;
604
- }
605
- // Default exponential backoff: 1000ms * 2^attempt
606
- if (typeof retryOption === 'object' && retryOption.retryDelay) {
607
- retryDelay = retryOption.retryDelay;
608
- }
609
- else {
610
- retryDelay = (attempt) => 1000 * Math.pow(2, attempt);
611
- }
612
- return { retries, retryDelay };
613
- }
614
- }
615
- exports.QueryResultImpl = QueryResultImpl;
26
+ exports.queryKeyFor = queryKeyFor;
616
27
  class QueryClient {
617
28
  store;
618
29
  context;
@@ -622,11 +33,12 @@ class QueryClient {
622
33
  refetchManager;
623
34
  networkManager;
624
35
  isServer;
625
- constructor(store, context = { fetch }, networkManager) {
36
+ constructor(store, context = { fetch }, networkManager, memoryEvictionManager, refetchManager) {
626
37
  this.store = store;
627
38
  this.context = context;
628
- this.memoryEvictionManager = new MemoryEvictionManager(this, this.context.evictionMultiplier);
629
- this.refetchManager = new RefetchManager(this.context.refetchMultiplier);
39
+ this.memoryEvictionManager =
40
+ memoryEvictionManager ?? new MemoryEvictionManager_js_1.MemoryEvictionManager(this, this.context.evictionMultiplier);
41
+ this.refetchManager = refetchManager ?? new RefetchManager_js_1.RefetchManager(this.context.refetchMultiplier);
630
42
  this.networkManager = networkManager ?? new NetworkManager_js_1.NetworkManager();
631
43
  this.isServer = typeof window === 'undefined';
632
44
  }
@@ -654,11 +66,11 @@ class QueryClient {
654
66
  * that triggers fetches and prepopulates with cached data
655
67
  */
656
68
  getQuery(queryDef, params) {
657
- const queryKey = queryKeyFor(queryDef, params);
69
+ const queryKey = (0, exports.queryKeyFor)(queryDef, params);
658
70
  let queryInstance = this.queryInstances.get(queryKey);
659
71
  // Create a new instance if it doesn't exist
660
72
  if (queryInstance === undefined) {
661
- queryInstance = new QueryResultImpl(queryDef, this, queryKey, params);
73
+ queryInstance = new QueryResult_js_1.QueryResultImpl(queryDef, this, queryKey, params);
662
74
  // Store for future use
663
75
  this.queryInstances.set(queryKey, queryInstance);
664
76
  }