@composy/store-core 0.0.1
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/CHANGELOG.md +5 -0
- package/README.md +172 -0
- package/dist/index.js +1852 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +1082 -0
- package/dist/index.min.js.map +1 -0
- package/es/core/async-action.d.ts +95 -0
- package/es/core/async-action.d.ts.map +1 -0
- package/es/core/async-action.js +116 -0
- package/es/core/async-action.js.map +1 -0
- package/es/core/base-store.d.ts +85 -0
- package/es/core/base-store.d.ts.map +1 -0
- package/es/core/base-store.js +185 -0
- package/es/core/base-store.js.map +1 -0
- package/es/core/batch-manager.d.ts +46 -0
- package/es/core/batch-manager.d.ts.map +1 -0
- package/es/core/batch-manager.js +64 -0
- package/es/core/batch-manager.js.map +1 -0
- package/es/core/create-store.d.ts +66 -0
- package/es/core/create-store.d.ts.map +1 -0
- package/es/core/create-store.js +29 -0
- package/es/core/create-store.js.map +1 -0
- package/es/core/event-bus.d.ts +34 -0
- package/es/core/event-bus.d.ts.map +1 -0
- package/es/core/event-bus.js +71 -0
- package/es/core/event-bus.js.map +1 -0
- package/es/core/index.d.ts +14 -0
- package/es/core/index.d.ts.map +1 -0
- package/es/core/index.js +17 -0
- package/es/core/pub-sub.d.ts +41 -0
- package/es/core/pub-sub.d.ts.map +1 -0
- package/es/core/pub-sub.js +71 -0
- package/es/core/pub-sub.js.map +1 -0
- package/es/engine/index.d.ts +10 -0
- package/es/engine/index.d.ts.map +1 -0
- package/es/engine/index.js +12 -0
- package/es/engine/plugin.d.ts +20 -0
- package/es/engine/plugin.d.ts.map +1 -0
- package/es/engine/plugin.js +153 -0
- package/es/engine/plugin.js.map +1 -0
- package/es/engine/types.d.ts +81 -0
- package/es/engine/types.d.ts.map +1 -0
- package/es/engine/types.js +10 -0
- package/es/errors/index.d.ts +208 -0
- package/es/errors/index.d.ts.map +1 -0
- package/es/errors/index.js +217 -0
- package/es/errors/index.js.map +1 -0
- package/es/index.d.ts +27 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +34 -0
- package/es/index.js.map +1 -0
- package/es/middleware/index.d.ts +13 -0
- package/es/middleware/index.d.ts.map +1 -0
- package/es/middleware/index.js +16 -0
- package/es/middleware/logger.d.ts +67 -0
- package/es/middleware/logger.d.ts.map +1 -0
- package/es/middleware/logger.js +136 -0
- package/es/middleware/logger.js.map +1 -0
- package/es/middleware/middleware-manager.d.ts +40 -0
- package/es/middleware/middleware-manager.d.ts.map +1 -0
- package/es/middleware/middleware-manager.js +79 -0
- package/es/middleware/middleware-manager.js.map +1 -0
- package/es/middleware/persist.d.ts +34 -0
- package/es/middleware/persist.d.ts.map +1 -0
- package/es/middleware/persist.js +102 -0
- package/es/middleware/persist.js.map +1 -0
- package/es/middleware/time-travel.d.ts +30 -0
- package/es/middleware/time-travel.d.ts.map +1 -0
- package/es/middleware/time-travel.js +151 -0
- package/es/middleware/time-travel.js.map +1 -0
- package/es/middleware/validation.d.ts +100 -0
- package/es/middleware/validation.d.ts.map +1 -0
- package/es/middleware/validation.js +74 -0
- package/es/middleware/validation.js.map +1 -0
- package/es/plugins/define-plugin.d.ts +74 -0
- package/es/plugins/define-plugin.d.ts.map +1 -0
- package/es/plugins/define-plugin.js +52 -0
- package/es/plugins/define-plugin.js.map +1 -0
- package/es/plugins/index.d.ts +8 -0
- package/es/plugins/index.d.ts.map +1 -0
- package/es/plugins/index.js +13 -0
- package/es/plugins/plugin-manager.d.ts +35 -0
- package/es/plugins/plugin-manager.d.ts.map +1 -0
- package/es/plugins/plugin-manager.js +77 -0
- package/es/plugins/plugin-manager.js.map +1 -0
- package/es/types/index.d.ts +12 -0
- package/es/types/index.d.ts.map +1 -0
- package/es/types/index.js +10 -0
- package/es/types/middleware.d.ts +172 -0
- package/es/types/middleware.d.ts.map +1 -0
- package/es/types/middleware.js +10 -0
- package/es/types/plugin.d.ts +136 -0
- package/es/types/plugin.d.ts.map +1 -0
- package/es/types/plugin.js +10 -0
- package/es/types/store.d.ts +198 -0
- package/es/types/store.d.ts.map +1 -0
- package/es/types/store.js +10 -0
- package/es/types/subscription.d.ts +158 -0
- package/es/types/subscription.d.ts.map +1 -0
- package/es/types/subscription.js +10 -0
- package/es/types/utility.d.ts +312 -0
- package/es/types/utility.d.ts.map +1 -0
- package/es/types/utility.js +10 -0
- package/es/utils/debounce-throttle.d.ts +86 -0
- package/es/utils/debounce-throttle.d.ts.map +1 -0
- package/es/utils/debounce-throttle.js +86 -0
- package/es/utils/debounce-throttle.js.map +1 -0
- package/es/utils/deep-clone.d.ts +143 -0
- package/es/utils/deep-clone.d.ts.map +1 -0
- package/es/utils/deep-clone.js +193 -0
- package/es/utils/deep-clone.js.map +1 -0
- package/es/utils/index.d.ts +13 -0
- package/es/utils/index.d.ts.map +1 -0
- package/es/utils/index.js +14 -0
- package/es/utils/selector.d.ts +213 -0
- package/es/utils/selector.d.ts.map +1 -0
- package/es/utils/selector.js +151 -0
- package/es/utils/selector.js.map +1 -0
- package/esm/core/async-action.d.ts +95 -0
- package/esm/core/async-action.js +116 -0
- package/esm/core/async-action.js.map +1 -0
- package/esm/core/base-store.d.ts +85 -0
- package/esm/core/base-store.js +185 -0
- package/esm/core/base-store.js.map +1 -0
- package/esm/core/batch-manager.d.ts +46 -0
- package/esm/core/batch-manager.js +64 -0
- package/esm/core/batch-manager.js.map +1 -0
- package/esm/core/create-store.d.ts +66 -0
- package/esm/core/create-store.js +29 -0
- package/esm/core/create-store.js.map +1 -0
- package/esm/core/event-bus.d.ts +34 -0
- package/esm/core/event-bus.js +71 -0
- package/esm/core/event-bus.js.map +1 -0
- package/esm/core/index.d.ts +14 -0
- package/esm/core/index.js +17 -0
- package/esm/core/pub-sub.d.ts +41 -0
- package/esm/core/pub-sub.js +71 -0
- package/esm/core/pub-sub.js.map +1 -0
- package/esm/engine/index.d.ts +10 -0
- package/esm/engine/index.js +12 -0
- package/esm/engine/plugin.d.ts +20 -0
- package/esm/engine/plugin.js +153 -0
- package/esm/engine/plugin.js.map +1 -0
- package/esm/engine/types.d.ts +81 -0
- package/esm/errors/index.d.ts +208 -0
- package/esm/errors/index.js +217 -0
- package/esm/errors/index.js.map +1 -0
- package/esm/index.d.ts +27 -0
- package/esm/index.js +36 -0
- package/esm/index.js.map +1 -0
- package/esm/middleware/index.d.ts +13 -0
- package/esm/middleware/logger.d.ts +67 -0
- package/esm/middleware/logger.js +119 -0
- package/esm/middleware/logger.js.map +1 -0
- package/esm/middleware/middleware-manager.d.ts +40 -0
- package/esm/middleware/middleware-manager.js +79 -0
- package/esm/middleware/middleware-manager.js.map +1 -0
- package/esm/middleware/persist.d.ts +34 -0
- package/esm/middleware/persist.js +102 -0
- package/esm/middleware/persist.js.map +1 -0
- package/esm/middleware/time-travel.d.ts +30 -0
- package/esm/middleware/time-travel.js +151 -0
- package/esm/middleware/time-travel.js.map +1 -0
- package/esm/middleware/validation.d.ts +100 -0
- package/esm/middleware/validation.js +74 -0
- package/esm/middleware/validation.js.map +1 -0
- package/esm/plugins/define-plugin.d.ts +74 -0
- package/esm/plugins/define-plugin.js +52 -0
- package/esm/plugins/define-plugin.js.map +1 -0
- package/esm/plugins/index.d.ts +8 -0
- package/esm/plugins/plugin-manager.d.ts +35 -0
- package/esm/plugins/plugin-manager.js +77 -0
- package/esm/plugins/plugin-manager.js.map +1 -0
- package/esm/types/index.d.ts +12 -0
- package/esm/types/middleware.d.ts +172 -0
- package/esm/types/plugin.d.ts +136 -0
- package/esm/types/store.d.ts +198 -0
- package/esm/types/subscription.d.ts +158 -0
- package/esm/types/utility.d.ts +312 -0
- package/esm/utils/debounce-throttle.d.ts +86 -0
- package/esm/utils/debounce-throttle.js +86 -0
- package/esm/utils/debounce-throttle.js.map +1 -0
- package/esm/utils/deep-clone.d.ts +143 -0
- package/esm/utils/deep-clone.js +193 -0
- package/esm/utils/deep-clone.js.map +1 -0
- package/esm/utils/index.d.ts +13 -0
- package/esm/utils/selector.d.ts +213 -0
- package/esm/utils/selector.js +151 -0
- package/esm/utils/selector.js.map +1 -0
- package/lib/core/async-action.cjs +118 -0
- package/lib/core/async-action.cjs.map +1 -0
- package/lib/core/async-action.d.ts +95 -0
- package/lib/core/base-store.cjs +187 -0
- package/lib/core/base-store.cjs.map +1 -0
- package/lib/core/base-store.d.ts +85 -0
- package/lib/core/batch-manager.cjs +69 -0
- package/lib/core/batch-manager.cjs.map +1 -0
- package/lib/core/batch-manager.d.ts +46 -0
- package/lib/core/create-store.cjs +31 -0
- package/lib/core/create-store.cjs.map +1 -0
- package/lib/core/create-store.d.ts +66 -0
- package/lib/core/event-bus.cjs +74 -0
- package/lib/core/event-bus.cjs.map +1 -0
- package/lib/core/event-bus.d.ts +34 -0
- package/lib/core/index.cjs +29 -0
- package/lib/core/index.d.ts +14 -0
- package/lib/core/pub-sub.cjs +74 -0
- package/lib/core/pub-sub.cjs.map +1 -0
- package/lib/core/pub-sub.d.ts +41 -0
- package/lib/engine/index.cjs +16 -0
- package/lib/engine/index.d.ts +10 -0
- package/lib/engine/plugin.cjs +157 -0
- package/lib/engine/plugin.cjs.map +1 -0
- package/lib/engine/plugin.d.ts +20 -0
- package/lib/engine/types.cjs +12 -0
- package/lib/engine/types.d.ts +81 -0
- package/lib/errors/index.cjs +233 -0
- package/lib/errors/index.cjs.map +1 -0
- package/lib/errors/index.d.ts +208 -0
- package/lib/index.cjs +101 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/middleware/index.cjs +28 -0
- package/lib/middleware/index.d.ts +13 -0
- package/lib/middleware/logger.cjs +140 -0
- package/lib/middleware/logger.cjs.map +1 -0
- package/lib/middleware/logger.d.ts +67 -0
- package/lib/middleware/middleware-manager.cjs +83 -0
- package/lib/middleware/middleware-manager.cjs.map +1 -0
- package/lib/middleware/middleware-manager.d.ts +40 -0
- package/lib/middleware/persist.cjs +105 -0
- package/lib/middleware/persist.cjs.map +1 -0
- package/lib/middleware/persist.d.ts +34 -0
- package/lib/middleware/time-travel.cjs +154 -0
- package/lib/middleware/time-travel.cjs.map +1 -0
- package/lib/middleware/time-travel.d.ts +30 -0
- package/lib/middleware/validation.cjs +76 -0
- package/lib/middleware/validation.cjs.map +1 -0
- package/lib/middleware/validation.d.ts +100 -0
- package/lib/package.json +3 -0
- package/lib/plugins/define-plugin.cjs +56 -0
- package/lib/plugins/define-plugin.cjs.map +1 -0
- package/lib/plugins/define-plugin.d.ts +74 -0
- package/lib/plugins/index.cjs +20 -0
- package/lib/plugins/index.d.ts +8 -0
- package/lib/plugins/plugin-manager.cjs +81 -0
- package/lib/plugins/plugin-manager.cjs.map +1 -0
- package/lib/plugins/plugin-manager.d.ts +35 -0
- package/lib/types/index.cjs +12 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/middleware.cjs +12 -0
- package/lib/types/middleware.d.ts +172 -0
- package/lib/types/plugin.cjs +12 -0
- package/lib/types/plugin.d.ts +136 -0
- package/lib/types/store.cjs +12 -0
- package/lib/types/store.d.ts +198 -0
- package/lib/types/subscription.cjs +12 -0
- package/lib/types/subscription.d.ts +158 -0
- package/lib/types/utility.cjs +12 -0
- package/lib/types/utility.d.ts +312 -0
- package/lib/utils/debounce-throttle.cjs +89 -0
- package/lib/utils/debounce-throttle.cjs.map +1 -0
- package/lib/utils/debounce-throttle.d.ts +86 -0
- package/lib/utils/deep-clone.cjs +202 -0
- package/lib/utils/deep-clone.cjs.map +1 -0
- package/lib/utils/deep-clone.d.ts +143 -0
- package/lib/utils/index.cjs +34 -0
- package/lib/utils/index.d.ts +13 -0
- package/lib/utils/selector.cjs +161 -0
- package/lib/utils/selector.cjs.map +1 -0
- package/lib/utils/selector.d.ts +213 -0
- package/package.json +155 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/store-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:27:33 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
//#region src/utils/selector.ts
|
|
11
|
+
const ARRAY_INDEX_RE = /^\d+$/;
|
|
12
|
+
function createSelector(selector, equalityFn = Object.is) {
|
|
13
|
+
let lastState;
|
|
14
|
+
let lastResult;
|
|
15
|
+
let initialized = false;
|
|
16
|
+
return (state) => {
|
|
17
|
+
if (initialized && lastState === state) return lastResult;
|
|
18
|
+
const result = selector(state);
|
|
19
|
+
if (!initialized || !equalityFn(result, lastResult)) lastResult = result;
|
|
20
|
+
lastState = state;
|
|
21
|
+
initialized = true;
|
|
22
|
+
return lastResult;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createSelectorWithDeps(dependencies, combiner, options = {}) {
|
|
26
|
+
const { equalityFn = Object.is, trackHitRate = false } = options;
|
|
27
|
+
let lastDeps;
|
|
28
|
+
let lastResult;
|
|
29
|
+
let initialized = false;
|
|
30
|
+
let hits = 0;
|
|
31
|
+
let calls = 0;
|
|
32
|
+
const selector = (state) => {
|
|
33
|
+
if (trackHitRate) calls++;
|
|
34
|
+
const deps = dependencies.map((dep) => dep(state));
|
|
35
|
+
if (initialized && lastDeps) {
|
|
36
|
+
let changed = false;
|
|
37
|
+
for (let i = 0; i < deps.length; i++) if (!Object.is(deps[i], lastDeps[i])) {
|
|
38
|
+
changed = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (!changed) {
|
|
42
|
+
if (trackHitRate) hits++;
|
|
43
|
+
return lastResult;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const result = combiner(...deps);
|
|
47
|
+
if (!initialized || !equalityFn(result, lastResult)) lastResult = result;
|
|
48
|
+
lastDeps = deps;
|
|
49
|
+
initialized = true;
|
|
50
|
+
return lastResult;
|
|
51
|
+
};
|
|
52
|
+
if (trackHitRate) selector.getHitRate = () => {
|
|
53
|
+
return calls > 0 ? hits / calls : 0;
|
|
54
|
+
};
|
|
55
|
+
return selector;
|
|
56
|
+
}
|
|
57
|
+
function combineSelectors(selectors, combiner) {
|
|
58
|
+
return (state) => {
|
|
59
|
+
return combiner(...selectors.map((selector) => selector(state)));
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createCachedSelector(selector, options = {}) {
|
|
63
|
+
const { maxSize = 10 } = options;
|
|
64
|
+
const cache = /* @__PURE__ */ new Map();
|
|
65
|
+
const memoizedSelector = (state) => {
|
|
66
|
+
if (cache.has(state)) {
|
|
67
|
+
const cached = cache.get(state);
|
|
68
|
+
cache.delete(state);
|
|
69
|
+
cache.set(state, cached);
|
|
70
|
+
return cached;
|
|
71
|
+
}
|
|
72
|
+
const result = selector(state);
|
|
73
|
+
if (cache.size >= maxSize) {
|
|
74
|
+
const firstKey = cache.keys().next().value;
|
|
75
|
+
if (firstKey !== void 0) cache.delete(firstKey);
|
|
76
|
+
}
|
|
77
|
+
cache.set(state, result);
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
memoizedSelector.clearCache = () => cache.clear();
|
|
81
|
+
memoizedSelector.getCacheSize = () => cache.size;
|
|
82
|
+
return memoizedSelector;
|
|
83
|
+
}
|
|
84
|
+
function createMemoizedSelector(selector, options = {}) {
|
|
85
|
+
return createSelector(selector, options.equalityFn);
|
|
86
|
+
}
|
|
87
|
+
function getByPath(obj, path, defaultValue) {
|
|
88
|
+
if (!path || path.length === 0) return obj;
|
|
89
|
+
const keys = path.replace(/\[([^\]]+)\]/g, ".$1").split(".").filter(Boolean);
|
|
90
|
+
let result = obj;
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
if (result === null || result === void 0) return defaultValue;
|
|
93
|
+
if (typeof result !== "object") return defaultValue;
|
|
94
|
+
result = result[key];
|
|
95
|
+
}
|
|
96
|
+
return result === void 0 ? defaultValue : result;
|
|
97
|
+
}
|
|
98
|
+
function setByPath(obj, path, value) {
|
|
99
|
+
if (!path || path.length === 0) return false;
|
|
100
|
+
const keys = path.replace(/\[([^\]]+)\]/g, ".$1").split(".").filter(Boolean);
|
|
101
|
+
let current = obj;
|
|
102
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
103
|
+
const key = keys[i];
|
|
104
|
+
const nextKey = keys[i + 1] ?? "";
|
|
105
|
+
const nextValue = current[key] ?? (ARRAY_INDEX_RE.test(nextKey) ? [] : {});
|
|
106
|
+
current[key] = nextValue;
|
|
107
|
+
current = current[key];
|
|
108
|
+
}
|
|
109
|
+
const lastKey = keys[keys.length - 1];
|
|
110
|
+
current[lastKey] = value;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
function deleteByPath(obj, path) {
|
|
114
|
+
if (!path || path.length === 0) return false;
|
|
115
|
+
const keys = path.replace(/\[([^\]]+)\]/g, ".$1").split(".").filter(Boolean);
|
|
116
|
+
let current = obj;
|
|
117
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
118
|
+
const key = keys[i];
|
|
119
|
+
if (current[key] === void 0 || current[key] === null) return false;
|
|
120
|
+
current = current[key];
|
|
121
|
+
}
|
|
122
|
+
const lastKey = keys[keys.length - 1];
|
|
123
|
+
if (lastKey in current) {
|
|
124
|
+
if (Array.isArray(current)) {
|
|
125
|
+
const index = Number.parseInt(lastKey, 10);
|
|
126
|
+
if (!Number.isNaN(index)) {
|
|
127
|
+
current.splice(index, 1);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
delete current[lastKey];
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
function hasPath(obj, path) {
|
|
137
|
+
if (!path || path.length === 0) return true;
|
|
138
|
+
const keys = path.replace(/\[([^\]]+)\]/g, ".$1").split(".").filter(Boolean);
|
|
139
|
+
let current = obj;
|
|
140
|
+
for (const key of keys) {
|
|
141
|
+
if (current === null || current === void 0) return false;
|
|
142
|
+
if (typeof current !== "object") return false;
|
|
143
|
+
if (!(key in current)) return false;
|
|
144
|
+
current = current[key];
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
export { combineSelectors, createCachedSelector, createMemoizedSelector, createSelector, createSelectorWithDeps, deleteByPath, getByPath, hasPath, setByPath };
|
|
150
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
151
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","names":[],"sources":["../../src/utils/selector.ts"],"sourcesContent":["/**\n * 状态选择器工具\n *\n * 提供高性能的状态选择器和路径操作工具\n * @module utils/selector\n */\n\nimport type { Selector, StateTree } from '../types'\n\nconst ARRAY_INDEX_RE = /^\\d+$/\n\n/**\n * 选择器相等性比较函数类型\n */\nexport type SelectorEqualityFn<R> = (a: R, b: R) => boolean\n\n/**\n * 记忆化选择器配置选项\n */\nexport interface MemoizedSelectorOptions<R> {\n /** 相等性比较函数 */\n equalityFn?: SelectorEqualityFn<R>\n /** 最大缓存大小(默认 1) */\n maxSize?: number\n /** 是否在开发模式下记录缓存命中率 */\n trackHitRate?: boolean\n}\n\n/**\n * 创建记忆化选择器\n *\n * 基本的记忆化选择器,只缓存最后一次结果\n *\n * @template S - 状态类型\n * @template R - 返回值类型\n * @param selector - 选择器函数\n * @param equalityFn - 相等性比较函数(默认使用 Object.is)\n * @returns 记忆化的选择器\n *\n * @example\n * ```typescript\n * const selectTodos = createSelector(\n * (state: AppState) => state.todos,\n * )\n *\n * const selectCompletedTodos = createSelector(\n * (state: AppState) => state.todos.filter(t => t.completed),\n * (a, b) => a.length === b.length && a.every((v, i) => v === b[i])\n * )\n * ```\n */\nexport function createSelector<S extends StateTree, R>(\n selector: Selector<S, R>,\n equalityFn: SelectorEqualityFn<R> = Object.is\n): Selector<S, R> {\n let lastState: S | undefined\n let lastResult: R | undefined\n let initialized = false\n\n return (state: S): R => {\n // 如果状态引用未变,直接返回缓存结果\n if (initialized && lastState === state) {\n return lastResult as R\n }\n\n const result = selector(state)\n\n // 检查结果是否改变\n if (!initialized || !equalityFn(result, lastResult as R)) {\n lastResult = result\n }\n\n lastState = state\n initialized = true\n\n return lastResult as R\n }\n}\n\n/**\n * 创建带依赖的选择器\n *\n * 类似 reselect 的实现,支持多个输入选择器\n *\n * @template S - 状态类型\n * @template Deps - 依赖结果类型\n * @template R - 返回值类型\n * @param dependencies - 依赖选择器数组\n * @param combiner - 组合函数\n * @param options - 配置选项\n * @returns 记忆化的选择器\n *\n * @example\n * ```typescript\n * const selectVisibleTodos = createSelectorWithDeps(\n * [\n * (state) => state.todos,\n * (state) => state.visibilityFilter,\n * ],\n * (todos, filter) => {\n * switch (filter) {\n * case 'SHOW_COMPLETED': return todos.filter(t => t.completed)\n * case 'SHOW_ACTIVE': return todos.filter(t => !t.completed)\n * default: return todos\n * }\n * }\n * )\n * ```\n */\nexport function createSelectorWithDeps<\n S extends StateTree,\n Deps extends unknown[],\n R,\n>(\n dependencies: { [K in keyof Deps]: Selector<S, Deps[K]> },\n combiner: (...args: Deps) => R,\n options: MemoizedSelectorOptions<R> = {}\n): Selector<S, R> {\n const { equalityFn = Object.is, trackHitRate = false } = options\n\n let lastDeps: Deps | undefined\n let lastResult: R | undefined\n let initialized = false\n\n // 缓存统计(仅开发模式)\n let hits = 0\n let calls = 0\n\n const selector = (state: S): R => {\n if (trackHitRate) calls++\n\n // 计算所有依赖的结果\n const deps = dependencies.map(dep => dep(state)) as Deps\n\n // 检查依赖是否改变\n if (initialized && lastDeps) {\n let changed = false\n for (let i = 0; i < deps.length; i++) {\n if (!Object.is(deps[i], lastDeps[i])) {\n changed = true\n break\n }\n }\n\n if (!changed) {\n if (trackHitRate) hits++\n return lastResult as R\n }\n }\n\n // 计算新结果\n const result = combiner(...deps)\n\n // 检查结果是否改变\n if (!initialized || !equalityFn(result, lastResult as R)) {\n lastResult = result\n }\n\n lastDeps = deps\n initialized = true\n\n return lastResult as R\n }\n\n // 添加缓存统计方法\n if (trackHitRate) {\n ;(selector as Selector<S, R> & { getHitRate: () => number }).getHitRate =\n () => {\n return calls > 0 ? hits / calls : 0\n }\n }\n\n return selector\n}\n\n/**\n * 组合多个选择器\n *\n * 简单的选择器组合,不带记忆化\n *\n * @template S - 状态类型\n * @template R - 结果类型数组\n * @template C - 组合结果类型\n * @param selectors - 选择器数组\n * @param combiner - 组合函数\n * @returns 组合后的选择器\n *\n * @example\n * ```typescript\n * const selectUserAndTodos = combineSelectors(\n * [\n * (state) => state.user,\n * (state) => state.todos,\n * ],\n * (user, todos) => ({ user, todos })\n * )\n * ```\n */\nexport function combineSelectors<S extends StateTree, R extends unknown[], C>(\n selectors: { [K in keyof R]: Selector<S, R[K]> },\n combiner: (...results: R) => C\n): Selector<S, C> {\n return (state: S): C => {\n const results = selectors.map(selector => selector(state)) as R\n return combiner(...results)\n }\n}\n\n/**\n * 创建带高级缓存的选择器\n *\n * 支持多个缓存条目,使用 LRU 策略\n *\n * @template S - 状态类型\n * @template R - 返回值类型\n * @param selector - 选择器函数\n * @param options - 配置选项\n * @returns 带缓存的选择器\n *\n * @example\n * ```typescript\n * const selectFilteredItems = createCachedSelector(\n * (state, filter) => state.items.filter(item => item.type === filter),\n * { maxSize: 5 }\n * )\n * ```\n */\nexport function createCachedSelector<S extends StateTree, R>(\n selector: Selector<S, R>,\n options: MemoizedSelectorOptions<R> = {}\n): Selector<S, R> & { clearCache: () => void; getCacheSize: () => number } {\n const { maxSize = 10 } = options\n\n // 使用 Map 保持插入顺序(LRU 策略)\n const cache = new Map<S, R>()\n\n const memoizedSelector = (state: S): R => {\n // 检查缓存\n if (cache.has(state)) {\n const cached = cache.get(state) as R\n // 移到末尾(最近使用)\n cache.delete(state)\n cache.set(state, cached)\n return cached\n }\n\n // 计算结果\n const result = selector(state)\n\n // 管理缓存大小(LRU 淘汰)\n if (cache.size >= maxSize) {\n const firstKey = cache.keys().next().value\n if (firstKey !== undefined) {\n cache.delete(firstKey)\n }\n }\n\n cache.set(state, result)\n return result\n }\n\n // 添加缓存管理方法\n memoizedSelector.clearCache = () => cache.clear()\n memoizedSelector.getCacheSize = () => cache.size\n\n return memoizedSelector\n}\n\n/**\n * 创建记忆化选择器(别名,同 createSelector)\n *\n * @template S - 状态类型\n * @template R - 返回值类型\n * @param selector - 选择器函数\n * @param options - 配置选项\n * @returns 记忆化的选择器\n */\nexport function createMemoizedSelector<S extends StateTree, R>(\n selector: Selector<S, R>,\n options: MemoizedSelectorOptions<R> = {}\n): Selector<S, R> {\n return createSelector(selector, options.equalityFn)\n}\n\n/**\n * 获取嵌套对象的值\n *\n * 支持点表示法和数组索引\n *\n * @template T - 返回值类型\n * @param obj - 对象\n * @param path - 路径(支持 'a.b.c' 或 'a[0].b')\n * @param defaultValue - 默认值(当路径不存在时返回)\n * @returns 值\n *\n * @example\n * ```typescript\n * const value = getByPath({ a: { b: { c: 1 } } }, 'a.b.c')\n * // value: 1\n *\n * const arr = getByPath({ items: [{ name: 'test' }] }, 'items[0].name')\n * // arr: 'test'\n *\n * const missing = getByPath({ a: 1 }, 'b.c', 'default')\n * // missing: 'default'\n * ```\n */\nexport function getByPath<T = unknown>(\n obj: unknown,\n path: string,\n defaultValue?: T\n): T | undefined {\n if (!path || path.length === 0) {\n return obj as T\n }\n\n // 解析路径(支持 'a.b.c' 和 'a[0].b')\n const keys = path\n .replace(/\\[([^\\]]+)\\]/g, '.$1') // 将 [0] 转换为 .0\n .split('.')\n .filter(Boolean)\n\n let result: unknown = obj\n\n for (const key of keys) {\n if (result === null || result === undefined) {\n return defaultValue\n }\n\n if (typeof result !== 'object') {\n return defaultValue\n }\n\n result = (result as Record<string, unknown>)[key]\n }\n\n return result === undefined ? defaultValue : (result as T)\n}\n\n/**\n * 设置嵌套对象的值\n *\n * 支持自动创建中间路径\n *\n * @param obj - 对象\n * @param path - 路径\n * @param value - 值\n * @returns 是否成功\n *\n * @example\n * ```typescript\n * const obj = {}\n * setByPath(obj, 'a.b.c', 1)\n * // obj: { a: { b: { c: 1 } } }\n *\n * setByPath(obj, 'items[0].name', 'test')\n * // obj: { a: { b: { c: 1 } }, items: [{ name: 'test' }] }\n * ```\n */\nexport function setByPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown\n): boolean {\n if (!path || path.length === 0) {\n return false\n }\n\n // 解析路径\n const keys = path\n .replace(/\\[([^\\]]+)\\]/g, '.$1')\n .split('.')\n .filter(Boolean)\n\n let current: Record<string, unknown> = obj\n\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n const nextKey = keys[i + 1] ?? ''\n\n const nextValue = current[key] ?? (ARRAY_INDEX_RE.test(nextKey) ? [] : {})\n // 根据下一个键决定创建对象还是数组\n current[key] = nextValue\n current = current[key] as Record<string, unknown>\n }\n\n const lastKey = keys[keys.length - 1]\n current[lastKey] = value\n return true\n}\n\n/**\n * 删除嵌套对象的值\n *\n * @param obj - 对象\n * @param path - 路径\n * @returns 是否成功删除\n *\n * @example\n * ```typescript\n * const obj = { a: { b: { c: 1 } } }\n * deleteByPath(obj, 'a.b.c')\n * // obj: { a: { b: {} } }\n * ```\n */\nexport function deleteByPath(\n obj: Record<string, unknown>,\n path: string\n): boolean {\n if (!path || path.length === 0) {\n return false\n }\n\n const keys = path\n .replace(/\\[([^\\]]+)\\]/g, '.$1')\n .split('.')\n .filter(Boolean)\n\n let current: Record<string, unknown> = obj\n\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (current[key] === undefined || current[key] === null) {\n return false\n }\n current = current[key] as Record<string, unknown>\n }\n\n const lastKey = keys[keys.length - 1]\n if (lastKey in current) {\n if (Array.isArray(current)) {\n const index = Number.parseInt(lastKey, 10)\n if (!Number.isNaN(index)) {\n current.splice(index, 1)\n return true\n }\n }\n delete current[lastKey]\n return true\n }\n\n return false\n}\n\n/**\n * 检查嵌套对象中是否存在指定路径\n *\n * @param obj - 对象\n * @param path - 路径\n * @returns 是否存在\n *\n * @example\n * ```typescript\n * hasPath({ a: { b: 1 } }, 'a.b') // true\n * hasPath({ a: { b: 1 } }, 'a.c') // false\n * ```\n */\nexport function hasPath(obj: unknown, path: string): boolean {\n if (!path || path.length === 0) {\n return true\n }\n\n const keys = path\n .replace(/\\[([^\\]]+)\\]/g, '.$1')\n .split('.')\n .filter(Boolean)\n\n let current: unknown = obj\n\n for (const key of keys) {\n if (current === null || current === undefined) {\n return false\n }\n\n if (typeof current !== 'object') {\n return false\n }\n\n if (!(key in (current as Record<string, unknown>))) {\n return false\n }\n\n current = (current as Record<string, unknown>)[key]\n }\n\n return true\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 异步 Action 工具
|
|
3
|
+
*
|
|
4
|
+
* 提供框架无关的异步操作状态管理
|
|
5
|
+
* @module core/async-action
|
|
6
|
+
*/
|
|
7
|
+
import type { Unsubscribe } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* 异步 Action 配置
|
|
10
|
+
*/
|
|
11
|
+
export interface AsyncActionOptions {
|
|
12
|
+
/** 重试次数 (默认 0) */
|
|
13
|
+
retryCount?: number;
|
|
14
|
+
/** 重试间隔 (毫秒, 默认 1000) */
|
|
15
|
+
retryDelay?: number;
|
|
16
|
+
/** 超时时间 (毫秒, 0=不限) */
|
|
17
|
+
timeout?: number;
|
|
18
|
+
/** 执行前是否自动清除上次错误 */
|
|
19
|
+
resetOnExecute?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 异步 Action 状态
|
|
23
|
+
*/
|
|
24
|
+
export interface AsyncActionState<T> {
|
|
25
|
+
/** 返回数据 */
|
|
26
|
+
data: T | null;
|
|
27
|
+
/** 错误信息 */
|
|
28
|
+
error: Error | null;
|
|
29
|
+
/** 是否加载中 */
|
|
30
|
+
loading: boolean;
|
|
31
|
+
/** 执行次数 */
|
|
32
|
+
executionCount: number;
|
|
33
|
+
/** 最后执行时间 */
|
|
34
|
+
lastExecutedAt: number | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 异步 Action 实例
|
|
38
|
+
*/
|
|
39
|
+
export interface AsyncAction<T, Args extends unknown[]> {
|
|
40
|
+
/** 当前状态 */
|
|
41
|
+
readonly state: AsyncActionState<T>;
|
|
42
|
+
/** 执行异步操作 */
|
|
43
|
+
execute: (...args: Args) => Promise<T>;
|
|
44
|
+
/** 重置状态 */
|
|
45
|
+
reset: () => void;
|
|
46
|
+
/** 取消当前执行 */
|
|
47
|
+
abort: () => void;
|
|
48
|
+
/** 订阅状态变化 */
|
|
49
|
+
subscribe: (callback: (state: AsyncActionState<T>) => void) => Unsubscribe;
|
|
50
|
+
/** 设置成功回调 */
|
|
51
|
+
onSuccess: (callback: (data: T) => void) => Unsubscribe;
|
|
52
|
+
/** 设置错误回调 */
|
|
53
|
+
onError: (callback: (error: Error) => void) => Unsubscribe;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 创建异步 Action
|
|
57
|
+
*
|
|
58
|
+
* 将异步函数包装为带状态追踪的 Action,自动管理 loading/data/error 状态
|
|
59
|
+
*
|
|
60
|
+
* @template T - 返回数据类型
|
|
61
|
+
* @template Args - 参数类型
|
|
62
|
+
* @param fn - 异步函数
|
|
63
|
+
* @param options - 配置选项
|
|
64
|
+
* @returns AsyncAction 实例
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // 基本用法
|
|
69
|
+
* const fetchUsers = createAsyncAction(async (page: number) => {
|
|
70
|
+
* const res = await fetch(`/api/users?page=${page}`)
|
|
71
|
+
* return res.json()
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* await fetchUsers.execute(1)
|
|
75
|
+
* console.log(fetchUsers.state.data) // 用户列表
|
|
76
|
+
* console.log(fetchUsers.state.loading) // false
|
|
77
|
+
*
|
|
78
|
+
* // 带重试
|
|
79
|
+
* const fetchWithRetry = createAsyncAction(
|
|
80
|
+
* async () => fetch('/api/unstable').then(r => r.json()),
|
|
81
|
+
* { retryCount: 3, retryDelay: 1000 }
|
|
82
|
+
* )
|
|
83
|
+
*
|
|
84
|
+
* // 订阅状态变化
|
|
85
|
+
* fetchUsers.subscribe((state) => {
|
|
86
|
+
* console.log('loading:', state.loading, 'data:', state.data)
|
|
87
|
+
* })
|
|
88
|
+
*
|
|
89
|
+
* // 成功/错误回调
|
|
90
|
+
* fetchUsers.onSuccess((data) => console.log('Got:', data))
|
|
91
|
+
* fetchUsers.onError((err) => console.error('Failed:', err))
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function createAsyncAction<T, Args extends unknown[] = []>(fn: (...args: Args) => Promise<T>, options?: AsyncActionOptions): AsyncAction<T, Args>;
|
|
95
|
+
//# sourceMappingURL=async-action.d.ts.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/store-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:27:33 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
import { createPubSub } from "./pub-sub.js";
|
|
11
|
+
//#region src/core/async-action.ts
|
|
12
|
+
function delay(ms) {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
}
|
|
15
|
+
function createAsyncAction(fn, options = {}) {
|
|
16
|
+
const { retryCount = 0, retryDelay = 1e3, timeout = 0, resetOnExecute = true } = options;
|
|
17
|
+
const _state = {
|
|
18
|
+
data: null,
|
|
19
|
+
error: null,
|
|
20
|
+
loading: false,
|
|
21
|
+
executionCount: 0,
|
|
22
|
+
lastExecutedAt: null
|
|
23
|
+
};
|
|
24
|
+
const statePubSub = createPubSub();
|
|
25
|
+
const successPubSub = createPubSub();
|
|
26
|
+
const errorPubSub = createPubSub();
|
|
27
|
+
let abortController = null;
|
|
28
|
+
const notify = () => {
|
|
29
|
+
statePubSub.publish({ ..._state });
|
|
30
|
+
};
|
|
31
|
+
const executeWithRetry = async (args, retriesLeft) => {
|
|
32
|
+
try {
|
|
33
|
+
return await fn(...args);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (retriesLeft > 0 && !abortController?.signal.aborted) {
|
|
36
|
+
await delay(retryDelay);
|
|
37
|
+
if (abortController?.signal.aborted) throw new Error("Aborted");
|
|
38
|
+
return executeWithRetry(args, retriesLeft - 1);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const execute = async (...args) => {
|
|
44
|
+
if (abortController) abortController.abort();
|
|
45
|
+
abortController = new AbortController();
|
|
46
|
+
if (resetOnExecute) _state.error = null;
|
|
47
|
+
_state.loading = true;
|
|
48
|
+
_state.executionCount++;
|
|
49
|
+
_state.lastExecutedAt = Date.now();
|
|
50
|
+
notify();
|
|
51
|
+
try {
|
|
52
|
+
let result;
|
|
53
|
+
if (timeout > 0) {
|
|
54
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
55
|
+
setTimeout(() => reject(/* @__PURE__ */ new Error(`Timeout after ${timeout}ms`)), timeout);
|
|
56
|
+
});
|
|
57
|
+
result = await Promise.race([executeWithRetry(args, retryCount), timeoutPromise]);
|
|
58
|
+
} else result = await executeWithRetry(args, retryCount);
|
|
59
|
+
if (!abortController.signal.aborted) {
|
|
60
|
+
_state.data = result;
|
|
61
|
+
_state.error = null;
|
|
62
|
+
_state.loading = false;
|
|
63
|
+
notify();
|
|
64
|
+
successPubSub.publish(result);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (!abortController.signal.aborted) {
|
|
69
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
70
|
+
_state.error = err;
|
|
71
|
+
_state.loading = false;
|
|
72
|
+
notify();
|
|
73
|
+
errorPubSub.publish(err);
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const reset = () => {
|
|
79
|
+
if (abortController) {
|
|
80
|
+
abortController.abort();
|
|
81
|
+
abortController = null;
|
|
82
|
+
}
|
|
83
|
+
_state.data = null;
|
|
84
|
+
_state.error = null;
|
|
85
|
+
_state.loading = false;
|
|
86
|
+
_state.executionCount = 0;
|
|
87
|
+
_state.lastExecutedAt = null;
|
|
88
|
+
notify();
|
|
89
|
+
};
|
|
90
|
+
const abort = () => {
|
|
91
|
+
if (abortController) {
|
|
92
|
+
abortController.abort();
|
|
93
|
+
abortController = null;
|
|
94
|
+
}
|
|
95
|
+
if (_state.loading) {
|
|
96
|
+
_state.loading = false;
|
|
97
|
+
_state.error = /* @__PURE__ */ new Error("Aborted");
|
|
98
|
+
notify();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
get state() {
|
|
103
|
+
return { ..._state };
|
|
104
|
+
},
|
|
105
|
+
execute,
|
|
106
|
+
reset,
|
|
107
|
+
abort,
|
|
108
|
+
subscribe: (callback) => statePubSub.subscribe(callback),
|
|
109
|
+
onSuccess: (callback) => successPubSub.subscribe(callback),
|
|
110
|
+
onError: (callback) => errorPubSub.subscribe(callback)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { createAsyncAction };
|
|
115
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
116
|
+
//# sourceMappingURL=async-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-action.js","names":[],"sources":["../../src/core/async-action.ts"],"sourcesContent":["/**\n * 异步 Action 工具\n *\n * 提供框架无关的异步操作状态管理\n * @module core/async-action\n */\n\nimport type { Unsubscribe } from '../types'\nimport { createPubSub } from './pub-sub'\n\n/**\n * 异步 Action 配置\n */\nexport interface AsyncActionOptions {\n /** 重试次数 (默认 0) */\n retryCount?: number\n /** 重试间隔 (毫秒, 默认 1000) */\n retryDelay?: number\n /** 超时时间 (毫秒, 0=不限) */\n timeout?: number\n /** 执行前是否自动清除上次错误 */\n resetOnExecute?: boolean\n}\n\n/**\n * 异步 Action 状态\n */\nexport interface AsyncActionState<T> {\n /** 返回数据 */\n data: T | null\n /** 错误信息 */\n error: Error | null\n /** 是否加载中 */\n loading: boolean\n /** 执行次数 */\n executionCount: number\n /** 最后执行时间 */\n lastExecutedAt: number | null\n}\n\n/**\n * 异步 Action 实例\n */\nexport interface AsyncAction<T, Args extends unknown[]> {\n /** 当前状态 */\n readonly state: AsyncActionState<T>\n /** 执行异步操作 */\n execute: (...args: Args) => Promise<T>\n /** 重置状态 */\n reset: () => void\n /** 取消当前执行 */\n abort: () => void\n /** 订阅状态变化 */\n subscribe: (callback: (state: AsyncActionState<T>) => void) => Unsubscribe\n /** 设置成功回调 */\n onSuccess: (callback: (data: T) => void) => Unsubscribe\n /** 设置错误回调 */\n onError: (callback: (error: Error) => void) => Unsubscribe\n}\n\n/**\n * 延迟工具函数\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/**\n * 创建异步 Action\n *\n * 将异步函数包装为带状态追踪的 Action,自动管理 loading/data/error 状态\n *\n * @template T - 返回数据类型\n * @template Args - 参数类型\n * @param fn - 异步函数\n * @param options - 配置选项\n * @returns AsyncAction 实例\n *\n * @example\n * ```typescript\n * // 基本用法\n * const fetchUsers = createAsyncAction(async (page: number) => {\n * const res = await fetch(`/api/users?page=${page}`)\n * return res.json()\n * })\n *\n * await fetchUsers.execute(1)\n * console.log(fetchUsers.state.data) // 用户列表\n * console.log(fetchUsers.state.loading) // false\n *\n * // 带重试\n * const fetchWithRetry = createAsyncAction(\n * async () => fetch('/api/unstable').then(r => r.json()),\n * { retryCount: 3, retryDelay: 1000 }\n * )\n *\n * // 订阅状态变化\n * fetchUsers.subscribe((state) => {\n * console.log('loading:', state.loading, 'data:', state.data)\n * })\n *\n * // 成功/错误回调\n * fetchUsers.onSuccess((data) => console.log('Got:', data))\n * fetchUsers.onError((err) => console.error('Failed:', err))\n * ```\n */\nexport function createAsyncAction<T, Args extends unknown[] = []>(\n fn: (...args: Args) => Promise<T>,\n options: AsyncActionOptions = {},\n): AsyncAction<T, Args> {\n const {\n retryCount = 0,\n retryDelay = 1000,\n timeout = 0,\n resetOnExecute = true,\n } = options\n\n // 内部状态\n const _state: AsyncActionState<T> = {\n data: null,\n error: null,\n loading: false,\n executionCount: 0,\n lastExecutedAt: null,\n }\n\n // 发布/订阅\n const statePubSub = createPubSub<AsyncActionState<T>>()\n const successPubSub = createPubSub<T>()\n const errorPubSub = createPubSub<Error>()\n\n // 当前 AbortController\n let abortController: AbortController | null = null\n\n /** 通知状态变化 */\n const notify = (): void => {\n statePubSub.publish({ ..._state })\n }\n\n /** 执行带重试的异步函数 */\n const executeWithRetry = async (args: Args, retriesLeft: number): Promise<T> => {\n try {\n return await fn(...args)\n }\n catch (error) {\n if (retriesLeft > 0 && !abortController?.signal.aborted) {\n await delay(retryDelay)\n if (abortController?.signal.aborted) {\n throw new Error('Aborted')\n }\n return executeWithRetry(args, retriesLeft - 1)\n }\n throw error\n }\n }\n\n /** 执行 */\n const execute = async (...args: Args): Promise<T> => {\n // 取消上一次执行\n if (abortController) {\n abortController.abort()\n }\n abortController = new AbortController()\n\n if (resetOnExecute) {\n _state.error = null\n }\n\n _state.loading = true\n _state.executionCount++\n _state.lastExecutedAt = Date.now()\n notify()\n\n try {\n let result: T\n\n if (timeout > 0) {\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout)\n })\n result = await Promise.race([\n executeWithRetry(args, retryCount),\n timeoutPromise,\n ])\n }\n else {\n result = await executeWithRetry(args, retryCount)\n }\n\n if (!abortController.signal.aborted) {\n _state.data = result\n _state.error = null\n _state.loading = false\n notify()\n successPubSub.publish(result)\n }\n\n return result\n }\n catch (error) {\n if (!abortController.signal.aborted) {\n const err = error instanceof Error ? error : new Error(String(error))\n _state.error = err\n _state.loading = false\n notify()\n errorPubSub.publish(err)\n }\n throw error\n }\n }\n\n /** 重置 */\n const reset = (): void => {\n if (abortController) {\n abortController.abort()\n abortController = null\n }\n _state.data = null\n _state.error = null\n _state.loading = false\n _state.executionCount = 0\n _state.lastExecutedAt = null\n notify()\n }\n\n /** 取消 */\n const abort = (): void => {\n if (abortController) {\n abortController.abort()\n abortController = null\n }\n if (_state.loading) {\n _state.loading = false\n _state.error = new Error('Aborted')\n notify()\n }\n }\n\n return {\n get state() { return { ..._state } },\n execute,\n reset,\n abort,\n subscribe: (callback) => statePubSub.subscribe(callback),\n onSuccess: (callback) => successPubSub.subscribe(callback),\n onError: (callback) => errorPubSub.subscribe(callback),\n }\n}\n"],"mappings":";;;;;;;;;;;AA+DA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAQ,YAAW,WAAW,SAAS,EAAE,CAAC;AACvD;AAyCO,SAAS,kBACd,IACA,UAA8B,CAAC,GACT;CACtB,MAAM,EACJ,aAAa,GACb,aAAa,KACb,UAAU,GACV,iBAAiB,SACf;CAGJ,MAAM,SAA8B;EAClC,MAAM;EACN,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,gBAAgB;CAClB;CAGA,MAAM,cAAc,aAAkC;CACtD,MAAM,gBAAgB,aAAgB;CACtC,MAAM,cAAc,aAAoB;CAGxC,IAAI,kBAA0C;CAG9C,MAAM,eAAqB;EACzB,YAAY,QAAQ,EAAE,GAAG,OAAO,CAAC;CACnC;CAGA,MAAM,mBAAmB,OAAO,MAAY,gBAAoC;EAC9E,IAAI;GACF,OAAO,MAAM,GAAG,GAAG,IAAI;EACzB,SACO,OAAO;GACZ,IAAI,cAAc,KAAK,CAAC,iBAAiB,OAAO,SAAS;IACvD,MAAM,MAAM,UAAU;IACtB,IAAI,iBAAiB,OAAO,SAC1B,MAAM,IAAI,MAAM,SAAS;IAE3B,OAAO,iBAAiB,MAAM,cAAc,CAAC;GAC/C;GACA,MAAM;EACR;CACF;CAGA,MAAM,UAAU,OAAA,GAAU,SAA2B;EAEnD,IAAI,iBACF,gBAAgB,MAAM;EAExB,kBAAkB,IAAI,gBAAgB;EAEtC,IAAI,gBACF,OAAO,QAAQ;EAGjB,OAAO,UAAU;EACjB,OAAO;EACP,OAAO,iBAAiB,KAAK,IAAI;EACjC,OAAO;EAEP,IAAI;GACF,IAAI;GAEJ,IAAI,UAAU,GAAG;IACf,MAAM,iBAAiB,IAAI,SAAgB,GAAG,WAAW;KACvD,iBAAiB,uBAAO,IAAI,MAAM,iBAAiB,QAAO,GAAI,CAAC,GAAG,OAAO;IAC3E,CAAC;IACD,SAAS,MAAM,QAAQ,KAAK,CAC1B,iBAAiB,MAAM,UAAU,GACjC,cACF,CAAC;GACH,OAEE,SAAS,MAAM,iBAAiB,MAAM,UAAU;GAGlD,IAAI,CAAC,gBAAgB,OAAO,SAAS;IACnC,OAAO,OAAO;IACd,OAAO,QAAQ;IACf,OAAO,UAAU;IACjB,OAAO;IACP,cAAc,QAAQ,MAAM;GAC9B;GAEA,OAAO;EACT,SACO,OAAO;GACZ,IAAI,CAAC,gBAAgB,OAAO,SAAS;IACnC,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACpE,OAAO,QAAQ;IACf,OAAO,UAAU;IACjB,OAAO;IACP,YAAY,QAAQ,GAAG;GACzB;GACA,MAAM;EACR;CACF;CAGA,MAAM,cAAoB;EACxB,IAAI,iBAAiB;GACnB,gBAAgB,MAAM;GACtB,kBAAkB;EACpB;EACA,OAAO,OAAO;EACd,OAAO,QAAQ;EACf,OAAO,UAAU;EACjB,OAAO,iBAAiB;EACxB,OAAO,iBAAiB;EACxB,OAAO;CACT;CAGA,MAAM,cAAoB;EACxB,IAAI,iBAAiB;GACnB,gBAAgB,MAAM;GACtB,kBAAkB;EACpB;EACA,IAAI,OAAO,SAAS;GAClB,OAAO,UAAU;GACjB,OAAO,wBAAQ,IAAI,MAAM,SAAS;GAClC,OAAO;EACT;CACF;CAEA,OAAO;EACL,IAAI,QAAQ;GAAE,OAAO,EAAE,GAAG,OAAO;EAAE;EACnC;EACA;EACA;EACA,YAAY,aAAa,YAAY,UAAU,QAAQ;EACvD,YAAY,aAAa,cAAc,UAAU,QAAQ;EACzD,UAAU,aAAa,YAAY,UAAU,QAAQ;CACvD;AACF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基础 Store 抽象类
|
|
3
|
+
*
|
|
4
|
+
* 提供框架无关的 Store 基础实现
|
|
5
|
+
* @module core/base-store
|
|
6
|
+
*/
|
|
7
|
+
import type { ActionContext, ActionSubscriber, StateMutation, StateSubscriber, StateTree, StoreActions, StoreDefinition, StoreGetters, StoreId, StoreInstance, StoreOptions, SubscribeOptions, Unsubscribe } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* 基础 Store 类
|
|
10
|
+
*
|
|
11
|
+
* @template Id - Store ID 类型
|
|
12
|
+
* @template S - 状态类型
|
|
13
|
+
* @template G - Getters 类型
|
|
14
|
+
* @template A - Actions 类型
|
|
15
|
+
*/
|
|
16
|
+
export declare class BaseStore<Id extends StoreId = StoreId, S extends StateTree = StateTree, G extends StoreGetters<S> = StoreGetters<S>, A extends StoreActions = StoreActions> implements StoreInstance<Id, S, G, A> {
|
|
17
|
+
/** Store ID */
|
|
18
|
+
readonly $id: Id;
|
|
19
|
+
/** Getter 计算结果 */
|
|
20
|
+
readonly getters: {
|
|
21
|
+
[K in keyof G]: ReturnType<G[K]>;
|
|
22
|
+
};
|
|
23
|
+
/** Action 方法集合 */
|
|
24
|
+
readonly actions: A;
|
|
25
|
+
/** 状态订阅管理器 */
|
|
26
|
+
private stateSubscribers;
|
|
27
|
+
/** Action 订阅管理器 */
|
|
28
|
+
private actionSubscribers;
|
|
29
|
+
/** 初始状态工厂 */
|
|
30
|
+
private initialStateFactory;
|
|
31
|
+
/** 当前状态 */
|
|
32
|
+
private _state;
|
|
33
|
+
/** 是否已销毁 */
|
|
34
|
+
private disposed;
|
|
35
|
+
/**
|
|
36
|
+
* 构造函数
|
|
37
|
+
*
|
|
38
|
+
* @param definition - Store 定义
|
|
39
|
+
* @param _options - Store 配置选项
|
|
40
|
+
*/
|
|
41
|
+
constructor(definition: StoreDefinition<Id, S, G, A>, _options?: StoreOptions);
|
|
42
|
+
/**
|
|
43
|
+
* 获取当前状态
|
|
44
|
+
*/
|
|
45
|
+
get $state(): S;
|
|
46
|
+
/**
|
|
47
|
+
* 设置状态
|
|
48
|
+
*/
|
|
49
|
+
set $state(newState: S);
|
|
50
|
+
/**
|
|
51
|
+
* 重置状态到初始值
|
|
52
|
+
*/
|
|
53
|
+
$reset(): void;
|
|
54
|
+
/**
|
|
55
|
+
* 批量更新状态
|
|
56
|
+
*/
|
|
57
|
+
$patch(partialState: Partial<S> | ((state: S) => void)): void;
|
|
58
|
+
/**
|
|
59
|
+
* 订阅状态变化
|
|
60
|
+
*/
|
|
61
|
+
$subscribe(callback: StateSubscriber<S>, options?: SubscribeOptions): Unsubscribe;
|
|
62
|
+
/**
|
|
63
|
+
* 订阅 Action 调用
|
|
64
|
+
*/
|
|
65
|
+
$onAction(callback: ActionSubscriber<A>): Unsubscribe;
|
|
66
|
+
/**
|
|
67
|
+
* 销毁 Store
|
|
68
|
+
*/
|
|
69
|
+
$dispose(): void;
|
|
70
|
+
/**
|
|
71
|
+
* 通知状态变化
|
|
72
|
+
*/
|
|
73
|
+
protected notifyStateChange(mutation: StateMutation<S>): void;
|
|
74
|
+
/**
|
|
75
|
+
* 通知 Action 调用
|
|
76
|
+
*/
|
|
77
|
+
protected notifyAction(context: ActionContext<A>): void;
|
|
78
|
+
/**
|
|
79
|
+
* 检查是否已销毁
|
|
80
|
+
*/
|
|
81
|
+
private checkDisposed;
|
|
82
|
+
private createGetters;
|
|
83
|
+
private createActions;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=base-store.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/store-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:27:33 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
import { createPubSub } from "./pub-sub.js";
|
|
11
|
+
import { globalEventBus } from "./event-bus.js";
|
|
12
|
+
//#region src/core/base-store.ts
|
|
13
|
+
function isPromiseLike(value) {
|
|
14
|
+
return !!value && typeof value.then === "function";
|
|
15
|
+
}
|
|
16
|
+
async function runCallbacks(callbacks, value) {
|
|
17
|
+
for (const callback of callbacks) await callback(value);
|
|
18
|
+
}
|
|
19
|
+
var BaseStore = class {
|
|
20
|
+
/**
|
|
21
|
+
* 构造函数
|
|
22
|
+
*
|
|
23
|
+
* @param definition - Store 定义
|
|
24
|
+
* @param _options - Store 配置选项
|
|
25
|
+
*/
|
|
26
|
+
constructor(definition, _options = {}) {
|
|
27
|
+
/** 状态订阅管理器 */
|
|
28
|
+
this.stateSubscribers = createPubSub();
|
|
29
|
+
/** Action 订阅管理器 */
|
|
30
|
+
this.actionSubscribers = createPubSub();
|
|
31
|
+
/** 是否已销毁 */
|
|
32
|
+
this.disposed = false;
|
|
33
|
+
this.$id = definition.id;
|
|
34
|
+
this.initialStateFactory = definition.state;
|
|
35
|
+
this._state = definition.state();
|
|
36
|
+
this.getters = this.createGetters(definition.getters || {});
|
|
37
|
+
this.actions = this.createActions(definition.actions || {});
|
|
38
|
+
globalEventBus.emit("store:created", { storeId: this.$id });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取当前状态
|
|
42
|
+
*/
|
|
43
|
+
get $state() {
|
|
44
|
+
return this._state;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 设置状态
|
|
48
|
+
*/
|
|
49
|
+
set $state(newState) {
|
|
50
|
+
this._state = newState;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 重置状态到初始值
|
|
54
|
+
*/
|
|
55
|
+
$reset() {
|
|
56
|
+
this.checkDisposed();
|
|
57
|
+
const oldState = { ...this._state };
|
|
58
|
+
this._state = this.initialStateFactory();
|
|
59
|
+
this.notifyStateChange({
|
|
60
|
+
type: "direct",
|
|
61
|
+
storeId: this.$id,
|
|
62
|
+
oldState
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 批量更新状态
|
|
67
|
+
*/
|
|
68
|
+
$patch(partialState) {
|
|
69
|
+
this.checkDisposed();
|
|
70
|
+
const oldState = { ...this._state };
|
|
71
|
+
if (typeof partialState === "function") {
|
|
72
|
+
partialState(this._state);
|
|
73
|
+
this.notifyStateChange({
|
|
74
|
+
type: "patch function",
|
|
75
|
+
storeId: this.$id,
|
|
76
|
+
oldState
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
Object.assign(this._state, partialState);
|
|
80
|
+
this.notifyStateChange({
|
|
81
|
+
type: "patch object",
|
|
82
|
+
storeId: this.$id,
|
|
83
|
+
oldState,
|
|
84
|
+
events: Object.keys(partialState)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 订阅状态变化
|
|
90
|
+
*/
|
|
91
|
+
$subscribe(callback, options = {}) {
|
|
92
|
+
this.checkDisposed();
|
|
93
|
+
return this.stateSubscribers.subscribe(({ mutation, state }) => callback(mutation, state), { immediate: options.immediate });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 订阅 Action 调用
|
|
97
|
+
*/
|
|
98
|
+
$onAction(callback) {
|
|
99
|
+
this.checkDisposed();
|
|
100
|
+
return this.actionSubscribers.subscribe(callback);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 销毁 Store
|
|
104
|
+
*/
|
|
105
|
+
$dispose() {
|
|
106
|
+
if (this.disposed) return;
|
|
107
|
+
this.disposed = true;
|
|
108
|
+
this.stateSubscribers.clear();
|
|
109
|
+
this.actionSubscribers.clear();
|
|
110
|
+
globalEventBus.emit("store:disposed", { storeId: this.$id });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 通知状态变化
|
|
114
|
+
*/
|
|
115
|
+
notifyStateChange(mutation) {
|
|
116
|
+
this.stateSubscribers.publish({
|
|
117
|
+
mutation,
|
|
118
|
+
state: this._state
|
|
119
|
+
});
|
|
120
|
+
globalEventBus.emit("state:changed", {
|
|
121
|
+
storeId: this.$id,
|
|
122
|
+
state: this._state,
|
|
123
|
+
mutation
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 通知 Action 调用
|
|
128
|
+
*/
|
|
129
|
+
notifyAction(context) {
|
|
130
|
+
this.actionSubscribers.publish(context);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 检查是否已销毁
|
|
134
|
+
*/
|
|
135
|
+
checkDisposed() {
|
|
136
|
+
if (this.disposed) throw new Error(`[Store] Store "${this.$id}" \u5DF2\u88AB\u9500\u6BC1`);
|
|
137
|
+
}
|
|
138
|
+
createGetters(gettersDefinition) {
|
|
139
|
+
const getterCache = {};
|
|
140
|
+
for (const key of Object.keys(gettersDefinition)) Object.defineProperty(getterCache, key, {
|
|
141
|
+
enumerable: true,
|
|
142
|
+
get: () => gettersDefinition[key](this._state)
|
|
143
|
+
});
|
|
144
|
+
return getterCache;
|
|
145
|
+
}
|
|
146
|
+
createActions(actionsDefinition) {
|
|
147
|
+
const actionCache = {};
|
|
148
|
+
for (const key of Object.keys(actionsDefinition)) {
|
|
149
|
+
const action = actionsDefinition[key];
|
|
150
|
+
actionCache[key] = ((...args) => {
|
|
151
|
+
this.checkDisposed();
|
|
152
|
+
const afterCallbacks = [];
|
|
153
|
+
const errorCallbacks = [];
|
|
154
|
+
const context = {
|
|
155
|
+
after: (callback) => afterCallbacks.push(callback),
|
|
156
|
+
args,
|
|
157
|
+
name: key,
|
|
158
|
+
onError: (callback) => errorCallbacks.push(callback),
|
|
159
|
+
storeId: this.$id
|
|
160
|
+
};
|
|
161
|
+
this.notifyAction(context);
|
|
162
|
+
try {
|
|
163
|
+
const result = action.apply(this, args);
|
|
164
|
+
if (isPromiseLike(result)) return result.then(async (resolvedResult) => {
|
|
165
|
+
await runCallbacks(afterCallbacks, resolvedResult);
|
|
166
|
+
return resolvedResult;
|
|
167
|
+
}).catch(async (error) => {
|
|
168
|
+
await runCallbacks(errorCallbacks, error);
|
|
169
|
+
throw error;
|
|
170
|
+
});
|
|
171
|
+
runCallbacks(afterCallbacks, result);
|
|
172
|
+
return result;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
runCallbacks(errorCallbacks, error);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return actionCache;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
//#endregion
|
|
183
|
+
export { BaseStore };
|
|
184
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
185
|
+
//# sourceMappingURL=base-store.js.map
|