@signalium/query 1.0.13 → 1.0.14

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 (151) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/development/index.js +2985 -0
  3. package/dist/cjs/development/index.js.map +1 -0
  4. package/dist/cjs/development/react/index.js +52 -0
  5. package/dist/cjs/development/react/index.js.map +1 -0
  6. package/dist/cjs/{stores/shared.js → development/shared-5acOO-tS.js} +11 -14
  7. package/dist/cjs/development/shared-5acOO-tS.js.map +1 -0
  8. package/dist/cjs/development/stores/async.js +304 -0
  9. package/dist/cjs/development/stores/async.js.map +1 -0
  10. package/dist/cjs/development/stores/sync.js +214 -0
  11. package/dist/cjs/development/stores/sync.js.map +1 -0
  12. package/dist/cjs/production/index.js +2985 -0
  13. package/dist/cjs/production/index.js.map +1 -0
  14. package/dist/cjs/production/package.json +3 -0
  15. package/dist/cjs/production/react/index.js +52 -0
  16. package/dist/cjs/production/react/index.js.map +1 -0
  17. package/dist/cjs/production/shared-5acOO-tS.js +20 -0
  18. package/dist/cjs/production/shared-5acOO-tS.js.map +1 -0
  19. package/dist/cjs/production/stores/async.js +304 -0
  20. package/dist/cjs/production/stores/async.js.map +1 -0
  21. package/dist/cjs/production/stores/sync.js +214 -0
  22. package/dist/cjs/production/stores/sync.js.map +1 -0
  23. package/dist/esm/EntityMap.d.ts +22 -0
  24. package/dist/esm/EntityMap.d.ts.map +1 -1
  25. package/dist/esm/MutationResult.d.ts +43 -0
  26. package/dist/esm/MutationResult.d.ts.map +1 -0
  27. package/dist/esm/QueryClient.d.ts +31 -2
  28. package/dist/esm/QueryClient.d.ts.map +1 -1
  29. package/dist/esm/development/index.js +2985 -0
  30. package/dist/esm/development/index.js.map +1 -0
  31. package/dist/esm/development/react/index.js +52 -0
  32. package/dist/esm/development/react/index.js.map +1 -0
  33. package/dist/esm/development/shared-Br5plsKm.js +21 -0
  34. package/dist/esm/development/shared-Br5plsKm.js.map +1 -0
  35. package/dist/esm/development/stores/async.js +304 -0
  36. package/dist/esm/development/stores/async.js.map +1 -0
  37. package/dist/esm/development/stores/sync.js +214 -0
  38. package/dist/esm/development/stores/sync.js.map +1 -0
  39. package/dist/esm/index.d.ts +4 -0
  40. package/dist/esm/index.d.ts.map +1 -1
  41. package/dist/esm/mutation.d.ts +82 -0
  42. package/dist/esm/mutation.d.ts.map +1 -0
  43. package/dist/esm/parseEntities.d.ts.map +1 -1
  44. package/dist/esm/production/index.js +2985 -0
  45. package/dist/esm/production/index.js.map +1 -0
  46. package/dist/esm/production/react/index.js +52 -0
  47. package/dist/esm/production/react/index.js.map +1 -0
  48. package/dist/esm/production/shared-Br5plsKm.js +21 -0
  49. package/dist/esm/production/shared-Br5plsKm.js.map +1 -0
  50. package/dist/esm/production/stores/async.js +304 -0
  51. package/dist/esm/production/stores/async.js.map +1 -0
  52. package/dist/esm/production/stores/sync.js +214 -0
  53. package/dist/esm/production/stores/sync.js.map +1 -0
  54. package/dist/esm/proxy.d.ts +6 -5
  55. package/dist/esm/proxy.d.ts.map +1 -1
  56. package/dist/esm/query.d.ts +3 -1
  57. package/dist/esm/query.d.ts.map +1 -1
  58. package/dist/esm/typeDefs.d.ts +4 -2
  59. package/dist/esm/typeDefs.d.ts.map +1 -1
  60. package/dist/esm/types.d.ts +71 -4
  61. package/dist/esm/types.d.ts.map +1 -1
  62. package/dist/esm/utils.d.ts +35 -0
  63. package/dist/esm/utils.d.ts.map +1 -1
  64. package/package.json +55 -21
  65. package/stores/async.js +1 -1
  66. package/stores/sync.js +1 -1
  67. package/dist/cjs/EntityMap.js +0 -103
  68. package/dist/cjs/EntityMap.js.map +0 -1
  69. package/dist/cjs/MemoryEvictionManager.js +0 -56
  70. package/dist/cjs/MemoryEvictionManager.js.map +0 -1
  71. package/dist/cjs/NetworkManager.js +0 -125
  72. package/dist/cjs/NetworkManager.js.map +0 -1
  73. package/dist/cjs/QueryClient.js +0 -162
  74. package/dist/cjs/QueryClient.js.map +0 -1
  75. package/dist/cjs/QueryResult.js +0 -920
  76. package/dist/cjs/QueryResult.js.map +0 -1
  77. package/dist/cjs/RefetchManager.js +0 -88
  78. package/dist/cjs/RefetchManager.js.map +0 -1
  79. package/dist/cjs/errors.js +0 -129
  80. package/dist/cjs/errors.js.map +0 -1
  81. package/dist/cjs/index.js +0 -43
  82. package/dist/cjs/index.js.map +0 -1
  83. package/dist/cjs/parseEntities.js +0 -142
  84. package/dist/cjs/parseEntities.js.map +0 -1
  85. package/dist/cjs/pathInterpolator.js +0 -69
  86. package/dist/cjs/pathInterpolator.js.map +0 -1
  87. package/dist/cjs/proxy.js +0 -263
  88. package/dist/cjs/proxy.js.map +0 -1
  89. package/dist/cjs/query.js +0 -184
  90. package/dist/cjs/query.js.map +0 -1
  91. package/dist/cjs/react/index.js +0 -6
  92. package/dist/cjs/react/index.js.map +0 -1
  93. package/dist/cjs/react/use-query.js +0 -56
  94. package/dist/cjs/react/use-query.js.map +0 -1
  95. package/dist/cjs/stores/async.js +0 -344
  96. package/dist/cjs/stores/async.js.map +0 -1
  97. package/dist/cjs/stores/shared.js.map +0 -1
  98. package/dist/cjs/stores/sync.js +0 -240
  99. package/dist/cjs/stores/sync.js.map +0 -1
  100. package/dist/cjs/tsconfig.cjs.tsbuildinfo +0 -1
  101. package/dist/cjs/type-utils.js +0 -3
  102. package/dist/cjs/type-utils.js.map +0 -1
  103. package/dist/cjs/typeDefs.js +0 -620
  104. package/dist/cjs/typeDefs.js.map +0 -1
  105. package/dist/cjs/types.js +0 -33
  106. package/dist/cjs/types.js.map +0 -1
  107. package/dist/cjs/utils.js +0 -23
  108. package/dist/cjs/utils.js.map +0 -1
  109. package/dist/esm/EntityMap.js +0 -99
  110. package/dist/esm/EntityMap.js.map +0 -1
  111. package/dist/esm/MemoryEvictionManager.js +0 -51
  112. package/dist/esm/MemoryEvictionManager.js.map +0 -1
  113. package/dist/esm/NetworkManager.js +0 -120
  114. package/dist/esm/NetworkManager.js.map +0 -1
  115. package/dist/esm/QueryClient.js +0 -154
  116. package/dist/esm/QueryClient.js.map +0 -1
  117. package/dist/esm/QueryResult.js +0 -916
  118. package/dist/esm/QueryResult.js.map +0 -1
  119. package/dist/esm/RefetchManager.js +0 -83
  120. package/dist/esm/RefetchManager.js.map +0 -1
  121. package/dist/esm/errors.js +0 -125
  122. package/dist/esm/errors.js.map +0 -1
  123. package/dist/esm/index.js +0 -8
  124. package/dist/esm/index.js.map +0 -1
  125. package/dist/esm/parseEntities.js +0 -135
  126. package/dist/esm/parseEntities.js.map +0 -1
  127. package/dist/esm/pathInterpolator.js +0 -66
  128. package/dist/esm/pathInterpolator.js.map +0 -1
  129. package/dist/esm/proxy.js +0 -254
  130. package/dist/esm/proxy.js.map +0 -1
  131. package/dist/esm/query.js +0 -177
  132. package/dist/esm/query.js.map +0 -1
  133. package/dist/esm/react/index.js +0 -2
  134. package/dist/esm/react/index.js.map +0 -1
  135. package/dist/esm/react/use-query.js +0 -53
  136. package/dist/esm/react/use-query.js.map +0 -1
  137. package/dist/esm/stores/async.js +0 -340
  138. package/dist/esm/stores/async.js.map +0 -1
  139. package/dist/esm/stores/shared.js +0 -13
  140. package/dist/esm/stores/shared.js.map +0 -1
  141. package/dist/esm/stores/sync.js +0 -234
  142. package/dist/esm/stores/sync.js.map +0 -1
  143. package/dist/esm/type-utils.js +0 -2
  144. package/dist/esm/type-utils.js.map +0 -1
  145. package/dist/esm/typeDefs.js +0 -606
  146. package/dist/esm/typeDefs.js.map +0 -1
  147. package/dist/esm/types.js +0 -30
  148. package/dist/esm/types.js.map +0 -1
  149. package/dist/esm/utils.js +0 -20
  150. package/dist/esm/utils.js.map +0 -1
  151. /package/dist/cjs/{package.json → development/package.json} +0 -0
@@ -1,916 +0,0 @@
1
- import { relay, signal, reactiveSignal, notifier, } from 'signalium';
2
- import { setReactivePromise } from 'signalium/utils';
3
- import { NetworkMode } from './types.js';
4
- import { getProxyId, parseValue } from './proxy.js';
5
- import { parseEntities, parseObjectEntities } from './parseEntities.js';
6
- import { ValidatorDef } from './typeDefs.js';
7
- import { extractParamsForKey, queryKeyFor, } from './QueryClient.js';
8
- // ======================================================
9
- // QueryResultExtra - Manages stream orphans and optimistic inserts
10
- // ======================================================
11
- /**
12
- * Manages extra data for a query result: stream orphans and optimistic inserts.
13
- * Created lazily when first needed.
14
- */
15
- class QueryResultExtra {
16
- _streamOrphansNotifier = undefined;
17
- _streamOrphans = undefined;
18
- _optimisticInsertsNotifier = undefined;
19
- _optimisticInserts = undefined;
20
- onChanged;
21
- constructor(onChanged) {
22
- this.onChanged = onChanged;
23
- }
24
- get streamOrphansNotifier() {
25
- return this._streamOrphansNotifier ?? (this._streamOrphansNotifier = notifier());
26
- }
27
- get optimisticInsertsNotifier() {
28
- return this._optimisticInsertsNotifier ?? (this._optimisticInsertsNotifier = notifier());
29
- }
30
- get streamOrphans() {
31
- return this._streamOrphans ?? (this._streamOrphans = new Set());
32
- }
33
- get optimisticInserts() {
34
- return this._optimisticInserts ?? (this._optimisticInserts = new Set());
35
- }
36
- /**
37
- * Returns the QueryExtra object for public API consumption.
38
- * Consumes the notifiers to establish reactive tracking.
39
- */
40
- getExtra() {
41
- this.streamOrphansNotifier.consume();
42
- this.optimisticInsertsNotifier.consume();
43
- return {
44
- streamOrphans: this.streamOrphans,
45
- optimisticInserts: this.optimisticInserts,
46
- };
47
- }
48
- /**
49
- * Add a stream orphan entity.
50
- * Returns true if the orphan was added (not a duplicate).
51
- */
52
- addStreamOrphan(entity) {
53
- const orphans = this.streamOrphans;
54
- const sizeBefore = orphans.size;
55
- orphans.add(entity);
56
- if (orphans.size !== sizeBefore) {
57
- this.streamOrphansNotifier.notify();
58
- // Check if this orphan was an optimistic insert - if so, remove it
59
- const proxyId = getProxyId(entity);
60
- if (proxyId !== undefined) {
61
- this.removeOptimisticInsertById(proxyId);
62
- }
63
- this.onChanged();
64
- return true;
65
- }
66
- return false;
67
- }
68
- /**
69
- * Add an optimistic insert entity.
70
- * Returns true if the insert was added (not a duplicate).
71
- */
72
- addOptimisticInsert(entity) {
73
- const inserts = this.optimisticInserts;
74
- const sizeBefore = inserts.size;
75
- inserts.add(entity);
76
- if (inserts.size !== sizeBefore) {
77
- this.optimisticInsertsNotifier.notify();
78
- this.onChanged();
79
- return true;
80
- }
81
- return false;
82
- }
83
- /**
84
- * Remove an optimistic insert by its entity.
85
- * Returns true if the insert was removed.
86
- */
87
- removeOptimisticInsert(entity) {
88
- const proxyId = getProxyId(entity);
89
- if (proxyId === undefined) {
90
- return false;
91
- }
92
- return this.removeOptimisticInsertById(proxyId);
93
- }
94
- /**
95
- * Remove an optimistic insert by proxy ID.
96
- */
97
- removeOptimisticInsertById(proxyId) {
98
- const inserts = this._optimisticInserts;
99
- if (inserts === undefined || inserts.size === 0) {
100
- return false;
101
- }
102
- for (const existing of inserts) {
103
- if (getProxyId(existing) === proxyId) {
104
- inserts.delete(existing);
105
- this.optimisticInsertsNotifier.notify();
106
- this.onChanged();
107
- return true;
108
- }
109
- }
110
- return false;
111
- }
112
- /**
113
- * Check if a proxy ID exists in stream orphans.
114
- */
115
- hasOrphanWithId(proxyId) {
116
- const orphans = this._streamOrphans;
117
- if (orphans === undefined) {
118
- return false;
119
- }
120
- for (const orphan of orphans) {
121
- if (getProxyId(orphan) === proxyId) {
122
- return true;
123
- }
124
- }
125
- return false;
126
- }
127
- /**
128
- * Reconcile orphans and optimistic inserts against the main response entity refs.
129
- * Removes any that now exist in the main response.
130
- */
131
- reconcile(allRefIds) {
132
- // Check stream orphans for entities that now exist in main response
133
- const orphans = this._streamOrphans;
134
- if (orphans !== undefined && orphans.size > 0) {
135
- let orphansChanged = false;
136
- for (const orphan of orphans) {
137
- const entityRefId = getProxyId(orphan);
138
- if (entityRefId !== undefined && allRefIds.has(entityRefId)) {
139
- orphans.delete(orphan);
140
- orphansChanged = true;
141
- }
142
- }
143
- if (orphansChanged) {
144
- this.streamOrphansNotifier.notify();
145
- }
146
- }
147
- // Check optimistic inserts for entities that now exist in main response or stream orphans
148
- const inserts = this._optimisticInserts;
149
- if (inserts !== undefined && inserts.size > 0) {
150
- let insertsChanged = false;
151
- for (const insert of inserts) {
152
- const entityRefId = getProxyId(insert);
153
- if (entityRefId !== undefined) {
154
- // Remove if entity is now in main response
155
- if (allRefIds.has(entityRefId)) {
156
- inserts.delete(insert);
157
- insertsChanged = true;
158
- }
159
- // Also remove if entity is now in stream orphans
160
- else if (orphans !== undefined && orphans.has(insert)) {
161
- inserts.delete(insert);
162
- insertsChanged = true;
163
- }
164
- }
165
- }
166
- if (insertsChanged) {
167
- this.optimisticInsertsNotifier.notify();
168
- }
169
- }
170
- }
171
- /**
172
- * Clear all stream orphans and optimistic inserts.
173
- * Called on refetch.
174
- */
175
- clear() {
176
- let changed = false;
177
- if (this._streamOrphans !== undefined && this._streamOrphans.size > 0) {
178
- this._streamOrphans = undefined;
179
- this.streamOrphansNotifier.notify();
180
- changed = true;
181
- }
182
- if (this._optimisticInserts !== undefined && this._optimisticInserts.size > 0) {
183
- this._optimisticInserts = undefined;
184
- this.optimisticInsertsNotifier.notify();
185
- changed = true;
186
- }
187
- if (changed) {
188
- this.onChanged();
189
- }
190
- }
191
- /**
192
- * Load extra data from cached values.
193
- */
194
- loadFromCache(cachedExtra, queryClient, streamShape, optimisticInsertsShape) {
195
- if (cachedExtra.streamOrphanRefs && cachedExtra.streamOrphanRefs.length > 0 && streamShape) {
196
- const orphans = this.streamOrphans;
197
- for (const refId of cachedExtra.streamOrphanRefs) {
198
- const entityRecord = queryClient.hydrateEntity(refId, streamShape);
199
- orphans.add(entityRecord.proxy);
200
- }
201
- }
202
- if (cachedExtra.optimisticInsertRefs && cachedExtra.optimisticInsertRefs.length > 0 && optimisticInsertsShape) {
203
- const inserts = this.optimisticInserts;
204
- for (const refId of cachedExtra.optimisticInsertRefs) {
205
- const entityRecord = queryClient.hydrateEntity(refId, optimisticInsertsShape);
206
- inserts.add(entityRecord.proxy);
207
- }
208
- }
209
- }
210
- /**
211
- * Get extra data for persistence (converts Sets to arrays of entity ref IDs).
212
- */
213
- getForPersistence() {
214
- const orphans = this._streamOrphans;
215
- const inserts = this._optimisticInserts;
216
- if ((orphans === undefined || orphans.size === 0) && (inserts === undefined || inserts.size === 0)) {
217
- return undefined;
218
- }
219
- const extra = {};
220
- if (orphans !== undefined && orphans.size > 0) {
221
- extra.streamOrphanRefs = [];
222
- for (const orphan of orphans) {
223
- const refId = getProxyId(orphan);
224
- if (refId !== undefined) {
225
- extra.streamOrphanRefs.push(refId);
226
- }
227
- }
228
- }
229
- if (inserts !== undefined && inserts.size > 0) {
230
- extra.optimisticInsertRefs = [];
231
- for (const insert of inserts) {
232
- const refId = getProxyId(insert);
233
- if (refId !== undefined) {
234
- extra.optimisticInsertRefs.push(refId);
235
- }
236
- }
237
- }
238
- return extra;
239
- }
240
- /**
241
- * Check if there's any extra data.
242
- */
243
- get hasData() {
244
- return ((this._streamOrphans !== undefined && this._streamOrphans.size > 0) ||
245
- (this._optimisticInserts !== undefined && this._optimisticInserts.size > 0));
246
- }
247
- }
248
- // ======================================================
249
- // QueryResultImpl
250
- // ======================================================
251
- /**
252
- * QueryResult wraps a DiscriminatedReactivePromise and adds additional functionality
253
- * like refetch, while forwarding all the base relay properties.
254
- * This class combines the old QueryInstance and QueryResultImpl into a single entity.
255
- */
256
- export class QueryResultImpl {
257
- def;
258
- queryKey; // Instance key (includes Signal identity)
259
- storageKey = -1; // Storage key (extracted values only)
260
- queryClient;
261
- initialized = false;
262
- isRefetchingSignal = signal(false);
263
- isFetchingMoreSignal = signal(false);
264
- updatedAt = undefined;
265
- params = undefined;
266
- refIds = undefined;
267
- allNestedRefIdsSignal = undefined;
268
- refetchPromise = undefined;
269
- fetchMorePromise = undefined;
270
- unsubscribe = undefined;
271
- relay;
272
- _relayState = undefined;
273
- wasPaused = false;
274
- currentParams = undefined;
275
- debounceTimer = undefined;
276
- get relayState() {
277
- const relayState = this._relayState;
278
- if (!relayState) {
279
- throw new Error('Relay state not initialized');
280
- }
281
- return relayState;
282
- }
283
- _extra = undefined;
284
- get extraData() {
285
- return this._extra ?? (this._extra = new QueryResultExtra(() => this.persistExtraData()));
286
- }
287
- _nextPageParams = undefined;
288
- get nextPageParams() {
289
- // Streams and non-infinite queries don't have pagination
290
- if (this.def.type !== "infiniteQuery" /* QueryType.InfiniteQuery */) {
291
- return null;
292
- }
293
- let params = this._nextPageParams;
294
- const value = this.relayState.value;
295
- if (params === undefined && value !== undefined) {
296
- if (!Array.isArray(value)) {
297
- throw new Error('Query result is not an array, this is a bug');
298
- }
299
- const infiniteDef = this.def;
300
- const nextParams = infiniteDef.pagination?.getNextPageParams?.(value[value.length - 1]);
301
- if (nextParams === undefined) {
302
- // store null to indicate that there is no next page, but we've already calculated
303
- params = null;
304
- }
305
- else {
306
- // Clone current params
307
- let hasDefinedParams = false;
308
- const clonedParams = { ...this.currentParams };
309
- // iterate over the next page params and copy any defined values to the
310
- for (const [key, value] of Object.entries(nextParams)) {
311
- if (value !== undefined && value !== null) {
312
- clonedParams[key] = value;
313
- hasDefinedParams = true;
314
- }
315
- }
316
- this._nextPageParams = params = hasDefinedParams ? clonedParams : null;
317
- }
318
- }
319
- return params ?? null;
320
- }
321
- constructor(def, queryClient, queryKey, params) {
322
- setReactivePromise(this);
323
- this.def = def;
324
- this.queryClient = queryClient;
325
- this.queryKey = queryKey; // Instance key (Signal identity)
326
- this.params = params;
327
- // Create the relay and handle activation/deactivation
328
- this.relay = relay(state => {
329
- this._relayState = state;
330
- // Extract params (reading Signal values establishes tracking)
331
- this.currentParams = extractParamsForKey(this.params);
332
- this.storageKey = queryKeyFor(this.def, this.currentParams);
333
- // Load from cache first, then fetch fresh data
334
- this.queryClient.activateQuery(this);
335
- // Store initial offline state
336
- const isPaused = this.isPaused;
337
- this.wasPaused = isPaused;
338
- if (this.initialized) {
339
- if (!isPaused) {
340
- // For any query with streams, resubscribe on reactivation
341
- if (this.def.type === "stream" /* QueryType.Stream */ ||
342
- this.def.stream) {
343
- this.setupSubscription();
344
- }
345
- if (this.def.type !== "stream" /* QueryType.Stream */ && this.isStale) {
346
- this.refetch();
347
- }
348
- }
349
- }
350
- else {
351
- this.initialize();
352
- }
353
- const deactivate = () => {
354
- // Clear debounce timer if active
355
- clearTimeout(this.debounceTimer);
356
- this.debounceTimer = undefined;
357
- // Last subscriber left, deactivate refetch and schedule memory eviction
358
- // Unsubscribe from any active streams
359
- this.unsubscribe?.();
360
- this.unsubscribe = undefined;
361
- // Remove from refetch manager if configured
362
- if (this.def.type !== "stream" /* QueryType.Stream */ && this.def.cache?.refetchInterval) {
363
- this.queryClient.refetchManager.removeQuery(this);
364
- }
365
- // Schedule removal from memory using the global eviction manager
366
- // This allows quick reactivation from memory if needed again soon
367
- // Disk cache (if configured) will still be available after eviction
368
- // Use queryKey for instance eviction, storageKey for cache eviction
369
- this.queryClient.memoryEvictionManager.scheduleEviction(this.queryKey);
370
- };
371
- // Return deactivation callback
372
- return {
373
- update: () => {
374
- const { wasPaused, isPaused } = this;
375
- this.wasPaused = isPaused;
376
- if (isPaused) {
377
- deactivate();
378
- // TODO: Add abort signal
379
- return;
380
- }
381
- // Read Signal values again to establish tracking for any new Signals
382
- // Extract params (reading Signal values establishes tracking)
383
- const newExtractedParams = extractParamsForKey(this.params);
384
- const newStorageKey = queryKeyFor(this.def, newExtractedParams);
385
- const paramsDidChange = newStorageKey !== this.storageKey;
386
- // Check if storage key changed (comparing hash values)
387
- if (paramsDidChange) {
388
- // Same storage key, just Signal instances changed but values are the same
389
- // Update params and trigger debounced refetch
390
- this.params = newExtractedParams;
391
- this.storageKey = newStorageKey;
392
- }
393
- if (wasPaused) {
394
- this.queryClient.activateQuery(this);
395
- if (this.def.type !== "stream" /* QueryType.Stream */) {
396
- const refreshStaleOnReconnect = this.def.cache?.refreshStaleOnReconnect ?? true;
397
- if (refreshStaleOnReconnect && this.isStale) {
398
- state.setPromise(this.runQuery(this.currentParams, true));
399
- }
400
- }
401
- else {
402
- this.setupSubscription();
403
- }
404
- }
405
- else if (paramsDidChange) {
406
- if (this.def.type !== "stream" /* QueryType.Stream */) {
407
- this.debouncedRefetch();
408
- }
409
- else {
410
- this.setupSubscription();
411
- }
412
- }
413
- },
414
- deactivate,
415
- };
416
- });
417
- }
418
- // ======================================================
419
- // ReactivePromise properties
420
- // =====================================================
421
- get value() {
422
- return this.relay.value;
423
- }
424
- get error() {
425
- return this.relay.error;
426
- }
427
- get isPending() {
428
- return this.relay.isPending;
429
- }
430
- get isRejected() {
431
- return this.relay.isRejected;
432
- }
433
- get isResolved() {
434
- return this.relay.isResolved;
435
- }
436
- get isSettled() {
437
- return this.relay.isSettled;
438
- }
439
- get isReady() {
440
- return this.relay.isReady;
441
- }
442
- // TODO: Intimate APIs needed for `useReactive`, this is a code smell and
443
- // we should find a better way to entangle these more generically
444
- get _version() {
445
- return this.relay._version;
446
- }
447
- get _signal() {
448
- return this.relay._signal;
449
- }
450
- get _flags() {
451
- return this.relay._flags;
452
- }
453
- // Forward Promise methods
454
- then(onfulfilled, onrejected) {
455
- return this.relay.then(onfulfilled, onrejected);
456
- }
457
- catch(onrejected) {
458
- return this.relay.catch(onrejected);
459
- }
460
- finally(onfinally) {
461
- return this.relay.finally(onfinally);
462
- }
463
- get [Symbol.toStringTag]() {
464
- return 'QueryResult';
465
- }
466
- // ======================================================
467
- // Internal fetch methods
468
- // ======================================================
469
- getAllEntityRefs() {
470
- let allNestedRefIdsSignal = this.allNestedRefIdsSignal;
471
- if (!allNestedRefIdsSignal) {
472
- const queryClient = this.queryClient;
473
- this.allNestedRefIdsSignal = allNestedRefIdsSignal = reactiveSignal(() => {
474
- // Entangle the relay value. Whenever the relay value is updated, the
475
- // allNestedRefIdsSignal will be updated, so no need for a second signal.
476
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
477
- this.relay.value;
478
- const allRefIds = new Set();
479
- if (this.refIds !== undefined) {
480
- for (const refId of this.refIds) {
481
- queryClient.getNestedEntityRefIds(refId, allRefIds);
482
- }
483
- }
484
- // Reconcile extra data against the main response
485
- this.extraData.reconcile(allRefIds);
486
- return allRefIds;
487
- });
488
- }
489
- return allNestedRefIdsSignal.value;
490
- }
491
- /**
492
- * Initialize the query by loading from cache and fetching if stale
493
- */
494
- async initialize() {
495
- const state = this.relayState;
496
- this.initialized = true;
497
- let cached;
498
- try {
499
- // Load from cache first (use storage key for cache operations)
500
- cached = await this.queryClient.loadCachedQuery(this.def, this.storageKey);
501
- if (cached !== undefined) {
502
- // Set the cached timestamp
503
- this.updatedAt = cached.updatedAt;
504
- // Set the cached reference IDs
505
- this.refIds = cached.refIds;
506
- // Load extra data (stream orphans and optimistic inserts) BEFORE setting state.value
507
- // because setting state.value resolves the relay
508
- if (cached.extra) {
509
- const def = this.def;
510
- this.extraData.loadFromCache(cached.extra, this.queryClient, def.stream?.shape, def.optimisticInserts?.shape);
511
- }
512
- // Set the value last - this resolves the relay
513
- const shape = this.def.shape;
514
- state.value =
515
- shape instanceof ValidatorDef
516
- ? parseEntities(cached.value, shape, this.queryClient, new Set())
517
- : parseValue(cached.value, shape, this.def.id);
518
- }
519
- }
520
- catch (error) {
521
- this.queryClient.deleteCachedQuery(this.storageKey);
522
- this.queryClient
523
- .getContext()
524
- .log?.warn?.('Failed to initialize query, the query cache may be corrupted or invalid', error);
525
- }
526
- if (this.isPaused) {
527
- return;
528
- }
529
- try {
530
- // Setup subscriptions (handles both StreamQuery and Query/InfiniteQuery with stream)
531
- if (this.def.type === "stream" /* QueryType.Stream */ ||
532
- this.def.stream) {
533
- this.setupSubscription();
534
- }
535
- // For non-stream queries, fetch if stale or no cache
536
- if (this.def.type !== "stream" /* QueryType.Stream */) {
537
- if (cached !== undefined) {
538
- // Check if data is stale
539
- if (this.isStale) {
540
- // Data is stale, trigger background refetch (with debounce if configured)
541
- if (this.def.debounce !== undefined && this.def.debounce > 0) {
542
- this.debouncedRefetch();
543
- }
544
- else {
545
- this.refetch();
546
- }
547
- }
548
- }
549
- else {
550
- // No cached data, fetch fresh immediately (don't debounce initial fetch)
551
- // Debounce only applies to refetches triggered by parameter changes
552
- state.setPromise(this.runQuery(this.currentParams, true));
553
- }
554
- }
555
- }
556
- catch (error) {
557
- // Relay will handle the error state automatically
558
- state.setError(error);
559
- }
560
- }
561
- /**
562
- * Handle stream updates. This method handles both StreamQuery and Query/InfiniteQuery with stream options.
563
- * - For StreamQuery: directly updates the relay state with the entity
564
- * - For Query/InfiniteQuery with stream: updates entities in response or adds to orphans
565
- */
566
- setupSubscription() {
567
- this.unsubscribe?.();
568
- let subscribeFn;
569
- let shapeDef;
570
- if (this.def.type === "stream" /* QueryType.Stream */) {
571
- shapeDef = this.def.shape;
572
- subscribeFn = this.def.subscribeFn;
573
- }
574
- else {
575
- const stream = this.def.stream;
576
- if (!stream) {
577
- return;
578
- }
579
- shapeDef = stream.shape;
580
- subscribeFn = stream.subscribeFn;
581
- }
582
- // Extract params (reading Signal values establishes tracking)
583
- const extractedParams = this.currentParams;
584
- this.unsubscribe = subscribeFn(this.queryClient.getContext(), extractedParams, update => {
585
- const parsedData = parseObjectEntities(update, shapeDef, this.queryClient);
586
- // Update the relay state
587
- if (this.def.type === "stream" /* QueryType.Stream */) {
588
- this.relayState.value = parsedData;
589
- this.updatedAt = Date.now();
590
- // Cache the data
591
- // Use storage key for cache operations
592
- this.queryClient.saveQueryData(this.def, this.storageKey, parsedData, this.updatedAt);
593
- }
594
- else {
595
- const allRefIds = this.getAllEntityRefs();
596
- const proxyId = getProxyId(parsedData);
597
- // Add to orphans if not in main response
598
- if (proxyId !== undefined && !allRefIds.has(proxyId)) {
599
- this.extraData.addStreamOrphan(parsedData);
600
- }
601
- }
602
- });
603
- }
604
- /**
605
- * Fetches fresh data, updates the cache, and updates updatedAt timestamp
606
- */
607
- async runQuery(params, reset = false) {
608
- // Check if paused before attempting fetch
609
- if (this.isPaused) {
610
- throw new Error('Query is paused due to network status');
611
- }
612
- const { retries, retryDelay } = this.getRetryConfig();
613
- let lastError;
614
- // Attempt fetch with retries
615
- for (let attempt = 0; attempt <= retries; attempt++) {
616
- try {
617
- const queryDef = this.def;
618
- const freshData = await queryDef.fetchFn(this.queryClient.getContext(), params);
619
- // Parse and cache the fresh data
620
- let entityRefs;
621
- const isInfinite = this.def.type === "infiniteQuery" /* QueryType.InfiniteQuery */;
622
- if (isInfinite && !reset && this.refIds !== undefined) {
623
- entityRefs = this.refIds;
624
- }
625
- else {
626
- entityRefs = this.refIds = new Set();
627
- }
628
- const shape = this.def.shape;
629
- const parsedData = shape instanceof ValidatorDef
630
- ? parseEntities(freshData, shape, this.queryClient, entityRefs)
631
- : parseValue(freshData, shape, this.def.id);
632
- let queryData;
633
- if (isInfinite) {
634
- const prevQueryData = this.relayState.value;
635
- queryData = reset || prevQueryData === undefined ? [parsedData] : [...prevQueryData, parsedData];
636
- }
637
- else {
638
- queryData = parsedData;
639
- }
640
- let updatedAt;
641
- if (reset) {
642
- updatedAt = this.updatedAt = Date.now();
643
- }
644
- else {
645
- updatedAt = this.updatedAt ??= Date.now();
646
- }
647
- this._nextPageParams = undefined;
648
- // Cache the data (synchronous, fire-and-forget)
649
- // Use storage key for cache operations
650
- this.queryClient.saveQueryData(this.def, this.storageKey, queryData, updatedAt, entityRefs, this.getExtraForPersistence());
651
- // Update the timestamp
652
- this.updatedAt = Date.now();
653
- return queryData;
654
- }
655
- catch (error) {
656
- lastError = error;
657
- // If we've exhausted retries, throw the error
658
- if (attempt >= retries) {
659
- throw error;
660
- }
661
- // Wait before retrying (unless paused)
662
- const delay = retryDelay(attempt);
663
- await new Promise(resolve => setTimeout(resolve, delay));
664
- // Check if paused during retry delay
665
- if (this.isPaused) {
666
- throw new Error('Query is paused due to network status');
667
- }
668
- }
669
- }
670
- // Should never reach here, but TypeScript needs it
671
- throw lastError;
672
- }
673
- // ======================================================
674
- // Private debounce methods
675
- // ======================================================
676
- /**
677
- * Triggers a debounced refetch. If debounce is configured, delays the fetch.
678
- * Otherwise, calls refetch immediately.
679
- */
680
- debouncedRefetch() {
681
- // We know this is a non-stream query because we're calling refetch, which is only available on non-stream queries
682
- const debounce = this.def.debounce;
683
- if (debounce === undefined) {
684
- this.refetch();
685
- return;
686
- }
687
- // Clear existing timer
688
- clearTimeout(this.debounceTimer);
689
- // Set new timer
690
- this.debounceTimer = setTimeout(() => {
691
- this.debounceTimer = undefined;
692
- this.refetch();
693
- }, debounce);
694
- }
695
- // ======================================================
696
- // Public methods
697
- // ======================================================
698
- refetch = () => {
699
- if (this.def.type === "stream" /* QueryType.Stream */) {
700
- throw new Error('Cannot refetch a stream query');
701
- }
702
- if (this.fetchMorePromise) {
703
- throw new Error('Query is fetching more, cannot refetch');
704
- }
705
- if (this.refetchPromise) {
706
- return this.refetchPromise;
707
- }
708
- // Clear debounce timer if active (manual refetch should bypass debounce)
709
- clearTimeout(this.debounceTimer);
710
- this.debounceTimer = undefined;
711
- // Clear memoized nextPageParams so it's recalculated after refetch
712
- this._nextPageParams = undefined;
713
- // Set the signal before any async operations so it's immediately visible
714
- // Use untrack to avoid reactive violations when called from reactive context
715
- this.isRefetchingSignal.value = true;
716
- this._version.update(v => v + 1);
717
- const promise = this.runQuery(this.currentParams, true)
718
- .then(result => {
719
- this.relayState.value = result;
720
- // Clear stream orphans and optimistic inserts on refetch
721
- if (this._extra !== undefined) {
722
- this._extra.clear();
723
- }
724
- return result;
725
- })
726
- .catch((error) => {
727
- this.relayState.setError(error);
728
- return Promise.reject(error);
729
- })
730
- .finally(() => {
731
- this._version.update(v => v + 1);
732
- this.isRefetchingSignal.value = false;
733
- this.refetchPromise = undefined;
734
- });
735
- this.refetchPromise = promise;
736
- return promise;
737
- };
738
- fetchNextPage = () => {
739
- if (this.def.type === "stream" /* QueryType.Stream */) {
740
- throw new Error('Cannot fetch next page on a stream query');
741
- }
742
- if (this.refetchPromise) {
743
- return Promise.reject(new Error('Query is refetching, cannot fetch next page'));
744
- }
745
- if (this.fetchMorePromise) {
746
- return this.fetchMorePromise;
747
- }
748
- // Read nextPageParams in untracked context to avoid reactive violations
749
- const nextPageParams = this.nextPageParams;
750
- if (!nextPageParams) {
751
- return Promise.reject(new Error('No next page params'));
752
- }
753
- // Set the signal before any async operations so it's immediately visible
754
- // Use untrack to avoid reactive violations when called from reactive context
755
- this.isFetchingMoreSignal.value = true;
756
- this._version.update(v => v + 1);
757
- const promise = this.runQuery(nextPageParams, false)
758
- .then(result => {
759
- this.relayState.value = result;
760
- return result;
761
- })
762
- .catch((error) => {
763
- this.relayState.setError(error);
764
- return Promise.reject(error);
765
- })
766
- .finally(() => {
767
- this._version.update(v => v + 1);
768
- this.isFetchingMoreSignal.value = false;
769
- this.fetchMorePromise = undefined;
770
- });
771
- this.fetchMorePromise = promise;
772
- return promise;
773
- };
774
- // ======================================================
775
- // Public properties
776
- // ======================================================
777
- get isRefetching() {
778
- return this.isRefetchingSignal.value;
779
- }
780
- get isFetchingMore() {
781
- return this.isFetchingMoreSignal.value;
782
- }
783
- get isFetching() {
784
- return this.relay.isPending || this.isRefetching || this.isFetchingMore;
785
- }
786
- get hasNextPage() {
787
- return this.nextPageParams !== null;
788
- }
789
- get extra() {
790
- this.getAllEntityRefs();
791
- return this.extraData.getExtra();
792
- }
793
- /**
794
- * Persist the current extra data to the store
795
- */
796
- persistExtraData() {
797
- if (this.updatedAt === undefined) {
798
- return; // Query not initialized yet
799
- }
800
- const extra = this._extra?.getForPersistence();
801
- // Use storage key for cache operations
802
- this.queryClient.saveQueryData(this.def, this.storageKey, this.relayState.value, this.updatedAt, this.refIds, extra);
803
- }
804
- /**
805
- * Get extra data for persistence (converts Sets to arrays of entity ref IDs)
806
- */
807
- getExtraForPersistence() {
808
- return this._extra?.getForPersistence();
809
- }
810
- /**
811
- * Add an optimistic insert to the query result.
812
- * The insert will be automatically removed when:
813
- * - The entity appears in a refetched response
814
- * - The entity appears as a stream orphan
815
- * - refetch() is called
816
- */
817
- addOptimisticInsert(insert) {
818
- // Check that the query has optimisticInserts configured
819
- const def = this.def;
820
- const optimisticInsertsConfig = def.optimisticInserts;
821
- if (optimisticInsertsConfig === undefined) {
822
- throw new Error('Query does not have optimisticInserts configured. Add optimisticInserts: { type: YourEntity } to the query definition.');
823
- }
824
- let proxyId = getProxyId(insert);
825
- let parsedInsert = insert;
826
- // If not already a proxy, parse it through the optimisticInserts shape
827
- if (proxyId === undefined) {
828
- parsedInsert = parseObjectEntities(insert, optimisticInsertsConfig.shape, this.queryClient);
829
- proxyId = getProxyId(parsedInsert);
830
- if (proxyId === undefined) {
831
- throw new Error('Optimistic insert must be or produce an entity proxy');
832
- }
833
- }
834
- // Check if already in main response
835
- const allRefIds = this.getAllEntityRefs();
836
- if (allRefIds.has(proxyId)) {
837
- return; // Already in response, no-op
838
- }
839
- // Check if already in stream orphans
840
- if (this.extraData.hasOrphanWithId(proxyId)) {
841
- return; // Already in stream orphans, no-op
842
- }
843
- this.extraData.addOptimisticInsert(parsedInsert);
844
- }
845
- /**
846
- * Remove an optimistic insert from the query result.
847
- * This is a no-op if the insert has already been removed.
848
- */
849
- removeOptimisticInsert(insert) {
850
- this.extraData.removeOptimisticInsert(insert);
851
- }
852
- get isStale() {
853
- // Streams are never stale - they're always receiving updates
854
- if (this.def.type === "stream" /* QueryType.Stream */) {
855
- return false;
856
- }
857
- if (this.updatedAt === undefined) {
858
- return true; // No data yet, needs fetch
859
- }
860
- const staleTime = this.def.cache?.staleTime ?? 0;
861
- return Date.now() - this.updatedAt >= staleTime;
862
- }
863
- get isPaused() {
864
- // Streams handle their own connection state
865
- if (this.def.type === "stream" /* QueryType.Stream */) {
866
- return false;
867
- }
868
- const networkMode = this.def.cache?.networkMode ?? NetworkMode.Online;
869
- const networkManager = this.queryClient.networkManager;
870
- // Read the online signal to make this reactive
871
- const isOnline = networkManager.getOnlineSignal().value;
872
- switch (networkMode) {
873
- case NetworkMode.Always:
874
- return false;
875
- case NetworkMode.Online:
876
- return !isOnline;
877
- case NetworkMode.OfflineFirst:
878
- // Only paused if we have no cached data AND we're offline
879
- return !isOnline && this.updatedAt === undefined;
880
- default:
881
- return false;
882
- }
883
- }
884
- getRetryConfig() {
885
- // Streams don't have retry config
886
- if (this.def.type === "stream" /* QueryType.Stream */) {
887
- return { retries: 0, retryDelay: () => 0 };
888
- }
889
- const retryOption = this.def.cache?.retry;
890
- const isServer = this.queryClient.isServer;
891
- // Default retry count: 3 on client, 0 on server
892
- let retries;
893
- let retryDelay;
894
- if (retryOption === false) {
895
- retries = 0;
896
- }
897
- else if (retryOption === undefined) {
898
- retries = isServer ? 0 : 3;
899
- }
900
- else if (typeof retryOption === 'number') {
901
- retries = retryOption;
902
- }
903
- else {
904
- retries = retryOption.retries;
905
- }
906
- // Default exponential backoff: 1000ms * 2^attempt
907
- if (typeof retryOption === 'object' && retryOption.retryDelay) {
908
- retryDelay = retryOption.retryDelay;
909
- }
910
- else {
911
- retryDelay = (attempt) => 1000 * Math.pow(2, attempt);
912
- }
913
- return { retries, retryDelay };
914
- }
915
- }
916
- //# sourceMappingURL=QueryResult.js.map