@raf-query/core 0.0.18 → 0.1.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 +22 -1
- package/README.md +101 -32
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/cache.d.mts +140 -0
- package/dist/cache.mjs +176 -0
- package/dist/entry.d.mts +113 -0
- package/dist/entry.mjs +421 -0
- package/dist/hydration.d.mts +37 -13
- package/dist/hydration.mjs +78 -17
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +6 -6
- package/dist/infinite.d.mts +5 -0
- package/dist/infinite.mjs +59 -0
- package/dist/listen.mjs +19 -0
- package/dist/observer.d.mts +59 -40
- package/dist/observer.mjs +215 -133
- package/dist/options.d.mts +50 -14
- package/dist/options.mjs +3 -0
- package/dist/platform.d.mts +27 -0
- package/dist/platform.mjs +44 -0
- package/dist/query.d.mts +80 -51
- package/dist/query.mjs +111 -421
- package/dist/request.mjs +47 -0
- package/dist/retry.mjs +25 -0
- package/dist/thenable.d.mts +29 -0
- package/dist/thenable.mjs +3 -3
- package/package.json +1 -1
- package/dist/all-keyed.d.mts +0 -18
- package/dist/all-keyed.mjs +0 -19
- package/dist/client.d.mts +0 -123
- package/dist/client.mjs +0 -266
- package/dist/online.mjs +0 -16
package/dist/query.mjs
CHANGED
|
@@ -1,429 +1,119 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { unsafeCast } from "./cast.mjs";
|
|
3
|
+
import { CancelledError as CancelledError$1, TypeId as TypeId$1, changes as changes$1, entryOf, fetch as fetch$1, hasNextPageUnsafe as hasNextPageUnsafe$1, hasPreviousPageUnsafe as hasPreviousPageUnsafe$1, invalidate as invalidate$1, isQuery as isQuery$1, isStaleUnsafe as isStaleUnsafe$1, provide, readUnsafe as readUnsafe$1, reset as reset$1, subscribeUnsafe as subscribeUnsafe$1 } from "./entry.mjs";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { dual } from "effect/Function";
|
|
6
6
|
//#region src/query.ts
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
/**
|
|
8
|
+
* One cache entry, as a data type: a `Query` carries its key, handle and
|
|
9
|
+
* options, and the functions here read it, subscribe to it and run its
|
|
10
|
+
* requests. Get one from `QueryCache.getQuery(factory, input)`.
|
|
11
|
+
*
|
|
12
|
+
* Requests are Effects. The `*Unsafe` functions read and subscribe
|
|
13
|
+
* synchronously, for renders and external stores that can't run an Effect.
|
|
14
|
+
*/
|
|
15
|
+
var query_exports = /* @__PURE__ */ __exportAll({
|
|
16
|
+
CancelledError: () => CancelledError,
|
|
17
|
+
TypeId: () => TypeId,
|
|
18
|
+
changes: () => changes,
|
|
19
|
+
fetch: () => fetch,
|
|
20
|
+
fetchNextPage: () => fetchNextPage,
|
|
21
|
+
fetchPreviousPage: () => fetchPreviousPage,
|
|
22
|
+
get: () => get,
|
|
23
|
+
getUnsafe: () => getUnsafe,
|
|
24
|
+
hasNextPageUnsafe: () => hasNextPageUnsafe,
|
|
25
|
+
hasPreviousPageUnsafe: () => hasPreviousPageUnsafe,
|
|
26
|
+
invalidate: () => invalidate,
|
|
27
|
+
isQuery: () => isQuery,
|
|
28
|
+
isStaleUnsafe: () => isStaleUnsafe,
|
|
29
|
+
observerCountUnsafe: () => observerCountUnsafe,
|
|
30
|
+
readUnsafe: () => readUnsafe,
|
|
31
|
+
refetch: () => refetch,
|
|
32
|
+
reset: () => reset,
|
|
33
|
+
subscribeUnsafe: () => subscribeUnsafe
|
|
34
|
+
});
|
|
35
|
+
const TypeId = TypeId$1;
|
|
36
|
+
const CancelledError = CancelledError$1;
|
|
37
|
+
const isQuery = isQuery$1;
|
|
38
|
+
function typed(effect) {
|
|
39
|
+
return unsafeCast(effect);
|
|
19
40
|
}
|
|
20
|
-
function
|
|
21
|
-
return
|
|
41
|
+
function typedState(state) {
|
|
42
|
+
return unsafeCast(state);
|
|
22
43
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Start a request, or join the one in flight. Fails with the request's
|
|
46
|
+
* error, or a {@link CancelledError} if the entry leaves the cache first.
|
|
47
|
+
* The entry's requests run with this fiber's services from now on, as after
|
|
48
|
+
* a `QueryCache` verb.
|
|
49
|
+
*/
|
|
50
|
+
const fetch = dual((args) => isQuery(args[0]), (self, request) => typed(Effect.contextWith((caller) => {
|
|
51
|
+
const entry = entryOf(self);
|
|
52
|
+
provide(entry, caller);
|
|
53
|
+
return fetch$1(entry, request);
|
|
54
|
+
})));
|
|
55
|
+
/** Refetch now; succeeds when the request settles, whatever its outcome. */
|
|
56
|
+
function refetch(self) {
|
|
57
|
+
return entryOf(self).commands.refetch;
|
|
27
58
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
59
|
+
/** Load the page after the last one; succeeds once it settles. */
|
|
60
|
+
function fetchNextPage(self) {
|
|
61
|
+
return entryOf(self).commands.fetchNextPage;
|
|
31
62
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
63
|
+
/** Load the page before the first one; succeeds once it settles. */
|
|
64
|
+
function fetchPreviousPage(self) {
|
|
65
|
+
return entryOf(self).commands.fetchPreviousPage;
|
|
35
66
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
/** Mark stale, and refetch if read; succeeds once that refetch settles. */
|
|
68
|
+
function invalidate(self) {
|
|
69
|
+
return invalidate$1(entryOf(self));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Drop data and errors, back to the state of a new entry. Readers suspend
|
|
73
|
+
* and refetch; succeeds when that refetch settles.
|
|
74
|
+
*/
|
|
75
|
+
function reset(self) {
|
|
76
|
+
return reset$1(entryOf(self));
|
|
77
|
+
}
|
|
78
|
+
/** The entry's current state. */
|
|
79
|
+
function get(self) {
|
|
80
|
+
return Effect.sync(() => getUnsafe(self));
|
|
81
|
+
}
|
|
82
|
+
/** {@link get}, synchronously. */
|
|
83
|
+
function getUnsafe(self) {
|
|
84
|
+
return typedState(entryOf(self).state);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The entry's current state, then every change. Counts as a reader while
|
|
88
|
+
* the stream runs, so invalidation and focus refetch it.
|
|
89
|
+
*/
|
|
90
|
+
function changes(self) {
|
|
91
|
+
return unsafeCast(changes$1(entryOf(self)));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The promise a render should unwrap. Starts the first request if nothing
|
|
95
|
+
* has, and retries a failed first load once its error has been shown.
|
|
96
|
+
*/
|
|
97
|
+
function readUnsafe(self) {
|
|
98
|
+
return unsafeCast(readUnsafe$1(entryOf(self)));
|
|
99
|
+
}
|
|
100
|
+
/** Listen for state changes. Subscribed entries count as read. */
|
|
101
|
+
function subscribeUnsafe(self, listener, options) {
|
|
102
|
+
return subscribeUnsafe$1(entryOf(self), listener, options);
|
|
103
|
+
}
|
|
104
|
+
/** Staleness as of now; `state.isStale` is only kept current while read. */
|
|
105
|
+
function isStaleUnsafe(self) {
|
|
106
|
+
return isStaleUnsafe$1(entryOf(self));
|
|
107
|
+
}
|
|
108
|
+
function hasNextPageUnsafe(self) {
|
|
109
|
+
return hasNextPageUnsafe$1(entryOf(self));
|
|
110
|
+
}
|
|
111
|
+
function hasPreviousPageUnsafe(self) {
|
|
112
|
+
return hasPreviousPageUnsafe$1(entryOf(self));
|
|
113
|
+
}
|
|
114
|
+
/** Readers currently subscribed. */
|
|
115
|
+
function observerCountUnsafe(self) {
|
|
116
|
+
return entryOf(self).listeners.size;
|
|
51
117
|
}
|
|
52
|
-
var QueryEntry = class {
|
|
53
|
-
hash;
|
|
54
|
-
key;
|
|
55
|
-
#errorShown = false;
|
|
56
|
-
#errorShownTimer;
|
|
57
|
-
#gcTimer;
|
|
58
|
-
#handle;
|
|
59
|
-
#host;
|
|
60
|
-
#inFlight;
|
|
61
|
-
#listeners = /* @__PURE__ */ new Map();
|
|
62
|
-
#options;
|
|
63
|
-
#pending;
|
|
64
|
-
#staleTimer;
|
|
65
|
-
#state;
|
|
66
|
-
constructor(host, key, hash) {
|
|
67
|
-
this.#host = host;
|
|
68
|
-
this.key = key;
|
|
69
|
-
this.hash = hash;
|
|
70
|
-
this.#handle = {
|
|
71
|
-
input: void 0,
|
|
72
|
-
key
|
|
73
|
-
};
|
|
74
|
-
this.#pending = pending();
|
|
75
|
-
this.#state = {
|
|
76
|
-
data: void 0,
|
|
77
|
-
error: null,
|
|
78
|
-
failureCount: 0,
|
|
79
|
-
fetchDirection: void 0,
|
|
80
|
-
hasData: false,
|
|
81
|
-
isFetching: false,
|
|
82
|
-
isInvalidated: false,
|
|
83
|
-
isStale: true,
|
|
84
|
-
promise: this.#pending.promise,
|
|
85
|
-
status: "pending",
|
|
86
|
-
updatedAt: 0
|
|
87
|
-
};
|
|
88
|
-
this.#scheduleGc();
|
|
89
|
-
}
|
|
90
|
-
get handle() {
|
|
91
|
-
return this.#handle;
|
|
92
|
-
}
|
|
93
|
-
get options() {
|
|
94
|
-
return this.#options;
|
|
95
|
-
}
|
|
96
|
-
/** Readers are subscribed; `invalidate` and focus refetch only what is read. */
|
|
97
|
-
get isObserved() {
|
|
98
|
-
return this.#listeners.size > 0;
|
|
99
|
-
}
|
|
100
|
-
get observerCount() {
|
|
101
|
-
return this.#listeners.size;
|
|
102
|
-
}
|
|
103
|
-
getState = () => this.#state;
|
|
104
|
-
isStaleNow = () => this.#computeStale(this.#state);
|
|
105
|
-
subscribe = (listener, observer = defaultObserver) => {
|
|
106
|
-
this.#listeners.set(listener, observer);
|
|
107
|
-
this.#clearGc();
|
|
108
|
-
this.#scheduleStale();
|
|
109
|
-
this.#host.observersChanged(this);
|
|
110
|
-
return () => {
|
|
111
|
-
if (!this.#listeners.delete(listener)) return;
|
|
112
|
-
if (this.#listeners.size === 0) {
|
|
113
|
-
clearTimeout(this.#staleTimer);
|
|
114
|
-
this.#scheduleGc();
|
|
115
|
-
}
|
|
116
|
-
this.#host.observersChanged(this);
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
/** Attach the options and input of the latest factory call. */
|
|
120
|
-
setOptions(options, input) {
|
|
121
|
-
this.#options = options;
|
|
122
|
-
if (this.#handle.input !== input) this.#handle = {
|
|
123
|
-
input,
|
|
124
|
-
key: this.key
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
read = () => {
|
|
128
|
-
const state = this.#state;
|
|
129
|
-
if (!state.hasData && !state.isFetching) {
|
|
130
|
-
if (state.status !== "error" || this.#errorShown) this.fetch().catch(noop);
|
|
131
|
-
else this.#markErrorShown();
|
|
132
|
-
}
|
|
133
|
-
return this.#state.promise;
|
|
134
|
-
};
|
|
135
|
-
fetch = (request = {}) => {
|
|
136
|
-
const options = this.#options;
|
|
137
|
-
const inFlight = this.#inFlight;
|
|
138
|
-
if (inFlight && !(request.restart && options)) {
|
|
139
|
-
if (request.direction) {
|
|
140
|
-
const next = () => this.fetch(request);
|
|
141
|
-
return inFlight.deferred.promise.then(next, next);
|
|
142
|
-
}
|
|
143
|
-
return inFlight.deferred.promise;
|
|
144
|
-
}
|
|
145
|
-
if (!options) return this.#state.promise;
|
|
146
|
-
inFlight?.controller.abort();
|
|
147
|
-
return this.#start(this.#request(options, request), request.direction);
|
|
148
|
-
};
|
|
149
|
-
refetch = () => this.fetch().then(noop, noop);
|
|
150
|
-
fetchNextPage = () => this.fetch({ direction: "forward" }).then(noop, noop);
|
|
151
|
-
fetchPreviousPage = () => this.fetch({ direction: "backward" }).then(noop, noop);
|
|
152
|
-
hasNextPage = () => {
|
|
153
|
-
const options = this.#options;
|
|
154
|
-
const data = this.#state.data;
|
|
155
|
-
return options?.kind === "infinite" && isInfiniteData(data) && nextPageParam(options, data) != null;
|
|
156
|
-
};
|
|
157
|
-
hasPreviousPage = () => {
|
|
158
|
-
const options = this.#options;
|
|
159
|
-
const data = this.#state.data;
|
|
160
|
-
return options?.kind === "infinite" && isInfiniteData(data) && previousPageParam(options, data) != null;
|
|
161
|
-
};
|
|
162
|
-
invalidate = () => {
|
|
163
|
-
this.#set({ isInvalidated: true });
|
|
164
|
-
if (!this.isObserved || !this.#options) return Promise.resolve();
|
|
165
|
-
return this.fetch({ restart: true }).then(noop, noop);
|
|
166
|
-
};
|
|
167
|
-
reset = () => {
|
|
168
|
-
clearTimeout(this.#errorShownTimer);
|
|
169
|
-
this.#errorShownTimer = void 0;
|
|
170
|
-
this.#errorShown = false;
|
|
171
|
-
this.#pending ??= pending();
|
|
172
|
-
this.#set({
|
|
173
|
-
data: void 0,
|
|
174
|
-
error: null,
|
|
175
|
-
failureCount: 0,
|
|
176
|
-
hasData: false,
|
|
177
|
-
isInvalidated: false,
|
|
178
|
-
promise: this.#pending.promise,
|
|
179
|
-
status: "pending",
|
|
180
|
-
updatedAt: 0
|
|
181
|
-
});
|
|
182
|
-
if (!this.#options || !this.isObserved && !this.#inFlight) return Promise.resolve();
|
|
183
|
-
return this.fetch({ restart: true }).then(noop, noop);
|
|
184
|
-
};
|
|
185
|
-
/** Write data directly, as if a request had returned it. */
|
|
186
|
-
setData(data, updatedAt = Date.now()) {
|
|
187
|
-
this.#succeed(data, updatedAt);
|
|
188
|
-
}
|
|
189
|
-
/** Adopt a request started elsewhere (the server's, during hydration). */
|
|
190
|
-
resume(promise) {
|
|
191
|
-
if (this.#state.hasData || this.#inFlight) return;
|
|
192
|
-
this.#start(Effect.tryPromise({
|
|
193
|
-
catch: identity,
|
|
194
|
-
try: () => Promise.resolve(promise)
|
|
195
|
-
}), void 0).catch(noop);
|
|
196
|
-
}
|
|
197
|
-
/** Refetch a read, stale entry whose readers want it on this event. */
|
|
198
|
-
onWindowEvent(event) {
|
|
199
|
-
const option = event === "focus" ? "refetchOnWindowFocus" : "refetchOnReconnect";
|
|
200
|
-
const fallback = this.#behavior(option) ?? true;
|
|
201
|
-
if ([...this.#listeners.values()].some((observer) => observer[option] ?? fallback) && this.#computeStale(this.#state)) this.fetch().catch(noop);
|
|
202
|
-
}
|
|
203
|
-
/** Stop timers and abort the in-flight request; the entry is leaving the cache. */
|
|
204
|
-
destroy() {
|
|
205
|
-
clearTimeout(this.#gcTimer);
|
|
206
|
-
clearTimeout(this.#staleTimer);
|
|
207
|
-
clearTimeout(this.#errorShownTimer);
|
|
208
|
-
const inFlight = this.#inFlight;
|
|
209
|
-
this.#inFlight = void 0;
|
|
210
|
-
if (inFlight) {
|
|
211
|
-
inFlight.controller.abort();
|
|
212
|
-
inFlight.deferred.reject(new CancelledError());
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
#behavior(name) {
|
|
216
|
-
return this.#options?.[name] ?? this.#host.defaults[name];
|
|
217
|
-
}
|
|
218
|
-
#computeStale(state) {
|
|
219
|
-
if (state.isInvalidated || !state.hasData) return true;
|
|
220
|
-
const staleTime = this.#behavior("staleTime") ?? 0;
|
|
221
|
-
return Date.now() - state.updatedAt >= staleTime;
|
|
222
|
-
}
|
|
223
|
-
#set(patch) {
|
|
224
|
-
const next = {
|
|
225
|
-
...this.#state,
|
|
226
|
-
...patch
|
|
227
|
-
};
|
|
228
|
-
this.#state = {
|
|
229
|
-
...next,
|
|
230
|
-
isStale: this.#computeStale(next)
|
|
231
|
-
};
|
|
232
|
-
for (const listener of this.#listeners.keys()) scheduleNotify(listener);
|
|
233
|
-
this.#host.changed(this);
|
|
234
|
-
this.#scheduleStale();
|
|
235
|
-
}
|
|
236
|
-
#start(request, direction) {
|
|
237
|
-
const deferred = this.#inFlight?.deferred ?? pending();
|
|
238
|
-
const run = {
|
|
239
|
-
controller: new AbortController(),
|
|
240
|
-
deferred
|
|
241
|
-
};
|
|
242
|
-
this.#inFlight = run;
|
|
243
|
-
this.#clearGc();
|
|
244
|
-
if (!this.#state.hasData && this.#state.status === "error") {
|
|
245
|
-
this.#pending = pending();
|
|
246
|
-
this.#set({
|
|
247
|
-
error: null,
|
|
248
|
-
failureCount: 0,
|
|
249
|
-
fetchDirection: direction,
|
|
250
|
-
isFetching: true,
|
|
251
|
-
promise: this.#pending.promise,
|
|
252
|
-
status: "pending"
|
|
253
|
-
});
|
|
254
|
-
} else this.#set({
|
|
255
|
-
failureCount: 0,
|
|
256
|
-
fetchDirection: direction,
|
|
257
|
-
isFetching: true
|
|
258
|
-
});
|
|
259
|
-
Effect.runPromiseExit(request, { signal: run.controller.signal }).then((exit) => this.#settle(run, exit), noop);
|
|
260
|
-
return deferred.promise;
|
|
261
|
-
}
|
|
262
|
-
#settle(run, exit) {
|
|
263
|
-
if (this.#inFlight !== run) return;
|
|
264
|
-
this.#inFlight = void 0;
|
|
265
|
-
if (Exit.isSuccess(exit)) run.deferred.resolve(this.#succeed(exit.value, Date.now()));
|
|
266
|
-
else if (Cause.hasInterruptsOnly(exit.cause)) {
|
|
267
|
-
this.#set({
|
|
268
|
-
fetchDirection: void 0,
|
|
269
|
-
isFetching: false
|
|
270
|
-
});
|
|
271
|
-
run.deferred.reject(new CancelledError());
|
|
272
|
-
} else {
|
|
273
|
-
const error = Cause.squash(exit.cause);
|
|
274
|
-
this.#fail(error);
|
|
275
|
-
run.deferred.reject(error);
|
|
276
|
-
}
|
|
277
|
-
this.#scheduleGc();
|
|
278
|
-
}
|
|
279
|
-
#succeed(next, updatedAt) {
|
|
280
|
-
const previous = this.#state;
|
|
281
|
-
const data = previous.hasData && this.#behavior("structuralSharing") !== false ? replaceEqualDeep(previous.data, next) : next;
|
|
282
|
-
const firstLoad = this.#pending;
|
|
283
|
-
this.#pending = void 0;
|
|
284
|
-
firstLoad?.resolve(data);
|
|
285
|
-
const unchanged = previous.hasData && data === previous.data;
|
|
286
|
-
this.#set({
|
|
287
|
-
data,
|
|
288
|
-
error: null,
|
|
289
|
-
failureCount: 0,
|
|
290
|
-
fetchDirection: void 0,
|
|
291
|
-
hasData: true,
|
|
292
|
-
isFetching: false,
|
|
293
|
-
isInvalidated: false,
|
|
294
|
-
promise: firstLoad?.promise ?? (unchanged ? previous.promise : fulfilled(data)),
|
|
295
|
-
status: "success",
|
|
296
|
-
updatedAt
|
|
297
|
-
});
|
|
298
|
-
return data;
|
|
299
|
-
}
|
|
300
|
-
#fail(error) {
|
|
301
|
-
this.#host.reportError(error, this);
|
|
302
|
-
if (!this.#state.hasData) {
|
|
303
|
-
const firstLoad = this.#pending;
|
|
304
|
-
this.#pending = void 0;
|
|
305
|
-
firstLoad?.reject(error);
|
|
306
|
-
this.#errorShown = false;
|
|
307
|
-
}
|
|
308
|
-
this.#set({
|
|
309
|
-
error,
|
|
310
|
-
fetchDirection: void 0,
|
|
311
|
-
isFetching: false,
|
|
312
|
-
status: "error"
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* A failed first load rejects its promise, so the reader throws to an error
|
|
317
|
-
* boundary. Every read in that same task (React retries a throwing render
|
|
318
|
-
* once, StrictMode renders twice) gets the rejection too; a read in a later
|
|
319
|
-
* task — the boundary was reset — starts a new request.
|
|
320
|
-
*/
|
|
321
|
-
#markErrorShown() {
|
|
322
|
-
if (this.#errorShownTimer !== void 0) return;
|
|
323
|
-
this.#errorShownTimer = setTimeout(() => {
|
|
324
|
-
this.#errorShownTimer = void 0;
|
|
325
|
-
this.#errorShown = true;
|
|
326
|
-
}, 0);
|
|
327
|
-
}
|
|
328
|
-
#request(options, request) {
|
|
329
|
-
if (options.kind === "query") return this.#attempt((signal) => options.queryFn({
|
|
330
|
-
queryKey: options.queryKey,
|
|
331
|
-
signal
|
|
332
|
-
}));
|
|
333
|
-
return this.#pagedRequest(options, request);
|
|
334
|
-
}
|
|
335
|
-
#pagedRequest(options, request) {
|
|
336
|
-
const current = isInfiniteData(this.#state.data) ? this.#state.data : void 0;
|
|
337
|
-
const fetchPage = (pageParam, direction) => this.#attempt((signal) => options.queryFn({
|
|
338
|
-
direction,
|
|
339
|
-
pageParam,
|
|
340
|
-
queryKey: options.queryKey,
|
|
341
|
-
signal
|
|
342
|
-
}));
|
|
343
|
-
return Effect.gen(function* () {
|
|
344
|
-
if (request.direction && current) {
|
|
345
|
-
const pageParam = request.direction === "forward" ? nextPageParam(options, current) : previousPageParam(options, current);
|
|
346
|
-
if (pageParam == null) return current;
|
|
347
|
-
const page = yield* fetchPage(pageParam, request.direction);
|
|
348
|
-
return addPage(current, page, pageParam, request.direction, options.maxPages);
|
|
349
|
-
}
|
|
350
|
-
const count = Math.max(request.pages ?? 1, current?.pages.length ?? 0);
|
|
351
|
-
let data = {
|
|
352
|
-
pageParams: [],
|
|
353
|
-
pages: []
|
|
354
|
-
};
|
|
355
|
-
let pageParam = current && current.pageParams.length > 0 ? current.pageParams[0] : options.initialPageParam;
|
|
356
|
-
for (let index = 0; index < count; index++) {
|
|
357
|
-
if (index > 0) {
|
|
358
|
-
const next = nextPageParam(options, data);
|
|
359
|
-
if (next == null) break;
|
|
360
|
-
pageParam = next;
|
|
361
|
-
}
|
|
362
|
-
const page = yield* fetchPage(pageParam, "forward");
|
|
363
|
-
data = addPage(data, page, pageParam, "forward", options.maxPages);
|
|
364
|
-
}
|
|
365
|
-
return data;
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
/** One request: run the `queryFn` with a fresh signal, retrying per policy. */
|
|
369
|
-
#attempt(call) {
|
|
370
|
-
const once = Effect.suspend(() => {
|
|
371
|
-
const controller = new AbortController();
|
|
372
|
-
return Effect.try({
|
|
373
|
-
catch: identity,
|
|
374
|
-
try: () => call(controller.signal)
|
|
375
|
-
}).pipe(Effect.flatMap((result) => isEffectResult(result) ? result : Effect.tryPromise({
|
|
376
|
-
catch: identity,
|
|
377
|
-
try: () => Promise.resolve(result)
|
|
378
|
-
})), Effect.onInterrupt(() => Effect.sync(() => controller.abort())));
|
|
379
|
-
});
|
|
380
|
-
return this.#withRetry(once);
|
|
381
|
-
}
|
|
382
|
-
#withRetry(once) {
|
|
383
|
-
const retry = this.#behavior("retry") ?? (this.#host.isServer ? 0 : 3);
|
|
384
|
-
const countFailure = Effect.sync(() => this.#set({ failureCount: this.#state.failureCount + 1 }));
|
|
385
|
-
const counted = once.pipe(Effect.tapError(() => countFailure));
|
|
386
|
-
const online = this.#host.isServer ? Effect.void : whenOnline;
|
|
387
|
-
if (Schedule.isSchedule(retry)) return Effect.suspend(() => {
|
|
388
|
-
let first = true;
|
|
389
|
-
const each = Effect.suspend(() => {
|
|
390
|
-
const wait = first ? Effect.void : online;
|
|
391
|
-
first = false;
|
|
392
|
-
return wait.pipe(Effect.andThen(counted));
|
|
393
|
-
});
|
|
394
|
-
return Effect.retry(each, retry);
|
|
395
|
-
});
|
|
396
|
-
const retryDelay = this.#behavior("retryDelay");
|
|
397
|
-
const delayFor = (failureCount, error) => {
|
|
398
|
-
if (retryDelay === void 0) return Math.min(1e3 * 2 ** (failureCount - 1), 3e4);
|
|
399
|
-
return Predicate.isNumber(retryDelay) ? retryDelay : retryDelay(failureCount, error);
|
|
400
|
-
};
|
|
401
|
-
const attempt = counted.pipe(Effect.catch((error) => {
|
|
402
|
-
const failureCount = this.#state.failureCount;
|
|
403
|
-
if (!shouldRetry(retry, failureCount, error)) return Effect.fail(error);
|
|
404
|
-
return Effect.sleep(delayFor(failureCount, error)).pipe(Effect.andThen(online), Effect.andThen(Effect.suspend(() => attempt)));
|
|
405
|
-
}));
|
|
406
|
-
return attempt;
|
|
407
|
-
}
|
|
408
|
-
#clearGc() {
|
|
409
|
-
clearTimeout(this.#gcTimer);
|
|
410
|
-
this.#gcTimer = void 0;
|
|
411
|
-
}
|
|
412
|
-
#scheduleGc() {
|
|
413
|
-
this.#clearGc();
|
|
414
|
-
if (this.isObserved || this.#inFlight) return;
|
|
415
|
-
const gcTime = this.#behavior("gcTime") ?? (this.#host.isServer ? Infinity : 3e5);
|
|
416
|
-
if (Number.isFinite(gcTime)) this.#gcTimer = setTimeout(() => this.#host.remove(this), gcTime);
|
|
417
|
-
}
|
|
418
|
-
#scheduleStale() {
|
|
419
|
-
clearTimeout(this.#staleTimer);
|
|
420
|
-
const state = this.#state;
|
|
421
|
-
if (this.#host.isServer || !this.isObserved || state.isStale) return;
|
|
422
|
-
const staleTime = this.#behavior("staleTime") ?? 0;
|
|
423
|
-
if (!Number.isFinite(staleTime)) return;
|
|
424
|
-
const remaining = state.updatedAt + staleTime - Date.now();
|
|
425
|
-
this.#staleTimer = setTimeout(() => this.#set({}), Math.max(remaining, 0) + 1);
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
118
|
//#endregion
|
|
429
|
-
export { CancelledError,
|
|
119
|
+
export { CancelledError, TypeId, changes, fetch, fetchNextPage, fetchPreviousPage, get, getUnsafe, hasNextPageUnsafe, hasPreviousPageUnsafe, invalidate, isQuery, isStaleUnsafe, observerCountUnsafe, query_exports, readUnsafe, refetch, reset, subscribeUnsafe };
|
package/dist/request.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Context, Effect, Scope } from "effect";
|
|
2
|
+
//#region src/request.ts
|
|
3
|
+
function isEffectResult(result) {
|
|
4
|
+
return Effect.isEffect(result);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* One `queryFn` call as an Effect. The signal it gets aborts when the fiber
|
|
8
|
+
* is interrupted; a returned Effect runs in the same fiber, so interrupting
|
|
9
|
+
* the request interrupts it. Plain values settle like promises, a microtask
|
|
10
|
+
* later. A `queryFn` that throws instead of returning is a defect, as in
|
|
11
|
+
* `Effect.sync`.
|
|
12
|
+
*/
|
|
13
|
+
function fromQueryFn(call) {
|
|
14
|
+
return Effect.callback((resume, signal) => {
|
|
15
|
+
let result;
|
|
16
|
+
try {
|
|
17
|
+
result = call(signal);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
resume(Effect.die(error));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (isEffectResult(result)) {
|
|
23
|
+
resume(result);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
Promise.resolve(result).then((value) => resume(Effect.succeed(value)), (error) => resume(Effect.fail(error)));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The services a request runs with: the cache's, under those of the verb
|
|
31
|
+
* that last called for the entry. The caller's `Scope` is left out: a
|
|
32
|
+
* request outlives the call that started it.
|
|
33
|
+
*/
|
|
34
|
+
function requestServices(cache, caller) {
|
|
35
|
+
return Context.merge(cache, Context.omit(Scope.Scope)(caller));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A request as it runs: in a `raf-query.request` span, with `services`,
|
|
39
|
+
* which hold what its `queryFn` Effect needs (entries erase that type with
|
|
40
|
+
* the data's). Typed failures become the query's error; defects are logged
|
|
41
|
+
* too, since nothing else reports them.
|
|
42
|
+
*/
|
|
43
|
+
function runRequest(request, hash, services) {
|
|
44
|
+
return request.pipe(Effect.tapDefect((defect) => Effect.logError("raf-query: queryFn died", defect)), Effect.withSpan("raf-query.request", { attributes: { "raf_query.hash": hash } }), Effect.provideContext(services));
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { fromQueryFn, requestServices, runRequest };
|
package/dist/retry.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect, Predicate, Schedule } from "effect";
|
|
2
|
+
//#region src/retry.ts
|
|
3
|
+
/** Doubling from 1 s, at most 30 s. */
|
|
4
|
+
const defaultDelays = Schedule.min([Schedule.exponential("1 second"), Schedule.spaced("30 seconds")]);
|
|
5
|
+
function delays(retryDelay) {
|
|
6
|
+
if (retryDelay === void 0) return defaultDelays;
|
|
7
|
+
if (Predicate.isNumber(retryDelay)) return Schedule.spaced(retryDelay);
|
|
8
|
+
return Schedule.forever.pipe(Schedule.modifyDelay((step) => Effect.succeed(retryDelay(step.attempt, step.input))));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A query's retry options as one `Schedule`, or `undefined` when it doesn't
|
|
12
|
+
* retry. `retryDelay` sets the delays; a count or predicate in `retry` stops
|
|
13
|
+
* the schedule, given the attempt number (1 after the first failure) and the
|
|
14
|
+
* error.
|
|
15
|
+
*/
|
|
16
|
+
function retrySchedule(behavior, isServer) {
|
|
17
|
+
const retry = behavior.retry ?? (isServer ? 0 : 3);
|
|
18
|
+
if (Schedule.isSchedule(retry)) return retry;
|
|
19
|
+
if (retry === false || retry === 0) return;
|
|
20
|
+
const schedule = delays(behavior.retryDelay);
|
|
21
|
+
if (retry === true) return schedule;
|
|
22
|
+
return schedule.pipe(Schedule.while((step) => Predicate.isNumber(retry) ? step.attempt <= retry : retry(step.attempt, step.input)));
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { retrySchedule };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/thenable.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Promises that carry their own result, in the fields React's `use()` reads
|
|
4
|
+
* before suspending (`status`, `value`, `reason`). A settled one can be read
|
|
5
|
+
* synchronously, so React renders it without a fallback.
|
|
6
|
+
*/
|
|
7
|
+
interface PendingStatus {
|
|
8
|
+
readonly status: "pending";
|
|
9
|
+
}
|
|
10
|
+
interface FulfilledStatus<T> {
|
|
11
|
+
readonly status: "fulfilled";
|
|
12
|
+
readonly value: T;
|
|
13
|
+
}
|
|
14
|
+
interface RejectedStatus {
|
|
15
|
+
readonly reason: unknown;
|
|
16
|
+
readonly status: "rejected";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A promise that says whether it has settled, and with what. `status` moves
|
|
20
|
+
* from `"pending"` to `"fulfilled"` (with `value`) or `"rejected"` (with
|
|
21
|
+
* `reason`) once, when the promise settles.
|
|
22
|
+
*/
|
|
23
|
+
export type Thenable<T> = Promise<T> & (FulfilledStatus<T> | PendingStatus | RejectedStatus);
|
|
24
|
+
export interface Pending<T> {
|
|
25
|
+
readonly promise: Thenable<T>;
|
|
26
|
+
reject(reason: unknown): void;
|
|
27
|
+
resolve(value: T): void;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
package/dist/thenable.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//#region src/thenable.ts
|
|
2
2
|
/**
|
|
3
|
-
* Promises that React's `use()`
|
|
4
|
-
* `status
|
|
5
|
-
*
|
|
3
|
+
* Promises that carry their own result, in the fields React's `use()` reads
|
|
4
|
+
* before suspending (`status`, `value`, `reason`). A settled one can be read
|
|
5
|
+
* synchronously, so React renders it without a fallback.
|
|
6
6
|
*/
|
|
7
7
|
const noop = () => {};
|
|
8
8
|
function fulfilled(value) {
|
package/package.json
CHANGED
package/dist/all-keyed.d.mts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//#region src/all-keyed.d.ts
|
|
2
|
-
/** The resolved form of a record passed to {@link allKeyed}. */
|
|
3
|
-
export type Settled<TRecord> = { -readonly [TKey in keyof TRecord]: Awaited<TRecord[TKey]>; };
|
|
4
|
-
/**
|
|
5
|
-
* Ponyfill of the TC39 `Promise.allKeyed` proposal: a record of promises in,
|
|
6
|
-
* a promise of the same record out, rejecting on the first failure.
|
|
7
|
-
*
|
|
8
|
-
* ```ts
|
|
9
|
-
* const data = await allKeyed({
|
|
10
|
-
* post: qc.ensure(postQuery, { slug }),
|
|
11
|
-
* author: qc.ensure(authorQuery, { slug }),
|
|
12
|
-
* });
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export declare function allKeyed<TRecord extends {
|
|
16
|
-
readonly [key: string]: unknown;
|
|
17
|
-
}>(record: TRecord): Promise<Settled<TRecord>>;
|
|
18
|
-
//#endregion
|