reactjrx 1.47.0 → 1.48.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/dist/index.cjs CHANGED
@@ -232,6 +232,107 @@ const createLocalforageAdapter = (forage) => ({
232
232
  await forage.setItem(key, JSON.stringify(value));
233
233
  }
234
234
  });
235
+ const createLocalStorageAdapter = (forage) => ({
236
+ getItem: async (key) => {
237
+ const serializedValue = forage.getItem(key);
238
+ if (!serializedValue)
239
+ return void 0;
240
+ return JSON.parse(serializedValue);
241
+ },
242
+ setItem: async (key, value) => {
243
+ forage.setItem(key, JSON.stringify(value));
244
+ }
245
+ });
246
+ const IDENTIFIER_PERSISTANCE_KEY = "__reactjrx";
247
+ const getNormalizedPersistanceValue = (unknownValue) => {
248
+ if (typeof unknownValue === "object" && unknownValue !== null && IDENTIFIER_PERSISTANCE_KEY in unknownValue && unknownValue[IDENTIFIER_PERSISTANCE_KEY] === IDENTIFIER_PERSISTANCE_KEY) {
249
+ return unknownValue;
250
+ }
251
+ return void 0;
252
+ };
253
+ const persistValue = async ({
254
+ adapter,
255
+ signal: signal2,
256
+ version
257
+ }) => {
258
+ const state = signal2.getValue();
259
+ const value = {
260
+ value: state,
261
+ [IDENTIFIER_PERSISTANCE_KEY]: IDENTIFIER_PERSISTANCE_KEY,
262
+ migrationVersion: version
263
+ };
264
+ await adapter.setItem(signal2.config.key, value);
265
+ };
266
+ const hydrateValueToSignal = ({
267
+ adapter,
268
+ version,
269
+ signal: signal2
270
+ }) => {
271
+ return rxjs.from(adapter.getItem(signal2.config.key)).pipe(
272
+ rxjs.switchMap((value) => {
273
+ const normalizedValue = getNormalizedPersistanceValue(value);
274
+ if (!normalizedValue)
275
+ return rxjs.of(value);
276
+ if (normalizedValue.migrationVersion !== void 0 && version > normalizedValue.migrationVersion) {
277
+ return rxjs.of(value);
278
+ }
279
+ signal2.setValue(value.value);
280
+ return rxjs.of(value);
281
+ })
282
+ );
283
+ };
284
+ const usePersistSignals = ({
285
+ entries = [],
286
+ onReady,
287
+ adapter = createLocalStorageAdapter(localStorage)
288
+ }) => {
289
+ const entriesRef = useLiveRef(entries);
290
+ const onReadyRef = useLiveRef(onReady);
291
+ const adapterRef = useLiveRef(adapter);
292
+ const isHydrated = useObserve(
293
+ () => {
294
+ const entries2 = entriesRef.current;
295
+ const stream = entries2.length === 0 ? rxjs.of(true) : rxjs.zip(
296
+ ...entries2.map(
297
+ ({ signal: signal2, version }) => hydrateValueToSignal({
298
+ adapter: adapterRef.current,
299
+ signal: signal2,
300
+ version
301
+ })
302
+ )
303
+ ).pipe(rxjs.map(() => true));
304
+ return stream.pipe(
305
+ rxjs.tap(() => {
306
+ if (onReadyRef.current != null)
307
+ onReadyRef.current();
308
+ }),
309
+ rxjs.catchError((error) => {
310
+ console.error("Unable to hydrate", error);
311
+ return rxjs.EMPTY;
312
+ })
313
+ );
314
+ },
315
+ { defaultValue: false },
316
+ []
317
+ );
318
+ useSubscribe(() => {
319
+ return !isHydrated ? rxjs.EMPTY : rxjs.merge(
320
+ ...entriesRef.current.map(
321
+ ({ signal: signal2, version }) => signal2.subject.pipe(
322
+ rxjs.throttleTime(500, rxjs.asyncScheduler, {
323
+ trailing: true
324
+ }),
325
+ rxjs.switchMap(
326
+ () => rxjs.from(
327
+ persistValue({ adapter: adapterRef.current, signal: signal2, version })
328
+ )
329
+ )
330
+ )
331
+ )
332
+ );
333
+ }, [isHydrated, adapterRef]);
334
+ return { isHydrated };
335
+ };
235
336
  const useUnmountObservable = () => {
236
337
  const subject = useSubject({
237
338
  onBeforeComplete: () => {
@@ -1575,6 +1676,7 @@ exports.useBehaviorSubject = useBehaviorSubject;
1575
1676
  exports.useLiveRef = useLiveRef;
1576
1677
  exports.useObserve = useObserve;
1577
1678
  exports.useObserveCallback = useObserveCallback;
1679
+ exports.usePersistSignals = usePersistSignals;
1578
1680
  exports.useQuery = useQuery;
1579
1681
  exports.useQueryClient = useQueryClient;
1580
1682
  exports.useSignalValue = useSignalValue;
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./lib/state/useSignalValue";
9
9
  export * from "./lib/state/constants";
10
10
  export * from "./lib/state/persistance/adapters/createSharedStoreAdapter";
11
11
  export * from "./lib/state/persistance/adapters/createLocalforageAdapter";
12
+ export * from "./lib/state/persistance/usePersistSignals";
12
13
  export * from "./lib/utils/useUnmountObservable";
13
14
  export * from "./lib/utils/retryBackoff";
14
15
  export * from "./lib/utils/useLiveRef";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
7
  import { useRef, useMemo, useCallback, useSyncExternalStore, useEffect, createContext, memo, useContext } from "react";
8
- import { distinctUntilChanged, tap, finalize, catchError, EMPTY, Subject, identity, BehaviorSubject, defer, iif, timer, throwError, scan, merge, map, take, startWith, of, combineLatest, from, first, takeUntil, filter, concatMap as concatMap$1, switchMap, mergeMap, fromEvent, skip, throttleTime, withLatestFrom, retry, shareReplay, endWith, delay, share, pairwise, NEVER, takeWhile } from "rxjs";
8
+ import { distinctUntilChanged, tap, finalize, catchError, EMPTY, Subject, identity, BehaviorSubject, of, zip, map, merge, throttleTime, asyncScheduler, switchMap, from, defer, iif, timer, throwError, scan, take, startWith, combineLatest, first, takeUntil, filter, concatMap as concatMap$1, mergeMap, fromEvent, skip, withLatestFrom, retry, shareReplay, endWith, delay, share, pairwise, NEVER, takeWhile } from "rxjs";
9
9
  import { retryWhen, concatMap, tap as tap$1 } from "rxjs/operators";
10
10
  import { jsxs, jsx } from "react/jsx-runtime";
11
11
  const useLiveRef = (value) => {
@@ -230,6 +230,107 @@ const createLocalforageAdapter = (forage) => ({
230
230
  await forage.setItem(key, JSON.stringify(value));
231
231
  }
232
232
  });
233
+ const createLocalStorageAdapter = (forage) => ({
234
+ getItem: async (key) => {
235
+ const serializedValue = forage.getItem(key);
236
+ if (!serializedValue)
237
+ return void 0;
238
+ return JSON.parse(serializedValue);
239
+ },
240
+ setItem: async (key, value) => {
241
+ forage.setItem(key, JSON.stringify(value));
242
+ }
243
+ });
244
+ const IDENTIFIER_PERSISTANCE_KEY = "__reactjrx";
245
+ const getNormalizedPersistanceValue = (unknownValue) => {
246
+ if (typeof unknownValue === "object" && unknownValue !== null && IDENTIFIER_PERSISTANCE_KEY in unknownValue && unknownValue[IDENTIFIER_PERSISTANCE_KEY] === IDENTIFIER_PERSISTANCE_KEY) {
247
+ return unknownValue;
248
+ }
249
+ return void 0;
250
+ };
251
+ const persistValue = async ({
252
+ adapter,
253
+ signal: signal2,
254
+ version
255
+ }) => {
256
+ const state = signal2.getValue();
257
+ const value = {
258
+ value: state,
259
+ [IDENTIFIER_PERSISTANCE_KEY]: IDENTIFIER_PERSISTANCE_KEY,
260
+ migrationVersion: version
261
+ };
262
+ await adapter.setItem(signal2.config.key, value);
263
+ };
264
+ const hydrateValueToSignal = ({
265
+ adapter,
266
+ version,
267
+ signal: signal2
268
+ }) => {
269
+ return from(adapter.getItem(signal2.config.key)).pipe(
270
+ switchMap((value) => {
271
+ const normalizedValue = getNormalizedPersistanceValue(value);
272
+ if (!normalizedValue)
273
+ return of(value);
274
+ if (normalizedValue.migrationVersion !== void 0 && version > normalizedValue.migrationVersion) {
275
+ return of(value);
276
+ }
277
+ signal2.setValue(value.value);
278
+ return of(value);
279
+ })
280
+ );
281
+ };
282
+ const usePersistSignals = ({
283
+ entries = [],
284
+ onReady,
285
+ adapter = createLocalStorageAdapter(localStorage)
286
+ }) => {
287
+ const entriesRef = useLiveRef(entries);
288
+ const onReadyRef = useLiveRef(onReady);
289
+ const adapterRef = useLiveRef(adapter);
290
+ const isHydrated = useObserve(
291
+ () => {
292
+ const entries2 = entriesRef.current;
293
+ const stream = entries2.length === 0 ? of(true) : zip(
294
+ ...entries2.map(
295
+ ({ signal: signal2, version }) => hydrateValueToSignal({
296
+ adapter: adapterRef.current,
297
+ signal: signal2,
298
+ version
299
+ })
300
+ )
301
+ ).pipe(map(() => true));
302
+ return stream.pipe(
303
+ tap(() => {
304
+ if (onReadyRef.current != null)
305
+ onReadyRef.current();
306
+ }),
307
+ catchError((error) => {
308
+ console.error("Unable to hydrate", error);
309
+ return EMPTY;
310
+ })
311
+ );
312
+ },
313
+ { defaultValue: false },
314
+ []
315
+ );
316
+ useSubscribe(() => {
317
+ return !isHydrated ? EMPTY : merge(
318
+ ...entriesRef.current.map(
319
+ ({ signal: signal2, version }) => signal2.subject.pipe(
320
+ throttleTime(500, asyncScheduler, {
321
+ trailing: true
322
+ }),
323
+ switchMap(
324
+ () => from(
325
+ persistValue({ adapter: adapterRef.current, signal: signal2, version })
326
+ )
327
+ )
328
+ )
329
+ )
330
+ );
331
+ }, [isHydrated, adapterRef]);
332
+ return { isHydrated };
333
+ };
233
334
  const useUnmountObservable = () => {
234
335
  const subject = useSubject({
235
336
  onBeforeComplete: () => {
@@ -1574,6 +1675,7 @@ export {
1574
1675
  useLiveRef,
1575
1676
  useObserve,
1576
1677
  useObserveCallback,
1678
+ usePersistSignals,
1577
1679
  useQuery,
1578
1680
  useQueryClient,
1579
1681
  useSignalValue,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reactjrx",
3
3
  "private": false,
4
- "version": "1.47.0",
4
+ "version": "1.48.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"