Extensions
The Extension System
Section titled “The Extension System”Reatom features a powerful Extension System using the .extend()
method. Extensions are reusable functions (often named with...
) that add capabilities (like async handling, persistence, validation, etc.) or derived state to atoms and actions.
Build-in Extensions
Section titled “Build-in Extensions”TODO link to references
withInit
, withComputed
, withAbort
adn many others.
How Extensions Work
Section titled “How Extensions Work”- Extension Factory (
with...
): A function (e.g.,withReset(initialValue)
) that might take options and returns the actual Extension Function. - Extension Function: This function receives the target atom/action (
target
) and returns either:- An Assigner Object: An object whose properties are merged onto the
target
. Function properties automatically become named, traceable actions (e.g.,withReset
adds{ reset: /* action */ }
). The extension function should return this object directly. - The original target: If you make some other transformations, like linking with other atoms or so on.
- An Assigner Object: An object whose properties are merged onto the
Rules Of Extension
Section titled “Rules Of Extension”- For extension type interface name use
NameExt
pattern, to have an ability to combine a few extensions interface to a meta extension. - To match an atom in the generic, extend
AtomLike
interface, (for only actions you can useAction
). - Always use the
withMiddleware
helper for middleware extensions. For assigner extensions, simply return the object to assign. - Use
Ext
generic to match specific atom type, useGenericExt
to match any atom (it isn’t require to write generic by yourself). - Use
target.name
to compute relative names of additional atoms and actions.
Assigner Extension
Section titled “Assigner Extension”To assign some properties to the atom, just return an object from the extension.
import { atom, action, AtomLike, Action, Ext, AtomState } from '@reatom/core'
// Define the shape of the added propertiesexport interface ResetExt<State> { reset: Action<[], State>}
// Extension Factory returning the assigner function directlyexport const withReset = <Target extends AtomLike>( initialValue: AtomState<Target>, // Get initial value type from Atom ): Ext<Target, ResetExt<AtomState<Target>>> => // Define extension input/output types // Return the extension function (target) => // target is the atom being extended // Return the object to assign ({ reset: action( () => target.set(initialValue), // Action logic uses target and initialValue `${target.name}.reset`, // Auto-naming based on target ), })
// Usage:const counter = atom(0, 'counter').extend(withReset(0))counter.set(10)counter.reset() // Works! State is 0.
Another example with additional state:
import { atom, AtomLike, Computed, Ext, AtomState } from '@reatom/core'
export interface HistoryExt<State> { history: Computed<[current: State, ...past: Array<State>]>}
export const withHistory = <Target extends AtomLike>( length = 2, ): Ext<Target, HistoryExt<AtomState<Target>>> => (target) => { type State = AtomState<Target> type History = [current: State, ...past: Array<State>] return (target) => ({ history: computed( (state?: State) => [target(), ...(state || []).slice(0, length)] as State, `${target.name}.history`, ), }) }
2. Middleware Extension (withMiddleware
): Intercepts/modifies behavior.
import { isAction, withMiddleware, GenericExt } from '@reatom/core'
// Simple logging middlewareconst withLogger = (): GenericExt => withMiddleware((target) => (next, ...params) => { // just a state reading, do nothing if (!isAction(target) && !params.length) return next()
console.log(`[${target.name}] Calling with:`, params) const result = next(...params) console.log(`[${target.name}] Result:`, result) return result })
// Usage:const message = atom('', 'message').extend(withLogger())message.set('Hello') // Logs call and new state
const greet = action((name: string) => `Hi, ${name}!`, 'greet').extend( withLogger(),)greet('Reatom') // Logs call and return value
Composition: Apply multiple extensions easily:
const persistentCounter = atom(0, 'persistentCounter').extend( withReset(0), withLogger(), // withPersist('counterKey') // Example using another hypothetical extension)
Reatom provides many built-in extensions, explore the ecosystem and create your own to build powerful, reusable abstractions!