dharma-core 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/createStorageAdapter.d.ts +1 -1
- package/dist/cjs/internal/createStorageAdapter.js +51 -33
- package/dist/cjs/internal/createStorageAdapter.js.map +1 -1
- package/dist/cjs/lib/StorageUnavailableError.d.ts +7 -0
- package/dist/cjs/lib/StorageUnavailableError.js +15 -0
- package/dist/cjs/lib/StorageUnavailableError.js.map +1 -0
- package/dist/cjs/lib/createEffect.js +1 -0
- package/dist/cjs/lib/createEffect.js.map +1 -1
- package/dist/cjs/lib/createStore.js +7 -9
- package/dist/cjs/lib/createStore.js.map +1 -1
- package/dist/cjs/lib/deeplyEquals.js +2 -3
- package/dist/cjs/lib/deeplyEquals.js.map +1 -1
- package/dist/cjs/lib/derive.js +14 -13
- package/dist/cjs/lib/derive.js.map +1 -1
- package/dist/cjs/lib/merge.d.ts +1 -0
- package/dist/cjs/lib/merge.js +9 -9
- package/dist/cjs/lib/merge.js.map +1 -1
- package/dist/cjs/types/types.d.ts +9 -2
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/createStorageAdapter.d.ts +1 -1
- package/dist/esm/internal/createStorageAdapter.js +51 -33
- package/dist/esm/internal/createStorageAdapter.js.map +1 -1
- package/dist/esm/lib/StorageUnavailableError.d.ts +7 -0
- package/dist/esm/lib/StorageUnavailableError.js +13 -0
- package/dist/esm/lib/StorageUnavailableError.js.map +1 -0
- package/dist/esm/lib/createEffect.js +1 -0
- package/dist/esm/lib/createEffect.js.map +1 -1
- package/dist/esm/lib/createStore.js +7 -9
- package/dist/esm/lib/createStore.js.map +1 -1
- package/dist/esm/lib/deeplyEquals.js +2 -3
- package/dist/esm/lib/deeplyEquals.js.map +1 -1
- package/dist/esm/lib/derive.js +14 -13
- package/dist/esm/lib/derive.js.map +1 -1
- package/dist/esm/lib/merge.d.ts +1 -0
- package/dist/esm/lib/merge.js +9 -9
- package/dist/esm/lib/merge.js.map +1 -1
- package/dist/esm/types/types.d.ts +9 -2
- package/package.json +1 -1
- package/dist/cjs/internal/warn.d.ts +0 -1
- package/dist/cjs/internal/warn.js +0 -6
- package/dist/cjs/internal/warn.js.map +0 -1
- package/dist/esm/internal/warn.d.ts +0 -1
- package/dist/esm/internal/warn.js +0 -4
- package/dist/esm/internal/warn.js.map +0 -1
package/dist/cjs/index.d.ts
CHANGED
package/dist/cjs/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var createEffect = require('./lib/createEffect.js');
|
|
|
4
4
|
var createStore = require('./lib/createStore.js');
|
|
5
5
|
var derive = require('./lib/derive.js');
|
|
6
6
|
var merge = require('./lib/merge.js');
|
|
7
|
+
var StorageUnavailableError = require('./lib/StorageUnavailableError.js');
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
|
|
@@ -11,4 +12,5 @@ exports.createEffect = createEffect.createEffect;
|
|
|
11
12
|
exports.createStore = createStore.createStore;
|
|
12
13
|
exports.derive = derive.derive;
|
|
13
14
|
exports.merge = merge.merge;
|
|
15
|
+
exports.StorageUnavailableError = StorageUnavailableError.StorageUnavailableError;
|
|
14
16
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SetState, StoreConfig } from "../types/types";
|
|
2
2
|
export declare const createStorageAdapter: <TState, TActions>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
|
|
3
|
-
onLoad: () =>
|
|
3
|
+
onLoad: () => void;
|
|
4
4
|
onChange: (newState: TState) => Promise<void>;
|
|
5
5
|
onAttach: () => void;
|
|
6
6
|
onDetach: () => void;
|
|
@@ -1,73 +1,91 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var StorageUnavailableError = require('../lib/StorageUnavailableError.js');
|
|
4
4
|
|
|
5
|
+
const isThenable = (value) => typeof value?.then === "function";
|
|
6
|
+
const getDefaultStorage = () => {
|
|
7
|
+
try {
|
|
8
|
+
// Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled
|
|
9
|
+
return localStorage;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
5
15
|
const createStorageAdapter = (config, get, set) => {
|
|
6
16
|
if (!config.persist) {
|
|
7
17
|
return null;
|
|
8
18
|
}
|
|
9
19
|
const IS_BROWSER = typeof window !== "undefined";
|
|
10
|
-
|
|
20
|
+
// React Native defines window without the event listener API
|
|
21
|
+
const CAN_LISTEN = IS_BROWSER && typeof window.addEventListener === "function";
|
|
22
|
+
const storage = config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);
|
|
23
|
+
const { key, serializer = JSON, syncWithStorageOn = "load", onStorageError, } = config;
|
|
11
24
|
if (!storage) {
|
|
12
|
-
|
|
25
|
+
onStorageError?.(new StorageUnavailableError.StorageUnavailableError(key));
|
|
13
26
|
return null;
|
|
14
27
|
}
|
|
15
|
-
const { key, serializer = JSON, initialState } = config;
|
|
16
|
-
const initKey = `init_${key}`;
|
|
17
|
-
const onLoad = async () => {
|
|
18
|
-
try {
|
|
19
|
-
let initialStateSnapshot = storage.getItem(initKey);
|
|
20
|
-
const initialStateString = serializer.stringify(initialState);
|
|
21
|
-
if (initialStateSnapshot instanceof Promise) {
|
|
22
|
-
initialStateSnapshot = await initialStateSnapshot;
|
|
23
|
-
}
|
|
24
|
-
if (initialStateSnapshot !== initialStateString) {
|
|
25
|
-
storage.setItem(initKey, initialStateString);
|
|
26
|
-
storage.removeItem(key);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
warn.warn("Failed to initialize snapshots. If this happened during SSR, you can safely ignore this warning.");
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
28
|
const syncWithSnapshot = async () => {
|
|
29
|
+
let nextState;
|
|
34
30
|
try {
|
|
31
|
+
const prevState = get();
|
|
35
32
|
let currentSnapshot = storage.getItem(key);
|
|
36
|
-
if (currentSnapshot
|
|
33
|
+
if (isThenable(currentSnapshot)) {
|
|
37
34
|
currentSnapshot = await currentSnapshot;
|
|
35
|
+
// The state changed while reading, so the snapshot is outdated
|
|
36
|
+
if (!Object.is(get(), prevState)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
38
39
|
}
|
|
39
|
-
if (currentSnapshot
|
|
40
|
-
|
|
40
|
+
if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {
|
|
41
|
+
return;
|
|
41
42
|
}
|
|
43
|
+
nextState = serializer.parse(currentSnapshot);
|
|
42
44
|
}
|
|
43
|
-
catch {
|
|
44
|
-
|
|
45
|
+
catch (error) {
|
|
46
|
+
onStorageError?.(error);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
set(nextState);
|
|
50
|
+
};
|
|
51
|
+
const onLoad = () => {
|
|
52
|
+
if (syncWithStorageOn === "load") {
|
|
53
|
+
syncWithSnapshot();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const onStorage = (event) => {
|
|
57
|
+
if (event.key === key && event.storageArea === storage) {
|
|
58
|
+
syncWithSnapshot();
|
|
45
59
|
}
|
|
46
60
|
};
|
|
47
61
|
const onAttach = () => {
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
if (syncWithStorageOn === "attach") {
|
|
63
|
+
syncWithSnapshot();
|
|
64
|
+
}
|
|
65
|
+
if (CAN_LISTEN) {
|
|
50
66
|
window.addEventListener("focus", syncWithSnapshot);
|
|
67
|
+
window.addEventListener("storage", onStorage);
|
|
51
68
|
}
|
|
52
69
|
};
|
|
53
70
|
const onChange = async (newState) => {
|
|
54
71
|
try {
|
|
55
72
|
let currentSnapshot = storage.getItem(key);
|
|
56
73
|
const newSnapshot = serializer.stringify(newState);
|
|
57
|
-
if (currentSnapshot
|
|
74
|
+
if (isThenable(currentSnapshot)) {
|
|
58
75
|
currentSnapshot = await currentSnapshot;
|
|
59
76
|
}
|
|
60
77
|
if (newSnapshot !== currentSnapshot) {
|
|
61
|
-
storage.setItem(key, newSnapshot);
|
|
78
|
+
await storage.setItem(key, newSnapshot);
|
|
62
79
|
}
|
|
63
80
|
}
|
|
64
|
-
catch {
|
|
65
|
-
|
|
81
|
+
catch (error) {
|
|
82
|
+
onStorageError?.(error);
|
|
66
83
|
}
|
|
67
84
|
};
|
|
68
85
|
const onDetach = () => {
|
|
69
|
-
if (
|
|
86
|
+
if (CAN_LISTEN) {
|
|
70
87
|
window.removeEventListener("focus", syncWithSnapshot);
|
|
88
|
+
window.removeEventListener("storage", onStorage);
|
|
71
89
|
}
|
|
72
90
|
};
|
|
73
91
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { SetState, StorageAPI, StoreConfig } from \"../types/types\";\
|
|
1
|
+
{"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { StorageUnavailableError } from \"../lib/StorageUnavailableError\";\nimport { SetState, StorageAPI, StoreConfig } from \"../types/types\";\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof (value as PromiseLike<unknown> | null)?.then === \"function\";\n\nconst getDefaultStorage = (): StorageAPI | undefined => {\n try {\n // Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled\n return localStorage;\n } catch {\n return undefined;\n }\n};\n\nexport const createStorageAdapter = <TState, TActions>(\n config: StoreConfig<TState, TActions>,\n get: () => TState,\n set: SetState<TState>,\n) => {\n if (!config.persist) {\n return null;\n }\n\n const IS_BROWSER = typeof window !== \"undefined\";\n // React Native defines window without the event listener API\n const CAN_LISTEN =\n IS_BROWSER && typeof window.addEventListener === \"function\";\n\n const storage =\n config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);\n\n const {\n key,\n serializer = JSON,\n syncWithStorageOn = \"load\",\n onStorageError,\n } = config;\n\n if (!storage) {\n onStorageError?.(new StorageUnavailableError(key));\n return null;\n }\n\n const syncWithSnapshot = async () => {\n let nextState: TState;\n\n try {\n const prevState = get();\n let currentSnapshot = storage.getItem(key);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n\n // The state changed while reading, so the snapshot is outdated\n if (!Object.is(get(), prevState)) {\n return;\n }\n }\n\n if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {\n return;\n }\n\n nextState = serializer.parse(currentSnapshot);\n } catch (error) {\n onStorageError?.(error);\n return;\n }\n\n set(nextState);\n };\n\n const onLoad = () => {\n if (syncWithStorageOn === \"load\") {\n syncWithSnapshot();\n }\n };\n\n const onStorage = (event: StorageEvent) => {\n if (event.key === key && event.storageArea === storage) {\n syncWithSnapshot();\n }\n };\n\n const onAttach = () => {\n if (syncWithStorageOn === \"attach\") {\n syncWithSnapshot();\n }\n\n if (CAN_LISTEN) {\n window.addEventListener(\"focus\", syncWithSnapshot);\n window.addEventListener(\"storage\", onStorage);\n }\n };\n\n const onChange = async (newState: TState) => {\n try {\n let currentSnapshot = storage.getItem(key);\n const newSnapshot = serializer.stringify(newState);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (newSnapshot !== currentSnapshot) {\n await storage.setItem(key, newSnapshot);\n }\n } catch (error) {\n onStorageError?.(error);\n }\n };\n\n const onDetach = () => {\n if (CAN_LISTEN) {\n window.removeEventListener(\"focus\", syncWithSnapshot);\n window.removeEventListener(\"storage\", onStorage);\n }\n };\n\n return {\n onLoad,\n onChange,\n onAttach,\n onDetach,\n };\n};\n"],"names":["StorageUnavailableError"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reported to `onStorageError` when a persisted store has no storage to use.
|
|
3
|
+
* This is expected during SSR, and can otherwise mean that local storage is inaccessible.
|
|
4
|
+
*/
|
|
5
|
+
export declare class StorageUnavailableError extends Error {
|
|
6
|
+
constructor(key: string);
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reported to `onStorageError` when a persisted store has no storage to use.
|
|
5
|
+
* This is expected during SSR, and can otherwise mean that local storage is inaccessible.
|
|
6
|
+
*/
|
|
7
|
+
class StorageUnavailableError extends Error {
|
|
8
|
+
constructor(key) {
|
|
9
|
+
super(`[dharma-core] No storage provider was found for "${key}". If this happened during SSR, it can be safely ignored.`);
|
|
10
|
+
this.name = "StorageUnavailableError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.StorageUnavailableError = StorageUnavailableError;
|
|
15
|
+
//# sourceMappingURL=StorageUnavailableError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageUnavailableError.js","sources":["../../../src/lib/StorageUnavailableError.ts"],"sourcesContent":["/**\n * Reported to `onStorageError` when a persisted store has no storage to use.\n * This is expected during SSR, and can otherwise mean that local storage is inaccessible.\n */\nexport class StorageUnavailableError extends Error {\n constructor(key: string) {\n super(\n `[dharma-core] No storage provider was found for \"${key}\". If this happened during SSR, it can be safely ignored.`,\n );\n this.name = \"StorageUnavailableError\";\n }\n}\n"],"names":[],"mappings":";;AAAA;;;AAGG;AACG,MAAO,uBAAwB,SAAQ,KAAK,CAAA;AAChD,IAAA,WAAA,CAAY,GAAW,EAAA;AACrB,QAAA,KAAK,CACH,CAAA,iDAAA,EAAoD,GAAG,CAAA,yDAAA,CAA2D,CACnH;AACD,QAAA,IAAI,CAAC,IAAI,GAAG,yBAAyB;IACvC;AACD;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":["deeplyEquals"],"mappings":";;;;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAACA,yBAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n prev = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":["deeplyEquals"],"mappings":";;;;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAACA,yBAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;QACvB,IAAI,GAAG,SAAS;AAClB,IAAA,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;KACR;AACH;;;;"}
|
|
@@ -21,16 +21,15 @@ const createStore = (config) => {
|
|
|
21
21
|
return state;
|
|
22
22
|
};
|
|
23
23
|
const set = (stateModifier) => {
|
|
24
|
+
const prevState = state;
|
|
24
25
|
setSilently(stateModifier);
|
|
25
|
-
|
|
26
|
+
if (!Object.is(state, prevState)) {
|
|
27
|
+
dispatch();
|
|
28
|
+
}
|
|
26
29
|
return state;
|
|
27
30
|
};
|
|
28
31
|
const resetSilently = () => setSilently(initialState);
|
|
29
|
-
const reset = () =>
|
|
30
|
-
resetSilently();
|
|
31
|
-
dispatch();
|
|
32
|
-
return state;
|
|
33
|
-
};
|
|
32
|
+
const reset = () => set(initialState);
|
|
34
33
|
const listeners = new Set();
|
|
35
34
|
const actions = config.actions?.({
|
|
36
35
|
set,
|
|
@@ -47,21 +46,20 @@ const createStore = (config) => {
|
|
|
47
46
|
listener(state);
|
|
48
47
|
listeners.add(listener);
|
|
49
48
|
return () => {
|
|
50
|
-
listeners.delete(listener)
|
|
51
|
-
if (listeners.size === 0) {
|
|
49
|
+
if (listeners.delete(listener) && listeners.size === 0) {
|
|
52
50
|
storageAdapter?.onDetach();
|
|
53
51
|
onDetach?.({ state, set, reset, initialState });
|
|
54
52
|
}
|
|
55
53
|
};
|
|
56
54
|
};
|
|
57
55
|
const dispatch = () => {
|
|
58
|
-
storageAdapter?.onChange(state);
|
|
59
56
|
onChange?.({
|
|
60
57
|
state,
|
|
61
58
|
set: setSilently,
|
|
62
59
|
reset: resetSilently,
|
|
63
60
|
initialState,
|
|
64
61
|
});
|
|
62
|
+
storageAdapter?.onChange(state);
|
|
65
63
|
listeners.forEach((listener) => listener(state));
|
|
66
64
|
};
|
|
67
65
|
storageAdapter?.onLoad();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n setSilently(stateModifier);\n dispatch();\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () =>
|
|
1
|
+
{"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n const prevState = state;\n setSilently(stateModifier);\n\n if (!Object.is(state, prevState)) {\n dispatch();\n }\n\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () => set(initialState);\n\n const listeners = new Set<Listener<TState>>();\n const actions = config.actions?.({\n set,\n get,\n reset,\n initialState,\n }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.({ state, set, reset, initialState });\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n if (listeners.delete(listener) && listeners.size === 0) {\n storageAdapter?.onDetach();\n onDetach?.({ state, set, reset, initialState });\n }\n };\n };\n\n const dispatch = () => {\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n initialState,\n });\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.({ state, set, reset, initialState });\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":["merge","createStorageAdapter"],"mappings":";;;;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM;IAErE,IAAI,KAAK,GAAG,YAAY;AAExB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,WAAW,GAAG,CAAC,aAAoC,KAAI;AAC3D,QAAA,KAAK,GAAGA,WAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACnC,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,MAAM,SAAS,GAAG,KAAK;QACvB,WAAW,CAAC,aAAa,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE;AAChC,YAAA,QAAQ,EAAE;QACZ;AAEA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC;IAErD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC;AAErC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAC7C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;AACb,KAAA,CAAa;IACd,MAAM,cAAc,GAAGC,yCAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;AAE7D,IAAA,MAAM,SAAS,GAAG,CAAC,QAA0B,KAAI;AAC/C,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;YACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,YAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD;QAEA,QAAQ,CAAC,KAAK,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;gBACtD,cAAc,EAAE,QAAQ,EAAE;AAC1B,gBAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YACjD;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,QAAQ,GAAG;YACT,KAAK;AACL,YAAA,GAAG,EAAE,WAAW;AAChB,YAAA,KAAK,EAAE,aAAa;YACpB,YAAY;AACb,SAAA,CAAC;AACF,QAAA,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;AAC/B,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,cAAc,EAAE,MAAM,EAAE;AACxB,IAAA,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE7C,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
|
|
@@ -30,12 +30,11 @@ const deeplyEquals = (a, b) => {
|
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
32
32
|
const keysA = Object.keys(a);
|
|
33
|
-
|
|
34
|
-
if (keysA.length !== keysB.length) {
|
|
33
|
+
if (keysA.length !== Object.keys(b).length) {
|
|
35
34
|
return false;
|
|
36
35
|
}
|
|
37
36
|
for (const key of keysA) {
|
|
38
|
-
if (!
|
|
37
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
39
38
|
return false;
|
|
40
39
|
}
|
|
41
40
|
if (!deeplyEquals(a[key], b[key])) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deeplyEquals.js","sources":["../../../src/lib/deeplyEquals.ts"],"sourcesContent":["export const deeplyEquals = (a: unknown, b: unknown): boolean => {\n if (a === b) {\n return true;\n }\n\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n\n const protoA = Object.getPrototypeOf(a);\n const protoB = Object.getPrototypeOf(b);\n\n if (protoA !== protoB) {\n return false;\n }\n\n if (protoA === Array.prototype) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n for (let i = 0; i < arrA.length; i++) {\n if (!deeplyEquals(arrA[i], arrB[i])) return false;\n }\n return true;\n }\n\n if (protoA !== Object.prototype) {\n return false;\n }\n\n const keysA = Object.keys(a);\n
|
|
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;;;;"}
|
package/dist/cjs/lib/derive.js
CHANGED
|
@@ -18,22 +18,24 @@ var deeplyEquals = require('./deeplyEquals.js');
|
|
|
18
18
|
*/
|
|
19
19
|
const derive = (store, deriveFn, dependencyFn) => {
|
|
20
20
|
let memo;
|
|
21
|
-
let
|
|
22
|
-
let
|
|
21
|
+
let hasMemo = false;
|
|
22
|
+
let prevState;
|
|
23
|
+
let prevDeps;
|
|
23
24
|
let unsubscribe;
|
|
24
25
|
const listeners = new Set();
|
|
25
|
-
const compute = () => {
|
|
26
|
-
memo = deriveFn(store.get());
|
|
27
|
-
isStale = false;
|
|
28
|
-
return memo;
|
|
29
|
-
};
|
|
30
26
|
const get = () => {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const state = store.get();
|
|
28
|
+
if (hasMemo && Object.is(state, prevState)) {
|
|
29
|
+
return memo;
|
|
30
|
+
}
|
|
31
|
+
prevState = state;
|
|
32
|
+
const deps = dependencyFn?.(state);
|
|
33
|
+
if (hasMemo && dependencyFn && deeplyEquals.deeplyEquals(prevDeps, deps)) {
|
|
34
|
+
return memo;
|
|
36
35
|
}
|
|
36
|
+
prevDeps = deps;
|
|
37
|
+
memo = deriveFn(state);
|
|
38
|
+
hasMemo = true;
|
|
37
39
|
return memo;
|
|
38
40
|
};
|
|
39
41
|
const subscribe = (listener) => {
|
|
@@ -50,7 +52,6 @@ const derive = (store, deriveFn, dependencyFn) => {
|
|
|
50
52
|
};
|
|
51
53
|
};
|
|
52
54
|
const listener = () => {
|
|
53
|
-
isStale = true;
|
|
54
55
|
listeners.forEach((listener) => listener(get()));
|
|
55
56
|
};
|
|
56
57
|
const mount = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { DerivedStore, Listener, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates a derived store from an existing store.\n *\n * A derived store computes its state based on the original store's state using a derive function.\n * The derived store's value is memoized and only recomputed when requested.\n *\n * @param store - The original store to derive from\n * @param deriveFn - A function that computes the derived state from the original state\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change\n *\n * @returns A new store containing the derived state\n *\n * @see {@link https://dharma.fransek.dev/core/derive/}\n */\nexport const derive = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n deriveFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): DerivedStore<TDerived> => {\n let memo: TDerived | undefined;\n let
|
|
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;;;;"}
|
package/dist/cjs/lib/merge.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { StateModifier } from "../types/types";
|
|
|
5
5
|
* @param {T} currentState - The current state object.
|
|
6
6
|
* @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
|
|
7
7
|
* @returns {T} The new state object, which is a combination of the current state and the state modifier.
|
|
8
|
+
* Returns `currentState` itself if the state modifier resolves to it.
|
|
8
9
|
*
|
|
9
10
|
* @see {@link https://dharma.fransek.dev/core/merge/}
|
|
10
11
|
*/
|
package/dist/cjs/lib/merge.js
CHANGED
|
@@ -8,21 +8,21 @@ const isObject = (value) => !!value && typeof value === "object" && !Array.isArr
|
|
|
8
8
|
* @param {T} currentState - The current state object.
|
|
9
9
|
* @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
|
|
10
10
|
* @returns {T} The new state object, which is a combination of the current state and the state modifier.
|
|
11
|
+
* Returns `currentState` itself if the state modifier resolves to it.
|
|
11
12
|
*
|
|
12
13
|
* @see {@link https://dharma.fransek.dev/core/merge/}
|
|
13
14
|
*/
|
|
14
15
|
const merge = (currentState, stateModifier) => {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const modifiedState = typeof stateModifier === "function"
|
|
17
|
+
? stateModifier(currentState)
|
|
18
|
+
: stateModifier;
|
|
19
|
+
if (Object.is(modifiedState, currentState)) {
|
|
20
|
+
return currentState;
|
|
17
21
|
}
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
if (isObject(modifiedState)) {
|
|
21
|
-
return { ...currentState, ...modifiedState };
|
|
22
|
-
}
|
|
23
|
-
return modifiedState;
|
|
22
|
+
if (isObject(modifiedState)) {
|
|
23
|
+
return { ...currentState, ...modifiedState };
|
|
24
24
|
}
|
|
25
|
-
return
|
|
25
|
+
return modifiedState;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
exports.merge = merge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n
|
|
1
|
+
{"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n * Returns `currentState` itself if the state modifier resolves to it.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n const modifiedState =\n typeof stateModifier === \"function\"\n ? stateModifier(currentState)\n : stateModifier;\n\n if (Object.is(modifiedState, currentState)) {\n return currentState;\n }\n\n if (isObject(modifiedState)) {\n return { ...currentState, ...modifiedState };\n }\n\n return modifiedState as T;\n};\n"],"names":[],"mappings":";;AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,KAAc,KAC9B,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAE/D;;;;;;;;;AASG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,MAAM,aAAa,GACjB,OAAO,aAAa,KAAK;AACvB,UAAE,aAAa,CAAC,YAAY;UAC1B,aAAa;IAEnB,IAAI,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE;AAC1C,QAAA,OAAO,YAAY;IACrB;AAEA,IAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,QAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;IAC9C;AAEA,IAAA,OAAO,aAAkB;AAC3B;;;;"}
|
|
@@ -67,11 +67,10 @@ export type BaseConfig<TState, TActions> = {
|
|
|
67
67
|
/** Invoked whenever the state changes. */
|
|
68
68
|
onChange?: StoreEventListener<TState>;
|
|
69
69
|
};
|
|
70
|
-
export type MaybePromise<T> = T |
|
|
70
|
+
export type MaybePromise<T> = T | PromiseLike<T>;
|
|
71
71
|
export type StorageAPI = {
|
|
72
72
|
getItem: (key: string) => MaybePromise<string | null>;
|
|
73
73
|
setItem: (key: string, value: string) => MaybePromise<void>;
|
|
74
|
-
removeItem: (key: string) => MaybePromise<void>;
|
|
75
74
|
};
|
|
76
75
|
export type Serializer<T = any> = {
|
|
77
76
|
stringify: (value: T) => string;
|
|
@@ -90,5 +89,13 @@ export type PersistConfig<TState> = {
|
|
|
90
89
|
storage?: StorageAPI;
|
|
91
90
|
/** The serializer to use for storing the state. Defaults to JSON. */
|
|
92
91
|
serializer?: Serializer<TState>;
|
|
92
|
+
/**
|
|
93
|
+
* When to initially synchronize the store's state with the storage. Defaults to "load".
|
|
94
|
+
*
|
|
95
|
+
* Set to "attach" when using SSR to avoid hydration errors.
|
|
96
|
+
*/
|
|
97
|
+
syncWithStorageOn?: "load" | "attach";
|
|
98
|
+
/** Invoked when a storage error occurs. */
|
|
99
|
+
onStorageError?: (error: unknown) => void;
|
|
93
100
|
};
|
|
94
101
|
export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { createEffect } from './lib/createEffect.js';
|
|
|
2
2
|
export { createStore } from './lib/createStore.js';
|
|
3
3
|
export { derive } from './lib/derive.js';
|
|
4
4
|
export { merge } from './lib/merge.js';
|
|
5
|
+
export { StorageUnavailableError } from './lib/StorageUnavailableError.js';
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SetState, StoreConfig } from "../types/types";
|
|
2
2
|
export declare const createStorageAdapter: <TState, TActions>(config: StoreConfig<TState, TActions>, get: () => TState, set: SetState<TState>) => {
|
|
3
|
-
onLoad: () =>
|
|
3
|
+
onLoad: () => void;
|
|
4
4
|
onChange: (newState: TState) => Promise<void>;
|
|
5
5
|
onAttach: () => void;
|
|
6
6
|
onDetach: () => void;
|
|
@@ -1,71 +1,89 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StorageUnavailableError } from '../lib/StorageUnavailableError.js';
|
|
2
2
|
|
|
3
|
+
const isThenable = (value) => typeof value?.then === "function";
|
|
4
|
+
const getDefaultStorage = () => {
|
|
5
|
+
try {
|
|
6
|
+
// Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled
|
|
7
|
+
return localStorage;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
3
13
|
const createStorageAdapter = (config, get, set) => {
|
|
4
14
|
if (!config.persist) {
|
|
5
15
|
return null;
|
|
6
16
|
}
|
|
7
17
|
const IS_BROWSER = typeof window !== "undefined";
|
|
8
|
-
|
|
18
|
+
// React Native defines window without the event listener API
|
|
19
|
+
const CAN_LISTEN = IS_BROWSER && typeof window.addEventListener === "function";
|
|
20
|
+
const storage = config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);
|
|
21
|
+
const { key, serializer = JSON, syncWithStorageOn = "load", onStorageError, } = config;
|
|
9
22
|
if (!storage) {
|
|
10
|
-
|
|
23
|
+
onStorageError?.(new StorageUnavailableError(key));
|
|
11
24
|
return null;
|
|
12
25
|
}
|
|
13
|
-
const { key, serializer = JSON, initialState } = config;
|
|
14
|
-
const initKey = `init_${key}`;
|
|
15
|
-
const onLoad = async () => {
|
|
16
|
-
try {
|
|
17
|
-
let initialStateSnapshot = storage.getItem(initKey);
|
|
18
|
-
const initialStateString = serializer.stringify(initialState);
|
|
19
|
-
if (initialStateSnapshot instanceof Promise) {
|
|
20
|
-
initialStateSnapshot = await initialStateSnapshot;
|
|
21
|
-
}
|
|
22
|
-
if (initialStateSnapshot !== initialStateString) {
|
|
23
|
-
storage.setItem(initKey, initialStateString);
|
|
24
|
-
storage.removeItem(key);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
warn("Failed to initialize snapshots. If this happened during SSR, you can safely ignore this warning.");
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
26
|
const syncWithSnapshot = async () => {
|
|
27
|
+
let nextState;
|
|
32
28
|
try {
|
|
29
|
+
const prevState = get();
|
|
33
30
|
let currentSnapshot = storage.getItem(key);
|
|
34
|
-
if (currentSnapshot
|
|
31
|
+
if (isThenable(currentSnapshot)) {
|
|
35
32
|
currentSnapshot = await currentSnapshot;
|
|
33
|
+
// The state changed while reading, so the snapshot is outdated
|
|
34
|
+
if (!Object.is(get(), prevState)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
36
37
|
}
|
|
37
|
-
if (currentSnapshot
|
|
38
|
-
|
|
38
|
+
if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {
|
|
39
|
+
return;
|
|
39
40
|
}
|
|
41
|
+
nextState = serializer.parse(currentSnapshot);
|
|
40
42
|
}
|
|
41
|
-
catch {
|
|
42
|
-
|
|
43
|
+
catch (error) {
|
|
44
|
+
onStorageError?.(error);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
set(nextState);
|
|
48
|
+
};
|
|
49
|
+
const onLoad = () => {
|
|
50
|
+
if (syncWithStorageOn === "load") {
|
|
51
|
+
syncWithSnapshot();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const onStorage = (event) => {
|
|
55
|
+
if (event.key === key && event.storageArea === storage) {
|
|
56
|
+
syncWithSnapshot();
|
|
43
57
|
}
|
|
44
58
|
};
|
|
45
59
|
const onAttach = () => {
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
if (syncWithStorageOn === "attach") {
|
|
61
|
+
syncWithSnapshot();
|
|
62
|
+
}
|
|
63
|
+
if (CAN_LISTEN) {
|
|
48
64
|
window.addEventListener("focus", syncWithSnapshot);
|
|
65
|
+
window.addEventListener("storage", onStorage);
|
|
49
66
|
}
|
|
50
67
|
};
|
|
51
68
|
const onChange = async (newState) => {
|
|
52
69
|
try {
|
|
53
70
|
let currentSnapshot = storage.getItem(key);
|
|
54
71
|
const newSnapshot = serializer.stringify(newState);
|
|
55
|
-
if (currentSnapshot
|
|
72
|
+
if (isThenable(currentSnapshot)) {
|
|
56
73
|
currentSnapshot = await currentSnapshot;
|
|
57
74
|
}
|
|
58
75
|
if (newSnapshot !== currentSnapshot) {
|
|
59
|
-
storage.setItem(key, newSnapshot);
|
|
76
|
+
await storage.setItem(key, newSnapshot);
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
|
-
catch {
|
|
63
|
-
|
|
79
|
+
catch (error) {
|
|
80
|
+
onStorageError?.(error);
|
|
64
81
|
}
|
|
65
82
|
};
|
|
66
83
|
const onDetach = () => {
|
|
67
|
-
if (
|
|
84
|
+
if (CAN_LISTEN) {
|
|
68
85
|
window.removeEventListener("focus", syncWithSnapshot);
|
|
86
|
+
window.removeEventListener("storage", onStorage);
|
|
69
87
|
}
|
|
70
88
|
};
|
|
71
89
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { SetState, StorageAPI, StoreConfig } from \"../types/types\";\
|
|
1
|
+
{"version":3,"file":"createStorageAdapter.js","sources":["../../../src/internal/createStorageAdapter.ts"],"sourcesContent":["import { StorageUnavailableError } from \"../lib/StorageUnavailableError\";\nimport { SetState, StorageAPI, StoreConfig } from \"../types/types\";\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof (value as PromiseLike<unknown> | null)?.then === \"function\";\n\nconst getDefaultStorage = (): StorageAPI | undefined => {\n try {\n // Accessing localStorage can throw, e.g. in sandboxed iframes or when cookies are disabled\n return localStorage;\n } catch {\n return undefined;\n }\n};\n\nexport const createStorageAdapter = <TState, TActions>(\n config: StoreConfig<TState, TActions>,\n get: () => TState,\n set: SetState<TState>,\n) => {\n if (!config.persist) {\n return null;\n }\n\n const IS_BROWSER = typeof window !== \"undefined\";\n // React Native defines window without the event listener API\n const CAN_LISTEN =\n IS_BROWSER && typeof window.addEventListener === \"function\";\n\n const storage =\n config.storage ?? (IS_BROWSER ? getDefaultStorage() : undefined);\n\n const {\n key,\n serializer = JSON,\n syncWithStorageOn = \"load\",\n onStorageError,\n } = config;\n\n if (!storage) {\n onStorageError?.(new StorageUnavailableError(key));\n return null;\n }\n\n const syncWithSnapshot = async () => {\n let nextState: TState;\n\n try {\n const prevState = get();\n let currentSnapshot = storage.getItem(key);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n\n // The state changed while reading, so the snapshot is outdated\n if (!Object.is(get(), prevState)) {\n return;\n }\n }\n\n if (!currentSnapshot || currentSnapshot === serializer.stringify(get())) {\n return;\n }\n\n nextState = serializer.parse(currentSnapshot);\n } catch (error) {\n onStorageError?.(error);\n return;\n }\n\n set(nextState);\n };\n\n const onLoad = () => {\n if (syncWithStorageOn === \"load\") {\n syncWithSnapshot();\n }\n };\n\n const onStorage = (event: StorageEvent) => {\n if (event.key === key && event.storageArea === storage) {\n syncWithSnapshot();\n }\n };\n\n const onAttach = () => {\n if (syncWithStorageOn === \"attach\") {\n syncWithSnapshot();\n }\n\n if (CAN_LISTEN) {\n window.addEventListener(\"focus\", syncWithSnapshot);\n window.addEventListener(\"storage\", onStorage);\n }\n };\n\n const onChange = async (newState: TState) => {\n try {\n let currentSnapshot = storage.getItem(key);\n const newSnapshot = serializer.stringify(newState);\n\n if (isThenable(currentSnapshot)) {\n currentSnapshot = await currentSnapshot;\n }\n\n if (newSnapshot !== currentSnapshot) {\n await storage.setItem(key, newSnapshot);\n }\n } catch (error) {\n onStorageError?.(error);\n }\n };\n\n const onDetach = () => {\n if (CAN_LISTEN) {\n window.removeEventListener(\"focus\", syncWithSnapshot);\n window.removeEventListener(\"storage\", onStorage);\n }\n };\n\n return {\n onLoad,\n onChange,\n onAttach,\n onDetach,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reported to `onStorageError` when a persisted store has no storage to use.
|
|
3
|
+
* This is expected during SSR, and can otherwise mean that local storage is inaccessible.
|
|
4
|
+
*/
|
|
5
|
+
export declare class StorageUnavailableError extends Error {
|
|
6
|
+
constructor(key: string);
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reported to `onStorageError` when a persisted store has no storage to use.
|
|
3
|
+
* This is expected during SSR, and can otherwise mean that local storage is inaccessible.
|
|
4
|
+
*/
|
|
5
|
+
class StorageUnavailableError extends Error {
|
|
6
|
+
constructor(key) {
|
|
7
|
+
super(`[dharma-core] No storage provider was found for "${key}". If this happened during SSR, it can be safely ignored.`);
|
|
8
|
+
this.name = "StorageUnavailableError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { StorageUnavailableError };
|
|
13
|
+
//# sourceMappingURL=StorageUnavailableError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageUnavailableError.js","sources":["../../../src/lib/StorageUnavailableError.ts"],"sourcesContent":["/**\n * Reported to `onStorageError` when a persisted store has no storage to use.\n * This is expected during SSR, and can otherwise mean that local storage is inaccessible.\n */\nexport class StorageUnavailableError extends Error {\n constructor(key: string) {\n super(\n `[dharma-core] No storage provider was found for \"${key}\". If this happened during SSR, it can be safely ignored.`,\n );\n this.name = \"StorageUnavailableError\";\n }\n}\n"],"names":[],"mappings":"AAAA;;;AAGG;AACG,MAAO,uBAAwB,SAAQ,KAAK,CAAA;AAChD,IAAA,WAAA,CAAY,GAAW,EAAA;AACrB,QAAA,KAAK,CACH,CAAA,iDAAA,EAAoD,GAAG,CAAA,yDAAA,CAA2D,CACnH;AACD,QAAA,IAAI,CAAC,IAAI,GAAG,yBAAyB;IACvC;AACD;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":[],"mappings":";;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"createEffect.js","sources":["../../../src/lib/createEffect.ts"],"sourcesContent":["import { Effect, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates an effect that runs when the store's state changes.\n *\n * @param store - The store instance to subscribe to\n * @param effectFn - The function to run when dependencies change. Receives the current state as argument.\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the effect will only run when these dependencies change.\n *\n * @returns An Effect object with mount and unmount methods\n *\n * @see {@link https://dharma.fransek.dev/core/createeffect/}\n */\nexport const createEffect = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n effectFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): Effect => {\n let prev: unknown[] | undefined;\n let unsubscribe: (() => void) | undefined;\n\n const execute = () => effectFn(store.get());\n\n const effect = () => {\n const next = dependencyFn?.(store.get());\n\n if (!prev || !deeplyEquals(prev, next)) {\n prev = next;\n execute();\n }\n };\n\n const mount = () => {\n if (!unsubscribe) {\n unsubscribe = store.subscribe(effect);\n }\n return unmount;\n };\n\n const unmount = () => {\n unsubscribe?.();\n unsubscribe = undefined;\n prev = undefined;\n };\n\n return {\n mount,\n unmount,\n };\n};\n"],"names":[],"mappings":";;AAGA;;;;;;;;;;AAUG;AACI,MAAM,YAAY,GAAG,CAC1B,KAA8B,EAC9B,QAAqC,EACrC,YAA2C,KACjC;AACV,IAAA,IAAI,IAA2B;AAC/B,IAAA,IAAI,WAAqC;AAEzC,IAAA,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAK;QAClB,MAAM,IAAI,GAAG,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACtC,IAAI,GAAG,IAAI;AACX,YAAA,OAAO,EAAE;QACX;AACF,IAAA,CAAC;IAED,MAAM,KAAK,GAAG,MAAK;QACjB,IAAI,CAAC,WAAW,EAAE;AAChB,YAAA,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;QACvC;AACA,QAAA,OAAO,OAAO;AAChB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAK;QACnB,WAAW,IAAI;QACf,WAAW,GAAG,SAAS;QACvB,IAAI,GAAG,SAAS;AAClB,IAAA,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;KACR;AACH;;;;"}
|
|
@@ -19,16 +19,15 @@ const createStore = (config) => {
|
|
|
19
19
|
return state;
|
|
20
20
|
};
|
|
21
21
|
const set = (stateModifier) => {
|
|
22
|
+
const prevState = state;
|
|
22
23
|
setSilently(stateModifier);
|
|
23
|
-
|
|
24
|
+
if (!Object.is(state, prevState)) {
|
|
25
|
+
dispatch();
|
|
26
|
+
}
|
|
24
27
|
return state;
|
|
25
28
|
};
|
|
26
29
|
const resetSilently = () => setSilently(initialState);
|
|
27
|
-
const reset = () =>
|
|
28
|
-
resetSilently();
|
|
29
|
-
dispatch();
|
|
30
|
-
return state;
|
|
31
|
-
};
|
|
30
|
+
const reset = () => set(initialState);
|
|
32
31
|
const listeners = new Set();
|
|
33
32
|
const actions = config.actions?.({
|
|
34
33
|
set,
|
|
@@ -45,21 +44,20 @@ const createStore = (config) => {
|
|
|
45
44
|
listener(state);
|
|
46
45
|
listeners.add(listener);
|
|
47
46
|
return () => {
|
|
48
|
-
listeners.delete(listener)
|
|
49
|
-
if (listeners.size === 0) {
|
|
47
|
+
if (listeners.delete(listener) && listeners.size === 0) {
|
|
50
48
|
storageAdapter?.onDetach();
|
|
51
49
|
onDetach?.({ state, set, reset, initialState });
|
|
52
50
|
}
|
|
53
51
|
};
|
|
54
52
|
};
|
|
55
53
|
const dispatch = () => {
|
|
56
|
-
storageAdapter?.onChange(state);
|
|
57
54
|
onChange?.({
|
|
58
55
|
state,
|
|
59
56
|
set: setSilently,
|
|
60
57
|
reset: resetSilently,
|
|
61
58
|
initialState,
|
|
62
59
|
});
|
|
60
|
+
storageAdapter?.onChange(state);
|
|
63
61
|
listeners.forEach((listener) => listener(state));
|
|
64
62
|
};
|
|
65
63
|
storageAdapter?.onLoad();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n setSilently(stateModifier);\n dispatch();\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () =>
|
|
1
|
+
{"version":3,"file":"createStore.js","sources":["../../../src/lib/createStore.ts"],"sourcesContent":["import { createStorageAdapter } from \"../internal/createStorageAdapter\";\nimport { Listener, StateModifier, Store, StoreConfig } from \"../types/types\";\nimport { merge } from \"./merge\";\n\n/**\n * Creates a store with an initial state and actions that can modify the state.\n *\n * @param {StoreConfig<TState, TActions>} [config] - The configuration for the store.\n *\n * @returns {Store<TState, TActions>} The created store with state management methods.\n *\n * @see {@link https://dharma.fransek.dev/core/createstore/}\n */\nexport const createStore = <TState, TActions = undefined>(\n config: StoreConfig<TState, TActions>,\n): Store<TState, TActions> => {\n const { initialState, onLoad, onAttach, onDetach, onChange } = config;\n\n let state = initialState;\n\n const get = () => state;\n\n const setSilently = (stateModifier: StateModifier<TState>) => {\n state = merge(state, stateModifier);\n return state;\n };\n\n const set = (stateModifier: StateModifier<TState>) => {\n const prevState = state;\n setSilently(stateModifier);\n\n if (!Object.is(state, prevState)) {\n dispatch();\n }\n\n return state;\n };\n\n const resetSilently = () => setSilently(initialState);\n\n const reset = () => set(initialState);\n\n const listeners = new Set<Listener<TState>>();\n const actions = config.actions?.({\n set,\n get,\n reset,\n initialState,\n }) as TActions;\n const storageAdapter = createStorageAdapter(config, get, set);\n\n const subscribe = (listener: Listener<TState>) => {\n if (listeners.size === 0) {\n storageAdapter?.onAttach();\n onAttach?.({ state, set, reset, initialState });\n }\n\n listener(state);\n listeners.add(listener);\n\n return () => {\n if (listeners.delete(listener) && listeners.size === 0) {\n storageAdapter?.onDetach();\n onDetach?.({ state, set, reset, initialState });\n }\n };\n };\n\n const dispatch = () => {\n onChange?.({\n state,\n set: setSilently,\n reset: resetSilently,\n initialState,\n });\n storageAdapter?.onChange(state);\n listeners.forEach((listener) => listener(state));\n };\n\n storageAdapter?.onLoad();\n onLoad?.({ state, set, reset, initialState });\n\n return {\n get,\n subscribe,\n actions,\n set,\n reset,\n initialState,\n };\n};\n"],"names":[],"mappings":";;;AAIA;;;;;;;;AAQG;AACI,MAAM,WAAW,GAAG,CACzB,MAAqC,KACV;AAC3B,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM;IAErE,IAAI,KAAK,GAAG,YAAY;AAExB,IAAA,MAAM,GAAG,GAAG,MAAM,KAAK;AAEvB,IAAA,MAAM,WAAW,GAAG,CAAC,aAAoC,KAAI;AAC3D,QAAA,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACnC,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,GAAG,GAAG,CAAC,aAAoC,KAAI;QACnD,MAAM,SAAS,GAAG,KAAK;QACvB,WAAW,CAAC,aAAa,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE;AAChC,YAAA,QAAQ,EAAE;QACZ;AAEA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC;IAErD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC;AAErC,IAAA,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB;AAC7C,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;QACH,GAAG;QACH,KAAK;QACL,YAAY;AACb,KAAA,CAAa;IACd,MAAM,cAAc,GAAG,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;AAE7D,IAAA,MAAM,SAAS,GAAG,CAAC,QAA0B,KAAI;AAC/C,QAAA,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;YACxB,cAAc,EAAE,QAAQ,EAAE;AAC1B,YAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QACjD;QAEA,QAAQ,CAAC,KAAK,CAAC;AACf,QAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEvB,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE;gBACtD,cAAc,EAAE,QAAQ,EAAE;AAC1B,gBAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YACjD;AACF,QAAA,CAAC;AACH,IAAA,CAAC;IAED,MAAM,QAAQ,GAAG,MAAK;AACpB,QAAA,QAAQ,GAAG;YACT,KAAK;AACL,YAAA,GAAG,EAAE,WAAW;AAChB,YAAA,KAAK,EAAE,aAAa;YACpB,YAAY;AACb,SAAA,CAAC;AACF,QAAA,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;AAC/B,QAAA,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClD,IAAA,CAAC;IAED,cAAc,EAAE,MAAM,EAAE;AACxB,IAAA,MAAM,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE7C,OAAO;QACL,GAAG;QACH,SAAS;QACT,OAAO;QACP,GAAG;QACH,KAAK;QACL,YAAY;KACb;AACH;;;;"}
|
|
@@ -28,12 +28,11 @@ const deeplyEquals = (a, b) => {
|
|
|
28
28
|
return false;
|
|
29
29
|
}
|
|
30
30
|
const keysA = Object.keys(a);
|
|
31
|
-
|
|
32
|
-
if (keysA.length !== keysB.length) {
|
|
31
|
+
if (keysA.length !== Object.keys(b).length) {
|
|
33
32
|
return false;
|
|
34
33
|
}
|
|
35
34
|
for (const key of keysA) {
|
|
36
|
-
if (!
|
|
35
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
37
36
|
return false;
|
|
38
37
|
}
|
|
39
38
|
if (!deeplyEquals(a[key], b[key])) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deeplyEquals.js","sources":["../../../src/lib/deeplyEquals.ts"],"sourcesContent":["export const deeplyEquals = (a: unknown, b: unknown): boolean => {\n if (a === b) {\n return true;\n }\n\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n\n const protoA = Object.getPrototypeOf(a);\n const protoB = Object.getPrototypeOf(b);\n\n if (protoA !== protoB) {\n return false;\n }\n\n if (protoA === Array.prototype) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n for (let i = 0; i < arrA.length; i++) {\n if (!deeplyEquals(arrA[i], arrB[i])) return false;\n }\n return true;\n }\n\n if (protoA !== Object.prototype) {\n return false;\n }\n\n const keysA = Object.keys(a);\n
|
|
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;;;;"}
|
package/dist/esm/lib/derive.js
CHANGED
|
@@ -16,22 +16,24 @@ import { deeplyEquals } from './deeplyEquals.js';
|
|
|
16
16
|
*/
|
|
17
17
|
const derive = (store, deriveFn, dependencyFn) => {
|
|
18
18
|
let memo;
|
|
19
|
-
let
|
|
20
|
-
let
|
|
19
|
+
let hasMemo = false;
|
|
20
|
+
let prevState;
|
|
21
|
+
let prevDeps;
|
|
21
22
|
let unsubscribe;
|
|
22
23
|
const listeners = new Set();
|
|
23
|
-
const compute = () => {
|
|
24
|
-
memo = deriveFn(store.get());
|
|
25
|
-
isStale = false;
|
|
26
|
-
return memo;
|
|
27
|
-
};
|
|
28
24
|
const get = () => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const state = store.get();
|
|
26
|
+
if (hasMemo && Object.is(state, prevState)) {
|
|
27
|
+
return memo;
|
|
28
|
+
}
|
|
29
|
+
prevState = state;
|
|
30
|
+
const deps = dependencyFn?.(state);
|
|
31
|
+
if (hasMemo && dependencyFn && deeplyEquals(prevDeps, deps)) {
|
|
32
|
+
return memo;
|
|
34
33
|
}
|
|
34
|
+
prevDeps = deps;
|
|
35
|
+
memo = deriveFn(state);
|
|
36
|
+
hasMemo = true;
|
|
35
37
|
return memo;
|
|
36
38
|
};
|
|
37
39
|
const subscribe = (listener) => {
|
|
@@ -48,7 +50,6 @@ const derive = (store, deriveFn, dependencyFn) => {
|
|
|
48
50
|
};
|
|
49
51
|
};
|
|
50
52
|
const listener = () => {
|
|
51
|
-
isStale = true;
|
|
52
53
|
listeners.forEach((listener) => listener(get()));
|
|
53
54
|
};
|
|
54
55
|
const mount = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive.js","sources":["../../../src/lib/derive.ts"],"sourcesContent":["import { DerivedStore, Listener, Store } from \"../types/types\";\nimport { deeplyEquals } from \"./deeplyEquals\";\n\n/**\n * Creates a derived store from an existing store.\n *\n * A derived store computes its state based on the original store's state using a derive function.\n * The derived store's value is memoized and only recomputed when requested.\n *\n * @param store - The original store to derive from\n * @param deriveFn - A function that computes the derived state from the original state\n * @param dependencyFn - Optional function that returns an array of dependencies. If provided, the derived state will only be recomputed when these dependencies change\n *\n * @returns A new store containing the derived state\n *\n * @see {@link https://dharma.fransek.dev/core/derive/}\n */\nexport const derive = <TState, TActions, TDerived>(\n store: Store<TState, TActions>,\n deriveFn: (state: TState) => TDerived,\n dependencyFn?: (state: TState) => unknown[],\n): DerivedStore<TDerived> => {\n let memo: TDerived | undefined;\n let
|
|
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;;;;"}
|
package/dist/esm/lib/merge.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { StateModifier } from "../types/types";
|
|
|
5
5
|
* @param {T} currentState - The current state object.
|
|
6
6
|
* @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
|
|
7
7
|
* @returns {T} The new state object, which is a combination of the current state and the state modifier.
|
|
8
|
+
* Returns `currentState` itself if the state modifier resolves to it.
|
|
8
9
|
*
|
|
9
10
|
* @see {@link https://dharma.fransek.dev/core/merge/}
|
|
10
11
|
*/
|
package/dist/esm/lib/merge.js
CHANGED
|
@@ -6,21 +6,21 @@ const isObject = (value) => !!value && typeof value === "object" && !Array.isArr
|
|
|
6
6
|
* @param {T} currentState - The current state object.
|
|
7
7
|
* @param {StateModifier<T>} stateModifier - A function or object that modifies the state.
|
|
8
8
|
* @returns {T} The new state object, which is a combination of the current state and the state modifier.
|
|
9
|
+
* Returns `currentState` itself if the state modifier resolves to it.
|
|
9
10
|
*
|
|
10
11
|
* @see {@link https://dharma.fransek.dev/core/merge/}
|
|
11
12
|
*/
|
|
12
13
|
const merge = (currentState, stateModifier) => {
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const modifiedState = typeof stateModifier === "function"
|
|
15
|
+
? stateModifier(currentState)
|
|
16
|
+
: stateModifier;
|
|
17
|
+
if (Object.is(modifiedState, currentState)) {
|
|
18
|
+
return currentState;
|
|
15
19
|
}
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
if (isObject(modifiedState)) {
|
|
19
|
-
return { ...currentState, ...modifiedState };
|
|
20
|
-
}
|
|
21
|
-
return modifiedState;
|
|
20
|
+
if (isObject(modifiedState)) {
|
|
21
|
+
return { ...currentState, ...modifiedState };
|
|
22
22
|
}
|
|
23
|
-
return
|
|
23
|
+
return modifiedState;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export { merge };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n
|
|
1
|
+
{"version":3,"file":"merge.js","sources":["../../../src/lib/merge.ts"],"sourcesContent":["import { StateModifier } from \"../types/types\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst isObject = (value: unknown): value is Record<keyof any, unknown> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Merges the current state with a state modifier.\n *\n * @param {T} currentState - The current state object.\n * @param {StateModifier<T>} stateModifier - A function or object that modifies the state.\n * @returns {T} The new state object, which is a combination of the current state and the state modifier.\n * Returns `currentState` itself if the state modifier resolves to it.\n *\n * @see {@link https://dharma.fransek.dev/core/merge/}\n */\nexport const merge = <T>(\n currentState: T,\n stateModifier: StateModifier<T>,\n): T => {\n const modifiedState =\n typeof stateModifier === \"function\"\n ? stateModifier(currentState)\n : stateModifier;\n\n if (Object.is(modifiedState, currentState)) {\n return currentState;\n }\n\n if (isObject(modifiedState)) {\n return { ...currentState, ...modifiedState };\n }\n\n return modifiedState as T;\n};\n"],"names":[],"mappings":"AAEA;AACA,MAAM,QAAQ,GAAG,CAAC,KAAc,KAC9B,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAE/D;;;;;;;;;AASG;MACU,KAAK,GAAG,CACnB,YAAe,EACf,aAA+B,KAC1B;AACL,IAAA,MAAM,aAAa,GACjB,OAAO,aAAa,KAAK;AACvB,UAAE,aAAa,CAAC,YAAY;UAC1B,aAAa;IAEnB,IAAI,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC,EAAE;AAC1C,QAAA,OAAO,YAAY;IACrB;AAEA,IAAA,IAAI,QAAQ,CAAC,aAAa,CAAC,EAAE;AAC3B,QAAA,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE;IAC9C;AAEA,IAAA,OAAO,aAAkB;AAC3B;;;;"}
|
|
@@ -67,11 +67,10 @@ export type BaseConfig<TState, TActions> = {
|
|
|
67
67
|
/** Invoked whenever the state changes. */
|
|
68
68
|
onChange?: StoreEventListener<TState>;
|
|
69
69
|
};
|
|
70
|
-
export type MaybePromise<T> = T |
|
|
70
|
+
export type MaybePromise<T> = T | PromiseLike<T>;
|
|
71
71
|
export type StorageAPI = {
|
|
72
72
|
getItem: (key: string) => MaybePromise<string | null>;
|
|
73
73
|
setItem: (key: string, value: string) => MaybePromise<void>;
|
|
74
|
-
removeItem: (key: string) => MaybePromise<void>;
|
|
75
74
|
};
|
|
76
75
|
export type Serializer<T = any> = {
|
|
77
76
|
stringify: (value: T) => string;
|
|
@@ -90,5 +89,13 @@ export type PersistConfig<TState> = {
|
|
|
90
89
|
storage?: StorageAPI;
|
|
91
90
|
/** The serializer to use for storing the state. Defaults to JSON. */
|
|
92
91
|
serializer?: Serializer<TState>;
|
|
92
|
+
/**
|
|
93
|
+
* When to initially synchronize the store's state with the storage. Defaults to "load".
|
|
94
|
+
*
|
|
95
|
+
* Set to "attach" when using SSR to avoid hydration errors.
|
|
96
|
+
*/
|
|
97
|
+
syncWithStorageOn?: "load" | "attach";
|
|
98
|
+
/** Invoked when a storage error occurs. */
|
|
99
|
+
onStorageError?: (error: unknown) => void;
|
|
93
100
|
};
|
|
94
101
|
export type StoreConfig<TState, TActions> = BaseConfig<TState, TActions> & (PersistConfig<TState> | NoPersistConfig);
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const warn: (message: string) => void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"warn.js","sources":["../../../src/internal/warn.ts"],"sourcesContent":["export const warn = (message: string) =>\n console.warn(`[dharma-core] ${message}`);\n"],"names":[],"mappings":";;AAAO,MAAM,IAAI,GAAG,CAAC,OAAe,KAClC,OAAO,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAA,CAAE;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const warn: (message: string) => void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"warn.js","sources":["../../../src/internal/warn.ts"],"sourcesContent":["export const warn = (message: string) =>\n console.warn(`[dharma-core] ${message}`);\n"],"names":[],"mappings":"AAAO,MAAM,IAAI,GAAG,CAAC,OAAe,KAClC,OAAO,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAA,CAAE;;;;"}
|