@storve/core 1.0.2 → 1.0.4
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/LICENSE +21 -0
- package/README.md +0 -16
- package/dist/adapters/indexedDB.cjs +0 -1
- package/dist/adapters/indexedDB.mjs +0 -1
- package/dist/adapters/localStorage.cjs +0 -1
- package/dist/adapters/localStorage.mjs +0 -1
- package/dist/adapters/memory.cjs +0 -1
- package/dist/adapters/memory.mjs +0 -1
- package/dist/adapters/sessionStorage.cjs +0 -1
- package/dist/adapters/sessionStorage.mjs +0 -1
- package/dist/async-entry.d.ts +0 -1
- package/dist/async.cjs +0 -1
- package/dist/async.d.ts +0 -1
- package/dist/async.mjs +0 -1
- package/dist/batch.d.ts +0 -1
- package/dist/compose.d.ts +0 -1
- package/dist/computed-entry.d.ts +0 -1
- package/dist/computed.cjs +0 -1
- package/dist/computed.d.ts +0 -1
- package/dist/computed.mjs +0 -1
- package/dist/devtools/history.d.ts +0 -1
- package/dist/devtools/index.d.ts +0 -1
- package/dist/devtools/redux-bridge.d.ts +0 -1
- package/dist/devtools/snapshots.d.ts +0 -1
- package/dist/devtools/withDevtools.d.ts +0 -1
- package/dist/devtools.cjs +0 -1
- package/dist/devtools.mjs +0 -1
- package/dist/extensions/noop.d.ts +0 -1
- package/dist/index.cjs +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +0 -1
- package/dist/persist/adapters/indexedDB.d.ts +0 -1
- package/dist/persist/adapters/localStorage.d.ts +0 -1
- package/dist/persist/adapters/memory.d.ts +0 -1
- package/dist/persist/adapters/sessionStorage.d.ts +0 -1
- package/dist/persist/debounce.d.ts +0 -1
- package/dist/persist/hydrate.d.ts +0 -1
- package/dist/persist/index.d.ts +0 -1
- package/dist/persist/serialize.d.ts +0 -1
- package/dist/persist.cjs +0 -1
- package/dist/persist.mjs +0 -1
- package/dist/proxy.d.ts +0 -1
- package/dist/registry-qtr1UpFU.js +0 -1
- package/dist/registry-zaKZ1P-s.js +0 -1
- package/dist/registry.d.ts +0 -1
- package/dist/signals/createSignal.d.ts +0 -1
- package/dist/signals/index.d.ts +0 -1
- package/dist/signals/useSignal.d.ts +0 -1
- package/dist/signals.cjs +0 -1
- package/dist/signals.mjs +0 -1
- package/dist/store.d.ts +0 -1
- package/dist/sync/channel.d.ts +0 -1
- package/dist/sync/index.d.ts +0 -1
- package/dist/sync/protocol.d.ts +0 -1
- package/dist/sync/withSync.d.ts +0 -1
- package/dist/sync.cjs +0 -1
- package/dist/sync.mjs +0 -1
- package/dist/types.d.ts +0 -1
- package/package.json +10 -3
- package/CHANGELOG.md +0 -151
- package/benchmarks/run.ts +0 -102
- package/benchmarks/week2.md +0 -9
- package/benchmarks/week2.ts +0 -64
- package/benchmarks/week4.md +0 -13
- package/benchmarks/week4.ts +0 -178
- package/benchmarks/week5.md +0 -15
- package/benchmarks/week5.ts +0 -184
- package/coverage/coverage-summary.json +0 -31
- package/dist/adapters/indexedDB.cjs.map +0 -1
- package/dist/adapters/indexedDB.mjs.map +0 -1
- package/dist/adapters/localStorage.cjs.map +0 -1
- package/dist/adapters/localStorage.mjs.map +0 -1
- package/dist/adapters/memory.cjs.map +0 -1
- package/dist/adapters/memory.mjs.map +0 -1
- package/dist/adapters/sessionStorage.cjs.map +0 -1
- package/dist/adapters/sessionStorage.mjs.map +0 -1
- package/dist/async-entry.d.ts.map +0 -1
- package/dist/async.cjs.map +0 -1
- package/dist/async.d.ts.map +0 -1
- package/dist/async.mjs.map +0 -1
- package/dist/batch.d.ts.map +0 -1
- package/dist/compose.d.ts.map +0 -1
- package/dist/computed-entry.d.ts.map +0 -1
- package/dist/computed.cjs.map +0 -1
- package/dist/computed.d.ts.map +0 -1
- package/dist/computed.mjs.map +0 -1
- package/dist/devtools/history.d.ts.map +0 -1
- package/dist/devtools/index.d.ts.map +0 -1
- package/dist/devtools/redux-bridge.d.ts.map +0 -1
- package/dist/devtools/snapshots.d.ts.map +0 -1
- package/dist/devtools/withDevtools.d.ts.map +0 -1
- package/dist/devtools.cjs.map +0 -1
- package/dist/devtools.mjs.map +0 -1
- package/dist/extensions/noop.d.ts.map +0 -1
- package/dist/index.cjs.js +0 -118
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.esm.js +0 -116
- package/dist/index.esm.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/persist/adapters/indexedDB.d.ts.map +0 -1
- package/dist/persist/adapters/localStorage.d.ts.map +0 -1
- package/dist/persist/adapters/memory.d.ts.map +0 -1
- package/dist/persist/adapters/sessionStorage.d.ts.map +0 -1
- package/dist/persist/debounce.d.ts.map +0 -1
- package/dist/persist/hydrate.d.ts.map +0 -1
- package/dist/persist/index.d.ts.map +0 -1
- package/dist/persist/serialize.d.ts.map +0 -1
- package/dist/persist.cjs.map +0 -1
- package/dist/persist.mjs.map +0 -1
- package/dist/proxy.d.ts.map +0 -1
- package/dist/registry-D3X0HSbl.js +0 -26
- package/dist/registry-D3X0HSbl.js.map +0 -1
- package/dist/registry-RDjbeJdx.js +0 -29
- package/dist/registry-RDjbeJdx.js.map +0 -1
- package/dist/registry-qtr1UpFU.js.map +0 -1
- package/dist/registry-zaKZ1P-s.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/signals/createSignal.d.ts.map +0 -1
- package/dist/signals/index.d.ts.map +0 -1
- package/dist/signals/useSignal.d.ts.map +0 -1
- package/dist/signals.cjs.map +0 -1
- package/dist/signals.mjs.map +0 -1
- package/dist/stats.html +0 -4949
- package/dist/store.d.ts.map +0 -1
- package/dist/sync/channel.d.ts.map +0 -1
- package/dist/sync/index.d.ts.map +0 -1
- package/dist/sync/protocol.d.ts.map +0 -1
- package/dist/sync/withSync.d.ts.map +0 -1
- package/dist/sync.cjs.map +0 -1
- package/dist/sync.mjs.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/rollup.config.mjs +0 -44
- package/src/async-entry.ts +0 -6
- package/src/async.ts +0 -240
- package/src/batch.ts +0 -33
- package/src/compose.ts +0 -50
- package/src/computed-entry.ts +0 -6
- package/src/computed.ts +0 -187
- package/src/devtools/history.ts +0 -103
- package/src/devtools/index.ts +0 -5
- package/src/devtools/redux-bridge.ts +0 -70
- package/src/devtools/snapshots.ts +0 -54
- package/src/devtools/withDevtools.ts +0 -196
- package/src/extensions/noop.ts +0 -12
- package/src/index.ts +0 -4
- package/src/persist/adapters/indexedDB.ts +0 -114
- package/src/persist/adapters/localStorage.ts +0 -28
- package/src/persist/adapters/memory.ts +0 -26
- package/src/persist/adapters/sessionStorage.ts +0 -28
- package/src/persist/debounce.ts +0 -28
- package/src/persist/hydrate.ts +0 -60
- package/src/persist/index.ts +0 -141
- package/src/persist/serialize.ts +0 -60
- package/src/proxy.ts +0 -87
- package/src/registry.ts +0 -67
- package/src/signals/createSignal.ts +0 -81
- package/src/signals/index.ts +0 -20
- package/src/signals/useSignal.ts +0 -18
- package/src/store.ts +0 -250
- package/src/sync/channel.ts +0 -15
- package/src/sync/index.ts +0 -3
- package/src/sync/protocol.ts +0 -18
- package/src/sync/withSync.ts +0 -147
- package/src/types.ts +0 -159
- package/tests/async.test.ts +0 -1100
- package/tests/batch.test.ts +0 -41
- package/tests/compose.test.ts +0 -209
- package/tests/computed.test.ts +0 -867
- package/tests/devtools.test.ts +0 -1039
- package/tests/integration/persist.integration.test.ts +0 -258
- package/tests/integration/signals.integration.test.ts +0 -309
- package/tests/integration.test.ts +0 -278
- package/tests/persist/adapters/indexedDB.adapter.test.ts +0 -185
- package/tests/persist/adapters/localStorage.adapter.test.ts +0 -105
- package/tests/persist/adapters/memory.adapter.test.ts +0 -112
- package/tests/persist/adapters/sessionStorage.adapter.test.ts +0 -128
- package/tests/persist/debounce.test.ts +0 -121
- package/tests/persist/hydrate.test.ts +0 -120
- package/tests/persist/migrate.test.ts +0 -208
- package/tests/persist/persist.test.ts +0 -357
- package/tests/persist/serialize.test.ts +0 -128
- package/tests/proxy.test.ts +0 -473
- package/tests/registry.test.ts +0 -67
- package/tests/signals/derived.test.ts +0 -244
- package/tests/signals/inference.test.ts +0 -108
- package/tests/signals/signal.test.ts +0 -348
- package/tests/signals/useSignal.test.tsx +0 -275
- package/tests/store.test.ts +0 -482
- package/tests/stress.test.ts +0 -268
- package/tests/sync.test.ts +0 -576
- package/tests/types.test.ts +0 -32
- package/tests/v0.3.test.ts +0 -813
- package/tree-shake-test.js +0 -1
- package/tsconfig.json +0 -15
- package/vitest.config.ts +0 -22
- package/vitest_play.ts +0 -7
package/src/devtools/history.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal structure for a history entry in the ring buffer.
|
|
3
|
-
*/
|
|
4
|
-
export interface HistoryEntry<S> {
|
|
5
|
-
state: S;
|
|
6
|
-
timestamp: number;
|
|
7
|
-
actionName: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Ring buffer for time-travel debugging.
|
|
12
|
-
* Fixed capacity ensures no unbounded growth.
|
|
13
|
-
*/
|
|
14
|
-
export interface RingBuffer<S> {
|
|
15
|
-
entries: HistoryEntry<S>[];
|
|
16
|
-
cursor: number;
|
|
17
|
-
capacity: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Creates an empty ring buffer with the given capacity.
|
|
22
|
-
* @param capacity - Max number of entries (default 50)
|
|
23
|
-
*/
|
|
24
|
-
export function createRingBuffer<S>(capacity: number = 50): RingBuffer<S> {
|
|
25
|
-
return {
|
|
26
|
-
entries: [],
|
|
27
|
-
cursor: -1,
|
|
28
|
-
capacity,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Pushes a new state to the ring buffer.
|
|
34
|
-
* Discards any redo stack (entries after the cursor).
|
|
35
|
-
* Drops oldest entry if capacity is exceeded.
|
|
36
|
-
*/
|
|
37
|
-
export function push<S>(buffer: RingBuffer<S>, state: S, actionName: string): RingBuffer<S> {
|
|
38
|
-
const entry: HistoryEntry<S> = {
|
|
39
|
-
state,
|
|
40
|
-
timestamp: Date.now(),
|
|
41
|
-
actionName,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Discard any entries after the current cursor (redo stack)
|
|
45
|
-
const currentEntries = buffer.entries.slice(0, buffer.cursor + 1);
|
|
46
|
-
|
|
47
|
-
let newEntries = [...currentEntries, entry];
|
|
48
|
-
let newCursor = newEntries.length - 1;
|
|
49
|
-
|
|
50
|
-
// Maintain capacity
|
|
51
|
-
if (newEntries.length > buffer.capacity) {
|
|
52
|
-
newEntries = newEntries.slice(newEntries.length - buffer.capacity);
|
|
53
|
-
newCursor = newEntries.length - 1;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
...buffer,
|
|
58
|
-
entries: newEntries,
|
|
59
|
-
cursor: newCursor,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Moves the cursor back one position and returns the state.
|
|
65
|
-
*/
|
|
66
|
-
export function undo<S>(buffer: RingBuffer<S>): { buffer: RingBuffer<S>; state: S | null } {
|
|
67
|
-
if (buffer.cursor > 0) {
|
|
68
|
-
const newCursor = buffer.cursor - 1;
|
|
69
|
-
return {
|
|
70
|
-
buffer: { ...buffer, cursor: newCursor },
|
|
71
|
-
state: buffer.entries[newCursor].state,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return { buffer, state: null };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Moves the cursor forward one position and returns the state.
|
|
79
|
-
*/
|
|
80
|
-
export function redo<S>(buffer: RingBuffer<S>): { buffer: RingBuffer<S>; state: S | null } {
|
|
81
|
-
if (buffer.cursor < buffer.entries.length - 1) {
|
|
82
|
-
const newCursor = buffer.cursor + 1;
|
|
83
|
-
return {
|
|
84
|
-
buffer: { ...buffer, cursor: newCursor },
|
|
85
|
-
state: buffer.entries[newCursor].state,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return { buffer, state: null };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns true if the ring buffer can perform an undo.
|
|
93
|
-
*/
|
|
94
|
-
export function canUndo<S>(buffer: RingBuffer<S>): boolean {
|
|
95
|
-
return buffer.cursor > 0;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Returns true if the ring buffer can perform a redo.
|
|
100
|
-
*/
|
|
101
|
-
export function canRedo<S>(buffer: RingBuffer<S>): boolean {
|
|
102
|
-
return buffer.cursor < buffer.entries.length - 1;
|
|
103
|
-
}
|
package/src/devtools/index.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Store } from '../types';
|
|
2
|
-
import { RingBuffer } from './history';
|
|
3
|
-
|
|
4
|
-
/** @internal */
|
|
5
|
-
export interface DevtoolsInternals<S> {
|
|
6
|
-
buffer: RingBuffer<S>;
|
|
7
|
-
initialState: S;
|
|
8
|
-
snapshots: Map<string, { state: S; timestamp: number }>;
|
|
9
|
-
_lastActionName: string | null;
|
|
10
|
-
_applySnapshot: (state: S) => void;
|
|
11
|
-
_isInternalUpdate: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface DevtoolsInstance {
|
|
15
|
-
init(state: unknown): void;
|
|
16
|
-
send(action: { type: string }, state: unknown): void;
|
|
17
|
-
subscribe(listener: (msg: { type: string; payload?: { type: string }; state?: string }) => void): () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ReduxDevtoolsExtension {
|
|
21
|
-
connect(options: { name: string; maxAge?: number }): DevtoolsInstance;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Connects a devtools-enabled store to the Redux DevTools browser extension.
|
|
26
|
-
*/
|
|
27
|
-
export function connectReduxDevtools<S extends object>(
|
|
28
|
-
store: Store<S> & { __devtools: DevtoolsInternals<S> },
|
|
29
|
-
name: string
|
|
30
|
-
): () => void {
|
|
31
|
-
if (typeof window === 'undefined') return () => {};
|
|
32
|
-
|
|
33
|
-
const extension = (window as unknown as { __REDUX_DEVTOOLS_EXTENSION__?: ReduxDevtoolsExtension }).__REDUX_DEVTOOLS_EXTENSION__;
|
|
34
|
-
if (!extension) return () => {};
|
|
35
|
-
|
|
36
|
-
const devtools: DevtoolsInstance = extension.connect({
|
|
37
|
-
name: `Storve | ${name}`,
|
|
38
|
-
maxAge: store.__devtools.buffer.capacity,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
devtools.init(store.getState());
|
|
42
|
-
|
|
43
|
-
const unsubscribeStore = store.subscribe((state) => {
|
|
44
|
-
// Only send updates if they are NOT internal (undo/redo/restore from ourselves)
|
|
45
|
-
if (store.__devtools._isInternalUpdate) return;
|
|
46
|
-
|
|
47
|
-
devtools.send(
|
|
48
|
-
{ type: store.__devtools._lastActionName ?? 'setState' },
|
|
49
|
-
state
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const unsubscribeDevtools = devtools.subscribe((message) => {
|
|
54
|
-
if (message.type === 'DISPATCH') {
|
|
55
|
-
if (message.payload?.type === 'JUMP_TO_STATE' || message.payload?.type === 'JUMP_TO_ACTION') {
|
|
56
|
-
if (message.state) {
|
|
57
|
-
store.__devtools._applySnapshot(JSON.parse(message.state));
|
|
58
|
-
}
|
|
59
|
-
} else if (message.payload?.type === 'RESET') {
|
|
60
|
-
store.__devtools._applySnapshot(store.__devtools.initialState);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return () => {
|
|
67
|
-
unsubscribeStore();
|
|
68
|
-
unsubscribeDevtools();
|
|
69
|
-
};
|
|
70
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal structure for a named snapshot entry.
|
|
3
|
-
*/
|
|
4
|
-
export interface SnapshotEntry<S> {
|
|
5
|
-
state: S;
|
|
6
|
-
timestamp: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* A Map of named state checkpoints.
|
|
11
|
-
*/
|
|
12
|
-
export type SnapshotMap<S> = Map<string, SnapshotEntry<S>>;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Creates a new empty snapshot map.
|
|
16
|
-
*/
|
|
17
|
-
export function createSnapshotMap<S>(): SnapshotMap<S> {
|
|
18
|
-
return new Map<string, SnapshotEntry<S>>();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Saves a state snapshot under the given name.
|
|
23
|
-
*/
|
|
24
|
-
export function saveSnapshot<S>(map: SnapshotMap<S>, name: string, state: S): SnapshotMap<S> {
|
|
25
|
-
const nextMap = new Map(map);
|
|
26
|
-
nextMap.set(name, {
|
|
27
|
-
state,
|
|
28
|
-
timestamp: Date.now(),
|
|
29
|
-
});
|
|
30
|
-
return nextMap;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Returns the snapshot entry for the given name, or null if not found.
|
|
35
|
-
*/
|
|
36
|
-
export function getSnapshot<S>(map: SnapshotMap<S>, name: string): SnapshotEntry<S> | null {
|
|
37
|
-
return map.get(name) || null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Removes the snapshot with the given name.
|
|
42
|
-
*/
|
|
43
|
-
export function deleteSnapshot<S>(map: SnapshotMap<S>, name: string): SnapshotMap<S> {
|
|
44
|
-
const nextMap = new Map(map);
|
|
45
|
-
nextMap.delete(name);
|
|
46
|
-
return nextMap;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Returns an array of all snapshot names.
|
|
51
|
-
*/
|
|
52
|
-
export function listSnapshots<S>(map: SnapshotMap<S>): string[] {
|
|
53
|
-
return Array.from(map.keys());
|
|
54
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { registerExtension } from '../registry';
|
|
2
|
-
import { createRingBuffer, push, undo, redo, canUndo, canRedo } from './history';
|
|
3
|
-
import { createSnapshotMap, saveSnapshot, getSnapshot, deleteSnapshot, listSnapshots } from './snapshots';
|
|
4
|
-
import { connectReduxDevtools, DevtoolsInternals } from './redux-bridge';
|
|
5
|
-
import type { Store, StoreState } from '../types';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Configuration options for DevTools.
|
|
9
|
-
*/
|
|
10
|
-
export interface DevtoolsOptions {
|
|
11
|
-
/** Label shown in Redux DevTools panel */
|
|
12
|
-
name: string;
|
|
13
|
-
/** Max history entries in ring buffer (default 50) */
|
|
14
|
-
maxHistory?: number;
|
|
15
|
-
/** Whether devtools are enabled (default true) */
|
|
16
|
-
enabled?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** @internal WeakMap to store devtools options for definitions without polluting state */
|
|
20
|
-
const DEVTOOLS_OPTIONS = new WeakMap<object, DevtoolsOptions>();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Augmented store type with devtools properties.
|
|
24
|
-
* @internal
|
|
25
|
-
*/
|
|
26
|
-
type StoreWithDevtools<S extends object> = Store<S> & {
|
|
27
|
-
__devtools?: DevtoolsInternals<S>;
|
|
28
|
-
undo?: () => void;
|
|
29
|
-
redo?: () => void;
|
|
30
|
-
canUndo?: boolean;
|
|
31
|
-
canRedo?: boolean;
|
|
32
|
-
snapshot?: (name: string) => void;
|
|
33
|
-
restore?: (name: string) => void;
|
|
34
|
-
deleteSnapshot?: (name: string) => void;
|
|
35
|
-
clearHistory?: () => void;
|
|
36
|
-
history?: readonly S[];
|
|
37
|
-
snapshots?: readonly string[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Wraps a store definition with DevTools capabilities.
|
|
43
|
-
* Must be imported to register the devtools extension.
|
|
44
|
-
*/
|
|
45
|
-
export function withDevtools<D extends object>(
|
|
46
|
-
definition: D,
|
|
47
|
-
options: DevtoolsOptions
|
|
48
|
-
): D {
|
|
49
|
-
DEVTOOLS_OPTIONS.set(definition, options);
|
|
50
|
-
return definition;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Register the extension via the registry pattern
|
|
54
|
-
registerExtension({
|
|
55
|
-
key: 'devtools',
|
|
56
|
-
processDefinition: (definition) => {
|
|
57
|
-
const options = DEVTOOLS_OPTIONS.get(definition);
|
|
58
|
-
if (!options || options.enabled === false) return { state: {} };
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
state: {},
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
extendStore: (context) => {
|
|
65
|
-
const { store, definition } = context as { store: Store<object>; definition: object };
|
|
66
|
-
const options = DEVTOOLS_OPTIONS.get(definition);
|
|
67
|
-
if (!options || options.enabled === false) return {};
|
|
68
|
-
|
|
69
|
-
const initialState = store.getState();
|
|
70
|
-
const internals: DevtoolsInternals<object> = {
|
|
71
|
-
buffer: createRingBuffer<object>(options.maxHistory || 50),
|
|
72
|
-
snapshots: createSnapshotMap<object>(),
|
|
73
|
-
initialState,
|
|
74
|
-
_isInternalUpdate: false,
|
|
75
|
-
_lastActionName: null,
|
|
76
|
-
_applySnapshot: (state: object) => {
|
|
77
|
-
internals._isInternalUpdate = true;
|
|
78
|
-
store.setState(state as unknown as Partial<StoreState<object>>);
|
|
79
|
-
internals._isInternalUpdate = false;
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
const devStore = store as unknown as StoreWithDevtools<object>;
|
|
83
|
-
devStore.__devtools = internals;
|
|
84
|
-
|
|
85
|
-
// One-shot flag: the very first subscribe callback after withSync broadcasts
|
|
86
|
-
// REQUEST_STATE may fire setState back to initialState. We skip that single echo
|
|
87
|
-
// so it doesn't appear as a spurious history entry.
|
|
88
|
-
let _initEchoPending = true;
|
|
89
|
-
|
|
90
|
-
// Use subscribe instead of wrapping setState to capture history.
|
|
91
|
-
// This is more robust against extension ordering issues.
|
|
92
|
-
store.subscribe(() => {
|
|
93
|
-
if (internals._isInternalUpdate) return;
|
|
94
|
-
|
|
95
|
-
const currentState = store.getState();
|
|
96
|
-
|
|
97
|
-
// On first callback: if there's no explicit action name and state matches
|
|
98
|
-
// initialState, this is likely a withSync echo — skip once and clear flag.
|
|
99
|
-
if (_initEchoPending && internals._lastActionName === null) {
|
|
100
|
-
const init = internals.initialState as Record<string, unknown>;
|
|
101
|
-
const curr = currentState as Record<string, unknown>;
|
|
102
|
-
const keys = Object.keys(init);
|
|
103
|
-
const matchesInit = keys.length === Object.keys(curr).length
|
|
104
|
-
&& keys.every((k) => curr[k] === init[k]);
|
|
105
|
-
if (matchesInit) {
|
|
106
|
-
_initEchoPending = false;
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
_initEchoPending = false;
|
|
111
|
-
|
|
112
|
-
const actionName = internals._lastActionName ?? 'setState';
|
|
113
|
-
internals.buffer = push(internals.buffer, currentState, actionName);
|
|
114
|
-
internals._lastActionName = null; // Reset after capture
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Bridge actions to capture names.
|
|
118
|
-
const rawActions = store.actions as Record<string, (...args: unknown[]) => unknown>;
|
|
119
|
-
Object.keys(rawActions).forEach((key) => {
|
|
120
|
-
const original = rawActions[key];
|
|
121
|
-
const wrapped = (...args: unknown[]) => {
|
|
122
|
-
internals._lastActionName = key;
|
|
123
|
-
return original(...args);
|
|
124
|
-
};
|
|
125
|
-
rawActions[key] = wrapped;
|
|
126
|
-
// Also update the store property if it was bound directly
|
|
127
|
-
const storeAsRecord = store as unknown as Record<string, unknown>;
|
|
128
|
-
if (storeAsRecord[key] === original) {
|
|
129
|
-
storeAsRecord[key] = wrapped;
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
if (typeof window !== 'undefined') {
|
|
134
|
-
connectReduxDevtools(devStore as Required<StoreWithDevtools<object>>, options.name);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
undo: () => {
|
|
139
|
-
const { buffer, state } = undo(internals.buffer);
|
|
140
|
-
if (state) {
|
|
141
|
-
internals.buffer = buffer;
|
|
142
|
-
internals._applySnapshot(state);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
redo: () => {
|
|
146
|
-
const { buffer, state } = redo(internals.buffer);
|
|
147
|
-
if (state) {
|
|
148
|
-
internals.buffer = buffer;
|
|
149
|
-
internals._applySnapshot(state);
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
get canUndo() {
|
|
153
|
-
return canUndo(internals.buffer);
|
|
154
|
-
},
|
|
155
|
-
get canRedo() {
|
|
156
|
-
return canRedo(internals.buffer);
|
|
157
|
-
},
|
|
158
|
-
snapshot: (name: string) => {
|
|
159
|
-
internals.snapshots = saveSnapshot(internals.snapshots, name, store.getState());
|
|
160
|
-
// We use an internal update to trigger subscribers without pushing to history
|
|
161
|
-
internals._isInternalUpdate = true;
|
|
162
|
-
store.setState({} as unknown as Partial<StoreState<object>>);
|
|
163
|
-
internals._isInternalUpdate = false;
|
|
164
|
-
},
|
|
165
|
-
restore: (name: string) => {
|
|
166
|
-
const entry = getSnapshot(internals.snapshots, name);
|
|
167
|
-
if (!entry) {
|
|
168
|
-
throw new Error(`Storve DevTools: Snapshot "${name}" not found.`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// restore() DOES push to history
|
|
172
|
-
internals._applySnapshot(entry.state);
|
|
173
|
-
internals.buffer = push(internals.buffer, entry.state, `restore('${name}')`);
|
|
174
|
-
},
|
|
175
|
-
deleteSnapshot: (name: string) => {
|
|
176
|
-
internals.snapshots = deleteSnapshot(internals.snapshots, name);
|
|
177
|
-
internals._isInternalUpdate = true;
|
|
178
|
-
store.setState({} as unknown as Partial<StoreState<object>>);
|
|
179
|
-
internals._isInternalUpdate = false;
|
|
180
|
-
},
|
|
181
|
-
clearHistory: () => {
|
|
182
|
-
internals.buffer = createRingBuffer(internals.buffer.capacity);
|
|
183
|
-
internals._isInternalUpdate = true;
|
|
184
|
-
store.setState({} as unknown as Partial<StoreState<object>>);
|
|
185
|
-
internals._isInternalUpdate = false;
|
|
186
|
-
},
|
|
187
|
-
get history() {
|
|
188
|
-
return [...internals.buffer.entries];
|
|
189
|
-
},
|
|
190
|
-
get snapshots() {
|
|
191
|
-
return listSnapshots(internals.snapshots);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
package/src/extensions/noop.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { createStore } from './store';
|
|
2
|
-
export { batch } from './batch';
|
|
3
|
-
export { compose } from './compose';
|
|
4
|
-
export type { Store, StoreDefinition, Listener, Unsubscribe, StoreOptions, StoreState, StoreActions, AsyncState, AsyncOptions, AsyncStatus, ComputedValue, WritableStoreState, ComputedKeys } from './types';
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import type { PersistAdapter } from '../index.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates an IndexedDB persistence adapter.
|
|
5
|
-
* Lazily opens the database on first interaction and caches the Promise.
|
|
6
|
-
* Safe for Server-Side Rendering (SSR) — if 'indexedDB' is totally unavailable,
|
|
7
|
-
* methods elegantly degrade to returning null / no-op promises.
|
|
8
|
-
*
|
|
9
|
-
* @param {string} [dbName='storve-persist'] - Optional custom database name.
|
|
10
|
-
* @returns {PersistAdapter} The IndexedDB persistence adapter.
|
|
11
|
-
*/
|
|
12
|
-
export function indexedDBAdapter(dbName: string = 'storve-persist'): PersistAdapter {
|
|
13
|
-
const STORE_NAME = 'keyval'
|
|
14
|
-
const isServer = typeof indexedDB === 'undefined'
|
|
15
|
-
let dbPromise: Promise<IDBDatabase | null> | null = null
|
|
16
|
-
|
|
17
|
-
function getDB(): Promise<IDBDatabase | null> {
|
|
18
|
-
if (isServer) return Promise.resolve(null)
|
|
19
|
-
if (dbPromise !== null) return dbPromise
|
|
20
|
-
|
|
21
|
-
dbPromise = new Promise((resolve) => {
|
|
22
|
-
try {
|
|
23
|
-
const request = indexedDB.open(dbName, 1)
|
|
24
|
-
|
|
25
|
-
request.onupgradeneeded = () => {
|
|
26
|
-
const db = request.result
|
|
27
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
28
|
-
db.createObjectStore(STORE_NAME)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
request.onsuccess = () => {
|
|
33
|
-
resolve(request.result)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
request.onerror = () => {
|
|
37
|
-
console.warn(`[storve] Failed to open IndexedDB "${dbName}"`)
|
|
38
|
-
resolve(null)
|
|
39
|
-
}
|
|
40
|
-
} catch (err) {
|
|
41
|
-
console.warn(`[storve] Exception opening IndexedDB "${dbName}":`, err)
|
|
42
|
-
resolve(null)
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
return dbPromise
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
async getItem(key: string): Promise<string | null> {
|
|
51
|
-
const db = await getDB()
|
|
52
|
-
if (db === null) return null
|
|
53
|
-
|
|
54
|
-
return new Promise((resolve) => {
|
|
55
|
-
try {
|
|
56
|
-
const transaction = db.transaction(STORE_NAME, 'readonly')
|
|
57
|
-
const store = transaction.objectStore(STORE_NAME)
|
|
58
|
-
const request = store.get(key)
|
|
59
|
-
|
|
60
|
-
request.onsuccess = () => {
|
|
61
|
-
const result = request.result
|
|
62
|
-
if (typeof result === 'string') {
|
|
63
|
-
resolve(result)
|
|
64
|
-
} else {
|
|
65
|
-
resolve(null)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
request.onerror = () => {
|
|
70
|
-
resolve(null)
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
resolve(null)
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
async setItem(key: string, value: string): Promise<void> {
|
|
79
|
-
const db = await getDB()
|
|
80
|
-
if (db === null) return
|
|
81
|
-
|
|
82
|
-
return new Promise((resolve) => {
|
|
83
|
-
try {
|
|
84
|
-
const transaction = db.transaction(STORE_NAME, 'readwrite')
|
|
85
|
-
const store = transaction.objectStore(STORE_NAME)
|
|
86
|
-
const request = store.put(value, key)
|
|
87
|
-
|
|
88
|
-
request.onsuccess = () => resolve()
|
|
89
|
-
request.onerror = () => resolve()
|
|
90
|
-
} catch {
|
|
91
|
-
resolve()
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
async removeItem(key: string): Promise<void> {
|
|
97
|
-
const db = await getDB()
|
|
98
|
-
if (db === null) return
|
|
99
|
-
|
|
100
|
-
return new Promise((resolve) => {
|
|
101
|
-
try {
|
|
102
|
-
const transaction = db.transaction(STORE_NAME, 'readwrite')
|
|
103
|
-
const store = transaction.objectStore(STORE_NAME)
|
|
104
|
-
const request = store.delete(key)
|
|
105
|
-
|
|
106
|
-
request.onsuccess = () => resolve()
|
|
107
|
-
request.onerror = () => resolve()
|
|
108
|
-
} catch {
|
|
109
|
-
resolve()
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { PersistAdapter } from '../index.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a localStorage persistence adapter.
|
|
5
|
-
* Uses window.localStorage to automatically persist state modifications in the browser.
|
|
6
|
-
* Safe for Server-Side Rendering (SSR) — if 'window' is completely undefined,
|
|
7
|
-
* methods gracefully return null or perform no-ops.
|
|
8
|
-
*
|
|
9
|
-
* @returns {PersistAdapter} The localStorage persistence adapter.
|
|
10
|
-
*/
|
|
11
|
-
export function localStorageAdapter(): PersistAdapter {
|
|
12
|
-
const isServer = typeof window === 'undefined'
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
getItem(key: string): string | null {
|
|
16
|
-
if (isServer) return null
|
|
17
|
-
return window.localStorage.getItem(key)
|
|
18
|
-
},
|
|
19
|
-
setItem(key: string, value: string): void {
|
|
20
|
-
if (isServer) return
|
|
21
|
-
window.localStorage.setItem(key, value)
|
|
22
|
-
},
|
|
23
|
-
removeItem(key: string): void {
|
|
24
|
-
if (isServer) return
|
|
25
|
-
window.localStorage.removeItem(key)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { PersistAdapter } from '../index.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates an entirely isolated memory-based persistence adapter.
|
|
5
|
-
* This adapter uses a closure-scoped Map to store data, ensuring fully
|
|
6
|
-
* segregated instances without any module-level state.
|
|
7
|
-
* Ideal for testing or Node/SSR environments where no real storage is available.
|
|
8
|
-
*
|
|
9
|
-
* @returns {PersistAdapter} An isolated memory adapter instance.
|
|
10
|
-
*/
|
|
11
|
-
export function memoryAdapter(): PersistAdapter {
|
|
12
|
-
const store = new Map<string, string>()
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
getItem(key: string): string | null {
|
|
16
|
-
const value = store.get(key)
|
|
17
|
-
return value !== undefined ? value : null
|
|
18
|
-
},
|
|
19
|
-
setItem(key: string, value: string): void {
|
|
20
|
-
store.set(key, value)
|
|
21
|
-
},
|
|
22
|
-
removeItem(key: string): void {
|
|
23
|
-
store.delete(key)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { PersistAdapter } from '../index.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a sessionStorage persistence adapter.
|
|
5
|
-
* Uses window.sessionStorage to persist state for the lifespan of the browser tab.
|
|
6
|
-
* Safe for Server-Side Rendering (SSR) — if 'window' is completely undefined,
|
|
7
|
-
* methods gracefully return null or perform no-ops.
|
|
8
|
-
*
|
|
9
|
-
* @returns {PersistAdapter} The sessionStorage persistence adapter.
|
|
10
|
-
*/
|
|
11
|
-
export function sessionStorageAdapter(): PersistAdapter {
|
|
12
|
-
const isServer = typeof window === 'undefined'
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
getItem(key: string): string | null {
|
|
16
|
-
if (isServer) return null
|
|
17
|
-
return window.sessionStorage.getItem(key)
|
|
18
|
-
},
|
|
19
|
-
setItem(key: string, value: string): void {
|
|
20
|
-
if (isServer) return
|
|
21
|
-
window.sessionStorage.setItem(key, value)
|
|
22
|
-
},
|
|
23
|
-
removeItem(key: string): void {
|
|
24
|
-
if (isServer) return
|
|
25
|
-
window.sessionStorage.removeItem(key)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
package/src/persist/debounce.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a debounced version of a provided function that delays its execution until after
|
|
3
|
-
* the specified milliseconds have elapsed since the last time it was called.
|
|
4
|
-
* If 'ms' is 0, the function invokes immediately.
|
|
5
|
-
*
|
|
6
|
-
* @template T - The arguments type array.
|
|
7
|
-
* @param {(...args: T) => void} fn - The function to debounce.
|
|
8
|
-
* @param {number} ms - The number of milliseconds to wait.
|
|
9
|
-
* @returns {(...args: T) => void} The new debounced function.
|
|
10
|
-
*/
|
|
11
|
-
export function createDebounce<T extends unknown[]>(fn: (...args: T) => void, ms: number): (...args: T) => void {
|
|
12
|
-
let timerId: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
-
|
|
14
|
-
return function(...args: T): void {
|
|
15
|
-
if (ms === 0) {
|
|
16
|
-
fn(...args)
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (timerId !== undefined) {
|
|
21
|
-
clearTimeout(timerId)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
timerId = setTimeout(() => {
|
|
25
|
-
fn(...args)
|
|
26
|
-
}, ms)
|
|
27
|
-
}
|
|
28
|
-
}
|