/** * `Store` — TEA-style reducer store. * * @module */ import type { Stream } from 'effect'; import { Effect, SubscriptionRef, Semaphore } from 'effect'; interface StoreShape { readonly _tag: 'Store'; readonly get: Effect.Effect; readonly changes: Stream.Stream; dispatch(msg: Msg): Effect.Effect; } interface EffectfulStoreShape { readonly _tag: 'Store'; readonly get: Effect.Effect; readonly changes: Stream.Stream; dispatch(msg: Msg): Effect.Effect; } const _make = (initial: S, reducer: (state: S, msg: Msg) => S): Effect.Effect> => Effect.gen(function* () { const ref = yield* SubscriptionRef.make(initial); return { _tag: 'Store' as const, get: SubscriptionRef.get(ref), changes: SubscriptionRef.changes(ref), dispatch: (msg: Msg) => SubscriptionRef.update(ref, (state) => reducer(state, msg)), }; }); const _makeWithEffect = ( initial: S, reducer: (state: S, msg: Msg) => Effect.Effect, ): Effect.Effect> => Effect.gen(function* () { const ref = yield* SubscriptionRef.make(initial); const mutex = yield* Semaphore.make(1); return { _tag: 'Store' as const, get: SubscriptionRef.get(ref), changes: SubscriptionRef.changes(ref), dispatch: (msg: Msg) => mutex.withPermits(1)( Effect.gen(function* () { const current = yield* SubscriptionRef.get(ref); const next = yield* reducer(current, msg); yield* SubscriptionRef.set(ref, next); }), ), }; }); /** * Store — TEA-style state container. * Build with an initial state and a pure `reducer(state, msg) => state`, then * dispatch messages; the store publishes the resulting state via `changes`. * Use `makeWithEffect` when the reducer is itself an `Effect`. */ export const Store = { /** Synchronous reducer store. */ make: _make, /** Reducer store where state transitions are themselves `Effect`s. */ makeWithEffect: _makeWithEffect, }; export declare namespace Store { /** Structural shape of a synchronous store. */ export type Shape = StoreShape; /** Structural shape of an effectful store; adds error channel `E` and requirements `R`. */ export type Effectful = EffectfulStoreShape; }