steddy 0.1.2 → 0.1.3

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useMemo, useRef, useCallback, useSyncExternalStore, useState, useContext } from 'react';
1
+ import { createContext, useRef, useEffect, useMemo, useCallback, useSyncExternalStore, useState, useContext } from 'react';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
 
4
4
  // src/useSteddy.ts
@@ -55,9 +55,13 @@ function createCoordinator(store) {
55
55
  }
56
56
  }
57
57
  const coordinator = {
58
- register(serializedKey, key, fetcher) {
58
+ register(serializedKey, key, fetcher, options) {
59
59
  cancelRelease(serializedKey);
60
- registered.set(serializedKey, { key, fetcher });
60
+ registered.set(serializedKey, {
61
+ key,
62
+ fetcher,
63
+ staleTime: options?.staleTime ?? DEDUP_WINDOW_MS
64
+ });
61
65
  },
62
66
  unregister(serializedKey) {
63
67
  cancelRelease(serializedKey);
@@ -78,7 +82,8 @@ function createCoordinator(store) {
78
82
  async revalidate(serializedKey, fetcher, options) {
79
83
  if (!options?.force) {
80
84
  const entry = store.get(serializedKey);
81
- if (entry && !entry.isValidating && entry.error == null && entry.data !== void 0 && Date.now() - entry.timestamp < DEDUP_WINDOW_MS) {
85
+ const staleTime = registered.get(serializedKey)?.staleTime ?? DEDUP_WINDOW_MS;
86
+ if (entry && !entry.isValidating && entry.error == null && entry.hasData && Date.now() - entry.timestamp < staleTime) {
82
87
  emit({ type: "dedup", key: serializedKey });
83
88
  return;
84
89
  }
@@ -115,6 +120,7 @@ function createCoordinator(store) {
115
120
  const previous = store.get(serializedKey);
116
121
  store.set(serializedKey, {
117
122
  data: previous?.data,
123
+ hasData: previous?.hasData ?? false,
118
124
  error: previous?.error,
119
125
  timestamp: previous?.timestamp ?? 0,
120
126
  isValidating: true
@@ -128,6 +134,7 @@ function createCoordinator(store) {
128
134
  inflight.delete(serializedKey);
129
135
  store.set(serializedKey, {
130
136
  data,
137
+ hasData: true,
131
138
  error: void 0,
132
139
  timestamp: Date.now(),
133
140
  isValidating: false
@@ -146,6 +153,7 @@ function createCoordinator(store) {
146
153
  const current = store.get(serializedKey);
147
154
  store.set(serializedKey, {
148
155
  data: current?.data,
156
+ hasData: current?.hasData ?? false,
149
157
  error,
150
158
  timestamp: current?.timestamp ?? 0,
151
159
  isValidating: false
@@ -238,6 +246,7 @@ function createCoordinator(store) {
238
246
  // src/store.ts
239
247
  var EMPTY_SNAPSHOT = Object.freeze({
240
248
  data: void 0,
249
+ hasData: false,
241
250
  error: void 0,
242
251
  timestamp: 0,
243
252
  isValidating: false
@@ -259,6 +268,10 @@ function createStore() {
259
268
  return entries.get(key);
260
269
  },
261
270
  set(key, entry) {
271
+ const existing = entries.get(key);
272
+ if (existing && Object.is(existing.data, entry.data) && existing.hasData === entry.hasData && existing.error === entry.error && existing.timestamp === entry.timestamp && existing.isValidating === entry.isValidating) {
273
+ return;
274
+ }
262
275
  entries.set(key, entry);
263
276
  emit(key);
264
277
  },
@@ -360,6 +373,7 @@ function createMutate(store, coordinator) {
360
373
  if (isThenable(next)) {
361
374
  store.set(serialized, {
362
375
  data: currentData,
376
+ hasData: previous?.hasData ?? false,
363
377
  error: previous?.error,
364
378
  timestamp: previous?.timestamp ?? 0,
365
379
  isValidating: true
@@ -367,6 +381,7 @@ function createMutate(store, coordinator) {
367
381
  const resolved = await next;
368
382
  store.set(serialized, {
369
383
  data: resolved,
384
+ hasData: true,
370
385
  error: void 0,
371
386
  timestamp: Date.now(),
372
387
  isValidating: revalidate
@@ -374,6 +389,7 @@ function createMutate(store, coordinator) {
374
389
  } else {
375
390
  store.set(serialized, {
376
391
  data: next,
392
+ hasData: true,
377
393
  error: void 0,
378
394
  timestamp: Date.now(),
379
395
  isValidating: revalidate
@@ -390,6 +406,7 @@ function createMutate(store, coordinator) {
390
406
  const current = store.get(serialized);
391
407
  store.set(serialized, {
392
408
  data: current?.data,
409
+ hasData: current?.hasData ?? false,
393
410
  error,
394
411
  timestamp: current?.timestamp ?? Date.now(),
395
412
  isValidating: false
@@ -421,24 +438,44 @@ function SteddyProvider({
421
438
  cache,
422
439
  children
423
440
  }) {
424
- const value = useMemo(() => {
425
- if (cache) {
426
- hydrateAll(cache, store);
441
+ const hydratedFingerprint = useRef(null);
442
+ useEffect(() => {
443
+ if (!cache) {
444
+ hydratedFingerprint.current = null;
445
+ return;
427
446
  }
428
- return {
447
+ const fingerprint = JSON.stringify(cache);
448
+ if (hydratedFingerprint.current === fingerprint) {
449
+ return;
450
+ }
451
+ hydratedFingerprint.current = fingerprint;
452
+ hydrateAll(cache, store);
453
+ }, [cache, store]);
454
+ const value = useMemo(
455
+ () => ({
429
456
  store,
430
457
  coordinator,
431
458
  mutate: createMutate(store, coordinator)
432
- };
433
- }, [store, coordinator, cache]);
459
+ }),
460
+ [store, coordinator]
461
+ );
434
462
  return /* @__PURE__ */ jsx(SteddyContext.Provider, { value, children });
435
463
  }
464
+ var warnedDefaultOnServer = false;
436
465
  function useSteddyRuntime() {
437
- return useContext(SteddyContext);
466
+ const runtime = useContext(SteddyContext);
467
+ if (process.env.NODE_ENV !== "production" && typeof window === "undefined" && runtime === defaultRuntime && !warnedDefaultOnServer) {
468
+ warnedDefaultOnServer = true;
469
+ console.warn(
470
+ "[steddy] useSteddy on the server without SteddyProvider shares one cache across requests. Use createRuntime() per request."
471
+ );
472
+ }
473
+ return runtime;
438
474
  }
439
475
  function hydrate(key, data, store = defaultStore) {
440
476
  store.set(serializeKey(key), {
441
477
  data,
478
+ hasData: true,
442
479
  error: void 0,
443
480
  timestamp: 0,
444
481
  isValidating: false
@@ -448,7 +485,7 @@ function dump(store = defaultStore) {
448
485
  const snapshot = {};
449
486
  for (const key of store.keys()) {
450
487
  const entry = store.get(key);
451
- if (!entry || entry.data === void 0) {
488
+ if (!entry?.hasData) {
452
489
  continue;
453
490
  }
454
491
  snapshot[key] = { data: entry.data, timestamp: entry.timestamp };
@@ -459,12 +496,23 @@ function hydrateAll(snapshot, store = defaultStore) {
459
496
  for (const [key, payload] of Object.entries(snapshot)) {
460
497
  store.set(key, {
461
498
  data: payload.data,
499
+ hasData: true,
462
500
  error: void 0,
463
- timestamp: 0,
501
+ timestamp: payload.timestamp,
464
502
  isValidating: false
465
503
  });
466
504
  }
467
505
  }
506
+ async function prefetch(key, fetcher, runtime = defaultRuntime) {
507
+ const serialized = serializeKey(key);
508
+ runtime.coordinator.register(serialized, key, fetcher);
509
+ try {
510
+ await runtime.coordinator.revalidate(serialized);
511
+ } catch {
512
+ } finally {
513
+ runtime.coordinator.unregister(serialized);
514
+ }
515
+ }
468
516
  function clear(key, runtime = defaultRuntime) {
469
517
  if (key === void 0) {
470
518
  for (const active of runtime.coordinator.getRegisteredKeys()) {
@@ -503,11 +551,15 @@ function useSteddy(key, fetcher, options) {
503
551
  fetcherRef.current = fetcher;
504
552
  const keyRef = useRef(key);
505
553
  keyRef.current = key;
554
+ const staleTime = options?.staleTime ?? DEDUP_WINDOW_MS;
555
+ const staleTimeRef = useRef(staleTime);
556
+ staleTimeRef.current = staleTime;
506
557
  if (serialized != null && key != null) {
507
558
  coordinator.register(
508
559
  serialized,
509
560
  key,
510
- (k, ctx) => fetcherRef.current(k, ctx)
561
+ (k, ctx) => fetcherRef.current(k, ctx),
562
+ { staleTime: staleTimeRef.current }
511
563
  );
512
564
  }
513
565
  const subscribe = useCallback(
@@ -520,7 +572,8 @@ function useSteddy(key, fetcher, options) {
520
572
  coordinator.register(
521
573
  serialized,
522
574
  originalKey,
523
- (k, ctx) => fetcherRef.current(k, ctx)
575
+ (k, ctx) => fetcherRef.current(k, ctx),
576
+ { staleTime: staleTimeRef.current }
524
577
  );
525
578
  const unsubscribe = store.subscribe(serialized, onStoreChange);
526
579
  if (!coordinator.isInFlight(serialized)) {
@@ -556,15 +609,15 @@ function useSteddy(key, fetcher, options) {
556
609
  const previousRef = useRef(
557
610
  void 0
558
611
  );
559
- if (serialized != null && snapshot.data !== void 0) {
612
+ if (serialized != null && snapshot.hasData) {
560
613
  previousRef.current = { serialized, data: snapshot.data };
561
614
  }
562
- const data = keepPreviousData && serialized != null && snapshot.data === void 0 && snapshot.error == null && previousRef.current != null && previousRef.current.serialized !== serialized ? previousRef.current.data : snapshot.data;
615
+ const data = keepPreviousData && serialized != null && !snapshot.hasData && snapshot.error == null && previousRef.current != null && previousRef.current.serialized !== serialized ? previousRef.current.data : snapshot.data;
563
616
  if (options?.suspense && serialized != null) {
564
617
  if (snapshot.error != null) {
565
618
  throw snapshot.error;
566
619
  }
567
- if (snapshot.data === void 0 && data === void 0) {
620
+ if (!snapshot.hasData && data === void 0) {
568
621
  const waiter = coordinator.getInFlightPromise(serialized) ?? coordinator.revalidate(serialized).catch(() => {
569
622
  });
570
623
  throw waiter;
@@ -573,7 +626,7 @@ function useSteddy(key, fetcher, options) {
573
626
  return {
574
627
  data,
575
628
  error: snapshot.error,
576
- isLoading: serialized != null && data === void 0 && snapshot.error == null,
629
+ isLoading: serialized != null && !snapshot.hasData && snapshot.error == null && data === void 0,
577
630
  isValidating: snapshot.isValidating,
578
631
  mutate: boundMutate
579
632
  };
@@ -664,7 +717,7 @@ function useSteddyInfinite(getKey, fetcher, options) {
664
717
  );
665
718
  const fingerprint = currentPages.map((page) => {
666
719
  const entry = store.getSnapshot(page.serialized);
667
- return `${page.serialized}:${entry.timestamp}:${entry.isValidating ? 1 : 0}:${entry.error == null ? 0 : 1}:${entry.data === void 0 ? 0 : 1}`;
720
+ return `${page.serialized}:${entry.timestamp}:${entry.isValidating ? 1 : 0}:${entry.error == null ? 0 : 1}:${entry.hasData ? 1 : 0}`;
668
721
  }).join("|");
669
722
  if (snapshotRef.current.fingerprint === fingerprint) {
670
723
  return snapshotRef.current;
@@ -681,7 +734,7 @@ function useSteddyInfinite(getKey, fetcher, options) {
681
734
  if (entry.error != null && error === void 0) {
682
735
  error = entry.error;
683
736
  }
684
- if (entry.data === void 0) {
737
+ if (!entry.hasData) {
685
738
  missing = true;
686
739
  break;
687
740
  }
@@ -748,6 +801,7 @@ function useSteddyInfinite(getKey, fetcher, options) {
748
801
  }
749
802
  store.set(page.serialized, {
750
803
  data: pagesValue[index],
804
+ hasData: true,
751
805
  error: void 0,
752
806
  timestamp: Date.now(),
753
807
  isValidating: revalidate
@@ -777,7 +831,7 @@ function useSteddyInfinite(getKey, fetcher, options) {
777
831
  return {
778
832
  data: snapshot.data,
779
833
  error: snapshot.error,
780
- isLoading: first != null && firstEntry?.data === void 0 && firstEntry?.error == null,
834
+ isLoading: first != null && !firstEntry?.hasData && firstEntry?.error == null,
781
835
  isValidating: snapshot.isValidating,
782
836
  size,
783
837
  setSize,
@@ -785,23 +839,37 @@ function useSteddyInfinite(getKey, fetcher, options) {
785
839
  };
786
840
  }
787
841
 
842
+ // src/plugins/subscribedKeys.ts
843
+ function keysWithSubscribers(keys, store) {
844
+ return keys.filter((key) => store.subscriberCount(key) > 0);
845
+ }
846
+
788
847
  // src/plugins/focus.ts
789
- function focusRevalidate(coordinator) {
790
- const onFocus = () => {
791
- for (const key of coordinator.getRegisteredKeys()) {
848
+ function focusRevalidate(coordinator, store) {
849
+ const run = () => {
850
+ if (typeof document !== "undefined" && document.visibilityState !== "visible") {
851
+ return;
852
+ }
853
+ const keys = store ? keysWithSubscribers(coordinator.getRegisteredKeys(), store) : coordinator.getRegisteredKeys();
854
+ for (const key of keys) {
792
855
  void coordinator.revalidate(key);
793
856
  }
794
857
  };
795
- window.addEventListener("focus", onFocus);
858
+ if (typeof document === "undefined") {
859
+ return () => {
860
+ };
861
+ }
862
+ document.addEventListener("visibilitychange", run);
796
863
  return () => {
797
- window.removeEventListener("focus", onFocus);
864
+ document.removeEventListener("visibilitychange", run);
798
865
  };
799
866
  }
800
867
 
801
868
  // src/plugins/reconnect.ts
802
- function reconnectRevalidate(coordinator) {
869
+ function reconnectRevalidate(coordinator, store) {
803
870
  const onOnline = () => {
804
- for (const key of coordinator.getRegisteredKeys()) {
871
+ const keys = store ? keysWithSubscribers(coordinator.getRegisteredKeys(), store) : coordinator.getRegisteredKeys();
872
+ for (const key of keys) {
805
873
  void coordinator.revalidate(key);
806
874
  }
807
875
  };
@@ -925,6 +993,20 @@ function measurePerf(coordinator, onEvent) {
925
993
  };
926
994
  }
927
995
 
928
- export { DEDUP_WINDOW_MS, SteddyProvider, UNSUBSCRIBE_GRACE_MS, clear, createCoordinator, createMutate, createRuntime, createStore, defaultCoordinator, defaultStore, dump, focusRevalidate, hydrate, hydrateAll, measurePerf, mutate, pollingRevalidate, reconnectRevalidate, retryOnError, serializeKey, ttlEvict, useSteddy, useSteddyInfinite };
996
+ // src/plugins/attachDefaults.ts
997
+ function attachDefaults(coordinator, store, options) {
998
+ const stops = [
999
+ focusRevalidate(coordinator, store),
1000
+ reconnectRevalidate(coordinator, store),
1001
+ ttlEvict(coordinator, options?.ttl ?? { maxAge: 3e5 })
1002
+ ];
1003
+ return () => {
1004
+ for (const stop of stops) {
1005
+ stop();
1006
+ }
1007
+ };
1008
+ }
1009
+
1010
+ export { DEDUP_WINDOW_MS, SteddyProvider, UNSUBSCRIBE_GRACE_MS, attachDefaults, clear, createCoordinator, createMutate, createRuntime, createStore, defaultCoordinator, defaultStore, dump, focusRevalidate, hydrate, hydrateAll, measurePerf, mutate, pollingRevalidate, prefetch, reconnectRevalidate, retryOnError, serializeKey, ttlEvict, useSteddy, useSteddyInfinite };
929
1011
  //# sourceMappingURL=index.js.map
930
1012
  //# sourceMappingURL=index.js.map