dharma-core 0.7.1 → 0.9.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 (38) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/index.d.ts +1 -0
  3. package/dist/cjs/index.js +2 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/lib/createStorageAdapter.d.ts +1 -1
  6. package/dist/cjs/lib/createStorageAdapter.js.map +1 -1
  7. package/dist/cjs/lib/createStore.d.ts +2 -75
  8. package/dist/cjs/lib/createStore.js +2 -79
  9. package/dist/cjs/lib/createStore.js.map +1 -1
  10. package/dist/cjs/lib/deeplyEquals.d.ts +1 -0
  11. package/dist/cjs/lib/deeplyEquals.js +49 -0
  12. package/dist/cjs/lib/deeplyEquals.js.map +1 -0
  13. package/dist/cjs/lib/derive.d.ts +16 -0
  14. package/dist/cjs/lib/derive.js +81 -0
  15. package/dist/cjs/lib/derive.js.map +1 -0
  16. package/dist/cjs/lib/merge.d.ts +2 -26
  17. package/dist/cjs/lib/merge.js +14 -29
  18. package/dist/cjs/lib/merge.js.map +1 -1
  19. package/dist/cjs/lib/types.d.ts +18 -15
  20. package/dist/esm/index.d.ts +1 -0
  21. package/dist/esm/index.js +1 -0
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/lib/createStorageAdapter.d.ts +1 -1
  24. package/dist/esm/lib/createStorageAdapter.js.map +1 -1
  25. package/dist/esm/lib/createStore.d.ts +2 -75
  26. package/dist/esm/lib/createStore.js +2 -79
  27. package/dist/esm/lib/createStore.js.map +1 -1
  28. package/dist/esm/lib/deeplyEquals.d.ts +1 -0
  29. package/dist/esm/lib/deeplyEquals.js +47 -0
  30. package/dist/esm/lib/deeplyEquals.js.map +1 -0
  31. package/dist/esm/lib/derive.d.ts +16 -0
  32. package/dist/esm/lib/derive.js +79 -0
  33. package/dist/esm/lib/derive.js.map +1 -0
  34. package/dist/esm/lib/merge.d.ts +2 -26
  35. package/dist/esm/lib/merge.js +14 -29
  36. package/dist/esm/lib/merge.js.map +1 -1
  37. package/dist/esm/lib/types.d.ts +18 -15
  38. package/package.json +6 -6
package/README.md CHANGED
@@ -31,4 +31,4 @@ decrement();
31
31
  unsubscribe();
32
32
  ```
33
33
 
34
- [Read the documentation](https://fransek.github.io/dharma/modules/dharma-core.html)
34
+ [Read the documentation](https://dharma.fransek.dev/)
@@ -1,3 +1,4 @@
1
1
  export * from "./lib/createStore";
2
+ export * from "./lib/derive";
2
3
  export * from "./lib/merge";
3
4
  export * from "./lib/types";
package/dist/cjs/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var createStore = require('./lib/createStore.js');
4
+ var derive = require('./lib/derive.js');
4
5
  var merge = require('./lib/merge.js');
5
6
 
6
7
 
7
8
 
8
9
  exports.createStore = createStore.createStore;
10
+ exports.derive = derive.derive;
9
11
  exports.merge = merge.merge;
10
12
  //# 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,5 +1,5 @@
1
1
  import { SetState, StoreConfig } from "./types";
2
- export declare const createStorageAdapter: <TState extends object, TActions extends object = Record<never, never>>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
2
+ export declare const createStorageAdapter: <TState, TActions>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
3
3
  onLoad: () => Promise<void>;
4
4
  onChange: (newState: TState) => Promise<void>;
5
5
  onAttach: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"createStorageAdapter.js","sources":["../../../src/lib/createStorageAdapter.ts"],"sourcesContent":["import { SetState, StorageAPI, StoreConfig } from \"./types\";\nimport { warn } from \"./warn\";\n\nexport const createStorageAdapter = <\n TState extends object,\n TActions extends object = Record<never, never>,\n>(\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,CAIlC,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/lib/createStorageAdapter.ts"],"sourcesContent":["import { SetState, StorageAPI, StoreConfig } from \"./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;;;;"}
@@ -6,79 +6,6 @@ import { Store, StoreConfig } from "./types";
6
6
  *
7
7
  * @returns {Store<TState, TActions>} The created store with state management methods.
8
8
  *
9
- * @example
10
- * Basic usage:
11
- * ```ts
12
- * import { createStore } from "dharma-core";
13
- *
14
- * const store = createStore({
15
- * initialState: { count: 0 },
16
- * defineActions: ({ set }) => ({
17
- * increment: () => set((state) => ({ count: state.count + 1 })),
18
- * decrement: () => set((state) => ({ count: state.count - 1 })),
19
- * }),
20
- * });
21
- * ```
22
- *
23
- * @example
24
- * Lifecycle hooks:
25
- * ```ts
26
- * import { createStore } from "dharma-core";
27
- * import { State } from "./types";
28
- *
29
- * const initialState: State = { data: null, loading: true, error: null };
30
- *
31
- * const store = createStore({
32
- * initialState,
33
- * onAttach: async ({ set }) => {
34
- * try {
35
- * const response = await fetch("https://api.example.com/data");
36
- * const data = await response.json();
37
- * set({ data, loading: false });
38
- * } catch (error) {
39
- * set({ error, loading: false });
40
- * }
41
- * },
42
- * onDetach: ({ reset }) => reset(),
43
- * });
44
- * ```
45
- *
46
- * @example
47
- * Persisting state:
48
- * ```ts
49
- * import { createStore } from "dharma-core";
50
- *
51
- * const store = createStore({
52
- * persist: true,
53
- * key: "count",
54
- * initialState: { count: 0 },
55
- * defineActions: ({ set }) => ({
56
- * increment: () => set((state) => ({ count: state.count + 1 })),
57
- * decrement: () => set((state) => ({ count: state.count - 1 })),
58
- * }),
59
- * });
60
- * ```
61
- *
62
- * **Note:** The state needs to be serializable by whatever serializer you choose (JSON by default).
63
- * If you need something more versatile I would recommend using a library like [superjson](https://github.com/flightcontrolhq/superjson).
64
- *
65
- * @example
66
- * Custom storage and serializer:
67
- * ```ts
68
- * import { createStore } from "dharma-core";
69
- * import superjson from "superjson";
70
- *
71
- * const store = createStore({
72
- * persist: true,
73
- * key: "count",
74
- * storage: sessionStorage,
75
- * serializer: superjson,
76
- * initialState: { count: 0 },
77
- * defineActions: ({ set }) => ({
78
- * increment: () => set((state) => ({ count: state.count + 1 })),
79
- * decrement: () => set((state) => ({ count: state.count - 1 })),
80
- * }),
81
- * });
82
- * ```
9
+ * @see {@link https://dharma.fransek.dev/core/createstore/}
83
10
  */
84
- export declare const createStore: <TState extends object, TActions extends object = Record<never, never>>(config: StoreConfig<TState, TActions>) => Store<TState, TActions>;
11
+ export declare const createStore: <TState, TActions = undefined>(config: StoreConfig<TState, TActions>) => Store<TState, TActions>;
@@ -10,80 +10,7 @@ var merge = require('./merge.js');
10
10
  *
11
11
  * @returns {Store<TState, TActions>} The created store with state management methods.
12
12
  *
13
- * @example
14
- * Basic usage:
15
- * ```ts
16
- * import { createStore } from "dharma-core";
17
- *
18
- * const store = createStore({
19
- * initialState: { count: 0 },
20
- * defineActions: ({ set }) => ({
21
- * increment: () => set((state) => ({ count: state.count + 1 })),
22
- * decrement: () => set((state) => ({ count: state.count - 1 })),
23
- * }),
24
- * });
25
- * ```
26
- *
27
- * @example
28
- * Lifecycle hooks:
29
- * ```ts
30
- * import { createStore } from "dharma-core";
31
- * import { State } from "./types";
32
- *
33
- * const initialState: State = { data: null, loading: true, error: null };
34
- *
35
- * const store = createStore({
36
- * initialState,
37
- * onAttach: async ({ set }) => {
38
- * try {
39
- * const response = await fetch("https://api.example.com/data");
40
- * const data = await response.json();
41
- * set({ data, loading: false });
42
- * } catch (error) {
43
- * set({ error, loading: false });
44
- * }
45
- * },
46
- * onDetach: ({ reset }) => reset(),
47
- * });
48
- * ```
49
- *
50
- * @example
51
- * Persisting state:
52
- * ```ts
53
- * import { createStore } from "dharma-core";
54
- *
55
- * const store = createStore({
56
- * persist: true,
57
- * key: "count",
58
- * initialState: { count: 0 },
59
- * defineActions: ({ set }) => ({
60
- * increment: () => set((state) => ({ count: state.count + 1 })),
61
- * decrement: () => set((state) => ({ count: state.count - 1 })),
62
- * }),
63
- * });
64
- * ```
65
- *
66
- * **Note:** The state needs to be serializable by whatever serializer you choose (JSON by default).
67
- * If you need something more versatile I would recommend using a library like [superjson](https://github.com/flightcontrolhq/superjson).
68
- *
69
- * @example
70
- * Custom storage and serializer:
71
- * ```ts
72
- * import { createStore } from "dharma-core";
73
- * import superjson from "superjson";
74
- *
75
- * const store = createStore({
76
- * persist: true,
77
- * key: "count",
78
- * storage: sessionStorage,
79
- * serializer: superjson,
80
- * initialState: { count: 0 },
81
- * defineActions: ({ set }) => ({
82
- * increment: () => set((state) => ({ count: state.count + 1 })),
83
- * decrement: () => set((state) => ({ count: state.count - 1 })),
84
- * }),
85
- * });
86
- * ```
13
+ * @see {@link https://dharma.fransek.dev/core/createstore/}
87
14
  */
88
15
  const createStore = (config) => {
89
16
  const { initialState, defineActions, onLoad, onAttach, onDetach, onChange } = config;
@@ -105,9 +32,7 @@ const createStore = (config) => {
105
32
  return state;
106
33
  };
107
34
  const listeners = new Set();
108
- const actions = defineActions
109
- ? defineActions({ set, get, reset })
110
- : {};
35
+ const actions = defineActions?.({ set, get, reset });
111
36
  const storageAdapter = createStorageAdapter.createStorageAdapter(config, get, set);
112
37
  const subscribe = (listener) => {
113
38
  if (listeners.size === 0) {
@@ -137,8 +62,6 @@ const createStore = (config) => {
137
62
  storageAdapter?.onLoad();
138
63
  return {
139
64
  get,
140
- set,
141
- reset,
142
65
  subscribe,
143
66
  actions,
144
67
  };
@@ -1 +1 @@
1
- {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"./createStorageAdapter\";\nimport { merge } from \"./merge\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"./types\";\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 * @example\n * Basic usage:\n * ```ts\n * import { createStore } from \"dharma-core\";\n *\n * const store = createStore({\n * initialState: { count: 0 },\n * defineActions: ({ set }) => ({\n * increment: () => set((state) => ({ count: state.count + 1 })),\n * decrement: () => set((state) => ({ count: state.count - 1 })),\n * }),\n * });\n * ```\n *\n * @example\n * Lifecycle hooks:\n * ```ts\n * import { createStore } from \"dharma-core\";\n * import { State } from \"./types\";\n *\n * const initialState: State = { data: null, loading: true, error: null };\n *\n * const store = createStore({\n * initialState,\n * onAttach: async ({ set }) => {\n * try {\n * const response = await fetch(\"https://api.example.com/data\");\n * const data = await response.json();\n * set({ data, loading: false });\n * } catch (error) {\n * set({ error, loading: false });\n * }\n * },\n * onDetach: ({ reset }) => reset(),\n * });\n * ```\n *\n * @example\n * Persisting state:\n * ```ts\n * import { createStore } from \"dharma-core\";\n *\n * const store = createStore({\n * persist: true,\n * key: \"count\",\n * initialState: { count: 0 },\n * defineActions: ({ set }) => ({\n * increment: () => set((state) => ({ count: state.count + 1 })),\n * decrement: () => set((state) => ({ count: state.count - 1 })),\n * }),\n * });\n * ```\n *\n * **Note:** The state needs to be serializable by whatever serializer you choose (JSON by default).\n * If you need something more versatile I would recommend using a library like [superjson](https://github.com/flightcontrolhq/superjson).\n *\n * @example\n * Custom storage and serializer:\n * ```ts\n * import { createStore } from \"dharma-core\";\n * import superjson from \"superjson\";\n *\n * const store = createStore({\n * persist: true,\n * key: \"count\",\n * storage: sessionStorage,\n * serializer: superjson,\n * initialState: { count: 0 },\n * defineActions: ({ set }) => ({\n * increment: () => set((state) => ({ count: state.count + 1 })),\n * decrement: () => set((state) => ({ count: state.count - 1 })),\n * }),\n * });\n * ```\n */\nexport const createStore = <\n TState extends object,\n TActions extends object = Record<never, never>,\n>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, defineActions, onLoad, onAttach, onDetach, onChange } =\n 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 = defineActions\n ? defineActions({ set, get, reset })\n : ({} as TActions);\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n onAttach?.({ state, set, reset });\n storageAdapter?.onAttach();\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n onDetach?.({ state, set, reset });\n storageAdapter?.onDetach();\n }\n };\n };\n\n const dispatch = () => {\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n });\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n onLoad?.({ state, set, reset });\n storageAdapter?.onLoad();\n\n return {\n get,\n set,\n reset,\n subscribe,\n actions,\n };\n};\n"],"names":["merge","createStorageAdapter"],"mappings":";;;;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiFG;AACI,MAAM,WAAW,GAAG,CAIzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GACzE,MAAM;IAER,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;IAC7C,MAAM,OAAO,GAAG;UACZ,aAAa,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE;UAChC,EAAe;IACpB,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,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACjC,cAAc,EAAE,QAAQ,EAAE;QAC5B;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,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;gBACjC,cAAc,EAAE,QAAQ,EAAE;YAC5B;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;AACrB,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,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC/B,cAAc,EAAE,MAAM,EAAE;IAExB,OAAO;QACL,GAAG;QACH,GAAG;QACH,KAAK;QACL,SAAS;QACT,OAAO;KACR;AACH;;;;"}
1
+ {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"./createStorageAdapter\";\nimport { merge } from \"./merge\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"./types\";\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, defineActions, onLoad, onAttach, onDetach, onChange } =\n 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 = defineActions?.({ set, get, reset }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n onAttach?.({ state, set, reset });\n storageAdapter?.onAttach();\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n onDetach?.({ state, set, reset });\n storageAdapter?.onDetach();\n }\n };\n };\n\n const dispatch = () => {\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n });\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n onLoad?.({ state, set, reset });\n storageAdapter?.onLoad();\n\n return {\n get,\n subscribe,\n actions,\n };\n};\n"],"names":["merge","createStorageAdapter"],"mappings":";;;;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GACzE,MAAM;IAER,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,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAa;IAChE,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,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACjC,cAAc,EAAE,QAAQ,EAAE;QAC5B;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,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;gBACjC,cAAc,EAAE,QAAQ,EAAE;YAC5B;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;AACrB,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,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC/B,cAAc,EAAE,MAAM,EAAE;IAExB,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;KACR;AACH;;;;"}
@@ -0,0 +1 @@
1
+ export declare const deeplyEquals: (a: unknown, b: unknown) => boolean;
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const deeplyEquals = (a, b) => {
4
+ if (a === b) {
5
+ return true;
6
+ }
7
+ if (a === null ||
8
+ b === null ||
9
+ typeof a !== "object" ||
10
+ typeof b !== "object") {
11
+ return false;
12
+ }
13
+ const protoA = Object.getPrototypeOf(a);
14
+ const protoB = Object.getPrototypeOf(b);
15
+ if (protoA !== protoB) {
16
+ return false;
17
+ }
18
+ if (protoA === Array.prototype) {
19
+ const arrA = a;
20
+ const arrB = b;
21
+ if (arrA.length !== arrB.length)
22
+ return false;
23
+ for (let i = 0; i < arrA.length; i++) {
24
+ if (!deeplyEquals(arrA[i], arrB[i]))
25
+ return false;
26
+ }
27
+ return true;
28
+ }
29
+ if (protoA !== Object.prototype) {
30
+ return false;
31
+ }
32
+ const keysA = Object.keys(a);
33
+ const keysB = Object.keys(b);
34
+ if (keysA.length !== keysB.length) {
35
+ return false;
36
+ }
37
+ for (const key of keysA) {
38
+ if (!keysB.includes(key)) {
39
+ return false;
40
+ }
41
+ if (!deeplyEquals(a[key], b[key])) {
42
+ return false;
43
+ }
44
+ }
45
+ return true;
46
+ };
47
+
48
+ exports.deeplyEquals = deeplyEquals;
49
+ //# sourceMappingURL=deeplyEquals.js.map
@@ -0,0 +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;;;;"}
@@ -0,0 +1,16 @@
1
+ import { DerivedStore, Store } from "./types";
2
+ /**
3
+ * Creates a derived store from an existing store.
4
+ *
5
+ * A derived store computes its state based on the original store's state using a derive function.
6
+ * The derived store's value is memoized and only recomputed when requested.
7
+ *
8
+ * @param store - The original store to derive from
9
+ * @param deriveFn - A function that computes the derived state from the original state
10
+ * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change
11
+ *
12
+ * @returns A new store containing the derived state
13
+ *
14
+ * @see {@link https://dharma.fransek.dev/core/derive/}
15
+ */
16
+ export declare const derive: <TState, TActions, TDerived>(store: Store<TState, TActions>, deriveFn: (state: TState) => TDerived, dependencyFn?: (state: TState) => unknown[]) => DerivedStore<TDerived>;
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ var deeplyEquals = require('./deeplyEquals.js');
4
+
5
+ /**
6
+ * Creates a derived store from an existing store.
7
+ *
8
+ * A derived store computes its state based on the original store's state using a derive function.
9
+ * The derived store's value is memoized and only recomputed when requested.
10
+ *
11
+ * @param store - The original store to derive from
12
+ * @param deriveFn - A function that computes the derived state from the original state
13
+ * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change
14
+ *
15
+ * @returns A new store containing the derived state
16
+ *
17
+ * @see {@link https://dharma.fransek.dev/core/derive/}
18
+ */
19
+ const derive = (store, deriveFn, dependencyFn) => {
20
+ let memo;
21
+ let prev;
22
+ let isStale = true;
23
+ let unsubscribe;
24
+ const listeners = new Set();
25
+ const compute = () => {
26
+ memo = deriveFn(store.get());
27
+ isStale = false;
28
+ return memo;
29
+ };
30
+ const get = () => {
31
+ if (!dependencyFn) {
32
+ if (isStale) {
33
+ return compute();
34
+ }
35
+ return memo;
36
+ }
37
+ const next = dependencyFn(store.get());
38
+ isStale &&= !prev || !deeplyEquals.deeplyEquals(prev, next);
39
+ if (isStale) {
40
+ prev = next;
41
+ return compute();
42
+ }
43
+ return memo;
44
+ };
45
+ const subscribe = (listener) => {
46
+ if (listeners.size === 0) {
47
+ mount();
48
+ }
49
+ listener(get());
50
+ listeners.add(listener);
51
+ return () => {
52
+ listeners.delete(listener);
53
+ if (listeners.size === 0) {
54
+ unmount();
55
+ }
56
+ };
57
+ };
58
+ const mount = () => {
59
+ if (!unsubscribe) {
60
+ unsubscribe = store.subscribe(() => {
61
+ isStale = true;
62
+ listeners.forEach((listener) => listener(get()));
63
+ });
64
+ }
65
+ };
66
+ const unmount = () => {
67
+ unsubscribe?.();
68
+ unsubscribe = undefined;
69
+ };
70
+ return {
71
+ get,
72
+ subscribe,
73
+ actions: {
74
+ mount,
75
+ unmount,
76
+ },
77
+ };
78
+ };
79
+
80
+ exports.derive = derive;
81
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { deeplyEquals } from \"./deeplyEquals\";\nimport { DerivedStore, Listener, Store } from \"./types\";\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 if (!dependencyFn) {\n if (isStale) {\n return compute();\n }\n\n return memo as TDerived;\n }\n\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 mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(() => {\n isStale = true;\n listeners.forEach((listener) => listener(get()));\n });\n }\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n get,\n subscribe,\n actions: {\n mount,\n unmount,\n },\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,IAAI,CAAC,YAAY,EAAE;YACjB,IAAI,OAAO,EAAE;gBACX,OAAO,OAAO,EAAE;YAClB;AAEA,YAAA,OAAO,IAAgB;QACzB;QAEA,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACtC,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,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAK;gBACjC,OAAO,GAAG,IAAI;AACd,gBAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;AAClD,YAAA,CAAC,CAAC;QACJ;AACF,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;AACzB,IAAA,CAAC;IAED,OAAO;QACL,GAAG;QACH,SAAS;AACT,QAAA,OAAO,EAAE;YACP,KAAK;YACL,OAAO;AACR,SAAA;KACF;AACH;;;;"}
@@ -6,30 +6,6 @@ import { StateModifier } from "./types";
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
8
  *
9
- * @example
10
- * ```ts
11
- * import { createStore, merge, StateModifier } from "dharma-core";
12
- *
13
- * const store = createStore({
14
- * initialState: {
15
- * counter: {
16
- * count: 0,
17
- * },
18
- * // ...
19
- * },
20
- * defineActions: ({ set }) => {
21
- * const setCounter = (counter: StateModifier<{ count: number }>) =>
22
- * set((state) => ({
23
- * counter: merge(state.counter, counter),
24
- * }));
25
- *
26
- * return {
27
- * increment: () => setCounter((state) => ({ count: state.count + 1 })),
28
- * decrement: () => setCounter((state) => ({ count: state.count - 1 })),
29
- * // ...
30
- * };
31
- * },
32
- * });
33
- * ```
9
+ * @see {@link https://dharma.fransek.dev/core/merge/}
34
10
  */
35
- export declare const merge: <T extends object>(currentState: T, stateModifier: StateModifier<T>) => T;
11
+ export declare const merge: <T>(currentState: T, stateModifier: StateModifier<T>) => T;
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ const isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
3
5
  /**
4
6
  * Merges the current state with a state modifier.
5
7
  *
@@ -7,37 +9,20 @@
7
9
  * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
8
10
  * @returns {T} The new state object, which is a combination of the current state and the state modifier.
9
11
  *
10
- * @example
11
- * ```ts
12
- * import { createStore, merge, StateModifier } from "dharma-core";
13
- *
14
- * const store = createStore({
15
- * initialState: {
16
- * counter: {
17
- * count: 0,
18
- * },
19
- * // ...
20
- * },
21
- * defineActions: ({ set }) => {
22
- * const setCounter = (counter: StateModifier<{ count: number }>) =>
23
- * set((state) => ({
24
- * counter: merge(state.counter, counter),
25
- * }));
26
- *
27
- * return {
28
- * increment: () => setCounter((state) => ({ count: state.count + 1 })),
29
- * decrement: () => setCounter((state) => ({ count: state.count - 1 })),
30
- * // ...
31
- * };
32
- * },
33
- * });
34
- * ```
12
+ * @see {@link https://dharma.fransek.dev/core/merge/}
35
13
  */
36
14
  const merge = (currentState, stateModifier) => {
37
- const newState = typeof stateModifier === "function"
38
- ? stateModifier(currentState)
39
- : stateModifier;
40
- return { ...currentState, ...newState };
15
+ if (isObject(stateModifier)) {
16
+ return { ...currentState, ...stateModifier };
17
+ }
18
+ if (typeof stateModifier === "function") {
19
+ const modifiedState = stateModifier(currentState);
20
+ if (isObject(modifiedState)) {
21
+ return { ...currentState, ...modifiedState };
22
+ }
23
+ return modifiedState;
24
+ }
25
+ return stateModifier;
41
26
  };
42
27
 
43
28
  exports.merge = merge;
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"./types\";\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 * @example\n * ```ts\n * import { createStore, merge, StateModifier } from \"dharma-core\";\n *\n * const store = createStore({\n * initialState: {\n * counter: {\n * count: 0,\n * },\n * // ...\n * },\n * defineActions: ({ set }) => {\n * const setCounter = (counter: StateModifier<{ count: number }>) =>\n * set((state) => ({\n * counter: merge(state.counter, counter),\n * }));\n *\n * return {\n * increment: () => setCounter((state) => ({ count: state.count + 1 })),\n * decrement: () => setCounter((state) => ({ count: state.count - 1 })),\n * // ...\n * };\n * },\n * });\n * ```\n */\nexport const merge = <T extends object>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n const newState =\n typeof stateModifier === \"function\"\n ? stateModifier(currentState)\n : stateModifier;\n return { ...currentState, ...newState };\n};\n"],"names":[],"mappings":";;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,MAAM,QAAQ,GACZ,OAAO,aAAa,KAAK;AACvB,UAAE,aAAa,CAAC,YAAY;UAC1B,aAAa;AACnB,IAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,QAAQ,EAAE;AACzC;;;;"}
1
+ {"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"./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,18 +1,21 @@
1
- export type Listener<TState extends object> = (state: TState) => void;
2
- export type SetState<TState extends object> = (stateModifier: StateModifier<TState>) => TState;
3
- export type Store<TState extends object, TActions extends object> = {
1
+ export type Listener<TState> = (state: TState) => void;
2
+ export type SetState<TState> = (stateModifier: StateModifier<TState>) => TState;
3
+ export type Store<TState, TActions> = {
4
4
  /** Returns the current state of the store. */
5
5
  get: () => TState;
6
- /** Sets the state of the store. */
7
- set: SetState<TState>;
8
- /** Resets the state of the store to its initial value. */
9
- reset: () => TState;
10
6
  /** Actions that can modify the state of the store. */
11
7
  actions: TActions;
12
8
  /** Subscribes to changes in the state of the store. Returns an unsubscribe function. */
13
9
  subscribe: (listener: Listener<TState>) => () => void;
14
10
  };
15
- export type StoreEventContext<TState extends object> = {
11
+ export type DerivedStoreActions = {
12
+ /** Start listening to changes in the original store. Called automatically on the first subscription. */
13
+ mount: () => void;
14
+ /** Stop listening to changes in the original store. Called automatically when there are no more subscribers. */
15
+ unmount: () => void;
16
+ };
17
+ export type DerivedStore<TState> = Store<TState, DerivedStoreActions>;
18
+ export type StoreEventContext<TState> = {
16
19
  /** The current state of the store. */
17
20
  state: TState;
18
21
  /** Sets the state of the store. */
@@ -20,9 +23,9 @@ export type StoreEventContext<TState extends object> = {
20
23
  /** Resets the state of the store to its initial value. */
21
24
  reset: () => TState;
22
25
  };
23
- export type StoreEventListener<TState extends object> = (context: StoreEventContext<TState>) => void;
24
- export type StateModifier<TState extends object> = Partial<TState> | ((state: TState) => Partial<TState>);
25
- export type StateHandler<TState extends object> = {
26
+ export type StoreEventListener<TState> = (context: StoreEventContext<TState>) => void;
27
+ export type StateModifier<TState> = Partial<TState> | ((state: TState) => Partial<TState>);
28
+ export type StateHandler<TState> = {
26
29
  /** Returns the current state of the store. */
27
30
  get: () => TState;
28
31
  /** Sets the state of the store. */
@@ -30,8 +33,8 @@ export type StateHandler<TState extends object> = {
30
33
  /** Resets the state of the store to its initial value. */
31
34
  reset: () => TState;
32
35
  };
33
- export type DefineActions<TState extends object, TActions> = (stateHandler: StateHandler<TState>) => TActions;
34
- export type BaseConfig<TState extends object, TActions extends object> = {
36
+ export type DefineActions<TState, TActions> = (stateHandler: StateHandler<TState>) => TActions;
37
+ export type BaseConfig<TState, TActions> = {
35
38
  /** The initial state of the store. */
36
39
  initialState: TState;
37
40
  /** A function that defines actions that can modify the state. */
@@ -59,7 +62,7 @@ export type NoPersistConfig = {
59
62
  /** Whether to persist the state of the store. */
60
63
  persist?: false;
61
64
  };
62
- export type PersistConfig<TState extends object> = {
65
+ export type PersistConfig<TState> = {
63
66
  /** Whether to persist the state of the store. */
64
67
  persist: true;
65
68
  /** The unique key used to identify this store in storage. Required if `persist` is true. */
@@ -69,4 +72,4 @@ export type PersistConfig<TState extends object> = {
69
72
  /** The serializer to use for storing the state. Defaults to JSON. */
70
73
  serializer?: Serializer<TState>;
71
74
  };
72
- export type StoreConfig<TState extends object, TActions extends object> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
75
+ export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
@@ -1,3 +1,4 @@
1
1
  export * from "./lib/createStore";
2
+ export * from "./lib/derive";
2
3
  export * from "./lib/merge";
3
4
  export * from "./lib/types";
package/dist/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { createStore } from './lib/createStore.js';
2
+ export { derive } from './lib/derive.js';
2
3
  export { merge } from './lib/merge.js';
3
4
  //# 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,5 +1,5 @@
1
1
  import { SetState, StoreConfig } from "./types";
2
- export declare const createStorageAdapter: <TState extends object, TActions extends object = Record<never, never>>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
2
+ export declare const createStorageAdapter: <TState, TActions>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
3
3
  onLoad: () => Promise<void>;
4
4
  onChange: (newState: TState) => Promise<void>;
5
5
  onAttach: () => void;