From a9f8bedf0500736daa6494a22c2eeb4f490b587e Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Thu, 29 Feb 2024 17:37:48 +0000 Subject: :bug: Fix bug with incorrect imports for server runtime. --- src/lustre/server.gleam | 202 -------------------------------------- src/lustre/server_component.gleam | 202 ++++++++++++++++++++++++++++++++++++++ src/server-runtime.ffi.mjs | 8 +- 3 files changed, 206 insertions(+), 206 deletions(-) delete mode 100644 src/lustre/server.gleam create mode 100644 src/lustre/server_component.gleam diff --git a/src/lustre/server.gleam b/src/lustre/server.gleam deleted file mode 100644 index d478e19..0000000 --- a/src/lustre/server.gleam +++ /dev/null @@ -1,202 +0,0 @@ -//// > **Note**: this is a _release candidate_ for v4.0.0 and documentation is still -//// > a work in progress. If you spot an issue with docs or the library, or would -//// > like to get involved, please [open an issue](https://github.com/lustre-labs/lustre/issues/new) -//// > or a pull request. -//// -//// > **Note**: while Lustre v4 is in release candidate status, server components -//// > **will not reliably work on Gleam's JavaScript target**. Until this message -//// > goes away, consider server components as being supported **only** on Erlang. -//// - -// IMPORTS --------------------------------------------------------------------- - -import gleam/bool -import gleam/dynamic.{type DecodeError, type Dynamic, DecodeError, dynamic} -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} -import lustre/internals/constants -import lustre/internals/runtime.{type Action, Attrs, Event, SetSelector} -import lustre/internals/patch - -// ELEMENTS -------------------------------------------------------------------- - -/// A simple wrapper to render a `` element. -/// -pub fn component(attrs: List(Attribute(msg))) -> Element(msg) { - element("lustre-server-component", attrs, []) -} - -// ATTRIBUTES ------------------------------------------------------------------ - -/// The `route` attribute should always be included on a [`component`](#component) -/// to tell the client runtime what path to initiate the WebSocket connection on. -/// -/// -/// -pub fn route(path: String) -> Attribute(msg) { - attribute("route", path) -} - -/// Ocassionally you may want to attach custom data to an event sent to the server. -/// This could be used to include a hash of the current build to detect if the -/// event was sent from a stale client. -/// -/// ```gleam -/// -/// ``` -/// -pub fn data(json: Json) -> Attribute(msg) { - json - |> json.to_string - |> attribute("data-lustre-data", _) -} - -/// Properties of the JavaScript event object are typically not serialisable. -/// This means if we want to pass them to the server we need to copy them into -/// a new object first. -/// -/// This attribute tells Lustre what properties to include. Properties can come -/// from nested objects by using dot notation. For example, you could include the -/// `id` of the target `element` by passing `["target.id"]`. -/// -/// ```gleam -/// import gleam/dynamic -/// import gleam/result.{try} -/// import lustre/element.{type Element} -/// import lustre/element/html -/// import lustre/event -/// import lustre/server -/// -/// pub fn custom_button(on_click: fn(String) -> msg) -> Element(msg) { -/// let handler = fn(event) { -/// use target <- try(dynamic.field("target", dynamic.dynamic)(event)) -/// use id <- try(dynamic.field("id", dynamic.string)(target)) -/// -/// Ok(on_click(id)) -/// } -/// -/// html.button([event.on_click(handler), server.include(["target.id"])], [ -/// element.text("Click me!") -/// ]) -/// } -/// ``` -/// -pub fn include(properties: List(String)) -> Attribute(msg) { - properties - |> json.array(json.string) - |> json.to_string - |> 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 --------------------------------------------------------------------- - -/// -/// -pub fn emit(event: String, data: Json) -> Effect(msg) { - effect.event(event, data) -} - -/// -/// -pub fn set_selector(sel: Selector(Action(runtime, msg))) -> Effect(msg) { - do_set_selector(sel) -} - -@target(erlang) -fn do_set_selector(sel: Selector(Action(runtime, msg))) -> Effect(msg) { - use _ <- effect.from - let self = process.new_subject() - - process.send(self, SetSelector(sel)) -} - -@target(javascript) -fn do_set_selector(_sel: Selector(Action(runtime, msg))) -> Effect(msg) { - effect.none() -} - -// DECODERS -------------------------------------------------------------------- - -/// -/// -pub fn decode_action( - dyn: Dynamic, -) -> Result(Action(runtime, ServerComponent), List(DecodeError)) { - dynamic.any([decode_event, decode_attrs])(dyn) -} - -fn decode_event(dyn: Dynamic) -> Result(Action(runtime, msg), List(DecodeError)) { - use #(kind, name, data) <- result.try(dynamic.tuple3( - dynamic.int, - dynamic, - dynamic, - )(dyn)) - use <- bool.guard( - kind != constants.event, - Error([ - DecodeError( - path: ["0"], - found: int.to_string(kind), - expected: int.to_string(constants.event), - ), - ]), - ) - use name <- result.try(dynamic.string(name)) - - Ok(Event(name, data)) -} - -fn decode_attrs(dyn: Dynamic) -> Result(Action(runtime, msg), List(DecodeError)) { - use #(kind, attrs) <- result.try(dynamic.tuple2(dynamic.int, dynamic)(dyn)) - use <- bool.guard( - kind != constants.attrs, - Error([ - DecodeError( - path: ["0"], - found: int.to_string(kind), - expected: int.to_string(constants.attrs), - ), - ]), - ) - use attrs <- result.try(dynamic.list(decode_attr)(attrs)) - - Ok(Attrs(attrs)) -} - -fn decode_attr(dyn: Dynamic) -> Result(#(String, Dynamic), List(DecodeError)) { - dynamic.tuple2(dynamic.string, dynamic)(dyn) -} - -// ENCODERS -------------------------------------------------------------------- - -/// -/// -pub fn encode_patch(patch: Patch(msg)) -> Json { - patch.patch_to_json(patch) -} diff --git a/src/lustre/server_component.gleam b/src/lustre/server_component.gleam new file mode 100644 index 0000000..d478e19 --- /dev/null +++ b/src/lustre/server_component.gleam @@ -0,0 +1,202 @@ +//// > **Note**: this is a _release candidate_ for v4.0.0 and documentation is still +//// > a work in progress. If you spot an issue with docs or the library, or would +//// > like to get involved, please [open an issue](https://github.com/lustre-labs/lustre/issues/new) +//// > or a pull request. +//// +//// > **Note**: while Lustre v4 is in release candidate status, server components +//// > **will not reliably work on Gleam's JavaScript target**. Until this message +//// > goes away, consider server components as being supported **only** on Erlang. +//// + +// IMPORTS --------------------------------------------------------------------- + +import gleam/bool +import gleam/dynamic.{type DecodeError, type Dynamic, DecodeError, dynamic} +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} +import lustre/internals/constants +import lustre/internals/runtime.{type Action, Attrs, Event, SetSelector} +import lustre/internals/patch + +// ELEMENTS -------------------------------------------------------------------- + +/// A simple wrapper to render a `` element. +/// +pub fn component(attrs: List(Attribute(msg))) -> Element(msg) { + element("lustre-server-component", attrs, []) +} + +// ATTRIBUTES ------------------------------------------------------------------ + +/// The `route` attribute should always be included on a [`component`](#component) +/// to tell the client runtime what path to initiate the WebSocket connection on. +/// +/// +/// +pub fn route(path: String) -> Attribute(msg) { + attribute("route", path) +} + +/// Ocassionally you may want to attach custom data to an event sent to the server. +/// This could be used to include a hash of the current build to detect if the +/// event was sent from a stale client. +/// +/// ```gleam +/// +/// ``` +/// +pub fn data(json: Json) -> Attribute(msg) { + json + |> json.to_string + |> attribute("data-lustre-data", _) +} + +/// Properties of the JavaScript event object are typically not serialisable. +/// This means if we want to pass them to the server we need to copy them into +/// a new object first. +/// +/// This attribute tells Lustre what properties to include. Properties can come +/// from nested objects by using dot notation. For example, you could include the +/// `id` of the target `element` by passing `["target.id"]`. +/// +/// ```gleam +/// import gleam/dynamic +/// import gleam/result.{try} +/// import lustre/element.{type Element} +/// import lustre/element/html +/// import lustre/event +/// import lustre/server +/// +/// pub fn custom_button(on_click: fn(String) -> msg) -> Element(msg) { +/// let handler = fn(event) { +/// use target <- try(dynamic.field("target", dynamic.dynamic)(event)) +/// use id <- try(dynamic.field("id", dynamic.string)(target)) +/// +/// Ok(on_click(id)) +/// } +/// +/// html.button([event.on_click(handler), server.include(["target.id"])], [ +/// element.text("Click me!") +/// ]) +/// } +/// ``` +/// +pub fn include(properties: List(String)) -> Attribute(msg) { + properties + |> json.array(json.string) + |> json.to_string + |> 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 --------------------------------------------------------------------- + +/// +/// +pub fn emit(event: String, data: Json) -> Effect(msg) { + effect.event(event, data) +} + +/// +/// +pub fn set_selector(sel: Selector(Action(runtime, msg))) -> Effect(msg) { + do_set_selector(sel) +} + +@target(erlang) +fn do_set_selector(sel: Selector(Action(runtime, msg))) -> Effect(msg) { + use _ <- effect.from + let self = process.new_subject() + + process.send(self, SetSelector(sel)) +} + +@target(javascript) +fn do_set_selector(_sel: Selector(Action(runtime, msg))) -> Effect(msg) { + effect.none() +} + +// DECODERS -------------------------------------------------------------------- + +/// +/// +pub fn decode_action( + dyn: Dynamic, +) -> Result(Action(runtime, ServerComponent), List(DecodeError)) { + dynamic.any([decode_event, decode_attrs])(dyn) +} + +fn decode_event(dyn: Dynamic) -> Result(Action(runtime, msg), List(DecodeError)) { + use #(kind, name, data) <- result.try(dynamic.tuple3( + dynamic.int, + dynamic, + dynamic, + )(dyn)) + use <- bool.guard( + kind != constants.event, + Error([ + DecodeError( + path: ["0"], + found: int.to_string(kind), + expected: int.to_string(constants.event), + ), + ]), + ) + use name <- result.try(dynamic.string(name)) + + Ok(Event(name, data)) +} + +fn decode_attrs(dyn: Dynamic) -> Result(Action(runtime, msg), List(DecodeError)) { + use #(kind, attrs) <- result.try(dynamic.tuple2(dynamic.int, dynamic)(dyn)) + use <- bool.guard( + kind != constants.attrs, + Error([ + DecodeError( + path: ["0"], + found: int.to_string(kind), + expected: int.to_string(constants.attrs), + ), + ]), + ) + use attrs <- result.try(dynamic.list(decode_attr)(attrs)) + + Ok(Attrs(attrs)) +} + +fn decode_attr(dyn: Dynamic) -> Result(#(String, Dynamic), List(DecodeError)) { + dynamic.tuple2(dynamic.string, dynamic)(dyn) +} + +// ENCODERS -------------------------------------------------------------------- + +/// +/// +pub fn encode_patch(patch: Patch(msg)) -> Json { + patch.patch_to_json(patch) +} diff --git a/src/server-runtime.ffi.mjs b/src/server-runtime.ffi.mjs index 5432824..4d1dc60 100644 --- a/src/server-runtime.ffi.mjs +++ b/src/server-runtime.ffi.mjs @@ -1,9 +1,9 @@ import { Ok, isEqual } from "./gleam.mjs"; import { - AddRenderer, + Subscribe, Dispatch, Event, - RemoveRenderer, + Unsubscribe, Shutdown, } from "./lustre/internals/runtime.mjs"; @@ -41,7 +41,7 @@ export class LustreServerApplication { send(action) { switch (true) { - case action instanceof AddRenderer: { + case action instanceof Subscribe: { this.#renderers.add(action[0]); return; } @@ -66,7 +66,7 @@ export class LustreServerApplication { } } - case action instanceof RemoveRenderer: { + case action instanceof Unsubscribe: { this.#renderers.delete(action[0]); return; } -- cgit v1.2.3