Bundle / foldkit-bundle
Sixty seconds: one counter
ts
import { Schema } from 'effect'
import type { HtmlBuilder } from 'foldkit/html'
import { defineMessageUnion } from 'foldkit/message'
import { Bundle } from 'foldkit-bundle'
const CountModel = Schema.Struct({ count: Schema.Number })
const CountMessage = defineMessageUnion({ Incremented: {} })
const Count = Bundle.make('Count', {
Model: CountModel,
Message: CountMessage,
init: () => ({ model: { count: 0 } }),
update: model => ({ model: { count: model.count + 1 } }),
})
const Page = Bundle.compose({ greeting: Schema.String }).pipe(Bundle.withChild('clicks', Count))
type Model = typeof Page.Model.Type
type Message = typeof Page.Message.Type
const { placements } = Page
const config = placements.complete({
init: () => placements.initial({ greeting: 'Hello' }),
update: placements.update(model => ({ model })),
subscriptions: placements.subscriptions(),
view: (model: Model, h: HtmlBuilder<Message>) =>
h.button(
[h.OnClick(Page.Message.GotClicksMessage({ message: CountMessage.Incremented() }))],
[`${model.greeting}: ${model.clicks.count}`],
),
})Count describes a child machine. Page is the parent: its own field,
greeting, and Count placed under clicks. From that one declaration it
derives the parent's Model, { greeting, clicks }, its Message union, with the
wrapper GotClicksMessage, and the assembly that routes that wrapper to the
child and writes its next value back into model.clicks. A click changes 0 to
1 through the parent's update; no additional store is created.
config supplies init, update, and view to your Foldkit runtime. These
declarations do not mount anything. For a runnable assembly, see
the settings example.