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