dharma-core 0.14.0 → 0.16.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.
@@ -25,9 +25,7 @@ const createEffect = (store, effectFn, dependencyFn) => {
25
25
  }
26
26
  };
27
27
  const mount = () => {
28
- if (!unsubscribe) {
29
- unsubscribe = store.subscribe(effect);
30
- }
28
+ unsubscribe ??= store.subscribe(effect);
31
29
  return unmount;
32
30
  };
33
31
  const unmount = () => {
@@ -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 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;;;;"}
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 unsubscribe ??= store.subscribe(effect);\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;AACjB,QAAA,WAAW,KAAK,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;AACvC,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;;;;"}
@@ -15,55 +15,54 @@ var merge = require('./merge.js');
15
15
  const createStore = (config) => {
16
16
  const { initialState, onLoad, onAttach, onDetach, onChange } = config;
17
17
  let state = initialState;
18
+ // Set while onChange runs, so its synchronous sets join the current dispatch
19
+ let isSilent = false;
18
20
  const get = () => state;
19
- const setSilently = (stateModifier) => {
20
- state = merge.merge(state, stateModifier);
21
- return state;
22
- };
23
21
  const set = (stateModifier) => {
24
22
  const prevState = state;
25
- setSilently(stateModifier);
26
- if (!Object.is(state, prevState)) {
23
+ state = merge.merge(state, stateModifier);
24
+ if (!Object.is(state, prevState) && !isSilent) {
27
25
  dispatch();
28
26
  }
29
27
  return state;
30
28
  };
31
- const resetSilently = () => setSilently(initialState);
32
29
  const reset = () => set(initialState);
33
30
  const listeners = new Set();
34
- const actions = config.actions?.({
35
- set,
31
+ const ctx = {
36
32
  get,
33
+ set,
37
34
  reset,
38
35
  initialState,
39
- });
36
+ };
37
+ const actions = config.actions?.(ctx);
40
38
  const storageAdapter = createStorageAdapter.createStorageAdapter(config, get, set);
41
39
  const subscribe = (listener) => {
42
40
  if (listeners.size === 0) {
43
41
  storageAdapter?.onAttach();
44
- onAttach?.({ state, set, reset, initialState });
42
+ onAttach?.(ctx);
45
43
  }
46
44
  listener(state);
47
45
  listeners.add(listener);
48
46
  return () => {
49
47
  if (listeners.delete(listener) && listeners.size === 0) {
50
48
  storageAdapter?.onDetach();
51
- onDetach?.({ state, set, reset, initialState });
49
+ onDetach?.(ctx);
52
50
  }
53
51
  };
54
52
  };
55
53
  const dispatch = () => {
56
- onChange?.({
57
- state,
58
- set: setSilently,
59
- reset: resetSilently,
60
- initialState,
61
- });
54
+ isSilent = true;
55
+ try {
56
+ onChange?.(ctx);
57
+ }
58
+ finally {
59
+ isSilent = false;
60
+ }
62
61
  storageAdapter?.onChange(state);
63
62
  listeners.forEach((listener) => listener(state));
64
63
  };
65
64
  storageAdapter?.onLoad();
66
- onLoad?.({ state, set, reset, initialState });
65
+ onLoad?.(ctx);
67
66
  return {
68
67
  get,
69
68
  subscribe,
@@ -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 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;;;;"}
1
+ {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport {\n Listener,\n StateContext,\n StateModifier,\n Store,\n StoreConfig,\n} 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 // Set while onChange runs, so its synchronous sets join the current dispatch\n let isSilent = false;\n\n const get = () => state;\n\n const set = (stateModifier: StateModifier<TState>) => {\n const prevState = state;\n state = merge(state, stateModifier);\n\n if (!Object.is(state, prevState) && !isSilent) {\n dispatch();\n }\n\n return state;\n };\n\n const reset = () => set(initialState);\n\n const listeners = new Set<Listener<TState>>();\n\n const ctx: StateContext<TState> = {\n get,\n set,\n reset,\n initialState,\n };\n\n const actions = config.actions?.(ctx) as TActions;\n\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.(ctx);\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?.(ctx);\n }\n };\n };\n\n const dispatch = () => {\n isSilent = true;\n try {\n onChange?.(ctx);\n } finally {\n isSilent = false;\n }\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.(ctx);\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":["merge","createStorageAdapter"],"mappings":";;;;;AAUA;;;;;;;;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;;IAGxB,IAAI,QAAQ,GAAG,KAAK;AAEpB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,MAAM,SAAS,GAAG,KAAK;AACvB,QAAA,KAAK,GAAGA,WAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AAEnC,QAAA,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE;AAC7C,YAAA,QAAQ,EAAE;QACZ;AAEA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC;AAErC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAE7C,IAAA,MAAM,GAAG,GAAyB;QAChC,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;KACb;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAa;IAEjD,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,GAAG,CAAC;QACjB;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,GAAG,CAAC;YACjB;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,QAAQ,GAAG,IAAI;AACf,QAAA,IAAI;AACF,YAAA,QAAQ,GAAG,GAAG,CAAC;QACjB;gBAAU;YACR,QAAQ,GAAG,KAAK;QAClB;AACA,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,GAAG,CAAC;IAEb,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
@@ -34,7 +34,7 @@ const deeplyEquals = (a, b) => {
34
34
  return false;
35
35
  }
36
36
  for (const key of keysA) {
37
- if (!Object.prototype.hasOwnProperty.call(b, key)) {
37
+ if (!Object.hasOwn(b, key)) {
38
38
  return false;
39
39
  }
40
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\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;;;;"}
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.hasOwn(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;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;AAC1B,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;;;;"}
@@ -55,9 +55,7 @@ const derive = (store, deriveFn, dependencyFn) => {
55
55
  listeners.forEach((listener) => listener(get()));
56
56
  };
57
57
  const mount = () => {
58
- if (!unsubscribe) {
59
- unsubscribe = store.subscribe(listener);
60
- }
58
+ unsubscribe ??= store.subscribe(listener);
61
59
  return unmount;
62
60
  };
63
61
  const unmount = () => {
@@ -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 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;;;;"}
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 unsubscribe ??= store.subscribe(listener);\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;AACjB,QAAA,WAAW,KAAK,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;AACzC,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;;;;"}
@@ -30,19 +30,9 @@ export type Effect = {
30
30
  /** Stop listening to changes in the original store. */
31
31
  unmount: () => void;
32
32
  };
33
- export type StoreEventContext<TState> = {
34
- /** The current state of the store. */
35
- state: TState;
36
- /** The initial state of the store. */
37
- initialState: TState;
38
- /** Sets the state of the store. */
39
- set: SetState<TState>;
40
- /** Resets the state of the store to its initial value. */
41
- reset: () => TState;
42
- };
43
- export type StoreEventListener<TState> = (context: StoreEventContext<TState>) => void;
33
+ export type StoreEventListener<TState> = (context: StateContext<TState>) => void;
44
34
  export type StateModifier<TState> = Partial<TState> | ((state: TState) => Partial<TState>);
45
- export type ActionContext<TState> = {
35
+ export type StateContext<TState> = Readonly<{
46
36
  /** Returns the current state of the store. */
47
37
  get: () => TState;
48
38
  /** Sets the state of the store. */
@@ -51,8 +41,8 @@ export type ActionContext<TState> = {
51
41
  reset: () => TState;
52
42
  /** The initial state of the store. */
53
43
  initialState: TState;
54
- };
55
- export type StoreActions<TState, TActions> = (context: ActionContext<TState>) => TActions;
44
+ }>;
45
+ export type StoreActions<TState, TActions> = (context: StateContext<TState>) => TActions;
56
46
  export type BaseConfig<TState, TActions> = {
57
47
  /** The initial state of the store. */
58
48
  initialState: TState;
@@ -72,15 +62,15 @@ export type StorageAPI = {
72
62
  getItem: (key: string) => MaybePromise<string | null>;
73
63
  setItem: (key: string, value: string) => MaybePromise<void>;
74
64
  };
75
- export type Serializer<T = any> = {
76
- stringify: (value: T) => string;
77
- parse: (value: string) => T;
65
+ export type Serializer = {
66
+ stringify: (value: any) => string;
67
+ parse: (value: string) => any;
78
68
  };
79
69
  export type NoPersistConfig = {
80
70
  /** Whether to persist the state of the store. */
81
71
  persist?: false;
82
72
  };
83
- export type PersistConfig<TState> = {
73
+ export type PersistConfig = {
84
74
  /** Whether to persist the state of the store. */
85
75
  persist: true;
86
76
  /** The unique key used to identify this store in storage. Required if `persist` is true. */
@@ -88,7 +78,7 @@ export type PersistConfig<TState> = {
88
78
  /** The storage to use for persisting the state. Defaults to local storage if available. */
89
79
  storage?: StorageAPI;
90
80
  /** The serializer to use for storing the state. Defaults to JSON. */
91
- serializer?: Serializer<TState>;
81
+ serializer?: Serializer;
92
82
  /**
93
83
  * When to initially synchronize the store's state with the storage. Defaults to "load".
94
84
  *
@@ -98,4 +88,8 @@ export type PersistConfig<TState> = {
98
88
  /** Invoked when a storage error occurs. */
99
89
  onStorageError?: (error: unknown) => void;
100
90
  };
101
- export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
91
+ export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig | NoPersistConfig);
92
+ /** @deprecated Use `StateContext` instead. */
93
+ export type StoreEventContext<TState> = StateContext<TState>;
94
+ /** @deprecated Use `StateContext` instead. */
95
+ export type ActionContext<TState> = StateContext<TState>;
@@ -23,9 +23,7 @@ const createEffect = (store, effectFn, dependencyFn) => {
23
23
  }
24
24
  };
25
25
  const mount = () => {
26
- if (!unsubscribe) {
27
- unsubscribe = store.subscribe(effect);
28
- }
26
+ unsubscribe ??= store.subscribe(effect);
29
27
  return unmount;
30
28
  };
31
29
  const unmount = () => {
@@ -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 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;;;;"}
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 unsubscribe ??= store.subscribe(effect);\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;AACjB,QAAA,WAAW,KAAK,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;AACvC,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;;;;"}
@@ -13,55 +13,54 @@ import { merge } from './merge.js';
13
13
  const createStore = (config) => {
14
14
  const { initialState, onLoad, onAttach, onDetach, onChange } = config;
15
15
  let state = initialState;
16
+ // Set while onChange runs, so its synchronous sets join the current dispatch
17
+ let isSilent = false;
16
18
  const get = () => state;
17
- const setSilently = (stateModifier) => {
18
- state = merge(state, stateModifier);
19
- return state;
20
- };
21
19
  const set = (stateModifier) => {
22
20
  const prevState = state;
23
- setSilently(stateModifier);
24
- if (!Object.is(state, prevState)) {
21
+ state = merge(state, stateModifier);
22
+ if (!Object.is(state, prevState) && !isSilent) {
25
23
  dispatch();
26
24
  }
27
25
  return state;
28
26
  };
29
- const resetSilently = () => setSilently(initialState);
30
27
  const reset = () => set(initialState);
31
28
  const listeners = new Set();
32
- const actions = config.actions?.({
33
- set,
29
+ const ctx = {
34
30
  get,
31
+ set,
35
32
  reset,
36
33
  initialState,
37
- });
34
+ };
35
+ const actions = config.actions?.(ctx);
38
36
  const storageAdapter = createStorageAdapter(config, get, set);
39
37
  const subscribe = (listener) => {
40
38
  if (listeners.size === 0) {
41
39
  storageAdapter?.onAttach();
42
- onAttach?.({ state, set, reset, initialState });
40
+ onAttach?.(ctx);
43
41
  }
44
42
  listener(state);
45
43
  listeners.add(listener);
46
44
  return () => {
47
45
  if (listeners.delete(listener) && listeners.size === 0) {
48
46
  storageAdapter?.onDetach();
49
- onDetach?.({ state, set, reset, initialState });
47
+ onDetach?.(ctx);
50
48
  }
51
49
  };
52
50
  };
53
51
  const dispatch = () => {
54
- onChange?.({
55
- state,
56
- set: setSilently,
57
- reset: resetSilently,
58
- initialState,
59
- });
52
+ isSilent = true;
53
+ try {
54
+ onChange?.(ctx);
55
+ }
56
+ finally {
57
+ isSilent = false;
58
+ }
60
59
  storageAdapter?.onChange(state);
61
60
  listeners.forEach((listener) => listener(state));
62
61
  };
63
62
  storageAdapter?.onLoad();
64
- onLoad?.({ state, set, reset, initialState });
63
+ onLoad?.(ctx);
65
64
  return {
66
65
  get,
67
66
  subscribe,
@@ -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 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;;;;"}
1
+ {"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport {\n Listener,\n StateContext,\n StateModifier,\n Store,\n StoreConfig,\n} 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 // Set while onChange runs, so its synchronous sets join the current dispatch\n let isSilent = false;\n\n const get = () => state;\n\n const set = (stateModifier: StateModifier<TState>) => {\n const prevState = state;\n state = merge(state, stateModifier);\n\n if (!Object.is(state, prevState) && !isSilent) {\n dispatch();\n }\n\n return state;\n };\n\n const reset = () => set(initialState);\n\n const listeners = new Set<Listener<TState>>();\n\n const ctx: StateContext<TState> = {\n get,\n set,\n reset,\n initialState,\n };\n\n const actions = config.actions?.(ctx) as TActions;\n\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.(ctx);\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?.(ctx);\n }\n };\n };\n\n const dispatch = () => {\n isSilent = true;\n try {\n onChange?.(ctx);\n } finally {\n isSilent = false;\n }\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.(ctx);\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":[],"mappings":";;;AAUA;;;;;;;;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;;IAGxB,IAAI,QAAQ,GAAG,KAAK;AAEpB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,MAAM,SAAS,GAAG,KAAK;AACvB,QAAA,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AAEnC,QAAA,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE;AAC7C,YAAA,QAAQ,EAAE;QACZ;AAEA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC;AAErC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAE7C,IAAA,MAAM,GAAG,GAAyB;QAChC,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;KACb;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAa;IAEjD,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,GAAG,CAAC;QACjB;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,GAAG,CAAC;YACjB;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;QACpB,QAAQ,GAAG,IAAI;AACf,QAAA,IAAI;AACF,YAAA,QAAQ,GAAG,GAAG,CAAC;QACjB;gBAAU;YACR,QAAQ,GAAG,KAAK;QAClB;AACA,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,GAAG,CAAC;IAEb,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
@@ -32,7 +32,7 @@ const deeplyEquals = (a, b) => {
32
32
  return false;
33
33
  }
34
34
  for (const key of keysA) {
35
- if (!Object.prototype.hasOwnProperty.call(b, key)) {
35
+ if (!Object.hasOwn(b, key)) {
36
36
  return false;
37
37
  }
38
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\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;;;;"}
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.hasOwn(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;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;AAC1B,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;;;;"}
@@ -53,9 +53,7 @@ const derive = (store, deriveFn, dependencyFn) => {
53
53
  listeners.forEach((listener) => listener(get()));
54
54
  };
55
55
  const mount = () => {
56
- if (!unsubscribe) {
57
- unsubscribe = store.subscribe(listener);
58
- }
56
+ unsubscribe ??= store.subscribe(listener);
59
57
  return unmount;
60
58
  };
61
59
  const unmount = () => {
@@ -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 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;;;;"}
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 unsubscribe ??= store.subscribe(listener);\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;AACjB,QAAA,WAAW,KAAK,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;AACzC,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;;;;"}
@@ -30,19 +30,9 @@ export type Effect = {
30
30
  /** Stop listening to changes in the original store. */
31
31
  unmount: () => void;
32
32
  };
33
- export type StoreEventContext<TState> = {
34
- /** The current state of the store. */
35
- state: TState;
36
- /** The initial state of the store. */
37
- initialState: TState;
38
- /** Sets the state of the store. */
39
- set: SetState<TState>;
40
- /** Resets the state of the store to its initial value. */
41
- reset: () => TState;
42
- };
43
- export type StoreEventListener<TState> = (context: StoreEventContext<TState>) => void;
33
+ export type StoreEventListener<TState> = (context: StateContext<TState>) => void;
44
34
  export type StateModifier<TState> = Partial<TState> | ((state: TState) => Partial<TState>);
45
- export type ActionContext<TState> = {
35
+ export type StateContext<TState> = Readonly<{
46
36
  /** Returns the current state of the store. */
47
37
  get: () => TState;
48
38
  /** Sets the state of the store. */
@@ -51,8 +41,8 @@ export type ActionContext<TState> = {
51
41
  reset: () => TState;
52
42
  /** The initial state of the store. */
53
43
  initialState: TState;
54
- };
55
- export type StoreActions<TState, TActions> = (context: ActionContext<TState>) => TActions;
44
+ }>;
45
+ export type StoreActions<TState, TActions> = (context: StateContext<TState>) => TActions;
56
46
  export type BaseConfig<TState, TActions> = {
57
47
  /** The initial state of the store. */
58
48
  initialState: TState;
@@ -72,15 +62,15 @@ export type StorageAPI = {
72
62
  getItem: (key: string) => MaybePromise<string | null>;
73
63
  setItem: (key: string, value: string) => MaybePromise<void>;
74
64
  };
75
- export type Serializer<T = any> = {
76
- stringify: (value: T) => string;
77
- parse: (value: string) => T;
65
+ export type Serializer = {
66
+ stringify: (value: any) => string;
67
+ parse: (value: string) => any;
78
68
  };
79
69
  export type NoPersistConfig = {
80
70
  /** Whether to persist the state of the store. */
81
71
  persist?: false;
82
72
  };
83
- export type PersistConfig<TState> = {
73
+ export type PersistConfig = {
84
74
  /** Whether to persist the state of the store. */
85
75
  persist: true;
86
76
  /** The unique key used to identify this store in storage. Required if `persist` is true. */
@@ -88,7 +78,7 @@ export type PersistConfig<TState> = {
88
78
  /** The storage to use for persisting the state. Defaults to local storage if available. */
89
79
  storage?: StorageAPI;
90
80
  /** The serializer to use for storing the state. Defaults to JSON. */
91
- serializer?: Serializer<TState>;
81
+ serializer?: Serializer;
92
82
  /**
93
83
  * When to initially synchronize the store's state with the storage. Defaults to "load".
94
84
  *
@@ -98,4 +88,8 @@ export type PersistConfig<TState> = {
98
88
  /** Invoked when a storage error occurs. */
99
89
  onStorageError?: (error: unknown) => void;
100
90
  };
101
- export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
91
+ export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig | NoPersistConfig);
92
+ /** @deprecated Use `StateContext` instead. */
93
+ export type StoreEventContext<TState> = StateContext<TState>;
94
+ /** @deprecated Use `StateContext` instead. */
95
+ export type ActionContext<TState> = StateContext<TState>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dharma-core",
3
- "version": "0.14.0",
3
+ "version": "0.16.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.",