diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 06d00ad..c9650e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: - otp-version: "26.0.2" - gleam-version: "1.4.1" + otp-version: "28" + gleam-version: "1.16.0" rebar3-version: "3" # elixir-version: "1.15.4" - run: gleam deps download diff --git a/demo/Dockerfile b/demo/Dockerfile index f00424d..3174f9e 100644 --- a/demo/Dockerfile +++ b/demo/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/gleam-lang/gleam:v1.6.3-erlang-alpine +FROM ghcr.io/gleam-lang/gleam:v1.16.0-erlang-alpine COPY gleam.toml manifest.toml /build/ diff --git a/demo/gleam.toml b/demo/gleam.toml index 5f3c6bc..9724eb0 100644 --- a/demo/gleam.toml +++ b/demo/gleam.toml @@ -15,15 +15,17 @@ version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.34.0 and < 2.0.0" pprint = ">= 1.0.3 and < 2.0.0" -barnacle = ">= 1.0.0 and < 2.0.0" -gleam_otp = ">= 0.12.1 and < 1.0.0" -gleam_erlang = ">= 0.28.0 and < 1.0.0" -mist = ">= 3.0.0 and < 4.0.0" -lustre = ">= 4.5.1 and < 5.0.0" -gleam_json = ">= 1.0.1 and < 2.0.0" -gleam_http = ">= 3.7.0 and < 4.0.0" +# barnacle = ">= 1.0.0 and < 2.0.0" +barnacle = { path = "../" } +gleam_otp = ">= 1.0.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" +mist = ">= 6.0.0 and < 7.0.0" +lustre = ">= 5.0.0 and < 6.0.0" +gleam_json = ">= 3.0.0 and < 4.0.0" +gleam_http = ">= 4.0.0 and < 5.0.0" argv = ">= 1.0.2 and < 2.0.0" youid = ">= 1.2.0 and < 2.0.0" +envoy = ">= 1.2.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/demo/manifest.toml b/demo/manifest.toml index 276f243..8f40cba 100644 --- a/demo/manifest.toml +++ b/demo/manifest.toml @@ -3,39 +3,40 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, - { name = "barnacle", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "barnacle", source = "hex", outer_checksum = "C081C978FB63AC96EAC90A8336575F3958DF94B50EE840779B88E8A256B98F48" }, - { name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" }, - { name = "glam", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "66EC3BCD632E51EED029678F8DF419659C1E57B1A93D874C5131FE220DFAD2B2" }, - { name = "gleam_crypto", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "8AE56026B3E05EBB1F076778478A762E9EB62B31AEEB4285755452F397029D22" }, - { name = "gleam_erlang", version = "0.28.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "BE551521F708DCE5CB954AFBBDF08519C1C44986521FD40753608825F48FFA9E" }, - { name = "gleam_http", version = "3.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "EA66440C2269F7CED0F6845E5BD0DB68095775D627FA709A841CA78A398D6D56" }, - { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, - { name = "gleam_otp", version = "0.12.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BFACC1513410DF5A1617169A9CD7EA334973AC71D860A17574BA7B2EADD89A6F" }, - { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, - { name = "glisten", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "912132751031473CB38F454120124FFC96AF6B0EA33D92C9C90DB16327A2A972" }, - { name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" }, + { name = "barnacle", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = ".." }, + { name = "envoy", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "9C6FBB6BFA02A52798BEEC5977A738CAD6E4A057F4B67FD0C8061AD2502C191A" }, + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, + { name = "glam", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "48A043A12091F93D911C316DD8F7FFD03CD9FF6F50848FC56E359D3C168DF0FA" }, + { name = "gleam_crypto", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "2DE9E4EF53CF6FEE049D4F765731F7178F7A11AEFAE00EEE63BF7536B354AD3F" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_stdlib", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "960090C2FB391784BB34267B099DC9315CC1B1F6013E7415BC763CEF1905D7D3" }, + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, + { name = "gleeunit", version = "1.10.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "254B697FE72EEAD7BF82E941723918E421317813AC49923EE76A18C788C61E72" }, + { name = "glisten", version = "9.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "glisten", source = "hex", outer_checksum = "7795AA50830656F3A0316A6B26595F893C83272DA901B3405E31339CAA31A10B" }, + { name = "gramps", version = "6.0.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "D55636072DEE173F6586A5679D3C02EC7A0DE3F8646B78C351B72908FF223DF7" }, + { name = "houdini", version = "1.2.1", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "6F8AC2F12974567FB744BEA66AC93CEB76AAEA19AD28564623F76CDA9BC26A85" }, { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, - { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, - { name = "lustre", version = "4.5.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "B592DA442F6577143CAFA35D4506DB2018DAEED9C707A921E33559E09F001DF1" }, - { name = "mist", version = "3.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "CDA1A74E768419235E16886463EC4722EFF4AB3F8D820A76EAD45D7C167D7282" }, - { name = "pprint", version = "1.0.3", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "76BBB92E23D12D954BD452686543F29EDE8EBEBB7FC0ACCBCA66EEF276EC3A06" }, - { name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" }, - { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, - { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, - { name = "youid", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_stdlib"], otp_app = "youid", source = "hex", outer_checksum = "EF0F693004E221155EE5909C6D3C945DD14F7117DBA882887CF5F45BE399B8CA" }, + { name = "logging", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "BC5F18CE5DD9686100229FE5409BDC3DD5C46D5A7DF2F804AD2D8F0DD6C5060E" }, + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, + { name = "mist", version = "6.0.3", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "1B07F321D5FA0CB162D81496F2DE96AEB6EF8980F4F38230A4CC3F849497E020" }, + { name = "pprint", version = "1.0.6", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "4E9B34AE03B2E81D60F230B9BAF1792BE1AC37AFB5564B8DEBEE56BAEC866B7D" }, + { name = "youid", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_stdlib", "gleam_time"], otp_app = "youid", source = "hex", outer_checksum = "7A3ABA44B1B38BC2BDCB5474C5317AA372BE58DFBC649815EE08B03526DDA18D" }, ] [requirements] argv = { version = ">= 1.0.2 and < 2.0.0" } -barnacle = { version = ">= 1.0.0 and < 2.0.0" } -gleam_erlang = { version = ">= 0.28.0 and < 1.0.0" } -gleam_http = { version = ">= 3.7.0 and < 4.0.0" } -gleam_json = { version = ">= 1.0.1 and < 2.0.0" } -gleam_otp = { version = ">= 0.12.1 and < 1.0.0" } +barnacle = { path = "../" } +envoy = { version = ">= 1.2.0 and < 2.0.0" } +gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } +gleam_http = { version = ">= 4.0.0 and < 5.0.0" } +gleam_json = { version = ">= 3.0.0 and < 4.0.0" } +gleam_otp = { version = ">= 1.0.0 and < 2.0.0" } gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } -lustre = { version = ">= 4.5.1 and < 5.0.0" } -mist = { version = ">= 3.0.0 and < 4.0.0" } +lustre = { version = ">= 5.0.0 and < 6.0.0" } +mist = { version = ">= 6.0.0 and < 7.0.0" } pprint = { version = ">= 1.0.3 and < 2.0.0" } youid = { version = ">= 1.2.0 and < 2.0.0" } diff --git a/demo/src/barnacle_demo.gleam b/demo/src/barnacle_demo.gleam index 374a595..2cda172 100644 --- a/demo/src/barnacle_demo.gleam +++ b/demo/src/barnacle_demo.gleam @@ -5,11 +5,11 @@ import argv import barnacle import barnacle_demo/node_list -import gleam/bytes_builder -import gleam/erlang +import envoy +import gleam/bytes_tree +import gleam/erlang/application import gleam/erlang/atom import gleam/erlang/node -import gleam/erlang/os import gleam/erlang/process.{type Selector, type Subject} import gleam/http/request.{type Request} import gleam/http/response.{type Response} @@ -18,7 +18,6 @@ import gleam/io import gleam/json import gleam/list import gleam/option.{type Option, None, Some} -import gleam/otp/actor import gleam/result import gleam/string import lustre @@ -56,18 +55,18 @@ pub fn main() { let listener = process.new_subject() // Get the Fly app name from the environment - let assert Ok(app_name) = os.get_env("FLY_APP_NAME") + let assert Ok(app_name) = envoy.get("FLY_APP_NAME") let barnacle = // Use internal DNS to connect to the nodes - barnacle.dns(basename, app_name <> ".internal") + barnacle.dns(basename, app_name <> ".internal", None) |> barnacle.with_listener(listener) |> barnacle.with_poll_interval(barnacle_poll_interval) let assert Ok(barnacle_subject) = barnacle.start(barnacle) - let app = node_list.app() - let assert Ok(node_list) = lustre.start_actor(app, barnacle_subject) + let assert Ok(node_list) = + lustre.start_server_component(node_list.component(), barnacle_subject.data) let assert Ok(_) = run_server(port, node_list) listen_and_publish(listener, node_list) @@ -76,11 +75,11 @@ pub fn main() { // ----- Server component updater ----- // type NodeList = - Subject(lustre.Action(node_list.Msg, lustre.ServerComponent)) + lustre.Runtime(node_list.Msg) fn listen_and_publish( listener: Subject(barnacle.BarnacleResponse(a)), - node_list: NodeList, + node_list: lustre.Runtime(_), ) { case process.receive(listener, listener_poll_interval) { Ok(barnacle.RefreshResponse(Ok(nodes))) -> { @@ -93,12 +92,15 @@ fn listen_and_publish( _ -> Nil } - process.send( + lustre.send( node_list, lustre.dispatch(node_list.UpdateNodes( [node.self(), ..node.visible()] - |> io.debug - |> list.map(fn(node) { node |> node.to_atom |> atom.to_string }) + |> fn(nodes) { + nodes |> string.inspect |> io.println + nodes + } + |> list.map(fn(node) { node |> node.name |> atom.to_string }) |> list.sort(string.compare), )), ) @@ -108,7 +110,7 @@ fn listen_and_publish( // ----- Mist server ----- // -fn run_server(port: Int, node_list: NodeList) { +fn run_server(port: Int, node_list: lustre.Runtime(_)) { fn(req: Request(Connection)) -> Response(ResponseData) { case request.path_segments(req) { ["node_list"] -> @@ -120,7 +122,7 @@ fn run_server(port: Int, node_list: NodeList) { ) ["lustre-server-component.mjs"] -> { - let assert Ok(priv) = erlang.priv_directory("lustre") + let assert Ok(priv) = application.priv_directory("lustre") let path = priv <> "/static/lustre-server-component.mjs" mist.send_file(path, offset: 0, limit: None) @@ -131,7 +133,7 @@ fn run_server(port: Int, node_list: NodeList) { }) |> result.lazy_unwrap(fn() { response.new(404) - |> response.set_body(mist.Bytes(bytes_builder.new())) + |> response.set_body(mist.Bytes(bytes_tree.new())) }) } @@ -158,8 +160,8 @@ fn run_server(port: Int, node_list: NodeList) { ), ]), ]) - |> element.to_document_string_builder - |> bytes_builder.from_string_builder + |> element.to_document_string() + |> bytes_tree.from_string() |> mist.Bytes, ) } @@ -167,58 +169,60 @@ fn run_server(port: Int, node_list: NodeList) { |> mist.new |> mist.bind("0.0.0.0") |> mist.port(port) - |> mist.start_http + |> mist.start() } -type State = - #(String, NodeList) +type State { + State(id: String, component: NodeList) +} fn socket_init( _conn: WebsocketConnection, - node_list: NodeList, -) -> #(State, Option(Selector(lustre.Patch(node_list.Msg)))) { + runtime: lustre.Runtime(_), +) -> #(State, Option(Selector(server_component.ClientMessage(node_list.Msg)))) { let self = process.new_subject() let id = uuid.v4() |> uuid.to_string - process.send(node_list, server_component.subscribe(id, process.send(self, _))) + let selector = + process.new_selector() + |> process.select(self) - #( - #(id, node_list), - Some(process.selecting(process.new_selector(), self, fn(a) { a })), - ) + server_component.register_subject(self) + |> lustre.send(to: runtime) + + #(State(id, runtime), Some(selector)) } fn socket_update( state: State, + msg: WebsocketMessage(server_component.ClientMessage(node_list.Msg)), conn: WebsocketConnection, - msg: WebsocketMessage(lustre.Patch(node_list.Msg)), -) { +) -> mist.Next(State, server_component.ClientMessage(node_list.Msg)) { case msg { mist.Text(json) -> { - let action = json.decode(json, server_component.decode_action) + let action = json.parse(json, server_component.runtime_message_decoder()) case action { - Ok(action) -> process.send(state.1, action) + Ok(action) -> lustre.send(state.component, action) Error(_) -> Nil } - actor.continue(state) + mist.continue(state) } - mist.Binary(_) -> actor.continue(state) - mist.Custom(patch) -> { - let assert Ok(_) = - patch - |> server_component.encode_patch - |> json.to_string - |> mist.send_text_frame(conn, _) + mist.Binary(_) -> mist.continue(state) + mist.Custom(client_message) -> { + let json = server_component.client_message_to_json(client_message) + + let assert Ok(_) = mist.send_text_frame(conn, json.to_string(json)) - actor.continue(state) + mist.continue(state) } - mist.Closed | mist.Shutdown -> actor.Stop(process.Normal) + mist.Closed | mist.Shutdown -> mist.stop() } } -fn socket_close(state: State) { - process.send(state.1, server_component.unsubscribe(state.0)) +fn socket_close(state: State) -> Nil { + lustre.shutdown() + |> lustre.send(to: state.component) } diff --git a/demo/src/barnacle_demo/node_list.gleam b/demo/src/barnacle_demo/node_list.gleam index 4ec3100..11e0699 100644 --- a/demo/src/barnacle_demo/node_list.gleam +++ b/demo/src/barnacle_demo/node_list.gleam @@ -6,20 +6,24 @@ import gleam/list import gleam/string import lustre import lustre/attribute +import lustre/effect import lustre/element import lustre/element/html import lustre/event -pub fn app() { - lustre.simple(init, update, view) +pub fn component() { + lustre.component(init, update, view, []) } pub opaque type Model(a) { Model(barnacle_subject: Subject(barnacle.Message(a)), nodes: List(String)) } -fn init(barnacle_subject: Subject(barnacle.Message(a))) -> Model(a) { - Model(barnacle_subject, [node.self() |> node.to_atom |> atom.to_string]) +fn init(barnacle_subject: Subject(barnacle.Message(a))) { + #( + Model(barnacle_subject, [node.self() |> node.name |> atom.to_string]), + effect.none(), + ) } pub type Msg { @@ -30,29 +34,31 @@ pub type Msg { @external(erlang, "barnacle_demo_ffi", "disconnect_from_node") fn disconnect_node(node: atom.Atom) -> Result(node.Node, Nil) -fn update(model: Model(a), msg: Msg) -> Model(a) { +fn update(model: Model(a), msg: Msg) -> #(Model(a), effect.Effect(Msg)) { case msg { - UpdateNodes(nodes) -> - Model(..model, nodes: nodes |> list.sort(string.compare)) + UpdateNodes(nodes) -> #( + Model(..model, nodes: nodes |> list.sort(string.compare)), + effect.none(), + ) DisconnectCurrentNode -> { - let self = node.self() |> node.to_atom |> atom.to_string + let self = node.self() |> node.name |> atom.to_string list.each(model.nodes, fn(node) { case node == self { True -> Nil False -> { - let _ = disconnect_node(node |> atom.create_from_string) + let _ = disconnect_node(node |> atom.create) Nil } } }) - Model(..model, nodes: [self]) + #(Model(..model, nodes: [self]), effect.none()) } } } fn view(model: Model(a)) -> element.Element(Msg) { - html.div([attribute.style([#("max-width", "100ch")])], [ + html.div([attribute.styles([#("max-width", "100ch")])], [ html.ul( [], list.map(model.nodes, fn(node) { html.li([], [html.text(node)]) }), @@ -64,8 +70,8 @@ fn view(model: Model(a)) -> element.Element(Msg) { ]), html.p([], [ html.text("Current node: "), - html.span([attribute.style([#("font-weight", "bold")])], [ - html.text(node.self() |> node.to_atom |> atom.to_string), + html.span([attribute.styles([#("font-weight", "bold")])], [ + html.text(node.self() |> node.name |> atom.to_string), ]), ]), html.button( diff --git a/gleam.toml b/gleam.toml index 28d4919..4b4a396 100644 --- a/gleam.toml +++ b/gleam.toml @@ -7,9 +7,9 @@ repository = { type = "github", user = "Pevensie", repo = "barnacle" } # links = [{ title = "Website", href = "" }] [dependencies] -gleam_stdlib = ">= 0.34.0 and < 2.0.0" -gleam_erlang = ">= 0.27.0 and < 1.0.0" -gleam_otp = ">= 0.12.1 and < 1.0.0" +gleam_stdlib = ">= 0.60.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" +gleam_otp = ">= 1.0.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index f00f8e5..4f93a0f 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,14 +2,14 @@ # You typically do not need to edit this file packages = [ - { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, - { name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" }, - { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, - { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_stdlib", version = "0.67.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "6CE3E4189A8B8EC2F73AB61A2FBDE49F159D6C9C61C49E3B3082E439F260D3D0" }, + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, ] [requirements] -gleam_erlang = { version = ">= 0.27.0 and < 1.0.0" } -gleam_otp = { version = ">= 0.12.1 and < 1.0.0" } -gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } +gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } +gleam_otp = { version = ">= 1.0.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.60.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/barnacle.gleam b/src/barnacle.gleam index a8942d6..699f664 100644 --- a/src/barnacle.gleam +++ b/src/barnacle.gleam @@ -3,11 +3,10 @@ import barnacle/local_epmd import gleam/erlang/atom import gleam/erlang/node import gleam/erlang/process.{type Subject} -import gleam/function import gleam/list import gleam/option.{type Option, None, Some} import gleam/otp/actor -import gleam/otp/supervisor +import gleam/otp/supervision import gleam/result import gleam/set import gleam/string @@ -18,7 +17,7 @@ import gleam/string /// strategy-specific functions. pub opaque type Barnacle(error) { Barnacle( - name: Option(atom.Atom), + name: Option(process.Name(Message(error))), strategy: Strategy(error), poll_interval: Int, listener: Option(Subject(BarnacleResponse(error))), @@ -35,8 +34,11 @@ fn default_barnacle() -> Barnacle(error) { } /// Give the barnacle actor process a custom name. -pub fn with_name(barnacle: Barnacle(error), name: String) -> Barnacle(error) { - Barnacle(..barnacle, name: Some(name |> atom.create_from_string)) +pub fn with_name( + barnacle: Barnacle(error), + name: process.Name(Message(error)), +) -> Barnacle(error) { + Barnacle(..barnacle, name: Some(name)) } /// Set the poll interval for the barnacle actor. Defaults to 5000ms. @@ -131,7 +133,7 @@ fn disconnect_node(node: atom.Atom) -> Result(node.Node, NodeDisconnectError) fn list_nodes() -> Result(List(atom.Atom), error) { [node.self(), ..node.visible()] - |> list.map(node.to_atom) + |> list.map(node.name) |> Ok } @@ -207,16 +209,9 @@ pub fn dns( ) -> Barnacle(dns.LookupError) { Barnacle( ..default_barnacle(), - strategy: Strategy( - ..default_strategy(), - discover_nodes: fn() { - dns.discover_nodes( - basename, - hostname_query, - option.unwrap(timeout, 5000), - ) - }, - ), + strategy: Strategy(..default_strategy(), discover_nodes: fn() { + dns.discover_nodes(basename, hostname_query, option.unwrap(timeout, 5000)) + }), ) } @@ -253,7 +248,7 @@ type State(error) { pub fn start(barnacle: Barnacle(error)) { barnacle |> spec(None) - |> actor.start_spec + |> actor.start } /// Create a child spec for your barnacle for use in a supervision tree. @@ -261,10 +256,10 @@ pub fn child_spec( barnacle: Barnacle(error), parent: Subject(Subject(Message(error))), ) { - supervisor.worker(fn(_) { + supervision.worker(fn() { barnacle |> spec(Some(parent)) - |> actor.start_spec + |> actor.start }) } @@ -282,18 +277,18 @@ pub fn run_once(barnacle: Barnacle(error)) { /// /// This will reset the poll timer. pub fn refresh(subject: Subject(Message(error)), timeout: Int) { - actor.call(subject, fn(subj) { Refresh(Some(subj)) }, timeout) + actor.call(subject, timeout, fn(subj) { Refresh(Some(subj)) }) } /// Pause a barnacle actor. Sending a [`refresh`](#refresh) will restart the actor. pub fn pause(subject: Subject(Message(error)), timeout: Int) { - actor.call(subject, fn(subj) { Pause(Some(subj)) }, timeout) + actor.call(subject, timeout, fn(subj) { Pause(Some(subj)) }) } /// Shutdown a barnacle actor. This will stop the actor, and stop any future /// refreshes. pub fn shutdown(subject: Subject(Message(error)), timeout: Int) { - actor.call(subject, fn(subj) { Shutdown(Some(subj)) }, timeout) + actor.call(subject, timeout, fn(subj) { Shutdown(Some(subj)) }) } /// The error type for a barnacle refresh. @@ -303,7 +298,7 @@ pub type RefreshError(error) { DisconnectError(List(#(atom.Atom, NodeDisconnectError))) } -fn handle_message(message: Message(error), state: State(error)) { +fn handle_message(state: State(error), message: Message(error)) { let State(self:, barnacle:, timer:) = state case message { Refresh(return) -> { @@ -327,7 +322,7 @@ fn handle_message(message: Message(error), state: State(error)) { cancel_timer(timer) send_response(return, Nil) send_response(barnacle.listener, ShutdownResponse(Nil)) - actor.Stop(process.Normal) + actor.stop() } } } @@ -336,22 +331,28 @@ fn spec( barnacle: Barnacle(error), parent: Option(Subject(Subject(Message(error)))), ) { - actor.Spec(init_timeout: 10_000, loop: handle_message, init: fn() { - option.map(barnacle.name, process.register(process.self(), _)) - let self = process.new_subject() + actor.new_with_initialiser(10_000, fn(self) { + use _ <- result.try( + barnacle.name + |> option.map(process.register(process.self(), _)) + |> option.unwrap(Ok(Nil)) + |> result.replace_error("Failed to register process"), + ) + let selector = process.new_selector() - |> process.selecting(self, function.identity) + |> process.select(self) option.map(parent, process.send(_, self)) let timer = process.send_after(self, barnacle.poll_interval, Refresh(None)) - actor.Ready( - selector: selector, - state: State(self:, barnacle:, timer: Some(timer)), - ) + actor.initialised(State(self:, barnacle:, timer: Some(timer))) + |> actor.selecting(selector) + |> actor.returning(self) + |> Ok }) + |> actor.on_message(handle_message) } fn send_response(maybe_client: Option(Subject(a)), response: a) -> Nil { @@ -375,7 +376,7 @@ fn refresh_nodes(barnacle: Barnacle(error)) -> RefreshResult(error) { |> result.map_error(StrategyError), ) - let self = node.self() |> node.to_atom + let self = node.self() |> node.name let available_nodes = available_nodes_list @@ -427,7 +428,7 @@ fn result_apply(results: List(Result(a, b))) -> Result(List(a), List(b)) { /// ``` pub fn get_node_basename(node: node.Node) -> Result(String, Nil) { node - |> node.to_atom + |> node.name |> atom.to_string |> string.split_once("@") |> result.map(fn(tuple) { tuple.0 }) diff --git a/src/barnacle/dns.gleam b/src/barnacle/dns.gleam index 31dc8a6..5a275d8 100644 --- a/src/barnacle/dns.gleam +++ b/src/barnacle/dns.gleam @@ -99,7 +99,7 @@ pub fn discover_nodes( ip_addresses |> list.map(fn(ip) { let ip_string = format_ip_address(ip) - { basename <> "@" <> ip_string } |> atom.create_from_string + { basename <> "@" <> ip_string } |> atom.create }) |> Ok } diff --git a/src/barnacle/local_epmd.gleam b/src/barnacle/local_epmd.gleam index 476c514..4f428e8 100644 --- a/src/barnacle/local_epmd.gleam +++ b/src/barnacle/local_epmd.gleam @@ -10,7 +10,7 @@ fn list_local_nodes() -> Result(List(String), Nil) fn get_hostname() -> String { let host = node.self() - |> node.to_atom + |> node.name |> atom.to_string let assert [_, hostname] = string.split(host, "@") @@ -22,7 +22,5 @@ fn get_hostname() -> String { pub fn discover_nodes() -> Result(List(atom.Atom), Nil) { let hostname = get_hostname() list_local_nodes() - |> result.map(list.map(_, fn(n) { - { n <> "@" <> hostname } |> atom.create_from_string - })) + |> result.map(list.map(_, fn(n) { { n <> "@" <> hostname } |> atom.create })) }