dharma-core 0.12.0 → 0.14.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.
Files changed (49) hide show
  1. package/dist/cjs/index.d.ts +1 -0
  2. package/dist/cjs/index.js +2 -0
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/internal/createStorageAdapter.d.ts +1 -1
  5. package/dist/cjs/internal/createStorageAdapter.js +51 -33
  6. package/dist/cjs/internal/createStorageAdapter.js.map +1 -1
  7. package/dist/cjs/lib/StorageUnavailableError.d.ts +7 -0
  8. package/dist/cjs/lib/StorageUnavailableError.js +15 -0
  9. package/dist/cjs/lib/StorageUnavailableError.js.map +1 -0
  10. package/dist/cjs/lib/createEffect.js +1 -0
  11. package/dist/cjs/lib/createEffect.js.map +1 -1
  12. package/dist/cjs/lib/createStore.js +7 -9
  13. package/dist/cjs/lib/createStore.js.map +1 -1
  14. package/dist/cjs/lib/deeplyEquals.js +2 -3
  15. package/dist/cjs/lib/deeplyEquals.js.map +1 -1
  16. package/dist/cjs/lib/derive.js +14 -13
  17. package/dist/cjs/lib/derive.js.map +1 -1
  18. package/dist/cjs/lib/merge.d.ts +1 -0
  19. package/dist/cjs/lib/merge.js +9 -9
  20. package/dist/cjs/lib/merge.js.map +1 -1
  21. package/dist/cjs/types/types.d.ts +9 -2
  22. package/dist/esm/index.d.ts +1 -0
  23. package/dist/esm/index.js +1 -0
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/internal/createStorageAdapter.d.ts +1 -1
  26. package/dist/esm/internal/createStorageAdapter.js +51 -33
  27. package/dist/esm/internal/createStorageAdapter.js.map +1 -1
  28. package/dist/esm/lib/StorageUnavailableError.d.ts +7 -0
  29. package/dist/esm/lib/StorageUnavailableError.js +13 -0
  30. package/dist/esm/lib/StorageUnavailableError.js.map +1 -0
  31. package/dist/esm/lib/createEffect.js +1 -0
  32. package/dist/esm/lib/createEffect.js.map +1 -1
  33. package/dist/esm/lib/createStore.js +7 -9
  34. package/dist/esm/lib/createStore.js.map +1 -1
  35. package/dist/esm/lib/deeplyEquals.js +2 -3
  36. package/dist/esm/lib/deeplyEquals.js.map +1 -1
  37. package/dist/esm/lib/derive.js +14 -13
  38. package/dist/esm/lib/derive.js.map +1 -1
  39. package/dist/esm/lib/merge.d.ts +1 -0
  40. package/dist/esm/lib/merge.js +9 -9
  41. package/dist/esm/lib/merge.js.map +1 -1
  42. package/dist/esm/types/types.d.ts +9 -2
  43. package/package.json +1 -1
  44. package/dist/cjs/internal/warn.d.ts +0 -1
  45. package/dist/cjs/internal/warn.js +0 -6
  46. package/dist/cjs/internal/warn.js.map +0 -1
  47. package/dist/esm/internal/warn.d.ts +0 -1
  48. package/dist/esm/internal/warn.js +0 -4
  49. package/dist/esm/internal/warn.js.map +0 -1
@@ -2,4 +2,5 @@ export * from "./lib/createEffect";
2
2
  export * from "./lib/createStore";
3
3
  export * from "./lib/derive";
4
4
  export * from "./lib/merge";
5
+ export * from "./lib/StorageUnavailableError";
5
6
  export * from "./types/types";
package/dist/cjs/index.js CHANGED
@@ -4,6 +4,7 @@ var createEffect = require('./lib/createEffect.js');
4
4
  var createStore = require('./lib/createStore.js');
5
5
  var derive = require('./lib/derive.js');
6
6
  var merge = require('./lib/merge.js');
7
+ var StorageUnavailableError = require('./lib/StorageUnavailableError.js');
7
8
 
8
9
 
9
10
 
@@ -11,4 +12,5 @@ exports.createEffect = createEffect.createEffect;
11
12
  exports.createStore = createStore.createStore;
12
13
  exports.derive = derive.derive;
13
14
  exports.merge = merge.merge;
15
+ exports.StorageUnavailableError = StorageUnavailableError.StorageUnavailableError;
14
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
@@ -1,6 +1,6 @@
1
1
  import { SetState, StoreConfig } from "../types/types";
2
2
  export declare const createStorageAdapter: <TState, TActions>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
3
- onLoad: () => Promise<void>;
3
+ onLoad: () => void;
4
4
  onChange: (newState: TState) => Promise<void>;
5
5
  onAttach: () => void;
6
6
  onDetach: () => void;
@@ -1,73 +1,91 @@
1
1
  'use strict';
2
2
 
3
- var warn = require('./warn.js');
3
+ var StorageUnavailableError = require('../lib/StorageUnavailableError.js');
4
4
 
5
+ const isThenable = (value) => typeof value?.then === "function";
6
+ const getDefaultStorage = () => {
7
+ try {
8
+ // Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled
9
+ return localStorage;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ };
5
15
  const createStorageAdapter = (config, get, set) => {
6
16
  if (!config.persist) {
7
17
  return null;
8
18
  }
9
19
  const IS_BROWSER = typeof window !== "undefined";
10
- const storage = config.storage ?? (IS_BROWSER ? localStorage : undefined);
20
+ // React Native defines window without the event listener API
21
+ const CAN_LISTEN = IS_BROWSER && typeof window.addEventListener === "function";
22
+ const storage = config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);
23
+ const { key, serializer = JSON, syncWithStorageOn = "load", onStorageError, } = config;
11
24
  if (!storage) {
12
- warn.warn("No storage provider found. If this happened during SSR, you can safely ignore this warning.");
25
+ onStorageError?.(new StorageUnavailableError.StorageUnavailableError(key));
13
26
  return null;
14
27
  }
15
- const { key, serializer = JSON, initialState } = config;
16
- const initKey = `init_${key}`;
17
- const onLoad = async () => {
18
- try {
19
- let initialStateSnapshot = storage.getItem(initKey);
20
- const initialStateString = serializer.stringify(initialState);
21
- if (initialStateSnapshot instanceof Promise) {
22
- initialStateSnapshot = await initialStateSnapshot;
23
- }
24
- if (initialStateSnapshot !== initialStateString) {
25
- storage.setItem(initKey, initialStateString);
26
- storage.removeItem(key);
27
- }
28
- }
29
- catch {
30
- warn.warn("Failed to initialize snapshots. If this happened during SSR, you can safely ignore this warning.");
31
- }
32
- };
33
28
  const syncWithSnapshot = async () => {
29
+ let nextState;
34
30
  try {
31
+ const prevState = get();
35
32
  let currentSnapshot = storage.getItem(key);
36
- if (currentSnapshot instanceof Promise) {
33
+ if (isThenable(currentSnapshot)) {
37
34
  currentSnapshot = await currentSnapshot;
35
+ // The state changed while reading, so the snapshot is outdated
36
+ if (!Object.is(get(), prevState)) {
37
+ return;
38
+ }
38
39
  }
39
- if (currentSnapshot && currentSnapshot !== serializer.stringify(get())) {
40
- set(serializer.parse(currentSnapshot));
40
+ if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {
41
+ return;
41
42
  }
43
+ nextState = serializer.parse(currentSnapshot);
42
44
  }
43
- catch {
44
- warn.warn("Failed to update state from snapshot");
45
+ catch (error) {
46
+ onStorageError?.(error);
47
+ return;
48
+ }
49
+ set(nextState);
50
+ };
51
+ const onLoad = () => {
52
+ if (syncWithStorageOn === "load") {
53
+ syncWithSnapshot();
54
+ }
55
+ };
56
+ const onStorage = (event) => {
57
+ if (event.key === key && event.storageArea === storage) {
58
+ syncWithSnapshot();
45
59
  }
46
60
  };
47
61
  const onAttach = () => {
48
- syncWithSnapshot();
49
- if (IS_BROWSER) {
62
+ if (syncWithStorageOn === "attach") {
63
+ syncWithSnapshot();
64
+ }
65
+ if (CAN_LISTEN) {
50
66
  window.addEventListener("focus", syncWithSnapshot);
67
+ window.addEventListener("storage", onStorage);
51
68
  }
52
69
  };
53
70
  const onChange = async (newState) => {
54
71
  try {
55
72
  let currentSnapshot = storage.getItem(key);
56
73
  const newSnapshot = serializer.stringify(newState);
57
- if (currentSnapshot instanceof Promise) {
74
+ if (isThenable(currentSnapshot)) {
58
75
  currentSnapshot = await currentSnapshot;
59
76
  }
60
77
  if (newSnapshot !== currentSnapshot) {
61
- storage.setItem(key, newSnapshot);
78
+ await storage.setItem(key, newSnapshot);
62
79
  }
63
80
  }
64
- catch {
65
- warn.warn("Failed to update snapshot");
81
+ catch (error) {
82
+ onStorageError?.(error);
66
83
  }
67
84
  };
68
85
  const onDetach = () => {
69
- if (IS_BROWSER) {
86
+ if (CAN_LISTEN) {
70
87
  window.removeEventListener("focus", syncWithSnapshot);
88
+ window.removeEventListener("storage", onStorage);
71
89
  }
72
90
  };
73
91
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { SetState, StorageAPI, StoreConfig } from \"../types/types\";\nimport { warn } from \"./warn\";\n\nexport const createStorageAdapter = <TState, TActions>(\n config: StoreConfig<TState, TActions>,\n get: () => TState,\n set: SetState<TState>,\n) => {\n if (!config.persist) {\n return null;\n }\n\n const IS_BROWSER = typeof window !== \"undefined\";\n\n const storage =\n config.storage ?? (IS_BROWSER ? (localStorage as StorageAPI) : undefined);\n\n if (!storage) {\n warn(\n \"No storage provider found. If this happened during SSR, you can safely ignore this warning.\",\n );\n return null;\n }\n\n const { key, serializer = JSON, initialState } = config;\n const initKey = `init_${key}`;\n\n const onLoad = async () => {\n try {\n let initialStateSnapshot = storage.getItem(initKey);\n const initialStateString = serializer.stringify(initialState);\n\n if (initialStateSnapshot instanceof Promise) {\n initialStateSnapshot = await initialStateSnapshot;\n }\n\n if (initialStateSnapshot !== initialStateString) {\n storage.setItem(initKey, initialStateString);\n storage.removeItem(key);\n }\n } catch {\n warn(\n \"Failed to initialize snapshots. If this happened during SSR, you can safely ignore this warning.\",\n );\n }\n };\n\n const syncWithSnapshot = async () => {\n try {\n let currentSnapshot = storage.getItem(key);\n\n if (currentSnapshot instanceof Promise) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (currentSnapshot && currentSnapshot !== serializer.stringify(get())) {\n set(serializer.parse(currentSnapshot));\n }\n } catch {\n warn(\"Failed to update state from snapshot\");\n }\n };\n\n const onAttach = () => {\n syncWithSnapshot();\n\n if (IS_BROWSER) {\n window.addEventListener(\"focus\", syncWithSnapshot);\n }\n };\n\n const onChange = async (newState: TState) => {\n try {\n let currentSnapshot = storage.getItem(key);\n const newSnapshot = serializer.stringify(newState);\n\n if (currentSnapshot instanceof Promise) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (newSnapshot !== currentSnapshot) {\n storage.setItem(key, newSnapshot);\n }\n } catch {\n warn(\"Failed to update snapshot\");\n }\n };\n\n const onDetach = () => {\n if (IS_BROWSER) {\n window.removeEventListener(\"focus\", syncWithSnapshot);\n }\n };\n\n return {\n onLoad,\n onChange,\n onAttach,\n onDetach,\n };\n};\n"],"names":["warn"],"mappings":";;;;AAGO,MAAM,oBAAoB,GAAG,CAClC,MAAqC,EACrC,GAAiB,EACjB,GAAqB,KACnB;AACF,IAAA,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;AACnB,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,WAAW;AAEhD,IAAA,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,KAAK,UAAU,GAAI,YAA2B,GAAG,SAAS,CAAC;IAE3E,IAAI,CAAC,OAAO,EAAE;QACZA,SAAI,CACF,6FAA6F,CAC9F;AACD,QAAA,OAAO,IAAI;IACb;IAEA,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM;AACvD,IAAA,MAAM,OAAO,GAAG,CAAA,KAAA,EAAQ,GAAG,EAAE;AAE7B,IAAA,MAAM,MAAM,GAAG,YAAW;AACxB,QAAA,IAAI;YACF,IAAI,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;YACnD,MAAM,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC;AAE7D,YAAA,IAAI,oBAAoB,YAAY,OAAO,EAAE;gBAC3C,oBAAoB,GAAG,MAAM,oBAAoB;YACnD;AAEA,YAAA,IAAI,oBAAoB,KAAK,kBAAkB,EAAE;AAC/C,gBAAA,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,CAAC;AAC5C,gBAAA,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YACzB;QACF;AAAE,QAAA,MAAM;YACNA,SAAI,CACF,kGAAkG,CACnG;QACH;AACF,IAAA,CAAC;AAED,IAAA,MAAM,gBAAgB,GAAG,YAAW;AAClC,QAAA,IAAI;YACF,IAAI,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;AAE1C,YAAA,IAAI,eAAe,YAAY,OAAO,EAAE;gBACtC,eAAe,GAAG,MAAM,eAAe;YACzC;AAEA,YAAA,IAAI,eAAe,IAAI,eAAe,KAAK,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE;gBACtE,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YACxC;QACF;AAAE,QAAA,MAAM;YACNA,SAAI,CAAC,sCAAsC,CAAC;QAC9C;AACF,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,gBAAgB,EAAE;QAElB,IAAI,UAAU,EAAE;AACd,YAAA,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,CAAC;QACpD;AACF,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAG,OAAO,QAAgB,KAAI;AAC1C,QAAA,IAAI;YACF,IAAI,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;YAC1C,MAAM,WAAW,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC;AAElD,YAAA,IAAI,eAAe,YAAY,OAAO,EAAE;gBACtC,eAAe,GAAG,MAAM,eAAe;YACzC;AAEA,YAAA,IAAI,WAAW,KAAK,eAAe,EAAE;AACnC,gBAAA,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,WAAW,CAAC;YACnC;QACF;AAAE,QAAA,MAAM;YACNA,SAAI,CAAC,2BAA2B,CAAC;QACnC;AACF,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,IAAI,UAAU,EAAE;AACd,YAAA,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,CAAC;QACvD;AACF,IAAA,CAAC;IAED,OAAO;QACL,MAAM;QACN,QAAQ;QACR,QAAQ;QACR,QAAQ;KACT;AACH;;;;"}
1
+ {"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { StorageUnavailableError } from \"../lib/StorageUnavailableError\";\nimport { SetState, StorageAPI, StoreConfig } from \"../types/types\";\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof (value as PromiseLike<unknown> | null)?.then === \"function\";\n\nconst getDefaultStorage = (): StorageAPI | undefined => {\n try {\n // Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled\n return localStorage;\n } catch {\n return undefined;\n }\n};\n\nexport const createStorageAdapter = <TState, TActions>(\n config: StoreConfig<TState, TActions>,\n get: () => TState,\n set: SetState<TState>,\n) => {\n if (!config.persist) {\n return null;\n }\n\n const IS_BROWSER = typeof window !== \"undefined\";\n // React Native defines window without the event listener API\n const CAN_LISTEN =\n IS_BROWSER && typeof window.addEventListener === \"function\";\n\n const storage =\n config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);\n\n const {\n key,\n serializer = JSON,\n syncWithStorageOn = \"load\",\n onStorageError,\n } = config;\n\n if (!storage) {\n onStorageError?.(new StorageUnavailableError(key));\n return null;\n }\n\n const syncWithSnapshot = async () => {\n let nextState: TState;\n\n try {\n const prevState = get();\n let currentSnapshot = storage.getItem(key);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n\n // The state changed while reading, so the snapshot is outdated\n if (!Object.is(get(), prevState)) {\n return;\n }\n }\n\n if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {\n return;\n }\n\n nextState = serializer.parse(currentSnapshot);\n } catch (error) {\n onStorageError?.(error);\n return;\n }\n\n set(nextState);\n };\n\n const onLoad = () => {\n if (syncWithStorageOn === \"load\") {\n syncWithSnapshot();\n }\n };\n\n const onStorage = (event: StorageEvent) => {\n if (event.key === key && event.storageArea === storage) {\n syncWithSnapshot();\n }\n };\n\n const onAttach = () => {\n if (syncWithStorageOn === \"attach\") {\n syncWithSnapshot();\n }\n\n if (CAN_LISTEN) {\n window.addEventListener(\"focus\", syncWithSnapshot);\n window.addEventListener(\"storage\", onStorage);\n }\n };\n\n const onChange = async (newState: TState) => {\n try {\n let currentSnapshot = storage.getItem(key);\n const newSnapshot = serializer.stringify(newState);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (newSnapshot !== currentSnapshot) {\n await storage.setItem(key, newSnapshot);\n }\n } catch (error) {\n onStorageError?.(error);\n }\n };\n\n const onDetach = () => {\n if (CAN_LISTEN) {\n window.removeEventListener(\"focus\", syncWithSnapshot);\n window.removeEventListener(\"storage\", onStorage);\n }\n };\n\n return {\n onLoad,\n onChange,\n onAttach,\n onDetach,\n };\n};\n"],"names":["StorageUnavailableError"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Reported to `onStorageError` when a persisted store has no storage to use.
3
+ * This is expected during SSR, and can otherwise mean that local storage is inaccessible.
4
+ */
5
+ export declare class StorageUnavailableError extends Error {
6
+ constructor(key: string);
7
+ }
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Reported to `onStorageError` when a persisted store has no storage to use.
5
+ * This is expected during SSR, and can otherwise mean that local storage is inaccessible.
6
+ */
7
+ class StorageUnavailableError extends Error {
8
+ constructor(key) {
9
+ super(`[dharma-core] No storage provider was found for "${key}". If this happened during SSR, it can be safely ignored.`);
10
+ this.name = "StorageUnavailableError";
11
+ }
12
+ }
13
+
14
+ exports.StorageUnavailableError = StorageUnavailableError;
15
+ //# sourceMappingURL=StorageUnavailableError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageUnavailableError.js","sources":["../../../src/lib/StorageUnavailableError.ts"],"sourcesContent":["/**\n * Reported to `onStorageError` when a persisted store has no storage to use.\n * This is expected during SSR, and can otherwise mean that local storage is inaccessible.\n */\nexport class StorageUnavailableError extends Error {\n constructor(key: string) {\n super(\n `[dharma-core] No storage provider was found for \"${key}\". If this happened during SSR, it can be safely ignored.`,\n );\n this.name = \"StorageUnavailableError\";\n }\n}\n"],"names":[],"mappings":";;AAAA;;;AAGG;AACG,MAAO,uBAAwB,SAAQ,KAAK,CAAA;AAChD,IAAA,WAAA,CAAY,GAAW,EAAA;AACrB,QAAA,KAAK,CACH,CAAA,iDAAA,EAAoD,GAAG,CAAA,yDAAA,CAA2D,CACnH;AACD,QAAA,IAAI,CAAC,IAAI,GAAG,yBAAyB;IACvC;AACD;;;;"}
@@ -33,6 +33,7 @@ const createEffect = (store, effectFn, dependencyFn) => {
33
33
  const unmount = () => {
34
34
  unsubscribe?.();
35
35
  unsubscribe = undefined;
36
+ prev = undefined;
36
37
  };
37
38
  return {
38
39
  mount,
@@ -1 +1 @@
1
- {"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":["deeplyEquals"],"mappings":";;;;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAACA,yBAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;KACR;AACH;;;;"}
1
+ {"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n prev = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":["deeplyEquals"],"mappings":";;;;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAACA,yBAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;QACvB,IAAI,GAAG,SAAS;AAClB,IAAA,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;KACR;AACH;;;;"}
@@ -21,16 +21,15 @@ const createStore = (config) => {
21
21
  return state;
22
22
  };
23
23
  const set = (stateModifier) => {
24
+ const prevState = state;
24
25
  setSilently(stateModifier);
25
- dispatch();
26
+ if (!Object.is(state, prevState)) {
27
+ dispatch();
28
+ }
26
29
  return state;
27
30
  };
28
31
  const resetSilently = () => setSilently(initialState);
29
- const reset = () => {
30
- resetSilently();
31
- dispatch();
32
- return state;
33
- };
32
+ const reset = () => set(initialState);
34
33
  const listeners = new Set();
35
34
  const actions = config.actions?.({
36
35
  set,
@@ -47,21 +46,20 @@ const createStore = (config) => {
47
46
  listener(state);
48
47
  listeners.add(listener);
49
48
  return () => {
50
- listeners.delete(listener);
51
- if (listeners.size === 0) {
49
+ if (listeners.delete(listener) && listeners.size === 0) {
52
50
  storageAdapter?.onDetach();
53
51
  onDetach?.({ state, set, reset, initialState });
54
52
  }
55
53
  };
56
54
  };
57
55
  const dispatch = () => {
58
- storageAdapter?.onChange(state);
59
56
  onChange?.({
60
57
  state,
61
58
  set: setSilently,
62
59
  reset: resetSilently,
63
60
  initialState,
64
61
  });
62
+ storageAdapter?.onChange(state);
65
63
  listeners.forEach((listener) => listener(state));
66
64
  };
67
65
  storageAdapter?.onLoad();
@@ -1 +1 @@
1
- {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n setSilently(stateModifier);\n dispatch();\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () => {\n resetSilently();\n dispatch();\n return state;\n };\n\n const listeners = new Set<Listener<TState>>();\n const actions = config.actions?.({\n set,\n get,\n reset,\n initialState,\n }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.({ state, set, reset, initialState });\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n storageAdapter?.onDetach();\n onDetach?.({ state, set, reset, initialState });\n }\n };\n };\n\n const dispatch = () => {\n storageAdapter?.onChange(state);\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n initialState,\n });\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.({ state, set, reset, initialState });\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":["merge","createStorageAdapter"],"mappings":";;;;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM;IAErE,IAAI,KAAK,GAAG,YAAY;AAExB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,WAAW,GAAG,CAAC,aAAoC,KAAI;AAC3D,QAAA,KAAK,GAAGA,WAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACnC,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,WAAW,CAAC,aAAa,CAAC;AAC1B,QAAA,QAAQ,EAAE;AACV,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC;IAErD,MAAM,KAAK,GAAG,MAAK;AACjB,QAAA,aAAa,EAAE;AACf,QAAA,QAAQ,EAAE;AACV,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAC7C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;AACb,KAAA,CAAa;IACd,MAAM,cAAc,GAAGC,yCAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;AAE7D,IAAA,MAAM,SAAS,GAAG,CAAC,QAA0B,KAAI;AAC/C,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;YACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,YAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD;QAEA,QAAQ,CAAC,KAAK,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAE1B,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;gBACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,gBAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YACjD;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;AAC/B,QAAA,QAAQ,GAAG;YACT,KAAK;AACL,YAAA,GAAG,EAAE,WAAW;AAChB,YAAA,KAAK,EAAE,aAAa;YACpB,YAAY;AACb,SAAA,CAAC;AACF,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,cAAc,EAAE,MAAM,EAAE;AACxB,IAAA,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE7C,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
1
+ {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n const prevState = state;\n setSilently(stateModifier);\n\n if (!Object.is(state, prevState)) {\n dispatch();\n }\n\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () => set(initialState);\n\n const listeners = new Set<Listener<TState>>();\n const actions = config.actions?.({\n set,\n get,\n reset,\n initialState,\n }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.({ state, set, reset, initialState });\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n if (listeners.delete(listener) && listeners.size === 0) {\n storageAdapter?.onDetach();\n onDetach?.({ state, set, reset, initialState });\n }\n };\n };\n\n const dispatch = () => {\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n initialState,\n });\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.({ state, set, reset, initialState });\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":["merge","createStorageAdapter"],"mappings":";;;;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM;IAErE,IAAI,KAAK,GAAG,YAAY;AAExB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,WAAW,GAAG,CAAC,aAAoC,KAAI;AAC3D,QAAA,KAAK,GAAGA,WAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACnC,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,MAAM,SAAS,GAAG,KAAK;QACvB,WAAW,CAAC,aAAa,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE;AAChC,YAAA,QAAQ,EAAE;QACZ;AAEA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC;IAErD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC;AAErC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAC7C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;AACb,KAAA,CAAa;IACd,MAAM,cAAc,GAAGC,yCAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;AAE7D,IAAA,MAAM,SAAS,GAAG,CAAC,QAA0B,KAAI;AAC/C,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;YACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,YAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD;QAEA,QAAQ,CAAC,KAAK,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;gBACtD,cAAc,EAAE,QAAQ,EAAE;AAC1B,gBAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YACjD;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,QAAQ,GAAG;YACT,KAAK;AACL,YAAA,GAAG,EAAE,WAAW;AAChB,YAAA,KAAK,EAAE,aAAa;YACpB,YAAY;AACb,SAAA,CAAC;AACF,QAAA,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;AAC/B,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,cAAc,EAAE,MAAM,EAAE;AACxB,IAAA,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE7C,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
@@ -30,12 +30,11 @@ const deeplyEquals = (a, b) => {
30
30
  return false;
31
31
  }
32
32
  const keysA = Object.keys(a);
33
- const keysB = Object.keys(b);
34
- if (keysA.length !== keysB.length) {
33
+ if (keysA.length !== Object.keys(b).length) {
35
34
  return false;
36
35
  }
37
36
  for (const key of keysA) {
38
- if (!keysB.includes(key)) {
37
+ if (!Object.prototype.hasOwnProperty.call(b, key)) {
39
38
  return false;
40
39
  }
41
40
  if (!deeplyEquals(a[key], b[key])) {
@@ -1 +1 @@
1
- {"version":3,"file":"deeplyEquals.js","sources":["../../../src/lib/deeplyEquals.ts"],"sourcesContent":["export const deeplyEquals = (a: unknown, b: unknown): boolean => {\n if (a === b) {\n return true;\n }\n\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n\n const protoA = Object.getPrototypeOf(a);\n const protoB = Object.getPrototypeOf(b);\n\n if (protoA !== protoB) {\n return false;\n }\n\n if (protoA === Array.prototype) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n for (let i = 0; i < arrA.length; i++) {\n if (!deeplyEquals(arrA[i], arrB[i])) return false;\n }\n return true;\n }\n\n if (protoA !== Object.prototype) {\n return false;\n }\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (!keysB.includes(key)) {\n return false;\n }\n if (\n !deeplyEquals(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n\n return true;\n};\n"],"names":[],"mappings":";;MAAa,YAAY,GAAG,CAAC,CAAU,EAAE,CAAU,KAAa;AAC9D,IAAA,IAAI,CAAC,KAAK,CAAC,EAAE;AACX,QAAA,OAAO,IAAI;IACb;IAEA,IACE,CAAC,KAAK,IAAI;AACV,QAAA,CAAC,KAAK,IAAI;QACV,OAAO,CAAC,KAAK,QAAQ;AACrB,QAAA,OAAO,CAAC,KAAK,QAAQ,EACrB;AACA,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAEvC,IAAA,IAAI,MAAM,KAAK,MAAM,EAAE;AACrB,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,IAAI,MAAM,KAAK,KAAK,CAAC,SAAS,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAc;QAC3B,MAAM,IAAI,GAAG,CAAc;AAC3B,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,KAAK;AAC7C,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACpC,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAAE,gBAAA,OAAO,KAAK;QACnD;AACA,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,IAAI,MAAM,KAAK,MAAM,CAAC,SAAS,EAAE;AAC/B,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE;AACjC,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE;QACvB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACxB,YAAA,OAAO,KAAK;QACd;AACA,QAAA,IACE,CAAC,YAAY,CACV,CAA6B,CAAC,GAAG,CAAC,EAClC,CAA6B,CAAC,GAAG,CAAC,CACpC,EACD;AACA,YAAA,OAAO,KAAK;QACd;IACF;AAEA,IAAA,OAAO,IAAI;AACb;;;;"}
1
+ {"version":3,"file":"deeplyEquals.js","sources":["../../../src/lib/deeplyEquals.ts"],"sourcesContent":["export const deeplyEquals = (a: unknown, b: unknown): boolean => {\n if (a === b) {\n return true;\n }\n\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n\n const protoA = Object.getPrototypeOf(a);\n const protoB = Object.getPrototypeOf(b);\n\n if (protoA !== protoB) {\n return false;\n }\n\n if (protoA === Array.prototype) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n for (let i = 0; i < arrA.length; i++) {\n if (!deeplyEquals(arrA[i], arrB[i])) return false;\n }\n return true;\n }\n\n if (protoA !== Object.prototype) {\n return false;\n }\n\n const keysA = Object.keys(a);\n\n if (keysA.length !== Object.keys(b).length) {\n return false;\n }\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) {\n return false;\n }\n if (\n !deeplyEquals(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n\n return true;\n};\n"],"names":[],"mappings":";;MAAa,YAAY,GAAG,CAAC,CAAU,EAAE,CAAU,KAAa;AAC9D,IAAA,IAAI,CAAC,KAAK,CAAC,EAAE;AACX,QAAA,OAAO,IAAI;IACb;IAEA,IACE,CAAC,KAAK,IAAI;AACV,QAAA,CAAC,KAAK,IAAI;QACV,OAAO,CAAC,KAAK,QAAQ;AACrB,QAAA,OAAO,CAAC,KAAK,QAAQ,EACrB;AACA,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAEvC,IAAA,IAAI,MAAM,KAAK,MAAM,EAAE;AACrB,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,IAAI,MAAM,KAAK,KAAK,CAAC,SAAS,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAc;QAC3B,MAAM,IAAI,GAAG,CAAc;AAC3B,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,KAAK;AAC7C,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACpC,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAAE,gBAAA,OAAO,KAAK;QACnD;AACA,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,IAAI,MAAM,KAAK,MAAM,CAAC,SAAS,EAAE;AAC/B,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAE5B,IAAA,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;AAC1C,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE;AACvB,QAAA,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;AACjD,YAAA,OAAO,KAAK;QACd;AACA,QAAA,IACE,CAAC,YAAY,CACV,CAA6B,CAAC,GAAG,CAAC,EAClC,CAA6B,CAAC,GAAG,CAAC,CACpC,EACD;AACA,YAAA,OAAO,KAAK;QACd;IACF;AAEA,IAAA,OAAO,IAAI;AACb;;;;"}
@@ -18,22 +18,24 @@ var deeplyEquals = require('./deeplyEquals.js');
18
18
  */
19
19
  const derive = (store, deriveFn, dependencyFn) => {
20
20
  let memo;
21
- let prev;
22
- let isStale = true;
21
+ let hasMemo = false;
22
+ let prevState;
23
+ let prevDeps;
23
24
  let unsubscribe;
24
25
  const listeners = new Set();
25
- const compute = () => {
26
- memo = deriveFn(store.get());
27
- isStale = false;
28
- return memo;
29
- };
30
26
  const get = () => {
31
- const next = dependencyFn?.(store.get());
32
- isStale &&= !prev || !deeplyEquals.deeplyEquals(prev, next);
33
- if (isStale) {
34
- prev = next;
35
- return compute();
27
+ const state = store.get();
28
+ if (hasMemo && Object.is(state, prevState)) {
29
+ return memo;
30
+ }
31
+ prevState = state;
32
+ const deps = dependencyFn?.(state);
33
+ if (hasMemo && dependencyFn && deeplyEquals.deeplyEquals(prevDeps, deps)) {
34
+ return memo;
36
35
  }
36
+ prevDeps = deps;
37
+ memo = deriveFn(state);
38
+ hasMemo = true;
37
39
  return memo;
38
40
  };
39
41
  const subscribe = (listener) => {
@@ -50,7 +52,6 @@ const derive = (store, deriveFn, dependencyFn) => {
50
52
  };
51
53
  };
52
54
  const listener = () => {
53
- isStale = true;
54
55
  listeners.forEach((listener) => listener(get()));
55
56
  };
56
57
  const mount = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { DerivedStore, Listener, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates a derived store from an existing store.\n *\n * A derived store computes its state based on the original store's state using a derive function.\n * The derived store's value is memoized and only recomputed when requested.\n *\n * @param store - The original store to derive from\n * @param deriveFn - A function that computes the derived state from the original state\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change\n *\n * @returns A new store containing the derived state\n *\n * @see {@link https://dharma.fransek.dev/core/derive/}\n */\nexport const derive = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n deriveFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): DerivedStore<TDerived> => {\n let memo: TDerived | undefined;\n let prev: unknown[] | undefined;\n let isStale = true;\n let unsubscribe: (() => void) | undefined;\n const listeners = new Set<Listener<TDerived>>();\n\n const compute = () => {\n memo = deriveFn(store.get());\n isStale = false;\n return memo;\n };\n\n const get = () => {\n const next = dependencyFn?.(store.get());\n isStale &&= !prev || !deeplyEquals(prev, next);\n\n if (isStale) {\n prev = next;\n return compute();\n }\n\n return memo as TDerived;\n };\n\n const subscribe = (listener: Listener<TDerived>) => {\n if (listeners.size === 0) {\n mount();\n }\n\n listener(get());\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n unmount();\n }\n };\n };\n\n const listener = () => {\n isStale = true;\n listeners.forEach((listener) => listener(get()));\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(listener);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n get,\n subscribe,\n mount,\n unmount,\n };\n};\n"],"names":["deeplyEquals"],"mappings":";;;;AAGA;;;;;;;;;;;;;AAaG;AACI,MAAM,MAAM,GAAG,CACpB,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjB;AAC1B,IAAA,IAAI,IAA0B;AAC9B,IAAA,IAAI,IAA2B;IAC/B,IAAI,OAAO,GAAG,IAAI;AAClB,IAAA,IAAI,WAAqC;AACzC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB;IAE/C,MAAM,OAAO,GAAG,MAAK;QACnB,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC5B,OAAO,GAAG,KAAK;AACf,QAAA,OAAO,IAAI;AACb,IAAA,CAAC;IAED,MAAM,GAAG,GAAG,MAAK;QACf,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC,IAAI,IAAI,CAACA,yBAAY,CAAC,IAAI,EAAE,IAAI,CAAC;QAE9C,IAAI,OAAO,EAAE;YACX,IAAI,GAAG,IAAI;YACX,OAAO,OAAO,EAAE;QAClB;AAEA,QAAA,OAAO,IAAgB;AACzB,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,QAA4B,KAAI;AACjD,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,YAAA,KAAK,EAAE;QACT;AAEA,QAAA,QAAQ,CAAC,GAAG,EAAE,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAE1B,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,OAAO,GAAG,IAAI;AACd,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;QACzC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,GAAG;QACH,SAAS;QACT,KAAK;QACL,OAAO;KACR;AACH;;;;"}
1
+ {"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { DerivedStore, Listener, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates a derived store from an existing store.\n *\n * A derived store computes its state based on the original store's state using a derive function.\n * The derived store's value is memoized and only recomputed when requested.\n *\n * @param store - The original store to derive from\n * @param deriveFn - A function that computes the derived state from the original state\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change\n *\n * @returns A new store containing the derived state\n *\n * @see {@link https://dharma.fransek.dev/core/derive/}\n */\nexport const derive = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n deriveFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): DerivedStore<TDerived> => {\n let memo: TDerived | undefined;\n let hasMemo = false;\n let prevState: TState | undefined;\n let prevDeps: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n const listeners = new Set<Listener<TDerived>>();\n\n const get = () => {\n const state = store.get();\n\n if (hasMemo && Object.is(state, prevState)) {\n return memo as TDerived;\n }\n\n prevState = state;\n const deps = dependencyFn?.(state);\n\n if (hasMemo && dependencyFn && deeplyEquals(prevDeps, deps)) {\n return memo as TDerived;\n }\n\n prevDeps = deps;\n memo = deriveFn(state);\n hasMemo = true;\n return memo;\n };\n\n const subscribe = (listener: Listener<TDerived>) => {\n if (listeners.size === 0) {\n mount();\n }\n\n listener(get());\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n unmount();\n }\n };\n };\n\n const listener = () => {\n listeners.forEach((listener) => listener(get()));\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(listener);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n get,\n subscribe,\n mount,\n unmount,\n };\n};\n"],"names":["deeplyEquals"],"mappings":";;;;AAGA;;;;;;;;;;;;;AAaG;AACI,MAAM,MAAM,GAAG,CACpB,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjB;AAC1B,IAAA,IAAI,IAA0B;IAC9B,IAAI,OAAO,GAAG,KAAK;AACnB,IAAA,IAAI,SAA6B;AACjC,IAAA,IAAI,QAA+B;AACnC,IAAA,IAAI,WAAqC;AACzC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB;IAE/C,MAAM,GAAG,GAAG,MAAK;AACf,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE;QAEzB,IAAI,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE;AAC1C,YAAA,OAAO,IAAgB;QACzB;QAEA,SAAS,GAAG,KAAK;AACjB,QAAA,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC;QAElC,IAAI,OAAO,IAAI,YAAY,IAAIA,yBAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE;AAC3D,YAAA,OAAO,IAAgB;QACzB;QAEA,QAAQ,GAAG,IAAI;AACf,QAAA,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC;QACtB,OAAO,GAAG,IAAI;AACd,QAAA,OAAO,IAAI;AACb,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,QAA4B,KAAI;AACjD,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,YAAA,KAAK,EAAE;QACT;AAEA,QAAA,QAAQ,CAAC,GAAG,EAAE,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAE1B,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;QACzC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,GAAG;QACH,SAAS;QACT,KAAK;QACL,OAAO;KACR;AACH;;;;"}
@@ -5,6 +5,7 @@ import { StateModifier } from "../types/types";
5
5
  * @param {T} currentState - The current state object.
6
6
  * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
7
7
  * @returns {T} The new state object, which is a combination of the current state and the state modifier.
8
+ * Returns `currentState` itself if the state modifier resolves to it.
8
9
  *
9
10
  * @see {@link https://dharma.fransek.dev/core/merge/}
10
11
  */
@@ -8,21 +8,21 @@ const isObject = (value) => !!value && typeof value === "object" && !Array.isArr
8
8
  * @param {T} currentState - The current state object.
9
9
  * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
10
10
  * @returns {T} The new state object, which is a combination of the current state and the state modifier.
11
+ * Returns `currentState` itself if the state modifier resolves to it.
11
12
  *
12
13
  * @see {@link https://dharma.fransek.dev/core/merge/}
13
14
  */
14
15
  const merge = (currentState, stateModifier) => {
15
- if (isObject(stateModifier)) {
16
- return { ...currentState, ...stateModifier };
16
+ const modifiedState = typeof stateModifier === "function"
17
+ ? stateModifier(currentState)
18
+ : stateModifier;
19
+ if (Object.is(modifiedState, currentState)) {
20
+ return currentState;
17
21
  }
18
- if (typeof stateModifier === "function") {
19
- const modifiedState = stateModifier(currentState);
20
- if (isObject(modifiedState)) {
21
- return { ...currentState, ...modifiedState };
22
- }
23
- return modifiedState;
22
+ if (isObject(modifiedState)) {
23
+ return { ...currentState, ...modifiedState };
24
24
  }
25
- return stateModifier;
25
+ return modifiedState;
26
26
  };
27
27
 
28
28
  exports.merge = merge;
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n if (isObject(stateModifier)) {\n return { ...currentState, ...stateModifier };\n }\n\n if (typeof stateModifier === \"function\") {\n const modifiedState = stateModifier(currentState);\n\n if (isObject(modifiedState)) {\n return { ...currentState, ...modifiedState };\n }\n\n return modifiedState;\n }\n\n return stateModifier;\n};\n"],"names":[],"mappings":";;AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,KAAc,KAC9B,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAE/D;;;;;;;;AAQG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,QAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;IAC9C;AAEA,IAAA,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE;AACvC,QAAA,MAAM,aAAa,GAAG,aAAa,CAAC,YAAY,CAAC;AAEjD,QAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,YAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;QAC9C;AAEA,QAAA,OAAO,aAAa;IACtB;AAEA,IAAA,OAAO,aAAa;AACtB;;;;"}
1
+ {"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n * Returns `currentState` itself if the state modifier resolves to it.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n const modifiedState =\n typeof stateModifier === \"function\"\n ? stateModifier(currentState)\n : stateModifier;\n\n if (Object.is(modifiedState, currentState)) {\n return currentState;\n }\n\n if (isObject(modifiedState)) {\n return { ...currentState, ...modifiedState };\n }\n\n return modifiedState as T;\n};\n"],"names":[],"mappings":";;AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,KAAc,KAC9B,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAE/D;;;;;;;;;AASG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,MAAM,aAAa,GACjB,OAAO,aAAa,KAAK;AACvB,UAAE,aAAa,CAAC,YAAY;UAC1B,aAAa;IAEnB,IAAI,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE;AAC1C,QAAA,OAAO,YAAY;IACrB;AAEA,IAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,QAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;IAC9C;AAEA,IAAA,OAAO,aAAkB;AAC3B;;;;"}
@@ -67,11 +67,10 @@ export type BaseConfig<TState, TActions> = {
67
67
  /** Invoked whenever the state changes. */
68
68
  onChange?: StoreEventListener<TState>;
69
69
  };
70
- export type MaybePromise<T> = T | Promise<T>;
70
+ export type MaybePromise<T> = T | PromiseLike<T>;
71
71
  export type StorageAPI = {
72
72
  getItem: (key: string) => MaybePromise<string | null>;
73
73
  setItem: (key: string, value: string) => MaybePromise<void>;
74
- removeItem: (key: string) => MaybePromise<void>;
75
74
  };
76
75
  export type Serializer<T = any> = {
77
76
  stringify: (value: T) => string;
@@ -90,5 +89,13 @@ export type PersistConfig<TState> = {
90
89
  storage?: StorageAPI;
91
90
  /** The serializer to use for storing the state. Defaults to JSON. */
92
91
  serializer?: Serializer<TState>;
92
+ /**
93
+ * When to initially synchronize the store's state with the storage. Defaults to "load".
94
+ *
95
+ * Set to "attach" when using SSR to avoid hydration errors.
96
+ */
97
+ syncWithStorageOn?: "load" | "attach";
98
+ /** Invoked when a storage error occurs. */
99
+ onStorageError?: (error: unknown) => void;
93
100
  };
94
101
  export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
@@ -2,4 +2,5 @@ export * from "./lib/createEffect";
2
2
  export * from "./lib/createStore";
3
3
  export * from "./lib/derive";
4
4
  export * from "./lib/merge";
5
+ export * from "./lib/StorageUnavailableError";
5
6
  export * from "./types/types";
package/dist/esm/index.js CHANGED
@@ -2,4 +2,5 @@ export { createEffect } from './lib/createEffect.js';
2
2
  export { createStore } from './lib/createStore.js';
3
3
  export { derive } from './lib/derive.js';
4
4
  export { merge } from './lib/merge.js';
5
+ export { StorageUnavailableError } from './lib/StorageUnavailableError.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -1,6 +1,6 @@
1
1
  import { SetState, StoreConfig } from "../types/types";
2
2
  export declare const createStorageAdapter: <TState, TActions>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
3
- onLoad: () => Promise<void>;
3
+ onLoad: () => void;
4
4
  onChange: (newState: TState) => Promise<void>;
5
5
  onAttach: () => void;
6
6
  onDetach: () => void;
@@ -1,71 +1,89 @@
1
- import { warn } from './warn.js';
1
+ import { StorageUnavailableError } from '../lib/StorageUnavailableError.js';
2
2
 
3
+ const isThenable = (value) => typeof value?.then === "function";
4
+ const getDefaultStorage = () => {
5
+ try {
6
+ // Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled
7
+ return localStorage;
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ };
3
13
  const createStorageAdapter = (config, get, set) => {
4
14
  if (!config.persist) {
5
15
  return null;
6
16
  }
7
17
  const IS_BROWSER = typeof window !== "undefined";
8
- const storage = config.storage ?? (IS_BROWSER ? localStorage : undefined);
18
+ // React Native defines window without the event listener API
19
+ const CAN_LISTEN = IS_BROWSER && typeof window.addEventListener === "function";
20
+ const storage = config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);
21
+ const { key, serializer = JSON, syncWithStorageOn = "load", onStorageError, } = config;
9
22
  if (!storage) {
10
- warn("No storage provider found. If this happened during SSR, you can safely ignore this warning.");
23
+ onStorageError?.(new StorageUnavailableError(key));
11
24
  return null;
12
25
  }
13
- const { key, serializer = JSON, initialState } = config;
14
- const initKey = `init_${key}`;
15
- const onLoad = async () => {
16
- try {
17
- let initialStateSnapshot = storage.getItem(initKey);
18
- const initialStateString = serializer.stringify(initialState);
19
- if (initialStateSnapshot instanceof Promise) {
20
- initialStateSnapshot = await initialStateSnapshot;
21
- }
22
- if (initialStateSnapshot !== initialStateString) {
23
- storage.setItem(initKey, initialStateString);
24
- storage.removeItem(key);
25
- }
26
- }
27
- catch {
28
- warn("Failed to initialize snapshots. If this happened during SSR, you can safely ignore this warning.");
29
- }
30
- };
31
26
  const syncWithSnapshot = async () => {
27
+ let nextState;
32
28
  try {
29
+ const prevState = get();
33
30
  let currentSnapshot = storage.getItem(key);
34
- if (currentSnapshot instanceof Promise) {
31
+ if (isThenable(currentSnapshot)) {
35
32
  currentSnapshot = await currentSnapshot;
33
+ // The state changed while reading, so the snapshot is outdated
34
+ if (!Object.is(get(), prevState)) {
35
+ return;
36
+ }
36
37
  }
37
- if (currentSnapshot && currentSnapshot !== serializer.stringify(get())) {
38
- set(serializer.parse(currentSnapshot));
38
+ if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {
39
+ return;
39
40
  }
41
+ nextState = serializer.parse(currentSnapshot);
40
42
  }
41
- catch {
42
- warn("Failed to update state from snapshot");
43
+ catch (error) {
44
+ onStorageError?.(error);
45
+ return;
46
+ }
47
+ set(nextState);
48
+ };
49
+ const onLoad = () => {
50
+ if (syncWithStorageOn === "load") {
51
+ syncWithSnapshot();
52
+ }
53
+ };
54
+ const onStorage = (event) => {
55
+ if (event.key === key && event.storageArea === storage) {
56
+ syncWithSnapshot();
43
57
  }
44
58
  };
45
59
  const onAttach = () => {
46
- syncWithSnapshot();
47
- if (IS_BROWSER) {
60
+ if (syncWithStorageOn === "attach") {
61
+ syncWithSnapshot();
62
+ }
63
+ if (CAN_LISTEN) {
48
64
  window.addEventListener("focus", syncWithSnapshot);
65
+ window.addEventListener("storage", onStorage);
49
66
  }
50
67
  };
51
68
  const onChange = async (newState) => {
52
69
  try {
53
70
  let currentSnapshot = storage.getItem(key);
54
71
  const newSnapshot = serializer.stringify(newState);
55
- if (currentSnapshot instanceof Promise) {
72
+ if (isThenable(currentSnapshot)) {
56
73
  currentSnapshot = await currentSnapshot;
57
74
  }
58
75
  if (newSnapshot !== currentSnapshot) {
59
- storage.setItem(key, newSnapshot);
76
+ await storage.setItem(key, newSnapshot);
60
77
  }
61
78
  }
62
- catch {
63
- warn("Failed to update snapshot");
79
+ catch (error) {
80
+ onStorageError?.(error);
64
81
  }
65
82
  };
66
83
  const onDetach = () => {
67
- if (IS_BROWSER) {
84
+ if (CAN_LISTEN) {
68
85
  window.removeEventListener("focus", syncWithSnapshot);
86
+ window.removeEventListener("storage", onStorage);
69
87
  }
70
88
  };
71
89
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { SetState, StorageAPI, StoreConfig } from \"../types/types\";\nimport { warn } from \"./warn\";\n\nexport const createStorageAdapter = <TState, TActions>(\n config: StoreConfig<TState, TActions>,\n get: () => TState,\n set: SetState<TState>,\n) => {\n if (!config.persist) {\n return null;\n }\n\n const IS_BROWSER = typeof window !== \"undefined\";\n\n const storage =\n config.storage ?? (IS_BROWSER ? (localStorage as StorageAPI) : undefined);\n\n if (!storage) {\n warn(\n \"No storage provider found. If this happened during SSR, you can safely ignore this warning.\",\n );\n return null;\n }\n\n const { key, serializer = JSON, initialState } = config;\n const initKey = `init_${key}`;\n\n const onLoad = async () => {\n try {\n let initialStateSnapshot = storage.getItem(initKey);\n const initialStateString = serializer.stringify(initialState);\n\n if (initialStateSnapshot instanceof Promise) {\n initialStateSnapshot = await initialStateSnapshot;\n }\n\n if (initialStateSnapshot !== initialStateString) {\n storage.setItem(initKey, initialStateString);\n storage.removeItem(key);\n }\n } catch {\n warn(\n \"Failed to initialize snapshots. If this happened during SSR, you can safely ignore this warning.\",\n );\n }\n };\n\n const syncWithSnapshot = async () => {\n try {\n let currentSnapshot = storage.getItem(key);\n\n if (currentSnapshot instanceof Promise) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (currentSnapshot && currentSnapshot !== serializer.stringify(get())) {\n set(serializer.parse(currentSnapshot));\n }\n } catch {\n warn(\"Failed to update state from snapshot\");\n }\n };\n\n const onAttach = () => {\n syncWithSnapshot();\n\n if (IS_BROWSER) {\n window.addEventListener(\"focus\", syncWithSnapshot);\n }\n };\n\n const onChange = async (newState: TState) => {\n try {\n let currentSnapshot = storage.getItem(key);\n const newSnapshot = serializer.stringify(newState);\n\n if (currentSnapshot instanceof Promise) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (newSnapshot !== currentSnapshot) {\n storage.setItem(key, newSnapshot);\n }\n } catch {\n warn(\"Failed to update snapshot\");\n }\n };\n\n const onDetach = () => {\n if (IS_BROWSER) {\n window.removeEventListener(\"focus\", syncWithSnapshot);\n }\n };\n\n return {\n onLoad,\n onChange,\n onAttach,\n onDetach,\n };\n};\n"],"names":[],"mappings":";;AAGO,MAAM,oBAAoB,GAAG,CAClC,MAAqC,EACrC,GAAiB,EACjB,GAAqB,KACnB;AACF,IAAA,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;AACnB,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,WAAW;AAEhD,IAAA,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,KAAK,UAAU,GAAI,YAA2B,GAAG,SAAS,CAAC;IAE3E,IAAI,CAAC,OAAO,EAAE;QACZ,IAAI,CACF,6FAA6F,CAC9F;AACD,QAAA,OAAO,IAAI;IACb;IAEA,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM;AACvD,IAAA,MAAM,OAAO,GAAG,CAAA,KAAA,EAAQ,GAAG,EAAE;AAE7B,IAAA,MAAM,MAAM,GAAG,YAAW;AACxB,QAAA,IAAI;YACF,IAAI,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;YACnD,MAAM,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC;AAE7D,YAAA,IAAI,oBAAoB,YAAY,OAAO,EAAE;gBAC3C,oBAAoB,GAAG,MAAM,oBAAoB;YACnD;AAEA,YAAA,IAAI,oBAAoB,KAAK,kBAAkB,EAAE;AAC/C,gBAAA,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,CAAC;AAC5C,gBAAA,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YACzB;QACF;AAAE,QAAA,MAAM;YACN,IAAI,CACF,kGAAkG,CACnG;QACH;AACF,IAAA,CAAC;AAED,IAAA,MAAM,gBAAgB,GAAG,YAAW;AAClC,QAAA,IAAI;YACF,IAAI,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;AAE1C,YAAA,IAAI,eAAe,YAAY,OAAO,EAAE;gBACtC,eAAe,GAAG,MAAM,eAAe;YACzC;AAEA,YAAA,IAAI,eAAe,IAAI,eAAe,KAAK,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE;gBACtE,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YACxC;QACF;AAAE,QAAA,MAAM;YACN,IAAI,CAAC,sCAAsC,CAAC;QAC9C;AACF,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,gBAAgB,EAAE;QAElB,IAAI,UAAU,EAAE;AACd,YAAA,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,CAAC;QACpD;AACF,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAG,OAAO,QAAgB,KAAI;AAC1C,QAAA,IAAI;YACF,IAAI,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;YAC1C,MAAM,WAAW,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC;AAElD,YAAA,IAAI,eAAe,YAAY,OAAO,EAAE;gBACtC,eAAe,GAAG,MAAM,eAAe;YACzC;AAEA,YAAA,IAAI,WAAW,KAAK,eAAe,EAAE;AACnC,gBAAA,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,WAAW,CAAC;YACnC;QACF;AAAE,QAAA,MAAM;YACN,IAAI,CAAC,2BAA2B,CAAC;QACnC;AACF,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,IAAI,UAAU,EAAE;AACd,YAAA,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,CAAC;QACvD;AACF,IAAA,CAAC;IAED,OAAO;QACL,MAAM;QACN,QAAQ;QACR,QAAQ;QACR,QAAQ;KACT;AACH;;;;"}
1
+ {"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { StorageUnavailableError } from \"../lib/StorageUnavailableError\";\nimport { SetState, StorageAPI, StoreConfig } from \"../types/types\";\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof (value as PromiseLike<unknown> | null)?.then === \"function\";\n\nconst getDefaultStorage = (): StorageAPI | undefined => {\n try {\n // Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled\n return localStorage;\n } catch {\n return undefined;\n }\n};\n\nexport const createStorageAdapter = <TState, TActions>(\n config: StoreConfig<TState, TActions>,\n get: () => TState,\n set: SetState<TState>,\n) => {\n if (!config.persist) {\n return null;\n }\n\n const IS_BROWSER = typeof window !== \"undefined\";\n // React Native defines window without the event listener API\n const CAN_LISTEN =\n IS_BROWSER && typeof window.addEventListener === \"function\";\n\n const storage =\n config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);\n\n const {\n key,\n serializer = JSON,\n syncWithStorageOn = \"load\",\n onStorageError,\n } = config;\n\n if (!storage) {\n onStorageError?.(new StorageUnavailableError(key));\n return null;\n }\n\n const syncWithSnapshot = async () => {\n let nextState: TState;\n\n try {\n const prevState = get();\n let currentSnapshot = storage.getItem(key);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n\n // The state changed while reading, so the snapshot is outdated\n if (!Object.is(get(), prevState)) {\n return;\n }\n }\n\n if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {\n return;\n }\n\n nextState = serializer.parse(currentSnapshot);\n } catch (error) {\n onStorageError?.(error);\n return;\n }\n\n set(nextState);\n };\n\n const onLoad = () => {\n if (syncWithStorageOn === \"load\") {\n syncWithSnapshot();\n }\n };\n\n const onStorage = (event: StorageEvent) => {\n if (event.key === key && event.storageArea === storage) {\n syncWithSnapshot();\n }\n };\n\n const onAttach = () => {\n if (syncWithStorageOn === \"attach\") {\n syncWithSnapshot();\n }\n\n if (CAN_LISTEN) {\n window.addEventListener(\"focus\", syncWithSnapshot);\n window.addEventListener(\"storage\", onStorage);\n }\n };\n\n const onChange = async (newState: TState) => {\n try {\n let currentSnapshot = storage.getItem(key);\n const newSnapshot = serializer.stringify(newState);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (newSnapshot !== currentSnapshot) {\n await storage.setItem(key, newSnapshot);\n }\n } catch (error) {\n onStorageError?.(error);\n }\n };\n\n const onDetach = () => {\n if (CAN_LISTEN) {\n window.removeEventListener(\"focus\", syncWithSnapshot);\n window.removeEventListener(\"storage\", onStorage);\n }\n };\n\n return {\n onLoad,\n onChange,\n onAttach,\n onDetach,\n };\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Reported to `onStorageError` when a persisted store has no storage to use.
3
+ * This is expected during SSR, and can otherwise mean that local storage is inaccessible.
4
+ */
5
+ export declare class StorageUnavailableError extends Error {
6
+ constructor(key: string);
7
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Reported to `onStorageError` when a persisted store has no storage to use.
3
+ * This is expected during SSR, and can otherwise mean that local storage is inaccessible.
4
+ */
5
+ class StorageUnavailableError extends Error {
6
+ constructor(key) {
7
+ super(`[dharma-core] No storage provider was found for "${key}". If this happened during SSR, it can be safely ignored.`);
8
+ this.name = "StorageUnavailableError";
9
+ }
10
+ }
11
+
12
+ export { StorageUnavailableError };
13
+ //# sourceMappingURL=StorageUnavailableError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageUnavailableError.js","sources":["../../../src/lib/StorageUnavailableError.ts"],"sourcesContent":["/**\n * Reported to `onStorageError` when a persisted store has no storage to use.\n * This is expected during SSR, and can otherwise mean that local storage is inaccessible.\n */\nexport class StorageUnavailableError extends Error {\n constructor(key: string) {\n super(\n `[dharma-core] No storage provider was found for \"${key}\". If this happened during SSR, it can be safely ignored.`,\n );\n this.name = \"StorageUnavailableError\";\n }\n}\n"],"names":[],"mappings":"AAAA;;;AAGG;AACG,MAAO,uBAAwB,SAAQ,KAAK,CAAA;AAChD,IAAA,WAAA,CAAY,GAAW,EAAA;AACrB,QAAA,KAAK,CACH,CAAA,iDAAA,EAAoD,GAAG,CAAA,yDAAA,CAA2D,CACnH;AACD,QAAA,IAAI,CAAC,IAAI,GAAG,yBAAyB;IACvC;AACD;;;;"}
@@ -31,6 +31,7 @@ const createEffect = (store, effectFn, dependencyFn) => {
31
31
  const unmount = () => {
32
32
  unsubscribe?.();
33
33
  unsubscribe = undefined;
34
+ prev = undefined;
34
35
  };
35
36
  return {
36
37
  mount,
@@ -1 +1 @@
1
- {"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":[],"mappings":";;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;KACR;AACH;;;;"}
1
+ {"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n prev = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":[],"mappings":";;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;QACvB,IAAI,GAAG,SAAS;AAClB,IAAA,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;KACR;AACH;;;;"}
@@ -19,16 +19,15 @@ const createStore = (config) => {
19
19
  return state;
20
20
  };
21
21
  const set = (stateModifier) => {
22
+ const prevState = state;
22
23
  setSilently(stateModifier);
23
- dispatch();
24
+ if (!Object.is(state, prevState)) {
25
+ dispatch();
26
+ }
24
27
  return state;
25
28
  };
26
29
  const resetSilently = () => setSilently(initialState);
27
- const reset = () => {
28
- resetSilently();
29
- dispatch();
30
- return state;
31
- };
30
+ const reset = () => set(initialState);
32
31
  const listeners = new Set();
33
32
  const actions = config.actions?.({
34
33
  set,
@@ -45,21 +44,20 @@ const createStore = (config) => {
45
44
  listener(state);
46
45
  listeners.add(listener);
47
46
  return () => {
48
- listeners.delete(listener);
49
- if (listeners.size === 0) {
47
+ if (listeners.delete(listener) && listeners.size === 0) {
50
48
  storageAdapter?.onDetach();
51
49
  onDetach?.({ state, set, reset, initialState });
52
50
  }
53
51
  };
54
52
  };
55
53
  const dispatch = () => {
56
- storageAdapter?.onChange(state);
57
54
  onChange?.({
58
55
  state,
59
56
  set: setSilently,
60
57
  reset: resetSilently,
61
58
  initialState,
62
59
  });
60
+ storageAdapter?.onChange(state);
63
61
  listeners.forEach((listener) => listener(state));
64
62
  };
65
63
  storageAdapter?.onLoad();
@@ -1 +1 @@
1
- {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n setSilently(stateModifier);\n dispatch();\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () => {\n resetSilently();\n dispatch();\n return state;\n };\n\n const listeners = new Set<Listener<TState>>();\n const actions = config.actions?.({\n set,\n get,\n reset,\n initialState,\n }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.({ state, set, reset, initialState });\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n storageAdapter?.onDetach();\n onDetach?.({ state, set, reset, initialState });\n }\n };\n };\n\n const dispatch = () => {\n storageAdapter?.onChange(state);\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n initialState,\n });\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.({ state, set, reset, initialState });\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":[],"mappings":";;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM;IAErE,IAAI,KAAK,GAAG,YAAY;AAExB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,WAAW,GAAG,CAAC,aAAoC,KAAI;AAC3D,QAAA,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACnC,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,WAAW,CAAC,aAAa,CAAC;AAC1B,QAAA,QAAQ,EAAE;AACV,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC;IAErD,MAAM,KAAK,GAAG,MAAK;AACjB,QAAA,aAAa,EAAE;AACf,QAAA,QAAQ,EAAE;AACV,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAC7C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;AACb,KAAA,CAAa;IACd,MAAM,cAAc,GAAG,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;AAE7D,IAAA,MAAM,SAAS,GAAG,CAAC,QAA0B,KAAI;AAC/C,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;YACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,YAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD;QAEA,QAAQ,CAAC,KAAK,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAE1B,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;gBACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,gBAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YACjD;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;AAC/B,QAAA,QAAQ,GAAG;YACT,KAAK;AACL,YAAA,GAAG,EAAE,WAAW;AAChB,YAAA,KAAK,EAAE,aAAa;YACpB,YAAY;AACb,SAAA,CAAC;AACF,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,cAAc,EAAE,MAAM,EAAE;AACxB,IAAA,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE7C,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
1
+ {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n const prevState = state;\n setSilently(stateModifier);\n\n if (!Object.is(state, prevState)) {\n dispatch();\n }\n\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () => set(initialState);\n\n const listeners = new Set<Listener<TState>>();\n const actions = config.actions?.({\n set,\n get,\n reset,\n initialState,\n }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.({ state, set, reset, initialState });\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n if (listeners.delete(listener) && listeners.size === 0) {\n storageAdapter?.onDetach();\n onDetach?.({ state, set, reset, initialState });\n }\n };\n };\n\n const dispatch = () => {\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n initialState,\n });\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.({ state, set, reset, initialState });\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":[],"mappings":";;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM;IAErE,IAAI,KAAK,GAAG,YAAY;AAExB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,WAAW,GAAG,CAAC,aAAoC,KAAI;AAC3D,QAAA,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACnC,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,MAAM,SAAS,GAAG,KAAK;QACvB,WAAW,CAAC,aAAa,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE;AAChC,YAAA,QAAQ,EAAE;QACZ;AAEA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC;IAErD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC;AAErC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAC7C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;AACb,KAAA,CAAa;IACd,MAAM,cAAc,GAAG,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;AAE7D,IAAA,MAAM,SAAS,GAAG,CAAC,QAA0B,KAAI;AAC/C,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;YACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,YAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD;QAEA,QAAQ,CAAC,KAAK,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;gBACtD,cAAc,EAAE,QAAQ,EAAE;AAC1B,gBAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YACjD;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,QAAQ,GAAG;YACT,KAAK;AACL,YAAA,GAAG,EAAE,WAAW;AAChB,YAAA,KAAK,EAAE,aAAa;YACpB,YAAY;AACb,SAAA,CAAC;AACF,QAAA,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;AAC/B,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,cAAc,EAAE,MAAM,EAAE;AACxB,IAAA,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE7C,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
@@ -28,12 +28,11 @@ const deeplyEquals = (a, b) => {
28
28
  return false;
29
29
  }
30
30
  const keysA = Object.keys(a);
31
- const keysB = Object.keys(b);
32
- if (keysA.length !== keysB.length) {
31
+ if (keysA.length !== Object.keys(b).length) {
33
32
  return false;
34
33
  }
35
34
  for (const key of keysA) {
36
- if (!keysB.includes(key)) {
35
+ if (!Object.prototype.hasOwnProperty.call(b, key)) {
37
36
  return false;
38
37
  }
39
38
  if (!deeplyEquals(a[key], b[key])) {
@@ -1 +1 @@
1
- {"version":3,"file":"deeplyEquals.js","sources":["../../../src/lib/deeplyEquals.ts"],"sourcesContent":["export const deeplyEquals = (a: unknown, b: unknown): boolean => {\n if (a === b) {\n return true;\n }\n\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n\n const protoA = Object.getPrototypeOf(a);\n const protoB = Object.getPrototypeOf(b);\n\n if (protoA !== protoB) {\n return false;\n }\n\n if (protoA === Array.prototype) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n for (let i = 0; i < arrA.length; i++) {\n if (!deeplyEquals(arrA[i], arrB[i])) return false;\n }\n return true;\n }\n\n if (protoA !== Object.prototype) {\n return false;\n }\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (!keysB.includes(key)) {\n return false;\n }\n if (\n !deeplyEquals(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n\n return true;\n};\n"],"names":[],"mappings":"MAAa,YAAY,GAAG,CAAC,CAAU,EAAE,CAAU,KAAa;AAC9D,IAAA,IAAI,CAAC,KAAK,CAAC,EAAE;AACX,QAAA,OAAO,IAAI;IACb;IAEA,IACE,CAAC,KAAK,IAAI;AACV,QAAA,CAAC,KAAK,IAAI;QACV,OAAO,CAAC,KAAK,QAAQ;AACrB,QAAA,OAAO,CAAC,KAAK,QAAQ,EACrB;AACA,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAEvC,IAAA,IAAI,MAAM,KAAK,MAAM,EAAE;AACrB,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,IAAI,MAAM,KAAK,KAAK,CAAC,SAAS,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAc;QAC3B,MAAM,IAAI,GAAG,CAAc;AAC3B,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,KAAK;AAC7C,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACpC,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAAE,gBAAA,OAAO,KAAK;QACnD;AACA,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,IAAI,MAAM,KAAK,MAAM,CAAC,SAAS,EAAE;AAC/B,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE;AACjC,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE;QACvB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACxB,YAAA,OAAO,KAAK;QACd;AACA,QAAA,IACE,CAAC,YAAY,CACV,CAA6B,CAAC,GAAG,CAAC,EAClC,CAA6B,CAAC,GAAG,CAAC,CACpC,EACD;AACA,YAAA,OAAO,KAAK;QACd;IACF;AAEA,IAAA,OAAO,IAAI;AACb;;;;"}
1
+ {"version":3,"file":"deeplyEquals.js","sources":["../../../src/lib/deeplyEquals.ts"],"sourcesContent":["export const deeplyEquals = (a: unknown, b: unknown): boolean => {\n if (a === b) {\n return true;\n }\n\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n\n const protoA = Object.getPrototypeOf(a);\n const protoB = Object.getPrototypeOf(b);\n\n if (protoA !== protoB) {\n return false;\n }\n\n if (protoA === Array.prototype) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n for (let i = 0; i < arrA.length; i++) {\n if (!deeplyEquals(arrA[i], arrB[i])) return false;\n }\n return true;\n }\n\n if (protoA !== Object.prototype) {\n return false;\n }\n\n const keysA = Object.keys(a);\n\n if (keysA.length !== Object.keys(b).length) {\n return false;\n }\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) {\n return false;\n }\n if (\n !deeplyEquals(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n\n return true;\n};\n"],"names":[],"mappings":"MAAa,YAAY,GAAG,CAAC,CAAU,EAAE,CAAU,KAAa;AAC9D,IAAA,IAAI,CAAC,KAAK,CAAC,EAAE;AACX,QAAA,OAAO,IAAI;IACb;IAEA,IACE,CAAC,KAAK,IAAI;AACV,QAAA,CAAC,KAAK,IAAI;QACV,OAAO,CAAC,KAAK,QAAQ;AACrB,QAAA,OAAO,CAAC,KAAK,QAAQ,EACrB;AACA,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;AAEvC,IAAA,IAAI,MAAM,KAAK,MAAM,EAAE;AACrB,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,IAAI,MAAM,KAAK,KAAK,CAAC,SAAS,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAc;QAC3B,MAAM,IAAI,GAAG,CAAc;AAC3B,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,KAAK;AAC7C,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACpC,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAAE,gBAAA,OAAO,KAAK;QACnD;AACA,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,IAAI,MAAM,KAAK,MAAM,CAAC,SAAS,EAAE;AAC/B,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAE5B,IAAA,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;AAC1C,QAAA,OAAO,KAAK;IACd;AAEA,IAAA,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE;AACvB,QAAA,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;AACjD,YAAA,OAAO,KAAK;QACd;AACA,QAAA,IACE,CAAC,YAAY,CACV,CAA6B,CAAC,GAAG,CAAC,EAClC,CAA6B,CAAC,GAAG,CAAC,CACpC,EACD;AACA,YAAA,OAAO,KAAK;QACd;IACF;AAEA,IAAA,OAAO,IAAI;AACb;;;;"}
@@ -16,22 +16,24 @@ import { deeplyEquals } from './deeplyEquals.js';
16
16
  */
17
17
  const derive = (store, deriveFn, dependencyFn) => {
18
18
  let memo;
19
- let prev;
20
- let isStale = true;
19
+ let hasMemo = false;
20
+ let prevState;
21
+ let prevDeps;
21
22
  let unsubscribe;
22
23
  const listeners = new Set();
23
- const compute = () => {
24
- memo = deriveFn(store.get());
25
- isStale = false;
26
- return memo;
27
- };
28
24
  const get = () => {
29
- const next = dependencyFn?.(store.get());
30
- isStale &&= !prev || !deeplyEquals(prev, next);
31
- if (isStale) {
32
- prev = next;
33
- return compute();
25
+ const state = store.get();
26
+ if (hasMemo && Object.is(state, prevState)) {
27
+ return memo;
28
+ }
29
+ prevState = state;
30
+ const deps = dependencyFn?.(state);
31
+ if (hasMemo && dependencyFn && deeplyEquals(prevDeps, deps)) {
32
+ return memo;
34
33
  }
34
+ prevDeps = deps;
35
+ memo = deriveFn(state);
36
+ hasMemo = true;
35
37
  return memo;
36
38
  };
37
39
  const subscribe = (listener) => {
@@ -48,7 +50,6 @@ const derive = (store, deriveFn, dependencyFn) => {
48
50
  };
49
51
  };
50
52
  const listener = () => {
51
- isStale = true;
52
53
  listeners.forEach((listener) => listener(get()));
53
54
  };
54
55
  const mount = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { DerivedStore, Listener, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates a derived store from an existing store.\n *\n * A derived store computes its state based on the original store's state using a derive function.\n * The derived store's value is memoized and only recomputed when requested.\n *\n * @param store - The original store to derive from\n * @param deriveFn - A function that computes the derived state from the original state\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change\n *\n * @returns A new store containing the derived state\n *\n * @see {@link https://dharma.fransek.dev/core/derive/}\n */\nexport const derive = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n deriveFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): DerivedStore<TDerived> => {\n let memo: TDerived | undefined;\n let prev: unknown[] | undefined;\n let isStale = true;\n let unsubscribe: (() => void) | undefined;\n const listeners = new Set<Listener<TDerived>>();\n\n const compute = () => {\n memo = deriveFn(store.get());\n isStale = false;\n return memo;\n };\n\n const get = () => {\n const next = dependencyFn?.(store.get());\n isStale &&= !prev || !deeplyEquals(prev, next);\n\n if (isStale) {\n prev = next;\n return compute();\n }\n\n return memo as TDerived;\n };\n\n const subscribe = (listener: Listener<TDerived>) => {\n if (listeners.size === 0) {\n mount();\n }\n\n listener(get());\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n unmount();\n }\n };\n };\n\n const listener = () => {\n isStale = true;\n listeners.forEach((listener) => listener(get()));\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(listener);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n get,\n subscribe,\n mount,\n unmount,\n };\n};\n"],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;AAaG;AACI,MAAM,MAAM,GAAG,CACpB,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjB;AAC1B,IAAA,IAAI,IAA0B;AAC9B,IAAA,IAAI,IAA2B;IAC/B,IAAI,OAAO,GAAG,IAAI;AAClB,IAAA,IAAI,WAAqC;AACzC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB;IAE/C,MAAM,OAAO,GAAG,MAAK;QACnB,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC5B,OAAO,GAAG,KAAK;AACf,QAAA,OAAO,IAAI;AACb,IAAA,CAAC;IAED,MAAM,GAAG,GAAG,MAAK;QACf,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC;QAE9C,IAAI,OAAO,EAAE;YACX,IAAI,GAAG,IAAI;YACX,OAAO,OAAO,EAAE;QAClB;AAEA,QAAA,OAAO,IAAgB;AACzB,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,QAA4B,KAAI;AACjD,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,YAAA,KAAK,EAAE;QACT;AAEA,QAAA,QAAQ,CAAC,GAAG,EAAE,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAE1B,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,OAAO,GAAG,IAAI;AACd,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;QACzC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,GAAG;QACH,SAAS;QACT,KAAK;QACL,OAAO;KACR;AACH;;;;"}
1
+ {"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { DerivedStore, Listener, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates a derived store from an existing store.\n *\n * A derived store computes its state based on the original store's state using a derive function.\n * The derived store's value is memoized and only recomputed when requested.\n *\n * @param store - The original store to derive from\n * @param deriveFn - A function that computes the derived state from the original state\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change\n *\n * @returns A new store containing the derived state\n *\n * @see {@link https://dharma.fransek.dev/core/derive/}\n */\nexport const derive = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n deriveFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): DerivedStore<TDerived> => {\n let memo: TDerived | undefined;\n let hasMemo = false;\n let prevState: TState | undefined;\n let prevDeps: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n const listeners = new Set<Listener<TDerived>>();\n\n const get = () => {\n const state = store.get();\n\n if (hasMemo && Object.is(state, prevState)) {\n return memo as TDerived;\n }\n\n prevState = state;\n const deps = dependencyFn?.(state);\n\n if (hasMemo && dependencyFn && deeplyEquals(prevDeps, deps)) {\n return memo as TDerived;\n }\n\n prevDeps = deps;\n memo = deriveFn(state);\n hasMemo = true;\n return memo;\n };\n\n const subscribe = (listener: Listener<TDerived>) => {\n if (listeners.size === 0) {\n mount();\n }\n\n listener(get());\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n unmount();\n }\n };\n };\n\n const listener = () => {\n listeners.forEach((listener) => listener(get()));\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(listener);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n get,\n subscribe,\n mount,\n unmount,\n };\n};\n"],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;AAaG;AACI,MAAM,MAAM,GAAG,CACpB,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjB;AAC1B,IAAA,IAAI,IAA0B;IAC9B,IAAI,OAAO,GAAG,KAAK;AACnB,IAAA,IAAI,SAA6B;AACjC,IAAA,IAAI,QAA+B;AACnC,IAAA,IAAI,WAAqC;AACzC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB;IAE/C,MAAM,GAAG,GAAG,MAAK;AACf,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE;QAEzB,IAAI,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE;AAC1C,YAAA,OAAO,IAAgB;QACzB;QAEA,SAAS,GAAG,KAAK;AACjB,QAAA,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC;QAElC,IAAI,OAAO,IAAI,YAAY,IAAI,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE;AAC3D,YAAA,OAAO,IAAgB;QACzB;QAEA,QAAQ,GAAG,IAAI;AACf,QAAA,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC;QACtB,OAAO,GAAG,IAAI;AACd,QAAA,OAAO,IAAI;AACb,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,QAA4B,KAAI;AACjD,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,YAAA,KAAK,EAAE;QACT;AAEA,QAAA,QAAQ,CAAC,GAAG,EAAE,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAE1B,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;AACxB,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;QACzC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,GAAG;QACH,SAAS;QACT,KAAK;QACL,OAAO;KACR;AACH;;;;"}
@@ -5,6 +5,7 @@ import { StateModifier } from "../types/types";
5
5
  * @param {T} currentState - The current state object.
6
6
  * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
7
7
  * @returns {T} The new state object, which is a combination of the current state and the state modifier.
8
+ * Returns `currentState` itself if the state modifier resolves to it.
8
9
  *
9
10
  * @see {@link https://dharma.fransek.dev/core/merge/}
10
11
  */
@@ -6,21 +6,21 @@ const isObject = (value) => !!value && typeof value === "object" && !Array.isArr
6
6
  * @param {T} currentState - The current state object.
7
7
  * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
8
8
  * @returns {T} The new state object, which is a combination of the current state and the state modifier.
9
+ * Returns `currentState` itself if the state modifier resolves to it.
9
10
  *
10
11
  * @see {@link https://dharma.fransek.dev/core/merge/}
11
12
  */
12
13
  const merge = (currentState, stateModifier) => {
13
- if (isObject(stateModifier)) {
14
- return { ...currentState, ...stateModifier };
14
+ const modifiedState = typeof stateModifier === "function"
15
+ ? stateModifier(currentState)
16
+ : stateModifier;
17
+ if (Object.is(modifiedState, currentState)) {
18
+ return currentState;
15
19
  }
16
- if (typeof stateModifier === "function") {
17
- const modifiedState = stateModifier(currentState);
18
- if (isObject(modifiedState)) {
19
- return { ...currentState, ...modifiedState };
20
- }
21
- return modifiedState;
20
+ if (isObject(modifiedState)) {
21
+ return { ...currentState, ...modifiedState };
22
22
  }
23
- return stateModifier;
23
+ return modifiedState;
24
24
  };
25
25
 
26
26
  export { merge };
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n if (isObject(stateModifier)) {\n return { ...currentState, ...stateModifier };\n }\n\n if (typeof stateModifier === \"function\") {\n const modifiedState = stateModifier(currentState);\n\n if (isObject(modifiedState)) {\n return { ...currentState, ...modifiedState };\n }\n\n return modifiedState;\n }\n\n return stateModifier;\n};\n"],"names":[],"mappings":"AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,KAAc,KAC9B,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAE/D;;;;;;;;AAQG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,QAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;IAC9C;AAEA,IAAA,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE;AACvC,QAAA,MAAM,aAAa,GAAG,aAAa,CAAC,YAAY,CAAC;AAEjD,QAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,YAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;QAC9C;AAEA,QAAA,OAAO,aAAa;IACtB;AAEA,IAAA,OAAO,aAAa;AACtB;;;;"}
1
+ {"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n * Returns `currentState` itself if the state modifier resolves to it.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n const modifiedState =\n typeof stateModifier === \"function\"\n ? stateModifier(currentState)\n : stateModifier;\n\n if (Object.is(modifiedState, currentState)) {\n return currentState;\n }\n\n if (isObject(modifiedState)) {\n return { ...currentState, ...modifiedState };\n }\n\n return modifiedState as T;\n};\n"],"names":[],"mappings":"AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,KAAc,KAC9B,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAE/D;;;;;;;;;AASG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,MAAM,aAAa,GACjB,OAAO,aAAa,KAAK;AACvB,UAAE,aAAa,CAAC,YAAY;UAC1B,aAAa;IAEnB,IAAI,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE;AAC1C,QAAA,OAAO,YAAY;IACrB;AAEA,IAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,QAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;IAC9C;AAEA,IAAA,OAAO,aAAkB;AAC3B;;;;"}
@@ -67,11 +67,10 @@ export type BaseConfig<TState, TActions> = {
67
67
  /** Invoked whenever the state changes. */
68
68
  onChange?: StoreEventListener<TState>;
69
69
  };
70
- export type MaybePromise<T> = T | Promise<T>;
70
+ export type MaybePromise<T> = T | PromiseLike<T>;
71
71
  export type StorageAPI = {
72
72
  getItem: (key: string) => MaybePromise<string | null>;
73
73
  setItem: (key: string, value: string) => MaybePromise<void>;
74
- removeItem: (key: string) => MaybePromise<void>;
75
74
  };
76
75
  export type Serializer<T = any> = {
77
76
  stringify: (value: T) => string;
@@ -90,5 +89,13 @@ export type PersistConfig<TState> = {
90
89
  storage?: StorageAPI;
91
90
  /** The serializer to use for storing the state. Defaults to JSON. */
92
91
  serializer?: Serializer<TState>;
92
+ /**
93
+ * When to initially synchronize the store's state with the storage. Defaults to "load".
94
+ *
95
+ * Set to "attach" when using SSR to avoid hydration errors.
96
+ */
97
+ syncWithStorageOn?: "load" | "attach";
98
+ /** Invoked when a storage error occurs. */
99
+ onStorageError?: (error: unknown) => void;
93
100
  };
94
101
  export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dharma-core",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "author": "Frans Ekman <fransedvinekman@gmail.com>",
5
5
  "license": "MIT",
6
6
  "description": "A simple and lightweight state management solution for JavaScript and TypeScript applications.",
@@ -1 +0,0 @@
1
- export declare const warn: (message: string) => void;
@@ -1,6 +0,0 @@
1
- 'use strict';
2
-
3
- const warn = (message) => console.warn(`[dharma-core] ${message}`);
4
-
5
- exports.warn = warn;
6
- //# sourceMappingURL=warn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"warn.js","sources":["../../../src/internal/warn.ts"],"sourcesContent":["export const warn = (message: string) =>\n console.warn(`[dharma-core] ${message}`);\n"],"names":[],"mappings":";;AAAO,MAAM,IAAI,GAAG,CAAC,OAAe,KAClC,OAAO,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAA,CAAE;;;;"}
@@ -1 +0,0 @@
1
- export declare const warn: (message: string) => void;
@@ -1,4 +0,0 @@
1
- const warn = (message) => console.warn(`[dharma-core] ${message}`);
2
-
3
- export { warn };
4
- //# sourceMappingURL=warn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"warn.js","sources":["../../../src/internal/warn.ts"],"sourcesContent":["export const warn = (message: string) =>\n console.warn(`[dharma-core] ${message}`);\n"],"names":[],"mappings":"AAAO,MAAM,IAAI,GAAG,CAAC,OAAe,KAClC,OAAO,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAA,CAAE;;;;"}