On this pageThe First Model
Init & Flags
init constructs the first Model and returns any Commands that should run when the application starts. It returns Update.Return<Model, Message>, the same type as update.
The counter starts at zero and has no startup work:
import { Schema } from 'effect'
import type { Runtime } from 'foldkit'
import { defineMessageUnion } from 'foldkit/message'
const Model = Schema.Struct({
count: Schema.Number,
})
type Model = typeof Model.Type
const Message = defineMessageUnion({
ClickedIncrement: {},
ClickedDecrement: {},
})
type Message = typeof Message.Type
const init: Runtime.ApplicationInit<Model, Message> = () => ({
model: { count: 0 },
})A non-routing application or element calls init with no arguments. A routing application passes the current URL, so its first Model can reflect the route. When the application declares Flags, they become the first argument in either form.
Flags carry data from outside the application into init. Typical sources include persisted state, runtime configuration, and request-specific data supplied during server rendering.
Define the boundary with a Flags Schema. For a fresh client boot, also define an Effect that obtains a value matching that Schema:
import { Effect, Option, Schema } from 'effect'
import { KeyValueStore } from 'effect/unstable/persistence'
import { BrowserKeyValueStore } from '@effect/platform-browser'
const Todo = Schema.Struct({
id: Schema.String,
text: Schema.String,
completed: Schema.Boolean,
})
const Todos = Schema.Array(Todo)
const TodosJsonString = Schema.fromJsonString(Schema.toCodecJson(Todos))
const Flags = Schema.Struct({
todos: Schema.Option(Todos),
})
type Flags = typeof Flags.Type
const flags: Effect.Effect<Flags> = Effect.gen(function* () {
const store = yield* KeyValueStore.KeyValueStore
const todosJson = yield* Effect.fromOption(
Option.fromNullishOr(yield* store.get('todos')),
)
const decodeTodos = Schema.decodeEffect(TodosJsonString)
const todos = yield* decodeTodos(todosJson)
return Flags.make({ todos: Option.some(todos) })
}).pipe(
Effect.catch(() => Effect.succeed(Flags.make({ todos: Option.none() }))),
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
)init receives the decoded Flags value and folds it into the first Model:
import { Option, Schema } from 'effect'
import type { Runtime } from 'foldkit'
import { defineMessageUnion } from 'foldkit/message'
const Model = Schema.Struct({
count: Schema.Number,
startingCount: Schema.Option(Schema.Number),
})
type Model = typeof Model.Type
const Flags = Schema.Struct({
savedCount: Schema.Option(Schema.Number),
})
type Flags = typeof Flags.Type
const Message = defineMessageUnion({
ClickedIncrement: {},
})
type Message = typeof Message.Type
const init: Runtime.ApplicationInit<Model, Message, Flags> = flags => ({
model: {
count: Option.getOrElse(flags.savedCount, () => 0),
startingCount: flags.savedCount,
},
})Pass the Schema to Runtime.makeApplication as Flags, then pass the Effect to Runtime.run. The runtime resolves the Effect before calling init. If the configuration omits the Schema, init takes no Flags argument and the compiler rejects mismatched wiring.
import { Runtime } from 'foldkit'
import { Flags, Model, flags, init, update, view } from './main'
const application = Runtime.makeApplication({
Model,
init,
update,
view,
Flags,
container: document.getElementById('root'),
})
Runtime.run(application, { flags })The example provides KeyValueStore inside the Flags Effect because that service is used only during startup. If the same singleton is also needed by Commands or Subscriptions, leave the requirement in the Effect type and provide it through the application's resources Layer. The runtime builds that Layer once and shares it. See Resources for the full setup.
Server rendering provides Flags from the request or build instead of running a client Flags Effect. renderToString uses that value to call init, encodes it through the Schema, and embeds the result in the HTML. Runtime.hydrate decodes the same value and calls the same init, so the client reconstructs the Model that produced the server HTML.
A hydrating entry does not provide a client Flags Effect. Missing or invalid handoff data fails startup instead of silently booting a different Model. The Server Rendering guide explains which data is safe and reproducible across that boundary.
Once one Model, Message union, and update function become too large to reason about as a unit, decompose the state machine into Submodels. Each child owns its own Model, Messages, update, and Commands behind an explicit parent boundary.