@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,136 @@
|
|
|
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 { MiddlewareError } from "../errors/index.js";
|
|
11
|
+
import { deepClone } from "../utils/deep-clone.js";
|
|
12
|
+
//#region src/middleware/logger.ts
|
|
13
|
+
const defaultOptions = {
|
|
14
|
+
logState: true,
|
|
15
|
+
logAction: true,
|
|
16
|
+
collapsed: true,
|
|
17
|
+
timestamp: true,
|
|
18
|
+
level: "log"
|
|
19
|
+
};
|
|
20
|
+
const LOG_COLORS = {
|
|
21
|
+
prevState: "color: #9E9E9E; font-weight: bold",
|
|
22
|
+
action: "color: #03A9F4; font-weight: bold",
|
|
23
|
+
nextState: "color: #4CAF50; font-weight: bold",
|
|
24
|
+
error: "color: #F44336; font-weight: bold",
|
|
25
|
+
duration: "color: #9C27B0; font-weight: normal",
|
|
26
|
+
title: "color: inherit; font-weight: bold"
|
|
27
|
+
};
|
|
28
|
+
function formatTime(date) {
|
|
29
|
+
return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}.${date.getMilliseconds().toString().padStart(3, "0")}`;
|
|
30
|
+
}
|
|
31
|
+
function formatDuration(startTime, endTime) {
|
|
32
|
+
const duration = endTime - startTime;
|
|
33
|
+
if (duration < 1) return "< 1ms";
|
|
34
|
+
return `${duration.toFixed(2)}ms`;
|
|
35
|
+
}
|
|
36
|
+
function safeCloneState(state) {
|
|
37
|
+
try {
|
|
38
|
+
return deepClone(state);
|
|
39
|
+
} catch {
|
|
40
|
+
return { ...state };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function createLoggerMiddleware(options = {}) {
|
|
44
|
+
const opts = {
|
|
45
|
+
...defaultOptions,
|
|
46
|
+
...options
|
|
47
|
+
};
|
|
48
|
+
const logger = opts.logger ?? console;
|
|
49
|
+
return {
|
|
50
|
+
name: "logger",
|
|
51
|
+
priority: 0,
|
|
52
|
+
handler: async (context, next) => {
|
|
53
|
+
const { storeId, type, state, payload } = context;
|
|
54
|
+
if (opts.filter && !opts.filter(context)) {
|
|
55
|
+
await next();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const isStateChange = type.startsWith("state:");
|
|
59
|
+
const isAction = type.startsWith("action:");
|
|
60
|
+
if (isStateChange && !opts.logState || isAction && !opts.logAction) {
|
|
61
|
+
await next();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const timestamp = opts.timestamp ? formatTime(/* @__PURE__ */ new Date()) : "";
|
|
65
|
+
const prefix = `[Store: ${storeId}]`;
|
|
66
|
+
const startTime = performance.now();
|
|
67
|
+
const prevState = safeCloneState(state);
|
|
68
|
+
const groupTitle = timestamp ? `${timestamp} ${prefix} ${type}` : `${prefix} ${type}`;
|
|
69
|
+
const startGroup = () => {
|
|
70
|
+
if (opts.collapsed && typeof logger.groupCollapsed === "function") logger.groupCollapsed(`%c${groupTitle}`, LOG_COLORS.title);
|
|
71
|
+
else if (typeof logger.group === "function") logger.group(`%c${groupTitle}`, LOG_COLORS.title);
|
|
72
|
+
};
|
|
73
|
+
const endGroup = () => {
|
|
74
|
+
if (typeof logger.groupEnd === "function") logger.groupEnd();
|
|
75
|
+
};
|
|
76
|
+
startGroup();
|
|
77
|
+
try {
|
|
78
|
+
const logFn = logger[opts.level ?? "log"] ?? logger.log;
|
|
79
|
+
if (typeof logFn === "function") {
|
|
80
|
+
logFn.call(logger, "%c prev state", LOG_COLORS.prevState, prevState);
|
|
81
|
+
if (payload !== void 0) logFn.call(logger, "%c payload", LOG_COLORS.action, payload);
|
|
82
|
+
}
|
|
83
|
+
await next();
|
|
84
|
+
const endTime = performance.now();
|
|
85
|
+
if (typeof logFn === "function") {
|
|
86
|
+
logFn.call(logger, "%c next state", LOG_COLORS.nextState, safeCloneState(context.state));
|
|
87
|
+
logFn.call(logger, "%c duration", LOG_COLORS.duration, formatDuration(startTime, endTime));
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (typeof logger.error === "function") logger.error("%c error", LOG_COLORS.error, error);
|
|
91
|
+
throw new MiddlewareError(`\u65E5\u5FD7\u4E2D\u95F4\u4EF6\u6267\u884C\u9519\u8BEF: ${error instanceof Error ? error.message : String(error)}`, "logger", error instanceof Error ? error : void 0);
|
|
92
|
+
} finally {
|
|
93
|
+
endGroup();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createSimpleLoggerMiddleware(options = {}) {
|
|
99
|
+
const { includePayload = true, format, filter } = options;
|
|
100
|
+
return {
|
|
101
|
+
name: "simple-logger",
|
|
102
|
+
priority: 0,
|
|
103
|
+
handler: async (context, next) => {
|
|
104
|
+
const { storeId, type, payload } = context;
|
|
105
|
+
if (filter && !filter(context)) {
|
|
106
|
+
await next();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const message = format ? format(context) : `[Store: ${storeId}] ${type}`;
|
|
110
|
+
if (includePayload && payload !== void 0) console.info(message, payload);
|
|
111
|
+
else console.info(message);
|
|
112
|
+
await next();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function createProductionLoggerMiddleware(options = {}) {
|
|
117
|
+
const { onError } = options;
|
|
118
|
+
return {
|
|
119
|
+
name: "production-logger",
|
|
120
|
+
priority: 0,
|
|
121
|
+
handler: async (context, next) => {
|
|
122
|
+
try {
|
|
123
|
+
await next();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
126
|
+
if (onError) onError(err, context);
|
|
127
|
+
else console.error(`[Store Error] ${context.storeId}:`, err);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { createLoggerMiddleware, createProductionLoggerMiddleware, createSimpleLoggerMiddleware };
|
|
135
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
136
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../src/middleware/logger.ts"],"sourcesContent":["/**\n * 日志中间件\n *\n * 提供状态变化和 Action 调用的日志记录,支持多种日志级别和格式化选项\n * @module middleware/logger\n */\n\nimport type { LoggerMiddlewareOptions, Middleware, MiddlewareContext, StateTree } from '../types'\nimport { MiddlewareError } from '../errors'\nimport { deepClone } from '../utils'\n\n/**\n * 默认日志选项\n */\nconst defaultOptions: Required<Omit<LoggerMiddlewareOptions, 'logger' | 'filter'>> = {\n logState: true,\n logAction: true,\n collapsed: true,\n timestamp: true,\n level: 'log',\n}\n\n/**\n * 日志颜色配置\n */\nconst LOG_COLORS = {\n prevState: 'color: #9E9E9E; font-weight: bold',\n action: 'color: #03A9F4; font-weight: bold',\n nextState: 'color: #4CAF50; font-weight: bold',\n error: 'color: #F44336; font-weight: bold',\n duration: 'color: #9C27B0; font-weight: normal',\n title: 'color: inherit; font-weight: bold',\n} as const\n\n/**\n * 格式化时间戳\n *\n * @param date - 日期对象\n * @returns 格式化的时间字符串\n */\nfunction formatTime(date: Date): string {\n const hours = date.getHours().toString().padStart(2, '0')\n const minutes = date.getMinutes().toString().padStart(2, '0')\n const seconds = date.getSeconds().toString().padStart(2, '0')\n const ms = date.getMilliseconds().toString().padStart(3, '0')\n return `${hours}:${minutes}:${seconds}.${ms}`\n}\n\n/**\n * 格式化时间差\n *\n * @param startTime - 开始时间\n * @param endTime - 结束时间\n * @returns 格式化的时间差字符串\n */\nfunction formatDuration(startTime: number, endTime: number): string {\n const duration = endTime - startTime\n if (duration < 1) {\n return '< 1ms'\n }\n return `${duration.toFixed(2)}ms`\n}\n\n/**\n * 安全克隆状态(防止日志中的对象被修改)\n *\n * @param state - 状态对象\n * @returns 克隆后的状态\n */\nfunction safeCloneState<S extends StateTree>(state: S): S {\n try {\n return deepClone(state)\n }\n catch {\n // 如果克隆失败,返回浅拷贝\n return { ...state }\n }\n}\n\n/**\n * 创建日志中间件\n *\n * 提供完整的状态变化和 Action 调用日志记录,支持时间戳、分组和性能计时\n *\n * @param options - 日志选项\n * @returns 日志中间件\n *\n * @example\n * ```typescript\n * const logger = createLoggerMiddleware({\n * logState: true,\n * collapsed: true,\n * timestamp: true,\n * filter: (context) => context.storeId !== 'internal'\n * })\n *\n * middlewareManager.use(logger)\n * ```\n */\nexport function createLoggerMiddleware(\n options: LoggerMiddlewareOptions = {},\n): Middleware {\n const opts = { ...defaultOptions, ...options }\n const logger = opts.logger ?? console\n\n return {\n name: 'logger',\n priority: 0, // 最先执行,确保能记录所有操作\n\n handler: async (context: MiddlewareContext, next: () => void | Promise<void>) => {\n const { storeId, type, state, payload } = context\n\n // 应用自定义过滤器\n if (opts.filter && !opts.filter(context)) {\n await next()\n return\n }\n\n // 判断是否需要记录\n const isStateChange = type.startsWith('state:')\n const isAction = type.startsWith('action:')\n\n if ((isStateChange && !opts.logState) || (isAction && !opts.logAction)) {\n await next()\n return\n }\n\n const timestamp = opts.timestamp ? formatTime(new Date()) : ''\n const prefix = `[Store: ${storeId}]`\n const startTime = performance.now()\n\n // 保存操作前的状态\n const prevState = safeCloneState(state)\n\n // 开始分组\n const groupTitle = timestamp ? `${timestamp} ${prefix} ${type}` : `${prefix} ${type}`\n\n const startGroup = (): void => {\n if (opts.collapsed && typeof logger.groupCollapsed === 'function') {\n logger.groupCollapsed(`%c${groupTitle}`, LOG_COLORS.title)\n }\n else if (typeof logger.group === 'function') {\n logger.group(`%c${groupTitle}`, LOG_COLORS.title)\n }\n }\n\n const endGroup = (): void => {\n if (typeof logger.groupEnd === 'function') {\n logger.groupEnd()\n }\n }\n\n startGroup()\n\n try {\n // 记录操作前的状态\n const logFn = logger[opts.level ?? 'log'] ?? logger.log\n\n if (typeof logFn === 'function') {\n logFn.call(logger, '%c prev state', LOG_COLORS.prevState, prevState)\n\n if (payload !== undefined) {\n logFn.call(logger, '%c payload', LOG_COLORS.action, payload)\n }\n }\n\n // 执行下一个中间件\n await next()\n\n const endTime = performance.now()\n\n // 记录操作后的状态\n if (typeof logFn === 'function') {\n logFn.call(logger, '%c next state', LOG_COLORS.nextState, safeCloneState(context.state))\n logFn.call(logger, '%c duration', LOG_COLORS.duration, formatDuration(startTime, endTime))\n }\n }\n catch (error) {\n // 记录错误\n if (typeof logger.error === 'function') {\n logger.error('%c error', LOG_COLORS.error, error)\n }\n\n // 包装并重新抛出错误\n throw new MiddlewareError(\n `日志中间件执行错误: ${error instanceof Error ? error.message : String(error)}`,\n 'logger',\n error instanceof Error ? error : undefined,\n )\n }\n finally {\n endGroup()\n }\n },\n }\n}\n\n/**\n * 简单日志中间件(不使用分组,适合生产环境)\n *\n * @param options - 配置选项\n * @param options.includePayload - 是否输出 payload\n * @param options.format - 自定义日志格式化函数\n * @param options.filter - 日志过滤器\n * @returns 简单日志中间件\n *\n * @example\n * ```typescript\n * const logger = createSimpleLoggerMiddleware({\n * includePayload: true,\n * format: (ctx) => `${ctx.storeId}: ${ctx.type}`\n * })\n * ```\n */\nexport function createSimpleLoggerMiddleware(options: {\n /** 是否包含 payload */\n includePayload?: boolean\n /** 自定义格式化函数 */\n format?: (context: MiddlewareContext) => string\n /** 过滤器 */\n filter?: (context: MiddlewareContext) => boolean\n} = {}): Middleware {\n const { includePayload = true, format, filter } = options\n\n return {\n name: 'simple-logger',\n priority: 0,\n\n handler: async (context: MiddlewareContext, next: () => void | Promise<void>) => {\n const { storeId, type, payload } = context\n\n // 应用过滤器\n if (filter && !filter(context)) {\n await next()\n return\n }\n\n // 使用自定义格式化或默认格式\n const message = format\n ? format(context)\n : `[Store: ${storeId}] ${type}`\n\n if (includePayload && payload !== undefined) {\n console.info(message, payload)\n }\n else {\n console.info(message)\n }\n\n await next()\n },\n }\n}\n\n/**\n * 创建生产环境日志中间件\n *\n * 只记录错误和警告,不记录正常状态变化\n *\n * @param options - 配置选项\n * @param options.onError - 自定义错误处理器\n * @returns 生产环境日志中间件\n */\nexport function createProductionLoggerMiddleware(options: {\n /** 自定义错误处理器 */\n onError?: (error: Error, context: MiddlewareContext) => void\n} = {}): Middleware {\n const { onError } = options\n\n return {\n name: 'production-logger',\n priority: 0,\n\n handler: async (context: MiddlewareContext, next: () => void | Promise<void>) => {\n try {\n await next()\n }\n catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n\n if (onError) {\n onError(err, context)\n }\n else {\n console.error(`[Store Error] ${context.storeId}:`, err)\n }\n\n throw error\n }\n },\n }\n}\n\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中间件管理器
|
|
3
|
+
*
|
|
4
|
+
* @module middleware/middleware-manager
|
|
5
|
+
*/
|
|
6
|
+
import type { MiddlewareManager } from '../types';
|
|
7
|
+
/**
|
|
8
|
+
* 创建中间件管理器
|
|
9
|
+
*
|
|
10
|
+
* @returns 中间件管理器实例
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const manager = createMiddlewareManager()
|
|
15
|
+
*
|
|
16
|
+
* // 添加日志中间件
|
|
17
|
+
* manager.use({
|
|
18
|
+
* name: 'logger',
|
|
19
|
+
* priority: 1,
|
|
20
|
+
* handler: async (ctx, next) => {
|
|
21
|
+
* console.log('Before:', ctx.type)
|
|
22
|
+
* await next()
|
|
23
|
+
* console.log('After:', ctx.type)
|
|
24
|
+
* }
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* // 执行中间件链
|
|
28
|
+
* await manager.execute({ storeId: 'test', state: {}, type: 'state:patch' })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function createMiddlewareManager(): MiddlewareManager;
|
|
32
|
+
/**
|
|
33
|
+
* 获取全局中间件管理器
|
|
34
|
+
*/
|
|
35
|
+
export declare function getGlobalMiddlewareManager(): MiddlewareManager;
|
|
36
|
+
/**
|
|
37
|
+
* 设置全局中间件管理器
|
|
38
|
+
*/
|
|
39
|
+
export declare function setGlobalMiddlewareManager(manager: MiddlewareManager): void;
|
|
40
|
+
//# sourceMappingURL=middleware-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-manager.d.ts","sourceRoot":"","sources":["../../src/middleware/middleware-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAiC,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEhF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAoH3D;AAOD;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,iBAAiB,CAK9D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAE3E"}
|
|
@@ -0,0 +1,79 @@
|
|
|
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/middleware/middleware-manager.ts
|
|
11
|
+
function createMiddlewareManager() {
|
|
12
|
+
const middlewares = /* @__PURE__ */ new Map();
|
|
13
|
+
let sortedMiddlewares = null;
|
|
14
|
+
const getSortedMiddlewares = () => {
|
|
15
|
+
if (sortedMiddlewares === null) sortedMiddlewares = Array.from(middlewares.values()).sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
16
|
+
return sortedMiddlewares;
|
|
17
|
+
};
|
|
18
|
+
const invalidateCache = () => {
|
|
19
|
+
sortedMiddlewares = null;
|
|
20
|
+
};
|
|
21
|
+
const use = (middleware) => {
|
|
22
|
+
if (middlewares.has(middleware.name)) console.warn(`[Middleware] \u4E2D\u95F4\u4EF6 "${middleware.name}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u8986\u76D6`);
|
|
23
|
+
middlewares.set(middleware.name, middleware);
|
|
24
|
+
invalidateCache();
|
|
25
|
+
};
|
|
26
|
+
const remove = (name) => {
|
|
27
|
+
if (middlewares.delete(name)) invalidateCache();
|
|
28
|
+
};
|
|
29
|
+
const get = (name) => {
|
|
30
|
+
return middlewares.get(name);
|
|
31
|
+
};
|
|
32
|
+
const has = (name) => {
|
|
33
|
+
return middlewares.has(name);
|
|
34
|
+
};
|
|
35
|
+
const execute = async (context) => {
|
|
36
|
+
const sorted = getSortedMiddlewares();
|
|
37
|
+
if (sorted.length === 0) return;
|
|
38
|
+
const dispatch = async (index) => {
|
|
39
|
+
const middleware = sorted[index];
|
|
40
|
+
if (!middleware) return;
|
|
41
|
+
let called = false;
|
|
42
|
+
const next = async () => {
|
|
43
|
+
if (called) throw new Error(`[Middleware] next() called multiple times in "${middleware.name}"`);
|
|
44
|
+
called = true;
|
|
45
|
+
await dispatch(index + 1);
|
|
46
|
+
};
|
|
47
|
+
await middleware.handler(context, next);
|
|
48
|
+
};
|
|
49
|
+
await dispatch(0);
|
|
50
|
+
};
|
|
51
|
+
const getAll = () => {
|
|
52
|
+
return getSortedMiddlewares();
|
|
53
|
+
};
|
|
54
|
+
const clear = () => {
|
|
55
|
+
middlewares.clear();
|
|
56
|
+
invalidateCache();
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
use,
|
|
60
|
+
remove,
|
|
61
|
+
get,
|
|
62
|
+
has,
|
|
63
|
+
execute,
|
|
64
|
+
getAll,
|
|
65
|
+
clear
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
let globalMiddlewareManager = null;
|
|
69
|
+
function getGlobalMiddlewareManager() {
|
|
70
|
+
if (!globalMiddlewareManager) globalMiddlewareManager = createMiddlewareManager();
|
|
71
|
+
return globalMiddlewareManager;
|
|
72
|
+
}
|
|
73
|
+
function setGlobalMiddlewareManager(manager) {
|
|
74
|
+
globalMiddlewareManager = manager;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createMiddlewareManager, getGlobalMiddlewareManager, setGlobalMiddlewareManager };
|
|
78
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
79
|
+
//# sourceMappingURL=middleware-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-manager.js","names":[],"sources":["../../src/middleware/middleware-manager.ts"],"sourcesContent":["/**\n * 中间件管理器\n *\n * @module middleware/middleware-manager\n */\n\nimport type { Middleware, MiddlewareContext, MiddlewareManager } from '../types'\n\n/**\n * 创建中间件管理器\n *\n * @returns 中间件管理器实例\n *\n * @example\n * ```typescript\n * const manager = createMiddlewareManager()\n *\n * // 添加日志中间件\n * manager.use({\n * name: 'logger',\n * priority: 1,\n * handler: async (ctx, next) => {\n * console.log('Before:', ctx.type)\n * await next()\n * console.log('After:', ctx.type)\n * }\n * })\n *\n * // 执行中间件链\n * await manager.execute({ storeId: 'test', state: {}, type: 'state:patch' })\n * ```\n */\nexport function createMiddlewareManager(): MiddlewareManager {\n /** 中间件列表 */\n const middlewares = new Map<string, Middleware>()\n\n /** 已排序的中间件列表缓存 */\n let sortedMiddlewares: Middleware[] | null = null\n\n /**\n * 获取排序后的中间件列表\n */\n const getSortedMiddlewares = (): Middleware[] => {\n if (sortedMiddlewares === null) {\n sortedMiddlewares = Array.from(middlewares.values()).sort(\n (a, b) => (a.priority ?? 100) - (b.priority ?? 100),\n )\n }\n return sortedMiddlewares\n }\n\n /**\n * 使中间件缓存失效\n */\n const invalidateCache = (): void => {\n sortedMiddlewares = null\n }\n\n /**\n * 添加中间件\n */\n const use = (middleware: Middleware): void => {\n if (middlewares.has(middleware.name)) {\n console.warn(`[Middleware] 中间件 \"${middleware.name}\" 已存在,将被覆盖`)\n }\n middlewares.set(middleware.name, middleware)\n invalidateCache()\n }\n\n /**\n * 移除中间件\n */\n const remove = (name: string): void => {\n if (middlewares.delete(name)) {\n invalidateCache()\n }\n }\n\n /**\n * 获取中间件\n */\n const get = (name: string): Middleware | undefined => {\n return middlewares.get(name)\n }\n\n /**\n * 是否存在中间件\n */\n const has = (name: string): boolean => {\n return middlewares.has(name)\n }\n\n /**\n * 执行中间件链\n */\n const execute = async (context: MiddlewareContext): Promise<void> => {\n const sorted = getSortedMiddlewares()\n\n if (sorted.length === 0) {\n return\n }\n\n const dispatch = async (index: number): Promise<void> => {\n const middleware = sorted[index]\n if (!middleware) {\n return\n }\n\n let called = false\n const next = async (): Promise<void> => {\n if (called) {\n throw new Error(`[Middleware] next() called multiple times in \"${middleware.name}\"`)\n }\n\n called = true\n await dispatch(index + 1)\n }\n\n await middleware.handler(context, next)\n }\n\n await dispatch(0)\n }\n\n /**\n * 获取所有中间件\n */\n const getAll = (): Middleware[] => {\n return getSortedMiddlewares()\n }\n\n /**\n * 清空所有中间件\n */\n const clear = (): void => {\n middlewares.clear()\n invalidateCache()\n }\n\n return {\n use,\n remove,\n get,\n has,\n execute,\n getAll,\n clear,\n }\n}\n\n/**\n * 全局中间件管理器\n */\nlet globalMiddlewareManager: MiddlewareManager | null = null\n\n/**\n * 获取全局中间件管理器\n */\nexport function getGlobalMiddlewareManager(): MiddlewareManager {\n if (!globalMiddlewareManager) {\n globalMiddlewareManager = createMiddlewareManager()\n }\n return globalMiddlewareManager\n}\n\n/**\n * 设置全局中间件管理器\n */\nexport function setGlobalMiddlewareManager(manager: MiddlewareManager): void {\n globalMiddlewareManager = manager\n}\n\n"],"mappings":";;;;;;;;;;AAgCO,SAAS,0BAA6C;CAE3D,MAAM,8BAAc,IAAI,IAAwB;CAGhD,IAAI,oBAAyC;CAK7C,MAAM,6BAA2C;EAC/C,IAAI,sBAAsB,MACxB,oBAAoB,MAAM,KAAK,YAAY,OAAO,CAAC,CAAA,CAAE,MAClD,GAAG,OAAO,EAAE,YAAY,QAAQ,EAAE,YAAY,IACjD;EAEF,OAAO;CACT;CAKA,MAAM,wBAA8B;EAClC,oBAAoB;CACtB;CAKA,MAAM,OAAO,eAAiC;EAC5C,IAAI,YAAY,IAAI,WAAW,IAAI,GACjC,QAAQ,KAAK,oCAAqB,WAAW,KAAI,mDAAY;EAE/D,YAAY,IAAI,WAAW,MAAM,UAAU;EAC3C,gBAAgB;CAClB;CAKA,MAAM,UAAU,SAAuB;EACrC,IAAI,YAAY,OAAO,IAAI,GACzB,gBAAgB;CAEpB;CAKA,MAAM,OAAO,SAAyC;EACpD,OAAO,YAAY,IAAI,IAAI;CAC7B;CAKA,MAAM,OAAO,SAA0B;EACrC,OAAO,YAAY,IAAI,IAAI;CAC7B;CAKA,MAAM,UAAU,OAAO,YAA8C;EACnE,MAAM,SAAS,qBAAqB;EAEpC,IAAI,OAAO,WAAW,GACpB;EAGF,MAAM,WAAW,OAAO,UAAiC;GACvD,MAAM,aAAa,OAAO;GAC1B,IAAI,CAAC,YACH;GAGF,IAAI,SAAS;GACb,MAAM,OAAO,YAA2B;IACtC,IAAI,QACF,MAAM,IAAI,MAAM,iDAAiD,WAAW,KAAI,EAAG;IAGrF,SAAS;IACT,MAAM,SAAS,QAAQ,CAAC;GAC1B;GAEA,MAAM,WAAW,QAAQ,SAAS,IAAI;EACxC;EAEA,MAAM,SAAS,CAAC;CAClB;CAKA,MAAM,eAA6B;EACjC,OAAO,qBAAqB;CAC9B;CAKA,MAAM,cAAoB;EACxB,YAAY,MAAM;EAClB,gBAAgB;CAClB;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;AAKA,IAAI,0BAAoD;AAKjD,SAAS,6BAAgD;CAC9D,IAAI,CAAC,yBACH,0BAA0B,wBAAwB;CAEpD,OAAO;AACT;AAKO,SAAS,2BAA2B,SAAkC;CAC3E,0BAA0B;AAC5B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 持久化中间件
|
|
3
|
+
*
|
|
4
|
+
* 提供状态持久化功能
|
|
5
|
+
* @module middleware/persist
|
|
6
|
+
*/
|
|
7
|
+
import type { Middleware, PersistMiddlewareOptions, StateTree, StoreId } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* 创建持久化中间件
|
|
10
|
+
*
|
|
11
|
+
* @param options - 持久化选项
|
|
12
|
+
* @returns 持久化中间件
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const persist = createPersistMiddleware({
|
|
17
|
+
* storage: 'localStorage',
|
|
18
|
+
* stores: ['user', 'settings'],
|
|
19
|
+
* debounce: 200,
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* middlewareManager.use(persist)
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createPersistMiddleware(options?: PersistMiddlewareOptions): Middleware;
|
|
26
|
+
/**
|
|
27
|
+
* 从存储恢复状态
|
|
28
|
+
*
|
|
29
|
+
* @param storeId - Store ID
|
|
30
|
+
* @param options - 持久化选项
|
|
31
|
+
* @returns 恢复的状态或 null
|
|
32
|
+
*/
|
|
33
|
+
export declare function restoreState<S extends StateTree>(storeId: StoreId, options?: PersistMiddlewareOptions): S | null;
|
|
34
|
+
//# sourceMappingURL=persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/middleware/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAqB,wBAAwB,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAgC3G;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,UAAU,CAgGZ;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,EAC9C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,wBAA6B,GACrC,CAAC,GAAG,IAAI,CAkBV"}
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { debounce } from "../utils/debounce-throttle.js";
|
|
11
|
+
//#region src/middleware/persist.ts
|
|
12
|
+
const defaultOptions = {
|
|
13
|
+
keyPrefix: "ldesign-store:",
|
|
14
|
+
storage: "localStorage",
|
|
15
|
+
debounce: 100,
|
|
16
|
+
serialize: JSON.stringify,
|
|
17
|
+
deserialize: JSON.parse
|
|
18
|
+
};
|
|
19
|
+
function resolveOptions(options) {
|
|
20
|
+
return {
|
|
21
|
+
...defaultOptions,
|
|
22
|
+
...options
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function getStorage(type) {
|
|
26
|
+
if (typeof window === "undefined") return null;
|
|
27
|
+
return type === "localStorage" ? localStorage : sessionStorage;
|
|
28
|
+
}
|
|
29
|
+
function createPersistMiddleware(options = {}) {
|
|
30
|
+
const opts = resolveOptions(options);
|
|
31
|
+
const storage = getStorage(opts.storage);
|
|
32
|
+
const debouncedSave = /* @__PURE__ */ new Map();
|
|
33
|
+
const getKey = (storeId) => {
|
|
34
|
+
return `${opts.keyPrefix}${storeId}`;
|
|
35
|
+
};
|
|
36
|
+
const filterState = (storeId, state) => {
|
|
37
|
+
const paths = opts.paths?.[storeId];
|
|
38
|
+
if (!paths || paths.length === 0) return state;
|
|
39
|
+
const filtered = {};
|
|
40
|
+
for (const path of paths) {
|
|
41
|
+
const keys = path.split(".");
|
|
42
|
+
let source = state;
|
|
43
|
+
let target = filtered;
|
|
44
|
+
for (let i = 0; i < keys.length; i++) {
|
|
45
|
+
const key = keys[i];
|
|
46
|
+
if (i === keys.length - 1) target[key] = source?.[key];
|
|
47
|
+
else {
|
|
48
|
+
target[key] = target[key] || {};
|
|
49
|
+
target = target[key];
|
|
50
|
+
source = source?.[key];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return filtered;
|
|
55
|
+
};
|
|
56
|
+
const saveState = (storeId, state) => {
|
|
57
|
+
if (!storage) return;
|
|
58
|
+
try {
|
|
59
|
+
const filteredState = filterState(storeId, state);
|
|
60
|
+
const serialized = opts.serialize(filteredState);
|
|
61
|
+
storage.setItem(getKey(storeId), serialized);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`[Persist] \u4FDD\u5B58\u72B6\u6001\u5931\u8D25 (${storeId}):`, error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const getDebouncedSave = (storeId) => {
|
|
67
|
+
let fn = debouncedSave.get(storeId);
|
|
68
|
+
if (!fn) {
|
|
69
|
+
fn = debounce((state) => saveState(storeId, state), opts.debounce);
|
|
70
|
+
debouncedSave.set(storeId, fn);
|
|
71
|
+
}
|
|
72
|
+
return fn;
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
name: "persist",
|
|
76
|
+
priority: 100,
|
|
77
|
+
handler: async (context, next) => {
|
|
78
|
+
await next();
|
|
79
|
+
const { storeId, type, state } = context;
|
|
80
|
+
if (!type.startsWith("state:")) return;
|
|
81
|
+
if (opts.stores && !opts.stores.includes(storeId)) return;
|
|
82
|
+
getDebouncedSave(storeId)(state);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function restoreState(storeId, options = {}) {
|
|
87
|
+
const opts = resolveOptions(options);
|
|
88
|
+
const storage = getStorage(opts.storage);
|
|
89
|
+
if (!storage) return null;
|
|
90
|
+
try {
|
|
91
|
+
const key = `${opts.keyPrefix}${storeId}`;
|
|
92
|
+
const data = storage.getItem(key);
|
|
93
|
+
if (data) return opts.deserialize(data);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(`[Persist] \u6062\u590D\u72B6\u6001\u5931\u8D25 (${storeId}):`, error);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { createPersistMiddleware, restoreState };
|
|
101
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
102
|
+
//# sourceMappingURL=persist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.js","names":[],"sources":["../../src/middleware/persist.ts"],"sourcesContent":["/**\n * 持久化中间件\n *\n * 提供状态持久化功能\n * @module middleware/persist\n */\n\nimport type { Middleware, MiddlewareContext, PersistMiddlewareOptions, StateTree, StoreId } from '../types'\nimport { debounce } from '../utils/debounce-throttle'\n\ntype ResolvedPersistMiddlewareOptions = Required<\n Pick<PersistMiddlewareOptions, 'debounce' | 'deserialize' | 'keyPrefix' | 'serialize' | 'storage'>\n> & Omit<PersistMiddlewareOptions, 'debounce' | 'deserialize' | 'keyPrefix' | 'serialize' | 'storage'>\n\n/**\n * 默认持久化选项\n */\nconst defaultOptions: ResolvedPersistMiddlewareOptions = {\n keyPrefix: 'ldesign-store:',\n storage: 'localStorage',\n debounce: 100,\n serialize: JSON.stringify,\n deserialize: JSON.parse,\n}\n\nfunction resolveOptions(options: PersistMiddlewareOptions): ResolvedPersistMiddlewareOptions {\n return { ...defaultOptions, ...options }\n}\n\n/**\n * 获取存储对象\n */\nfunction getStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {\n if (typeof window === 'undefined') {\n return null\n }\n return type === 'localStorage' ? localStorage : sessionStorage\n}\n\n/**\n * 创建持久化中间件\n *\n * @param options - 持久化选项\n * @returns 持久化中间件\n *\n * @example\n * ```typescript\n * const persist = createPersistMiddleware({\n * storage: 'localStorage',\n * stores: ['user', 'settings'],\n * debounce: 200,\n * })\n *\n * middlewareManager.use(persist)\n * ```\n */\nexport function createPersistMiddleware(\n options: PersistMiddlewareOptions = {},\n): Middleware {\n const opts = resolveOptions(options)\n const storage = getStorage(opts.storage)\n\n /** 保存状态的防抖函数映射 */\n const debouncedSave = new Map<StoreId, (state: StateTree) => void>()\n\n /**\n * 获取存储键名\n */\n const getKey = (storeId: StoreId): string => {\n return `${opts.keyPrefix}${storeId}`\n }\n\n /**\n * 筛选需要持久化的状态路径\n */\n const filterState = (storeId: StoreId, state: StateTree): StateTree => {\n const paths = opts.paths?.[storeId]\n if (!paths || paths.length === 0) {\n return state\n }\n\n const filtered: StateTree = {}\n for (const path of paths) {\n const keys = path.split('.')\n let source: unknown = state\n let target: StateTree = filtered\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i]\n if (i === keys.length - 1) {\n target[key] = (source as StateTree)?.[key]\n }\n else {\n target[key] = target[key] || {}\n target = target[key] as StateTree\n source = (source as StateTree)?.[key]\n }\n }\n }\n\n return filtered\n }\n\n /**\n * 保存状态到存储\n */\n const saveState = (storeId: StoreId, state: StateTree): void => {\n if (!storage) return\n\n try {\n const filteredState = filterState(storeId, state)\n const serialized = opts.serialize(filteredState)\n storage.setItem(getKey(storeId), serialized)\n }\n catch (error) {\n console.error(`[Persist] 保存状态失败 (${storeId}):`, error)\n }\n }\n\n /**\n * 获取或创建防抖保存函数\n */\n const getDebouncedSave = (storeId: StoreId): (state: StateTree) => void => {\n let fn = debouncedSave.get(storeId)\n if (!fn) {\n fn = debounce((state: StateTree) => saveState(storeId, state), opts.debounce)\n debouncedSave.set(storeId, fn)\n }\n return fn\n }\n\n return {\n name: 'persist',\n priority: 100, // 最后执行\n\n handler: async (context: MiddlewareContext, next: () => void | Promise<void>) => {\n await next()\n\n const { storeId, type, state } = context\n\n // 只在状态变化时持久化\n if (!type.startsWith('state:')) {\n return\n }\n\n // 检查是否需要持久化此 Store\n if (opts.stores && !opts.stores.includes(storeId)) {\n return\n }\n\n // 使用防抖保存\n getDebouncedSave(storeId)(state)\n },\n }\n}\n\n/**\n * 从存储恢复状态\n *\n * @param storeId - Store ID\n * @param options - 持久化选项\n * @returns 恢复的状态或 null\n */\nexport function restoreState<S extends StateTree>(\n storeId: StoreId,\n options: PersistMiddlewareOptions = {},\n): S | null {\n const opts = resolveOptions(options)\n const storage = getStorage(opts.storage)\n\n if (!storage) return null\n\n try {\n const key = `${opts.keyPrefix}${storeId}`\n const data = storage.getItem(key)\n if (data) {\n return opts.deserialize(data) as S\n }\n }\n catch (error) {\n console.error(`[Persist] 恢复状态失败 (${storeId}):`, error)\n }\n\n return null\n}\n\n"],"mappings":";;;;;;;;;;;AAiBA,MAAM,iBAAmD;CACvD,WAAW;CACX,SAAS;CACT,UAAU;CACV,WAAW,KAAK;CAChB,aAAa,KAAK;AACpB;AAEA,SAAS,eAAe,SAAqE;CAC3F,OAAO;EAAE,GAAG;EAAgB,GAAG;CAAQ;AACzC;AAKA,SAAS,WAAW,MAAyD;CAC3E,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,OAAO,SAAS,iBAAiB,eAAe;AAClD;AAmBO,SAAS,wBACd,UAAoC,CAAC,GACzB;CACZ,MAAM,OAAO,eAAe,OAAO;CACnC,MAAM,UAAU,WAAW,KAAK,OAAO;CAGvC,MAAM,gCAAgB,IAAI,IAAyC;CAKnE,MAAM,UAAU,YAA6B;EAC3C,OAAO,GAAG,KAAK,YAAY;CAC7B;CAKA,MAAM,eAAe,SAAkB,UAAgC;EACrE,MAAM,QAAQ,KAAK,QAAQ;EAC3B,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B,OAAO;EAGT,MAAM,WAAsB,CAAC;EAC7B,KAAA,MAAW,QAAQ,OAAO;GACxB,MAAM,OAAO,KAAK,MAAM,GAAG;GAC3B,IAAI,SAAkB;GACtB,IAAI,SAAoB;GAExB,KAAA,IAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;IACpC,MAAM,MAAM,KAAK;IACjB,IAAI,MAAM,KAAK,SAAS,GACtB,OAAO,OAAQ,SAAuB;SAEnC;KACH,OAAO,OAAO,OAAO,QAAQ,CAAC;KAC9B,SAAS,OAAO;KAChB,SAAU,SAAuB;IACnC;GACF;EACF;EAEA,OAAO;CACT;CAKA,MAAM,aAAa,SAAkB,UAA2B;EAC9D,IAAI,CAAC,SAAS;EAEd,IAAI;GACF,MAAM,gBAAgB,YAAY,SAAS,KAAK;GAChD,MAAM,aAAa,KAAK,UAAU,aAAa;GAC/C,QAAQ,QAAQ,OAAO,OAAO,GAAG,UAAU;EAC7C,SACO,OAAO;GACZ,QAAQ,MAAM,mDAAqB,QAAO,KAAM,KAAK;EACvD;CACF;CAKA,MAAM,oBAAoB,YAAiD;EACzE,IAAI,KAAK,cAAc,IAAI,OAAO;EAClC,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,UAAqB,UAAU,SAAS,KAAK,GAAG,KAAK,QAAQ;GAC5E,cAAc,IAAI,SAAS,EAAE;EAC/B;EACA,OAAO;CACT;CAEA,OAAO;EACL,MAAM;EACN,UAAU;EAEV,SAAS,OAAO,SAA4B,SAAqC;GAC/E,MAAM,KAAK;GAEX,MAAM,EAAE,SAAS,MAAM,UAAU;GAGjC,IAAI,CAAC,KAAK,WAAW,QAAQ,GAC3B;GAIF,IAAI,KAAK,UAAU,CAAC,KAAK,OAAO,SAAS,OAAO,GAC9C;GAIF,iBAAiB,OAAO,CAAA,CAAE,KAAK;EACjC;CACF;AACF;AASO,SAAS,aACd,SACA,UAAoC,CAAC,GAC3B;CACV,MAAM,OAAO,eAAe,OAAO;CACnC,MAAM,UAAU,WAAW,KAAK,OAAO;CAEvC,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI;EACF,MAAM,MAAM,GAAG,KAAK,YAAY;EAChC,MAAM,OAAO,QAAQ,QAAQ,GAAG;EAChC,IAAI,MACF,OAAO,KAAK,YAAY,IAAI;CAEhC,SACO,OAAO;EACZ,QAAQ,MAAM,mDAAqB,QAAO,KAAM,KAAK;CACvD;CAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Middleware, // 状态快照接口
|
|
2
|
+
StateTree, // 状态树类型
|
|
3
|
+
StoreId, // Store 标识类型
|
|
4
|
+
TimeTravelController, // 时间旅行控制器接口
|
|
5
|
+
TimeTravelMiddlewareOptions } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* 创建时间旅行中间件
|
|
8
|
+
*
|
|
9
|
+
* 返回一个中间件函数和一个控制器对象:
|
|
10
|
+
* - 中间件负责拦截 state: 类型的操作,在操作前后记录状态快照
|
|
11
|
+
* - 控制器提供 undo/redo/goto/clear 等方法,用于在历史快照之间跳转
|
|
12
|
+
*
|
|
13
|
+
* @param options - 中间件配置选项(可选,与默认值合并)
|
|
14
|
+
* @returns 包含 middleware(中间件)和 controller(控制器)的对象
|
|
15
|
+
*/
|
|
16
|
+
export declare function createTimeTravelMiddleware(options?: TimeTravelMiddlewareOptions): {
|
|
17
|
+
middleware: Middleware;
|
|
18
|
+
controller: TimeTravelController;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 外部设置恢复回调的辅助函数
|
|
22
|
+
* 将外部提供的状态恢复函数注入到时间旅行控制器中
|
|
23
|
+
*
|
|
24
|
+
* 使用场景:Store 运行时初始化时调用此函数,将控制器与 Store 的状态恢复逻辑连接
|
|
25
|
+
*
|
|
26
|
+
* @param controller - 时间旅行控制器实例
|
|
27
|
+
* @param callback - 恢复回调函数,接收 storeId 和已深拷贝的状态树
|
|
28
|
+
*/
|
|
29
|
+
export declare function setRestoreCallback(controller: TimeTravelController, callback: (storeId: StoreId, state: StateTree) => void): void;
|
|
30
|
+
//# sourceMappingURL=time-travel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-travel.d.ts","sourceRoot":"","sources":["../../src/middleware/time-travel.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,UAAU,EAEkB,SAAS;AACrC,SAAS,EAAmB,QAAQ;AACpC,OAAO,EAAqB,aAAa;AACzC,oBAAoB,EAAQ,YAAY;AACxC,2BAA2B,EAC5B,MAAM,UAAU,CAAA;AA+BjB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,GAAE,2BAAgC,GACxC;IACD,UAAU,EAAE,UAAU,CAAA;IACtB,UAAU,EAAE,oBAAoB,CAAA;CACjC,CA2NA;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,oBAAoB,EAChC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,GACrD,IAAI,CAYN"}
|
|
@@ -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
|
+
import { deepClone } from "../utils/deep-clone.js";
|
|
11
|
+
//#region src/middleware/time-travel.ts
|
|
12
|
+
const defaultOptions = {
|
|
13
|
+
maxHistory: 100,
|
|
14
|
+
enabled: true
|
|
15
|
+
};
|
|
16
|
+
function createTimeTravelMiddleware(options = {}) {
|
|
17
|
+
const opts = {
|
|
18
|
+
...defaultOptions,
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
const maxHistory = opts.maxHistory ?? defaultOptions.maxHistory ?? 100;
|
|
22
|
+
const history = [];
|
|
23
|
+
let currentIndex = -1;
|
|
24
|
+
let isTraveling = false;
|
|
25
|
+
let restoreCallback = () => {};
|
|
26
|
+
const addSnapshot = (snapshot) => {
|
|
27
|
+
if (!opts.enabled || isTraveling) return;
|
|
28
|
+
if (currentIndex < history.length - 1) history.splice(currentIndex + 1);
|
|
29
|
+
history.push(snapshot);
|
|
30
|
+
currentIndex = history.length - 1;
|
|
31
|
+
if (history.length > maxHistory) {
|
|
32
|
+
history.shift();
|
|
33
|
+
currentIndex--;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const controller = {
|
|
37
|
+
/**
|
|
38
|
+
* 撤销操作
|
|
39
|
+
* 将当前指针前移一步,恢复到上一个历史快照状态
|
|
40
|
+
*/
|
|
41
|
+
undo: () => {
|
|
42
|
+
if (!controller.canUndo()) return;
|
|
43
|
+
isTraveling = true;
|
|
44
|
+
currentIndex--;
|
|
45
|
+
const snapshot = history[currentIndex];
|
|
46
|
+
restoreCallback(snapshot.storeId, deepClone(snapshot.state));
|
|
47
|
+
isTraveling = false;
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* 重做操作
|
|
51
|
+
* 将当前指针后移一步,恢复到下一个历史快照状态
|
|
52
|
+
*/
|
|
53
|
+
redo: () => {
|
|
54
|
+
if (!controller.canRedo()) return;
|
|
55
|
+
isTraveling = true;
|
|
56
|
+
currentIndex++;
|
|
57
|
+
const snapshot = history[currentIndex];
|
|
58
|
+
restoreCallback(snapshot.storeId, deepClone(snapshot.state));
|
|
59
|
+
isTraveling = false;
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* 跳转到指定历史索引
|
|
63
|
+
* 将当前指针直接移动到指定位置,恢复对应的快照状态
|
|
64
|
+
*
|
|
65
|
+
* @param index - 目标历史索引(0-based)
|
|
66
|
+
*/
|
|
67
|
+
goto: (index) => {
|
|
68
|
+
if (index < 0 || index >= history.length) return;
|
|
69
|
+
isTraveling = true;
|
|
70
|
+
currentIndex = index;
|
|
71
|
+
const snapshot = history[currentIndex];
|
|
72
|
+
restoreCallback(snapshot.storeId, deepClone(snapshot.state));
|
|
73
|
+
isTraveling = false;
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* 获取完整的快照历史(返回深拷贝,防止外部修改内部历史记录)
|
|
77
|
+
* @returns 历史快照数组的深拷贝
|
|
78
|
+
*/
|
|
79
|
+
getHistory: () => deepClone(history),
|
|
80
|
+
/**
|
|
81
|
+
* 获取当前快照指针索引
|
|
82
|
+
* @returns 当前索引值,-1 表示无快照
|
|
83
|
+
*/
|
|
84
|
+
getCurrentIndex: () => currentIndex,
|
|
85
|
+
/**
|
|
86
|
+
* 判断是否可以撤销
|
|
87
|
+
* 当前索引大于 0 时,说明前面还有历史快照
|
|
88
|
+
* @returns 是否可以执行 undo 操作
|
|
89
|
+
*/
|
|
90
|
+
canUndo: () => currentIndex > 0,
|
|
91
|
+
/**
|
|
92
|
+
* 判断是否可以重做
|
|
93
|
+
* 当前索引小于历史长度 - 1 时,说明后面还有快照
|
|
94
|
+
* @returns 是否可以执行 redo 操作
|
|
95
|
+
*/
|
|
96
|
+
canRedo: () => currentIndex < history.length - 1,
|
|
97
|
+
/**
|
|
98
|
+
* 清除所有历史快照并重置指针
|
|
99
|
+
* 通常在 Store 销毁或需要重置历史时调用
|
|
100
|
+
*/
|
|
101
|
+
clear: () => {
|
|
102
|
+
history.length = 0;
|
|
103
|
+
currentIndex = -1;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
controller.__setRestoreCallback = (callback) => {
|
|
107
|
+
restoreCallback = callback;
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
middleware: {
|
|
111
|
+
name: "time-travel",
|
|
112
|
+
priority: 50,
|
|
113
|
+
/**
|
|
114
|
+
* 中间件处理函数
|
|
115
|
+
* 只拦截 "state:" 类型的操作(状态变更操作),其他类型直接放行
|
|
116
|
+
* 正在时间旅行时也不记录快照,避免循环
|
|
117
|
+
*
|
|
118
|
+
* @param context - 中间件上下文,包含 storeId、操作类型、状态等信息
|
|
119
|
+
* @param next - 调用下一个中间件或最终操作的函数
|
|
120
|
+
*/
|
|
121
|
+
handler: async (context, next) => {
|
|
122
|
+
const { storeId, type, state } = context;
|
|
123
|
+
if (!type.startsWith("state:") || isTraveling) {
|
|
124
|
+
await next();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const snapshot = {
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
state: deepClone(state),
|
|
130
|
+
type,
|
|
131
|
+
storeId
|
|
132
|
+
};
|
|
133
|
+
await next();
|
|
134
|
+
addSnapshot(snapshot);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
controller
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function setRestoreCallback(controller, callback) {
|
|
141
|
+
const controllerWithSetter = controller;
|
|
142
|
+
if (typeof controllerWithSetter.__setRestoreCallback === "function") {
|
|
143
|
+
controllerWithSetter.__setRestoreCallback(callback);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
console.warn("[TimeTravel] 请使用 createTimeTravelMiddleware 返回的 controller");
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
export { createTimeTravelMiddleware, setRestoreCallback };
|
|
150
|
+
/*! End of @composy/store-core | Powered by @composy/builder */
|
|
151
|
+
//# sourceMappingURL=time-travel.js.map
|