@raf-query/core 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -2
- package/README.md +28 -18
- package/dist/client.d.mts +132 -119
- package/dist/client.mjs +59 -162
- package/dist/entry.d.mts +67 -148
- package/dist/entry.mjs +303 -474
- package/dist/features/focus-refetch/focus-refetch.d.mts +15 -0
- package/dist/features/focus-refetch/focus-refetch.mjs +18 -0
- package/dist/features/polling/polling.d.mts +12 -0
- package/dist/features/polling/polling.mjs +35 -0
- package/dist/features/reconnect-refetch/reconnect-refetch.d.mts +15 -0
- package/dist/features/reconnect-refetch/reconnect-refetch.mjs +14 -0
- package/dist/features/shared.mjs +13 -0
- package/dist/guards.mjs +23 -0
- package/dist/hydration/hydration.d.mts +73 -0
- package/dist/hydration/hydration.mjs +128 -0
- package/dist/index.d.mts +18 -13
- package/dist/index.mjs +15 -13
- package/dist/infinite/infinite.d.mts +71 -0
- package/dist/infinite/infinite.mjs +88 -0
- package/dist/internal.d.mts +13 -0
- package/dist/internal.mjs +7 -0
- package/dist/keys/keys.d.mts +46 -0
- package/dist/keys/keys.mjs +53 -0
- package/dist/keys/targets.d.mts +56 -0
- package/dist/keys/targets.mjs +85 -0
- package/dist/links/retry/retry.d.mts +26 -0
- package/dist/links/retry/retry.mjs +51 -0
- package/dist/links/timeout/timeout.d.mts +9 -0
- package/dist/links/timeout/timeout.mjs +27 -0
- package/dist/load.d.mts +124 -0
- package/dist/load.mjs +165 -0
- package/dist/options.d.mts +112 -338
- package/dist/options.mjs +25 -36
- package/dist/presets.d.mts +29 -0
- package/dist/presets.mjs +25 -0
- package/dist/root.d.mts +82 -0
- package/dist/structural-sharing/structural-sharing.mjs +32 -0
- package/dist/subscribe.d.mts +15 -0
- package/dist/subscribe.mjs +29 -0
- package/dist/thenable.d.mts +13 -17
- package/dist/thenable.mjs +25 -6
- package/dist/verbs.d.mts +109 -0
- package/dist/verbs.mjs +87 -0
- package/package.json +13 -9
- package/dist/_virtual/_rolldown/runtime.mjs +0 -13
- package/dist/all-keyed.d.mts +0 -18
- package/dist/all-keyed.mjs +0 -19
- package/dist/binding.d.mts +0 -92
- package/dist/binding.mjs +0 -186
- package/dist/cache.d.mts +0 -164
- package/dist/cache.mjs +0 -184
- package/dist/controls.d.mts +0 -93
- package/dist/controls.mjs +0 -69
- package/dist/hydration.d.mts +0 -84
- package/dist/hydration.mjs +0 -148
- package/dist/infinite.d.mts +0 -5
- package/dist/infinite.mjs +0 -103
- package/dist/key.d.mts +0 -18
- package/dist/key.mjs +0 -36
- package/dist/listen.mjs +0 -19
- package/dist/notify.mjs +0 -21
- package/dist/observer.d.mts +0 -110
- package/dist/observer.mjs +0 -300
- package/dist/platform.d.mts +0 -27
- package/dist/platform.mjs +0 -44
- package/dist/query.d.mts +0 -137
- package/dist/query.mjs +0 -147
- package/dist/request.mjs +0 -47
- package/dist/retry.mjs +0 -25
- package/dist/share.mjs +0 -40
package/dist/entry.mjs
CHANGED
|
@@ -1,526 +1,355 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { retrySchedule } from "./retry.mjs";
|
|
1
|
+
import { listeners } from "./subscribe.mjs";
|
|
2
|
+
import { isObject } from "./guards.mjs";
|
|
3
|
+
import { hashKey } from "./keys/keys.mjs";
|
|
4
|
+
import { matches } from "./keys/targets.mjs";
|
|
5
|
+
import { optionsOf } from "./options.mjs";
|
|
6
|
+
import { replaceEqualDeep } from "./structural-sharing/structural-sharing.mjs";
|
|
8
7
|
import { fulfilled, pending } from "./thenable.mjs";
|
|
9
|
-
import { Cause, Data, Deferred, Effect, Exit, FiberMap, Pipeable, Predicate, Schedule, Scope } from "effect";
|
|
10
8
|
//#region src/entry.ts
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
const initialState = {
|
|
10
|
+
data: void 0,
|
|
11
|
+
error: void 0,
|
|
12
|
+
fetchingDirection: void 0,
|
|
13
|
+
hasData: false,
|
|
14
|
+
isFetching: false,
|
|
15
|
+
status: "pending",
|
|
16
|
+
updatedAt: 0
|
|
18
17
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
const fiveMinutes = 3e5;
|
|
19
|
+
function abortError() {
|
|
20
|
+
return new DOMException("The query was cancelled", "AbortError");
|
|
21
|
+
}
|
|
22
|
+
function notify(entry) {
|
|
23
|
+
const client = entry.client;
|
|
24
|
+
const changed = client["~changed"];
|
|
25
|
+
if (changed.size === 0) queueMicrotask(() => {
|
|
26
|
+
const batch = [...changed];
|
|
27
|
+
changed.clear();
|
|
28
|
+
for (const item of batch) {
|
|
29
|
+
item.readers.emit(item.state);
|
|
30
|
+
client["~events"].emit({
|
|
31
|
+
entry: item,
|
|
32
|
+
type: "updated"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
changed.add(entry);
|
|
37
|
+
}
|
|
38
|
+
function setState(entry, patch) {
|
|
39
|
+
entry.state = {
|
|
40
|
+
...entry.state,
|
|
41
|
+
...patch
|
|
42
|
+
};
|
|
43
|
+
notify(entry);
|
|
38
44
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
45
|
+
function newPending(entry) {
|
|
46
|
+
const next = pending();
|
|
47
|
+
entry.pendingThenable = next;
|
|
48
|
+
entry.thenable = next.promise;
|
|
41
49
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
50
|
+
function gcTime(entry) {
|
|
51
|
+
return entry.options?.gcTime ?? entry.client.config.gcTime ?? fiveMinutes;
|
|
44
52
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
run(task, effect, options);
|
|
53
|
+
function scheduleGc(entry) {
|
|
54
|
+
const client = entry.client;
|
|
55
|
+
client.clock.clearTimeout(entry.gcTimer);
|
|
56
|
+
entry.gcTimer = void 0;
|
|
57
|
+
const time = gcTime(entry);
|
|
58
|
+
if (client.isServer || entry.readers.size > 0 || !Number.isFinite(time)) return;
|
|
59
|
+
entry.gcTimer = client.clock.setTimeout(() => {
|
|
60
|
+
entry.gcTimer = void 0;
|
|
61
|
+
if (entry.run) {
|
|
62
|
+
entry.run.done.promise.then(() => scheduleGc(entry));
|
|
63
|
+
return;
|
|
57
64
|
}
|
|
58
|
-
|
|
65
|
+
remove(entry);
|
|
66
|
+
}, time);
|
|
59
67
|
}
|
|
60
|
-
function
|
|
61
|
-
const scope = Scope.forkUnsafe(cache.scope);
|
|
68
|
+
function create(client, key, hash) {
|
|
62
69
|
const first = pending();
|
|
63
|
-
const entry =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
handle: {
|
|
68
|
-
input: void 0,
|
|
69
|
-
key
|
|
70
|
-
},
|
|
70
|
+
const entry = {
|
|
71
|
+
client,
|
|
72
|
+
factory: void 0,
|
|
73
|
+
gcTimer: void 0,
|
|
71
74
|
hash,
|
|
72
|
-
|
|
73
|
-
inFlight: void 0,
|
|
75
|
+
input: void 0,
|
|
74
76
|
key,
|
|
75
|
-
listeners: /* @__PURE__ */ new Map(),
|
|
76
77
|
options: void 0,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
status: "pending",
|
|
94
|
-
updatedAt: 0
|
|
95
|
-
},
|
|
96
|
-
tasks: makeTasks(cache, scope)
|
|
97
|
-
});
|
|
98
|
-
const command = (request) => Effect.suspend(() => isDisabled(entry) ? Effect.void : Effect.ignore(fetch(entry, request)));
|
|
99
|
-
const commands = {
|
|
100
|
-
fetchNextPage: command({ direction: "forward" }),
|
|
101
|
-
fetchPreviousPage: command({ direction: "backward" }),
|
|
102
|
-
refetch: command()
|
|
78
|
+
pendingThenable: first,
|
|
79
|
+
readers: listeners((size) => {
|
|
80
|
+
client["~events"].emit({
|
|
81
|
+
entry,
|
|
82
|
+
type: "readers"
|
|
83
|
+
});
|
|
84
|
+
if (size === 0) scheduleGc(entry);
|
|
85
|
+
else {
|
|
86
|
+
client.clock.clearTimeout(entry.gcTimer);
|
|
87
|
+
entry.gcTimer = void 0;
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
requestedAt: client["~seq"],
|
|
91
|
+
run: void 0,
|
|
92
|
+
state: initialState,
|
|
93
|
+
thenable: first.promise
|
|
103
94
|
};
|
|
104
|
-
|
|
95
|
+
client["~entries"].set(hash, entry);
|
|
96
|
+
client["~events"].emit({
|
|
97
|
+
entry,
|
|
98
|
+
type: "added"
|
|
99
|
+
});
|
|
105
100
|
scheduleGc(entry);
|
|
106
101
|
return entry;
|
|
107
102
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
function isObserved(entry) {
|
|
113
|
-
return entry.listeners.size > 0;
|
|
114
|
-
}
|
|
115
|
-
function behavior(entry, name) {
|
|
116
|
-
return entry.options?.[name] ?? entry.cache.defaults[name];
|
|
117
|
-
}
|
|
118
|
-
/** The entry's latest factory call returned `enabled: false`: it doesn't run. */
|
|
119
|
-
function isDisabled(entry) {
|
|
120
|
-
return behavior(entry, "enabled") === false;
|
|
121
|
-
}
|
|
122
|
-
/** Until a factory call attaches options, `meta` comes from hydration. */
|
|
123
|
-
function metaOf(entry) {
|
|
124
|
-
return entry.options ? behavior(entry, "meta") : entry.hydratedMeta ?? entry.cache.defaults.meta;
|
|
125
|
-
}
|
|
126
|
-
function computeStale(entry, state) {
|
|
127
|
-
if (state.isInvalidated || !state.hasData) return true;
|
|
128
|
-
const staleTime = behavior(entry, "staleTime") ?? 0;
|
|
129
|
-
return now(entry) - state.updatedAt >= staleTime;
|
|
130
|
-
}
|
|
131
|
-
function isStaleUnsafe(entry) {
|
|
132
|
-
return computeStale(entry, entry.state);
|
|
133
|
-
}
|
|
134
|
-
function hasNextPageUnsafe(entry) {
|
|
135
|
-
const options = entry.options;
|
|
136
|
-
const data = entry.state.data;
|
|
137
|
-
return options?.kind === "infinite" && isInfiniteData(data) && nextPageParam(options, data) != null;
|
|
138
|
-
}
|
|
139
|
-
function hasPreviousPageUnsafe(entry) {
|
|
140
|
-
const options = entry.options;
|
|
141
|
-
const data = entry.state.data;
|
|
142
|
-
return options?.kind === "infinite" && isInfiniteData(data) && previousPageParam(options, data) != null;
|
|
143
|
-
}
|
|
144
|
-
function subscribeUnsafe(entry, listener, options = defaultSubscribeOptions) {
|
|
145
|
-
entry.listeners.set(listener, options);
|
|
146
|
-
entry.retriesStopped = false;
|
|
147
|
-
entry.tasks.cancel("gc");
|
|
148
|
-
scheduleStale(entry);
|
|
149
|
-
emit(entry.cache, {
|
|
150
|
-
query: entry,
|
|
151
|
-
type: "observers"
|
|
152
|
-
});
|
|
153
|
-
return () => {
|
|
154
|
-
if (!entry.listeners.delete(listener)) return;
|
|
155
|
-
if (entry.listeners.size === 0) {
|
|
156
|
-
entry.tasks.cancel("stale");
|
|
157
|
-
scheduleGc(entry);
|
|
158
|
-
const inFlight = entry.inFlight;
|
|
159
|
-
if (inFlight) scheduleNotify(() => unobserved(entry, inFlight));
|
|
160
|
-
}
|
|
161
|
-
emit(entry.cache, {
|
|
162
|
-
query: entry,
|
|
163
|
-
type: "observers"
|
|
164
|
-
});
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
function changes(entry) {
|
|
168
|
-
return listen((push) => {
|
|
169
|
-
push(entry.state);
|
|
170
|
-
return subscribeUnsafe(entry, () => push(entry.state));
|
|
171
|
-
});
|
|
103
|
+
/** The entry for a key, created if missing. Hydration uses it. */
|
|
104
|
+
function entryForKey(client, key) {
|
|
105
|
+
const hash = hashKey(key);
|
|
106
|
+
return client["~entries"].get(hash) ?? create(client, key, hash);
|
|
172
107
|
}
|
|
173
108
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
109
|
+
* The entry for a factory's input, created if missing, with the factory's
|
|
110
|
+
* current options. Every read goes through here, which links an entry that
|
|
111
|
+
* was only hydrated to its factory.
|
|
177
112
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
113
|
+
function entryOf(client, factory, input) {
|
|
114
|
+
const options = optionsOf(factory, input);
|
|
115
|
+
const entry = entryForKey(client, options.queryKey);
|
|
116
|
+
entry.factory = factory;
|
|
117
|
+
entry.input = input;
|
|
183
118
|
entry.options = options;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
key: entry.key
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* The promise a render should unwrap. Starts the first request if nothing
|
|
191
|
-
* has, and retries a failed first load once its error has been shown.
|
|
192
|
-
*/
|
|
193
|
-
function readUnsafe(entry) {
|
|
194
|
-
const state = entry.state;
|
|
195
|
-
if (!state.hasData && !state.isFetching) {
|
|
196
|
-
if (state.status !== "error" || entry.errorShown) begin(entry, {});
|
|
197
|
-
else markErrorShown(entry);
|
|
198
|
-
}
|
|
199
|
-
return entry.state.promise;
|
|
200
|
-
}
|
|
201
|
-
/** Start a request unless one is in flight, without waiting for it. */
|
|
202
|
-
function start(entry) {
|
|
203
|
-
begin(entry, {});
|
|
119
|
+
scheduleGc(entry);
|
|
120
|
+
return entry;
|
|
204
121
|
}
|
|
205
|
-
/**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
* result is never shown; it refetches only what `invalidate` marked.
|
|
209
|
-
*/
|
|
210
|
-
function needsRefetch(entry) {
|
|
211
|
-
return entry.cache.isServer ? entry.state.isInvalidated : isStaleUnsafe(entry);
|
|
122
|
+
/** Whether an invalidation newer than the entry's data covers it. */
|
|
123
|
+
function isInvalidated(entry) {
|
|
124
|
+
return entry.client["~stamps"].some((stamp) => stamp.at > entry.requestedAt && matches(entry, stamp));
|
|
212
125
|
}
|
|
213
|
-
/**
|
|
214
|
-
function
|
|
126
|
+
/** Whether the entry's data should be refetched before it's trusted. */
|
|
127
|
+
function isStale(entry) {
|
|
215
128
|
const state = entry.state;
|
|
216
|
-
if (state.hasData
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return Effect.suspend(() => {
|
|
220
|
-
const inFlight = entry.inFlight;
|
|
221
|
-
if (inFlight && request.direction && !request.restart) return Deferred.await(inFlight).pipe(Effect.exit, Effect.andThen(fetch(entry, request)));
|
|
222
|
-
return begin(entry, request);
|
|
223
|
-
});
|
|
129
|
+
if (!state.hasData) return true;
|
|
130
|
+
const staleTime = entry.options?.staleTime ?? entry.client.config.staleTime ?? 0;
|
|
131
|
+
return entry.client.clock.now() - state.updatedAt >= staleTime || isInvalidated(entry);
|
|
224
132
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
failureCount: 0,
|
|
241
|
-
hasData: false,
|
|
242
|
-
isInvalidated: false,
|
|
243
|
-
promise: entry.pending.promise,
|
|
244
|
-
status: "pending",
|
|
245
|
-
updatedAt: 0
|
|
246
|
-
});
|
|
247
|
-
if (!entry.options || isDisabled(entry) || !isObserved(entry) && !entry.inFlight) return Effect.void;
|
|
248
|
-
return Effect.ignore(begin(entry, { restart: true }));
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
/** Write data directly, as if a request had returned it. */
|
|
252
|
-
function setData(entry, data, updatedAt = now(entry)) {
|
|
253
|
-
succeed(entry, data, updatedAt);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Adopt a request started elsewhere (the server's, during hydration). Its
|
|
257
|
-
* data counts as fetched when the sender got it, not when it arrived.
|
|
258
|
-
*/
|
|
259
|
-
function resume(entry, promise) {
|
|
260
|
-
if (entry.state.hasData || entry.inFlight) return;
|
|
261
|
-
let updatedAt;
|
|
262
|
-
startRequest(entry, Effect.promise(async () => {
|
|
263
|
-
const result = await promise;
|
|
264
|
-
updatedAt = result.updatedAt;
|
|
265
|
-
return result.data;
|
|
266
|
-
}), void 0, () => updatedAt);
|
|
267
|
-
}
|
|
268
|
-
/** Refetch a read, stale entry whose readers want it on this event. */
|
|
269
|
-
function onPlatformEvent(entry, event) {
|
|
270
|
-
const option = event === "focus" ? "refetchOnWindowFocus" : "refetchOnReconnect";
|
|
271
|
-
const fallback = behavior(entry, option) ?? true;
|
|
272
|
-
if ([...entry.listeners.values()].some((options) => options[option] ?? fallback) && isStaleUnsafe(entry)) start(entry);
|
|
273
|
-
}
|
|
274
|
-
/** Take `entry` out of its cache: interrupt its fibers and fail its awaiters. */
|
|
275
|
-
function remove(entry) {
|
|
276
|
-
const cache = entry.cache;
|
|
277
|
-
if (cache.entries.get(entry.hash) !== entry) return;
|
|
278
|
-
cache.entries.delete(entry.hash);
|
|
279
|
-
const inFlight = entry.inFlight;
|
|
280
|
-
entry.inFlight = void 0;
|
|
281
|
-
Effect.runForkWith(cache.services)(Scope.close(entry.scope, Exit.void));
|
|
282
|
-
if (inFlight) Deferred.doneUnsafe(inFlight, Exit.fail(new CancelledError()));
|
|
283
|
-
emit(cache, {
|
|
284
|
-
query: entry,
|
|
285
|
-
type: "removed"
|
|
133
|
+
/** Write data. `startedAt` orders it against invalidations. */
|
|
134
|
+
function write(entry, data, updatedAt, startedAt) {
|
|
135
|
+
const previous = entry.state;
|
|
136
|
+
const next = (entry.options?.structuralSharing ?? entry.client.config.structuralSharing ?? true) && previous.hasData ? replaceEqualDeep(previous.data, data) : data;
|
|
137
|
+
entry.requestedAt = Math.max(entry.requestedAt, startedAt);
|
|
138
|
+
if (entry.pendingThenable) {
|
|
139
|
+
entry.pendingThenable.resolve(next);
|
|
140
|
+
entry.pendingThenable = void 0;
|
|
141
|
+
} else if (!previous.hasData || next !== previous.data) entry.thenable = fulfilled(next);
|
|
142
|
+
setState(entry, {
|
|
143
|
+
data: next,
|
|
144
|
+
error: void 0,
|
|
145
|
+
hasData: true,
|
|
146
|
+
status: "success",
|
|
147
|
+
updatedAt
|
|
286
148
|
});
|
|
149
|
+
scheduleGc(entry);
|
|
287
150
|
}
|
|
288
151
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
152
|
+
* Record a failed request. Data the entry had stays readable. Without data,
|
|
153
|
+
* the request in flight has a pending thenable for readers: it rejects.
|
|
291
154
|
*/
|
|
292
|
-
function
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (entry.cache.isServer) {
|
|
297
|
-
const firstLoad = entry.pending;
|
|
298
|
-
entry.pending = void 0;
|
|
299
|
-
firstLoad?.reject(error);
|
|
300
|
-
}
|
|
301
|
-
return Effect.die(error);
|
|
302
|
-
}
|
|
303
|
-
const options = entry.options;
|
|
304
|
-
const inFlight = entry.inFlight;
|
|
305
|
-
if (inFlight && !(request.restart && options)) return Deferred.await(inFlight);
|
|
306
|
-
if (!options) {
|
|
307
|
-
const promise = entry.state.promise;
|
|
308
|
-
return Effect.promise(() => promise);
|
|
155
|
+
function failWith(entry, error) {
|
|
156
|
+
if (!entry.state.hasData) {
|
|
157
|
+
entry.pendingThenable?.reject(error);
|
|
158
|
+
entry.pendingThenable = void 0;
|
|
309
159
|
}
|
|
310
|
-
entry
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
function set(entry, patch) {
|
|
314
|
-
const next = {
|
|
315
|
-
...entry.state,
|
|
316
|
-
...patch
|
|
317
|
-
};
|
|
318
|
-
entry.state = {
|
|
319
|
-
...next,
|
|
320
|
-
isStale: computeStale(entry, next)
|
|
321
|
-
};
|
|
322
|
-
for (const listener of entry.listeners.keys()) scheduleNotify(listener);
|
|
323
|
-
emit(entry.cache, {
|
|
324
|
-
query: entry,
|
|
325
|
-
type: "updated"
|
|
160
|
+
setState(entry, {
|
|
161
|
+
error,
|
|
162
|
+
status: "error"
|
|
326
163
|
});
|
|
327
|
-
scheduleStale(entry);
|
|
328
164
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
entry.retriesStopped = false;
|
|
335
|
-
entry.signalRead = false;
|
|
336
|
-
entry.tasks.cancel("gc");
|
|
337
|
-
if (!entry.state.hasData && !entry.pending) {
|
|
338
|
-
entry.pending = pending();
|
|
339
|
-
set(entry, {
|
|
340
|
-
error: null,
|
|
341
|
-
failureCount: 0,
|
|
342
|
-
fetchDirection: direction,
|
|
343
|
-
isFetching: true,
|
|
344
|
-
promise: entry.pending.promise,
|
|
345
|
-
status: "pending"
|
|
346
|
-
});
|
|
347
|
-
} else set(entry, {
|
|
348
|
-
failureCount: 0,
|
|
349
|
-
fetchDirection: direction,
|
|
350
|
-
isFetching: true
|
|
165
|
+
function finish(entry, run, outcome) {
|
|
166
|
+
entry.run = void 0;
|
|
167
|
+
setState(entry, {
|
|
168
|
+
fetchingDirection: void 0,
|
|
169
|
+
isFetching: false
|
|
351
170
|
});
|
|
352
|
-
|
|
353
|
-
return awaiter;
|
|
354
|
-
}
|
|
355
|
-
function settle(entry, awaiter, exit, updatedAt) {
|
|
356
|
-
if (entry.inFlight !== awaiter) return;
|
|
357
|
-
entry.inFlight = void 0;
|
|
358
|
-
if (Exit.isSuccess(exit)) {
|
|
359
|
-
const data = succeed(entry, exit.value, updatedAt);
|
|
360
|
-
Deferred.doneUnsafe(awaiter, Exit.succeed(data));
|
|
361
|
-
} else if (Cause.hasInterruptsOnly(exit.cause)) {
|
|
362
|
-
set(entry, {
|
|
363
|
-
fetchDirection: void 0,
|
|
364
|
-
isFetching: false
|
|
365
|
-
});
|
|
366
|
-
Deferred.doneUnsafe(awaiter, Exit.fail(new CancelledError()));
|
|
367
|
-
} else {
|
|
368
|
-
fail(entry, Cause.squash(exit.cause));
|
|
369
|
-
Deferred.doneUnsafe(awaiter, exit);
|
|
370
|
-
}
|
|
171
|
+
run.done.resolve(outcome);
|
|
371
172
|
scheduleGc(entry);
|
|
372
173
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
174
|
+
function isAsyncIterable(value) {
|
|
175
|
+
return isObject(value) && Symbol.asyncIterator in value;
|
|
176
|
+
}
|
|
177
|
+
async function pump(entry, run, chunks, stream) {
|
|
178
|
+
let data = stream.initial;
|
|
179
|
+
try {
|
|
180
|
+
for await (const chunk of chunks) {
|
|
181
|
+
if (entry.run !== run) return;
|
|
182
|
+
data = stream.reduce(data, chunk);
|
|
183
|
+
write(entry, data, entry.client.clock.now(), run.startedAt);
|
|
184
|
+
if (!run.settled) {
|
|
185
|
+
run.settled = true;
|
|
186
|
+
run.done.resolve({ ok: true });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (entry.run === run) {
|
|
191
|
+
failWith(entry, error);
|
|
192
|
+
finish(entry, run, {
|
|
193
|
+
error,
|
|
194
|
+
ok: false
|
|
195
|
+
});
|
|
196
|
+
}
|
|
384
197
|
return;
|
|
385
198
|
}
|
|
386
|
-
entry.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
fetchDirection: void 0,
|
|
391
|
-
isFetching: false
|
|
392
|
-
});
|
|
393
|
-
Deferred.doneUnsafe(awaiter, Exit.succeed(entry.state.data));
|
|
394
|
-
scheduleGc(entry);
|
|
199
|
+
if (entry.run === run) {
|
|
200
|
+
if (!run.settled) write(entry, data, entry.client.clock.now(), run.startedAt);
|
|
201
|
+
finish(entry, run, { ok: true });
|
|
202
|
+
}
|
|
395
203
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
data,
|
|
406
|
-
error: null,
|
|
407
|
-
failureCount: 0,
|
|
408
|
-
fetchDirection: isFetching ? previous.fetchDirection : void 0,
|
|
409
|
-
hasData: true,
|
|
410
|
-
isFetching,
|
|
411
|
-
isInvalidated: false,
|
|
412
|
-
promise: firstLoad?.promise ?? (unchanged ? previous.promise : fulfilled(data)),
|
|
413
|
-
status: "success",
|
|
414
|
-
updatedAt
|
|
204
|
+
function terminal(op) {
|
|
205
|
+
return new Promise((resolve) => {
|
|
206
|
+
const options = op.options;
|
|
207
|
+
const paged = options["~run"];
|
|
208
|
+
resolve(paged ? paged(op) : options.queryFn({
|
|
209
|
+
meta: op.meta,
|
|
210
|
+
queryKey: op.queryKey,
|
|
211
|
+
signal: op.signal
|
|
212
|
+
}));
|
|
415
213
|
});
|
|
416
|
-
return data;
|
|
417
214
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const firstLoad = entry.pending;
|
|
426
|
-
entry.pending = void 0;
|
|
427
|
-
firstLoad?.reject(error);
|
|
428
|
-
entry.errorShown = false;
|
|
429
|
-
}
|
|
430
|
-
set(entry, {
|
|
431
|
-
error,
|
|
432
|
-
fetchDirection: void 0,
|
|
433
|
-
isFetching: false,
|
|
434
|
-
status: "error"
|
|
435
|
-
});
|
|
215
|
+
function chain(op) {
|
|
216
|
+
const links = op.client.config.links ?? [];
|
|
217
|
+
const call = (index, current) => {
|
|
218
|
+
const link = links[index];
|
|
219
|
+
return link ? link(current, (next) => call(index + 1, next)) : terminal(current);
|
|
220
|
+
};
|
|
221
|
+
return call(0, op);
|
|
436
222
|
}
|
|
437
223
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* task — the boundary was reset — starts a new request.
|
|
224
|
+
* Run the entry's request, or join the one in flight. Resolves with how it
|
|
225
|
+
* ended, never rejects: a removed entry's request ends with an `AbortError`.
|
|
226
|
+
* Whoever waited on a restarted request gets the new one's outcome.
|
|
442
227
|
*/
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
228
|
+
function fetchEntry(entry, request = {}) {
|
|
229
|
+
const current = entry.run;
|
|
230
|
+
if (current && !request.restart && request.direction === void 0) return current.done.promise;
|
|
231
|
+
const options = entry.options;
|
|
232
|
+
if (!options) return Promise.resolve({ ok: true });
|
|
233
|
+
current?.controller.abort();
|
|
234
|
+
const client = entry.client;
|
|
235
|
+
const run = {
|
|
236
|
+
controller: new AbortController(),
|
|
237
|
+
done: current && !current.settled ? current.done : Promise.withResolvers(),
|
|
238
|
+
settled: false,
|
|
239
|
+
startedAt: ++client["~seq"]
|
|
240
|
+
};
|
|
241
|
+
entry.run = run;
|
|
242
|
+
if (!entry.state.hasData && !entry.pendingThenable) newPending(entry);
|
|
243
|
+
setState(entry, {
|
|
244
|
+
fetchingDirection: request.direction,
|
|
245
|
+
isFetching: true
|
|
246
|
+
});
|
|
247
|
+
const state = entry.state;
|
|
248
|
+
chain({
|
|
249
|
+
client,
|
|
250
|
+
data: state.data,
|
|
251
|
+
direction: request.direction,
|
|
252
|
+
hasData: state.hasData,
|
|
253
|
+
hash: entry.hash,
|
|
254
|
+
input: entry.input,
|
|
255
|
+
meta: options.meta ?? {},
|
|
256
|
+
options,
|
|
257
|
+
pages: request.pages,
|
|
258
|
+
queryKey: entry.key,
|
|
259
|
+
signal: run.controller.signal
|
|
260
|
+
}).then((result) => {
|
|
261
|
+
if (entry.run !== run) return;
|
|
262
|
+
if (options.stream && isAsyncIterable(result)) {
|
|
263
|
+
pump(entry, run, result, options.stream);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
write(entry, result, client.clock.now(), run.startedAt);
|
|
267
|
+
finish(entry, run, { ok: true });
|
|
268
|
+
}, (error) => {
|
|
269
|
+
if (entry.run !== run) return;
|
|
270
|
+
failWith(entry, error);
|
|
271
|
+
finish(entry, run, {
|
|
272
|
+
error,
|
|
273
|
+
ok: false
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
return run.done.promise;
|
|
277
|
+
}
|
|
278
|
+
/** Take over a request started elsewhere (hydration of a pending entry). */
|
|
279
|
+
function adopt(entry, promise) {
|
|
280
|
+
const run = {
|
|
281
|
+
controller: new AbortController(),
|
|
282
|
+
done: Promise.withResolvers(),
|
|
283
|
+
settled: false,
|
|
284
|
+
startedAt: entry.client["~seq"]
|
|
285
|
+
};
|
|
286
|
+
entry.run = run;
|
|
287
|
+
if (!entry.state.hasData && !entry.pendingThenable) newPending(entry);
|
|
288
|
+
setState(entry, { isFetching: true });
|
|
289
|
+
Promise.resolve(promise).then((sent) => {
|
|
290
|
+
if (entry.run === run) {
|
|
291
|
+
write(entry, sent.data, sent.updatedAt, run.startedAt);
|
|
292
|
+
finish(entry, run, { ok: true });
|
|
456
293
|
}
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
get signal() {
|
|
465
|
-
return readSignal();
|
|
294
|
+
}, (error) => {
|
|
295
|
+
if (entry.run === run) {
|
|
296
|
+
failWith(entry, error);
|
|
297
|
+
finish(entry, run, {
|
|
298
|
+
error,
|
|
299
|
+
ok: false
|
|
300
|
+
});
|
|
466
301
|
}
|
|
467
|
-
})
|
|
302
|
+
});
|
|
468
303
|
}
|
|
469
|
-
/**
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
304
|
+
/** Subscribe to an entry's state. Subscribers are its readers: an entry with none can be gc'd. */
|
|
305
|
+
function subscribeEntry(entry, listener, options) {
|
|
306
|
+
return entry.readers.subscribe(listener, options);
|
|
307
|
+
}
|
|
308
|
+
/** Drop the entry, aborting its request. Waiters get an `AbortError`. */
|
|
309
|
+
function remove(entry) {
|
|
310
|
+
const client = entry.client;
|
|
311
|
+
if (client["~entries"].get(entry.hash) !== entry) return;
|
|
312
|
+
client["~entries"].delete(entry.hash);
|
|
313
|
+
client.clock.clearTimeout(entry.gcTimer);
|
|
314
|
+
const run = entry.run;
|
|
315
|
+
entry.run = void 0;
|
|
316
|
+
if (run) {
|
|
317
|
+
run.controller.abort();
|
|
318
|
+
run.done.resolve({
|
|
319
|
+
error: abortError(),
|
|
320
|
+
ok: false
|
|
480
321
|
});
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const schedule = retrySchedule({
|
|
486
|
-
retry: behavior(entry, "retry"),
|
|
487
|
-
retryDelay: behavior(entry, "retryDelay")
|
|
488
|
-
}, entry.cache.isServer);
|
|
489
|
-
if (!schedule) return once;
|
|
490
|
-
const online = entry.cache.isServer ? Effect.void : whenOnline;
|
|
491
|
-
return Effect.suspend(() => {
|
|
492
|
-
let retrying = false;
|
|
493
|
-
let lastError;
|
|
494
|
-
const unlessStopped = (next) => Effect.suspend(() => entry.retriesStopped ? Effect.fail(lastError) : next);
|
|
495
|
-
const gated = Effect.suspend(() => {
|
|
496
|
-
if (!retrying) {
|
|
497
|
-
retrying = true;
|
|
498
|
-
return once;
|
|
499
|
-
}
|
|
500
|
-
return unlessStopped(Effect.andThen(online, unlessStopped(once)));
|
|
501
|
-
}).pipe(Effect.tapError((error) => Effect.sync(() => {
|
|
502
|
-
lastError = error;
|
|
503
|
-
})));
|
|
504
|
-
return Effect.retry(gated, Schedule.while(schedule, () => !entry.retriesStopped));
|
|
322
|
+
}
|
|
323
|
+
client["~events"].emit({
|
|
324
|
+
entry,
|
|
325
|
+
type: "removed"
|
|
505
326
|
});
|
|
506
327
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
328
|
+
/** Drop data and errors. Read entries (or ones loading) refetch; resolves when they settle. */
|
|
329
|
+
function reset(entry) {
|
|
330
|
+
entry.state = initialState;
|
|
331
|
+
newPending(entry);
|
|
332
|
+
notify(entry);
|
|
333
|
+
if (entry.readers.size > 0 || entry.run) return fetchEntry(entry, { restart: true });
|
|
334
|
+
return Promise.resolve({ ok: true });
|
|
514
335
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Record an invalidation: one stamp per target, all at one point in the
|
|
338
|
+
* sequence. Entries they cover that have readers refetch now, once however
|
|
339
|
+
* many stamps cover them; the rest find out when they are next read.
|
|
340
|
+
* Resolves when the refetches settle.
|
|
341
|
+
*/
|
|
342
|
+
function invalidate(client, makeStamps) {
|
|
343
|
+
const stamps = makeStamps(++client["~seq"]);
|
|
344
|
+
const entries = [...client["~entries"].values()];
|
|
345
|
+
const oldest = Math.min(...entries.map((entry) => entry.requestedAt));
|
|
346
|
+
client["~stamps"] = [...client["~stamps"].filter((kept) => kept.at > oldest), ...stamps];
|
|
347
|
+
const refetches = [];
|
|
348
|
+
for (const entry of entries) if (stamps.some((stamp) => matches(entry, stamp))) {
|
|
349
|
+
notify(entry);
|
|
350
|
+
if (entry.readers.size > 0) refetches.push(fetchEntry(entry, { restart: true }));
|
|
521
351
|
}
|
|
522
|
-
|
|
523
|
-
entry.tasks.fork("stale", Effect.sleep(Math.max(remaining, 0) + 1).pipe(Effect.andThen(Effect.sync(() => set(entry, {})))));
|
|
352
|
+
return Promise.all(refetches).then(() => {});
|
|
524
353
|
}
|
|
525
354
|
//#endregion
|
|
526
|
-
export {
|
|
355
|
+
export { abortError, adopt, entryForKey, entryOf, fetchEntry, invalidate, isInvalidated, isStale, remove, reset, subscribeEntry, write };
|