break up library, move bots to their own repositories
This commit is contained in:
73
packages/util/src/reactive-state.ts
Normal file
73
packages/util/src/reactive-state.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
/**
|
||||
* Creates a reactive state object that batches changes and notifies all subscribers with the full old and new state.
|
||||
*
|
||||
* @param initialState - The initial state object
|
||||
* @returns Object with { state: reactive proxy, subscribe: function to add callbacks }
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { state, subscribe } = createBatchedReactiveState({ count: 0, user: { name: 'Alice' } });
|
||||
*
|
||||
* const unsubscribe1 = subscribe((newState, oldState) => {
|
||||
* console.log('Subscriber 1:', oldState, '->', newState);
|
||||
* });
|
||||
*
|
||||
* const unsubscribe2 = subscribe((newState, oldState) => {
|
||||
* console.log('Subscriber 2:', newState.count);
|
||||
* });
|
||||
*
|
||||
* state.count = 1; // Triggers both subscribers once
|
||||
* state.user.name = 'Bob'; // Triggers both subscribers once (batched)
|
||||
*
|
||||
* unsubscribe1(); // Remove first subscriber
|
||||
* state.count = 2; // Only subscriber 2 is called
|
||||
* ```
|
||||
*/
|
||||
export function createReactiveState<T extends object>(initialState: T) {
|
||||
const callbacks = new Set<(newState: T, oldState: T) => void>();
|
||||
let isBatching = false;
|
||||
let oldState: T | null = null;
|
||||
let scheduled = false;
|
||||
|
||||
const rootState = cloneDeep(initialState);
|
||||
|
||||
function createReactiveObject(obj: any): any {
|
||||
return new Proxy(obj, {
|
||||
get(target, property, receiver) {
|
||||
const value = Reflect.get(target, property, receiver);
|
||||
return typeof value === 'object' && value !== null ? createReactiveObject(value) : value;
|
||||
},
|
||||
set(target, property, value, receiver) {
|
||||
if (!isBatching) {
|
||||
isBatching = true;
|
||||
oldState = cloneDeep(rootState);
|
||||
}
|
||||
const success = Reflect.set(target, property, value, receiver);
|
||||
if (success) {
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
queueMicrotask(() => {
|
||||
callbacks.forEach((cb) => cb(rootState, oldState!));
|
||||
isBatching = false;
|
||||
oldState = null;
|
||||
scheduled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
return success;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const state = createReactiveObject(rootState);
|
||||
|
||||
return [
|
||||
state,
|
||||
function subscribe(callback: (newState: T, oldState: T) => void) {
|
||||
callbacks.add(callback);
|
||||
return () => callbacks.delete(callback);
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
Reference in New Issue
Block a user