@coaction/history 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @coaction/history
2
2
 
3
- ![Node CI](https://github.com/unadlib/coaction/workflows/Node%20CI/badge.svg)
3
+ ![Node CI](https://github.com/coactionjs/coaction/workflows/Node%20CI/badge.svg)
4
4
  [![npm](https://img.shields.io/npm/v/@coaction/history.svg)](https://www.npmjs.com/package/@coaction/history)
5
5
  ![license](https://img.shields.io/npm/l/@coaction/history)
6
6
 
@@ -40,4 +40,4 @@ store.getState().increment();
40
40
 
41
41
  ## Documentation
42
42
 
43
- You can find the documentation [here](https://github.com/unadlib/coaction).
43
+ You can find the documentation [here](https://github.com/coactionjs/coaction).
package/dist/index.d.mts CHANGED
@@ -1,18 +1,19 @@
1
- import { Middleware } from 'coaction';
1
+ import { Middleware } from "coaction";
2
2
 
3
+ //#region packages/coaction-history/src/index.d.ts
3
4
  type HistoryOptions<T extends object> = {
4
- limit?: number;
5
- partialize?: (state: T) => object;
5
+ limit?: number;
6
+ partialize?: (state: T) => object;
6
7
  };
7
- type HistoryApi<T extends object> = {
8
- undo: () => boolean;
9
- redo: () => boolean;
10
- clear: () => void;
11
- canUndo: () => boolean;
12
- canRedo: () => boolean;
13
- getPast: () => object[];
14
- getFuture: () => object[];
8
+ type HistoryApi<_T extends object> = {
9
+ undo: () => boolean;
10
+ redo: () => boolean;
11
+ clear: () => void;
12
+ canUndo: () => boolean;
13
+ canRedo: () => boolean;
14
+ getPast: () => object[];
15
+ getFuture: () => object[];
15
16
  };
16
- declare const history: <T extends object>({ limit, partialize }?: HistoryOptions<T>) => Middleware<T>;
17
-
18
- export { type HistoryApi, type HistoryOptions, history };
17
+ declare const history: <T extends object>(options?: HistoryOptions<T>) => Middleware<T>;
18
+ //#endregion
19
+ export { HistoryApi, HistoryOptions, history };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,19 @@
1
- import { Middleware } from 'coaction';
1
+ import { Middleware } from "coaction";
2
2
 
3
+ //#region packages/coaction-history/src/index.d.ts
3
4
  type HistoryOptions<T extends object> = {
4
- limit?: number;
5
- partialize?: (state: T) => object;
5
+ limit?: number;
6
+ partialize?: (state: T) => object;
6
7
  };
7
- type HistoryApi<T extends object> = {
8
- undo: () => boolean;
9
- redo: () => boolean;
10
- clear: () => void;
11
- canUndo: () => boolean;
12
- canRedo: () => boolean;
13
- getPast: () => object[];
14
- getFuture: () => object[];
8
+ type HistoryApi<_T extends object> = {
9
+ undo: () => boolean;
10
+ redo: () => boolean;
11
+ clear: () => void;
12
+ canUndo: () => boolean;
13
+ canRedo: () => boolean;
14
+ getPast: () => object[];
15
+ getFuture: () => object[];
15
16
  };
16
- declare const history: <T extends object>({ limit, partialize }?: HistoryOptions<T>) => Middleware<T>;
17
-
18
- export { type HistoryApi, type HistoryOptions, history };
17
+ declare const history: <T extends object>(options?: HistoryOptions<T>) => Middleware<T>;
18
+ //#endregion
19
+ export { HistoryApi, HistoryOptions, history };
package/dist/index.js CHANGED
@@ -1,200 +1,251 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let coaction = require("coaction");
3
+ //#region packages/coaction-history/src/index.ts
4
+ const historySuppressionSymbol = Symbol.for("coaction.history.suppress");
5
+ const isUnsafeKey = (key) => typeof key === "string" && (key === "__proto__" || key === "prototype" || key === "constructor");
6
+ const getOwnEnumerableKeys = (value) => Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key) && !isUnsafeKey(key));
7
+ const setOwnEnumerable = (target, key, value) => {
8
+ if (isUnsafeKey(key)) return;
9
+ target[key] = value;
9
10
  };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
11
+ const isObjectRecord = (value) => Object.prototype.toString.call(value) === "[object Object]";
12
+ const toSnapshot = (state, visited = /* @__PURE__ */ new WeakMap()) => {
13
+ if (Array.isArray(state)) {
14
+ if (visited.has(state)) return visited.get(state);
15
+ const next = [];
16
+ next.length = state.length;
17
+ visited.set(state, next);
18
+ const stateRecord = state;
19
+ const nextRecord = next;
20
+ for (const key of getOwnEnumerableKeys(state)) {
21
+ const value = stateRecord[key];
22
+ if (typeof value === "function") continue;
23
+ setOwnEnumerable(nextRecord, key, toSnapshot(value, visited));
24
+ }
25
+ return next;
26
+ }
27
+ if (typeof state === "object" && state !== null) {
28
+ if (!isObjectRecord(state)) return state;
29
+ if (visited.has(state)) return visited.get(state);
30
+ const next = {};
31
+ visited.set(state, next);
32
+ for (const key of getOwnEnumerableKeys(state)) {
33
+ const value = state[key];
34
+ if (typeof value === "function") continue;
35
+ setOwnEnumerable(next, key, toSnapshot(value, visited));
36
+ }
37
+ return next;
38
+ }
39
+ return state;
17
40
  };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- history: () => history
24
- });
25
- module.exports = __toCommonJS(index_exports);
26
-
27
- // src/index.ts
28
- var toSnapshot = (state, visited = /* @__PURE__ */ new WeakMap()) => {
29
- if (Array.isArray(state)) {
30
- if (visited.has(state)) {
31
- return visited.get(state);
32
- }
33
- const next = [];
34
- visited.set(state, next);
35
- for (const item of state) {
36
- next.push(toSnapshot(item, visited));
37
- }
38
- return next;
39
- }
40
- if (typeof state === "object" && state !== null) {
41
- if (visited.has(state)) {
42
- return visited.get(state);
43
- }
44
- const next = {};
45
- visited.set(state, next);
46
- for (const key of Object.keys(state)) {
47
- const value = state[key];
48
- if (typeof value === "function") {
49
- continue;
50
- }
51
- next[key] = toSnapshot(value, visited);
52
- }
53
- return next;
54
- }
55
- return state;
41
+ const isEqual = (a, b, visited = /* @__PURE__ */ new WeakMap()) => {
42
+ if (Object.is(a, b)) return true;
43
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
44
+ const aIsArray = Array.isArray(a);
45
+ const bIsArray = Array.isArray(b);
46
+ if (aIsArray || bIsArray) {
47
+ if (!aIsArray || !bIsArray || a.length !== b.length) return false;
48
+ } else if (!isObjectRecord(a) || !isObjectRecord(b)) return false;
49
+ let seenTargets = visited.get(a);
50
+ if (!seenTargets) {
51
+ seenTargets = /* @__PURE__ */ new WeakSet();
52
+ visited.set(a, seenTargets);
53
+ } else if (seenTargets.has(b)) return true;
54
+ seenTargets.add(b);
55
+ const aObject = a;
56
+ const bObject = b;
57
+ const aKeys = getOwnEnumerableKeys(aObject);
58
+ const bKeys = getOwnEnumerableKeys(bObject);
59
+ if (aKeys.length !== bKeys.length) return false;
60
+ for (const key of aKeys) {
61
+ if (!Object.prototype.hasOwnProperty.call(bObject, key)) return false;
62
+ if (!isEqual(aObject[key], bObject[key], visited)) return false;
63
+ }
64
+ return true;
56
65
  };
57
- var isEqual = (a, b, visited = /* @__PURE__ */ new WeakMap()) => {
58
- if (Object.is(a, b)) {
59
- return true;
60
- }
61
- if (Array.isArray(a) || Array.isArray(b)) {
62
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
63
- return false;
64
- }
65
- for (let index = 0; index < a.length; index += 1) {
66
- if (!isEqual(a[index], b[index], visited)) {
67
- return false;
68
- }
69
- }
70
- return true;
71
- }
72
- if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
73
- return false;
74
- }
75
- let seenTargets = visited.get(a);
76
- if (!seenTargets) {
77
- seenTargets = /* @__PURE__ */ new WeakSet();
78
- visited.set(a, seenTargets);
79
- } else if (seenTargets.has(b)) {
80
- return true;
81
- }
82
- seenTargets.add(b);
83
- const aObject = a;
84
- const bObject = b;
85
- const aKeys = Object.keys(aObject);
86
- const bKeys = Object.keys(bObject);
87
- if (aKeys.length !== bKeys.length) {
88
- return false;
89
- }
90
- for (const key of aKeys) {
91
- if (!Object.prototype.hasOwnProperty.call(bObject, key)) {
92
- return false;
93
- }
94
- if (!isEqual(aObject[key], bObject[key], visited)) {
95
- return false;
96
- }
97
- }
98
- return true;
66
+ const hasCircularReference = (value, ancestors = /* @__PURE__ */ new WeakSet(), seen = /* @__PURE__ */ new WeakSet()) => {
67
+ if (typeof value !== "object" || value === null) return false;
68
+ if (!Array.isArray(value) && !isObjectRecord(value)) return false;
69
+ if (ancestors.has(value)) return true;
70
+ if (seen.has(value)) return false;
71
+ seen.add(value);
72
+ ancestors.add(value);
73
+ for (const key of getOwnEnumerableKeys(value)) {
74
+ const child = value[key];
75
+ if (typeof child !== "function" && hasCircularReference(child, ancestors, seen)) return true;
76
+ }
77
+ ancestors.delete(value);
78
+ return false;
99
79
  };
100
- var applySnapshot = (target, nextState, currentState) => {
101
- const next = nextState;
102
- const current = currentState;
103
- for (const key of Object.keys(current)) {
104
- if (!Object.prototype.hasOwnProperty.call(next, key)) {
105
- delete target[key];
106
- }
107
- }
108
- for (const key of Object.keys(next)) {
109
- target[key] = toSnapshot(next[key]);
110
- }
80
+ const applySnapshot = (target, nextState, currentState) => {
81
+ const next = nextState;
82
+ const current = currentState;
83
+ const snapshotVisited = /* @__PURE__ */ new WeakMap();
84
+ snapshotVisited.set(nextState, target);
85
+ if (Array.isArray(target) && Array.isArray(nextState)) target.length = nextState.length;
86
+ for (const key of getOwnEnumerableKeys(current)) if (!Object.prototype.hasOwnProperty.call(next, key)) delete target[key];
87
+ for (const key of getOwnEnumerableKeys(next)) setOwnEnumerable(target, key, toSnapshot(next[key], snapshotVisited));
111
88
  };
112
- var history = ({
113
- limit = 100,
114
- partialize = (state) => state
115
- } = {}) => (store) => {
116
- const past = [];
117
- const future = [];
118
- let isTimeTraveling = false;
119
- const getSnapshot = () => toSnapshot(partialize(store.getPureState()));
120
- const pushPast = (snapshot) => {
121
- past.push(snapshot);
122
- if (past.length > limit) {
123
- past.shift();
124
- }
125
- };
126
- const baseSetState = store.setState;
127
- store.setState = (next, updater) => {
128
- const previous = getSnapshot();
129
- const result = baseSetState(next, updater);
130
- if (isTimeTraveling) {
131
- return result;
132
- }
133
- const current = getSnapshot();
134
- if (!isEqual(previous, current)) {
135
- pushPast(previous);
136
- future.length = 0;
137
- }
138
- return result;
139
- };
140
- const api = {
141
- undo: () => {
142
- const previous = past.pop();
143
- if (!previous) {
144
- return false;
145
- }
146
- const current = getSnapshot();
147
- future.push(current);
148
- isTimeTraveling = true;
149
- try {
150
- baseSetState((draft) => {
151
- applySnapshot(
152
- draft,
153
- previous,
154
- current
155
- );
156
- });
157
- } finally {
158
- isTimeTraveling = false;
159
- }
160
- return true;
161
- },
162
- redo: () => {
163
- const next = future.pop();
164
- if (!next) {
165
- return false;
166
- }
167
- const current = getSnapshot();
168
- past.push(current);
169
- isTimeTraveling = true;
170
- try {
171
- baseSetState((draft) => {
172
- applySnapshot(
173
- draft,
174
- next,
175
- current
176
- );
177
- });
178
- } finally {
179
- isTimeTraveling = false;
180
- }
181
- return true;
182
- },
183
- clear: () => {
184
- past.length = 0;
185
- future.length = 0;
186
- },
187
- canUndo: () => past.length > 0,
188
- canRedo: () => future.length > 0,
189
- getPast: () => [...past],
190
- getFuture: () => [...future]
191
- };
192
- Object.assign(store, {
193
- history: api
194
- });
195
- return store;
89
+ const isPatchableObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && isObjectRecord(value);
90
+ const applyPartialSnapshot = (target, nextState, currentState, visited = /* @__PURE__ */ new WeakMap(), snapshotVisited = /* @__PURE__ */ new WeakMap()) => {
91
+ if (!snapshotVisited.has(nextState)) snapshotVisited.set(nextState, target);
92
+ let seenCurrentStates = visited.get(nextState);
93
+ if (!seenCurrentStates) {
94
+ seenCurrentStates = /* @__PURE__ */ new WeakSet();
95
+ visited.set(nextState, seenCurrentStates);
96
+ } else if (seenCurrentStates.has(currentState)) return;
97
+ seenCurrentStates.add(currentState);
98
+ const next = nextState;
99
+ const current = currentState;
100
+ if (Array.isArray(target) && Array.isArray(nextState)) target.length = nextState.length;
101
+ for (const key of getOwnEnumerableKeys(current)) if (!Object.prototype.hasOwnProperty.call(next, key)) delete target[key];
102
+ for (const key of getOwnEnumerableKeys(next)) {
103
+ const nextValue = next[key];
104
+ const currentValue = current[key];
105
+ const targetValue = target[key];
106
+ if (isPatchableObject(nextValue) && isPatchableObject(currentValue) && isPatchableObject(targetValue)) {
107
+ applyPartialSnapshot(targetValue, nextValue, currentValue, visited, snapshotVisited);
108
+ continue;
109
+ }
110
+ setOwnEnumerable(target, key, toSnapshot(nextValue, snapshotVisited));
111
+ }
196
112
  };
197
- // Annotate the CommonJS export names for ESM import in node:
198
- 0 && (module.exports = {
199
- history
200
- });
113
+ const cloneSnapshotList = (snapshots) => snapshots.map((snapshot) => toSnapshot(snapshot));
114
+ const history = (options = {}) => (store) => {
115
+ if (store.share === "client") throw new Error("history() is not supported in client store mode. Apply history() to the main shared store instead.");
116
+ const { limit = 100, partialize = (state) => state } = options;
117
+ const applyHistorySnapshot = options.partialize ? applyPartialSnapshot : applySnapshot;
118
+ const applyStore = store.apply?.bind(store);
119
+ const past = [];
120
+ const future = [];
121
+ let isTimeTraveling = false;
122
+ let isSetStateRecording = false;
123
+ let suppressionDepth = 0;
124
+ let lastSnapshot;
125
+ let unsubscribeStore;
126
+ const getSnapshot = () => toSnapshot(partialize(store.getPureState()));
127
+ const runWithoutRecording = (callback) => {
128
+ suppressionDepth += 1;
129
+ try {
130
+ return callback();
131
+ } finally {
132
+ suppressionDepth -= 1;
133
+ lastSnapshot = getSnapshot();
134
+ }
135
+ };
136
+ const pushPast = (snapshot) => {
137
+ past.push(snapshot);
138
+ if (past.length > limit) past.shift();
139
+ };
140
+ const recordChange = (previous, current) => {
141
+ if (!isEqual(previous, current)) {
142
+ pushPast(previous);
143
+ future.length = 0;
144
+ }
145
+ lastSnapshot = current;
146
+ };
147
+ const applyTimeTravelSnapshot = (snapshot, current) => {
148
+ if (applyStore && hasCircularReference(snapshot)) {
149
+ const nextState = toSnapshot(store.getPureState());
150
+ applyHistorySnapshot(nextState, snapshot, current);
151
+ applyStore(nextState);
152
+ return;
153
+ }
154
+ baseSetState((draft) => {
155
+ applyHistorySnapshot(draft, snapshot, current);
156
+ });
157
+ };
158
+ const cancelReadySubscription = (0, coaction.onStoreReady)(store, () => {
159
+ lastSnapshot = getSnapshot();
160
+ unsubscribeStore = store.subscribe(() => {
161
+ const current = getSnapshot();
162
+ if (isSetStateRecording || isTimeTraveling || suppressionDepth > 0) {
163
+ lastSnapshot = current;
164
+ return;
165
+ }
166
+ recordChange(lastSnapshot ?? current, current);
167
+ });
168
+ });
169
+ const baseSetState = store.setState;
170
+ store.setState = (next, updater) => {
171
+ const previous = getSnapshot();
172
+ isSetStateRecording = true;
173
+ let result;
174
+ try {
175
+ result = baseSetState(next, updater);
176
+ } finally {
177
+ isSetStateRecording = false;
178
+ }
179
+ if (isTimeTraveling || suppressionDepth > 0) {
180
+ lastSnapshot = getSnapshot();
181
+ return result;
182
+ }
183
+ recordChange(previous, getSnapshot());
184
+ return result;
185
+ };
186
+ Object.assign(store, { history: {
187
+ undo: () => {
188
+ const previous = past.pop();
189
+ if (!previous) return false;
190
+ const current = getSnapshot();
191
+ future.push(current);
192
+ isTimeTraveling = true;
193
+ try {
194
+ applyTimeTravelSnapshot(previous, current);
195
+ } catch (error) {
196
+ future.pop();
197
+ past.push(previous);
198
+ throw error;
199
+ } finally {
200
+ isTimeTraveling = false;
201
+ }
202
+ return true;
203
+ },
204
+ redo: () => {
205
+ const next = future.pop();
206
+ if (!next) return false;
207
+ const current = getSnapshot();
208
+ past.push(current);
209
+ isTimeTraveling = true;
210
+ try {
211
+ applyTimeTravelSnapshot(next, current);
212
+ } catch (error) {
213
+ past.pop();
214
+ future.push(next);
215
+ throw error;
216
+ } finally {
217
+ isTimeTraveling = false;
218
+ }
219
+ return true;
220
+ },
221
+ clear: () => {
222
+ past.length = 0;
223
+ future.length = 0;
224
+ },
225
+ canUndo: () => past.length > 0,
226
+ canRedo: () => future.length > 0,
227
+ getPast: () => cloneSnapshotList(past),
228
+ getFuture: () => cloneSnapshotList(future)
229
+ } });
230
+ Object.defineProperty(store, historySuppressionSymbol, {
231
+ configurable: true,
232
+ value: runWithoutRecording
233
+ });
234
+ if (typeof store.destroy === "function") {
235
+ const baseDestroy = store.destroy;
236
+ let destroyed = false;
237
+ store.destroy = () => {
238
+ if (destroyed) return;
239
+ destroyed = true;
240
+ cancelReadySubscription();
241
+ unsubscribeStore?.();
242
+ unsubscribeStore = void 0;
243
+ const metadataStore = store;
244
+ if (metadataStore[historySuppressionSymbol] === runWithoutRecording) delete metadataStore[historySuppressionSymbol];
245
+ baseDestroy();
246
+ };
247
+ }
248
+ return store;
249
+ };
250
+ //#endregion
251
+ exports.history = history;
package/dist/index.mjs CHANGED
@@ -1,173 +1,250 @@
1
- // src/index.ts
2
- var toSnapshot = (state, visited = /* @__PURE__ */ new WeakMap()) => {
3
- if (Array.isArray(state)) {
4
- if (visited.has(state)) {
5
- return visited.get(state);
6
- }
7
- const next = [];
8
- visited.set(state, next);
9
- for (const item of state) {
10
- next.push(toSnapshot(item, visited));
11
- }
12
- return next;
13
- }
14
- if (typeof state === "object" && state !== null) {
15
- if (visited.has(state)) {
16
- return visited.get(state);
17
- }
18
- const next = {};
19
- visited.set(state, next);
20
- for (const key of Object.keys(state)) {
21
- const value = state[key];
22
- if (typeof value === "function") {
23
- continue;
24
- }
25
- next[key] = toSnapshot(value, visited);
26
- }
27
- return next;
28
- }
29
- return state;
1
+ import { onStoreReady } from "coaction";
2
+ //#region packages/coaction-history/src/index.ts
3
+ const historySuppressionSymbol = Symbol.for("coaction.history.suppress");
4
+ const isUnsafeKey = (key) => typeof key === "string" && (key === "__proto__" || key === "prototype" || key === "constructor");
5
+ const getOwnEnumerableKeys = (value) => Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key) && !isUnsafeKey(key));
6
+ const setOwnEnumerable = (target, key, value) => {
7
+ if (isUnsafeKey(key)) return;
8
+ target[key] = value;
30
9
  };
31
- var isEqual = (a, b, visited = /* @__PURE__ */ new WeakMap()) => {
32
- if (Object.is(a, b)) {
33
- return true;
34
- }
35
- if (Array.isArray(a) || Array.isArray(b)) {
36
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
37
- return false;
38
- }
39
- for (let index = 0; index < a.length; index += 1) {
40
- if (!isEqual(a[index], b[index], visited)) {
41
- return false;
42
- }
43
- }
44
- return true;
45
- }
46
- if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
47
- return false;
48
- }
49
- let seenTargets = visited.get(a);
50
- if (!seenTargets) {
51
- seenTargets = /* @__PURE__ */ new WeakSet();
52
- visited.set(a, seenTargets);
53
- } else if (seenTargets.has(b)) {
54
- return true;
55
- }
56
- seenTargets.add(b);
57
- const aObject = a;
58
- const bObject = b;
59
- const aKeys = Object.keys(aObject);
60
- const bKeys = Object.keys(bObject);
61
- if (aKeys.length !== bKeys.length) {
62
- return false;
63
- }
64
- for (const key of aKeys) {
65
- if (!Object.prototype.hasOwnProperty.call(bObject, key)) {
66
- return false;
67
- }
68
- if (!isEqual(aObject[key], bObject[key], visited)) {
69
- return false;
70
- }
71
- }
72
- return true;
10
+ const isObjectRecord = (value) => Object.prototype.toString.call(value) === "[object Object]";
11
+ const toSnapshot = (state, visited = /* @__PURE__ */ new WeakMap()) => {
12
+ if (Array.isArray(state)) {
13
+ if (visited.has(state)) return visited.get(state);
14
+ const next = [];
15
+ next.length = state.length;
16
+ visited.set(state, next);
17
+ const stateRecord = state;
18
+ const nextRecord = next;
19
+ for (const key of getOwnEnumerableKeys(state)) {
20
+ const value = stateRecord[key];
21
+ if (typeof value === "function") continue;
22
+ setOwnEnumerable(nextRecord, key, toSnapshot(value, visited));
23
+ }
24
+ return next;
25
+ }
26
+ if (typeof state === "object" && state !== null) {
27
+ if (!isObjectRecord(state)) return state;
28
+ if (visited.has(state)) return visited.get(state);
29
+ const next = {};
30
+ visited.set(state, next);
31
+ for (const key of getOwnEnumerableKeys(state)) {
32
+ const value = state[key];
33
+ if (typeof value === "function") continue;
34
+ setOwnEnumerable(next, key, toSnapshot(value, visited));
35
+ }
36
+ return next;
37
+ }
38
+ return state;
73
39
  };
74
- var applySnapshot = (target, nextState, currentState) => {
75
- const next = nextState;
76
- const current = currentState;
77
- for (const key of Object.keys(current)) {
78
- if (!Object.prototype.hasOwnProperty.call(next, key)) {
79
- delete target[key];
80
- }
81
- }
82
- for (const key of Object.keys(next)) {
83
- target[key] = toSnapshot(next[key]);
84
- }
40
+ const isEqual = (a, b, visited = /* @__PURE__ */ new WeakMap()) => {
41
+ if (Object.is(a, b)) return true;
42
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
43
+ const aIsArray = Array.isArray(a);
44
+ const bIsArray = Array.isArray(b);
45
+ if (aIsArray || bIsArray) {
46
+ if (!aIsArray || !bIsArray || a.length !== b.length) return false;
47
+ } else if (!isObjectRecord(a) || !isObjectRecord(b)) return false;
48
+ let seenTargets = visited.get(a);
49
+ if (!seenTargets) {
50
+ seenTargets = /* @__PURE__ */ new WeakSet();
51
+ visited.set(a, seenTargets);
52
+ } else if (seenTargets.has(b)) return true;
53
+ seenTargets.add(b);
54
+ const aObject = a;
55
+ const bObject = b;
56
+ const aKeys = getOwnEnumerableKeys(aObject);
57
+ const bKeys = getOwnEnumerableKeys(bObject);
58
+ if (aKeys.length !== bKeys.length) return false;
59
+ for (const key of aKeys) {
60
+ if (!Object.prototype.hasOwnProperty.call(bObject, key)) return false;
61
+ if (!isEqual(aObject[key], bObject[key], visited)) return false;
62
+ }
63
+ return true;
85
64
  };
86
- var history = ({
87
- limit = 100,
88
- partialize = (state) => state
89
- } = {}) => (store) => {
90
- const past = [];
91
- const future = [];
92
- let isTimeTraveling = false;
93
- const getSnapshot = () => toSnapshot(partialize(store.getPureState()));
94
- const pushPast = (snapshot) => {
95
- past.push(snapshot);
96
- if (past.length > limit) {
97
- past.shift();
98
- }
99
- };
100
- const baseSetState = store.setState;
101
- store.setState = (next, updater) => {
102
- const previous = getSnapshot();
103
- const result = baseSetState(next, updater);
104
- if (isTimeTraveling) {
105
- return result;
106
- }
107
- const current = getSnapshot();
108
- if (!isEqual(previous, current)) {
109
- pushPast(previous);
110
- future.length = 0;
111
- }
112
- return result;
113
- };
114
- const api = {
115
- undo: () => {
116
- const previous = past.pop();
117
- if (!previous) {
118
- return false;
119
- }
120
- const current = getSnapshot();
121
- future.push(current);
122
- isTimeTraveling = true;
123
- try {
124
- baseSetState((draft) => {
125
- applySnapshot(
126
- draft,
127
- previous,
128
- current
129
- );
130
- });
131
- } finally {
132
- isTimeTraveling = false;
133
- }
134
- return true;
135
- },
136
- redo: () => {
137
- const next = future.pop();
138
- if (!next) {
139
- return false;
140
- }
141
- const current = getSnapshot();
142
- past.push(current);
143
- isTimeTraveling = true;
144
- try {
145
- baseSetState((draft) => {
146
- applySnapshot(
147
- draft,
148
- next,
149
- current
150
- );
151
- });
152
- } finally {
153
- isTimeTraveling = false;
154
- }
155
- return true;
156
- },
157
- clear: () => {
158
- past.length = 0;
159
- future.length = 0;
160
- },
161
- canUndo: () => past.length > 0,
162
- canRedo: () => future.length > 0,
163
- getPast: () => [...past],
164
- getFuture: () => [...future]
165
- };
166
- Object.assign(store, {
167
- history: api
168
- });
169
- return store;
65
+ const hasCircularReference = (value, ancestors = /* @__PURE__ */ new WeakSet(), seen = /* @__PURE__ */ new WeakSet()) => {
66
+ if (typeof value !== "object" || value === null) return false;
67
+ if (!Array.isArray(value) && !isObjectRecord(value)) return false;
68
+ if (ancestors.has(value)) return true;
69
+ if (seen.has(value)) return false;
70
+ seen.add(value);
71
+ ancestors.add(value);
72
+ for (const key of getOwnEnumerableKeys(value)) {
73
+ const child = value[key];
74
+ if (typeof child !== "function" && hasCircularReference(child, ancestors, seen)) return true;
75
+ }
76
+ ancestors.delete(value);
77
+ return false;
170
78
  };
171
- export {
172
- history
79
+ const applySnapshot = (target, nextState, currentState) => {
80
+ const next = nextState;
81
+ const current = currentState;
82
+ const snapshotVisited = /* @__PURE__ */ new WeakMap();
83
+ snapshotVisited.set(nextState, target);
84
+ if (Array.isArray(target) && Array.isArray(nextState)) target.length = nextState.length;
85
+ for (const key of getOwnEnumerableKeys(current)) if (!Object.prototype.hasOwnProperty.call(next, key)) delete target[key];
86
+ for (const key of getOwnEnumerableKeys(next)) setOwnEnumerable(target, key, toSnapshot(next[key], snapshotVisited));
173
87
  };
88
+ const isPatchableObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && isObjectRecord(value);
89
+ const applyPartialSnapshot = (target, nextState, currentState, visited = /* @__PURE__ */ new WeakMap(), snapshotVisited = /* @__PURE__ */ new WeakMap()) => {
90
+ if (!snapshotVisited.has(nextState)) snapshotVisited.set(nextState, target);
91
+ let seenCurrentStates = visited.get(nextState);
92
+ if (!seenCurrentStates) {
93
+ seenCurrentStates = /* @__PURE__ */ new WeakSet();
94
+ visited.set(nextState, seenCurrentStates);
95
+ } else if (seenCurrentStates.has(currentState)) return;
96
+ seenCurrentStates.add(currentState);
97
+ const next = nextState;
98
+ const current = currentState;
99
+ if (Array.isArray(target) && Array.isArray(nextState)) target.length = nextState.length;
100
+ for (const key of getOwnEnumerableKeys(current)) if (!Object.prototype.hasOwnProperty.call(next, key)) delete target[key];
101
+ for (const key of getOwnEnumerableKeys(next)) {
102
+ const nextValue = next[key];
103
+ const currentValue = current[key];
104
+ const targetValue = target[key];
105
+ if (isPatchableObject(nextValue) && isPatchableObject(currentValue) && isPatchableObject(targetValue)) {
106
+ applyPartialSnapshot(targetValue, nextValue, currentValue, visited, snapshotVisited);
107
+ continue;
108
+ }
109
+ setOwnEnumerable(target, key, toSnapshot(nextValue, snapshotVisited));
110
+ }
111
+ };
112
+ const cloneSnapshotList = (snapshots) => snapshots.map((snapshot) => toSnapshot(snapshot));
113
+ const history = (options = {}) => (store) => {
114
+ if (store.share === "client") throw new Error("history() is not supported in client store mode. Apply history() to the main shared store instead.");
115
+ const { limit = 100, partialize = (state) => state } = options;
116
+ const applyHistorySnapshot = options.partialize ? applyPartialSnapshot : applySnapshot;
117
+ const applyStore = store.apply?.bind(store);
118
+ const past = [];
119
+ const future = [];
120
+ let isTimeTraveling = false;
121
+ let isSetStateRecording = false;
122
+ let suppressionDepth = 0;
123
+ let lastSnapshot;
124
+ let unsubscribeStore;
125
+ const getSnapshot = () => toSnapshot(partialize(store.getPureState()));
126
+ const runWithoutRecording = (callback) => {
127
+ suppressionDepth += 1;
128
+ try {
129
+ return callback();
130
+ } finally {
131
+ suppressionDepth -= 1;
132
+ lastSnapshot = getSnapshot();
133
+ }
134
+ };
135
+ const pushPast = (snapshot) => {
136
+ past.push(snapshot);
137
+ if (past.length > limit) past.shift();
138
+ };
139
+ const recordChange = (previous, current) => {
140
+ if (!isEqual(previous, current)) {
141
+ pushPast(previous);
142
+ future.length = 0;
143
+ }
144
+ lastSnapshot = current;
145
+ };
146
+ const applyTimeTravelSnapshot = (snapshot, current) => {
147
+ if (applyStore && hasCircularReference(snapshot)) {
148
+ const nextState = toSnapshot(store.getPureState());
149
+ applyHistorySnapshot(nextState, snapshot, current);
150
+ applyStore(nextState);
151
+ return;
152
+ }
153
+ baseSetState((draft) => {
154
+ applyHistorySnapshot(draft, snapshot, current);
155
+ });
156
+ };
157
+ const cancelReadySubscription = onStoreReady(store, () => {
158
+ lastSnapshot = getSnapshot();
159
+ unsubscribeStore = store.subscribe(() => {
160
+ const current = getSnapshot();
161
+ if (isSetStateRecording || isTimeTraveling || suppressionDepth > 0) {
162
+ lastSnapshot = current;
163
+ return;
164
+ }
165
+ recordChange(lastSnapshot ?? current, current);
166
+ });
167
+ });
168
+ const baseSetState = store.setState;
169
+ store.setState = (next, updater) => {
170
+ const previous = getSnapshot();
171
+ isSetStateRecording = true;
172
+ let result;
173
+ try {
174
+ result = baseSetState(next, updater);
175
+ } finally {
176
+ isSetStateRecording = false;
177
+ }
178
+ if (isTimeTraveling || suppressionDepth > 0) {
179
+ lastSnapshot = getSnapshot();
180
+ return result;
181
+ }
182
+ recordChange(previous, getSnapshot());
183
+ return result;
184
+ };
185
+ Object.assign(store, { history: {
186
+ undo: () => {
187
+ const previous = past.pop();
188
+ if (!previous) return false;
189
+ const current = getSnapshot();
190
+ future.push(current);
191
+ isTimeTraveling = true;
192
+ try {
193
+ applyTimeTravelSnapshot(previous, current);
194
+ } catch (error) {
195
+ future.pop();
196
+ past.push(previous);
197
+ throw error;
198
+ } finally {
199
+ isTimeTraveling = false;
200
+ }
201
+ return true;
202
+ },
203
+ redo: () => {
204
+ const next = future.pop();
205
+ if (!next) return false;
206
+ const current = getSnapshot();
207
+ past.push(current);
208
+ isTimeTraveling = true;
209
+ try {
210
+ applyTimeTravelSnapshot(next, current);
211
+ } catch (error) {
212
+ past.pop();
213
+ future.push(next);
214
+ throw error;
215
+ } finally {
216
+ isTimeTraveling = false;
217
+ }
218
+ return true;
219
+ },
220
+ clear: () => {
221
+ past.length = 0;
222
+ future.length = 0;
223
+ },
224
+ canUndo: () => past.length > 0,
225
+ canRedo: () => future.length > 0,
226
+ getPast: () => cloneSnapshotList(past),
227
+ getFuture: () => cloneSnapshotList(future)
228
+ } });
229
+ Object.defineProperty(store, historySuppressionSymbol, {
230
+ configurable: true,
231
+ value: runWithoutRecording
232
+ });
233
+ if (typeof store.destroy === "function") {
234
+ const baseDestroy = store.destroy;
235
+ let destroyed = false;
236
+ store.destroy = () => {
237
+ if (destroyed) return;
238
+ destroyed = true;
239
+ cancelReadySubscription();
240
+ unsubscribeStore?.();
241
+ unsubscribeStore = void 0;
242
+ const metadataStore = store;
243
+ if (metadataStore[historySuppressionSymbol] === runWithoutRecording) delete metadataStore[historySuppressionSymbol];
244
+ baseDestroy();
245
+ };
246
+ }
247
+ return store;
248
+ };
249
+ //#endregion
250
+ export { history };
package/package.json CHANGED
@@ -1,22 +1,33 @@
1
1
  {
2
2
  "name": "@coaction/history",
3
- "version": "1.5.0",
3
+ "version": "2.0.0",
4
4
  "description": "A undo/redo middleware for Coaction",
5
5
  "keywords": [
6
- "state",
7
6
  "coaction",
8
- "middleware",
9
7
  "history",
10
- "undo",
11
- "redo"
12
- ],
13
- "authors": [
14
- "Michael Lin <unadlib@gmail.com> (https://github.com/unadlib)"
8
+ "middleware",
9
+ "redo",
10
+ "state",
11
+ "undo"
15
12
  ],
16
- "homepage": "https://github.com/unadlib/coaction/tree/main/packages/coaction-history#readme",
13
+ "homepage": "https://github.com/coactionjs/coaction/tree/main/packages/coaction-history#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/coactionjs/coaction/issues"
16
+ },
17
17
  "license": "MIT",
18
+ "author": "unadlib",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/coactionjs/coaction.git",
22
+ "directory": "packages/coaction-history"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "sideEffects": false,
18
28
  "main": "dist/index.js",
19
29
  "module": "dist/index.mjs",
30
+ "types": "dist/index.d.ts",
20
31
  "exports": {
21
32
  ".": {
22
33
  "types": "./dist/index.d.ts",
@@ -26,39 +37,23 @@
26
37
  },
27
38
  "./package.json": "./package.json"
28
39
  },
29
- "types": "dist/index.d.ts",
30
- "sideEffects": false,
31
- "files": [
32
- "dist"
33
- ],
34
- "repository": {
35
- "type": "git",
36
- "url": "https://github.com/unadlib/coaction.git",
37
- "directory": "packages/coaction-history"
38
- },
39
- "bugs": {
40
- "url": "https://github.com/unadlib/coaction/issues"
41
- },
42
- "peerDependencies": {
43
- "coaction": "^1.5.0"
44
- },
45
- "peerDependenciesMeta": {
46
- "coaction": {
47
- "optional": true
48
- }
49
- },
50
- "devDependencies": {
51
- "coaction": "1.5.0"
52
- },
53
40
  "publishConfig": {
54
41
  "access": "public",
55
42
  "provenance": true
56
43
  },
57
- "author": "unadlib",
44
+ "devDependencies": {
45
+ "coaction": "2.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "coaction": "^2.0.0"
49
+ },
50
+ "authors": [
51
+ "Michael Lin <unadlib@gmail.com> (https://github.com/unadlib)"
52
+ ],
58
53
  "scripts": {
59
54
  "clean": "rm -rf dist",
60
55
  "test": "vitest run test",
61
- "build": "tsup index.ts --format cjs,esm --dts --clean --out-dir dist",
62
- "dev": "tsup index.ts --format cjs,esm --dts --watch --out-dir dist"
56
+ "build": "node ../../scripts/build-package.mjs",
57
+ "dev": "node ../../scripts/build-package.mjs --watch"
63
58
  }
64
59
  }