@raf-query/core 0.11.1 → 0.13.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 +39 -1
- package/README.md +28 -18
- package/dist/client.d.mts +163 -119
- package/dist/client.mjs +90 -162
- package/dist/entry.d.mts +73 -147
- package/dist/entry.mjs +314 -471
- package/dist/features/focus-refetch/focus-refetch.d.mts +17 -0
- package/dist/features/focus-refetch/focus-refetch.mjs +20 -0
- package/dist/features/polling/polling.d.mts +16 -0
- package/dist/features/polling/polling.mjs +39 -0
- package/dist/features/reconnect-refetch/reconnect-refetch.d.mts +17 -0
- package/dist/features/reconnect-refetch/reconnect-refetch.mjs +16 -0
- package/dist/features/shared.mjs +13 -0
- package/dist/guards.mjs +23 -0
- package/dist/hydration/hydration.d.mts +108 -0
- package/dist/hydration/hydration.mjs +142 -0
- package/dist/index.d.mts +19 -13
- package/dist/index.mjs +16 -13
- package/dist/infinite/infinite.d.mts +83 -0
- package/dist/infinite/infinite.mjs +120 -0
- package/dist/infinite/paging.d.mts +71 -0
- package/dist/infinite/paging.mjs +62 -0
- package/dist/internal.d.mts +13 -0
- package/dist/internal.mjs +7 -0
- package/dist/keys/keys.d.mts +54 -0
- package/dist/keys/keys.mjs +53 -0
- package/dist/keys/targets.d.mts +62 -0
- package/dist/keys/targets.mjs +85 -0
- package/dist/links/retry/retry.d.mts +33 -0
- package/dist/links/retry/retry.mjs +53 -0
- package/dist/links/timeout/timeout.d.mts +17 -0
- package/dist/links/timeout/timeout.mjs +35 -0
- package/dist/load.d.mts +149 -0
- package/dist/load.mjs +179 -0
- package/dist/options.d.mts +161 -332
- package/dist/options.mjs +28 -40
- package/dist/presets.d.mts +35 -0
- package/dist/presets.mjs +27 -0
- package/dist/root.d.mts +86 -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 +15 -17
- package/dist/thenable.mjs +25 -6
- package/dist/verbs.d.mts +150 -0
- package/dist/verbs.mjs +115 -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 -95
- package/dist/controls.mjs +0 -70
- 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,369 @@
|
|
|
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
|
};
|
|
18
|
+
const fiveMinutes = 3e5;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Make the `AbortError` (a `DOMException`) that a `load` waiting on a removed query rejects with.
|
|
21
|
+
*
|
|
22
|
+
* @see https://raf-query.katt.dev/docs/query-cancellation#when-requests-are-aborted
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
function abortError() {
|
|
25
|
+
return new DOMException("The query was cancelled", "AbortError");
|
|
26
|
+
}
|
|
27
|
+
function notify(entry) {
|
|
28
|
+
const client = entry.client;
|
|
29
|
+
const changed = client["~changed"];
|
|
30
|
+
if (changed.size === 0) queueMicrotask(() => {
|
|
31
|
+
const batch = [...changed];
|
|
32
|
+
changed.clear();
|
|
33
|
+
for (const item of batch) {
|
|
34
|
+
item.readers.emit(item.state);
|
|
35
|
+
client["~events"].emit({
|
|
36
|
+
entry: item,
|
|
37
|
+
type: "updated"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
changed.add(entry);
|
|
38
42
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
43
|
+
function setState(entry, patch) {
|
|
44
|
+
entry.state = {
|
|
45
|
+
...entry.state,
|
|
46
|
+
...patch
|
|
47
|
+
};
|
|
48
|
+
notify(entry);
|
|
49
|
+
}
|
|
50
|
+
function newPending(entry) {
|
|
51
|
+
const next = pending();
|
|
52
|
+
entry.pendingThenable = next;
|
|
53
|
+
entry.thenable = next.promise;
|
|
41
54
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
55
|
+
function gcTime(entry) {
|
|
56
|
+
return entry.options?.gcTime ?? entry.client.config.gcTime ?? fiveMinutes;
|
|
44
57
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
run(task, effect, options);
|
|
58
|
+
function scheduleGc(entry) {
|
|
59
|
+
const client = entry.client;
|
|
60
|
+
client.clock.clearTimeout(entry.gcTimer);
|
|
61
|
+
entry.gcTimer = void 0;
|
|
62
|
+
const time = gcTime(entry);
|
|
63
|
+
if (client.isServer || entry.readers.size > 0 || !Number.isFinite(time)) return;
|
|
64
|
+
entry.gcTimer = client.clock.setTimeout(() => {
|
|
65
|
+
entry.gcTimer = void 0;
|
|
66
|
+
if (entry.run) {
|
|
67
|
+
entry.run.done.promise.then(() => scheduleGc(entry));
|
|
68
|
+
return;
|
|
57
69
|
}
|
|
58
|
-
|
|
70
|
+
remove(entry);
|
|
71
|
+
}, time);
|
|
59
72
|
}
|
|
60
|
-
function
|
|
61
|
-
const scope = Scope.forkUnsafe(cache.scope);
|
|
73
|
+
function create(client, key, hash) {
|
|
62
74
|
const first = pending();
|
|
63
|
-
const entry =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
handle: {
|
|
68
|
-
input: void 0,
|
|
69
|
-
key
|
|
70
|
-
},
|
|
75
|
+
const entry = {
|
|
76
|
+
client,
|
|
77
|
+
factory: void 0,
|
|
78
|
+
gcTimer: void 0,
|
|
71
79
|
hash,
|
|
72
|
-
|
|
73
|
-
inFlight: void 0,
|
|
80
|
+
input: void 0,
|
|
74
81
|
key,
|
|
75
|
-
listeners: /* @__PURE__ */ new Map(),
|
|
76
82
|
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()
|
|
83
|
+
pendingThenable: first,
|
|
84
|
+
readers: listeners((size) => {
|
|
85
|
+
client["~events"].emit({
|
|
86
|
+
entry,
|
|
87
|
+
type: "readers"
|
|
88
|
+
});
|
|
89
|
+
if (size === 0) scheduleGc(entry);
|
|
90
|
+
else {
|
|
91
|
+
client.clock.clearTimeout(entry.gcTimer);
|
|
92
|
+
entry.gcTimer = void 0;
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
requestedAt: client["~seq"],
|
|
96
|
+
run: void 0,
|
|
97
|
+
state: initialState,
|
|
98
|
+
thenable: first.promise
|
|
103
99
|
};
|
|
104
|
-
|
|
100
|
+
client["~entries"].set(hash, entry);
|
|
101
|
+
client["~events"].emit({
|
|
102
|
+
entry,
|
|
103
|
+
type: "added"
|
|
104
|
+
});
|
|
105
105
|
scheduleGc(entry);
|
|
106
106
|
return entry;
|
|
107
107
|
}
|
|
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
|
-
});
|
|
108
|
+
/** The entry for a key, created if missing. Hydration uses it. */
|
|
109
|
+
function entryForKey(client, key) {
|
|
110
|
+
const hash = hashKey(key);
|
|
111
|
+
return client["~entries"].get(hash) ?? create(client, key, hash);
|
|
172
112
|
}
|
|
173
113
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
114
|
+
* The entry for a factory's input, created if missing, with the factory's
|
|
115
|
+
* current options. Every read goes through here, which links an entry that
|
|
116
|
+
* was only hydrated to its factory.
|
|
177
117
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
118
|
+
function entryOf(client, factory, input) {
|
|
119
|
+
const options = optionsOf(factory, input);
|
|
120
|
+
const entry = entryForKey(client, options.queryKey);
|
|
121
|
+
entry.factory = factory;
|
|
122
|
+
entry.input = input;
|
|
183
123
|
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;
|
|
124
|
+
scheduleGc(entry);
|
|
125
|
+
return entry;
|
|
200
126
|
}
|
|
201
|
-
/**
|
|
202
|
-
function
|
|
203
|
-
|
|
127
|
+
/** Whether an invalidation newer than the entry's data covers it. */
|
|
128
|
+
function isInvalidated(entry) {
|
|
129
|
+
return entry.client["~stamps"].some((stamp) => stamp.at > entry.requestedAt && matches(entry, stamp));
|
|
204
130
|
}
|
|
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);
|
|
131
|
+
/** Whether the entry is a browser-only query (`ssr: false`) on a server client: nothing starts it there. */
|
|
132
|
+
function isBrowserOnly(entry) {
|
|
133
|
+
return entry.client.isServer && entry.options?.ssr === false;
|
|
212
134
|
}
|
|
213
|
-
/**
|
|
214
|
-
function
|
|
135
|
+
/** Whether the entry's data should be refetched before it's trusted. */
|
|
136
|
+
function isStale(entry) {
|
|
215
137
|
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
|
-
});
|
|
224
|
-
}
|
|
225
|
-
function invalidate(entry) {
|
|
226
|
-
return Effect.suspend(() => {
|
|
227
|
-
set(entry, { isInvalidated: true });
|
|
228
|
-
if (!isObserved(entry) || !entry.options || isDisabled(entry)) return Effect.void;
|
|
229
|
-
return Effect.ignore(begin(entry, { restart: true }));
|
|
230
|
-
});
|
|
138
|
+
if (!state.hasData) return true;
|
|
139
|
+
const staleTime = entry.options?.staleTime ?? entry.client.config.staleTime ?? 0;
|
|
140
|
+
return entry.client.clock.now() - state.updatedAt >= staleTime || isInvalidated(entry);
|
|
231
141
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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"
|
|
142
|
+
/** Write data. `startedAt` orders it against invalidations. */
|
|
143
|
+
function write(entry, data, updatedAt, startedAt) {
|
|
144
|
+
const previous = entry.state;
|
|
145
|
+
const next = (entry.options?.structuralSharing ?? entry.client.config.structuralSharing ?? true) && previous.hasData ? replaceEqualDeep(previous.data, data) : data;
|
|
146
|
+
entry.requestedAt = Math.max(entry.requestedAt, startedAt);
|
|
147
|
+
if (entry.pendingThenable) {
|
|
148
|
+
entry.pendingThenable.resolve(next);
|
|
149
|
+
entry.pendingThenable = void 0;
|
|
150
|
+
} else if (!previous.hasData || next !== previous.data) entry.thenable = fulfilled(next);
|
|
151
|
+
setState(entry, {
|
|
152
|
+
data: next,
|
|
153
|
+
error: void 0,
|
|
154
|
+
hasData: true,
|
|
155
|
+
status: "success",
|
|
156
|
+
updatedAt
|
|
286
157
|
});
|
|
158
|
+
scheduleGc(entry);
|
|
287
159
|
}
|
|
288
160
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
161
|
+
* Record a failed request. Data the entry had stays readable. Without data,
|
|
162
|
+
* the request in flight has a pending thenable for readers: it rejects.
|
|
291
163
|
*/
|
|
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);
|
|
164
|
+
function failWith(entry, error) {
|
|
165
|
+
if (!entry.state.hasData) {
|
|
166
|
+
entry.pendingThenable?.reject(error);
|
|
167
|
+
entry.pendingThenable = void 0;
|
|
309
168
|
}
|
|
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"
|
|
169
|
+
setState(entry, {
|
|
170
|
+
error,
|
|
171
|
+
status: "error"
|
|
326
172
|
});
|
|
327
|
-
scheduleStale(entry);
|
|
328
173
|
}
|
|
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
|
|
174
|
+
function finish(entry, run, outcome) {
|
|
175
|
+
entry.run = void 0;
|
|
176
|
+
setState(entry, {
|
|
177
|
+
fetchingDirection: void 0,
|
|
178
|
+
isFetching: false
|
|
351
179
|
});
|
|
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
|
-
}
|
|
180
|
+
run.done.resolve(outcome);
|
|
371
181
|
scheduleGc(entry);
|
|
372
182
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
183
|
+
function isAsyncIterable(value) {
|
|
184
|
+
return isObject(value) && Symbol.asyncIterator in value;
|
|
185
|
+
}
|
|
186
|
+
async function pump(entry, run, chunks, stream) {
|
|
187
|
+
let data = stream.initial;
|
|
188
|
+
try {
|
|
189
|
+
for await (const chunk of chunks) {
|
|
190
|
+
if (entry.run !== run) return;
|
|
191
|
+
data = stream.reduce(data, chunk);
|
|
192
|
+
write(entry, data, entry.client.clock.now(), run.startedAt);
|
|
193
|
+
if (!run.settled) {
|
|
194
|
+
run.settled = true;
|
|
195
|
+
run.done.resolve({ ok: true });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (entry.run === run) {
|
|
200
|
+
failWith(entry, error);
|
|
201
|
+
finish(entry, run, {
|
|
202
|
+
error,
|
|
203
|
+
ok: false
|
|
204
|
+
});
|
|
205
|
+
}
|
|
384
206
|
return;
|
|
385
207
|
}
|
|
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);
|
|
208
|
+
if (entry.run === run) {
|
|
209
|
+
if (!run.settled) write(entry, data, entry.client.clock.now(), run.startedAt);
|
|
210
|
+
finish(entry, run, { ok: true });
|
|
211
|
+
}
|
|
395
212
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
|
213
|
+
function terminal(op) {
|
|
214
|
+
return new Promise((resolve) => {
|
|
215
|
+
const options = op.options;
|
|
216
|
+
const paged = options["~run"];
|
|
217
|
+
resolve(paged ? paged(op) : options.queryFn({
|
|
218
|
+
client: op.client,
|
|
219
|
+
meta: op.meta,
|
|
220
|
+
queryKey: op.queryKey,
|
|
221
|
+
signal: op.signal
|
|
222
|
+
}));
|
|
415
223
|
});
|
|
416
|
-
return data;
|
|
417
224
|
}
|
|
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
|
-
});
|
|
225
|
+
function chain(op) {
|
|
226
|
+
const links = op.client.config.links ?? [];
|
|
227
|
+
const call = (index, current) => {
|
|
228
|
+
const link = links[index];
|
|
229
|
+
return link ? link(current, (next) => call(index + 1, next)) : terminal(current);
|
|
230
|
+
};
|
|
231
|
+
return call(0, op);
|
|
436
232
|
}
|
|
437
233
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* task — the boundary was reset — starts a new request.
|
|
234
|
+
* Run the entry's request, or join the one in flight. Resolves with how it
|
|
235
|
+
* ended, never rejects: a removed entry's request ends with an `AbortError`.
|
|
236
|
+
* Whoever waited on a restarted request gets the new one's outcome.
|
|
442
237
|
*/
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
238
|
+
function fetchEntry(entry, request = {}) {
|
|
239
|
+
const current = entry.run;
|
|
240
|
+
if (current && !request.restart && request.direction === void 0) return current.done.promise;
|
|
241
|
+
const options = entry.options;
|
|
242
|
+
if (!options || isBrowserOnly(entry)) return Promise.resolve({ ok: true });
|
|
243
|
+
current?.controller.abort();
|
|
244
|
+
const client = entry.client;
|
|
245
|
+
const run = {
|
|
246
|
+
controller: new AbortController(),
|
|
247
|
+
done: current && !current.settled ? current.done : Promise.withResolvers(),
|
|
248
|
+
settled: false,
|
|
249
|
+
startedAt: ++client["~seq"]
|
|
250
|
+
};
|
|
251
|
+
entry.run = run;
|
|
252
|
+
if (!entry.state.hasData && !entry.pendingThenable) newPending(entry);
|
|
253
|
+
setState(entry, {
|
|
254
|
+
fetchingDirection: request.direction,
|
|
255
|
+
isFetching: true
|
|
256
|
+
});
|
|
257
|
+
const state = entry.state;
|
|
258
|
+
chain({
|
|
259
|
+
client,
|
|
260
|
+
data: state.data,
|
|
261
|
+
direction: request.direction,
|
|
262
|
+
hasData: state.hasData,
|
|
263
|
+
hash: entry.hash,
|
|
264
|
+
input: entry.input,
|
|
265
|
+
meta: options.meta ?? {},
|
|
266
|
+
options,
|
|
267
|
+
pages: request.pages,
|
|
268
|
+
queryKey: entry.key,
|
|
269
|
+
signal: run.controller.signal
|
|
270
|
+
}).then((result) => {
|
|
271
|
+
if (entry.run !== run) return;
|
|
272
|
+
if (options.stream && isAsyncIterable(result)) {
|
|
273
|
+
pump(entry, run, result, options.stream);
|
|
274
|
+
return;
|
|
456
275
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
direction,
|
|
461
|
-
meta,
|
|
462
|
-
pageParam,
|
|
463
|
-
queryKey,
|
|
464
|
-
get signal() {
|
|
465
|
-
return readSignal();
|
|
276
|
+
if (!options["~run"] && entry.requestedAt > run.startedAt) {
|
|
277
|
+
finish(entry, run, { ok: true });
|
|
278
|
+
return;
|
|
466
279
|
}
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
function attempt(entry, call) {
|
|
476
|
-
const once = fromQueryFn((signal) => {
|
|
477
|
-
const result = call(() => {
|
|
478
|
-
entry.signalRead = true;
|
|
479
|
-
return signal;
|
|
280
|
+
write(entry, result, client.clock.now(), run.startedAt);
|
|
281
|
+
finish(entry, run, { ok: true });
|
|
282
|
+
}, (error) => {
|
|
283
|
+
if (entry.run !== run) return;
|
|
284
|
+
failWith(entry, error);
|
|
285
|
+
finish(entry, run, {
|
|
286
|
+
error,
|
|
287
|
+
ok: false
|
|
480
288
|
});
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
289
|
+
});
|
|
290
|
+
return run.done.promise;
|
|
291
|
+
}
|
|
292
|
+
/** Take over a request started elsewhere (hydration of a pending entry). */
|
|
293
|
+
function adopt(entry, promise) {
|
|
294
|
+
const run = {
|
|
295
|
+
controller: new AbortController(),
|
|
296
|
+
done: Promise.withResolvers(),
|
|
297
|
+
settled: false,
|
|
298
|
+
startedAt: entry.client["~seq"]
|
|
299
|
+
};
|
|
300
|
+
entry.run = run;
|
|
301
|
+
if (!entry.state.hasData && !entry.pendingThenable) newPending(entry);
|
|
302
|
+
setState(entry, { isFetching: true });
|
|
303
|
+
Promise.resolve(promise).then((sent) => {
|
|
304
|
+
if (entry.run === run) {
|
|
305
|
+
write(entry, sent.data, sent.updatedAt, run.startedAt);
|
|
306
|
+
finish(entry, run, { ok: true });
|
|
307
|
+
}
|
|
308
|
+
}, (error) => {
|
|
309
|
+
if (entry.run === run) {
|
|
310
|
+
failWith(entry, error);
|
|
311
|
+
finish(entry, run, {
|
|
312
|
+
error,
|
|
313
|
+
ok: false
|
|
314
|
+
});
|
|
315
|
+
}
|
|
505
316
|
});
|
|
506
317
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
318
|
+
/** Subscribe to an entry's state. Subscribers are its readers: an entry with none can be gc'd. */
|
|
319
|
+
function subscribeEntry(entry, listener, options) {
|
|
320
|
+
return entry.readers.subscribe(listener, options);
|
|
321
|
+
}
|
|
322
|
+
/** Drop the entry, aborting its request. Waiters get an `AbortError`. */
|
|
323
|
+
function remove(entry) {
|
|
324
|
+
const client = entry.client;
|
|
325
|
+
if (client["~entries"].get(entry.hash) !== entry) return;
|
|
326
|
+
client["~entries"].delete(entry.hash);
|
|
327
|
+
client.clock.clearTimeout(entry.gcTimer);
|
|
328
|
+
const run = entry.run;
|
|
329
|
+
entry.run = void 0;
|
|
330
|
+
if (run) {
|
|
331
|
+
run.controller.abort();
|
|
332
|
+
run.done.resolve({
|
|
333
|
+
error: abortError(),
|
|
334
|
+
ok: false
|
|
335
|
+
});
|
|
512
336
|
}
|
|
513
|
-
|
|
337
|
+
client["~events"].emit({
|
|
338
|
+
entry,
|
|
339
|
+
type: "removed"
|
|
340
|
+
});
|
|
514
341
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
342
|
+
/** Drop data and errors. Read entries (or ones loading) refetch; resolves when they settle. */
|
|
343
|
+
function reset(entry) {
|
|
344
|
+
entry.state = initialState;
|
|
345
|
+
newPending(entry);
|
|
346
|
+
notify(entry);
|
|
347
|
+
if (entry.readers.size > 0 || entry.run) return fetchEntry(entry, { restart: true });
|
|
348
|
+
return Promise.resolve({ ok: true });
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Record an invalidation: one stamp per target, all at one point in the
|
|
352
|
+
* sequence. Entries they cover that have readers refetch now, once however
|
|
353
|
+
* many stamps cover them; the rest find out when they are next read.
|
|
354
|
+
* Resolves when the refetches settle.
|
|
355
|
+
*/
|
|
356
|
+
function invalidate(client, makeStamps) {
|
|
357
|
+
const stamps = makeStamps(++client["~seq"]);
|
|
358
|
+
const entries = [...client["~entries"].values()];
|
|
359
|
+
const oldest = Math.min(...entries.map((entry) => entry.requestedAt));
|
|
360
|
+
client["~stamps"] = [...client["~stamps"].filter((kept) => kept.at > oldest), ...stamps];
|
|
361
|
+
const refetches = [];
|
|
362
|
+
for (const entry of entries) if (stamps.some((stamp) => matches(entry, stamp))) {
|
|
363
|
+
notify(entry);
|
|
364
|
+
if (entry.readers.size > 0) refetches.push(fetchEntry(entry, { restart: true }));
|
|
521
365
|
}
|
|
522
|
-
|
|
523
|
-
entry.tasks.fork("stale", Effect.sleep(Math.max(remaining, 0) + 1).pipe(Effect.andThen(Effect.sync(() => set(entry, {})))));
|
|
366
|
+
return Promise.all(refetches).then(() => {});
|
|
524
367
|
}
|
|
525
368
|
//#endregion
|
|
526
|
-
export {
|
|
369
|
+
export { abortError, adopt, entryForKey, entryOf, fetchEntry, invalidate, isBrowserOnly, isInvalidated, isStale, remove, reset, subscribeEntry, write };
|