@iostore/devtools 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/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @iostore/devtools
2
+
3
+ IO DevTools 不是 Redux DevTools 的替代品, 而是一个 状态运行时观察系统。
4
+
5
+ Commit 是“因”
6
+
7
+ Diff 是“证据”
8
+
9
+ Snapshot 是“历史”
10
+
11
+ Derived 是“计算图”
@@ -0,0 +1,6 @@
1
+ export type { IoDevtools, IoDevtoolsBridge, IoDevtoolsEvent, IoDevtoolsOptions, IoDevtoolsPerfSample, IoDevtoolsPerfSummary, IoDevtoolsState, IoHistoryEntry, IoPatchDiff, IoPatchDiffTreeNode, IoSnapshotDiff, ReduxDevToolsImportState, } from './lib/types.js';
2
+ export { createIoDevtools } from './lib/create-io-devtools.js';
3
+ export { diffSnapshots } from './lib/diff-snapshots.js';
4
+ export { buildPatchDiffTree } from './lib/patch-diff-tree.js';
5
+ export { exportReduxDevToolsImportState } from './lib/export-redux-devtools.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createIoDevtools } from './lib/create-io-devtools.js';
2
+ export { diffSnapshots } from './lib/diff-snapshots.js';
3
+ export { buildPatchDiffTree } from './lib/patch-diff-tree.js';
4
+ export { exportReduxDevToolsImportState } from './lib/export-redux-devtools.js';
@@ -0,0 +1,3 @@
1
+ import type { IoDevtools, IoDevtoolsOptions } from './types.js';
2
+ export declare function createIoDevtools(targetInput: unknown, options?: IoDevtoolsOptions): IoDevtools;
3
+ //# sourceMappingURL=create-io-devtools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-io-devtools.d.ts","sourceRoot":"","sources":["../../src/lib/create-io-devtools.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,UAAU,EAGV,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AAqDpB,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,OAAO,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CA+OZ"}
@@ -0,0 +1,269 @@
1
+ import { applyUpdate, getLinkInfo, onError, undoUpdate } from '@iostore/store';
2
+ import { diffSnapshots } from './diff-snapshots.js';
3
+ import { createReduxBridgeConnector } from './devtools/bridge.js';
4
+ import { createHistoryController } from './devtools/history.js';
5
+ import { createPerfTracker } from './devtools/perf.js';
6
+ import { exportReduxDevToolsImportState } from './export-redux-devtools.js';
7
+ import { sanitizeForJson } from './sanitize.js';
8
+ function nowEpochMs() {
9
+ return Date.now();
10
+ }
11
+ function nowPerfMs() {
12
+ return globalThis.performance?.now
13
+ ? globalThis.performance.now()
14
+ : Date.now();
15
+ }
16
+ function createIdFactory(prefix) {
17
+ let seq = 0;
18
+ return () => {
19
+ seq += 1;
20
+ const rand = globalThis.crypto?.randomUUID?.();
21
+ return rand ? `${prefix}-${rand}` : `${prefix}-${seq}`;
22
+ };
23
+ }
24
+ function clampInt(value, min, max) {
25
+ return Math.max(min, Math.min(max, value));
26
+ }
27
+ function asTarget(target) {
28
+ const t = target;
29
+ if (typeof t?.snapshot !== 'function' ||
30
+ typeof t?.subscribeUpdate !== 'function') {
31
+ throw new Error('createIoDevtools: target must implement snapshot() and subscribeUpdate()');
32
+ }
33
+ return target;
34
+ }
35
+ function patchToDiff(patch) {
36
+ if (patch.op === 'set')
37
+ return { op: 'set', path: patch.path, prev: patch.prev, next: patch.next };
38
+ if (patch.op === 'splice')
39
+ return {
40
+ op: 'splice',
41
+ path: patch.path,
42
+ start: patch.start,
43
+ deleteCount: patch.deleteCount,
44
+ deleted: patch.deleted,
45
+ items: patch.items,
46
+ };
47
+ return { op: 'sort', path: patch.path, order: patch.order };
48
+ }
49
+ export function createIoDevtools(targetInput, options) {
50
+ const target = asTarget(targetInput);
51
+ const createId = createIdFactory(options?.name ?? 'io');
52
+ let enabled = options?.enabled ?? true;
53
+ let paused = false;
54
+ const errors = [];
55
+ const listeners = new Set();
56
+ let isTimeTraveling = false;
57
+ const perfEnabled = options?.perf?.enabled ?? true;
58
+ const perfWindowSize = clampInt(options?.perf?.windowSize ?? 60, 1, 5_000);
59
+ const perfSampleRate = Math.max(0, Math.min(1, options?.perf?.sampleRate ?? 1));
60
+ const snapshotStrategy = options?.captureSnapshots ?? 'always';
61
+ const maxHistory = clampInt(options?.maxHistory ?? 10_000, 1, 10_000);
62
+ const emit = (event) => {
63
+ for (const listener of listeners) {
64
+ try {
65
+ listener(event);
66
+ }
67
+ catch (error) {
68
+ options?.onDevtoolsError?.(error);
69
+ }
70
+ }
71
+ };
72
+ function getState() {
73
+ let links;
74
+ try {
75
+ links = getLinkInfo(target);
76
+ }
77
+ catch (error) {
78
+ options?.onDevtoolsError?.(error);
79
+ links = undefined;
80
+ }
81
+ return {
82
+ enabled,
83
+ paused,
84
+ cursor: historyController.getCursor(),
85
+ history: historyController.history,
86
+ errors,
87
+ links,
88
+ perf: perfTracker.getState(),
89
+ };
90
+ }
91
+ const reportDevtoolsError = (error, source) => {
92
+ errors.push(error);
93
+ emit({ type: 'error', source, error, state: getState() });
94
+ options?.onDevtoolsError?.(error);
95
+ };
96
+ const perfTracker = createPerfTracker({
97
+ enabled: perfEnabled,
98
+ windowSize: perfWindowSize,
99
+ sampleRate: perfSampleRate,
100
+ emit,
101
+ getState,
102
+ });
103
+ const historyController = createHistoryController({
104
+ target,
105
+ options,
106
+ emit,
107
+ getState,
108
+ createId,
109
+ perfTracker,
110
+ snapshotStrategy,
111
+ maxHistory,
112
+ nowEpochMs,
113
+ nowPerfMs,
114
+ diffSnapshots,
115
+ patchToDiff,
116
+ sanitizeForJson,
117
+ exportReduxDevToolsImportState,
118
+ });
119
+ const onUpdate = (update) => {
120
+ if (!enabled || paused || isTimeTraveling)
121
+ return;
122
+ try {
123
+ historyController.appendHistory(update);
124
+ }
125
+ catch (error) {
126
+ reportDevtoolsError(error, 'devtools');
127
+ }
128
+ };
129
+ const errorHandler = (error, path, operation) => {
130
+ if (!enabled)
131
+ return;
132
+ errors.push(error);
133
+ emit({
134
+ type: 'error',
135
+ source: 'io',
136
+ error,
137
+ path,
138
+ operation,
139
+ state: getState(),
140
+ });
141
+ };
142
+ const unsubscribeUpdate = target.subscribeUpdate(onUpdate);
143
+ const unsubscribeError = onError(targetInput, errorHandler);
144
+ const initialSnapshot = target.snapshot();
145
+ historyController.setInitialSnapshot(initialSnapshot);
146
+ historyController.setLastSnapshot(initialSnapshot);
147
+ const withTimeTraveling = (fn) => {
148
+ isTimeTraveling = true;
149
+ try {
150
+ return fn();
151
+ }
152
+ finally {
153
+ isTimeTraveling = false;
154
+ }
155
+ };
156
+ const applyTimeTravel = (update, kind) => {
157
+ const from = historyController.getCursor();
158
+ return withTimeTraveling(() => {
159
+ try {
160
+ applyUpdate(targetInput, update, { emitUpdate: false });
161
+ const to = kind === 'undo' ? from - 1 : from + 1;
162
+ const next = clampInt(to, -1, historyController.history.length - 1);
163
+ historyController.setCursor(next);
164
+ emit({
165
+ type: 'timeTravel',
166
+ kind,
167
+ from,
168
+ to: historyController.getCursor(),
169
+ state: getState(),
170
+ });
171
+ return true;
172
+ }
173
+ catch (error) {
174
+ reportDevtoolsError(error, 'devtools');
175
+ return false;
176
+ }
177
+ });
178
+ };
179
+ const undo = () => {
180
+ const cursor = historyController.getCursor();
181
+ if (cursor < 0)
182
+ return false;
183
+ const entry = historyController.history[cursor];
184
+ return applyTimeTravel(undoUpdate(entry.update), 'undo');
185
+ };
186
+ const redo = () => {
187
+ const cursor = historyController.getCursor();
188
+ if (cursor >= historyController.history.length - 1)
189
+ return false;
190
+ const entry = historyController.history[cursor + 1];
191
+ return applyTimeTravel(entry.update, 'redo');
192
+ };
193
+ const goTo = (index) => {
194
+ const to = clampInt(index, -1, historyController.history.length - 1);
195
+ const from = historyController.getCursor();
196
+ if (to === from)
197
+ return true;
198
+ const step = () => (historyController.getCursor() > to ? undo() : redo());
199
+ while (historyController.getCursor() !== to) {
200
+ if (!step())
201
+ return false;
202
+ }
203
+ emit({
204
+ type: 'timeTravel',
205
+ kind: 'goTo',
206
+ from,
207
+ to: historyController.getCursor(),
208
+ state: getState(),
209
+ });
210
+ return true;
211
+ };
212
+ const clear = () => {
213
+ const from = historyController.getCursor();
214
+ historyController.clearHistory();
215
+ emit({
216
+ type: 'timeTravel',
217
+ kind: 'clear',
218
+ from,
219
+ to: historyController.getCursor(),
220
+ state: getState(),
221
+ });
222
+ };
223
+ const connectReduxDevToolsExtension = createReduxBridgeConnector({
224
+ target,
225
+ options,
226
+ listeners,
227
+ emit,
228
+ getState,
229
+ reportDevtoolsError,
230
+ history: historyController.history,
231
+ clearHistory: historyController.clearHistory,
232
+ resetSnapshots: (next) => {
233
+ historyController.setInitialSnapshot(next);
234
+ historyController.setLastSnapshot(next);
235
+ },
236
+ goTo,
237
+ withTimeTraveling,
238
+ sanitizeForJson,
239
+ });
240
+ const devtools = {
241
+ getState,
242
+ subscribe: (listener) => {
243
+ listeners.add(listener);
244
+ return () => listeners.delete(listener);
245
+ },
246
+ setEnabled: (next) => {
247
+ enabled = next;
248
+ },
249
+ pause: () => {
250
+ paused = true;
251
+ },
252
+ resume: () => {
253
+ paused = false;
254
+ },
255
+ destroy: () => {
256
+ unsubscribeUpdate();
257
+ unsubscribeError();
258
+ listeners.clear();
259
+ },
260
+ clear,
261
+ timeTravel: { undo, redo, goTo },
262
+ export: {
263
+ json: historyController.exportJson,
264
+ reduxDevToolsImport: historyController.exportReduxImport,
265
+ },
266
+ connectReduxDevToolsExtension,
267
+ };
268
+ return devtools;
269
+ }
@@ -0,0 +1,21 @@
1
+ import type { IoDevtoolsBridge, IoDevtoolsEvent, IoDevtoolsOptions, IoDevtoolsState, IoDevtoolsTarget, IoHistoryEntry } from '../types.js';
2
+ type BridgeDeps = {
3
+ target: IoDevtoolsTarget;
4
+ options?: IoDevtoolsOptions;
5
+ listeners: Set<(event: IoDevtoolsEvent) => void>;
6
+ emit: (event: IoDevtoolsEvent) => void;
7
+ getState: () => IoDevtoolsState;
8
+ reportDevtoolsError: (error: unknown, source: 'bridge') => void;
9
+ history: IoHistoryEntry[];
10
+ clearHistory: () => void;
11
+ resetSnapshots: (next: unknown) => void;
12
+ goTo: (index: number) => boolean;
13
+ withTimeTraveling: <T>(fn: () => T) => T;
14
+ sanitizeForJson: (value: unknown, options?: IoDevtoolsOptions['export']) => unknown;
15
+ };
16
+ export declare function createReduxBridgeConnector(deps: BridgeDeps): (bridgeOptions?: {
17
+ window?: unknown;
18
+ name?: string;
19
+ }) => IoDevtoolsBridge | null;
20
+ export {};
21
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../src/lib/devtools/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,cAAc,EAEf,MAAM,aAAa,CAAC;AAWrB,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,SAAS,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,eAAe,CAAC;IAChC,mBAAmB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,cAAc,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACzC,eAAe,EAAE,CACf,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,KAClC,OAAO,CAAC;CACd,CAAC;AAUF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,UAAU,IACjD,gBAAgB;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,KAAG,gBAAgB,GAAG,IAAI,CAoG5B"}
@@ -0,0 +1,93 @@
1
+ function getReduxExtension(win) {
2
+ const w = win;
3
+ const ext = w?.__REDUX_DEVTOOLS_EXTENSION__;
4
+ if (!ext)
5
+ return null;
6
+ if (typeof ext.connect !== 'function')
7
+ return null;
8
+ return ext;
9
+ }
10
+ export function createReduxBridgeConnector(deps) {
11
+ return (bridgeOptions) => {
12
+ const enabledByConfig = deps.options?.reduxDevTools?.enabled ?? false;
13
+ if (!enabledByConfig)
14
+ return null;
15
+ const win = bridgeOptions?.window ?? globalThis;
16
+ const ext = getReduxExtension(win);
17
+ if (!ext)
18
+ return null;
19
+ const name = bridgeOptions?.name ??
20
+ deps.options?.reduxDevTools?.name ??
21
+ deps.options?.name ??
22
+ 'IO';
23
+ const connection = ext.connect({ name });
24
+ connection.init(deps.sanitizeForJson(deps.target.snapshot(), deps.options?.export));
25
+ const onBridgeUpdate = (event) => {
26
+ if (event.type !== 'mutation')
27
+ return;
28
+ const index = deps.history.length - 1;
29
+ const actionType = `IO/${index + 1}`;
30
+ const state = deps.sanitizeForJson(deps.target.snapshot(), deps.options?.export);
31
+ try {
32
+ connection.send({ type: actionType }, state);
33
+ }
34
+ catch (error) {
35
+ deps.reportDevtoolsError(error, 'bridge');
36
+ }
37
+ };
38
+ const unsub = (() => {
39
+ const fn = (event) => onBridgeUpdate(event);
40
+ deps.listeners.add(fn);
41
+ return () => deps.listeners.delete(fn);
42
+ })();
43
+ const handleMessage = (message) => {
44
+ const m = message;
45
+ if (m?.type !== 'DISPATCH')
46
+ return;
47
+ const payload = m.payload;
48
+ const dispatchType = payload?.type;
49
+ if (dispatchType === 'RESET') {
50
+ deps.clearHistory();
51
+ connection.init(deps.sanitizeForJson(deps.target.snapshot(), deps.options?.export));
52
+ return;
53
+ }
54
+ if (dispatchType === 'COMMIT') {
55
+ deps.clearHistory();
56
+ const snapshot = deps.target.snapshot();
57
+ deps.resetSnapshots(snapshot);
58
+ connection.init(deps.sanitizeForJson(snapshot, deps.options?.export));
59
+ return;
60
+ }
61
+ if (dispatchType === 'JUMP_TO_ACTION' ||
62
+ dispatchType === 'JUMP_TO_STATE') {
63
+ const actionIdRaw = payload?.actionId;
64
+ const actionId = typeof actionIdRaw === 'number'
65
+ ? actionIdRaw
66
+ : typeof actionIdRaw === 'string'
67
+ ? Number(actionIdRaw)
68
+ : NaN;
69
+ if (!Number.isFinite(actionId))
70
+ return;
71
+ const nextIndex = actionId - 1;
72
+ deps.withTimeTraveling(() => deps.goTo(nextIndex));
73
+ }
74
+ };
75
+ connection.subscribe(handleMessage);
76
+ deps.emit({ type: 'bridge', connected: true, state: deps.getState() });
77
+ const bridge = {
78
+ disconnect: () => {
79
+ try {
80
+ unsub();
81
+ connection.unsubscribe();
82
+ }
83
+ catch (error) {
84
+ deps.reportDevtoolsError(error, 'bridge');
85
+ }
86
+ finally {
87
+ deps.emit({ type: 'bridge', connected: false, state: deps.getState() });
88
+ }
89
+ },
90
+ };
91
+ return bridge;
92
+ };
93
+ }
@@ -0,0 +1,41 @@
1
+ import type { IoPatch, IoUpdate } from '@iostore/store';
2
+ import type { IoDevtoolsEvent, IoDevtoolsOptions, IoDevtoolsSnapshotStrategy, IoDevtoolsState, IoDevtoolsTarget, IoHistoryEntry, IoPatchDiff, ReduxDevToolsImportState } from '../types.js';
3
+ import type { PerfTracker } from './perf.js';
4
+ type HistoryDeps = {
5
+ target: IoDevtoolsTarget;
6
+ options?: IoDevtoolsOptions;
7
+ emit: (event: IoDevtoolsEvent) => void;
8
+ getState: () => IoDevtoolsState;
9
+ createId: () => string;
10
+ perfTracker: PerfTracker;
11
+ snapshotStrategy: IoDevtoolsSnapshotStrategy;
12
+ maxHistory: number;
13
+ nowEpochMs: () => number;
14
+ nowPerfMs: () => number;
15
+ diffSnapshots: (prev: unknown, next: unknown, options?: {
16
+ maxChanges?: number;
17
+ }) => unknown;
18
+ patchToDiff: (patch: IoPatch) => IoPatchDiff;
19
+ sanitizeForJson: (value: unknown, options?: IoDevtoolsOptions['export']) => unknown;
20
+ exportReduxDevToolsImportState: (args: {
21
+ initialState: unknown;
22
+ history: ReadonlyArray<IoHistoryEntry>;
23
+ cursor: number;
24
+ }) => ReduxDevToolsImportState;
25
+ };
26
+ export type HistoryController = {
27
+ history: IoHistoryEntry[];
28
+ getCursor: () => number;
29
+ setCursor: (next: number) => void;
30
+ getLastSnapshot: () => unknown | undefined;
31
+ setLastSnapshot: (next: unknown | undefined) => void;
32
+ getInitialSnapshot: () => unknown;
33
+ setInitialSnapshot: (next: unknown) => void;
34
+ appendHistory: (update: IoUpdate) => void;
35
+ clearHistory: () => void;
36
+ exportJson: () => string;
37
+ exportReduxImport: () => ReduxDevToolsImportState;
38
+ };
39
+ export declare function createHistoryController(deps: HistoryDeps): HistoryController;
40
+ export {};
41
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../../src/lib/devtools/history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,WAAW,EAEX,wBAAwB,EACzB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,eAAe,CAAC;IAChC,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,gBAAgB,EAAE,0BAA0B,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,aAAa,EAAE,CACb,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,OAAO,CAAC;IACb,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,WAAW,CAAC;IAC7C,eAAe,EAAE,CACf,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,KAClC,OAAO,CAAC;IACb,8BAA8B,EAAE,CAAC,IAAI,EAAE;QACrC,YAAY,EAAE,OAAO,CAAC;QACtB,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;QACvC,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,wBAAwB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,eAAe,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAClC,kBAAkB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,aAAa,EAAE,CAAC,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;CACnD,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,WAAW,GAAG,iBAAiB,CA2J5E"}
@@ -0,0 +1,127 @@
1
+ export function createHistoryController(deps) {
2
+ let cursor = -1;
3
+ let lastEpochMs = deps.nowEpochMs();
4
+ let lastSnapshot;
5
+ let initialSnapshot;
6
+ const history = [];
7
+ const maybeCaptureSnapshot = () => {
8
+ if (deps.snapshotStrategy === 'never')
9
+ return {};
10
+ const t0 = deps.nowPerfMs();
11
+ const snapshot = deps.target.snapshot();
12
+ const t1 = deps.nowPerfMs();
13
+ return { snapshot, ms: t1 - t0 };
14
+ };
15
+ const appendHistory = (update) => {
16
+ const epoch = deps.nowEpochMs();
17
+ const intervalMs = epoch - lastEpochMs;
18
+ lastEpochMs = epoch;
19
+ const t0 = deps.nowPerfMs();
20
+ if (cursor < history.length - 1)
21
+ history.splice(cursor + 1);
22
+ const snapshotBefore = deps.snapshotStrategy === 'always' ? lastSnapshot : undefined;
23
+ const beforeInfo = deps.snapshotStrategy === 'always' ? { snapshot: snapshotBefore } : undefined;
24
+ const afterInfo = maybeCaptureSnapshot();
25
+ const patches = deps.options?.filterPatch
26
+ ? update.patches.filter((p) => deps.options?.filterPatch?.(p, update))
27
+ : update.patches;
28
+ const patchDiffs = patches.map(deps.patchToDiff);
29
+ let diffMs;
30
+ if (deps.snapshotStrategy === 'always' &&
31
+ beforeInfo?.snapshot !== undefined &&
32
+ afterInfo.snapshot !== undefined) {
33
+ const d0 = deps.nowPerfMs();
34
+ deps.diffSnapshots(beforeInfo.snapshot, afterInfo.snapshot, {
35
+ maxChanges: 1,
36
+ });
37
+ const d1 = deps.nowPerfMs();
38
+ diffMs = d1 - d0;
39
+ }
40
+ const t1 = deps.nowPerfMs();
41
+ const perf = deps.perfTracker.enabled
42
+ ? {
43
+ patchCount: update.patches.length,
44
+ intervalMs,
45
+ snapshotMs: afterInfo.ms,
46
+ diffMs,
47
+ totalMs: t1 - t0,
48
+ }
49
+ : undefined;
50
+ const entry = {
51
+ id: deps.createId(),
52
+ timestamp: epoch,
53
+ update,
54
+ patchDiffs,
55
+ snapshotBefore: deps.snapshotStrategy === 'always' ? snapshotBefore : undefined,
56
+ snapshotAfter: deps.snapshotStrategy === 'always' ? afterInfo.snapshot : undefined,
57
+ perf,
58
+ };
59
+ history.push(entry);
60
+ while (history.length > deps.maxHistory) {
61
+ history.shift();
62
+ cursor -= 1;
63
+ }
64
+ cursor = history.length - 1;
65
+ lastSnapshot = entry.snapshotAfter ?? lastSnapshot;
66
+ if (entry.perf)
67
+ deps.perfTracker.record(entry.perf);
68
+ deps.emit({ type: 'mutation', entry, state: deps.getState() });
69
+ };
70
+ const clearHistory = () => {
71
+ history.length = 0;
72
+ cursor = -1;
73
+ };
74
+ const exportJson = () => {
75
+ const payload = {
76
+ name: deps.options?.name ?? 'IO',
77
+ initialSnapshot: deps.sanitizeForJson(initialSnapshot, deps.options?.export),
78
+ cursor,
79
+ history: history.map((e) => ({
80
+ id: e.id,
81
+ timestamp: e.timestamp,
82
+ update: e.update,
83
+ patchDiffs: e.patchDiffs,
84
+ snapshotBefore: deps.snapshotStrategy === 'always'
85
+ ? deps.sanitizeForJson(e.snapshotBefore, deps.options?.export)
86
+ : undefined,
87
+ snapshotAfter: deps.snapshotStrategy === 'always'
88
+ ? deps.sanitizeForJson(e.snapshotAfter, deps.options?.export)
89
+ : undefined,
90
+ perf: e.perf,
91
+ })),
92
+ };
93
+ return JSON.stringify(payload, null, 2);
94
+ };
95
+ const exportReduxImport = () => {
96
+ if (deps.snapshotStrategy !== 'always') {
97
+ throw new Error('devtools.export.reduxDevToolsImport requires captureSnapshots="always"');
98
+ }
99
+ return deps.exportReduxDevToolsImportState({
100
+ initialState: deps.sanitizeForJson(initialSnapshot, deps.options?.export),
101
+ history: history.map((e) => ({
102
+ ...e,
103
+ snapshotAfter: deps.sanitizeForJson(e.snapshotAfter, deps.options?.export),
104
+ })),
105
+ cursor,
106
+ });
107
+ };
108
+ return {
109
+ history,
110
+ getCursor: () => cursor,
111
+ setCursor: (next) => {
112
+ cursor = next;
113
+ },
114
+ getLastSnapshot: () => lastSnapshot,
115
+ setLastSnapshot: (next) => {
116
+ lastSnapshot = next;
117
+ },
118
+ getInitialSnapshot: () => initialSnapshot,
119
+ setInitialSnapshot: (next) => {
120
+ initialSnapshot = next;
121
+ },
122
+ appendHistory,
123
+ clearHistory,
124
+ exportJson,
125
+ exportReduxImport,
126
+ };
127
+ }
@@ -0,0 +1,16 @@
1
+ import type { IoDevtoolsEvent, IoDevtoolsPerfSample, IoDevtoolsState } from '../types.js';
2
+ type PerfTrackerOptions = {
3
+ enabled: boolean;
4
+ windowSize: number;
5
+ sampleRate: number;
6
+ emit: (event: IoDevtoolsEvent) => void;
7
+ getState: () => IoDevtoolsState;
8
+ };
9
+ export type PerfTracker = {
10
+ enabled: boolean;
11
+ getState: () => IoDevtoolsState['perf'];
12
+ record: (sample: IoDevtoolsPerfSample) => void;
13
+ };
14
+ export declare function createPerfTracker(options: PerfTrackerOptions): PerfTracker;
15
+ export {};
16
+ //# sourceMappingURL=perf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perf.d.ts","sourceRoot":"","sources":["../../../src/lib/devtools/perf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EAEpB,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,eAAe,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAChD,CAAC;AAsDF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAyB1E"}
@@ -0,0 +1,73 @@
1
+ function defaultPerfSummary(windowSize) {
2
+ return {
3
+ windowSize,
4
+ avgTotalMs: 0,
5
+ maxTotalMs: 0,
6
+ avgSnapshotMs: 0,
7
+ maxSnapshotMs: 0,
8
+ avgDiffMs: 0,
9
+ maxDiffMs: 0,
10
+ };
11
+ }
12
+ function computePerfSummary(recent, windowSize) {
13
+ if (recent.length === 0)
14
+ return defaultPerfSummary(windowSize);
15
+ let totalSum = 0;
16
+ let totalMax = 0;
17
+ let snapSum = 0;
18
+ let snapMax = 0;
19
+ let diffSum = 0;
20
+ let diffMax = 0;
21
+ let snapCount = 0;
22
+ let diffCount = 0;
23
+ for (const s of recent) {
24
+ totalSum += s.totalMs;
25
+ totalMax = Math.max(totalMax, s.totalMs);
26
+ if (typeof s.snapshotMs === 'number') {
27
+ snapSum += s.snapshotMs;
28
+ snapMax = Math.max(snapMax, s.snapshotMs);
29
+ snapCount += 1;
30
+ }
31
+ if (typeof s.diffMs === 'number') {
32
+ diffSum += s.diffMs;
33
+ diffMax = Math.max(diffMax, s.diffMs);
34
+ diffCount += 1;
35
+ }
36
+ }
37
+ return {
38
+ windowSize,
39
+ avgTotalMs: totalSum / recent.length,
40
+ maxTotalMs: totalMax,
41
+ avgSnapshotMs: snapCount ? snapSum / snapCount : undefined,
42
+ maxSnapshotMs: snapCount ? snapMax : undefined,
43
+ avgDiffMs: diffCount ? diffSum / diffCount : undefined,
44
+ maxDiffMs: diffCount ? diffMax : undefined,
45
+ };
46
+ }
47
+ export function createPerfTracker(options) {
48
+ const perfRecent = [];
49
+ const getPerfState = () => {
50
+ if (!options.enabled)
51
+ return undefined;
52
+ return {
53
+ recent: perfRecent,
54
+ summary: computePerfSummary(perfRecent, options.windowSize),
55
+ };
56
+ };
57
+ const record = (sample) => {
58
+ if (!options.enabled)
59
+ return;
60
+ if (options.sampleRate < 1 && Math.random() > options.sampleRate)
61
+ return;
62
+ perfRecent.push(sample);
63
+ while (perfRecent.length > options.windowSize)
64
+ perfRecent.shift();
65
+ const summary = computePerfSummary(perfRecent, options.windowSize);
66
+ options.emit({ type: 'perf', sample, summary, state: options.getState() });
67
+ };
68
+ return {
69
+ enabled: options.enabled,
70
+ getState: getPerfState,
71
+ record,
72
+ };
73
+ }
@@ -0,0 +1,9 @@
1
+ import type { IoSnapshotDiff } from './types.js';
2
+ type DiffOptions = {
3
+ maxDepth?: number;
4
+ maxChanges?: number;
5
+ maxArrayLength?: number;
6
+ };
7
+ export declare function diffSnapshots(prev: unknown, next: unknown, options?: DiffOptions): IoSnapshotDiff[];
8
+ export {};
9
+ //# sourceMappingURL=diff-snapshots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-snapshots.d.ts","sourceRoot":"","sources":["../../src/lib/diff-snapshots.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,KAAK,WAAW,GAAG;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAYF,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,WAAW,GACpB,cAAc,EAAE,CAqClB"}
@@ -0,0 +1,46 @@
1
+ const defaultOptions = {
2
+ maxDepth: 6,
3
+ maxChanges: 500,
4
+ maxArrayLength: 200,
5
+ };
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ export function diffSnapshots(prev, next, options) {
10
+ const o = { ...defaultOptions, ...(options ?? {}) };
11
+ const diffs = [];
12
+ const visit = (path, a, b, depth) => {
13
+ if (diffs.length >= o.maxChanges)
14
+ return;
15
+ if (Object.is(a, b))
16
+ return;
17
+ if (depth >= o.maxDepth) {
18
+ diffs.push({ path, prev: a, next: b });
19
+ return;
20
+ }
21
+ if (Array.isArray(a) && Array.isArray(b)) {
22
+ const len = Math.max(a.length, b.length);
23
+ const capped = Math.min(len, o.maxArrayLength);
24
+ for (let i = 0; i < capped; i += 1) {
25
+ visit([...path, i], a[i], b[i], depth + 1);
26
+ if (diffs.length >= o.maxChanges)
27
+ return;
28
+ }
29
+ if (len !== capped)
30
+ diffs.push({ path, prev: a, next: b });
31
+ return;
32
+ }
33
+ if (isRecord(a) && isRecord(b)) {
34
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
35
+ for (const key of keys) {
36
+ visit([...path, key], a[key], b[key], depth + 1);
37
+ if (diffs.length >= o.maxChanges)
38
+ return;
39
+ }
40
+ return;
41
+ }
42
+ diffs.push({ path, prev: a, next: b });
43
+ };
44
+ visit([], prev, next, 0);
45
+ return diffs;
46
+ }
@@ -0,0 +1,7 @@
1
+ import type { IoHistoryEntry, ReduxDevToolsImportState } from './types.js';
2
+ export declare function exportReduxDevToolsImportState(args: {
3
+ initialState: unknown;
4
+ history: ReadonlyArray<IoHistoryEntry>;
5
+ cursor: number;
6
+ }): ReduxDevToolsImportState;
7
+ //# sourceMappingURL=export-redux-devtools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-redux-devtools.d.ts","sourceRoot":"","sources":["../../src/lib/export-redux-devtools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAgBpB,wBAAgB,8BAA8B,CAAC,IAAI,EAAE;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,wBAAwB,CA6B3B"}
@@ -0,0 +1,41 @@
1
+ function formatPath(path) {
2
+ if (path.length === 0)
3
+ return '$';
4
+ return '$.' + path.map((s) => (typeof s === 'number' ? `[${s}]` : String(s))).join('.');
5
+ }
6
+ function summarizeEntry(entry) {
7
+ const first = entry.patchDiffs[0];
8
+ if (!first)
9
+ return 'IO_UPDATE';
10
+ if (first.op === 'set')
11
+ return `IO_SET ${formatPath(first.path)}`;
12
+ if (first.op === 'splice')
13
+ return `IO_SPLICE ${formatPath(first.path)}`;
14
+ if (first.op === 'sort')
15
+ return `IO_SORT ${formatPath(first.path)}`;
16
+ return 'IO_UPDATE';
17
+ }
18
+ export function exportReduxDevToolsImportState(args) {
19
+ const actionsById = {
20
+ '0': { type: '@@IO/INIT' },
21
+ };
22
+ const computedStates = [{ state: args.initialState }];
23
+ for (let i = 0; i < args.history.length; i += 1) {
24
+ const entry = args.history[i];
25
+ const actionId = String(i + 1);
26
+ actionsById[actionId] = { type: summarizeEntry(entry) };
27
+ computedStates.push({ state: entry.snapshotAfter });
28
+ }
29
+ const stagedActionIds = new Array(args.history.length + 1);
30
+ for (let i = 0; i < stagedActionIds.length; i += 1)
31
+ stagedActionIds[i] = i;
32
+ const currentStateIndex = Math.max(0, Math.min(computedStates.length - 1, args.cursor + 1));
33
+ return {
34
+ actionsById,
35
+ computedStates,
36
+ currentStateIndex,
37
+ nextActionId: args.history.length + 1,
38
+ skippedActionIds: [],
39
+ stagedActionIds,
40
+ };
41
+ }
@@ -0,0 +1,3 @@
1
+ import type { IoPatchDiff, IoPatchDiffTreeNode } from './types.js';
2
+ export declare function buildPatchDiffTree(patches: ReadonlyArray<IoPatchDiff>): IoPatchDiffTreeNode[];
3
+ //# sourceMappingURL=patch-diff-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-diff-tree.d.ts","sourceRoot":"","sources":["../../src/lib/patch-diff-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAanE,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAClC,mBAAmB,EAAE,CA+BvB"}
@@ -0,0 +1,39 @@
1
+ function ensureChild(parent, key, path) {
2
+ if (!parent.childrenMap)
3
+ parent.childrenMap = new Map();
4
+ const existing = parent.childrenMap.get(key);
5
+ if (existing)
6
+ return existing;
7
+ const next = { key, path };
8
+ parent.childrenMap.set(key, next);
9
+ return next;
10
+ }
11
+ export function buildPatchDiffTree(patches) {
12
+ const root = { key: '$', path: [] };
13
+ for (const patch of patches) {
14
+ let node = root;
15
+ const path = patch.path ?? [];
16
+ for (let i = 0; i < path.length; i += 1) {
17
+ const segment = path[i];
18
+ node = ensureChild(node, segment, path.slice(0, i + 1));
19
+ }
20
+ if (!node.patches)
21
+ node.patches = [];
22
+ node.patches.push(patch);
23
+ }
24
+ const toPublic = (n) => {
25
+ const children = n.childrenMap
26
+ ? Array.from(n.childrenMap.values()).map(toPublic)
27
+ : undefined;
28
+ return {
29
+ key: n.key,
30
+ path: n.path,
31
+ children: children && children.length > 0 ? children : undefined,
32
+ patches: n.patches && n.patches.length > 0 ? n.patches : undefined,
33
+ };
34
+ };
35
+ const result = root.childrenMap
36
+ ? Array.from(root.childrenMap.values()).map(toPublic)
37
+ : [];
38
+ return result;
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { IoDevtoolsExportOptions } from './types.js';
2
+ export declare function sanitizeForJson(value: unknown, options?: IoDevtoolsExportOptions): unknown;
3
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAqB1D,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CA4C1F"}
@@ -0,0 +1,70 @@
1
+ const defaultOptions = {
2
+ maxDepth: 8,
3
+ maxArrayLength: 200,
4
+ maxStringLength: 10_000,
5
+ redact: (_path, value) => value,
6
+ };
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ function clampString(value, max) {
11
+ if (value.length <= max)
12
+ return value;
13
+ return value.slice(0, max) + `…(${value.length - max} more)`;
14
+ }
15
+ export function sanitizeForJson(value, options) {
16
+ const o = { ...defaultOptions, ...(options ?? {}) };
17
+ const walk = (path, v, depth) => {
18
+ const redacted = o.redact(path, v);
19
+ if (depth >= o.maxDepth)
20
+ return '[MaxDepth]';
21
+ if (redacted === null)
22
+ return null;
23
+ if (redacted === undefined)
24
+ return undefined;
25
+ if (typeof redacted === 'string')
26
+ return clampString(redacted, o.maxStringLength);
27
+ if (typeof redacted === 'number')
28
+ return Number.isFinite(redacted) ? redacted : String(redacted);
29
+ if (typeof redacted === 'boolean')
30
+ return redacted;
31
+ if (typeof redacted === 'bigint')
32
+ return redacted.toString();
33
+ if (typeof redacted === 'symbol')
34
+ return redacted.toString();
35
+ if (typeof redacted === 'function')
36
+ return '[Function]';
37
+ if (redacted instanceof Date)
38
+ return redacted.toISOString();
39
+ if (redacted instanceof Error) {
40
+ return {
41
+ name: redacted.name,
42
+ message: redacted.message,
43
+ stack: redacted.stack,
44
+ };
45
+ }
46
+ if (Array.isArray(redacted)) {
47
+ const out = [];
48
+ const capped = Math.min(redacted.length, o.maxArrayLength);
49
+ for (let i = 0; i < capped; i += 1)
50
+ out.push(walk([...path, i], redacted[i], depth + 1));
51
+ if (redacted.length > capped)
52
+ out.push(`[+${redacted.length - capped} more items]`);
53
+ return out;
54
+ }
55
+ if (isRecord(redacted)) {
56
+ const out = {};
57
+ for (const [k, child] of Object.entries(redacted)) {
58
+ out[k] = walk([...path, k], child, depth + 1);
59
+ }
60
+ return out;
61
+ }
62
+ try {
63
+ return JSON.parse(JSON.stringify(redacted));
64
+ }
65
+ catch {
66
+ return String(redacted);
67
+ }
68
+ };
69
+ return walk([], value, 0);
70
+ }
@@ -0,0 +1,165 @@
1
+ import type { IoLinkInfo, IoMutationOp, IoPatch, IoUpdate } from '@iostore/store';
2
+ export type Unsubscribe = () => void;
3
+ export type IoPath = IoPatch['path'];
4
+ export type IoErrorHandler = (error: unknown, path: IoPath, operation: IoMutationOp) => void;
5
+ export type IoDevtoolsTarget = {
6
+ snapshot: () => unknown;
7
+ subscribeUpdate: (fn: (u: IoUpdate) => void) => Unsubscribe;
8
+ };
9
+ export type IoDevtoolsSnapshotStrategy = 'never' | 'always';
10
+ export type IoDevtoolsPerfOptions = {
11
+ enabled?: boolean;
12
+ sampleRate?: number;
13
+ windowSize?: number;
14
+ };
15
+ export type IoDevtoolsReduxBridgeOptions = {
16
+ enabled?: boolean;
17
+ name?: string;
18
+ };
19
+ export type IoDevtoolsExportOptions = {
20
+ maxDepth?: number;
21
+ maxArrayLength?: number;
22
+ maxStringLength?: number;
23
+ redact?: (path: IoPath, value: unknown) => unknown;
24
+ };
25
+ export type IoDevtoolsOptions = {
26
+ name?: string;
27
+ enabled?: boolean;
28
+ maxHistory?: number;
29
+ captureSnapshots?: IoDevtoolsSnapshotStrategy;
30
+ perf?: IoDevtoolsPerfOptions;
31
+ export?: IoDevtoolsExportOptions;
32
+ reduxDevTools?: IoDevtoolsReduxBridgeOptions;
33
+ filterPatch?: (patch: IoPatch, update: IoUpdate) => boolean;
34
+ onDevtoolsError?: (error: unknown) => void;
35
+ };
36
+ export type IoDevtoolsPerfSample = {
37
+ patchCount: number;
38
+ intervalMs?: number;
39
+ snapshotMs?: number;
40
+ diffMs?: number;
41
+ totalMs: number;
42
+ };
43
+ export type IoDevtoolsPerfSummary = {
44
+ windowSize: number;
45
+ avgTotalMs: number;
46
+ maxTotalMs: number;
47
+ avgSnapshotMs?: number;
48
+ maxSnapshotMs?: number;
49
+ avgDiffMs?: number;
50
+ maxDiffMs?: number;
51
+ };
52
+ export type IoPatchDiff = {
53
+ op: 'set';
54
+ path: IoPath;
55
+ prev: unknown;
56
+ next: unknown;
57
+ } | {
58
+ op: 'splice';
59
+ path: IoPath;
60
+ start: number;
61
+ deleteCount: number;
62
+ deleted: ReadonlyArray<unknown>;
63
+ items: ReadonlyArray<unknown>;
64
+ } | {
65
+ op: 'sort';
66
+ path: IoPath;
67
+ order: ReadonlyArray<number>;
68
+ };
69
+ export type IoSnapshotDiff = {
70
+ path: IoPath;
71
+ prev: unknown;
72
+ next: unknown;
73
+ };
74
+ export type IoPatchDiffTreeNode = {
75
+ key: PropertyKey;
76
+ path: IoPath;
77
+ children?: IoPatchDiffTreeNode[];
78
+ patches?: IoPatchDiff[];
79
+ };
80
+ export type IoHistoryEntry = {
81
+ id: string;
82
+ timestamp: number;
83
+ update: IoUpdate;
84
+ patchDiffs: IoPatchDiff[];
85
+ snapshotBefore?: unknown;
86
+ snapshotAfter?: unknown;
87
+ perf?: IoDevtoolsPerfSample;
88
+ };
89
+ export type IoDevtoolsState = {
90
+ enabled: boolean;
91
+ paused: boolean;
92
+ cursor: number;
93
+ history: ReadonlyArray<IoHistoryEntry>;
94
+ errors: ReadonlyArray<unknown>;
95
+ links?: IoLinkInfo;
96
+ perf?: {
97
+ recent: ReadonlyArray<IoDevtoolsPerfSample>;
98
+ summary: IoDevtoolsPerfSummary;
99
+ };
100
+ };
101
+ export type IoDevtoolsEvent = {
102
+ type: 'mutation';
103
+ entry: IoHistoryEntry;
104
+ state: IoDevtoolsState;
105
+ } | {
106
+ type: 'error';
107
+ source: 'io' | 'devtools' | 'bridge';
108
+ error: unknown;
109
+ path?: IoPath;
110
+ operation?: IoMutationOp;
111
+ state: IoDevtoolsState;
112
+ } | {
113
+ type: 'timeTravel';
114
+ kind: 'undo' | 'redo' | 'goTo' | 'clear';
115
+ from: number;
116
+ to: number;
117
+ state: IoDevtoolsState;
118
+ } | {
119
+ type: 'perf';
120
+ sample: IoDevtoolsPerfSample;
121
+ summary: IoDevtoolsPerfSummary;
122
+ state: IoDevtoolsState;
123
+ } | {
124
+ type: 'bridge';
125
+ connected: boolean;
126
+ state: IoDevtoolsState;
127
+ };
128
+ export type IoDevtoolsBridge = {
129
+ disconnect: () => void;
130
+ };
131
+ export type IoDevtools = {
132
+ getState: () => IoDevtoolsState;
133
+ subscribe: (listener: (event: IoDevtoolsEvent) => void) => Unsubscribe;
134
+ setEnabled: (enabled: boolean) => void;
135
+ pause: () => void;
136
+ resume: () => void;
137
+ destroy: () => void;
138
+ clear: () => void;
139
+ timeTravel: {
140
+ undo: () => boolean;
141
+ redo: () => boolean;
142
+ goTo: (index: number) => boolean;
143
+ };
144
+ export: {
145
+ json: () => string;
146
+ reduxDevToolsImport: () => ReduxDevToolsImportState;
147
+ };
148
+ connectReduxDevToolsExtension: (options?: {
149
+ window?: unknown;
150
+ name?: string;
151
+ }) => IoDevtoolsBridge | null;
152
+ };
153
+ export type ReduxDevToolsImportState = {
154
+ actionsById: Record<string, {
155
+ type: string;
156
+ }>;
157
+ computedStates: Array<{
158
+ state: unknown;
159
+ }>;
160
+ currentStateIndex: number;
161
+ nextActionId: number;
162
+ skippedActionIds: number[];
163
+ stagedActionIds: number[];
164
+ };
165
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@iostore/devtools",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ "./package.json": "./package.json",
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "!**/*.tsbuildinfo"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "peerDependencies": {
24
+ "@iostore/store": "*"
25
+ },
26
+ "devDependencies": {
27
+ "@iostore/store": "*"
28
+ },
29
+ "nx": {
30
+ "tags": [
31
+ "scope:io-devtools"
32
+ ],
33
+ "targets": {
34
+ "coverage": {
35
+ "executor": "nx:run-commands",
36
+ "cache": false,
37
+ "outputs": [
38
+ "{projectRoot}/test-output/vitest/coverage"
39
+ ],
40
+ "options": {
41
+ "cwd": "packages/io-devtools",
42
+ "command": "vitest run --coverage"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }