undux
Version:
Dead simple state management for React
153 lines (138 loc) • 4.2 kB
text/typescript
import { Observable } from 'rxjs'
import { Emitter } from './emitter'
import { mapValues } from './utils'
export type Undux<State extends object> = {
[K in keyof State]: {
key: K
previousValue: State[K]
value: State[K]
}
}
/**
* Public Store interface. When you want to reference the Store type,
* this is the type to use.
*/
export interface Store<State extends object> {
get<K extends keyof State>(key: K): State[K]
set<K extends keyof State>(key: K): (value: State[K]) => void
setFrom_EXPERIMENTAL(f: (store: Store<State>) => void): void
on<K extends keyof State>(key: K): Observable<State[K]>
onAll(): Observable<Undux<State>[keyof State]>
getState(): Readonly<State>
}
/**
* Immutable snapshot of the current store state. One StoreSnapshot per
* StoreDefinition is usually alive at a time.
*/
export class StoreSnapshot<State extends object> implements Store<State> {
constructor(
private state: State,
private storeDefinition: StoreDefinition<State>,
) {}
get<K extends keyof State>(key: K) {
return this.state[key]
}
set<K extends keyof State>(key: K) {
return this.storeDefinition.set(key)
}
setFrom_EXPERIMENTAL(f: (store: Store<State>) => void): void {
return this.storeDefinition.setFrom_EXPERIMENTAL(f)
}
on<K extends keyof State>(key: K) {
return this.storeDefinition.on(key)
}
onAll() {
return this.storeDefinition.onAll()
}
getState() {
return Object.freeze(this.state)
}
}
export type Options = {
isDevMode: boolean
}
let DEFAULT_OPTIONS: Readonly<Options> = {
isDevMode: false,
}
/**
* We create a single instance of this per <Container />.
*/
export class StoreDefinition<State extends object> implements Store<State> {
private storeSnapshot: StoreSnapshot<State>
private alls: Emitter<Undux<State>>
private emitter: Emitter<State>
private setters: {
readonly [K in keyof State]: (value: State[K]) => void
}
constructor(state: State, options: Options) {
// Initialize emitters
this.alls = new Emitter(options.isDevMode)
this.emitter = new Emitter(options.isDevMode)
// Set initial state
this.storeSnapshot = new StoreSnapshot(state, this)
// Cache setters
this.setters = mapValues(state, (v, key) => (value: typeof v) => {
let previousValue = this.storeSnapshot.get(key)
this.storeSnapshot = new StoreSnapshot(
Object.assign({}, this.storeSnapshot.getState(), { [key]: value }),
this,
)
this.emitter.emit(key, value)
this.alls.emit(key, { key, previousValue, value })
})
}
on<K extends keyof State>(key: K): Observable<State[K]> {
return this.emitter.on(key)
}
onAll(): Observable<Undux<State>[keyof State]> {
return this.alls.all()
}
get<K extends keyof State>(key: K) {
return this.storeSnapshot.get(key)
}
set<K extends keyof State>(key: K): (value: State[K]) => void {
return this.setters[key]
}
setFrom_EXPERIMENTAL(f: (store: Store<State>) => void): void {
return f(this.storeSnapshot)
}
getCurrentSnapshot() {
return this.storeSnapshot
}
toStore(): Store<State> {
return this.storeSnapshot
}
getState() {
return this.storeSnapshot.getState()
}
}
/**
* @deprecated Use `createConnectedStore` instead.
*/
export function createStore<State extends object>(
initialState: State,
options: Options = DEFAULT_OPTIONS,
): StoreDefinition<State> {
return new StoreDefinition<State>(initialState, options)
}
export type Effects<State extends object> = (
store: StoreDefinition<State>,
) => StoreDefinition<State>
export type EffectsAs<
States extends {
[alias: string]: any
},
> = (stores: { [K in keyof States]: StoreDefinition<States[K]> }) => {
[K in keyof States]: StoreDefinition<States[K]>
}
/**
* @deprecated Use `Effects` instead.
*/
export type Plugin<State extends object> = (
store: StoreDefinition<State>,
) => StoreDefinition<State>
export * from './plugins/withLogger'
export * from './plugins/withReduxDevtools'
export * from './react'
export { CreateConnectedStore } from './react/createConnectedStore'
export { CreateConnectedStoreAs } from './react/createConnectedStoreAs'