steddy 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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/index.cjs +786 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +158 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +764 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
import { createContext, useMemo, useRef, useCallback, useSyncExternalStore, useState, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/useSteddy.ts
|
|
5
|
+
|
|
6
|
+
// src/coordinator.ts
|
|
7
|
+
var DEDUP_WINDOW_MS = 2e3;
|
|
8
|
+
function isAbortError(error) {
|
|
9
|
+
return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
10
|
+
}
|
|
11
|
+
function createCoordinator(store) {
|
|
12
|
+
const registered = /* @__PURE__ */ new Map();
|
|
13
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
14
|
+
let generation = 0;
|
|
15
|
+
function abortInternal(serializedKey, markIdle) {
|
|
16
|
+
const current = inflight.get(serializedKey);
|
|
17
|
+
if (!current) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
current.controller.abort();
|
|
21
|
+
current.settle();
|
|
22
|
+
inflight.delete(serializedKey);
|
|
23
|
+
if (markIdle) {
|
|
24
|
+
const entry = store.get(serializedKey);
|
|
25
|
+
if (entry?.isValidating) {
|
|
26
|
+
store.set(serializedKey, { ...entry, isValidating: false });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const coordinator = {
|
|
31
|
+
register(serializedKey, key, fetcher) {
|
|
32
|
+
registered.set(serializedKey, { key, fetcher });
|
|
33
|
+
},
|
|
34
|
+
unregister(serializedKey) {
|
|
35
|
+
registered.delete(serializedKey);
|
|
36
|
+
},
|
|
37
|
+
async revalidate(serializedKey, fetcher, options) {
|
|
38
|
+
if (!options?.force) {
|
|
39
|
+
const entry = store.get(serializedKey);
|
|
40
|
+
if (entry && !entry.isValidating && entry.error == null && entry.data !== void 0 && Date.now() - entry.timestamp < DEDUP_WINDOW_MS) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
abortInternal(serializedKey, false);
|
|
45
|
+
const record = registered.get(serializedKey);
|
|
46
|
+
const key = record?.key ?? serializedKey;
|
|
47
|
+
const run = fetcher ?? record?.fetcher;
|
|
48
|
+
if (!run) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const currentGeneration = ++generation;
|
|
53
|
+
let settled = false;
|
|
54
|
+
let settleWaiter;
|
|
55
|
+
const waiter = new Promise((resolve) => {
|
|
56
|
+
settleWaiter = resolve;
|
|
57
|
+
});
|
|
58
|
+
const settle = () => {
|
|
59
|
+
if (!settled) {
|
|
60
|
+
settled = true;
|
|
61
|
+
settleWaiter();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
inflight.set(serializedKey, {
|
|
65
|
+
controller,
|
|
66
|
+
generation: currentGeneration,
|
|
67
|
+
waiter,
|
|
68
|
+
settle
|
|
69
|
+
});
|
|
70
|
+
const previous = store.get(serializedKey);
|
|
71
|
+
store.set(serializedKey, {
|
|
72
|
+
data: previous?.data,
|
|
73
|
+
error: previous?.error,
|
|
74
|
+
timestamp: previous?.timestamp ?? 0,
|
|
75
|
+
isValidating: true
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
const data = await run(key, { signal: controller.signal });
|
|
79
|
+
const stillCurrent = inflight.get(serializedKey)?.generation === currentGeneration;
|
|
80
|
+
if (!stillCurrent || controller.signal.aborted) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
inflight.delete(serializedKey);
|
|
84
|
+
store.set(serializedKey, {
|
|
85
|
+
data,
|
|
86
|
+
error: void 0,
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
isValidating: false
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const stillCurrent = inflight.get(serializedKey)?.generation === currentGeneration;
|
|
92
|
+
if (!stillCurrent || controller.signal.aborted || isAbortError(error)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
inflight.delete(serializedKey);
|
|
96
|
+
const current = store.get(serializedKey);
|
|
97
|
+
store.set(serializedKey, {
|
|
98
|
+
data: current?.data,
|
|
99
|
+
error,
|
|
100
|
+
timestamp: current?.timestamp ?? 0,
|
|
101
|
+
isValidating: false
|
|
102
|
+
});
|
|
103
|
+
throw error;
|
|
104
|
+
} finally {
|
|
105
|
+
settle();
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
isInFlight(serializedKey) {
|
|
109
|
+
return inflight.has(serializedKey);
|
|
110
|
+
},
|
|
111
|
+
getInFlightPromise(serializedKey) {
|
|
112
|
+
return inflight.get(serializedKey)?.waiter;
|
|
113
|
+
},
|
|
114
|
+
getRegisteredKeys() {
|
|
115
|
+
return [...registered.keys()];
|
|
116
|
+
},
|
|
117
|
+
abort(serializedKey) {
|
|
118
|
+
abortInternal(serializedKey, true);
|
|
119
|
+
},
|
|
120
|
+
evict(options) {
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
const removable = [];
|
|
123
|
+
for (const key of store.keys()) {
|
|
124
|
+
if (inflight.has(key) || store.subscriberCount(key) > 0) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const entry = store.get(key);
|
|
128
|
+
if (!entry) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
removable.push({ key, timestamp: entry.timestamp });
|
|
132
|
+
}
|
|
133
|
+
const victims = /* @__PURE__ */ new Set();
|
|
134
|
+
if (options.maxAge != null) {
|
|
135
|
+
for (const item of removable) {
|
|
136
|
+
if (now - item.timestamp >= options.maxAge) {
|
|
137
|
+
victims.add(item.key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (options.maxKeys != null) {
|
|
142
|
+
const liveCount = store.keys().filter((key) => !victims.has(key)).length;
|
|
143
|
+
let over = liveCount - options.maxKeys;
|
|
144
|
+
if (over > 0) {
|
|
145
|
+
const extra = removable.filter((item) => !victims.has(item.key)).sort((a, b) => a.timestamp - b.timestamp);
|
|
146
|
+
for (const item of extra) {
|
|
147
|
+
if (over <= 0) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
victims.add(item.key);
|
|
151
|
+
over -= 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const key of victims) {
|
|
156
|
+
abortInternal(key, false);
|
|
157
|
+
registered.delete(key);
|
|
158
|
+
store.delete(key);
|
|
159
|
+
}
|
|
160
|
+
return [...victims];
|
|
161
|
+
},
|
|
162
|
+
reset() {
|
|
163
|
+
for (const key of [...inflight.keys()]) {
|
|
164
|
+
abortInternal(key, false);
|
|
165
|
+
}
|
|
166
|
+
registered.clear();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
return coordinator;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/store.ts
|
|
173
|
+
var EMPTY_SNAPSHOT = Object.freeze({
|
|
174
|
+
data: void 0,
|
|
175
|
+
error: void 0,
|
|
176
|
+
timestamp: 0,
|
|
177
|
+
isValidating: false
|
|
178
|
+
});
|
|
179
|
+
function createStore() {
|
|
180
|
+
const entries = /* @__PURE__ */ new Map();
|
|
181
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
182
|
+
function emit(key) {
|
|
183
|
+
const subs = listeners.get(key);
|
|
184
|
+
if (!subs) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const callback of subs) {
|
|
188
|
+
callback();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
get(key) {
|
|
193
|
+
return entries.get(key);
|
|
194
|
+
},
|
|
195
|
+
set(key, entry) {
|
|
196
|
+
entries.set(key, entry);
|
|
197
|
+
emit(key);
|
|
198
|
+
},
|
|
199
|
+
delete(key) {
|
|
200
|
+
const existed = entries.delete(key);
|
|
201
|
+
if (existed) {
|
|
202
|
+
emit(key);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
subscribe(key, callback) {
|
|
206
|
+
let subs = listeners.get(key);
|
|
207
|
+
if (!subs) {
|
|
208
|
+
subs = /* @__PURE__ */ new Set();
|
|
209
|
+
listeners.set(key, subs);
|
|
210
|
+
}
|
|
211
|
+
subs.add(callback);
|
|
212
|
+
return () => {
|
|
213
|
+
const current = listeners.get(key);
|
|
214
|
+
if (!current) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
current.delete(callback);
|
|
218
|
+
if (current.size === 0) {
|
|
219
|
+
listeners.delete(key);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
getSnapshot(key) {
|
|
224
|
+
return entries.get(key) ?? EMPTY_SNAPSHOT;
|
|
225
|
+
},
|
|
226
|
+
subscriberCount(key) {
|
|
227
|
+
return listeners.get(key)?.size ?? 0;
|
|
228
|
+
},
|
|
229
|
+
keys() {
|
|
230
|
+
return [...entries.keys()];
|
|
231
|
+
},
|
|
232
|
+
clear() {
|
|
233
|
+
const keys = /* @__PURE__ */ new Set([...entries.keys(), ...listeners.keys()]);
|
|
234
|
+
entries.clear();
|
|
235
|
+
for (const key of keys) {
|
|
236
|
+
emit(key);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/defaults.ts
|
|
243
|
+
var defaultStore = createStore();
|
|
244
|
+
var defaultCoordinator = createCoordinator(defaultStore);
|
|
245
|
+
|
|
246
|
+
// src/key.ts
|
|
247
|
+
function serializeKey(key) {
|
|
248
|
+
if (typeof key === "string") {
|
|
249
|
+
return key;
|
|
250
|
+
}
|
|
251
|
+
return JSON.stringify(key);
|
|
252
|
+
}
|
|
253
|
+
function keysShallowEqual(a, b) {
|
|
254
|
+
if (a === b) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (a == null || b == null) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (typeof a === "string" || typeof b === "string") {
|
|
261
|
+
return a === b;
|
|
262
|
+
}
|
|
263
|
+
if (a.length !== b.length) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
for (let i = 0; i < a.length; i++) {
|
|
267
|
+
if (!Object.is(a[i], b[i])) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/mutate.ts
|
|
275
|
+
function isThenable(value) {
|
|
276
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
277
|
+
}
|
|
278
|
+
function createMutate(store, coordinator) {
|
|
279
|
+
return async function mutate2(key, updater, options) {
|
|
280
|
+
const revalidate = options?.revalidate ?? true;
|
|
281
|
+
const rollbackOnError = options?.rollbackOnError ?? true;
|
|
282
|
+
const serialized = serializeKey(key);
|
|
283
|
+
const previous = store.get(serialized);
|
|
284
|
+
const restore = () => {
|
|
285
|
+
if (previous === void 0) {
|
|
286
|
+
store.delete(serialized);
|
|
287
|
+
} else {
|
|
288
|
+
store.set(serialized, previous);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
try {
|
|
292
|
+
const currentData = previous?.data;
|
|
293
|
+
const next = typeof updater === "function" ? updater(currentData) : updater;
|
|
294
|
+
if (isThenable(next)) {
|
|
295
|
+
store.set(serialized, {
|
|
296
|
+
data: currentData,
|
|
297
|
+
error: previous?.error,
|
|
298
|
+
timestamp: previous?.timestamp ?? 0,
|
|
299
|
+
isValidating: true
|
|
300
|
+
});
|
|
301
|
+
const resolved = await next;
|
|
302
|
+
store.set(serialized, {
|
|
303
|
+
data: resolved,
|
|
304
|
+
error: void 0,
|
|
305
|
+
timestamp: Date.now(),
|
|
306
|
+
isValidating: revalidate
|
|
307
|
+
});
|
|
308
|
+
} else {
|
|
309
|
+
store.set(serialized, {
|
|
310
|
+
data: next,
|
|
311
|
+
error: void 0,
|
|
312
|
+
timestamp: Date.now(),
|
|
313
|
+
isValidating: revalidate
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (revalidate) {
|
|
317
|
+
await coordinator.revalidate(serialized, void 0, { force: true });
|
|
318
|
+
}
|
|
319
|
+
return store.get(serialized)?.data;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (rollbackOnError) {
|
|
322
|
+
restore();
|
|
323
|
+
} else {
|
|
324
|
+
const current = store.get(serialized);
|
|
325
|
+
store.set(serialized, {
|
|
326
|
+
data: current?.data,
|
|
327
|
+
error,
|
|
328
|
+
timestamp: current?.timestamp ?? Date.now(),
|
|
329
|
+
isValidating: false
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
var mutate = createMutate(defaultStore, defaultCoordinator);
|
|
337
|
+
function createRuntime() {
|
|
338
|
+
const store = createStore();
|
|
339
|
+
const coordinator = createCoordinator(store);
|
|
340
|
+
return {
|
|
341
|
+
store,
|
|
342
|
+
coordinator,
|
|
343
|
+
mutate: createMutate(store, coordinator)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
var defaultRuntime = {
|
|
347
|
+
store: defaultStore,
|
|
348
|
+
coordinator: defaultCoordinator,
|
|
349
|
+
mutate: createMutate(defaultStore, defaultCoordinator)
|
|
350
|
+
};
|
|
351
|
+
var SteddyContext = createContext(defaultRuntime);
|
|
352
|
+
function SteddyProvider({
|
|
353
|
+
store,
|
|
354
|
+
coordinator,
|
|
355
|
+
cache,
|
|
356
|
+
children
|
|
357
|
+
}) {
|
|
358
|
+
const value = useMemo(() => {
|
|
359
|
+
if (cache) {
|
|
360
|
+
hydrateAll(cache, store);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
store,
|
|
364
|
+
coordinator,
|
|
365
|
+
mutate: createMutate(store, coordinator)
|
|
366
|
+
};
|
|
367
|
+
}, [store, coordinator, cache]);
|
|
368
|
+
return /* @__PURE__ */ jsx(SteddyContext.Provider, { value, children });
|
|
369
|
+
}
|
|
370
|
+
function useSteddyRuntime() {
|
|
371
|
+
return useContext(SteddyContext);
|
|
372
|
+
}
|
|
373
|
+
function hydrate(key, data, store = defaultStore) {
|
|
374
|
+
store.set(serializeKey(key), {
|
|
375
|
+
data,
|
|
376
|
+
error: void 0,
|
|
377
|
+
timestamp: 0,
|
|
378
|
+
isValidating: false
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function dump(store = defaultStore) {
|
|
382
|
+
const snapshot = {};
|
|
383
|
+
for (const key of store.keys()) {
|
|
384
|
+
const entry = store.get(key);
|
|
385
|
+
if (!entry || entry.data === void 0) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
snapshot[key] = { data: entry.data, timestamp: entry.timestamp };
|
|
389
|
+
}
|
|
390
|
+
return snapshot;
|
|
391
|
+
}
|
|
392
|
+
function hydrateAll(snapshot, store = defaultStore) {
|
|
393
|
+
for (const [key, payload] of Object.entries(snapshot)) {
|
|
394
|
+
store.set(key, {
|
|
395
|
+
data: payload.data,
|
|
396
|
+
error: void 0,
|
|
397
|
+
timestamp: 0,
|
|
398
|
+
isValidating: false
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function clear(key, runtime = defaultRuntime) {
|
|
403
|
+
if (key === void 0) {
|
|
404
|
+
for (const active of runtime.coordinator.getRegisteredKeys()) {
|
|
405
|
+
runtime.coordinator.abort(active);
|
|
406
|
+
}
|
|
407
|
+
runtime.coordinator.reset();
|
|
408
|
+
runtime.store.clear();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const serialized = serializeKey(key);
|
|
412
|
+
runtime.coordinator.abort(serialized);
|
|
413
|
+
runtime.coordinator.unregister(serialized);
|
|
414
|
+
runtime.store.delete(serialized);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/useSteddy.ts
|
|
418
|
+
function useSerializedKey(key) {
|
|
419
|
+
const prevKey = useRef(null);
|
|
420
|
+
const prevSerialized = useRef(null);
|
|
421
|
+
if (key == null) {
|
|
422
|
+
prevKey.current = null;
|
|
423
|
+
prevSerialized.current = null;
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
if (prevSerialized.current != null && keysShallowEqual(prevKey.current, key)) {
|
|
427
|
+
return prevSerialized.current;
|
|
428
|
+
}
|
|
429
|
+
prevKey.current = key;
|
|
430
|
+
prevSerialized.current = serializeKey(key);
|
|
431
|
+
return prevSerialized.current;
|
|
432
|
+
}
|
|
433
|
+
function useSteddy(key, fetcher, options) {
|
|
434
|
+
const { store, coordinator, mutate: runtimeMutate } = useSteddyRuntime();
|
|
435
|
+
const serialized = useSerializedKey(key);
|
|
436
|
+
const fetcherRef = useRef(fetcher);
|
|
437
|
+
fetcherRef.current = fetcher;
|
|
438
|
+
const keyRef = useRef(key);
|
|
439
|
+
keyRef.current = key;
|
|
440
|
+
if (serialized != null && key != null) {
|
|
441
|
+
coordinator.register(
|
|
442
|
+
serialized,
|
|
443
|
+
key,
|
|
444
|
+
(k, ctx) => fetcherRef.current(k, ctx)
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const subscribe = useCallback(
|
|
448
|
+
(onStoreChange) => {
|
|
449
|
+
if (serialized == null || keyRef.current == null) {
|
|
450
|
+
return () => {
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const originalKey = keyRef.current;
|
|
454
|
+
coordinator.register(
|
|
455
|
+
serialized,
|
|
456
|
+
originalKey,
|
|
457
|
+
(k, ctx) => fetcherRef.current(k, ctx)
|
|
458
|
+
);
|
|
459
|
+
const unsubscribe = store.subscribe(serialized, onStoreChange);
|
|
460
|
+
if (!coordinator.isInFlight(serialized)) {
|
|
461
|
+
void coordinator.revalidate(serialized).catch(() => {
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return () => {
|
|
465
|
+
unsubscribe();
|
|
466
|
+
if (store.subscriberCount(serialized) === 0) {
|
|
467
|
+
coordinator.abort(serialized);
|
|
468
|
+
coordinator.unregister(serialized);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
},
|
|
472
|
+
[serialized, store, coordinator]
|
|
473
|
+
);
|
|
474
|
+
const getSnapshot = useCallback(() => {
|
|
475
|
+
if (serialized == null) {
|
|
476
|
+
return EMPTY_SNAPSHOT;
|
|
477
|
+
}
|
|
478
|
+
return store.getSnapshot(serialized);
|
|
479
|
+
}, [serialized, store]);
|
|
480
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
481
|
+
const boundMutate = useCallback(
|
|
482
|
+
(updater, mutateOptions) => {
|
|
483
|
+
if (keyRef.current == null) {
|
|
484
|
+
return Promise.resolve(void 0);
|
|
485
|
+
}
|
|
486
|
+
return runtimeMutate(keyRef.current, updater, mutateOptions);
|
|
487
|
+
},
|
|
488
|
+
[runtimeMutate]
|
|
489
|
+
);
|
|
490
|
+
if (options?.suspense && serialized != null) {
|
|
491
|
+
if (snapshot.error != null) {
|
|
492
|
+
throw snapshot.error;
|
|
493
|
+
}
|
|
494
|
+
if (snapshot.data === void 0) {
|
|
495
|
+
const waiter = coordinator.getInFlightPromise(serialized) ?? coordinator.revalidate(serialized).catch(() => {
|
|
496
|
+
});
|
|
497
|
+
throw waiter;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
data: snapshot.data,
|
|
502
|
+
error: snapshot.error,
|
|
503
|
+
isLoading: serialized != null && snapshot.data === void 0 && snapshot.error == null,
|
|
504
|
+
isValidating: snapshot.isValidating,
|
|
505
|
+
mutate: boundMutate
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function collectPages(getKey, getData, size) {
|
|
509
|
+
const pages = [];
|
|
510
|
+
let previous;
|
|
511
|
+
for (let index = 0; index < size; index++) {
|
|
512
|
+
const key = getKey(index, previous);
|
|
513
|
+
if (key == null) {
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
const serialized = serializeKey(key);
|
|
517
|
+
pages.push({ key, serialized });
|
|
518
|
+
previous = getData(serialized);
|
|
519
|
+
}
|
|
520
|
+
return pages;
|
|
521
|
+
}
|
|
522
|
+
function useSteddyInfinite(getKey, fetcher, options) {
|
|
523
|
+
const { store, coordinator, mutate: runtimeMutate } = useSteddyRuntime();
|
|
524
|
+
const [size, setSizeState] = useState(options?.initialSize ?? 1);
|
|
525
|
+
const fetcherRef = useRef(fetcher);
|
|
526
|
+
fetcherRef.current = fetcher;
|
|
527
|
+
const getKeyRef = useRef(getKey);
|
|
528
|
+
getKeyRef.current = getKey;
|
|
529
|
+
const pages = collectPages(
|
|
530
|
+
getKey,
|
|
531
|
+
(serialized) => store.get(serialized)?.data,
|
|
532
|
+
size
|
|
533
|
+
);
|
|
534
|
+
const serializedList = pages.map((page) => page.serialized).join("\0");
|
|
535
|
+
for (const page of pages) {
|
|
536
|
+
coordinator.register(
|
|
537
|
+
page.serialized,
|
|
538
|
+
page.key,
|
|
539
|
+
(k, ctx) => fetcherRef.current(k, ctx)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const subscribe = useCallback(
|
|
543
|
+
(onStoreChange) => {
|
|
544
|
+
const currentPages = collectPages(
|
|
545
|
+
getKeyRef.current,
|
|
546
|
+
(serialized) => store.get(serialized)?.data,
|
|
547
|
+
size
|
|
548
|
+
);
|
|
549
|
+
const unsubscribes = currentPages.map((page) => {
|
|
550
|
+
coordinator.register(
|
|
551
|
+
page.serialized,
|
|
552
|
+
page.key,
|
|
553
|
+
(k, ctx) => fetcherRef.current(k, ctx)
|
|
554
|
+
);
|
|
555
|
+
if (!coordinator.isInFlight(page.serialized)) {
|
|
556
|
+
void coordinator.revalidate(page.serialized).catch(() => {
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return store.subscribe(page.serialized, onStoreChange);
|
|
560
|
+
});
|
|
561
|
+
return () => {
|
|
562
|
+
for (const [index, page] of currentPages.entries()) {
|
|
563
|
+
unsubscribes[index]?.();
|
|
564
|
+
if (store.subscriberCount(page.serialized) === 0) {
|
|
565
|
+
coordinator.abort(page.serialized);
|
|
566
|
+
coordinator.unregister(page.serialized);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
},
|
|
571
|
+
[serializedList, size, store, coordinator]
|
|
572
|
+
);
|
|
573
|
+
const snapshotRef = useRef({
|
|
574
|
+
fingerprint: "",
|
|
575
|
+
data: void 0,
|
|
576
|
+
error: void 0,
|
|
577
|
+
isValidating: false
|
|
578
|
+
});
|
|
579
|
+
const getSnapshot = useCallback(() => {
|
|
580
|
+
const currentPages = collectPages(
|
|
581
|
+
getKeyRef.current,
|
|
582
|
+
(serialized) => store.get(serialized)?.data,
|
|
583
|
+
size
|
|
584
|
+
);
|
|
585
|
+
const fingerprint = currentPages.map((page) => {
|
|
586
|
+
const entry = store.getSnapshot(page.serialized);
|
|
587
|
+
return `${page.serialized}:${entry.timestamp}:${entry.isValidating ? 1 : 0}:${entry.error == null ? 0 : 1}:${entry.data === void 0 ? 0 : 1}`;
|
|
588
|
+
}).join("|");
|
|
589
|
+
if (snapshotRef.current.fingerprint === fingerprint) {
|
|
590
|
+
return snapshotRef.current;
|
|
591
|
+
}
|
|
592
|
+
const loaded = [];
|
|
593
|
+
let error;
|
|
594
|
+
let isValidating = false;
|
|
595
|
+
let missing = false;
|
|
596
|
+
for (const page of currentPages) {
|
|
597
|
+
const entry = store.getSnapshot(page.serialized);
|
|
598
|
+
if (entry.isValidating) {
|
|
599
|
+
isValidating = true;
|
|
600
|
+
}
|
|
601
|
+
if (entry.error != null && error === void 0) {
|
|
602
|
+
error = entry.error;
|
|
603
|
+
}
|
|
604
|
+
if (entry.data === void 0) {
|
|
605
|
+
missing = true;
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
loaded.push(entry.data);
|
|
609
|
+
}
|
|
610
|
+
snapshotRef.current = {
|
|
611
|
+
fingerprint,
|
|
612
|
+
data: missing && loaded.length === 0 ? void 0 : loaded,
|
|
613
|
+
error,
|
|
614
|
+
isValidating
|
|
615
|
+
};
|
|
616
|
+
return snapshotRef.current;
|
|
617
|
+
}, [serializedList, size, store]);
|
|
618
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
619
|
+
const setSize = useCallback(
|
|
620
|
+
(next) => {
|
|
621
|
+
setSizeState(
|
|
622
|
+
(current) => typeof next === "function" ? next(current) : next
|
|
623
|
+
);
|
|
624
|
+
},
|
|
625
|
+
[]
|
|
626
|
+
);
|
|
627
|
+
const boundMutate = useCallback(
|
|
628
|
+
async (updater, mutateOptions) => {
|
|
629
|
+
const currentPages = collectPages(
|
|
630
|
+
getKeyRef.current,
|
|
631
|
+
(serialized) => store.get(serialized)?.data,
|
|
632
|
+
size
|
|
633
|
+
);
|
|
634
|
+
const first2 = currentPages[0];
|
|
635
|
+
if (!first2) {
|
|
636
|
+
return void 0;
|
|
637
|
+
}
|
|
638
|
+
if (updater !== void 0 && typeof updater !== "function") {
|
|
639
|
+
const pagesValue = updater;
|
|
640
|
+
const firstPage = pagesValue[0];
|
|
641
|
+
if (firstPage !== void 0) {
|
|
642
|
+
await runtimeMutate(first2.key, firstPage, mutateOptions);
|
|
643
|
+
}
|
|
644
|
+
return pagesValue;
|
|
645
|
+
}
|
|
646
|
+
await Promise.all(
|
|
647
|
+
currentPages.map(
|
|
648
|
+
(page) => coordinator.revalidate(page.serialized, void 0, { force: true }).catch(() => {
|
|
649
|
+
})
|
|
650
|
+
)
|
|
651
|
+
);
|
|
652
|
+
return currentPages.map((page) => store.get(page.serialized)?.data).filter((page) => page !== void 0);
|
|
653
|
+
},
|
|
654
|
+
[coordinator, runtimeMutate, size, store]
|
|
655
|
+
);
|
|
656
|
+
const first = pages[0];
|
|
657
|
+
const firstEntry = first ? store.getSnapshot(first.serialized) : void 0;
|
|
658
|
+
return {
|
|
659
|
+
data: snapshot.data,
|
|
660
|
+
error: snapshot.error,
|
|
661
|
+
isLoading: first != null && firstEntry?.data === void 0 && firstEntry?.error == null,
|
|
662
|
+
isValidating: snapshot.isValidating,
|
|
663
|
+
size,
|
|
664
|
+
setSize,
|
|
665
|
+
mutate: boundMutate
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/plugins/focus.ts
|
|
670
|
+
function focusRevalidate(coordinator) {
|
|
671
|
+
const onFocus = () => {
|
|
672
|
+
for (const key of coordinator.getRegisteredKeys()) {
|
|
673
|
+
void coordinator.revalidate(key);
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
window.addEventListener("focus", onFocus);
|
|
677
|
+
return () => {
|
|
678
|
+
window.removeEventListener("focus", onFocus);
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/plugins/reconnect.ts
|
|
683
|
+
function reconnectRevalidate(coordinator) {
|
|
684
|
+
const onOnline = () => {
|
|
685
|
+
for (const key of coordinator.getRegisteredKeys()) {
|
|
686
|
+
void coordinator.revalidate(key);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
window.addEventListener("online", onOnline);
|
|
690
|
+
return () => {
|
|
691
|
+
window.removeEventListener("online", onOnline);
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/plugins/polling.ts
|
|
696
|
+
function pollingRevalidate(coordinator, key, interval) {
|
|
697
|
+
const id = setInterval(() => {
|
|
698
|
+
void coordinator.revalidate(key);
|
|
699
|
+
}, interval);
|
|
700
|
+
return () => {
|
|
701
|
+
clearInterval(id);
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/plugins/retry.ts
|
|
706
|
+
function isAbortError2(error) {
|
|
707
|
+
return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
708
|
+
}
|
|
709
|
+
function sleep(ms, signal) {
|
|
710
|
+
return new Promise((resolve, reject) => {
|
|
711
|
+
if (signal.aborted) {
|
|
712
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const id = setTimeout(resolve, ms);
|
|
716
|
+
const onAbort = () => {
|
|
717
|
+
clearTimeout(id);
|
|
718
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
719
|
+
};
|
|
720
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
function retryOnError(_coordinator, options) {
|
|
724
|
+
return (fetcher) => async (key, context) => {
|
|
725
|
+
let lastError;
|
|
726
|
+
for (let attempt = 0; attempt < options.attempts; attempt++) {
|
|
727
|
+
if (context.signal.aborted) {
|
|
728
|
+
throw context.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
return await fetcher(key, context);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
lastError = error;
|
|
734
|
+
if (isAbortError2(error) || attempt === options.attempts - 1) {
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
await sleep(options.backoff * 2 ** attempt, context.signal);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
throw lastError;
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/plugins/ttl.ts
|
|
745
|
+
function ttlEvict(coordinator, options) {
|
|
746
|
+
const maxAge = options.maxAge;
|
|
747
|
+
const maxKeys = options.maxKeys;
|
|
748
|
+
const evictOptions = {
|
|
749
|
+
...maxAge != null ? { maxAge } : {},
|
|
750
|
+
...maxKeys != null ? { maxKeys } : {}
|
|
751
|
+
};
|
|
752
|
+
const tick = () => {
|
|
753
|
+
coordinator.evict(evictOptions);
|
|
754
|
+
};
|
|
755
|
+
tick();
|
|
756
|
+
const id = setInterval(tick, options.interval ?? 3e4);
|
|
757
|
+
return () => {
|
|
758
|
+
clearInterval(id);
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export { DEDUP_WINDOW_MS, SteddyProvider, clear, createCoordinator, createMutate, createRuntime, createStore, defaultCoordinator, defaultStore, dump, focusRevalidate, hydrate, hydrateAll, mutate, pollingRevalidate, reconnectRevalidate, retryOnError, serializeKey, ttlEvict, useSteddy, useSteddyInfinite };
|
|
763
|
+
//# sourceMappingURL=index.js.map
|
|
764
|
+
//# sourceMappingURL=index.js.map
|