Skip to main content
On this pageOverview

Subscriptions

Overview

Commands are great for one-off side effects, but what about ongoing streams of events? Think timers, WebSocket connections, or keyboard input. For these, Foldkit provides Subscriptions.

A Subscription is a reactive binding between your model and a long-running stream. You declare which part of the model the subscription depends on, and Foldkit manages the stream lifecycle automatically, starting it when the component mounts and restarting it whenever those dependencies change.

Let's look at a stopwatch example. We want a timer that ticks every 100ms, but only when isRunning is true. This gives us a way to start and stop the stopwatch based on user input.

import { Duration, Effect, Schema as S, Stream } from 'effect'
import { Subscription } from 'foldkit'
import { m } from 'foldkit/message'

// MESSAGE

const Ticked = m('Ticked')
const Message = S.Union(Ticked)
type Message = typeof Message.Type

// MODEL

const Model = S.Struct({
  isRunning: S.Boolean,
  elapsed: S.Number,
})

type Model = typeof Model.Type

// SUBSCRIPTION

const SubscriptionDeps = S.Struct({
  tick: S.Struct({
    isRunning: S.Boolean,
  }),
})

const subscriptions = Subscription.makeSubscriptions(
  SubscriptionDeps,
)<Model, Message>({
  tick: {
    modelToDependencies: model => ({ isRunning: model.isRunning }),
    depsToStream: ({ isRunning }) =>
      Stream.when(
        Stream.tick(Duration.millis(100)).pipe(
          Stream.map(() => Effect.succeed(Ticked())),
        ),
        () => isRunning,
      ),
  },
})

The key concept is SubscriptionDeps. This schema defines what parts of the model your subscriptions depend on. Each subscription has two functions:

modelToDependencies extracts the relevant dependencies from the model.

depsToStream creates a stream based on those dependencies.

Foldkit structurally compares the dependencies between updates. The stream is only restarted when the dependencies actually change, not on every model update.

When isRunning changes from false to true, the stream starts ticking. When it changes back to false, the stream stops. Foldkit handles all the lifecycle management for you.

For a more complex example using WebSocket connections, see the websocket-chat example. For a full real-world application, check out Typing Terminal (source).

If you're coming from Elm, Subscriptions in Foldkit produce Command<Message> rather than plain Message. This means each item in the stream can do async work before resolving to a message, avoiding extra round-trips through update.