Skip to content
Ilha
Esc
↑↓navigate↵open⌘Jpreview
On this page

Elm architecture

Elm-style Model, update, commands, and subscriptions for one island.

@ilha/model holds one island’s state in a single model. You describe transitions in update, side effects as return values, and rendering in view.

It is a separate package from ilha. Add it when one island owns enough messages to deserve a single loop. Plain atom() keeps working for everything else.

Install

npm install @ilha/model
pnpm add @ilha/model
yarn add @ilha/model
bun add @ilha/model
nub add @ilha/model
aube add @ilha/model

ilha and effect are peer dependencies.

Define the loop

You define the initial model, one pure update, and a pure view. Event handlers report facts with dispatch, and update decides what each fact means.

import { import DataData } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
} from "@ilha/model";
import type { type Dispatch<Message> = (message: Message) => void
Send a message into the app loop.
Dispatch
} from "@ilha/model";
type
type Message = {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{
type Decrement: {}Decrement: {}; type Increment: {}Increment: {}; }>; const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}
Message
>();
interface Model { readonly Model.count: numbercount: number; } const
const update: (model: Model, message: Message) => {
    model: {
        count: number;
    };
} | {
    model: {
        count: number;
    };
}
update
= (model: Modelmodel: Model,
message: {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}
message
:
type Message = {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}
Message
) =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>
Message
.
$match: <{
    Decrement: () => {
        model: {
            count: number;
        };
    };
    Increment: () => {
        model: {
            count: number;
        };
    };
}>(value: {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}, cases: {
    Decrement: () => {
        model: {
            count: number;
        };
    };
    Increment: () => {
        model: {
            count: number;
        };
    };
}) => {
    model: {
        count: number;
    };
} | {
    model: {
        count: number;
    };
} (+1 overload)
$match
(
message: {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}
message
, {
type Decrement: () => {
    model: {
        count: number;
    };
}
Decrement
: () => ({
model: {
    count: number;
}
model
: { count: numbercount: model: Modelmodel.Model.count: numbercount - 1 } }),
type Increment: () => {
    model: {
        count: number;
    };
}
Increment
: () => ({
model: {
    count: number;
}
model
: { count: numbercount: model: Modelmodel.Model.count: numbercount + 1 } }),
}); const const view: (model: Model, dispatch: Dispatch<Message>) => Viewview = (model: Modelmodel: Model,
dispatch: Dispatch<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>
dispatch
: type Dispatch<Message> = (message: Message) => void
Send a message into the app loop.
Dispatch
<
type Message = {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}
Message
>) => (
<JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div> <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{model: Modelmodel.Model.count: numbercount}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p> <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() =>
dispatch: (message: {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}) => void
dispatch
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>
Message
.
type Decrement: (args: void) => {
    readonly _tag: "Decrement";
}
Decrement
())}>
- </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton> <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() =>
dispatch: (message: {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}) => void
dispatch
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>
Message
.
type Increment: (args: void) => {
    readonly _tag: "Increment";
}
Increment
())}>
+ </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton> </JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div> ); const
const app: ModelApp<{
    count: number;
}, {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}, never, never>
app
=
defineApp<{
    count: number;
}, {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}, Layer<never, never, never>, {
    init: () => {
        model: {
            count: number;
        };
    };
    update: (model: Model, message: Message) => {
        model: {
            count: number;
        };
    } | {
        model: {
            count: number;
        };
    };
    view: (model: Model, dispatch: Dispatch<Message>) => View;
}>(options: LooseOptions<{
    count: number;
}, {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}> & {
    init: () => {
        model: {
            count: number;
        };
    };
    update: (model: Model, message: Message) => {
        model: {
            count: number;
        };
    } | {
        model: {
            count: number;
        };
    };
    view: (model: Model, dispatch: Dispatch<Message>) => View;
} & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<{
    count: number;
}, {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}, any>) & (() => {
    model: {
        count: number;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: {
    count: number;
} & {
    count: number;
}
model
: { count: numbercount: 0 } }),
update: ((model: {
    count: number;
}, message: {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}) => UpdateResult<{
    count: number;
}, {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}, any>) & ((model: Model, message: Message) => {
    model: {
        count: number;
    };
} | {
    model: {
        count: number;
    };
})
The only place state changes. Must be pure.
update
,
view: ((model: {
    count: number;
}, dispatch: Dispatch<{
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}>) => View) & ((model: Model, dispatch: Dispatch<Message>) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
});
export const const Counter: ComponentCounter =
const app: ModelApp<{
    count: number;
}, {
    readonly _tag: "Decrement";
} | {
    readonly _tag: "Increment";
}, never, never>
app
.ModelApp<{ count: number; }, { readonly _tag: "Decrement"; } | { readonly _tag: "Increment"; }, never, never>.component: Componentcomponent;

Mount Counter like any component: mount(host, Counter) or await renderToString(Counter). The model atom snapshots like any other atom, so SSR and hydration work unchanged.

Name messages for their cause (ClickedSave, GotUser), and keep update pure so every transition stays testable. Messages are plain data describing what happened, never callbacks or class instances.

Send one-shot work

update returns the next model plus optional commands. A command is an Effect that resolves to a follow-up message or nothing. The runtime runs commands concurrently with each other.

import { import DataData, import EffectEffect } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
} from "@ilha/model";
type
type Message = {
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{
type AddBonus: {
    readonly amount: number;
}
AddBonus
: { readonly amount: numberamount: number };
type FetchBonus: {}FetchBonus: {}; }>; const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}
Message
>();
interface Model { readonly Model.count: numbercount: number; } const
const update: (model: Model, message: Message) => {
    cmd: Effect.Effect<{
        readonly _tag: "AddBonus";
        readonly amount: number;
    }, never, never>;
    model: Model;
} | {
    model: {
        count: number;
    };
}
update
= (model: Modelmodel: Model,
message: {
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}
message
:
type Message = {
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}
Message
) =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}>
Message
.
$match: <{
    FetchBonus: () => {
        cmd: Effect.Effect<{
            readonly _tag: "AddBonus";
            readonly amount: number;
        }, never, never>;
        model: Model;
    };
    AddBonus: ({ amount }: {
        readonly _tag: "AddBonus";
        readonly amount: number;
    }) => {
        model: {
            count: number;
        };
    };
}>(value: {
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}, cases: {
    FetchBonus: () => {
        cmd: Effect.Effect<{
            readonly _tag: "AddBonus";
            readonly amount: number;
        }, never, never>;
        model: Model;
    };
    AddBonus: ({ amount }: {
        readonly _tag: "AddBonus";
        readonly amount: number;
    }) => {
        model: {
            count: number;
        };
    };
}) => {
    ...;
} | {
    ...;
} (+1 overload)
$match
(
message: {
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}
message
, {
type FetchBonus: () => {
    cmd: Effect.Effect<{
        readonly _tag: "AddBonus";
        readonly amount: number;
    }, never, never>;
    model: Model;
}
FetchBonus
: () => ({
cmd: Effect.Effect<{
    readonly _tag: "AddBonus";
    readonly amount: number;
}, never, never>
cmd
: import EffectEffect.
const succeed: <{
    readonly _tag: "AddBonus";
    readonly amount: number;
}>(value: {
    readonly _tag: "AddBonus";
    readonly amount: number;
}) => Effect.Effect<{
    readonly _tag: "AddBonus";
    readonly amount: number;
}, never, never>
Creates an `Effect` that always succeeds with a given value. **When to use** Use when an effect should complete successfully with a specific value without any errors or external dependencies. **Example** (Creating a successful effect) ```ts import.meta.vitest import { Effect } from "effect" // Creating an effect that represents a successful scenario // // ┌─── Effect<number, never, never> // ▼ const success = Effect.succeed(42) Effect.runSync(success) // => 42 ```
@see{@link fail} to create an effect that represents a failure.@stabilitystable@categoryconstructors@since2.0.0
succeed
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "AddBonus";
    readonly amount: number;
} | {
    readonly _tag: "FetchBonus";
}>
Message
.
type AddBonus: (args: {
    readonly amount: number;
}) => {
    readonly _tag: "AddBonus";
    readonly amount: number;
}
AddBonus
({ amount: numberamount: 10 })),
model: Modelmodel, }),
type AddBonus: ({ amount }: {
    readonly _tag: "AddBonus";
    readonly amount: number;
}) => {
    model: {
        count: number;
    };
}
AddBonus
: ({ amount: numberamount }) => ({
model: {
    count: number;
}
model
: { count: numbercount: model: Modelmodel.Model.count: numbercount + amount: numberamount },
}), }); const const view: (model: Model) => Viewview = (model: Modelmodel: Model) => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{model: Modelmodel.Model.count: numbercount}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; export const
const app: ModelApp<Model, {
    readonly _tag: "AddBonus";
    readonly amount: number;
}, never, never>
app
=
defineApp<Model, {
    readonly _tag: "AddBonus";
    readonly amount: number;
}, Layer<never, never, never>, {
    init: () => {
        model: {
            count: number;
        };
    };
    update: (model: Model, message: Message) => {
        cmd: Effect.Effect<{
            readonly _tag: "AddBonus";
            readonly amount: number;
        }, never, never>;
        model: Model;
    } | {
        model: {
            count: number;
        };
    };
    view: (model: Model) => View;
}>(options: LooseOptions<Model, {
    readonly _tag: "AddBonus";
    readonly amount: number;
}> & {
    init: () => {
        model: {
            count: number;
        };
    };
    update: (model: Model, message: Message) => {
        cmd: Effect.Effect<{
            readonly _tag: "AddBonus";
            readonly amount: number;
        }, never, never>;
        model: Model;
    } | {
        model: {
            count: number;
        };
    };
    view: (model: Model) => View;
} & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<Model, {
    readonly _tag: "AddBonus";
    readonly amount: number;
}, any>) & (() => {
    model: {
        count: number;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: Model & {
    count: number;
}
model
: { count: numbercount: 0 } }),
update: ((model: Model, message: {
    readonly _tag: "AddBonus";
    readonly amount: number;
}) => UpdateResult<Model, {
    readonly _tag: "AddBonus";
    readonly amount: number;
}, any>) & ((model: Model, message: Message) => {
    cmd: Effect.Effect<{
        readonly _tag: "AddBonus";
        readonly amount: number;
    }, never, never>;
    model: Model;
} | {
    model: {
        count: number;
    };
})
The only place state changes. Must be pure.
update
,
view: ((model: Model, dispatch: Dispatch<{
    readonly _tag: "AddBonus";
    readonly amount: number;
}>) => View) & ((model: Model) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
,
});

A command’s error channel is never: the compiler rejects Effect.fail, so you map every expected failure into a message. A defect — a throw, a die, a missing service — is logged through the Effect logger and the island keeps running.

Reach services through a layer

Commands and subscriptions read services from one app-level layer. Name the layer in defineApp; commands use its services with no extra wiring.

import { import ContextContext, import DataData, import EffectEffect, import LayerLayer } from "effect";
import { 
const defineApp: <Model, Message, L extends Layer.Any = Layer.Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Layer.Success<L>, Layer.Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
} from "@ilha/model";
type
type Message = {
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{
type Greet: {}Greet: {};
type Greeted: {
    readonly text: string;
}
Greeted
: { readonly text: stringtext: string };
}>; const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}
Message
>();
const
const Greeter: Context.Service<{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>
Greeter
= import ContextContext.
const Service: <{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>(key: string, options?: {} | undefined) => Context.Service<{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}> (+2 overloads)
Creates a `Context` service key. **When to use** Use when you need to define a context service key for a dependency that must be provided by the surrounding context. **Details** Call `Context.Service("Key")` for a function-style key, or use the two-stage form `Context.Service<Self, Shape>()("Key")` for class-style service declarations. The returned key can be yielded as an Effect and passed to `Context.make`, `Context.add`, and the Context getter functions. **Gotchas** The string key is the runtime identity of the service. Reusing the same key string for unrelated services makes them occupy the same slot in a `Context`. **Example** (Creating service keys) ```ts import.meta.vitest import { Context } from "effect" // Create a simple service const Database = Context.Service<{ query: (sql: string) => string }>("Database") // Create a service class class Config extends Context.Service<Config, { port: number }>()("Config") {} // Use the services to create contexts const db = Context.make(Database, { query: (sql) => `Result: ${sql}` }) const config = Context.make(Config, { port: 8080 }) Context.get(db, Database).query("SELECT 1") // => "Result: SELECT 1" Context.get(config, Config).port // => 8080 ```
@see{@link Reference} for service keys with default values@stabilitystable@categoryservices@since4.0.0
Service
<{ readonly greet: (name: string) => stringgreet: (name: stringname: string) => string }>(
"Greeter" ); export const
const app: ModelApp<{
    greeting: string;
}, {
    readonly _tag: "Greeted";
    readonly text: string;
}, {
    readonly greet: (name: string) => string;
}, never>
app
=
defineApp<{
    greeting: string;
}, {
    readonly _tag: "Greeted";
    readonly text: string;
}, Layer.Layer<{
    readonly greet: (name: string) => string;
}, never, never>, {
    init: () => {
        model: {
            greeting: string;
        };
    };
    layer: Layer.Layer<{
        readonly greet: (name: string) => string;
    }, never, never>;
    update: (model: {
        greeting: string;
    }, message: Message) => {
        cmd: Effect.Effect<{
            readonly _tag: "Greeted";
            readonly text: string;
        }, never, {
            readonly greet: (name: string) => string;
        }>;
        model: {
            greeting: string;
        };
    } | {
        model: {
            greeting: string;
        };
    };
    view: (model: {
        greeting: string;
    }) => View;
}>(options: LooseOptions<...> & ... 1 more ... & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<{
    greeting: string;
}, {
    readonly _tag: "Greeted";
    readonly text: string;
}, any>) & (() => {
    model: {
        greeting: string;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: {
    greeting: string;
} & {
    greeting: string;
}
model
: { greeting: stringgreeting: "" } }),
layer: Layer.Layer<{
    readonly greet: (name: string) => string;
}, never, never>
layer
: import LayerLayer.
const succeed: <{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>(service: Context.Key<{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>, resource: {
    readonly greet: (name: string) => string;
}) => Layer.Layer<{
    readonly greet: (name: string) => string;
}, never, never> (+1 overload)
Constructs a layer that provides a single service from an already available value. **When to use** Use when you need a `Layer` that provides a service from an already constructed implementation without effectful acquisition. **Example** (Creating a layer from a service implementation) ```ts import.meta.vitest import { Context, Effect, Layer } from "effect" class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string> }>()("Database") {} const DatabaseLayer = Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`)) }) const program = Database.use((database) => database.query("SELECT 1")) Effect.runSync(Effect.provide(program, DatabaseLayer)) // => "Query result: SELECT 1" ```
@see{@link sync} for constructing layers from lazy values@stabilitystable@categoryconstructors@since2.0.0
succeed
(
const Greeter: Context.Service<{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>
Greeter
, { greet: (name: string) => stringgreet: (name: stringname) => `Hello, ${name: stringname}!` }),
update: ((model: {
    greeting: string;
}, message: {
    readonly _tag: "Greeted";
    readonly text: string;
}) => UpdateResult<{
    greeting: string;
}, {
    readonly _tag: "Greeted";
    readonly text: string;
}, any>) & ((model: {
    greeting: string;
}, message: Message) => {
    cmd: Effect.Effect<{
        readonly _tag: "Greeted";
        readonly text: string;
    }, never, {
        readonly greet: (name: string) => string;
    }>;
    model: {
        greeting: string;
    };
} | {
    model: {
        greeting: string;
    };
})
The only place state changes. Must be pure.
update
: (
model: {
    greeting: string;
}
model
: { greeting: stringgreeting: string },
message: {
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}
message
:
type Message = {
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}
Message
) =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}>
Message
.
$match: <{
    Greet: () => {
        cmd: Effect.Effect<{
            readonly _tag: "Greeted";
            readonly text: string;
        }, never, {
            readonly greet: (name: string) => string;
        }>;
        model: {
            greeting: string;
        };
    };
    Greeted: ({ text }: {
        readonly _tag: "Greeted";
        readonly text: string;
    }) => {
        model: {
            greeting: string;
        };
    };
}>(value: {
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}, cases: {
    Greet: () => {
        cmd: Effect.Effect<{
            readonly _tag: "Greeted";
            readonly text: string;
        }, never, {
            readonly greet: (name: string) => string;
        }>;
        model: {
            greeting: string;
        };
    };
    Greeted: ({ text }: {
        readonly _tag: "Greeted";
        readonly text: string;
    }) => {
        model: {
            greeting: string;
        };
    };
}) => {
    ...;
} | {
    ...;
} (+1 overload)
$match
(
message: {
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}
message
, {
type Greet: () => {
    cmd: Effect.Effect<{
        readonly _tag: "Greeted";
        readonly text: string;
    }, never, {
        readonly greet: (name: string) => string;
    }>;
    model: {
        greeting: string;
    };
}
Greet
: () => ({
cmd: Effect.Effect<{
    readonly _tag: "Greeted";
    readonly text: string;
}, never, {
    readonly greet: (name: string) => string;
}>
cmd
: import EffectEffect.
const map: <{
    readonly greet: (name: string) => string;
}, never, {
    readonly greet: (name: string) => string;
}, {
    readonly _tag: "Greeted";
    readonly text: string;
}>(self: Effect.Effect<{
    readonly greet: (name: string) => string;
}, never, {
    readonly greet: (name: string) => string;
}>, f: (a: {
    readonly greet: (name: string) => string;
}) => {
    readonly _tag: "Greeted";
    readonly text: string;
}) => Effect.Effect<{
    readonly _tag: "Greeted";
    readonly text: string;
}, never, {
    readonly greet: (name: string) => string;
}> (+1 overload)
Transforms the value inside an effect by applying a function to it. **When to use** Use to transform an effect's success value with a function that returns a plain value, producing a new effect without changing the original effect's typed error or context requirements. **Details** `map` takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value. It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value. **Example** (Choosing map syntax variants) ```ts import.meta.vitest import { Effect, pipe } from "effect" const output: Array<unknown> = [] const myEffect = Effect.succeed(1) const transformation = (n: number) => n + 1 const mappedWithPipe = pipe(myEffect, Effect.map(transformation)) const mappedWithDataFirst = Effect.map(myEffect, transformation) const mappedWithMethod = myEffect.pipe(Effect.map(transformation)) void output.push(Effect.runSync(Effect.all([ mappedWithPipe, mappedWithDataFirst, mappedWithMethod ]))) output // => [[2, 2, 2]] ``` **Example** (Adding a service charge) ```ts import.meta.vitest import { Effect, pipe } from "effect" const addServiceCharge = (amount: number) => amount + 1 const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) const finalAmount = pipe( fetchTransactionAmount, Effect.map(addServiceCharge) ) await Effect.runPromise(finalAmount) // => 101 ```
@see{@link mapError} for a version that operates on the error channel.@see{@link mapBoth} for a version that operates on both channels.@see{@link flatMap} or {@link andThen} for a version that can return a new effect.@stabilitystable@categorymapping@since2.0.0
map
(import EffectEffect.
const service: <{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>(service: Context.Key<{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>) => Effect.Effect<{
    readonly greet: (name: string) => string;
}, never, {
    readonly greet: (name: string) => string;
}>
Accesses a service from the context. **Example** (Accessing a required service) ```ts import.meta.vitest import { Context, Effect } from "effect" interface Database { readonly query: (sql: string) => Effect.Effect<string> } const Database = Context.Service<Database>("Database") const program = Effect.gen(function*() { const db = yield* Effect.service(Database) return yield* db.query("SELECT * FROM users") }) const runnable = Effect.provideService(program, Database, { query: (sql) => Effect.succeed(`Result for: ${sql}`) }) Effect.runSync(runnable) // => "Result for: SELECT * FROM users" ```
@stabilitystable@categoryaccessors@since4.0.0
service
(
const Greeter: Context.Service<{
    readonly greet: (name: string) => string;
}, {
    readonly greet: (name: string) => string;
}>
Greeter
), (
greeter: {
    readonly greet: (name: string) => string;
}
greeter
) =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Greet";
} | {
    readonly _tag: "Greeted";
    readonly text: string;
}>
Message
.
type Greeted: (args: {
    readonly text: string;
}) => {
    readonly _tag: "Greeted";
    readonly text: string;
}
Greeted
({ text: stringtext:
greeter: {
    readonly greet: (name: string) => string;
}
greeter
.greet: (name: string) => stringgreet("you") })
),
model: {
    greeting: string;
}
model
,
}),
type Greeted: ({ text }: {
    readonly _tag: "Greeted";
    readonly text: string;
}) => {
    model: {
        greeting: string;
    };
}
Greeted
: ({ text: stringtext }) => ({
model: {
    greeting: string;
}
model
: { greeting: stringgreeting: text: stringtext } }),
}),
view: ((model: {
    greeting: string;
}, dispatch: Dispatch<{
    readonly _tag: "Greeted";
    readonly text: string;
}>) => View) & ((model: {
    greeting: string;
}) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
: (
model: {
    greeting: string;
}
model
: { greeting: stringgreeting: string }) => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{
model: {
    greeting: string;
}
model
.greeting: stringgreeting}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>,
});

A command that needs a service outside the layer does not compile. The same services are available inside subscription streams.

Keep only the latest of a kind

Wrap a command in keyed when a new request makes the old one obsolete: every start interrupts the running command with the same key, so only the latest result dispatches. Different keys run independently, and keys are per app instance.

import { import DataData, import EffectEffect } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
, const keyed: <Message, R>(key: string, cmd: Cmd<Message, R>) => Cmd<Message, R>
Latest-wins command: starting one interrupts the running command with the same key, so only the latest result dispatches. Different keys run independently. Keys are per app instance: two mounted instances never share them.
keyed
} from "@ilha/model";
import type { type Dispatch<Message> = (message: Message) => void
Send a message into the app loop.
Dispatch
} from "@ilha/model";
type
type Message = {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{
type GotResults: {
    readonly results: string;
}
GotResults
: { readonly results: stringresults: string };
type Typed: {
    readonly query: string;
}
Typed
: { readonly query: stringquery: string };
}>; const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}
Message
>();
interface Model { readonly Model.query: stringquery: string; readonly Model.results: stringresults: string; } const
const search: (query: string) => Effect.Effect<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never, never>
search
= (query: stringquery: string) =>
import EffectEffect.
const succeed: <{
    readonly _tag: "GotResults";
    readonly results: string;
}>(value: {
    readonly _tag: "GotResults";
    readonly results: string;
}) => Effect.Effect<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never, never>
Creates an `Effect` that always succeeds with a given value. **When to use** Use when an effect should complete successfully with a specific value without any errors or external dependencies. **Example** (Creating a successful effect) ```ts import.meta.vitest import { Effect } from "effect" // Creating an effect that represents a successful scenario // // ┌─── Effect<number, never, never> // ▼ const success = Effect.succeed(42) Effect.runSync(success) // => 42 ```
@see{@link fail} to create an effect that represents a failure.@stabilitystable@categoryconstructors@since2.0.0
succeed
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>
Message
.
type GotResults: (args: {
    readonly results: string;
}) => {
    readonly _tag: "GotResults";
    readonly results: string;
}
GotResults
({ results: stringresults: `hits for ${query: stringquery}` }));
const
const update: (model: Model, message: Message) => {
    model: {
        results: string;
        query: string;
    };
} | {
    cmd: Cmd<{
        readonly _tag: "GotResults";
        readonly results: string;
    }, never>;
    model: {
        query: string;
        results: string;
    };
}
update
= (model: Modelmodel: Model,
message: {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}
message
:
type Message = {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}
Message
) =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>
Message
.
$match: <{
    GotResults: ({ results }: {
        readonly _tag: "GotResults";
        readonly results: string;
    }) => {
        model: {
            results: string;
            query: string;
        };
    };
    Typed: ({ query }: {
        readonly _tag: "Typed";
        readonly query: string;
    }) => {
        cmd: Cmd<{
            readonly _tag: "GotResults";
            readonly results: string;
        }, never>;
        model: {
            query: string;
            results: string;
        };
    };
}>(value: {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}, cases: {
    GotResults: ({ results }: {
        readonly _tag: "GotResults";
        readonly results: string;
    }) => {
        model: {
            results: string;
            query: string;
        };
    };
    Typed: ({ query }: {
        readonly _tag: "Typed";
        readonly query: string;
    }) => {
        cmd: Cmd<{
            readonly _tag: "GotResults";
            readonly results: string;
        }, never>;
        model: {
            query: string;
            results: string;
        };
    };
}) => {
    ...;
} | {
    ...;
} (+1 overload)
$match
(
message: {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}
message
, {
type GotResults: ({ results }: {
    readonly _tag: "GotResults";
    readonly results: string;
}) => {
    model: {
        results: string;
        query: string;
    };
}
GotResults
: ({ results: stringresults }) => ({
model: {
    results: string;
    query: string;
}
model
: { ...model: Modelmodel, results: stringresults } }),
type Typed: ({ query }: {
    readonly _tag: "Typed";
    readonly query: string;
}) => {
    cmd: Cmd<{
        readonly _tag: "GotResults";
        readonly results: string;
    }, never>;
    model: {
        query: string;
        results: string;
    };
}
Typed
: ({ query: stringquery }) => ({
cmd: Cmd<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never>
cmd
:
keyed<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never>(key: string, cmd: Cmd<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never>): Cmd<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never>
Latest-wins command: starting one interrupts the running command with the same key, so only the latest result dispatches. Different keys run independently. Keys are per app instance: two mounted instances never share them.
keyed
("search",
const search: (query: string) => Effect.Effect<{
    readonly _tag: "GotResults";
    readonly results: string;
}, never, never>
search
(query: stringquery)),
model: {
    query: string;
    results: string;
}
model
: { ...model: Modelmodel, query: stringquery },
}), }); const const view: (model: Model, dispatch: Dispatch<Message>) => Viewview = (model: Modelmodel: Model,
dispatch: Dispatch<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>
dispatch
: type Dispatch<Message> = (message: Message) => void
Send a message into the app loop.
Dispatch
<
type Message = {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}
Message
>) => (
<JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div> <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{model: Modelmodel.Model.results: stringresults}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p> <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() =>
dispatch: (message: {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}) => void
dispatch
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>
Message
.
type Typed: (args: {
    readonly query: string;
}) => {
    readonly _tag: "Typed";
    readonly query: string;
}
Typed
({ query: stringquery: "a" }))}
> a </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton> <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() =>
dispatch: (message: {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}) => void
dispatch
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>
Message
.
type Typed: (args: {
    readonly query: string;
}) => {
    readonly _tag: "Typed";
    readonly query: string;
}
Typed
({ query: stringquery: "ab" }))}
> ab </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton> </JSX.IntrinsicElements.div: HTMLAttributes<HTMLDivElement>div> ); export const
const app: ModelApp<{
    query: string;
    results: string;
}, {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}, never, never>
app
=
defineApp<{
    query: string;
    results: string;
}, {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}, Layer<never, never, never>, {
    init: () => {
        model: {
            query: string;
            results: string;
        };
    };
    update: (model: Model, message: Message) => {
        model: {
            results: string;
            query: string;
        };
    } | {
        cmd: Cmd<{
            readonly _tag: "GotResults";
            readonly results: string;
        }, never>;
        model: {
            query: string;
            results: string;
        };
    };
    view: (model: Model, dispatch: Dispatch<Message>) => View;
}>(options: LooseOptions<{
    query: string;
    results: string;
}, {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}> & {
    init: () => {
        model: {
            query: string;
            results: string;
        };
    };
    update: (model: Model, message: Message) => {
        model: {
            results: string;
            query: string;
        };
    } | {
        cmd: Cmd<{
            readonly _tag: "GotResults";
            readonly results: string;
        }, never>;
        model: {
            query: string;
            results: string;
        };
    };
    view: (model: Model, dispatch: Dispatch<Message>) => View;
} & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<{
    query: string;
    results: string;
}, {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}, any>) & (() => {
    model: {
        query: string;
        results: string;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: {
    query: string;
    results: string;
} & {
    query: string;
    results: string;
}
model
: { query: stringquery: "", results: stringresults: "" } }),
update: ((model: {
    query: string;
    results: string;
}, message: {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}) => UpdateResult<{
    query: string;
    results: string;
}, {
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}, any>) & ((model: Model, message: Message) => {
    model: {
        results: string;
        query: string;
    };
} | {
    cmd: Cmd<{
        readonly _tag: "GotResults";
        readonly results: string;
    }, never>;
    model: {
        query: string;
        results: string;
    };
})
The only place state changes. Must be pure.
update
,
view: ((model: {
    query: string;
    results: string;
}, dispatch: Dispatch<{
    readonly _tag: "GotResults";
    readonly results: string;
} | {
    readonly _tag: "Typed";
    readonly query: string;
}>) => View) & ((model: Model, dispatch: Dispatch<Message>) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
,
});

Typing ab after a interrupts the first search; only the second result lands.

Listen to ongoing work

A subscription is a stream whose lifetime follows a slice of the model. When the dependencies change, the old stream stops and a new one starts; the other subscriptions keep running. When the island unmounts, every stream stops.

Name each entry with subscription() so its stream receives the exact type its deps returns:

import { import DataData, import StreamStream } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
, const subscription: <Model, Message, Deps, R = never>(sub: Subscription<Model, Message, Deps, R>) => Subscription<Model, Message, Deps, R>
Name one subscription. Each entry infers its own `Deps` from its `deps` function, so `stream` receives the exact type its deps return. Annotate the model parameter; everything else infers.
subscription
} from "@ilha/model";
type
type Message = {
    readonly _tag: "Tick";
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{ type Tick: {}Tick: {} }>;
const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Tick";
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "Tick";
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Tick";
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "Tick";
}
Message
>();
interface Model { readonly Model.count: numbercount: number; readonly Model.on: booleanon: boolean; } const const view: (model: Model) => Viewview = (model: Modelmodel: Model) => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{model: Modelmodel.Model.count: numbercount}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; export const
const app: ModelApp<Model, {
    readonly _tag: "Tick";
}, never, never>
app
=
defineApp<Model, {
    readonly _tag: "Tick";
}, Layer<never, never, never>, {
    init: () => {
        model: {
            count: number;
            on: boolean;
        };
    };
    subscriptions: Subscription<Model, {
        readonly _tag: "Tick";
    }, boolean, never>[];
    update: (model: Model, message: Message) => {
        model: Model;
    };
    view: (model: Model) => View;
}>(options: LooseOptions<Model, {
    readonly _tag: "Tick";
}> & {
    init: () => {
        model: {
            count: number;
            on: boolean;
        };
    };
    subscriptions: Subscription<Model, {
        readonly _tag: "Tick";
    }, boolean, never>[];
    update: (model: Model, message: Message) => {
        model: Model;
    };
    view: (model: Model) => View;
} & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<Model, {
    readonly _tag: "Tick";
}, any>) & (() => {
    model: {
        count: number;
        on: boolean;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: Model & {
    count: number;
    on: boolean;
}
model
: { count: numbercount: 0, on: booleanon: true } }),
subscriptions: readonly Subscription<Model, {
    readonly _tag: "Tick";
}, any, any>[] & Subscription<Model, {
    readonly _tag: "Tick";
}, boolean, never>[]
Ongoing work gated by slices of the model.
subscriptions
: [
subscription<Model, {
    readonly _tag: "Tick";
}, boolean, never>(sub: Subscription<Model, {
    readonly _tag: "Tick";
}, boolean, never>): Subscription<Model, {
    readonly _tag: "Tick";
}, boolean, never>
Name one subscription. Each entry infers its own `Deps` from its `deps` function, so `stream` receives the exact type its deps return. Annotate the model parameter; everything else infers.
subscription
({
Subscription<Model, { readonly _tag: "Tick"; }, boolean, never>.deps: (model: Model) => boolean
Values controlling the lifetime; the stream restarts when they change.
deps
: (model: Modelmodel: Model) => model: Modelmodel.Model.on: booleanon,
Subscription<Model, { readonly _tag: "Tick"; }, boolean, never>.stream: (deps: boolean) => Stream.Stream<{
    readonly _tag: "Tick";
}, never, AtomRegistry>
Stream of messages for the current deps. Restarted when deps change.
stream
: (on: booleanon) =>
on: booleanon ? import StreamStream.const tick: (interval: Input) => Stream.Stream<void>
Creates a stream that emits `void` immediately once, then emits another `void` after each specified interval. **Example** (Emitting ticks on an interval) ```ts import.meta.vitest import { Effect, Stream } from "effect" await Effect.runPromise(Stream.tick(0).pipe(Stream.take(3), Stream.runCollect)) // => [undefined, undefined, undefined] ```
@stabilitystable@categoryconstructors@since2.0.0
tick
("1 second").
Pipeable.pipe<Stream.Stream<void, never, never>, Stream.Stream<{
    readonly _tag: "Tick";
}, never, never>>(this: Stream.Stream<void, never, never>, ab: (_: Stream.Stream<void, never, never>) => Stream.Stream<{
    readonly _tag: "Tick";
}, never, never>): Stream.Stream<{
    readonly _tag: "Tick";
}, never, never> (+21 overloads)
pipe
(import StreamStream.
const map: <void, {
    readonly _tag: "Tick";
}>(f: (a: void, i: number) => {
    readonly _tag: "Tick";
}) => <E, R>(self: Stream.Stream<void, E, R>) => Stream.Stream<{
    readonly _tag: "Tick";
}, E, R> (+1 overload)
Transforms the elements of this stream using the supplied function. **Example** (Mapping stream values) ```ts import.meta.vitest import { Effect, Option, Stream } from "effect" const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] ```
@stabilitystable@categorymapping@since2.0.0
map
(() =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Tick";
}>
Message
.
type Tick: (args: void) => {
    readonly _tag: "Tick";
}
Tick
()))
: import StreamStream.const empty: Stream.Stream<never, never, never>
Creates an empty stream. **Example** (Creating an empty stream) ```ts import.meta.vitest import { Effect, Stream } from "effect" await Effect.runPromise(Stream.runCollect(Stream.empty)) // => [] ```
@stabilitystable@categoryconstructors@since2.0.0
empty
,
}), ],
update: ((model: Model, message: {
    readonly _tag: "Tick";
}) => UpdateResult<Model, {
    readonly _tag: "Tick";
}, any>) & ((model: Model, message: Message) => {
    model: Model;
})
The only place state changes. Must be pure.
update
: (model: Modelmodel: Model,
message: {
    readonly _tag: "Tick";
}
message
:
type Message = {
    readonly _tag: "Tick";
}
Message
) =>
message: {
    readonly _tag: "Tick";
}
message
._tag: "Tick"_tag === "Tick"
? { model: Modelmodel: { ...model: Modelmodel, Model.count: numbercount: model: Modelmodel.Model.count: numbercount + 1 } } : { model: Modelmodel },
view: ((model: Model, dispatch: Dispatch<{
    readonly _tag: "Tick";
}>) => View) & ((model: Model) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
,
});

Dependencies can be any value; equal deps keep the stream alive, compared structurally. A subscription never runs on the server; server output reflects the init model only. A stream’s error channel is never, so convert errors into messages inside the stream; a defect is logged and that subscription goes quiet until its deps change again.

Run start-up work from init

init returns the first model plus an optional command. The command runs once per client mount — including after hydration — and never during server rendering.

import { import DataData, import EffectEffect } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
} from "@ilha/model";
type
type Message = {
    readonly _tag: "Loaded";
    readonly items: number;
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{
type Loaded: {
    readonly items: number;
}
Loaded
: { readonly items: numberitems: number } }>;
const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Loaded";
    readonly items: number;
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "Loaded";
    readonly items: number;
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Loaded";
    readonly items: number;
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "Loaded";
    readonly items: number;
}
Message
>();
interface Model { readonly Model.items: numberitems: number; } export const
const app: ModelApp<Model, {
    readonly _tag: "Loaded";
    readonly items: number;
}, never, never>
app
=
defineApp<Model, {
    readonly _tag: "Loaded";
    readonly items: number;
}, Layer<never, never, never>, {
    init: () => {
        cmd: Effect.Effect<{
            readonly _tag: "Loaded";
            readonly items: number;
        }, never, never>;
        model: {
            items: number;
        };
    };
    update: (model: Model, message: Message) => {
        model: Model;
    };
    view: (model: Model) => View;
}>(options: LooseOptions<Model, {
    readonly _tag: "Loaded";
    readonly items: number;
}> & {
    init: () => {
        cmd: Effect.Effect<{
            readonly _tag: "Loaded";
            readonly items: number;
        }, never, never>;
        model: {
            items: number;
        };
    };
    update: (model: Model, message: Message) => {
        model: Model;
    };
    view: (model: Model) => View;
} & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<Model, {
    readonly _tag: "Loaded";
    readonly items: number;
}, any>) & (() => {
    cmd: Effect.Effect<{
        readonly _tag: "Loaded";
        readonly items: number;
    }, never, never>;
    model: {
        items: number;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
cmd: (Cmd<{
    readonly _tag: "Loaded";
    readonly items: number;
}, any> & Effect.Effect<{
    readonly _tag: "Loaded";
    readonly items: number;
}, never, never>) | (readonly Cmd<{
    readonly _tag: "Loaded";
    readonly items: number;
}, any>[] & Effect.Effect<{
    readonly _tag: "Loaded";
    readonly items: number;
}, never, never>)
cmd
: import EffectEffect.
const succeed: <{
    readonly _tag: "Loaded";
    readonly items: number;
}>(value: {
    readonly _tag: "Loaded";
    readonly items: number;
}) => Effect.Effect<{
    readonly _tag: "Loaded";
    readonly items: number;
}, never, never>
Creates an `Effect` that always succeeds with a given value. **When to use** Use when an effect should complete successfully with a specific value without any errors or external dependencies. **Example** (Creating a successful effect) ```ts import.meta.vitest import { Effect } from "effect" // Creating an effect that represents a successful scenario // // ┌─── Effect<number, never, never> // ▼ const success = Effect.succeed(42) Effect.runSync(success) // => 42 ```
@see{@link fail} to create an effect that represents a failure.@stabilitystable@categoryconstructors@since2.0.0
succeed
(
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Loaded";
    readonly items: number;
}>
Message
.
type Loaded: (args: {
    readonly items: number;
}) => {
    readonly _tag: "Loaded";
    readonly items: number;
}
Loaded
({ items: numberitems: 3 })),
model: Model & {
    items: number;
}
model
: { items: numberitems: 0 },
}),
update: ((model: Model, message: {
    readonly _tag: "Loaded";
    readonly items: number;
}) => UpdateResult<Model, {
    readonly _tag: "Loaded";
    readonly items: number;
}, any>) & ((model: Model, message: Message) => {
    model: Model;
})
The only place state changes. Must be pure.
update
: (model: Modelmodel: Model,
message: {
    readonly _tag: "Loaded";
    readonly items: number;
}
message
:
type Message = {
    readonly _tag: "Loaded";
    readonly items: number;
}
Message
) =>
message: {
    readonly _tag: "Loaded";
    readonly items: number;
}
message
._tag: "Loaded"_tag === "Loaded" ? { model: Modelmodel: { Model.items: numberitems:
message: {
    readonly _tag: "Loaded";
    readonly items: number;
}
message
.items: numberitems } } : { model: Modelmodel },
view: ((model: Model, dispatch: Dispatch<{
    readonly _tag: "Loaded";
    readonly items: number;
}>) => View) & ((model: Model) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
: (model: Modelmodel: Model) => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{model: Modelmodel.Model.items: numberitems}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>,
});

Fetch the first page, read stored settings, open a socket: if it happens once at start-up, it belongs here rather than in a subscription.

Hydrate with a schema

The model snapshots to JSON for hydration. Keep it JSON-safe, or pass a Schema codec so richer values round-trip:

import { import SchemaSchema } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
} from "@ilha/model";
export const
const app: ModelApp<{
    readonly at: Date;
}, unknown, never, never>
app
=
defineApp<{
    readonly at: Date;
}, unknown, Layer<never, never, never>, {
    init: () => {
        model: {
            at: Date;
        };
    };
    schema: Schema.Struct<{
        readonly at: Schema.Date;
    }>;
    update: (model: {
        at: Date;
    }) => {
        model: {
            at: Date;
        };
    };
    view: (model: {
        at: Date;
    }) => View;
}>(options: LooseOptions<{
    readonly at: Date;
}, unknown> & {
    init: () => {
        model: {
            at: Date;
        };
    };
    schema: Schema.Struct<{
        readonly at: Schema.Date;
    }>;
    update: (model: {
        at: Date;
    }) => {
        model: {
            at: Date;
        };
    };
    view: (model: {
        at: Date;
    }) => View;
} & {
    layer?: Layer<never, never, never> | undefined;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<{
    readonly at: Date;
}, unknown, any>) & (() => {
    model: {
        at: Date;
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: {
    readonly at: Date;
} & {
    at: Date;
}
model
: { at: Dateat: new
var Date: DateConstructor
new (value: number | string | Date) => Date (+4 overloads)
Date
("2024-05-06T00:00:00.000Z") } }),
schema: Schema.ConstraintCodec<{
    readonly at: Date;
}, unknown, never, never> & Schema.Struct<{
    readonly at: Schema.Date;
}>
Codec for the model snapshot. Without it the model must be JSON-safe; with it, values like `Date` survive server rendering into hydration.
schema
: import SchemaSchema.
function Struct<{
    readonly at: Schema.Date;
}>(fields: {
    readonly at: Schema.Date;
}): Schema.Struct<{
    readonly at: Schema.Date;
}>
Defines a struct schema from a map of field schemas. **Details** Each field value is a schema. Use {@link optionalKey } or {@link optional } to mark fields as optional, and {@link mutableKey } to mark them as mutable. The resulting schema's `Type` is a readonly object type with the fields' decoded types. The `Encoded` form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The `__proto__` field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order. **Example** (Defining a basic struct) ```ts import.meta.vitest import { Schema } from "effect" const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, email: Schema.optionalKey(Schema.String) }) // { readonly name: string; readonly age: number; readonly email?: string } type Person = typeof Person.Type Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 } ```
@stabilitystable@categoryconstructors@since3.10.0
Struct
({ at: Schema.Dateat: import SchemaSchema.const Date: Schema.Date
Type-level representation of {@link Date } . Schema for valid JavaScript `Date` objects. **When to use** Use to validate in-memory values that must already be valid JavaScript date objects. **Details** This schema accepts `Date` instances whose timestamp is not `NaN`. The default JSON serializer encodes dates as ISO 8601 strings. **Example** (Defining a Date schema) ```ts import.meta.vitest import { Schema } from "effect" const date = Schema.decodeUnknownSync(Schema.Date)(new Date("2024-01-01")) date.toISOString() // => "2024-01-01T00:00:00.000Z" ```
@stabilitystable@categorymodels@since4.0.0@see{@link DateFromString} for decoding strings into Date instances@see{@link DateFromMillis} for decoding epoch milliseconds into Date instances@stabilitystable@categoryschemas@since4.0.0
Date
}),
update: ((model: {
    readonly at: Date;
}, message: unknown) => UpdateResult<{
    readonly at: Date;
}, unknown, any>) & ((model: {
    at: Date;
}) => {
    model: {
        at: Date;
    };
})
The only place state changes. Must be pure.
update
: (
model: {
    at: Date;
}
model
: { at: Dateat: Date }) => ({
model: {
    readonly at: Date;
} & {
    at: Date;
}
model
}),
view: ((model: {
    readonly at: Date;
}, dispatch: Dispatch<unknown>) => View) & ((model: {
    at: Date;
}) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
: (
model: {
    at: Date;
}
model
: { at: Dateat: Date }) => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{
model: {
    at: Date;
}
model
.at: Dateat.Date.getUTCFullYear(): number
Gets the year using Universal Coordinated Time (UTC).
getUTCFullYear
()}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>,
});

Without schema the model must be JSON-safe: no Date, Map, class instances, or functions. Messages carry no such constraint; they are never snapshotted.

Split a child out

A child owns its model and messages; the parent stores the child model, wraps child messages, and delegates with foldChild. The parent never edits the child slice directly.

import { import DataData } from "effect";
import { 
const defineApp: <Model, Message, L extends Any = Layer<never, never, never>, O extends LooseOptions<Model, Message> = LooseOptions<Model, Message>>(options: LooseOptions<Model, Message> & O & {
    layer?: L | undefined;
} & CheckServices<O, L>) => ModelApp<Model, Message, Success<L>, Error<L>>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
, const foldChild: <ParentModel, ParentMessage, ChildModel, ChildMessage, R = never>(options: FoldChildOptions<ParentModel, ParentMessage, ChildModel, ChildMessage, R>) => ((model: ParentModel, message: ChildMessage) => UpdateResult<ParentModel, ParentMessage, R>)
Embed a child update in a parent. Runs the child, writes its model back, and lifts its commands so results arrive wrapped for the parent.
foldChild
} from "@ilha/model";
import type { type Dispatch<Message> = (message: Message) => void
Send a message into the app loop.
Dispatch
} from "@ilha/model";
type
type ChildMessage = {
    readonly _tag: "Bump";
}
ChildMessage
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{ type Bump: {}Bump: {} }>;
const
const ChildMessage: Data.TaggedEnum.Constructor<{
    readonly _tag: "Bump";
}>
ChildMessage
= import DataData.
const taggedEnum: <{
    readonly _tag: "Bump";
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Bump";
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type ChildMessage = {
    readonly _tag: "Bump";
}
ChildMessage
>();
interface Child { readonly Child.n: numbern: number; } const
const childUpdate: (model: Child, message: ChildMessage) => {
    model: {
        n: number;
    };
}
childUpdate
= (model: Childmodel: Child,
message: {
    readonly _tag: "Bump";
}
message
:
type ChildMessage = {
    readonly _tag: "Bump";
}
ChildMessage
) =>
const ChildMessage: Data.TaggedEnum.Constructor<{
    readonly _tag: "Bump";
}>
ChildMessage
.
$match: <{
    Bump: () => {
        model: {
            n: number;
        };
    };
}>(value: {
    readonly _tag: "Bump";
}, cases: {
    Bump: () => {
        model: {
            n: number;
        };
    };
}) => {
    model: {
        n: number;
    };
} (+1 overload)
$match
(
message: {
    readonly _tag: "Bump";
}
message
, {
type Bump: () => {
    model: {
        n: number;
    };
}
Bump
: () => ({
model: {
    n: number;
}
model
: { n: numbern: model: Childmodel.Child.n: numbern + 1 } }),
}); type
type ParentMessage = {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
ParentMessage
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{
type Child: {
    readonly message: ChildMessage;
}
Child
: { readonly
message: {
    readonly _tag: "Bump";
}
message
:
type ChildMessage = {
    readonly _tag: "Bump";
}
ChildMessage
};
}>; const
const ParentMessage: Data.TaggedEnum.Constructor<{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}>
ParentMessage
= import DataData.
const taggedEnum: <{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type ParentMessage = {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
ParentMessage
>();
interface Parent { readonly Parent.child: Childchild: Child; } const
const foldChildState: (model: Parent, message: {
    readonly _tag: "Bump";
}) => UpdateResult<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, never>
foldChildState
=
foldChild<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, Child, {
    readonly _tag: "Bump";
}, never>(options: FoldChildOptions<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, Child, {
    readonly _tag: "Bump";
}, never>): (model: Parent, message: {
    readonly _tag: "Bump";
}) => UpdateResult<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, never>
Embed a child update in a parent. Runs the child, writes its model back, and lifts its commands so results arrive wrapped for the parent.
foldChild
<Parent,
type ParentMessage = {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
ParentMessage
, Child,
type ChildMessage = {
    readonly _tag: "Bump";
}
ChildMessage
>({
FoldChildOptions<Parent, { readonly _tag: "Child"; readonly message: ChildMessage; }, Child, { readonly _tag: "Bump"; }, never>.read: (model: Parent) => Child | undefined
Read the child slice. `undefined` leaves the parent unchanged.
read
: (model: Parentmodel) => model: Parentmodel.Parent.child: Childchild,
FoldChildOptions<Parent, { readonly _tag: "Child"; readonly message: ChildMessage; }, Child, { readonly _tag: "Bump"; }, never>.toParentMessage: (message: {
    readonly _tag: "Bump";
}) => {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
Lift child messages, usually into a `Got*` wrapper.
toParentMessage
: (
message: {
    readonly _tag: "Bump";
}
message
) =>
const ParentMessage: Data.TaggedEnum.Constructor<{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}>
ParentMessage
.
type Child: (args: {
    readonly message: ChildMessage;
}) => {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
Child
({
message: {
    readonly _tag: "Bump";
}
message
}),
FoldChildOptions<Parent, { readonly _tag: "Child"; readonly message: ChildMessage; }, Child, { readonly _tag: "Bump"; }, never>.update: (model: Child, message: {
    readonly _tag: "Bump";
}) => UpdateResult<Child, {
    readonly _tag: "Bump";
}, never>
The child's own update. The parent never touches the child model.
update
:
const childUpdate: (model: Child, message: ChildMessage) => {
    model: {
        n: number;
    };
}
childUpdate
,
FoldChildOptions<Parent, { readonly _tag: "Child"; readonly message: ChildMessage; }, Child, { readonly _tag: "Bump"; }, never>.write: (model: Parent, child: Child) => Parent
Write the child slice back.
write
: (model: Parentmodel, child: Childchild) => ({ ...model: Parentmodel, Parent.child: Childchild }),
}); export const
const app: ModelApp<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, never, never>
app
=
defineApp<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, Layer<never, never, never>, {
    init: () => {
        model: {
            child: {
                n: number;
            };
        };
    };
    update: (model: Parent, message: ParentMessage) => UpdateResult<Parent, {
        readonly _tag: "Child";
        readonly message: ChildMessage;
    }, never>;
    view: (model: Parent, dispatch: Dispatch<ParentMessage>) => View;
}>(options: LooseOptions<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}> & {
    init: () => {
        model: {
            child: {
                n: number;
            };
        };
    };
    update: (model: Parent, message: ParentMessage) => UpdateResult<Parent, {
        readonly _tag: "Child";
        readonly message: ChildMessage;
    }, never>;
    view: (model: Parent, dispatch: Dispatch<ParentMessage>) => View;
} & {
    ...;
}): ModelApp<...>
Define an Elm-style app. One atom holds the whole model; `dispatch` runs `update` and rerenders; commands run concurrently inside the app runtime; each subscription restarts only when its own deps change and stops on unmount.
defineApp
({
init: (() => UpdateResult<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, any>) & (() => {
    model: {
        child: {
            n: number;
        };
    };
})
Build the first model, plus an optional start-up command. Must be pure. Runs once per instance; the command runs once per client mount, never during server rendering.
init
: () => ({
model: Parent & {
    child: {
        n: number;
    };
}
model
: {
child: Child & {
    n: number;
}
child
: { n: numbern: 0 } } }),
update: ((model: Parent, message: {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}) => UpdateResult<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, any>) & ((model: Parent, message: ParentMessage) => UpdateResult<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, never>)
The only place state changes. Must be pure.
update
: (model: Parentmodel: Parent,
message: {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
message
:
type ParentMessage = {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
ParentMessage
) =>
message: {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
message
._tag: "Child"_tag === "Child"
?
const foldChildState: (model: Parent, message: {
    readonly _tag: "Bump";
}) => UpdateResult<Parent, {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}, never>
foldChildState
(model: Parentmodel,
message: {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
message
.
message: {
    readonly _tag: "Bump";
}
message
)
: { UpdateResult<Model, Message, R = never>.model: Parentmodel },
view: ((model: Parent, dispatch: Dispatch<{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}>) => View) & ((model: Parent, dispatch: Dispatch<ParentMessage>) => View)
Render the model. Must be pure; report facts with `dispatch`.
view
: (model: Parentmodel: Parent,
dispatch: Dispatch<{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}>
dispatch
: type Dispatch<Message> = (message: Message) => void
Send a message into the app loop.
Dispatch
<
type ParentMessage = {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
ParentMessage
>) => (
<JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() =>
dispatch: (message: {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}) => void
dispatch
(
const ParentMessage: Data.TaggedEnum.Constructor<{
    readonly _tag: "Child";
    readonly message: ChildMessage;
}>
ParentMessage
.
type Child: (args: {
    readonly message: ChildMessage;
}) => {
    readonly _tag: "Child";
    readonly message: ChildMessage;
}
Child
({
message: {
    readonly _tag: "Bump";
}
message
:
const ChildMessage: Data.TaggedEnum.Constructor<{
    readonly _tag: "Bump";
}>
ChildMessage
.
type Bump: (args: void) => {
    readonly _tag: "Bump";
}
Bump
() }))
} > {model: Parentmodel.Parent.child: Childchild.Child.n: numbern} </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton> ), });

Name each wrapper Got*, such as GotSettingsMessage. Combine sequential steps on one model with combine: the steps run in order, and their commands run concurrently like any command list.

Test the loop

update is a plain function, so test transitions without rendering. Drive the view with ilha/test when people must reach the behavior through clicks.

import { const expect: Expect
Asserts that a value matches some criteria.
@linkhttps://jestjs.io/docs/expect#reference@example```ts expect(1 + 1).toBe(2); expect([1,2,3]).toContain(2); expect(null).toBeNull(); ```@paramactual The actual (received) value
expect
, const test: Test<[]>
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
} from "bun:test";
import type { interface UpdateResult<Model, Message, R = never>
What `init` and `update` return: the next model plus optional one-shot work. Several commands run concurrently with each other.
UpdateResult
} from "@ilha/model";
import { import DataData } from "effect"; type
type Message = {
    readonly _tag: "Increment";
}
Message
= import DataData.
type TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>> = keyof A extends infer Tag ? Tag extends keyof A ? Simplify<{
    readonly _tag: Tag;
} & { readonly [K in keyof A[Tag]]: A[Tag][K]; }> : never : never
Transforms a record of variant definitions into a discriminated union type. **When to use** Use when you have two or more variants that share a common `_tag` discriminator. **Details** Each key in the record becomes a variant with `readonly _tag` set to that key. Use with {@link taggedEnum } to get constructors and matchers. **Gotchas** Variant records must **not** include a `_tag` property; it is added automatically. **Example** (Defining a tagged enum) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly status: 400; readonly message: string } NotFound: { readonly status: 404 } }> // Equivalent to: // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } // | { readonly _tag: "NotFound"; readonly status: 404 } const { BadRequest, NotFound } = Data.taggedEnum<HttpError>() BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" ``` Namespace for `TaggedEnum` utility types. **When to use** Use to reference utility types for constructing, extracting, and matching `TaggedEnum` variants. **Details** Provides helper types for: - Generic tagged enums ( {@link TaggedEnum.WithGenerics } , {@link TaggedEnum.Kind } ) - Extracting constructor arguments ( {@link TaggedEnum.Args } ) and variant values ( {@link TaggedEnum.Value } ) - Full constructor objects ( {@link TaggedEnum.Constructor } )
@see{@link taggedEnum} — constructors and matchers for a `TaggedEnum`@see{@link TaggedEnum.WithGenerics} — generic tagged enums@see{@link TaggedEnum.Constructor} — the constructor object type@stabilitystable@categorymodels@since2.0.0@stabilitystable@since2.0.0
TaggedEnum
<{ type Increment: {}Increment: {} }>;
const
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Increment";
}>
Message
= import DataData.
const taggedEnum: <{
    readonly _tag: "Increment";
}>() => Data.TaggedEnum<A extends Record<string, Record<string, any>> & UntaggedChildren<A>>.Constructor<{
    readonly _tag: "Increment";
}> (+1 overload)
Creates constructors and matchers for a `TaggedEnum` type. **When to use** Use when you model a closed union with plain data objects and want construction, tag checks, and exhaustive matching from the same definition. **Details** Returns an object with: - One constructor per variant (keyed by tag name) - `$is(tag)` — returns a type-guard function that checks only the `_tag` field - `$match` — exhaustive pattern matching (data-first or data-last) **Gotchas** - Constructors produce **plain objects**, not class instances. - `$is(tag)` only checks the `_tag` field, not the full structure. It relies on the tag being globally unique and the value being produced by your constructors. For untrusted input, validate with the `Schema` module first. **Example** (Creating and matching tagged enum values) ```ts import.meta.vitest import { Data } from "effect" type HttpError = Data.TaggedEnum<{ BadRequest: { readonly message: string } NotFound: { readonly url: string } }> const { BadRequest, NotFound, $is, $match } = Data.taggedEnum<HttpError>() const err = NotFound({ url: "/missing" }) $is("NotFound")(err) // => true $match(err, { BadRequest: (e) => e.message, NotFound: (e) => `${e.url} not found` }) // => "/missing not found" ``` **Example** (Defining a generic tagged enum) ```ts import.meta.vitest import { Data } from "effect" type MyResult<E, A> = Data.TaggedEnum<{ Failure: { readonly error: E } Success: { readonly value: A } }> interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: MyResult<this["A"], this["B"]> } const { Failure, Success } = Data.taggedEnum<MyResultDef>() const ok = Success({ value: 42 }) // ok: { readonly _tag: "Success"; readonly value: number } ok // => { value: 42, _tag: "Success" } ```
@see{@link TaggedEnum} — the type-level companion@see{@link TaggedEnum.Constructor} — the returned object type@see{@link TaggedEnum.WithGenerics} — generic enum support@stabilitystable@categoryconstructors@since2.0.0
taggedEnum
<
type Message = {
    readonly _tag: "Increment";
}
Message
>();
interface Model { readonly Model.count: numbercount: number; } const const update: (model: Model, message: Message) => UpdateResult<Model, Message>update = (model: Modelmodel: Model,
message: {
    readonly _tag: "Increment";
}
message
:
type Message = {
    readonly _tag: "Increment";
}
Message
): interface UpdateResult<Model, Message, R = never>
What `init` and `update` return: the next model plus optional one-shot work. Several commands run concurrently with each other.
UpdateResult
<Model,
type Message = {
    readonly _tag: "Increment";
}
Message
> =>
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Increment";
}>
Message
.
$match: <{
    Increment: () => {
        model: {
            count: number;
        };
    };
}>(value: {
    readonly _tag: "Increment";
}, cases: {
    Increment: () => {
        model: {
            count: number;
        };
    };
}) => {
    model: {
        count: number;
    };
} (+1 overload)
$match
(
message: {
    readonly _tag: "Increment";
}
message
, {
type Increment: () => {
    model: {
        count: number;
    };
}
Increment
: () => ({
model: {
    count: number;
}
model
: { count: numbercount: model: Modelmodel.Model.count: numbercount + 1 } }),
}); function test(label: string, fn: (done: (err?: unknown) => void) => void | Promise<unknown>, options?: number | TestOptions): void (+1 overload)
Runs a test, with the options before the test function.
@example```ts test("can retry", { retry: 3 }, () => { expect(Math.random()).toBeLessThan(0.5); }); ```
test
("increment adds one", () => {
const
const next: UpdateResult<Model, {
    readonly _tag: "Increment";
}, never>
next
= const update: (model: Model, message: Message) => UpdateResult<Model, Message>update({ Model.count: numbercount: 0 },
const Message: Data.TaggedEnum.Constructor<{
    readonly _tag: "Increment";
}>
Message
.
type Increment: (args: void) => {
    readonly _tag: "Increment";
}
Increment
());
expect<Model>(actual: Model, customFailMessage?: string): Matchers<Model> (+2 overloads)
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(
const next: UpdateResult<Model, {
    readonly _tag: "Increment";
}, never>
next
.UpdateResult<Model, { readonly _tag: "Increment"; }, never>.model: Modelmodel).MatchersBuiltin<Model>.toEqual(expected: Model): void (+1 overload)
Asserts that a value is deeply equal to what is expected.
@exampleexpect(100 + 23).toBe(123); expect("d" + "og").toBe("dog"); expect([456]).toEqual([456]); expect({ value: 1 }).toEqual({ value: 1 });@paramexpected the expected value
toEqual
({ Model.count: numbercount: 1 });
expect<Cmd<{
    readonly _tag: "Increment";
}, never> | readonly Cmd<{
    readonly _tag: "Increment";
}, never>[] | undefined>(actual: Cmd<{
    readonly _tag: "Increment";
}, never> | readonly Cmd<{
    readonly _tag: "Increment";
}, never>[] | undefined, customFailMessage?: string): Matchers<Cmd<{
    readonly _tag: "Increment";
}, never> | readonly Cmd<{
    readonly _tag: "Increment";
}, never>[] | undefined> (+2 overloads)
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(
const next: UpdateResult<Model, {
    readonly _tag: "Increment";
}, never>
next
.
UpdateResult<Model, { readonly _tag: "Increment"; }, never>.cmd?: Cmd<{
    readonly _tag: "Increment";
}, never> | readonly Cmd<{
    readonly _tag: "Increment";
}, never>[] | undefined
cmd
).MatchersBuiltin<Cmd<{ readonly _tag: "Increment"; }, never> | readonly Cmd<{ readonly _tag: "Increment"; }, never>[] | undefined>.toBeUndefined(): void
Asserts that a value is `undefined`.
@exampleexpect(undefined).toBeUndefined(); expect(null).toBeUndefined(); // fail
toBeUndefined
();
});

Know the limits

@ilha/model covers the core loop: model, messages, commands, subscriptions, and submodels. It deliberately leaves out a DevTools overlay (use ilha/debug plus the message flow), route biparsers (routes stay in your router; the route is just a model field), and async-data, field, machine, and query values. Those arrive as later value helpers; nothing here needs to change to adopt them.

Was this page helpful?