diff options
-rw-r--r-- | src/lustre.gleam | 40 | ||||
-rw-r--r-- | src/lustre/internals/runtime.gleam | 24 | ||||
-rw-r--r-- | src/lustre/server.gleam | 33 |
3 files changed, 47 insertions, 50 deletions
diff --git a/src/lustre.gleam b/src/lustre.gleam index 884b6f5..b79488d 100644 --- a/src/lustre.gleam +++ b/src/lustre.gleam @@ -163,6 +163,7 @@ // IMPORTS --------------------------------------------------------------------- +import argv import gleam/bool import gleam/dict.{type Dict} import gleam/dynamic.{type Decoder} @@ -170,15 +171,14 @@ import gleam/erlang/process.{type Subject} import gleam/option.{type Option, None, Some} import gleam/otp/actor.{type StartError} import gleam/result -import lustre/effect.{type Effect} -import lustre/element.{type Element} -import lustre/internals/runtime -import lustre/server.{type Patch} -import argv import glint import lustre/cli/add import lustre/cli/build import lustre/cli/dev +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/internals/patch +import lustre/internals/runtime // MAIN ------------------------------------------------------------------------ @@ -288,6 +288,14 @@ pub type ServerComponent pub type Action(msg, runtime) = runtime.Action(msg, runtime) +/// Patches are sent by server components to any connected renderers. Because +/// server components are not opinionated about your network layer or how your +/// wider application is organised, it is your responsibility to make sure a `Patch` +/// makes its way to the server component client runtime. +/// +pub type Patch(msg) = + patch.Patch(msg) + /// Starting a Lustre application might fail for a number of reasons. This error /// type enumerates all those reasons, even though some of them are only possible /// on certain targets. @@ -505,21 +513,6 @@ pub fn register(_app: App(Nil, model, msg), _name: String) -> Result(Nil, Error) // ACTIONS --------------------------------------------------------------------- -/// A [`ServerComponent`](#ServerComponent) broadcasts patches to be applied to -/// the DOM to any connected clients. This action is used to add a new client to -/// a running server component. -/// -/// The `id` should be a unique identifier for the client, but it can be any type -/// you want. This is only used if you want to remove the client in the future -/// using [`remove_renderer`](#remove_renderer). -/// -pub fn add_renderer( - id: any, - renderer: fn(Patch(msg)) -> Nil, -) -> Action(msg, ServerComponent) { - runtime.AddRenderer(dynamic.from(id), renderer) -} - /// Dispatch a message to a running application's `update` function. This can be /// used as a way for the outside world to communicate with a Lustre app without /// the app needing to initiate things with an effect. @@ -531,13 +524,6 @@ pub fn dispatch(msg: msg) -> Action(msg, runtime) { runtime.Dispatch(msg) } -/// Remove a registered renderer from a server component. If no renderer with the -/// given id is found, this action has no effect. -/// -pub fn remove_renderer(id: any) -> Action(msg, ServerComponent) { - runtime.RemoveRenderer(dynamic.from(id)) -} - /// Instruct a running application to shut down. For client SPAs this will stop /// the runtime and unmount the app from the DOM. For server components, this will /// stop the runtime and prevent any further patches from being sent to connected diff --git a/src/lustre/internals/runtime.gleam b/src/lustre/internals/runtime.gleam index 716daac..49d1982 100644 --- a/src/lustre/internals/runtime.gleam +++ b/src/lustre/internals/runtime.gleam @@ -25,7 +25,7 @@ type State(model, msg, runtime) { update: fn(model, msg) -> #(model, Effect(msg)), view: fn(model) -> Element(msg), html: Element(msg), - renderers: Dict(Dynamic, fn(Patch(msg)) -> Nil), + renderers: Dict(String, fn(Patch(msg)) -> Nil), handlers: Dict(String, fn(Dynamic) -> Result(msg, Nil)), on_attribute_change: Dict(String, Decoder(msg)), ) @@ -34,16 +34,16 @@ type State(model, msg, runtime) { /// /// pub type Action(msg, runtime) { - AddRenderer(Dynamic, fn(Patch(msg)) -> Nil) Attrs(List(#(String, Dynamic))) Batch(List(msg), Effect(msg)) Debug(DebugAction) Dispatch(msg) Emit(String, Json) Event(String, Dynamic) - RemoveRenderer(Dynamic) SetSelector(Selector(Action(msg, runtime))) Shutdown + Subscribe(String, fn(Patch(msg)) -> Nil) + Unsubscribe(String) } pub type DebugAction { @@ -106,14 +106,6 @@ fn loop( |> loop(state) } - AddRenderer(id, renderer) -> { - let renderers = dict.insert(state.renderers, id, renderer) - let next = State(..state, renderers: renderers) - - renderer(Init(dict.keys(state.on_attribute_change), state.html)) - actor.continue(next) - } - Batch([], _) -> actor.continue(state) Batch([msg], other_effects) -> { let #(model, effects) = state.update(state.model, msg) @@ -189,7 +181,15 @@ fn loop( } } - RemoveRenderer(id) -> { + Subscribe(id, renderer) -> { + let renderers = dict.insert(state.renderers, id, renderer) + let next = State(..state, renderers: renderers) + + renderer(Init(dict.keys(state.on_attribute_change), state.html)) + actor.continue(next) + } + + Unsubscribe(id) -> { let renderers = dict.delete(state.renderers, id) let next = State(..state, renderers: renderers) diff --git a/src/lustre/server.gleam b/src/lustre/server.gleam index 841f897..d478e19 100644 --- a/src/lustre/server.gleam +++ b/src/lustre/server.gleam @@ -16,6 +16,7 @@ import gleam/erlang/process.{type Selector} import gleam/int import gleam/json.{type Json} import gleam/result +import lustre.{type Patch, type ServerComponent} import lustre/attribute.{type Attribute, attribute} import lustre/effect.{type Effect} import lustre/element.{type Element, element} @@ -23,16 +24,6 @@ import lustre/internals/constants import lustre/internals/runtime.{type Action, Attrs, Event, SetSelector} import lustre/internals/patch -// TYPES ----------------------------------------------------------------------- - -/// Patches are sent by server components to any connected renderers. Because -/// server components are not opinionated about your network layer or how your -/// wider application is organised, it is your responsibility to make sure a `Patch` -/// makes its way to the server component client runtime. -/// -pub type Patch(msg) = - patch.Patch(msg) - // ELEMENTS -------------------------------------------------------------------- /// A simple wrapper to render a `<lustre-server-component>` element. @@ -103,6 +94,26 @@ pub fn include(properties: List(String)) -> Attribute(msg) { |> attribute("data-lustre-include", _) } +// ACTIONS --------------------------------------------------------------------- + +/// A [`ServerComponent`](../lustre#ServerComponent) broadcasts patches to be applied +/// to the DOM to any connected clients. This action is used to add a new client +/// to a running server component. +/// +pub fn subscribe( + id: String, + renderer: fn(Patch(msg)) -> Nil, +) -> Action(msg, ServerComponent) { + runtime.Subscribe(id, renderer) +} + +/// Remove a registered renderer from a server component. If no renderer with the +/// given id is found, this action has no effect. +/// +pub fn unsubscribe(id: String) -> Action(msg, ServerComponent) { + runtime.Unsubscribe(id) +} + // EFFECTS --------------------------------------------------------------------- /// @@ -136,7 +147,7 @@ fn do_set_selector(_sel: Selector(Action(runtime, msg))) -> Effect(msg) { /// pub fn decode_action( dyn: Dynamic, -) -> Result(Action(runtime, msg), List(DecodeError)) { +) -> Result(Action(runtime, ServerComponent), List(DecodeError)) { dynamic.any([decode_event, decode_attrs])(dyn) } |