@plq/use-persisted-state 1.4.1 → 1.4.2

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.
Files changed (63) hide show
  1. package/lib/create-async-persisted-state.d.ts.map +1 -1
  2. package/lib/create-async-persisted-state.js +10 -50
  3. package/lib/create-async-persisted-state.js.map +1 -1
  4. package/lib/create-persisted-state.d.ts.map +1 -1
  5. package/lib/create-persisted-state.js +4 -16
  6. package/lib/create-persisted-state.js.map +1 -1
  7. package/lib/index.d.ts.map +1 -1
  8. package/lib/storages/chrome-storage.d.ts.map +1 -1
  9. package/lib/storages/chrome-storage.js +6 -15
  10. package/lib/storages/chrome-storage.js.map +1 -1
  11. package/lib/storages/local-storage.d.ts.map +1 -1
  12. package/lib/storages/local-storage.js +1 -2
  13. package/lib/storages/local-storage.js.map +1 -1
  14. package/lib/storages/session-storage.d.ts.map +1 -1
  15. package/lib/storages/session-storage.js +1 -2
  16. package/lib/storages/session-storage.js.map +1 -1
  17. package/lib/utils/change-notifier.d.ts +1 -8
  18. package/lib/utils/change-notifier.d.ts.map +1 -1
  19. package/lib/utils/change-notifier.js +1 -10
  20. package/lib/utils/change-notifier.js.map +1 -1
  21. package/lib/utils/create-web-storage.d.ts +1 -8
  22. package/lib/utils/create-web-storage.d.ts.map +1 -1
  23. package/lib/utils/create-web-storage.js +5 -17
  24. package/lib/utils/create-web-storage.js.map +1 -1
  25. package/lib/utils/extension-storage.d.ts +2 -12
  26. package/lib/utils/extension-storage.d.ts.map +1 -1
  27. package/lib/utils/extension-storage.js +3 -14
  28. package/lib/utils/extension-storage.js.map +1 -1
  29. package/lib/utils/get-new-item.d.ts +1 -12
  30. package/lib/utils/get-new-item.d.ts.map +1 -1
  31. package/lib/utils/get-new-item.js +2 -16
  32. package/lib/utils/get-new-item.js.map +1 -1
  33. package/lib/utils/get-persisted-value.d.ts +1 -7
  34. package/lib/utils/get-persisted-value.d.ts.map +1 -1
  35. package/lib/utils/get-persisted-value.js +3 -13
  36. package/lib/utils/get-persisted-value.js.map +1 -1
  37. package/lib/utils/is-async-storage.d.ts +1 -7
  38. package/lib/utils/is-async-storage.d.ts.map +1 -1
  39. package/lib/utils/is-async-storage.js +6 -25
  40. package/lib/utils/is-async-storage.js.map +1 -1
  41. package/lib/utils/storage-event-router.d.ts +1 -5
  42. package/lib/utils/storage-event-router.d.ts.map +1 -1
  43. package/lib/utils/storage-event-router.js +3 -14
  44. package/lib/utils/storage-event-router.js.map +1 -1
  45. package/lib/utils/use-storage-handler.d.ts +1 -10
  46. package/lib/utils/use-storage-handler.d.ts.map +1 -1
  47. package/lib/utils/use-storage-handler.js +10 -57
  48. package/lib/utils/use-storage-handler.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/create-async-persisted-state.ts +10 -50
  51. package/src/create-persisted-state.ts +4 -16
  52. package/src/index.ts +1 -4
  53. package/src/storages/chrome-storage.ts +6 -18
  54. package/src/storages/local-storage.ts +1 -2
  55. package/src/storages/session-storage.ts +1 -2
  56. package/src/utils/change-notifier.ts +1 -10
  57. package/src/utils/create-web-storage.ts +5 -17
  58. package/src/utils/extension-storage.ts +3 -14
  59. package/src/utils/get-new-item.ts +2 -16
  60. package/src/utils/get-persisted-value.ts +3 -13
  61. package/src/utils/is-async-storage.ts +6 -26
  62. package/src/utils/storage-event-router.ts +3 -14
  63. package/src/utils/use-storage-handler.ts +11 -68
@@ -2,29 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = getPersistedValue;
4
4
  const is_1 = require("@plq/is");
5
- /**
6
- * Resolves the value a hook starts from: the persisted entry for `key` when the stored payload
7
- * carries one, the initial value otherwise.
8
- *
9
- * Presence is decided by the key rather than by the value, so a persisted `null` comes back as
10
- * the value the user set instead of being mistaken for an absence and replaced.
11
- */
5
+ /** Resolves a hook's starting value by key presence, so a persisted `null` is not mistaken for an absence. */
12
6
  function getPersistedValue(key, initialValue, persist) {
13
7
  let initialPersist;
14
8
  try {
15
9
  initialPersist = persist ? JSON.parse(persist) : {};
16
10
  }
17
11
  catch (err) {
18
- // A shared backend can hold a foreign or truncated entry, so a parse failure is expected here
19
- // and must not stop the component mounting. It is reported rather than swallowed because the
20
- // fallback discards whatever was persisted, and because the change path logs the same failure.
12
+ // A shared backend can hold a foreign or truncated entry, so a parse failure must not stop the mount.
21
13
  console.error("use-persisted-state: Can't parse value from storage", err);
22
14
  initialPersist = {};
23
15
  }
24
16
  let initialOrPersistedValue = (0, is_1.isFunction)(initialValue) ? initialValue() : initialValue;
25
- // `JSON.parse` yields any JSON value, so a foreign entry can be a primitive,
26
- // and `in` throws on one. This runs in the `useState` initializer, where a
27
- // throw stops the component mounting instead of falling back.
17
+ // `in` throws on a primitive, and this runs in the `useState` initializer, where a throw stops the mount.
28
18
  if ((0, is_1.isObject)(initialPersist) && key in initialPersist) {
29
19
  initialOrPersistedValue = initialPersist[key];
30
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"get-persisted-value.js","sourceRoot":"","sources":["../../src/utils/get-persisted-value.ts"],"names":[],"mappings":";;AASA,oCAwBC;AAjCD,gCAA8C;AAE9C;;;;;;GAMG;AACH,SAAwB,iBAAiB,CAAI,GAAW,EAAE,YAA2B,EAAE,OAAgB;IACrG,IAAI,cAAuB,CAAA;IAE3B,IAAI,CAAC;QACH,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,8FAA8F;QAC9F,6FAA6F;QAC7F,+FAA+F;QAC/F,OAAO,CAAC,KAAK,CAAC,qDAAqD,EAAE,GAAG,CAAC,CAAA;QAEzE,cAAc,GAAG,EAAE,CAAA;IACrB,CAAC;IAED,IAAI,uBAAuB,GAAG,IAAA,eAAU,EAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,YAAY,CAAA;IAEtF,6EAA6E;IAC7E,2EAA2E;IAC3E,8DAA8D;IAC9D,IAAI,IAAA,aAAQ,EAAC,cAAc,CAAC,IAAI,GAAG,IAAI,cAAc,EAAE,CAAC;QACtD,uBAAuB,GAAG,cAAc,CAAC,GAAG,CAAM,CAAA;IACpD,CAAC;IAED,OAAO,uBAAuB,CAAA;AAChC,CAAC"}
1
+ {"version":3,"file":"get-persisted-value.js","sourceRoot":"","sources":["../../src/utils/get-persisted-value.ts"],"names":[],"mappings":";;AAGA,oCAoBC;AAvBD,gCAA8C;AAE9C,8GAA8G;AAC9G,SAAwB,iBAAiB,CAAI,GAAW,EAAE,YAA2B,EAAE,OAAgB;IACrG,IAAI,cAAuB,CAAA;IAE3B,IAAI,CAAC;QACH,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sGAAsG;QACtG,OAAO,CAAC,KAAK,CAAC,qDAAqD,EAAE,GAAG,CAAC,CAAA;QAEzE,cAAc,GAAG,EAAE,CAAA;IACrB,CAAC;IAED,IAAI,uBAAuB,GAAG,IAAA,eAAU,EAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,YAAY,CAAA;IAEtF,0GAA0G;IAC1G,IAAI,IAAA,aAAQ,EAAC,cAAc,CAAC,IAAI,GAAG,IAAI,cAAc,EAAE,CAAC;QACtD,uBAAuB,GAAG,cAAc,CAAC,GAAG,CAAM,CAAA;IACpD,CAAC;IAED,OAAO,uBAAuB,CAAA;AAChC,CAAC"}
@@ -1,10 +1,4 @@
1
1
  import type { AsyncStorage } from '../@types/storage';
2
- /**
3
- * Narrows a storage to {@link AsyncStorage}, deciding which hook the entry point builds.
4
- *
5
- * Inspection never writes: `set` and `remove` are only checked for shape, never invoked, so
6
- * `get` alone decides. A candidate pairing an async `get` with a sync `set` satisfies neither
7
- * storage interface, which is why proving the other two members is not worth a write.
8
- */
2
+ /** Narrows a storage to {@link AsyncStorage}. Inspecting never writes: `set` and `remove` are never called. */
9
3
  export default function isAsyncStorage(storage: unknown): storage is AsyncStorage;
10
4
  //# sourceMappingURL=is-async-storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"is-async-storage.d.ts","sourceRoot":"","sources":["../../src/utils/is-async-storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAcrD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,YAAY,CAoDhF"}
1
+ {"version":3,"file":"is-async-storage.d.ts","sourceRoot":"","sources":["../../src/utils/is-async-storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAWrD,+GAA+G;AAC/G,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,YAAY,CAyChF"}
@@ -2,17 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = isAsyncStorage;
4
4
  const is_1 = require("@plq/is");
5
- // `AsyncStorage` is three methods, so every member has to be callable: a promise standing in for
6
- // one satisfies nothing the hook then does with it. `@plq/is` classifies async functions as their
7
- // own type, so `isFunction` rejects them and both checks are needed to admit a callable member.
5
+ // `@plq/is` classifies async functions as their own type, so `isFunction` alone rejects them.
8
6
  const isCallableMember = (member) => (0, is_1.isFunction)(member) || (0, is_1.isAsyncFunction)(member);
9
- /**
10
- * Narrows a storage to {@link AsyncStorage}, deciding which hook the entry point builds.
11
- *
12
- * Inspection never writes: `set` and `remove` are only checked for shape, never invoked, so
13
- * `get` alone decides. A candidate pairing an async `get` with a sync `set` satisfies neither
14
- * storage interface, which is why proving the other two members is not worth a write.
15
- */
7
+ /** Narrows a storage to {@link AsyncStorage}. Inspecting never writes: `set` and `remove` are never called. */
16
8
  function isAsyncStorage(storage) {
17
9
  const candidate = storage;
18
10
  if (!candidate) {
@@ -29,31 +21,20 @@ function isAsyncStorage(storage) {
29
21
  if (!(0, is_1.isFunction)(get)) {
30
22
  return false;
31
23
  }
32
- // `AsyncStorage` admits a plain function returning a promise, and nothing short of a call tells
33
- // that apart from a sync get. Any adapter written that way can only be recognised here, and a
34
- // third-party one is free to be written that way, so this line stays necessary however the
35
- // shipped adapters happen to declare themselves. A read is the one probe that leaves the
36
- // inspected storage as it found it.
24
+ // A plain function returning a promise is a valid `AsyncStorage`, and only a call tells it from a sync get.
37
25
  let probe;
38
26
  try {
39
- // Called as a method of the storage it belongs to: an adapter written as an
40
- // object of methods reads its own state through `this`, and probing the
41
- // extracted function would throw on a storage that is perfectly valid.
27
+ // Called as a method: an adapter reading its own state through `this` would throw on a torn-off function.
42
28
  probe = get.call(candidate, '');
43
29
  }
44
30
  catch {
45
- // Not swallowed, deferred: consumers build the factory at module scope, where
46
- // a throw takes the import down instead of reaching a component. A storage
47
- // whose read fails is not provably async, and the sync path raises the same
48
- // failure at the first read, where it can be handled.
31
+ // Deferred, not swallowed: factories are built at module scope, where a throw takes the import down.
49
32
  return false;
50
33
  }
51
34
  if (!(0, is_1.isPromise)(probe)) {
52
35
  return false;
53
36
  }
54
- // Only the probe's shape answers the question, so its outcome is discarded by construction.
55
- // The rejection still has to be claimed: unclaimed, it terminates the process on Node 15+,
56
- // letting a storage that merely fails a read take the consuming application down with it.
37
+ // An unclaimed rejection terminates the process on Node 15+, so the discarded probe still has to be claimed.
57
38
  probe.then(undefined, () => undefined);
58
39
  return true;
59
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"is-async-storage.js","sourceRoot":"","sources":["../../src/utils/is-async-storage.ts"],"names":[],"mappings":";;AAsBA,iCAoDC;AA1ED,gCAAgE;AAUhE,iGAAiG;AACjG,kGAAkG;AAClG,gGAAgG;AAChG,MAAM,gBAAgB,GAAG,CAAC,MAAe,EAAW,EAAE,CAAC,IAAA,eAAU,EAAC,MAAM,CAAC,IAAI,IAAA,oBAAe,EAAC,MAAM,CAAC,CAAA;AAEpG;;;;;;GAMG;AACH,SAAwB,cAAc,CAAC,OAAgB;IACrD,MAAM,SAAS,GAAG,OAA8C,CAAA;IAEhE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAEtC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,8EAA8E;IAC9E,IAAI,IAAA,oBAAe,EAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC,IAAA,eAAU,EAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,gGAAgG;IAChG,8FAA8F;IAC9F,2FAA2F;IAC3F,yFAAyF;IACzF,oCAAoC;IACpC,IAAI,KAAc,CAAA;IAElB,IAAI,CAAC;QACH,4EAA4E;QAC5E,wEAAwE;QACxE,uEAAuE;QACvE,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,8EAA8E;QAC9E,2EAA2E;QAC3E,4EAA4E;QAC5E,sDAAsD;QACtD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,CAAC,IAAA,cAAS,EAAC,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,4FAA4F;IAC5F,2FAA2F;IAC3F,0FAA0F;IAC1F,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAEtC,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"is-async-storage.js","sourceRoot":"","sources":["../../src/utils/is-async-storage.ts"],"names":[],"mappings":";;AAaA,iCAyCC;AAtDD,gCAAgE;AAShE,8FAA8F;AAC9F,MAAM,gBAAgB,GAAG,CAAC,MAAe,EAAW,EAAE,CAAC,IAAA,eAAU,EAAC,MAAM,CAAC,IAAI,IAAA,oBAAe,EAAC,MAAM,CAAC,CAAA;AAEpG,+GAA+G;AAC/G,SAAwB,cAAc,CAAC,OAAgB;IACrD,MAAM,SAAS,GAAG,OAA8C,CAAA;IAEhE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAEtC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,8EAA8E;IAC9E,IAAI,IAAA,oBAAe,EAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC,IAAA,eAAU,EAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,4GAA4G;IAC5G,IAAI,KAAc,CAAA;IAElB,IAAI,CAAC;QACH,0GAA0G;QAC1G,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,qGAAqG;QACrG,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,CAAC,IAAA,cAAS,EAAC,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,6GAA6G;IAC7G,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAEtC,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -6,11 +6,7 @@ export interface StorageEventRoute {
6
6
  [key: string]: StorageChange;
7
7
  }): void;
8
8
  }
9
- /**
10
- * Starts routing DOM storage events to `route`. Every adapter shares one
11
- * subscription on the global object, so creating adapters nobody listens to
12
- * costs nothing.
13
- */
9
+ /** Starts routing DOM storage events to `route`. Every adapter shares one subscription on the global object. */
14
10
  export declare function addRoute(route: StorageEventRoute): void;
15
11
  /** Stops routing to `route`, releasing the shared subscription with the last one. */
16
12
  export declare function removeRoute(route: StorageEventRoute): void;
@@ -1 +1 @@
1
- {"version":3,"file":"storage-event-router.d.ts","sourceRoot":"","sources":["../../src/utils/storage-event-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;KAAE,GAAG,IAAI,CAAA;CACtD;AAoCD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAQvD;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAQ1D"}
1
+ {"version":3,"file":"storage-event-router.d.ts","sourceRoot":"","sources":["../../src/utils/storage-event-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;KAAE,GAAG,IAAI,CAAA;CACtD;AA6BD,gHAAgH;AAChH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAQvD;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAQ1D"}
@@ -14,28 +14,17 @@ function routeStorageEvent(event) {
14
14
  },
15
15
  };
16
16
  for (const route of routes) {
17
- // A browser always names the area it changed, so real cross-tab events
18
- // reach only the adapter of that area. An event a consumer synthesized —
19
- // the sole way to test cross-tab sync under jsdom, and a common way to
20
- // notify the writing tab in production — often cannot name one: the
21
- // StorageEvent constructor rejects a mocked storage there. Those still go
22
- // everywhere, exactly as they did before areas were told apart.
17
+ // An event a consumer synthesized often cannot name an area, so those still reach every route.
23
18
  if (event.storageArea == null || event.storageArea === route.storage) {
24
19
  route.fire(changes);
25
20
  }
26
21
  }
27
22
  }
28
- // A runtime can provide storage without the DOM event API, and can provide half
29
- // of that API: subscribing where nothing can unsubscribe strands a listener and
30
- // makes releasing it throw, so both halves are required before taking one.
23
+ // A runtime can provide half the DOM event API, and subscribing where nothing can unsubscribe strands a listener.
31
24
  function canSubscribe() {
32
25
  return typeof globalThis.addEventListener === 'function' && typeof globalThis.removeEventListener === 'function';
33
26
  }
34
- /**
35
- * Starts routing DOM storage events to `route`. Every adapter shares one
36
- * subscription on the global object, so creating adapters nobody listens to
37
- * costs nothing.
38
- */
27
+ /** Starts routing DOM storage events to `route`. Every adapter shares one subscription on the global object. */
39
28
  function addRoute(route) {
40
29
  routes.add(route);
41
30
  if (!subscription && canSubscribe()) {
@@ -1 +1 @@
1
- {"version":3,"file":"storage-event-router.js","sourceRoot":"","sources":["../../src/utils/storage-event-router.ts"],"names":[],"mappings":";;AA+CA,4BAQC;AAGD,kCAQC;AA1DD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAA;AAE3C,IAAI,YAAY,GAA2C,IAAI,CAAA;AAE/D,SAAS,iBAAiB,CAAC,KAAmB;IAC5C,IAAI,CAAC,KAAK,CAAC,GAAG;QAAE,OAAM;IAEtB,MAAM,OAAO,GAAG;QACd,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;YACX,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB;KACF,CAAA;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,uEAAuE;QACvE,yEAAyE;QACzE,uEAAuE;QACvE,oEAAoE;QACpE,0EAA0E;QAC1E,gEAAgE;QAChE,IAAI,KAAK,CAAC,WAAW,IAAI,IAAI,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;YACrE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,2EAA2E;AAC3E,SAAS,YAAY;IACnB,OAAO,OAAO,UAAU,CAAC,gBAAgB,KAAK,UAAU,IAAI,OAAO,UAAU,CAAC,mBAAmB,KAAK,UAAU,CAAA;AAClH,CAAC;AAED;;;;GAIG;AACH,SAAgB,QAAQ,CAAC,KAAwB;IAC/C,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAEjB,IAAI,CAAC,YAAY,IAAI,YAAY,EAAE,EAAE,CAAC;QACpC,YAAY,GAAG,iBAAiB,CAAA;QAEhC,UAAU,CAAC,gBAAgB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IACtD,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAgB,WAAW,CAAC,KAAwB;IAClD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QACtC,UAAU,CAAC,mBAAmB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;QAEvD,YAAY,GAAG,IAAI,CAAA;IACrB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"storage-event-router.js","sourceRoot":"","sources":["../../src/utils/storage-event-router.ts"],"names":[],"mappings":";;AAoCA,4BAQC;AAGD,kCAQC;AA/CD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAA;AAE3C,IAAI,YAAY,GAA2C,IAAI,CAAA;AAE/D,SAAS,iBAAiB,CAAC,KAAmB;IAC5C,IAAI,CAAC,KAAK,CAAC,GAAG;QAAE,OAAM;IAEtB,MAAM,OAAO,GAAG;QACd,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;YACX,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB;KACF,CAAA;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,+FAA+F;QAC/F,IAAI,KAAK,CAAC,WAAW,IAAI,IAAI,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;YACrE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED,kHAAkH;AAClH,SAAS,YAAY;IACnB,OAAO,OAAO,UAAU,CAAC,gBAAgB,KAAK,UAAU,IAAI,OAAO,UAAU,CAAC,mBAAmB,KAAK,UAAU,CAAA;AAClH,CAAC;AAED,gHAAgH;AAChH,SAAgB,QAAQ,CAAC,KAAwB;IAC/C,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAEjB,IAAI,CAAC,YAAY,IAAI,YAAY,EAAE,EAAE,CAAC;QACpC,YAAY,GAAG,iBAAiB,CAAA;QAEhC,UAAU,CAAC,gBAAgB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IACtD,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAgB,WAAW,CAAC,KAAwB;IAClD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QACtC,UAAU,CAAC,mBAAmB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;QAEvD,YAAY,GAAG,IAAI,CAAA;IACrB,CAAC;AACH,CAAC"}
@@ -1,15 +1,6 @@
1
1
  import type React from 'react';
2
2
  import type { AsyncStorage, Storage } from '../@types/storage';
3
- /**
4
- * Remembers an entry the hook is about to write, so the change the backend
5
- * reports for it can be told apart from someone else's write.
6
- *
7
- * Recorded before the write, because a backend may report it before the call
8
- * settles. One slot was not enough: a backend free to report after its write
9
- * settles - chrome and browser storage both are - lets the next write overwrite
10
- * the record of a write still waiting to be reported, and that unsuppressed echo
11
- * then puts the earlier value back over the later one.
12
- */
3
+ /** Records an entry before it is written, because a backend may report the change before the write settles. */
13
4
  export declare function recordOwnWrite(pendingOwnWrites: React.RefObject<string[]>, item: string): void;
14
5
  export default function useStorageHandler<T>(key: string, storageKey: string, applyValue: (value: T) => void, storage: AsyncStorage | Storage, initialValue: T | (() => T), pendingOwnWrites: React.RefObject<string[]>): void;
15
6
  //# sourceMappingURL=use-storage-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-storage-handler.d.ts","sourceRoot":"","sources":["../../src/utils/use-storage-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAiB,MAAM,mBAAmB,CAAA;AAkE7E;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM9F;AA+DD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,CAAC,EACzC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAC9B,OAAO,EAAE,YAAY,GAAG,OAAO,EAC/B,YAAY,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAC3B,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,GAC1C,IAAI,CAsBN"}
1
+ {"version":3,"file":"use-storage-handler.d.ts","sourceRoot":"","sources":["../../src/utils/use-storage-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAiB,MAAM,mBAAmB,CAAA;AA8C7E,+GAA+G;AAC/G,wBAAgB,cAAc,CAAC,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM9F;AAwCD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,CAAC,EACzC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAC9B,OAAO,EAAE,YAAY,GAAG,OAAO,EAC/B,YAAY,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAC3B,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,GAC1C,IAAI,CAiBN"}
@@ -13,90 +13,48 @@ function readStoredValue(key, entry) {
13
13
  console.error("use-persisted-state: Can't parse value from storage", err);
14
14
  return { status: 'unavailable' };
15
15
  }
16
- // A foreign entry can be any JSON value, and `in` throws on a primitive. This
17
- // runs inside the adapter's notify loop, where a throw also cuts off every
18
- // listener queued behind this one.
16
+ // `in` throws on a primitive, and a throw here cuts off every listener queued behind this one.
19
17
  if (!(0, is_1.isObject)(parsed) || !(key in parsed))
20
18
  return { status: 'unavailable' };
21
19
  return { status: 'stored', value: parsed[key] };
22
20
  }
23
- // Restores the initial value when the whole entry is removed from the storage.
24
21
  function applyRemoval(change, itemKey, applyValue, latestInitialValue) {
25
22
  if (change.oldValue === null || change.oldValue === undefined)
26
23
  return;
27
24
  const oldValue = readStoredValue(itemKey, change.oldValue);
28
25
  const declaredInitialValue = latestInitialValue.current;
29
- // Compared against the resolved value: a factory is never equal to what it
30
- // produces, so comparing the declaration re-applies the initial value on every
31
- // removal.
26
+ // Resolved before comparing: a factory is never equal to what it produces.
32
27
  const initialValue = (0, is_1.isFunction)(declaredInitialValue) ? declaredInitialValue() : declaredInitialValue;
33
- // An entry with no readable value for the key cannot be shown to have held the
34
- // initial value, so the fallback is applied rather than skipped. The entry is
35
- // gone either way, and stale state left in place is worse than a redundant
36
- // restore.
28
+ // Restore redundantly rather than leave stale state when the entry's old value cannot be read.
37
29
  if (oldValue.status === 'stored' && oldValue.value === initialValue)
38
30
  return;
39
31
  applyValue(initialValue);
40
32
  }
41
- // A backend that reports nothing back leaves every record unmatched, so the list
42
- // needs a ceiling or it grows by one string per write for as long as the hook is
43
- // mounted. A bound, not a tuning: a backend that does report drains the list on
44
- // every echo, and one that reports late is what the list exists for.
33
+ // A list, not one slot: a write may still be awaiting its echo when the next one starts.
34
+ // Capped, because a backend that never reports back leaves every record unmatched.
45
35
  const MAX_PENDING_OWN_WRITES = 8;
46
- /**
47
- * Remembers an entry the hook is about to write, so the change the backend
48
- * reports for it can be told apart from someone else's write.
49
- *
50
- * Recorded before the write, because a backend may report it before the call
51
- * settles. One slot was not enough: a backend free to report after its write
52
- * settles - chrome and browser storage both are - lets the next write overwrite
53
- * the record of a write still waiting to be reported, and that unsuppressed echo
54
- * then puts the earlier value back over the later one.
55
- */
36
+ /** Records an entry before it is written, because a backend may report the change before the write settles. */
56
37
  function recordOwnWrite(pendingOwnWrites, item) {
57
38
  const pending = pendingOwnWrites.current;
58
39
  if (pending.length >= MAX_PENDING_OWN_WRITES)
59
40
  pending.shift();
60
41
  pending.push(item);
61
42
  }
62
- /**
63
- * Reports whether this change is a write the hook itself made, forgetting the
64
- * record when it is. A backend reports a write to every listener, the one that
65
- * made it included, and applying that echo is the storage-to-state re-sync this
66
- * hook was rebuilt without, arriving by another road: it decodes the entry again
67
- * and hands the caller an equal value with a new identity, plus a render for a
68
- * value it already holds.
69
- *
70
- * The price, accepted knowingly: for a value JSON cannot carry, the writer keeps
71
- * what it set - NaN - while every other component on the key decodes the null
72
- * that reached storage, so the two disagree until one of them remounts. Applying
73
- * the echo would settle that disagreement and bring back both the second
74
- * storage-to-state path and the render per write, so it is not the repair it
75
- * looks like.
76
- *
77
- * Forgetting on the first match keeps a later identical entry a change. Another
78
- * hook writing the same bytes is suppressed along with it, and nothing is lost:
79
- * it would have replaced a value with an equal one.
80
- */
43
+ // Applying a hook's own echo would re-decode the entry and re-render for a value it already holds.
81
44
  function consumeOwnWriteEcho(entry, pendingOwnWrites) {
82
45
  const matched = pendingOwnWrites.current.indexOf(entry);
83
46
  if (matched === -1)
84
47
  return false;
85
- // Everything recorded before the reported entry goes with it: a backend applies
86
- // writes in the order it took them and reports them in that order too, so a
87
- // record still unmatched by now has no echo left to wait for.
48
+ // A backend reports writes in the order it took them, so a record still unmatched has no echo left.
88
49
  pendingOwnWrites.current.splice(0, matched + 1);
89
50
  return true;
90
51
  }
91
- // Builds the change handler. Not a hook, despite living next to one.
92
52
  function createStorageHandler(itemKey, storageKey, applyValue, latestInitialValue, pendingOwnWrites) {
93
53
  return (changes) => {
94
54
  for (const [key, change] of Object.entries(changes)) {
95
55
  if (key !== storageKey)
96
56
  continue;
97
- // Ahead of the echo check, which needs an entry to match and a removal has
98
- // none. A removal is never one of this hook's own writes anyway: only the
99
- // entries it stores are recorded.
57
+ // Ahead of the echo check, which needs an entry to match and a removal has none.
100
58
  if (change.newValue === null || change.newValue === undefined) {
101
59
  applyRemoval(change, itemKey, applyValue, latestInitialValue);
102
60
  continue;
@@ -110,12 +68,7 @@ function createStorageHandler(itemKey, storageKey, applyValue, latestInitialValu
110
68
  };
111
69
  }
112
70
  function useStorageHandler(key, storageKey, applyValue, storage, initialValue, pendingOwnWrites) {
113
- // A removal restores the initial value the hook holds now, the same one the
114
- // key-change path reads, so a caller whose default travels with its data gets
115
- // that record's default back rather than the mounted one. Tracked through a ref
116
- // rather than a dependency because an inline object or factory has a new
117
- // identity every render, which would tear the subscription down and rebuild it;
118
- // written during render, as the key the hook is rendering for is.
71
+ // A ref, not a dependency: an inline initial value changes identity every render and would churn it.
119
72
  const latestInitialValue = (0, react_1.useRef)(initialValue);
120
73
  latestInitialValue.current = initialValue;
121
74
  (0, react_1.useEffect)(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-storage-handler.js","sourceRoot":"","sources":["../../src/utils/use-storage-handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-storage-handler.js","sourceRoot":"","sources":["../../src/utils/use-storage-handler.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plq/use-persisted-state",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "useState hook with persistence in storage",
5
5
  "type": "commonjs",
6
6
  "main": "lib/index.js",
@@ -15,20 +15,11 @@ export default function createAsyncPersistedState<S extends AsyncStorage>(
15
15
  ): [PersistedState, () => Promise<void>] {
16
16
  const safeStorageKey = `persisted_state_hook:${storageKey}`
17
17
 
18
- // Every hook this factory makes lives in one entry, and storing a value means
19
- // reading that entry, merging one key into it and writing all of it back, with
20
- // a suspension point on either side of the merge. Left to overlap, a second
21
- // writer merges into a snapshot taken before the first one landed and stores
22
- // it: the first writer's key is gone from storage while its value is still on
23
- // screen, and nothing reports the disagreement. It is the entry, not the hook,
24
- // that has to be taken one at a time, so the chain belongs to the factory.
18
+ // Storing is read-merge-write on one shared entry, so overlapping writers would drop each other's keys.
25
19
  let entryWrites: Promise<unknown> = Promise.resolve()
26
20
 
27
21
  const clear = (): Promise<void> => {
28
- // Removing the entry changes it as much as storing does, so it takes its turn
29
- // in the same chain. Outside it, a write already queued lands after the
30
- // removal and brings back what was cleared - and "clear this data" is the one
31
- // request that cannot be allowed to half happen.
22
+ // In the same chain as writes, or a queued write lands after the removal and restores what was cleared.
32
23
  const removal = entryWrites.then(() => storage.remove(safeStorageKey))
33
24
 
34
25
  entryWrites = removal.catch(() => undefined)
@@ -44,23 +35,16 @@ export default function createAsyncPersistedState<S extends AsyncStorage>(
44
35
  try {
45
36
  newItem = getNewItem<T>(key, persistedItem[safeStorageKey], newValue)
46
37
  } catch {
47
- // Refused, and reported where it was refused. A write replaces the whole
48
- // entry, so an entry that cannot be read is one no write can be built on
49
- // without dropping every other hook's key; skipping leaves the bytes for
50
- // a repair to reach. The caller keeps what it set, unpersisted.
38
+ // A write replaces the whole entry, so an unreadable one is skipped rather than rebuilt without other keys.
51
39
  return
52
40
  }
53
41
 
54
- // Recorded before the write, because the backend may report it before the
55
- // promise settles.
56
42
  recordOwnWrite(pendingOwnWrites, newItem)
57
43
 
58
44
  await storage.set({ [safeStorageKey]: newItem })
59
45
  })
60
46
 
61
- // The chain has to outlive a failed write, or one backend rejection stops
62
- // every later write on this entry. The rejection itself is not swallowed: it
63
- // stays on the promise handed back to the caller that asked for the write.
47
+ // The chain must outlive a failed write; the rejection still reaches the caller through `write`.
64
48
  entryWrites = write.catch(() => undefined)
65
49
 
66
50
  return write
@@ -69,14 +53,10 @@ export default function createAsyncPersistedState<S extends AsyncStorage>(
69
53
  const usePersistedState = <T>(key: string, initialValue: T | (() => T)): UsePersistedState<T> => {
70
54
  const [state, setState] = useState<T>(initialValue)
71
55
 
72
- // The setter must resolve updater functions against the last value applied,
73
- // not against the one captured by the render that created it. Reading the
74
- // render closure collapses updates batched into a single event.
56
+ // Updater functions must resolve against the last applied value, not the render closure's.
75
57
  const latestValue = useRef(state)
76
58
 
77
- // Whether anything has already put a value in place. A load that settles after
78
- // the caller set one must not revert it, and no signal local to the load can
79
- // answer this: the write it loses to happens outside the load entirely.
59
+ // A load that settles after the caller set a value must not revert it.
80
60
  const hasAppliedValue = useRef(false)
81
61
 
82
62
  const applyValue = useCallback((value: T): void => {
@@ -86,15 +66,12 @@ export default function createAsyncPersistedState<S extends AsyncStorage>(
86
66
  setState(value)
87
67
  }, [])
88
68
 
89
- // The entries this hook has written and not yet seen reported back.
90
69
  const pendingOwnWrites = useRef<string[]>([])
91
70
 
92
71
  const setPersistedState = useCallback(
93
72
  async (newState: React.SetStateAction<T>): Promise<void> => {
94
73
  const newValue = getNewValue<T>(newState, latestValue.current)
95
74
 
96
- // Applied before the write is even queued: the caller sees its value at
97
- // once, and only the trip to storage waits its turn.
98
75
  applyValue(newValue)
99
76
 
100
77
  await commitEntry<T>(key, newValue, pendingOwnWrites)
@@ -102,26 +79,14 @@ export default function createAsyncPersistedState<S extends AsyncStorage>(
102
79
  [key, applyValue],
103
80
  )
104
81
 
105
- // As in `useState`, the value the load falls back to is the one given on the
106
- // first render. Later identities of an inline object must not reload, or the
107
- // effect re-runs on every render and never settles.
82
+ // The first render's initial value: reloading on a later identity would re-run the effect forever.
108
83
  const mountInitialValue = useRef(initialValue)
109
84
 
110
- // Subscribed before the load below, and the order is load-bearing: React runs
111
- // effects in declaration order, so reading first would leave an interval
112
- // between the read and the subscription in which a write belongs to neither
113
- // and is lost. Reading last covers everything written before the subscription
114
- // began, and the listener covers everything after.
85
+ // Subscribed before the load: effects run in declaration order, and reading first would lose writes in between.
115
86
  useStorageHandler<T>(key, safeStorageKey, applyValue, storage, initialValue, pendingOwnWrites)
116
87
 
117
88
  useEffect(() => {
118
- // Two separate questions, and one cell cannot hold both: whether this load
119
- // is still the current one, which only the closure it belongs to can answer,
120
- // and whether a value has been applied meanwhile, which outlives it. A load
121
- // abandoned by a key change would otherwise read the flag its successor had
122
- // just set, apply the previous key's value and shut the successor out. The
123
- // second question is also what makes subscribing first safe: a value the
124
- // listener delivers while the load is in flight is not overwritten by it.
89
+ // Separate from `hasAppliedValue`: only this closure knows whether its own load is still current.
125
90
  let isCancelled = false
126
91
 
127
92
  hasAppliedValue.current = false
@@ -134,12 +99,7 @@ export default function createAsyncPersistedState<S extends AsyncStorage>(
134
99
 
135
100
  applyValue(getPersistedValue<T>(key, mountInitialValue.current, persist[safeStorageKey]))
136
101
  } catch (err) {
137
- // An extension backend rejects on a quota error or an invalidated
138
- // extension context. Nothing awaits this load, so an unclaimed
139
- // rejection terminates the process on Node 15+ and surfaces as an
140
- // uncaught error in the browser. The initial value is already in
141
- // state, so the mount stands on it and the failure is reported rather
142
- // than swallowed, as the synchronous path reports its own.
102
+ // Nothing awaits this load, so an unclaimed rejection would surface as an uncaught error.
143
103
  console.error("use-persisted-state: Can't read value from storage", err)
144
104
  }
145
105
  }
@@ -19,14 +19,10 @@ export default function createPersistedState(storageKey: string, storage: Storag
19
19
  getPersistedValue<T>(key, initialValue, storage.get(safeStorageKey)[safeStorageKey])
20
20
 
21
21
  const usePersistedState = <T>(key: string, initialValue: T): UsePersistedState<T> => {
22
- // Reading through the initializer keeps the storage out of the render path:
23
- // the hook runs on every render of every consuming component, so a re-read is
24
- // only warranted when the key it is asked for actually changes.
22
+ // Read through the initializer: the hook runs on every render, so storage stays out of the render path.
25
23
  const [state, setState] = useState<T>(() => readPersisted(key, initialValue))
26
24
 
27
- // The setter must resolve updater functions against the last value applied,
28
- // not against the one captured by the render that created it. Reading the
29
- // render closure collapses updates batched into a single event.
25
+ // Updater functions must resolve against the last applied value, not the render closure's.
30
26
  const latestValue = useRef(state)
31
27
 
32
28
  const applyValue = useCallback((value: T): void => {
@@ -35,11 +31,7 @@ export default function createPersistedState(storageKey: string, storage: Storag
35
31
  setState(value)
36
32
  }, [])
37
33
 
38
- // A mounted hook handed a different key has to show that key's value before
39
- // anything can be written, or the next update stores the previous key's value
40
- // under the new key and destroys what was there. Adjusting during render ties
41
- // the re-read to the change itself, where an effect would cost a read and a
42
- // second render on every mount as well.
34
+ // A new key must be read before anything is written, or the next update stores the old key's value under it.
43
35
  const renderedKey = useRef(key)
44
36
 
45
37
  if (renderedKey.current !== key) {
@@ -48,7 +40,6 @@ export default function createPersistedState(storageKey: string, storage: Storag
48
40
  applyValue(readPersisted(key, initialValue))
49
41
  }
50
42
 
51
- // The entries this hook has written and not yet seen reported back.
52
43
  const pendingOwnWrites = useRef<string[]>([])
53
44
 
54
45
  const setPersistedState = useCallback(
@@ -63,10 +54,7 @@ export default function createPersistedState(storageKey: string, storage: Storag
63
54
  try {
64
55
  newItem = getNewItem<T>(key, persistedItem, newValue)
65
56
  } catch {
66
- // Refused, and reported where it was refused. A write replaces the whole
67
- // entry, so an entry that cannot be read is one no write can be built on
68
- // without dropping every other hook's key; skipping leaves the bytes for
69
- // a repair to reach. The caller keeps what it set, unpersisted.
57
+ // A write replaces the whole entry, so an unreadable one is skipped rather than rebuilt without other keys.
70
58
  return
71
59
  }
72
60
 
package/src/index.ts CHANGED
@@ -20,9 +20,6 @@ export default function createPersistedState<S extends Storage | AsyncStorage>(
20
20
  export { default as createPersistedState } from './create-persisted-state'
21
21
  export { default as createAsyncPersistedState } from './create-async-persisted-state'
22
22
 
23
- // The contract a backend implements, and the shape the hook hands back. Both appear in the
24
- // signatures above, so a consumer writing an adapter or typing a wrapper has to be able to name
25
- // them from the supported entry point rather than from `lib/`, which the exports map keeps open
26
- // only as a frozen legacy path.
23
+ // Both appear in the signatures above, so a consumer writing an adapter can name them from the entry point.
27
24
  export type { AsyncStorage, Storage, StorageChange, StorageChangeEvent, StorageChangeListener } from './@types/storage'
28
25
  export type { PersistedState, UsePersistedState } from './@types/hook'
@@ -7,25 +7,13 @@ chrome.storage.onChanged.addListener((changes, area) => {
7
7
  registry.fire(toStorageChanges(changes), area)
8
8
  })
9
9
 
10
- // chrome.storage is callback-based, unlike its promise-based firefox counterpart.
10
+ // Passing no callback selects the promise form; the callback form leaves failures in `runtime.lastError`.
11
+ // Called through `storage`: a torn-off `StorageArea` method throws `Illegal invocation`, unlike firefox's.
12
+ // `get` stays `async` so asking whether this storage is asynchronous costs no round trip to the extension.
11
13
  const createStorage = (storage: chrome.storage.StorageArea, area: Area): AsyncStorage => ({
12
- // Declared async, like the firefox adapter, so that asking whether this
13
- // storage is asynchronous can be answered from the function itself instead of
14
- // by calling it — a call here costs a round trip to the extension process.
15
- get: async keys =>
16
- new Promise(resolve => {
17
- storage.get(keys, items => {
18
- resolve(toStoredItems(items))
19
- })
20
- }),
21
- set: items =>
22
- new Promise(resolve => {
23
- storage.set(items, resolve)
24
- }),
25
- remove: keys =>
26
- new Promise(resolve => {
27
- storage.remove(keys, resolve)
28
- }),
14
+ get: async keys => toStoredItems(await storage.get(keys)),
15
+ set: items => storage.set(items),
16
+ remove: keys => storage.remove(keys),
29
17
  onChanged: registry.createOnChanged(area),
30
18
  })
31
19
 
@@ -1,5 +1,4 @@
1
1
  import createStorage from '../utils/create-web-storage'
2
2
 
3
- // The typeof guard keeps this import from throwing on server runtimes (SSR),
4
- // where the browser storage globals are not defined.
3
+ // The typeof guard keeps this import from throwing on server runtimes, where the global is not defined.
5
4
  export default createStorage(typeof localStorage !== 'undefined' ? localStorage : undefined)
@@ -1,5 +1,4 @@
1
1
  import createStorage from '../utils/create-web-storage'
2
2
 
3
- // The typeof guard keeps this import from throwing on server runtimes (SSR),
4
- // where the browser storage globals are not defined.
3
+ // The typeof guard keeps this import from throwing on server runtimes, where the global is not defined.
5
4
  export default createStorage(typeof sessionStorage !== 'undefined' ? sessionStorage : undefined)