Effect
LiveStore itself is built on top of Effect which is a powerful library to write production-grade TypeScript code. It’s also possible (and recommended) to use Effect directly in your application code.
Schema
Section titled “Schema”LiveStore uses the Effect Schema library to define schemas for the following:
- Read model table column definitions
- Event event payloads definitions
- Query response types
For convenience, LiveStore re-exports the Schema
module from the effect
package, which is the same as if you’d import it via import { Schema } from 'effect'
directly.
Equal
and Hash
Traits
Section titled “Equal and Hash Traits”LiveStore’s reactive primitives (LiveQueryDef
and SignalDef
) implement Effect’s Equal
and Hash
traits, enabling efficient integration with Effect’s data structures and collections.
Effect Atom Integration
Section titled “Effect Atom Integration”LiveStore integrates seamlessly with Effect Atom for reactive state management in React applications. This provides a powerful combination of Effect’s functional programming capabilities with LiveStore’s event sourcing and CQRS patterns.
Effect Atom is an external package developed by Tim Smart that provides a more Effect-idiomatic alternative to the @livestore/react
package. While @livestore/react
offers a straightforward React integration, Effect Atom leverages Effect API/patterns throughout, making it a natural choice for applications already using Effect.
Installation
Section titled “Installation”pnpm install @effect-atom/atom-livestore @effect-atom/atom-react
Store Creation
Section titled “Store Creation”Create a LiveStore-backed atom store with persistence and worker support using the AtomLivestore.Tag
pattern:
/// <reference types="vite/client" />
import { import AtomLivestore
AtomLivestore } from '@effect-atom/atom-livestore'import { const makePersistedAdapter: (options: WebAdapterOptions) => Adapter
Creates a web adapter with persistent storage (currently only supports OPFS).
Requires both a web worker and a shared worker.
makePersistedAdapter } from '@livestore/adapter-web'import const LiveStoreSharedWorker: new (options?: { name?: string;}) => SharedWorker
LiveStoreSharedWorker from '@livestore/adapter-web/shared-worker?sharedworker'import const LiveStoreWorker: new (options?: { name?: string;}) => Worker
LiveStoreWorker from '@livestore/adapter-web/worker?worker'import { function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates } from 'react-dom'import { const schema: FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>
schema } from './schema.ts'
export { const schema: FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>export schema
schema } from './schema.ts'
// Create a persistent adapter with OPFS storageconst const adapter: Adapter
adapter = function makePersistedAdapter(options: WebAdapterOptions): Adapter
Creates a web adapter with persistent storage (currently only supports OPFS).
Requires both a web worker and a shared worker.
makePersistedAdapter({ storage: { readonly type: "opfs"; readonly directory?: string | undefined;}
Specifies where to persist data for this adapter
storage: { type: "opfs"
type: 'opfs' }, worker: ((options: { name: string;}) => globalThis.Worker) | (new (options: { name: string;}) => globalThis.Worker)
worker: const LiveStoreWorker: new (options?: { name?: string;}) => Worker
LiveStoreWorker, sharedWorker: ((options: { name: string;}) => globalThis.SharedWorker) | (new (options: { name: string;}) => globalThis.SharedWorker)
This is mostly an implementation detail and needed to be exposed into app code
due to a current Vite limitation (https://github.com/vitejs/vite/issues/8427).
In most cases this should look like:
import LiveStoreSharedWorker from '@livestore/adapter-web/shared-worker?sharedworker'
const adapter = makePersistedAdapter({ sharedWorker: LiveStoreSharedWorker, // ...})
sharedWorker: const LiveStoreSharedWorker: new (options?: { name?: string;}) => SharedWorker
LiveStoreSharedWorker,})
// Define the store as a service tagexport class class StoreTag
StoreTag extends import AtomLivestore
AtomLivestore.const Tag: <StoreTag>() => <Id, S, Context>(id: Id, options: CreateStoreOptions<S, Context> & { readonly otelOptions?: Partial<OtelOptions> | undefined;}) => AtomLivestore.AtomLiveStore<StoreTag, Id, S, Context>
Tag<class StoreTag
StoreTag>()('StoreTag', { CreateStoreOptions<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.schema: FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>
schema, CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}>.storeId: string
storeId: 'default', CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}>.adapter: Adapter
adapter, CreateStoreOptions<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.batchUpdates?: (run: () => void) => void
batchUpdates: function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)
unstable_batchedUpdates, // React batching for performance}) {}
The StoreTag
class provides the following static methods:
StoreTag.runtime
- Access to Effect runtimeStoreTag.commit
- Commit events to the storeStoreTag.store
- Access store with EffectStoreTag.storeUnsafe
- Direct store access when store is already loaded (synchronous)StoreTag.makeQuery
- Create query atoms with EffectStoreTag.makeQueryUnsafe
- Create query atoms without Effect
Defining Query Atoms
Section titled “Defining Query Atoms”Create reactive query atoms that automatically update when the underlying data changes:
import { import Atom
Atom } from '@effect-atom/atom'import { const queryDb: { <TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>; <TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: { map?: (rows: TResultSchema) => TResult; label?: string; deps?: DepKey; }): LiveQueryDef<TResult>;}
NOTE queryDb
is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps
option.
queryDb, const sql: (template: TemplateStringsArray, ...args: unknown[]) => string
This is a tag function for tagged literals.
it lets us get syntax highlighting on SQL queries in VSCode, but
doesn't do anything at runtime.
Code copied from: https://esdiscuss.org/topic/string-identity-template-tag
sql } from '@livestore/livestore'import { import Schema
Schema } from 'effect'import { class StoreTag
StoreTag } from './atoms.ts'
// User schema for type safetyconst const User: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}>
User = import Schema
Schema.function Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}>(fields: { id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}): Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String, name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String, isActive: typeof Schema.Boolean
isActive: import Schema
Schema.class Booleanexport Boolean
Boolean,})
const const Product: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}>
Product = import Schema
Schema.function Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}>(fields: { id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}): Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String, name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String, createdAt: typeof Schema.DateTimeUtc
createdAt: import Schema
Schema.class DateTimeUtc
Defines a schema that attempts to convert a string
to a DateTime.Utc
instance using the DateTime.unsafeMake
constructor.
DateTimeUtc,})
// Search term atom for dynamic queriesexport const const searchTermAtom: Atom.Writable<string, string>
searchTermAtom = import Atom
Atom.const make: <string>(initialValue: string) => Atom.Writable<string, string> (+5 overloads)
make<string>('')
// Re-export from utils for convenienceexport { const usersQueryAtom: Atom.Atom<Result<readonly { readonly id: string; readonly name: string; readonly email: string; readonly isActive: boolean; readonly createdAt: Date;}[], never>>
usersQueryAtom as const usersAtom: Atom.Atom<Result<readonly { readonly id: string; readonly name: string; readonly email: string; readonly isActive: boolean; readonly createdAt: Date;}[], never>>export usersAtom
usersAtom } from './utils.ts'
// Query with SQLexport const const activeUsersAtom: Atom.Atom<Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>
activeUsersAtom = class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.makeQuery: <readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[]>(query: LiveQueryDef<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], "def">) => Atom.Atom<Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>
Creates a Atom that allows you to resolve a LiveQueryDef. It embeds the loading
of the Store and will emit a Result
that contains the result of the query
makeQuery( queryDb<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[]>(queryInput: QueryInputRaw<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], readonly any[]> | QueryBuilder<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], any, any>, options?: { map?: (rows: readonly { readonly id: string; readonly name: string; readonly isActive: boolean; }[]) => readonly { readonly id: string; readonly name: string; readonly isActive: boolean; }[]; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<...> (+1 overload)
NOTE queryDb
is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps
option.
queryDb({ query: string
query: const sql: (template: TemplateStringsArray, ...args: unknown[]) => string
This is a tag function for tagged literals.
it lets us get syntax highlighting on SQL queries in VSCode, but
doesn't do anything at runtime.
Code copied from: https://esdiscuss.org/topic/string-identity-template-tag
sql`SELECT * FROM users WHERE isActive = true ORDER BY name`, schema: Schema.Schema<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], readonly any[], never>
schema: import Schema
Schema.Array<Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}>>(value: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}>): Schema.Array$<Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}>>export Array
Array(const User: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; isActive: typeof Schema.Boolean;}>
User), }),)
// Static query example - dynamic queries would need a different approach// For dynamic queries, you'd typically use a derived atom that depends on searchTermAtomexport const const searchResultsAtom: Atom.Atom<Result<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], never>>
searchResultsAtom = class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.makeQuery: <readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[]>(query: LiveQueryDef<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], "def">) => Atom.Atom<Result<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], never>>
Creates a Atom that allows you to resolve a LiveQueryDef. It embeds the loading
of the Store and will emit a Result
that contains the result of the query
makeQuery( queryDb<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[]>(queryInput: QueryInputRaw<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], readonly any[]> | QueryBuilder<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], any, any>, options?: { map?: (rows: readonly { readonly id: string; readonly name: string; readonly createdAt: Utc; }[]) => readonly { readonly id: string; readonly name: string; readonly createdAt: Utc; }[]; label?: string; deps?: DepKey;} | undefined): LiveQueryDef<...> (+1 overload)
NOTE queryDb
is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
When using contextual data when constructing the query, please make sure to include it in the deps
option.
queryDb({ query: string
query: const sql: (template: TemplateStringsArray, ...args: unknown[]) => string
This is a tag function for tagged literals.
it lets us get syntax highlighting on SQL queries in VSCode, but
doesn't do anything at runtime.
Code copied from: https://esdiscuss.org/topic/string-identity-template-tag
sql`SELECT * FROM products ORDER BY createdAt DESC`, schema: Schema.Schema<readonly { readonly id: string; readonly name: string; readonly createdAt: Utc;}[], readonly any[], never>
schema: import Schema
Schema.Array<Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}>>(value: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}>): Schema.Array$<Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}>>export Array
Array(const Product: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; createdAt: typeof Schema.DateTimeUtc;}>
Product), }),)
Using Queries in React Components
Section titled “Using Queries in React Components”Access query results in React components with the useAtomValue
hook. When using StoreTag.makeQuery
(non-unsafe API), the result is wrapped in a Result type for proper loading and error handling:
import { import Result
Result, const useAtomValue: { <A>(atom: Atom<A>): A; <A, B>(atom: Atom<A>, f: (_: A) => B): B;}
useAtomValue } from '@effect-atom/atom-react'import { const activeUsersAtom: Atom<Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>
activeUsersAtom } from './queries.ts'
export function function UserList(): JSX.Element
UserList() { const const users: Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>
users = useAtomValue<Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>(atom: Atom<Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>): Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never> (+1 overload)
useAtomValue(const activeUsersAtom: Atom<Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>
activeUsersAtom)
return import Result
Result.const builder: <Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>>(self: Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>) => Result.Builder<never, readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never, true>
builder(const users: Result.Result<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>
users) .onInitial<JSX.Element>(f: (result: Result.Initial<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>) => JSX.Element): Result.Builder<JSX.Element, readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never, never>
onInitial(() => <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading users...</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>) .onSuccess<JSX.Element>(f: (value: readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], result: Result.Success<readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[], never>) => JSX.Element): Result.Builder<JSX.Element, never, never, never>
onSuccess((users: readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[]
users) => ( <React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul> {users: readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[]
users.ReadonlyArray<{ readonly id: string; readonly name: string; readonly isActive: boolean; }>.map<JSX.Element>(callbackfn: (value: { readonly id: string; readonly name: string; readonly isActive: boolean;}, index: number, array: readonly { readonly id: string; readonly name: string; readonly isActive: boolean;}[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
map((user: { readonly id: string; readonly name: string; readonly isActive: boolean;}
user) => ( <React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li React.Attributes.key?: React.Key | null | undefined
key={user: { readonly id: string; readonly name: string; readonly isActive: boolean;}
user.id: string
id}>{user: { readonly id: string; readonly name: string; readonly isActive: boolean;}
user.name: string
name}</React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li> ))} </React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
ul> )) .onDefect<JSX.Element>(f: (defect: unknown, result: Result.Failure<never, never>) => JSX.Element): Result.Builder<JSX.Element, never, never, never>
onDefect((error: any
error: any) => <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Error: {error: any
error.any
message}</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>) .function render(): JSX.Element
render()}
Integrating Effect Services
Section titled “Integrating Effect Services”Combine Effect services with LiveStore operations using the store’s runtime:
import { const useAtomSet: <R, W, Mode extends "value" | "promise" | "promiseExit" = never>(atom: Writable<R, W>, options?: { readonly mode?: ([R] extends [Result<any, any>] ? Mode : "value") | undefined;}) => "promise" extends Mode ? ((value: W, options?: { readonly signal?: AbortSignal | undefined;} | undefined) => Promise<Result.Success<R>>) : "promiseExit" extends Mode ? ((value: W, options?: { readonly signal?: AbortSignal | undefined;} | undefined) => Promise<Exit<Result.Success<R>, Result.Failure<R>>>) : ((value: W | ((value: R) => W)) => void)
useAtomSet } from '@effect-atom/atom-react'import { import Context
Context, import Effect
Effect } from 'effect'import { class StoreTag
StoreTag } from './atoms.ts'import { const events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; productCreated: EventDef<...>; ... 4 more ...; itemUpdated: EventDef<...>;}
events } from './schema.ts'
// Example service definitionexport class class MyService
MyService extends import Context
Context.const Tag: <"MyService">(id: "MyService") => <Self, Shape>() => Context.TagClass<Self, "MyService", Shape>
Tag('MyService')< class MyService
MyService, { processItem: (name: string) => Effect.Effect<{ name: string; metadata: Record<string, unknown>;}>
processItem: (name: string
name: string) => import Effect
Effect.interface Effect<out A, out E = never, out R = never>
The Effect
interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect
interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R
, and the result can either be a success with a value of type A
or a
failure with an error of type E
. The Effect
is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect
value, you need a Runtime
, which provides the
environment necessary to run and manage the computation.
Effect<{ name: string
name: string metadata: Record<string, unknown>
metadata: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record<string, unknown> }> }>() {}
// Use the commit hook for event handlingexport const const useCommit: () => (value: {} | ((value: void) => {})) => void
useCommit = () => useAtomSet<void, {}, never>(atom: Writable<void, {}>, options?: { readonly mode?: "value" | undefined;} | undefined): (value: {} | ((value: void) => {})) => void
useAtomSet(class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.commit: Writable<void, {}>
A Atom.Writable that allows you to commit an event to the Store.
commit)
// Simple commit exampleexport const const createItemAtom: AtomResultFn<string, void, never>
createItemAtom = class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.runtime: AtomRuntime<StoreTag, never>
runtime.AtomRuntime<StoreTag, never>.fn: <string>() => { <E, A>(fn: (arg: string, get: FnContext) => Effect.Effect<A, E, StoreTag | Scope | AtomRegistry | Reactivity>, options?: { readonly initialValue?: A | undefined; readonly reactivityKeys?: ReadonlyArray<unknown> | ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined; } | undefined): AtomResultFn<string, A, E>; <E, A>(fn: (arg: string, get: FnContext) => Stream<...>, options?: { ...; } | undefined): AtomResultFn<...>;} (+2 overloads)
fn<string>()((itemName: string
itemName, get: FnContext
get) => { return import Effect
Effect.const sync: <void>(thunk: LazyArg<void>) => Effect.Effect<void, never, never>
Creates an Effect
that represents a synchronous side-effectful computation.
Details
The provided function (thunk
) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
When to Use
Use this function when you are sure the operation will not fail.
Example (Logging a Message)
import { Effect } from "effect"
const log = (message: string) => Effect.sync(() => { console.log(message) // side effect })
// ┌─── Effect<void, never, never>// ▼const program = log("Hello, World!")
sync(() => { const const store: Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined
store = get: FnContext<Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined>(atom: Atom<...>) => Store<...> | undefined
get(class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.storeUnsafe: Atom<Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined>
A Atom that allows you to access the Store. It will emit the Store or
undefined
if has not been created yet.
storeUnsafe) if (const store: Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined
store) { const store: Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}>
store.Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.commit: <readonly [{ name: "itemCreated"; args: { readonly id: string; readonly name: string; readonly metadata: { readonly [x: string]: unknown; }; };}]>(list_0: { name: "itemCreated"; args: { readonly id: string; readonly name: string; readonly metadata: { readonly [x: string]: unknown; }; };}) => void (+3 overloads)
commit( const events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; productCreated: EventDef<...>; ... 4 more ...; itemUpdated: EventDef<...>;}
events.itemCreated: (args: { readonly id: string; readonly name: string; readonly metadata: { readonly [x: string]: unknown; };}) => { name: "itemCreated"; args: { readonly id: string; readonly name: string; readonly metadata: { readonly [x: string]: unknown; }; };}
Helper function to construct a partial event
itemCreated({ id: string
id: var crypto: Crypto
crypto.Crypto.randomUUID(): `${string}-${string}-${string}-${string}-${string}` (+1 overload)
randomUUID(), name: string
name: itemName: string
itemName, metadata: { readonly [x: string]: unknown;}
metadata: { createdAt: string
createdAt: new var Date: DateConstructornew () => Date (+3 overloads)
Date().Date.toISOString(): string
Returns a date as a string value in ISO format.
toISOString() }, }), ) } })})
// Use in a React componentexport function function CreateItemButton(): JSX.Element
CreateItemButton() { const const createItem: (value: string | typeof Reset | ((value: Result<void, never>) => string | typeof Reset)) => void
createItem = useAtomSet<Result<void, never>, string | typeof Reset, never>(atom: Writable<Result<void, never>, string | typeof Reset>, options?: { readonly mode?: undefined;} | undefined): (value: string | typeof Reset | ((value: Result<void, never>) => string | typeof Reset)) => void
useAtomSet(const createItemAtom: AtomResultFn<string, void, never>
createItemAtom)
const const handleClick: () => void
handleClick = () => { const createItem: (value: string | typeof Reset | ((value: Result<void, never>) => string | typeof Reset)) => void
createItem('New Item') }
return ( <React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button React.ButtonHTMLAttributes<HTMLButtonElement>.type?: "button" | "submit" | "reset" | undefined
type="button" React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
onClick={const handleClick: () => void
handleClick}> Create Item </React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> )}
Advanced Patterns
Section titled “Advanced Patterns”Optimistic Updates
Section titled “Optimistic Updates”Combine local state with LiveStore for optimistic UI updates. When using StoreTag.makeQueryUnsafe
, the data is directly available:
import { import Atom
Atom } from '@effect-atom/atom'import { const pendingTodosAtom: Atom.Writable<PendingTodo[], PendingTodo[]>
pendingTodosAtom, const todosQueryUnsafeAtom: Atom.Atom<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined>
todosQueryUnsafeAtom } from '../store-setup/utils.ts'
// Combine real and pending todos for optimistic UIexport const const optimisticTodoAtom: Atom.Atom<(PendingTodo | { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;})[]>
optimisticTodoAtom = import Atom
Atom.const make: <(PendingTodo | { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;})[]>(create: (get: Atom.Context) => (PendingTodo | { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;})[]) => Atom.Atom<(PendingTodo | { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;})[]> (+5 overloads)
make((get: Atom.Context
get) => { const const todos: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined
todos = get: Atom.Context<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined>(atom: Atom.Atom<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined>) => readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined
get(const todosQueryUnsafeAtom: Atom.Atom<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined>
todosQueryUnsafeAtom) // Direct array, not wrapped in Result const const pending: PendingTodo[]
pending = get: Atom.Context<PendingTodo[]>(atom: Atom.Atom<PendingTodo[]>) => PendingTodo[]
get(const pendingTodosAtom: Atom.Writable<PendingTodo[], PendingTodo[]>
pendingTodosAtom)
return [...(const todos: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] | undefined
todos || []), ...const pending: PendingTodo[]
pending]})
Derived State
Section titled “Derived State”Create computed atoms based on LiveStore queries. When using the non-unsafe API, handle the Result type:
import { import Atom
Atom } from '@effect-atom/atom'import { import Result
Result } from '@effect-atom/atom-react'import { const todosQueryAtom: Atom.Atom<Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>>
todosQueryAtom } from '../store-setup/utils.ts'
// Derive statistics from todosexport const const todoStatsAtom: Atom.Atom<Result.Result<{ total: number; completed: number; pending: number;}, never>>
todoStatsAtom = import Atom
Atom.const make: <Result.Result<{ total: number; completed: number; pending: number;}, never>>(create: (get: Atom.Context) => Result.Result<{ total: number; completed: number; pending: number;}, never>) => Atom.Atom<Result.Result<{ total: number; completed: number; pending: number;}, never>> (+5 overloads)
make((get: Atom.Context
get) => { const const todos: Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>
todos = get: Atom.Context<Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>>(atom: Atom.Atom<Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>>) => Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>
get(const todosQueryAtom: Atom.Atom<Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>>
todosQueryAtom) // Result wrapped
return import Result
Result.const map: <never, readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], { total: number; completed: number; pending: number;}>(self: Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>, f: (a: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]) => { total: number; completed: number; pending: number;}) => Result.Result<{ total: number; completed: number; pending: number;}, never> (+1 overload)
map(const todos: Result.Result<readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[], never>
todos, (todoList: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]
todoList) => ({ total: number
total: todoList: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]
todoList.ReadonlyArray<T>.length: number
Gets the length of the array. This is a number one higher than the highest element defined in an array.
length, completed: number
completed: todoList: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]
todoList.ReadonlyArray<{ readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date; }>.filter(predicate: (value: { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}, index: number, array: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]) => unknown, thisArg?: any): { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
filter((t: { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}
t) => t: { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}
t.completed: boolean
completed).Array<{ readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date; }>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length, pending: number
pending: todoList: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]
todoList.ReadonlyArray<{ readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date; }>.filter(predicate: (value: { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}, index: number, array: readonly { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[]) => unknown, thisArg?: any): { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
filter((t: { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}
t) => !t: { readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date;}
t.completed: boolean
completed).Array<{ readonly id: string; readonly text: string; readonly completed: boolean; readonly createdAt: Date; }>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length, }))})
Batch Operations
Section titled “Batch Operations”Perform multiple commits efficiently (commits are synchronous):
import { import Effect
Effect } from 'effect'import { class StoreTag
StoreTag } from '../store-setup/atoms.ts'import { const events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; productCreated: EventDef<...>; ... 4 more ...; itemUpdated: EventDef<...>;}
events } from '../store-setup/schema.ts'
// Bulk update atom for batch operationsexport const const bulkUpdateAtom: AtomResultFn<string[], void, never>
bulkUpdateAtom = class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.runtime: AtomRuntime<StoreTag, never>
runtime.AtomRuntime<StoreTag, never>.fn: <string[]>() => { <E, A>(fn: (arg: string[], get: FnContext) => Effect.Effect<A, E, StoreTag | Scope | AtomRegistry | Reactivity>, options?: { readonly initialValue?: A | undefined; readonly reactivityKeys?: ReadonlyArray<unknown> | ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined; } | undefined): AtomResultFn<string[], A, E>; <E, A>(fn: (arg: string[], get: FnContext) => Stream<...>, options?: { ...; } | undefined): AtomResultFn<...>;} (+2 overloads)
fn<string[]>()( import Effect
Effect.const fn: <never, void, [ids: string[], get: FnContext]>(body: (ids: string[], get: FnContext) => Generator<never, void, never>) => (ids: string[], get: FnContext) => Effect.Effect<void, never, never> (+20 overloads)
fn(function* (ids: string[]
ids, get: FnContext
get) { const const store: Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined
store = get: FnContext<Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined>(atom: Atom<...>) => Store<...> | undefined
get(class StoreTag
StoreTag.AtomLiveStore<StoreTag, "StoreTag", FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.storeUnsafe: Atom<Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined>
A Atom that allows you to access the Store. It will emit the Store or
undefined
if has not been created yet.
storeUnsafe) if (!const store: Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}> | undefined
store) return
// Commit multiple events synchronously for (const const id: string
id of ids: string[]
ids) { const store: Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; ... 5 more ...; itemUpdated: EventDef<...>; }; state: InternalState;}>, {}>
store.Store<FromInputSchema.DeriveSchema<{ events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; ... 6 more ...; itemUpdated: EventDef<...>; }; state: InternalState; }>, {}>.commit: <readonly [{ name: "itemUpdated"; args: { readonly id: string; readonly status: string; };}]>(list_0: { name: "itemUpdated"; args: { readonly id: string; readonly status: string; };}) => void (+3 overloads)
commit(const events: { userCreated: EventDef<"userCreated", { readonly id: string; readonly name: string; readonly email: string; }, { readonly id: string; readonly name: string; readonly email: string; }, false>; userUpdated: EventDef<"userUpdated", { readonly id: string; readonly name: Option<string>; readonly email: Option<string>; readonly isActive: Option<boolean>; }, { readonly id: string; readonly name?: string | undefined; readonly email?: string | undefined; readonly isActive?: boolean | undefined; }, false>; productCreated: EventDef<...>; ... 4 more ...; itemUpdated: EventDef<...>;}
events.itemUpdated: (args: { readonly id: string; readonly status: string;}) => { name: "itemUpdated"; args: { readonly id: string; readonly status: string; };}
Helper function to construct a partial event
itemUpdated({ id: string
id, status: string
status: 'processed' })) } }),)
Best Practices
Section titled “Best Practices”- Use
StoreTag.makeQuery
for queries: This ensures proper Effect integration and error handling - Leverage Effect services: Integrate business logic through Effect services for better testability
- Handle loading states: Use
Result.builder
pattern for consistent loading/error UI - Batch React updates: Always provide
batchUpdates
for better performance - Label queries: Add descriptive labels to queries for better debugging
- Type safety: Let TypeScript infer types from schemas rather than manual annotations
Real-World Example
Section titled “Real-World Example”For a comprehensive example of LiveStore with Effect Atom in action, check out Cheffect - a recipe management application that demonstrates:
- Complete Effect service integration
- AI-powered recipe extraction using Effect services
- Complex query patterns with search and filtering
- Worker-based persistence with OPFS
- Production-ready error handling and logging