stunk 2.0.0 → 2.2.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/LICENSE +27 -27
- package/README.md +43 -43
- package/dist/src/core/asyncChunk.d.ts +28 -0
- package/dist/{core → src/core}/asyncChunk.js +8 -4
- package/dist/{core → src/core}/computed.d.ts +5 -0
- package/dist/src/core/computed.js +58 -0
- package/dist/{core → src/core}/core.d.ts +4 -0
- package/dist/{core → src/core}/core.js +41 -33
- package/dist/src/core/selector.d.ts +17 -0
- package/dist/src/core/selector.js +45 -0
- package/dist/{core → src/core}/types.d.ts +3 -3
- package/dist/{use-react → src/use-react}/hooks/useAsyncChunk.d.ts +3 -3
- package/dist/{use-react → src/use-react}/hooks/useAsyncChunk.js +1 -1
- package/dist/{use-react → src/use-react}/hooks/useDerive.js +8 -2
- package/dist/{utils.d.ts → src/utils.d.ts} +1 -0
- package/dist/{utils.js → src/utils.js} +34 -0
- package/dist/tests/async-chunk.test.d.ts +1 -0
- package/dist/tests/async-chunk.test.js +164 -0
- package/dist/tests/batch-chunk.test.d.ts +1 -0
- package/dist/tests/batch-chunk.test.js +89 -0
- package/dist/tests/chunk.test.d.ts +1 -0
- package/dist/tests/chunk.test.js +215 -0
- package/dist/tests/computed.test.d.ts +1 -0
- package/dist/tests/computed.test.js +192 -0
- package/dist/tests/diamond-dep.test.d.ts +1 -0
- package/dist/tests/diamond-dep.test.js +74 -0
- package/dist/tests/history.test.d.ts +1 -0
- package/dist/tests/history.test.js +73 -0
- package/dist/tests/middleware.test.d.ts +1 -0
- package/dist/tests/middleware.test.js +30 -0
- package/dist/tests/persist.test.d.ts +1 -0
- package/dist/tests/persist.test.js +43 -0
- package/dist/tests/select-chunk.test.d.ts +1 -0
- package/dist/tests/select-chunk.test.js +167 -0
- package/package.json +97 -79
- package/dist/core/asyncChunk.d.ts +0 -22
- package/dist/core/computed.js +0 -42
- package/dist/core/selector.d.ts +0 -2
- package/dist/core/selector.js +0 -23
- package/jest.config.js +0 -4
- package/src/core/asyncChunk.ts +0 -83
- package/src/core/computed.ts +0 -65
- package/src/core/core.ts +0 -128
- package/src/core/selector.ts +0 -27
- package/src/core/types.ts +0 -17
- package/src/index.ts +0 -10
- package/src/middleware/history.ts +0 -113
- package/src/middleware/index.ts +0 -7
- package/src/middleware/logger.ts +0 -6
- package/src/middleware/persistence.ts +0 -45
- package/src/middleware/validator.ts +0 -8
- package/src/use-react/hooks/useAsyncChunk.ts +0 -38
- package/src/use-react/hooks/useChunk.ts +0 -40
- package/src/use-react/hooks/useChunkProperty.ts +0 -21
- package/src/use-react/hooks/useChunkValue.ts +0 -15
- package/src/use-react/hooks/useChunkValues.ts +0 -35
- package/src/use-react/hooks/useComputed.ts +0 -34
- package/src/use-react/hooks/useDerive.ts +0 -15
- package/src/use-react/index.ts +0 -9
- package/src/utils.ts +0 -103
- package/tests/async-chunk.test.ts +0 -215
- package/tests/batch-chunk.test.ts +0 -108
- package/tests/chunk.test.ts +0 -189
- package/tests/computed.test.ts +0 -93
- package/tests/history.test.ts +0 -99
- package/tests/middleware.test.ts +0 -37
- package/tests/persist.test.ts +0 -57
- package/tests/select-chunk.test.ts +0 -133
- package/tests/update.test.ts +0 -70
- package/tsconfig.json +0 -23
- /package/dist/{core → src/core}/types.js +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{middleware → src/middleware}/history.d.ts +0 -0
- /package/dist/{middleware → src/middleware}/history.js +0 -0
- /package/dist/{middleware → src/middleware}/index.d.ts +0 -0
- /package/dist/{middleware → src/middleware}/index.js +0 -0
- /package/dist/{middleware → src/middleware}/logger.d.ts +0 -0
- /package/dist/{middleware → src/middleware}/logger.js +0 -0
- /package/dist/{middleware → src/middleware}/persistence.d.ts +0 -0
- /package/dist/{middleware → src/middleware}/persistence.js +0 -0
- /package/dist/{middleware → src/middleware}/validator.d.ts +0 -0
- /package/dist/{middleware → src/middleware}/validator.js +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunk.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunk.js +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunkProperty.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunkProperty.js +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunkValue.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunkValue.js +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunkValues.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useChunkValues.js +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useComputed.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useComputed.js +0 -0
- /package/dist/{use-react → src/use-react}/hooks/useDerive.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/index.d.ts +0 -0
- /package/dist/{use-react → src/use-react}/index.js +0 -0
package/dist/core/selector.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { chunk } from "./core";
|
|
2
|
-
export function select(sourceChunk, selector) {
|
|
3
|
-
const initialValue = selector(sourceChunk.get());
|
|
4
|
-
const selectedChunk = chunk(initialValue);
|
|
5
|
-
let previousSelected = initialValue;
|
|
6
|
-
// Subscribe to source changes with equality checking
|
|
7
|
-
sourceChunk.subscribe((newValue) => {
|
|
8
|
-
const newSelected = selector(newValue);
|
|
9
|
-
// Only update if the selected value actually changed
|
|
10
|
-
if (!Object.is(newSelected, previousSelected)) {
|
|
11
|
-
previousSelected = newSelected;
|
|
12
|
-
selectedChunk.set(newSelected);
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
// Return read-only version of the chunk
|
|
16
|
-
return {
|
|
17
|
-
...selectedChunk,
|
|
18
|
-
// Prevent setting values directly on the selector
|
|
19
|
-
set: () => {
|
|
20
|
-
throw new Error('Cannot set values directly on a selector. Modify the source chunk instead.');
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
package/jest.config.js
DELETED
package/src/core/asyncChunk.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { chunk, Chunk } from "./core";
|
|
2
|
-
import { AsyncChunkOpt } from "./types";
|
|
3
|
-
|
|
4
|
-
export interface AsyncState<T> {
|
|
5
|
-
loading: boolean;
|
|
6
|
-
error: Error | null;
|
|
7
|
-
data: T | null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface AsyncChunk<T> extends Chunk<AsyncState<T>> {
|
|
11
|
-
/**
|
|
12
|
-
* Reload the data from the source.
|
|
13
|
-
*/
|
|
14
|
-
reload: () => Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Mutate the data directly.
|
|
17
|
-
*/
|
|
18
|
-
mutate: (mutator: (currentData: T | null) => T) => void;
|
|
19
|
-
/**
|
|
20
|
-
* Reset the state to the initial value.
|
|
21
|
-
*/
|
|
22
|
-
reset: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function asyncChunk<T>(fetcher: () => Promise<T>, options: AsyncChunkOpt<T> = {}): AsyncChunk<T> {
|
|
26
|
-
const {
|
|
27
|
-
initialData = null,
|
|
28
|
-
onError,
|
|
29
|
-
retryCount = 0,
|
|
30
|
-
retryDelay = 1000,
|
|
31
|
-
} = options;
|
|
32
|
-
|
|
33
|
-
const initialState: AsyncState<T> = {
|
|
34
|
-
loading: true,
|
|
35
|
-
error: null,
|
|
36
|
-
data: initialData,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const baseChunk = chunk(initialState);
|
|
40
|
-
|
|
41
|
-
const fetchData = async (retries = retryCount): Promise<void> => {
|
|
42
|
-
baseChunk.set({ ...baseChunk.get(), loading: true, error: null });
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const data = await fetcher();
|
|
46
|
-
baseChunk.set({ loading: false, error: null, data });
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (retries > 0) {
|
|
49
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
50
|
-
return fetchData(retries - 1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
54
|
-
baseChunk.set({ loading: false, error: errorObj, data: baseChunk.get().data });
|
|
55
|
-
|
|
56
|
-
if (onError) {
|
|
57
|
-
onError(errorObj);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Initial fetch
|
|
64
|
-
fetchData();
|
|
65
|
-
|
|
66
|
-
const asyncChunkInstance: AsyncChunk<T> = {
|
|
67
|
-
...baseChunk,
|
|
68
|
-
reload: async () => {
|
|
69
|
-
await fetchData();
|
|
70
|
-
},
|
|
71
|
-
mutate: (mutator: (currentData: T | null) => T) => {
|
|
72
|
-
const currentState = baseChunk.get();
|
|
73
|
-
const newData = mutator(currentState.data);
|
|
74
|
-
baseChunk.set({ ...currentState, data: newData });
|
|
75
|
-
},
|
|
76
|
-
reset: () => {
|
|
77
|
-
baseChunk.set(initialState);
|
|
78
|
-
fetchData();
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return asyncChunkInstance;
|
|
83
|
-
}
|
package/src/core/computed.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Chunk, chunk } from "./core";
|
|
2
|
-
|
|
3
|
-
// Helper type to extract the value type from a Chunk
|
|
4
|
-
export type ChunkValue<T> = T extends Chunk<infer U> ? U : never;
|
|
5
|
-
|
|
6
|
-
// Helper type to transform an array of Chunks into an array of their value types
|
|
7
|
-
export type DependencyValues<T extends Chunk<any>[]> = {
|
|
8
|
-
[K in keyof T]: T[K] extends Chunk<any> ? ChunkValue<T[K]> : never;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export interface Computed<T> extends Chunk<T> {
|
|
12
|
-
isDirty: () => boolean;
|
|
13
|
-
recompute: () => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function computed<TDeps extends Chunk<any>[], TResult>(
|
|
17
|
-
dependencies: [...TDeps],
|
|
18
|
-
computeFn: (...args: DependencyValues<TDeps>) => TResult
|
|
19
|
-
): Computed<TResult> {
|
|
20
|
-
let isDirty = false; // Initialized to false
|
|
21
|
-
let cachedValue: TResult = computeFn(...dependencies.map(d => d.get()) as DependencyValues<TDeps>);
|
|
22
|
-
|
|
23
|
-
const recalculate = () => {
|
|
24
|
-
const values = dependencies.map(dep => dep.get()) as DependencyValues<TDeps>;
|
|
25
|
-
cachedValue = computeFn(...values);
|
|
26
|
-
isDirty = false; // Reset to false after recomputation
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const computedChunk = chunk(cachedValue);
|
|
30
|
-
|
|
31
|
-
const originalGet = computedChunk.get;
|
|
32
|
-
computedChunk.get = () => {
|
|
33
|
-
if (isDirty) {
|
|
34
|
-
recalculate();
|
|
35
|
-
computedChunk.set(cachedValue); // Update the chunk value after recomputation
|
|
36
|
-
}
|
|
37
|
-
return cachedValue; // Return the cached value directly
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const lastValues = dependencies.map(dep => dep.get());
|
|
41
|
-
|
|
42
|
-
dependencies.forEach((dep, index) => {
|
|
43
|
-
dep.subscribe(() => {
|
|
44
|
-
const newValue = dep.get();
|
|
45
|
-
if (newValue !== lastValues[index] && !isDirty) {
|
|
46
|
-
lastValues[index] = newValue;
|
|
47
|
-
isDirty = true;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
...computedChunk,
|
|
54
|
-
isDirty: () => isDirty,
|
|
55
|
-
recompute: () => {
|
|
56
|
-
if (isDirty) {
|
|
57
|
-
recalculate();
|
|
58
|
-
computedChunk.set(cachedValue); // Update the chunk value after manual recomputation
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
set: () => {
|
|
62
|
-
throw new Error('Cannot directly set a computed value');
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
}
|
package/src/core/core.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { processMiddleware } from "../utils";
|
|
2
|
-
|
|
3
|
-
export type Subscriber<T> = (newValue: T) => void;
|
|
4
|
-
export type Middleware<T> = (value: T, next: (newValue: T) => void) => void;
|
|
5
|
-
|
|
6
|
-
export interface Chunk<T> {
|
|
7
|
-
/** Get the current value of the chunk. */
|
|
8
|
-
get: () => T;
|
|
9
|
-
/** Set a new value for the chunk & Update existing value efficiently. */
|
|
10
|
-
set: (newValueOrUpdater: T | ((currentValue: T) => T)) => void;
|
|
11
|
-
/** Subscribe to changes in the chunk. Returns an unsubscribe function. */
|
|
12
|
-
subscribe: (callback: Subscriber<T>) => () => void;
|
|
13
|
-
/** Create a derived chunk based on this chunk's value. */
|
|
14
|
-
derive: <D>(fn: (value: T) => D) => Chunk<D>;
|
|
15
|
-
/** Reset the chunk to its initial value. */
|
|
16
|
-
reset: () => void;
|
|
17
|
-
/** Destroy the chunk and all its subscribers. */
|
|
18
|
-
destroy: () => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let batchDepth = 0;
|
|
22
|
-
const batchQueue = new Set<() => void>();
|
|
23
|
-
|
|
24
|
-
export function batch(callback: () => void) {
|
|
25
|
-
batchDepth++;
|
|
26
|
-
try {
|
|
27
|
-
callback();
|
|
28
|
-
} finally {
|
|
29
|
-
batchDepth--;
|
|
30
|
-
if (batchDepth === 0) {
|
|
31
|
-
// Execute all queued updates
|
|
32
|
-
batchQueue.forEach(update => update());
|
|
33
|
-
batchQueue.clear();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function chunk<T>(initialValue: T, middleware: Middleware<T>[] = []): Chunk<T> {
|
|
39
|
-
if (initialValue === undefined || initialValue === null) {
|
|
40
|
-
throw new Error("Initial value cannot be undefined or null.");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let value = initialValue;
|
|
44
|
-
const subscribers = new Set<Subscriber<T>>();
|
|
45
|
-
let isDirty = false;
|
|
46
|
-
|
|
47
|
-
const notifySubscribers = () => {
|
|
48
|
-
if (batchDepth > 0) {
|
|
49
|
-
if (!isDirty) {
|
|
50
|
-
isDirty = true;
|
|
51
|
-
batchQueue.add(() => {
|
|
52
|
-
if (isDirty) {
|
|
53
|
-
subscribers.forEach((subscriber) => subscriber(value));
|
|
54
|
-
isDirty = false;
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
subscribers.forEach((subscriber) => subscriber(value));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const get = () => value;
|
|
64
|
-
|
|
65
|
-
const set = (newValueOrUpdater: T | ((currentValue: T) => T)) => {
|
|
66
|
-
let newValue: T;
|
|
67
|
-
|
|
68
|
-
if (typeof newValueOrUpdater === 'function') {
|
|
69
|
-
// Handle updater function
|
|
70
|
-
newValue = (newValueOrUpdater as (currentValue: T) => T)(value);
|
|
71
|
-
} else {
|
|
72
|
-
// Handle direct value assignment
|
|
73
|
-
newValue = newValueOrUpdater;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const processedValue = processMiddleware(newValue, middleware);
|
|
77
|
-
|
|
78
|
-
if (processedValue !== value) {
|
|
79
|
-
value = processedValue as T & {};
|
|
80
|
-
notifySubscribers();
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const subscribe = (callback: Subscriber<T>) => {
|
|
85
|
-
if (typeof callback !== "function") {
|
|
86
|
-
throw new Error("Callback must be a function.");
|
|
87
|
-
}
|
|
88
|
-
if (subscribers.has(callback)) {
|
|
89
|
-
console.warn("Callback is already subscribed. This may lead to duplicate updates.");
|
|
90
|
-
}
|
|
91
|
-
subscribers.add(callback);
|
|
92
|
-
callback(value);
|
|
93
|
-
|
|
94
|
-
return () => subscribers.delete(callback);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const reset = () => {
|
|
98
|
-
value = initialValue;
|
|
99
|
-
subscribers.forEach((subscriber) => subscriber(value));
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const destroy = () => {
|
|
103
|
-
if (subscribers.size > 0) {
|
|
104
|
-
console.warn("Destroying chunk with active subscribers. This may lead to memory leaks.");
|
|
105
|
-
}
|
|
106
|
-
// Just clear subscribers without calling unsubscribe
|
|
107
|
-
subscribers.clear();
|
|
108
|
-
value = initialValue;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const derive = <D>(fn: (value: T) => D) => {
|
|
113
|
-
if (typeof fn !== "function") {
|
|
114
|
-
throw new Error("Derive function must be a function.");
|
|
115
|
-
}
|
|
116
|
-
const derivedValue = fn(value);
|
|
117
|
-
const derivedChunk = chunk(derivedValue);
|
|
118
|
-
|
|
119
|
-
subscribe(() => {
|
|
120
|
-
const newDerivedValue = fn(value);
|
|
121
|
-
derivedChunk.set(newDerivedValue);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return derivedChunk;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
return { get, set, subscribe, derive, reset, destroy };
|
|
128
|
-
}
|
package/src/core/selector.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { chunk, Chunk } from "./core";
|
|
2
|
-
|
|
3
|
-
export function select<T, S>(sourceChunk: Chunk<T>, selector: (value: T) => S): Chunk<S> {
|
|
4
|
-
const initialValue = selector(sourceChunk.get());
|
|
5
|
-
const selectedChunk = chunk(initialValue);
|
|
6
|
-
let previousSelected = initialValue;
|
|
7
|
-
|
|
8
|
-
// Subscribe to source changes with equality checking
|
|
9
|
-
sourceChunk.subscribe((newValue) => {
|
|
10
|
-
const newSelected = selector(newValue);
|
|
11
|
-
|
|
12
|
-
// Only update if the selected value actually changed
|
|
13
|
-
if (!Object.is(newSelected, previousSelected)) {
|
|
14
|
-
previousSelected = newSelected;
|
|
15
|
-
selectedChunk.set(newSelected);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Return read-only version of the chunk
|
|
20
|
-
return {
|
|
21
|
-
...selectedChunk,
|
|
22
|
-
// Prevent setting values directly on the selector
|
|
23
|
-
set: () => {
|
|
24
|
-
throw new Error('Cannot set values directly on a selector. Modify the source chunk instead.');
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
}
|
package/src/core/types.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { AsyncChunk } from "./asyncChunk";
|
|
2
|
-
|
|
3
|
-
export type AsyncChunkOpt<T> = {
|
|
4
|
-
initialData?: T | null;
|
|
5
|
-
onError?: (error: Error) => void;
|
|
6
|
-
retryCount?: number;
|
|
7
|
-
retryDelay?: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type InferAsyncData<T> = T extends AsyncChunk<infer U> ? U : never;
|
|
11
|
-
|
|
12
|
-
export type CombinedData<T> = { [K in keyof T]: InferAsyncData<T[K]> | null };
|
|
13
|
-
export type CombinedState<T> = {
|
|
14
|
-
loading: boolean;
|
|
15
|
-
error: Error | null;
|
|
16
|
-
data: CombinedData<T>;
|
|
17
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export { chunk, batch } from './core/core';
|
|
2
|
-
export { asyncChunk } from './core/asyncChunk';
|
|
3
|
-
export { computed } from './core/computed';
|
|
4
|
-
export { select } from './core/selector'
|
|
5
|
-
|
|
6
|
-
export { combineAsyncChunks, once, isChunk, isValidChunkValue } from './utils';
|
|
7
|
-
|
|
8
|
-
export type { Chunk, Middleware } from './core/core';
|
|
9
|
-
|
|
10
|
-
export * as middleware from "./middleware";
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { Chunk } from "../core/core";
|
|
2
|
-
|
|
3
|
-
export interface ChunkWithHistory<T> extends Chunk<T> {
|
|
4
|
-
/**
|
|
5
|
-
* Reverts to the previous state (if available).
|
|
6
|
-
*/
|
|
7
|
-
undo: () => void;
|
|
8
|
-
/**
|
|
9
|
-
* Moves to the next state (if available).
|
|
10
|
-
*/
|
|
11
|
-
redo: () => void;
|
|
12
|
-
/**
|
|
13
|
-
* Returns true if there is a previous state to revert to.
|
|
14
|
-
*/
|
|
15
|
-
canUndo: () => boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Returns true if there is a next state to move to.
|
|
18
|
-
*/
|
|
19
|
-
canRedo: () => boolean;
|
|
20
|
-
/**
|
|
21
|
-
* Returns an array of all the values in the history.
|
|
22
|
-
*/
|
|
23
|
-
getHistory: () => T[];
|
|
24
|
-
/**
|
|
25
|
-
* Clears the history, keeping only the current value.
|
|
26
|
-
*/
|
|
27
|
-
clearHistory: () => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function withHistory<T>(
|
|
31
|
-
baseChunk: Chunk<T>,
|
|
32
|
-
options: { maxHistory?: number } = {}
|
|
33
|
-
): ChunkWithHistory<T> {
|
|
34
|
-
const { maxHistory = 100 } = options;
|
|
35
|
-
const history: T[] = [baseChunk.get()];
|
|
36
|
-
let currentIndex = 0;
|
|
37
|
-
let isHistoryAction = false;
|
|
38
|
-
|
|
39
|
-
const historyChunk: ChunkWithHistory<T> = {
|
|
40
|
-
...baseChunk,
|
|
41
|
-
|
|
42
|
-
set: (newValueOrUpdater: T | ((currentValue: T) => T)) => {
|
|
43
|
-
if (isHistoryAction) {
|
|
44
|
-
baseChunk.set(newValueOrUpdater);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Process the value or updater function
|
|
49
|
-
let newValue: T;
|
|
50
|
-
if (typeof newValueOrUpdater === 'function') {
|
|
51
|
-
// Get current value and apply the updater function
|
|
52
|
-
const currentValue = baseChunk.get();
|
|
53
|
-
newValue = (newValueOrUpdater as ((currentValue: T) => T))(currentValue);
|
|
54
|
-
} else {
|
|
55
|
-
// Use directly as the new value
|
|
56
|
-
newValue = newValueOrUpdater;
|
|
57
|
-
}
|
|
58
|
-
history.splice(currentIndex + 1);
|
|
59
|
-
history.push(newValue);
|
|
60
|
-
|
|
61
|
-
// Limit history size
|
|
62
|
-
if (history.length > maxHistory) {
|
|
63
|
-
console.warn("History limit reached. Removing oldest entries.");
|
|
64
|
-
const removeCount = history.length - maxHistory;
|
|
65
|
-
history.splice(0, removeCount);
|
|
66
|
-
currentIndex = Math.max(0, currentIndex - removeCount);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
currentIndex = history.length - 1;
|
|
70
|
-
baseChunk.set(newValue);
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
undo: () => {
|
|
74
|
-
if (!historyChunk.canUndo()) return;
|
|
75
|
-
|
|
76
|
-
isHistoryAction = true;
|
|
77
|
-
currentIndex--;
|
|
78
|
-
historyChunk.set(history[currentIndex]);
|
|
79
|
-
isHistoryAction = false;
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
redo: () => {
|
|
83
|
-
if (!historyChunk.canRedo()) return;
|
|
84
|
-
|
|
85
|
-
isHistoryAction = true;
|
|
86
|
-
currentIndex++;
|
|
87
|
-
historyChunk.set(history[currentIndex]);
|
|
88
|
-
isHistoryAction = false;
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
canUndo: () => currentIndex > 0,
|
|
92
|
-
|
|
93
|
-
canRedo: () => currentIndex < history.length - 1,
|
|
94
|
-
|
|
95
|
-
getHistory: () => [...history],
|
|
96
|
-
|
|
97
|
-
clearHistory: () => {
|
|
98
|
-
const currentValue = baseChunk.get();
|
|
99
|
-
history.length = 0;
|
|
100
|
-
history.push(currentValue);
|
|
101
|
-
currentIndex = 0;
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
// Override destroy to clean up history
|
|
105
|
-
destroy: () => {
|
|
106
|
-
history.length = 0;
|
|
107
|
-
baseChunk.destroy();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return historyChunk;
|
|
112
|
-
|
|
113
|
-
}
|
package/src/middleware/index.ts
DELETED
package/src/middleware/logger.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Chunk } from "../core/core";
|
|
2
|
-
|
|
3
|
-
export interface PersistOptions<T> {
|
|
4
|
-
key: string;
|
|
5
|
-
storage?: Storage;
|
|
6
|
-
serialize?: (value: T) => string;
|
|
7
|
-
deserialize?: (value: string) => T;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function withPersistence<T>(
|
|
11
|
-
baseChunk: Chunk<T>,
|
|
12
|
-
options: PersistOptions<T>
|
|
13
|
-
): Chunk<T> {
|
|
14
|
-
const {
|
|
15
|
-
key,
|
|
16
|
-
storage = localStorage,
|
|
17
|
-
serialize = JSON.stringify,
|
|
18
|
-
deserialize = JSON.parse,
|
|
19
|
-
} = options;
|
|
20
|
-
|
|
21
|
-
// Try to load initial state from storage
|
|
22
|
-
try {
|
|
23
|
-
const savedChunk = storage.getItem(key);
|
|
24
|
-
if (savedChunk) {
|
|
25
|
-
const parsed = deserialize(savedChunk);
|
|
26
|
-
baseChunk.set(parsed)
|
|
27
|
-
}
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error('Failed to load persisted state:', error);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Save to storage
|
|
33
|
-
baseChunk.subscribe((newValue) => {
|
|
34
|
-
try {
|
|
35
|
-
const serialized = serialize(newValue);
|
|
36
|
-
storage.setItem(key, serialized);
|
|
37
|
-
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.log('Failed to persist chunk', error)
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
return baseChunk
|
|
44
|
-
|
|
45
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from "react";
|
|
2
|
-
|
|
3
|
-
import { AsyncChunk, AsyncState } from "../../core/asyncChunk";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A hook that handles asynchronous state with built-in reactivity.
|
|
7
|
-
* Provides loading, error, and data states.
|
|
8
|
-
*/
|
|
9
|
-
export function useAsyncChunk<T>(asyncChunk: AsyncChunk<T>) {
|
|
10
|
-
const [state, setState] = useState<AsyncState<T>>(() => asyncChunk.get());
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const unsubscribe = asyncChunk.subscribe((newState) => {
|
|
14
|
-
setState(newState);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return () => unsubscribe();
|
|
18
|
-
}, [asyncChunk]);
|
|
19
|
-
|
|
20
|
-
const reload = useCallback(() => asyncChunk.reload(), [asyncChunk]);
|
|
21
|
-
const mutate = useCallback(
|
|
22
|
-
(mutator: (currentData: T | null) => T) => asyncChunk.mutate(mutator),
|
|
23
|
-
[asyncChunk]
|
|
24
|
-
);
|
|
25
|
-
const reset = useCallback(() => asyncChunk.reset(), [asyncChunk]);
|
|
26
|
-
|
|
27
|
-
const { data, loading, error } = state;
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
state,
|
|
31
|
-
data,
|
|
32
|
-
loading,
|
|
33
|
-
error,
|
|
34
|
-
reload,
|
|
35
|
-
mutate,
|
|
36
|
-
reset
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from "react";
|
|
2
|
-
|
|
3
|
-
import { select } from "../../core/selector";
|
|
4
|
-
|
|
5
|
-
import type { Chunk } from "../../core/core";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A lightweight hook that subscribes to a chunk and returns its current value, along with setters, selector, reset and destroy.
|
|
9
|
-
* Ensures reactivity and prevents unnecessary re-renders.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export function useChunk<T, S = T>(
|
|
13
|
-
chunk: Chunk<T>,
|
|
14
|
-
selector?: (value: T) => S
|
|
15
|
-
) {
|
|
16
|
-
const selectedChunk = selector ? select(chunk, selector) : chunk;
|
|
17
|
-
|
|
18
|
-
const [state, setState] = useState<S>(() => selectedChunk.get() as S);
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
const unsubscribe = selectedChunk.subscribe((newValue) => {
|
|
22
|
-
setState(() => newValue as S);
|
|
23
|
-
});
|
|
24
|
-
return () => unsubscribe();
|
|
25
|
-
}, [selectedChunk]);
|
|
26
|
-
|
|
27
|
-
const set = useCallback((valueOrUpdater: T | ((currentValue: T) => T)) => {
|
|
28
|
-
chunk.set(valueOrUpdater);
|
|
29
|
-
}, [chunk]);
|
|
30
|
-
|
|
31
|
-
const reset = useCallback(() => {
|
|
32
|
-
chunk.reset();
|
|
33
|
-
}, [chunk]);
|
|
34
|
-
|
|
35
|
-
const destroy = useCallback(() => {
|
|
36
|
-
chunk.destroy();
|
|
37
|
-
}, [chunk]);
|
|
38
|
-
|
|
39
|
-
return [state, set, reset, destroy] as const;
|
|
40
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
|
|
3
|
-
import { useChunkValue } from "./useChunkValue";
|
|
4
|
-
|
|
5
|
-
import type { Chunk } from "../../core/core";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A hook that subscribes to a specific property of a chunk.
|
|
9
|
-
* This optimizes renders by only updating when the selected property changes.
|
|
10
|
-
*/
|
|
11
|
-
export function useChunkProperty<T, K extends keyof T>(
|
|
12
|
-
chunk: Chunk<T>,
|
|
13
|
-
property: K
|
|
14
|
-
): T[K] {
|
|
15
|
-
const selector = useMemo(
|
|
16
|
-
() => (state: T) => state[property],
|
|
17
|
-
[property]
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
return useChunkValue(chunk, selector);
|
|
21
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { useChunk } from "./useChunk";
|
|
2
|
-
|
|
3
|
-
import type { Chunk } from "../../core/core";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A lightweight hook that subscribes to a chunk and returns only its current value.
|
|
7
|
-
* Useful for read-only components that don't need to update the chunk.
|
|
8
|
-
*/
|
|
9
|
-
export function useChunkValue<T, S = T>(
|
|
10
|
-
chunk: Chunk<T>,
|
|
11
|
-
selector?: (value: T) => S
|
|
12
|
-
): S {
|
|
13
|
-
const [value] = useChunk(chunk, selector);
|
|
14
|
-
return value;
|
|
15
|
-
}
|