@sanity/sdk
Version:
93 lines (84 loc) • 2.9 kB
text/typescript
import {Observable} from 'rxjs'
import {devtools, type DevtoolsOptions} from 'zustand/middleware'
import {createStore} from 'zustand/vanilla'
/**
* Represents a reactive store state container with multiple access patterns
*/
export interface StoreState<TState> {
/**
* Gets the current state value
*
* @remarks
* This is a direct synchronous accessor that doesn't trigger subscriptions
*/
get: () => TState
/**
* Updates the store state
* @param name - Action name for devtools tracking
* @param updatedState - New state value or updater function
*
* @remarks
* When providing a partial object, previous top-level keys not included in
* the update will be preserved.
*/
set: (name: string, updatedState: Partial<TState> | ((s: TState) => Partial<TState>)) => void
/**
* Observable stream of state changes
* @remarks
* - Emits immediately with current state on subscription
* - Shares underlying subscription between observers
* - Only emits when state reference changes
* - Completes when store is disposed
*/
observable: Observable<TState>
}
/**
* Creates a reactive store state container with multiple access patterns
* @param initialState - Initial state value for the store
* @param devToolsOptions - Configuration for Zustand devtools integration
* @returns StoreState instance with get/set/observable interface
*
* @example
* ```typescript
* // Create a simple counter store
* const counterStore = createStoreState({ count: 0 });
*
* // Update state
* counterStore.set('increment', { count: 1 });
*
* // Observe changes
* counterStore.observable.subscribe(console.log);
* ```
*
* @remarks
* Uses Zustand for state management under the hood with RxJS for observable interface.
* Designed to work with both imperative and reactive programming patterns.
*/
export function createStoreState<TState>(
initialState: TState,
devToolsOptions?: DevtoolsOptions,
): StoreState<TState> {
// Create underlying Zustand store with devtools integration
const store = createStore<TState>()(devtools(() => initialState, devToolsOptions))
return {
get: store.getState,
set: (actionKey, updatedState) => {
const currentState = store.getState()
const nextState =
typeof updatedState === 'function' ? updatedState(currentState) : updatedState
// Optimization: Skip update if state reference remains the same
if (currentState !== nextState) {
store.setState(nextState, false, actionKey)
}
},
observable: new Observable((observer) => {
// Emit current state immediately on subscription
const emit = () => observer.next(store.getState())
emit()
// Subscribe to Zustand store changes
const unsubscribe = store.subscribe(emit)
// Cleanup when observable unsubscribed
return () => unsubscribe()
}),
}
}