lily/store
The Store holds your application state and update logic,
shared across client and server. The Wiring tells the
runtime how to dispatch messages to the right store slice and how to
merge incoming snapshots from the server.
Build a Wiring in your shared package and import it in both the
client (passed to client.start) and the server
(passed to server.new):
import lily/store
pub fn wiring() -> store.Wiring(Model, Message) {
store.wiring()
|> store.session(
extract: fn(message) {
case message {
Session(inner) -> Ok(inner)
_ -> Error(Nil)
}
},
update: session_update,
field_get: fn(model: Model) { model.session },
field_set: fn(model, session) { Model(..model, session:) },
)
|> store.topic(
id: "chat",
extract: fn(message) {
case message {
Chat(inner) -> Ok(inner)
_ -> Error(Nil)
}
},
update: chat_update,
field_get: fn(model: Model) { model.chat },
field_set: fn(model, chat) { Model(..model, chat:) },
)
}
Model fields that should not be synced to the server can be wrapped in
Local. The server holds Local fields at their initial values
and the client runtime preserves them when the server sends a snapshot on
reconnect. Pair with
client.session_field to persist them
across page navigations.
The same store runs on the client via
client.start and the server via
server.start, meaning your update function
works identically on both sides.
Types
Model fields that are client-only and not synced to the server should be
wrapped using Local(_). The server holds Local fields at their initial
values and the client runtime preserves them when applying a server
snapshot on reconnect.
pub type Model {
Model(count: Int, theme: store.Local(String))
}
pub type Local(a) {
Local(a)
}
Constructors
-
Local(a)
The store with your application state and update logic. The same store
runs on both the client (via client.start)
and the server (via server.start). Construct via
new; fields are not exposed to keep the internal layout free
to evolve.
pub opaque type Store(model, message)
Wiring configuration for multi-store Lily apps. A Wiring(model, message)
value tells the client how to dispatch messages to the session store or to
a topic store, and how to merge incoming snapshots back into the outer
model. Build with wiring, then pipe through
session and topic.
pub opaque type Wiring(model, message)
Values
pub fn new(
initial_model model: model,
with update: fn(model, message) -> model,
) -> Store(model, message)
Create a new store, seeded with an initial_model (similar to Lustre’s
init) and an update function that transforms the model based on a given
message.
let app_store =
Model(count: 0, user: "Guest")
|> store.new(with: update)
pub fn session(
wiring: Wiring(model, message),
extract extract: fn(message) -> Result(session_message, Nil),
update update: fn(session_model, session_message) -> session_model,
field_get field_get: fn(model) -> session_model,
field_set field_set: fn(model, session_model) -> model,
) -> Wiring(model, message)
Register the session store entry in the wiring. The extract function
identifies session messages; update applies them to the session
sub-model; field_get and field_set map between the outer model and
the session sub-model.
store.wiring()
|> store.session(
extract: fn(message) {
case message {
Session(inner) -> Ok(inner)
_ -> Error(Nil)
}
},
update: session_update,
field_get: fn(model: Model) { model.session },
field_set: fn(model, session) { Model(..model, session:) },
)
pub fn topic(
wiring: Wiring(model, message),
id id: String,
extract extract: fn(message) -> Result(topic_message, Nil),
update update: fn(topic_model, topic_message) -> topic_model,
field_get field_get: fn(model) -> topic_model,
field_set field_set: fn(model, topic_model) -> model,
) -> Wiring(model, message)
Register a topic store entry in the wiring. The id is the topic
identifier used in client.subscribe; the other parameters are the same
as for session.
store.wiring()
|> store.topic(
id: "chat",
extract: fn(message) {
case message {
Chat(inner) -> Ok(inner)
_ -> Error(Nil)
}
},
update: chat_update,
field_get: fn(model: Model) { model.chat },
field_set: fn(model, chat) { Model(..model, chat:) },
)
pub fn wiring() -> Wiring(model, message)
Create an empty wiring configuration. Pipe through session
and topic to register stores. Pass the result to
client.start and
server.new.
store.wiring()
|> store.session(extract:, update:, field_get:, field_set:)
|> store.topic(id: "chat", extract:, update:, field_get:, field_set:)