//// //// //// //// **Tip:** Hover the links for short summaries! //// //// #### Builder //// [new](#new "Create a new builder for a handler"), //// [port](#port "Set the port"), //// [random_port](#random_port "Choose a random port"), //// [bind](#bind "Bind to a network interface"), //// [bind_all](#bind_all "Bind to all network interfaces"), //// [after_start](#after_start "Set a callback once the server is started") //// //// #### Server Operations //// [start](#start "Start the server") //// [get_port](#get_port "Get the port the server is listening on"), //// [get_interface](#get_interface "Get the interface the server is listening on") //// [stop](#stop "Stop the server") //// //// #### Request Handling //// [read_bytes_tree](#read_bytes_tree "Read the body as a BytesTree"), //// [read_bytes](#read_bytes "Read the body as a BitArray"), //// [read_string](#read_string "Read the body as a String"), //// [read_json](#read_json "Read and decode the body as JSON") //// //// #### Response Creation //// [send_file](#send_file "Send a file response"), //// [send_bytes](#send_bytes "Send binary data"), //// [send_chunked](#send_chunked "Send a streamed rseponse"), //// [send_html](#send_html "Send html response"), //// [send_json](#send_json "Send JSON response"), //// [send_string](#send_string "Send text response"), //// [send_status](#send_status "Send a status code response"), //// [server_sent_events](#server_sent_events "Send a SSE stream response"), //// [websocket](#websocket "Open a websocket connection"), //// [send](#send "Helper for chunked/SSE/Websocket responses"), //// [step](#step "Helper for chunked/SSE/Websocket responses"), //// [close](#close "Helper for chunked/SSE/Websocket responses"), //// [dispatch](#dispatch "Send a message to a running websocket") //// //// #### Advanced //// [adapt](#adapt "Convert a Gleam handler to a fetch handler"), //// [adapt_node](#adapt_node "Convert a Gleam handler to a Node.JS handler"), //// [detect_runtime](#detect_runtime "Detect JavaScript runtime environment"), //// [status_text](#status_text "Convert a status code into a string") import filepath import gleam/bit_array import gleam/bytes_tree.{type BytesTree} import gleam/dynamic.{type Dynamic} import gleam/dynamic/decode.{type Decoder} import gleam/http/request.{type Request} import gleam/http/response.{type Response, Response} import gleam/int import gleam/io import gleam/javascript/promise.{type Promise} import gleam/json.{type Json} import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import marceau // -- BUILDER ------------------------------------------------------------------ pub opaque type Builder { Builder( port: Int, interface: String, handler: fn(Request(ReadableStream)) -> Promise(Response(ReadableStream)), after_start: fn(Int, String) -> Nil, ) } /// Creates a new server builder with the given request handler. /// /// The server listens on `http://localhost:4000` by default. See the examples /// pages for example on how to use this function. /// ///
/// /// Back to top ↑ /// ///
pub fn new( handler: fn(Request(ReadableStream)) -> Promise(Response(ReadableStream)), ) -> Builder { Builder( port: 4000, interface: "127.0.0.1", handler: handler, after_start: default_after_start, ) } fn default_after_start(port: Int, interface: String) { io.println( "smol: Listening on http://" <> interface <> ":" <> int.to_string(port), ) } /// Sets the port for the server to listen on. /// /// By default smol tries to listen on port `4000`. /// /// ## Example /// /// ```gleam /// smol.new(handler) /// |> smol.port(8080) /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn port(builder: Builder, port: Int) -> Builder { Builder(..builder, port:) } /// Configures the server to use a random available port. /// /// Useful for testing to avoid port conflicts. /// /// ## Example /// /// ```gleam /// smol.new(handler) /// |> smol.random_port() /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn random_port(builder: Builder) -> Builder { Builder(..builder, port: 0) } /// Sets the network interface to listen on. /// /// By default smol listens on `"127.0.0.1"` (localhost). /// /// ## Example /// /// ```gleam /// smol.new(handler) /// |> smol.bind("192.168.1.100") /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn bind(builder: Builder, interface: String) -> Builder { Builder(..builder, interface:) } /// Configures the server to listen on all network interfaces (`0.0.0.0`). /// /// ## Example /// /// ```gleam /// smol.new(handler) /// |> smol.bind_all() /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn bind_all(builder: Builder) -> Builder { Builder(..builder, interface: "0.0.0.0") } /// Sets a callback function to be called after the server starts. /// /// The function receives the actual port and interface the server is listening /// on. By default, a simple `Listening on...` message is printed. /// /// ## Example /// /// ```gleam /// fn log_start(port, interface) { /// io.println("Server started on " <> interface <> ":" <> int.to_string(port)) /// } /// /// smol.new(handler) /// |> smol.after_start(log_start) /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn after_start( builder: Builder, after_start: fn(Int, String) -> Nil, ) -> Builder { Builder(..builder, after_start:) } // -- FETCH REQUEST RESPONSE BODIES -------------------------------------------- /// smol uses a standard web `ReadableStream` under the hood on all runtimes, /// including Node.js! pub type ReadableStream pub type ReadError { ExcessBody MalformedBody } pub type FileError { IsDir NoAccess NoEntry UnknownFileError RuntimeNotSupportedFileError } /// Reads the request body as a BytesTree. /// /// The `max_body_limit` parameter sets a limit on the size of the body that /// can be read. /// /// ### Example /// /// ```gleam /// use body <- promise.await(smol.read_bytes_tree(request, 1024 * 1024)) /// case body { /// Ok(bytes_tree) -> { /// // ... do something with the body ... /// let response = smol.send_string("Received bytes") /// promise.resolve(response) /// } /// Error(error) -> { /// let response = smol.send_status(400) /// promise.resolve(response) /// } /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
@external(javascript, "./smol.ffi.mjs", "read") pub fn read_bytes_tree( request: Request(ReadableStream), max_body_limit: Int, ) -> Promise(Result(BytesTree, ReadError)) /// Reads the request body as a BitArray. /// /// The `max_body_limit` parameter sets a limit on the size of the body that /// can be read. /// /// /// ## Example /// /// ```gleam /// // a simple echo server! /// use body <- promise.await(smol.read_bytes(request, max_body_limit: 1024 * 1024)) /// case body { /// Ok(bytes) -> { /// let response = smol.send_bytes(bytes) /// promise.resolve(response) /// } /// Error(error) -> { /// let response = smol.send_status(400) /// promise.resolve(response) /// } /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn read_bytes( request: Request(ReadableStream), max_body_limit max_body_limit: Int, ) -> Promise(Result(BitArray, ReadError)) { use body <- try(read_bytes_tree(request, max_body_limit)) return(bytes_tree.to_bit_array(body)) } /// Reads the request body as a UTF-8 encoded String. /// /// The `max_body_limit` parameter sets a limit on the size of the body that /// can be read. /// /// ## Example /// /// ```gleam /// use body <- promise.await(smol.read_string(request, max_body_limit: 1024 * 1024)) /// case body { /// Ok(text) -> { /// let response = smol.send_string("You sent: " <> text) /// promise.resolve(response) /// } /// Error(error) -> { /// let response = smol.send_status(400) /// promise.resolve(response) /// } /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn read_string( request: Request(ReadableStream), max_body_limit max_body_limit: Int, ) -> Promise(Result(String, ReadError)) { use body <- try(read_bytes(request, max_body_limit)) case bit_array.to_string(body) { Ok(text) -> return(text) Error(_) -> fail(MalformedBody) } } /// Reads and decodes a JSON request body. /// /// The `decoder` parameter is used to decode the JSON into a specific type. /// The `max_body_limit` parameter sets a limit on the size of the body that /// can be read. /// /// ## Example /// /// ```gleam /// type User { /// User(name: String, age: Int) /// } /// /// fn user_decoder() { /// use name <- decode.field("name", decode.string) /// use age <- decode.field("age", decode.int) /// decode.success(User(name:, age:)) /// } /// /// fn handler(request) { /// use user <- promise.await(smol.read_json( /// request, /// using: user_decoder(), /// max_body_limit: 1024 * 1024, /// )) /// /// case user { /// Ok(user) -> { /// // Process the user /// let response = smol.send_string("Hello, " <> user.name) /// promise.resolve(response) /// } /// Error(error) -> { /// let response = smol.send_status(400) /// promise.resolve(response) /// } /// } /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn read_json( request: Request(ReadableStream), using decoder: Decoder(a), max_body_limit max_body_limit: Int, ) -> Promise(Result(a, json.DecodeError)) { use body <- promise.await(read_string(request, max_body_limit)) case body { Ok(body) -> case json.parse(from: body, using: decoder) { Error(error) -> fail(error) Ok(result) -> return(result) } Error(_) -> fail(json.UnexpectedEndOfInput) } } /// Sends a file as the response. /// /// The `path` parameter is the file path to send. The path will be joined to /// the current working directory. Make sure the path cannot escape the folder /// you are trying to serve from! /// /// The `offset` parameter specifies the starting byte offset (default 0). /// The `limit` parameter specifies the maximum number of bytes to send /// (default is the entire file). /// /// `Content-Length` and `Content-Type` headers will be set automatically based /// on the file name and size. /// /// ## Example /// /// ```gleam /// fn handler(request) { /// use response <- promise.await(smol.send_file( /// "./priv/public/index.html", /// offset: 0, /// limit: option.None, /// )) /// /// case response { /// Ok(response) -> promise.resolve(response) /// Error(error) -> promise.resolve(smol.send_status(404)) /// } /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn send_file( path: String, offset offset: Int, limit limit: Option(Int), ) -> Promise(Result(Response(ReadableStream), FileError)) { let limit = option.unwrap(limit, 0) use file <- try(from_file(path, offset, limit)) let content_length = case limit > 0 { True -> int.clamp(file.size - offset, 0, limit) False -> int.max(file.size - offset, 0) } let content_type = case file.mime { option.None -> filepath.extension(path) |> result.map(marceau.extension_to_mime_type) |> result.unwrap("application/octet-stream") option.Some(mime) -> mime } return( Response(status: 200, body: file.stream, headers: [ #("content-length", int.to_string(content_length)), #("content-type", content_type), ]), ) } @internal pub type File { File(stream: ReadableStream, size: Int, mime: option.Option(String)) } @external(javascript, "./smol.ffi.mjs", "from_file") fn from_file( path: String, offset: Int, limit: Int, ) -> Promise(Result(File, FileError)) /// Creates a response with binary data. /// /// The response will have content-type `application/octet-stream`. /// /// ## Example /// /// ```gleam /// fn handler(request) { /// let binary_data = <<1, 2, 3, 4, 5>> /// let response = smol.send_bytes(binary_data) /// promise.resolve(response) /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn send_bytes(bytes: BitArray) -> Response(ReadableStream) { Response( status: 200, headers: [ #("content-type", "application/octet-stream"), #("content-length", int.to_string(bit_array.byte_size(bytes))), ], body: from_bytes(bytes), ) } /// Creates a plain text response with the given string. /// /// The response will have context-type: `text/plain; charset=utf-8`. /// /// /// ## Example /// /// ```gleam /// fn handler(request) { /// let response = smol.send_string("Hello, World!") /// promise.resolve(response) /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn send_string(text: String) -> Response(ReadableStream) { let body = <> Response( status: 200, headers: [ #("content-type", "text/plain; charset=utf-8"), #("content-length", int.to_string(bit_array.byte_size(body))), ], body: from_bytes(body), ) } /// Creates a html response with the given string. /// /// The response will have context-type: `text/html; charset=utf-8`. /// /// /// ## Example /// /// ```gleam /// fn handler(request) { /// let response = smol.send_html("

Hello, World!") /// promise.resolve(response) /// } /// ``` /// ///
/// /// Back to top ↑ /// pub fn send_html(text: String) -> Response(ReadableStream) { let body = <> Response( status: 200, headers: [ #("content-type", "text/html; charset=utf-8"), #("content-length", int.to_string(bit_array.byte_size(body))), ], body: from_bytes(body), ) } /// A small helper to create a response with the given status code and a /// standard status message. /// /// ## Example /// /// ```gleam /// fn handler(request) { /// let response = smol.send_status(404) // Response with "Not Found" body /// promise.resolve(response) /// } /// ``` /// ///
/// /// Back to top ↑ /// ///
pub fn send_status(status: Int) -> Response(ReadableStream) { let body = <> Response( status:, headers: [ #("content-type", "text/plain; charset=utf-8"), #("content-length", int.to_string(bit_array.byte_size(body))), ], body: from_bytes(body), ) } /// Creates a JSON response with the given JSON data. /// /// The response will have Content-Type: application/json; charset=utf-8. /// /// ```gleam /// fn handler(request) { /// let user = User(name: "Ada", age: 4) /// let data = json.object([ /// #("name", json.string(user.name)), /// #("age", json.int(user.age)), /// ]) /// /// let response = smol.send_json(data) /// promise.resolve(response) /// } /// ``` /// /// pub fn send_json(json: Json) -> Response(ReadableStream) { let body = <> Response( status: 200, headers: [ #("content-type", "application/json; charset=utf-8"), #("content-length", int.to_string(bit_array.byte_size(body))), ], body: from_bytes(body), ) } /// A type used in various functions as a return type from "unfold" or "update" /// - style functions, usually used in combination with a `Promise`, similar to /// `actor.Next` or `yielder.Step`. /// /// Represents the result of a stateful iteration that can send messages back to /// a client. pub type Step(state, output) { /// Stop iterating, close the connection. We are done sending. Close /// Loop, without sending anything. Step(state: state) /// Loop, sending `returning` to the client. Send(state: state, sending: output) } /// A convenience method useful for async functions. /// /// Equivalent to `promise.resolve(Close)` /// /// pub fn close() -> Promise(Step(state, output)) { promise.resolve(Close) } /// A convenience method useful for async functions. /// /// Equivalent to `promise.resolve(Step(state))` /// /// pub fn step(state state: state) -> Promise(Step(state, output)) { promise.resolve(Step(state)) } /// A convenience method useful for async functions. /// /// Equivalent to `promise.resolve(Send(state:, returning:))` /// /// pub fn send( state state: state, sending sending: output, ) -> Promise(Step(state, output)) { promise.resolve(Send(state:, sending:)) } /// Creates a chunked transfer-encoded HTTP response from a yielder function. /// /// The yielder function produces chunks of data over time, allowing efficient /// streaming of large responses without loading everything into memory at once. /// /// Note that file responses are automatically streamed. /// /// ## Example /// /// ```gleam /// // Stream a large file in chunks /// fn stream_handler(request) { /// send_chunked( /// from: #(0, file_size), /// with: fn(state) { /// let #(offset, total) = state /// /// case offset >= total { /// // we are done! /// True -> smol.close() /// False -> { /// // Read next chunk (10KB at a time) /// let chunk_size = int.min(10240, total - offset) /// use chunk <- promise.await(read_chunk(path, offset, chunk_size)) /// /// // Return chunk and advance to next offset /// smol.send( /// state: #(offset + chunk_size, total), /// sending: chunk, /// ) /// } /// } /// } /// ) /// |> promise.resolve /// } /// ``` /// /// pub fn send_chunked( from state: state, with fun: fn(state) -> Promise(Step(state, BitArray)), ) -> Response(ReadableStream) { let body = from_unfold(state, fun) Response( status: 200, headers: [ #("conent-type", "application/octet-stream"), #("transfer-encoding", "chunked"), #("connection", "keep-alive"), ], body: body, ) } fn from_bytes(bytes: BitArray) -> ReadableStream { use has_sent <- from_unfold(False) case has_sent { False -> send(state: True, sending: bytes) True -> close() } } @external(javascript, "./smol.ffi.mjs", "from_unfold") fn from_unfold( state: state, unfold: fn(state) -> Promise(Step(state, BitArray)), ) -> ReadableStream // -- SERVER-SENT EVENTS ------------------------------------------------------- pub type SseEvent { SseEvent( event: Option(String), data: String, id: Option(String), retry: Option(Int), ) } fn format_sse_event(event: SseEvent) -> BitArray { let str = case event.event { Some(event_type) -> "event: " <> event_type <> "\n" None -> "" } let str = case event.id { Some(id) -> str <> "id: " <> id <> "\n" None -> str } let str = case event.retry { Some(ms) -> str <> "retry: " <> int.to_string(ms) <> "\n" None -> str } // Add data (split multi-line data with multiple data: lines) let str = { use str, line <- list.fold(string.split(event.data, on: "\n"), str) str <> "data: " <> line <> "\n" } let str = str <> "\n" <> } /// Creates a Server-Sent Events (SSE) response, producing events using an /// async yielder function. /// /// The unfold function takes a state and returns either: /// - `Ok(#(new_state, event))` to emit an event and continue with new state /// - `Error(Nil)` to end the stream /// /// ## Example /// /// ```gleam /// fn sse_handler(request) { /// // Create a counter that generates events every second /// use count <- server_sent_events(0) /// use _ <- promise.await(promise.wait(1000)) /// case count < 10 { /// True -> { /// let event = SseEvent( /// event: Some("update"), /// data: "Count is " <> int.to_string(count), /// id: None, /// retry: None, /// ) /// smol.send(state: count + 1, sending: event) /// } /// False -> smol.close() /// } /// } /// ``` /// /// pub fn server_sent_events( from state: state, with fun: fn(state) -> Promise(Step(state, SseEvent)), ) -> Response(ReadableStream) { let fun = fn(state) { use result <- promise.await(fun(state)) case result { Send(new_state, event) -> send(new_state, format_sse_event(event)) Step(new_state) -> step(new_state) Close -> close() } } send_chunked(state, fun) |> response.set_header("content-type", "text/event-stream") |> response.set_header("cache-control", "no-cache") } // -- WEBSOCKETS --------------------------------------------------------------- pub type WebsocketMessage { Binary(bytes: BitArray) Text(text: String) } pub opaque type WebsocketRuntime(msg) { WebsocketRuntime(send: fn(msg) -> Nil) } /// Attempt to open a websocket connection in response to this request. /// /// The client has to have made a valid `Upgrade` request, otherwise smol will /// respond with status 426. /// /// When the upgrade succeeds, an actor-like process is started, sending and /// receiving messages from the client. /// /// ```gleam /// type Msg { /// ConnectionOpened /// ReceivedMessage(smol.WebsocketMessage) /// ConnectionClosed /// } /// /// fn websockets_handler(request) { /// use count, msg <- smol.websocket( /// init: 1, /// on_open: ConnectionOpened, /// on_message: ReceivedMessage, /// on_close: ConnectionClosed /// ) /// /// case msg { /// ConnectionClosed -> smol.close() /// ReceivedMessage(smol.Text(text:)) -> { /// let response = "Message No. " <> int.to_string(count) <> ": " <> text /// smol.send(state: count + 1, sending: smol.Text(response)) /// } /// ReceivedMessage(smol.Binary(_)) -> smol.step(state: count + 1) /// } /// } /// ``` /// /// pub fn websocket( init init: state, update update: fn(state, msg) -> Promise(Step(state, WebsocketMessage)), on_open handle_open: msg, on_message handle_message: fn(WebsocketMessage) -> msg, on_close handle_close: msg, ) -> #(Response(ReadableStream), WebsocketRuntime(msg)) { // NOTE: both the body and response are kinda fake here - // the body is empty and is used as an object holding a reference to the actor spec // the response is closer to reality, but upgrade will be handled by the // server runtime again, and headers like sec-websocket-accept are missing.. let #(body, send) = from_upgrade( init:, update:, on_open: handle_open, on_message: handle_message, on_close: handle_close, ) // We construct an Upgrade required response here to indicate that the server // _wanted_ to use websockets, but those were not properly implemented in one // way or another. let response = Response(status: 426, headers: [], body: body) let runtime = WebsocketRuntime(send) #(response, runtime) } @external(javascript, "./smol.ffi.mjs", "from_upgrade") fn from_upgrade( init init: state, update update: fn(state, msg) -> Promise(Step(state, WebsocketMessage)), on_open handle_open: msg, on_message handle_message: fn(WebsocketMessage) -> msg, on_close handle_close: msg, ) -> #(ReadableStream, fn(msg) -> Nil) /// Send a custom message to a running websocket actor. /// /// pub fn dispatch(runtime: WebsocketRuntime(msg), msg: msg) -> Nil { runtime.send(msg) } // -- START -------------------------------------------------------------------- pub opaque type Server { Server(port: Int, interface: String, stop: fn() -> promise.Promise(Nil)) } @internal pub fn started( port: Int, interface: String, stop: fn() -> promise.Promise(Nil), ) -> Result(Server, _) { Ok(Server(port:, interface:, stop:)) } /// Adapts a smol handler function to work with standard Web /// Request/Response objects. /// /// This is useful for integrating with other JavaScript frameworks or /// environments that use the standard Fetch API, like serverless platforms or /// service workers. /// /// @external(javascript, "./adapt.ffi.mjs", "adapt") pub fn adapt( handler: fn(Request(ReadableStream)) -> Promise(Response(ReadableStream)), ) -> fn(Dynamic) -> Promise(Dynamic) /// Adapts a smol handler function to work with NodeJS. /// /// This can be useful for integrating Gleam and smol into existing Node projects, /// or for legacy environments where Node is assumed. /// /// @external(javascript, "./adapt.ffi.mjs", "adaptNode") pub fn adapt_node( handler: fn(Request(ReadableStream)) -> Promise(Response(ReadableStream)), ) -> fn(Dynamic, Dynamic) -> Nil /// Starts the server with the configured options. /// /// The returned promise resolves once the server has been started or failed /// to start. /// /// ## Example /// /// ```gleam /// let handler = fn(request) { /// let response = smol.send_string("Hello, Joe!") /// promise.resolve(response) /// } /// /// use server <- promise.await( /// smol.new(handler) /// |> smol.port(8080) /// |> smol.start() /// ) /// /// case server { /// Ok(server) -> { /// io.println("Server started!") /// } /// Error(_) -> { /// io.println("Could not start the server") /// } /// } /// ``` /// /// @external(javascript, "./smol.ffi.mjs", "start") pub fn start(builder: Builder) -> Promise(Result(Server, Nil)) /// Gets the port that the server is listening on. /// /// This is especially useful when using `random_port()` to determine which /// port was assigned. /// /// ## Example /// /// ```gleam /// use server <- promise.await(smol.start(builder)) /// use server <- result.try(server) /// let port = smol.get_port(server) /// io.println("Server listening on port " <> int.to_string(port)) /// ``` /// /// pub fn get_port(server: Server) -> Int { server.port } /// Gets the network interface that the server is listening on. /// /// ## Example /// /// ```gleam /// use server <- promise.await(smol.start(builder)) /// use server <- result.try(server) /// let interface = smol.get_interface(server) /// io.println("Server listening on interface " <> interface) /// ``` /// /// pub fn get_interface(server: Server) -> String { server.interface } /// Stops the server gracefully. /// /// Returns a Promise that resolves when the server has fully stopped. /// /// ## Example /// /// ```gleam /// use server <- promise.await(smol.start(builder)) /// case server { /// Ok(server) -> { /// io.println("Server started, stopping in 10 seconds...") /// use _ <- promise.await(promise.wait(10_0000)) /// io.println("Stopping the server...") /// use _ <- promise.await(smol.stop(server)) /// io.println("Server stopped.") /// } /// Error(_) -> { /// io.println("Could not start the server!") /// } /// } /// ``` /// /// pub fn stop(server: Server) -> Promise(Nil) { server.stop() } // -- HELPERS ------------------------------------------------------------------ pub type Runtime { Node Deno Bun } /// Detects the current JavaScript runtime environment. /// /// Returns a Result containing the Runtime or an error if the runtime couldn't /// be detected. /// /// ## Example /// /// ```gleam /// fn main() { /// case smol.detect_runtime() { /// Ok(smol.Node) -> io.println("Running on Node.js") /// Ok(smol.Deno) -> io.println("Running on Deno") /// Ok(smol.Bun) -> io.println("Running on Bun") /// Error(_) -> io.println("Unknown runtime") /// } /// } /// ``` /// /// @external(javascript, "./smol.ffi.mjs", "detect_runtime") pub fn detect_runtime() -> Result(Runtime, Nil) /// Returns the status text, given a status code. /// /// If the status code is not known, return the code as a string instead. /// /// ## Example /// /// ```gleam /// smol.status_text(418) /// // --> "I'm a teapot" /// /// smol.status_text(911) /// // --> "911" /// ``` /// /// pub fn status_text(status: Int) -> String { case status { 100 -> "Continue" 101 -> "Switching Protocols" 103 -> "Early Hints" 200 -> "OK" 201 -> "Created" 202 -> "Accepted" 203 -> "Non-Authoritative Information" 204 -> "No Content" 205 -> "Reset Content" 206 -> "Partial Content" 300 -> "Multiple Choices" 301 -> "Moved Permanently" 302 -> "Found" 303 -> "See Other" 304 -> "Not Modified" 307 -> "Temporary Redirect" 308 -> "Permanent Redirect" 400 -> "Bad Request" 401 -> "Unauthorized" 402 -> "Payment Required" 403 -> "Forbidden" 404 -> "Not Found" 405 -> "Method Not Allowed" 406 -> "Not Acceptable" 407 -> "Proxy Authentication Required" 408 -> "Request Timeout" 409 -> "Conflict" 410 -> "Gone" 411 -> "Length Required" 412 -> "Precondition Failed" 413 -> "Payload Too Large" 414 -> "URI Too Long" 415 -> "Unsupported Media Type" 416 -> "Range Not Satisfiable" 417 -> "Expectation Failed" 418 -> "I'm a teapot" 422 -> "Unprocessable Entity" 425 -> "Too Early" 426 -> "Upgrade Required" 428 -> "Precondition Required" 429 -> "Too Many Requests" 431 -> "Request Header Fields Too Large" 451 -> "Unavailable For Legal Reasons" 500 -> "Internal Server Error" 501 -> "Not Implemented" 502 -> "Bad Gateway" 503 -> "Service Unavailable" 504 -> "Gateway Timeout" 505 -> "HTTP Version Not Supported" 506 -> "Variant Also Negotiates" 507 -> "Insufficient Storage" 508 -> "Loop Detected" 510 -> "Not Extended" 511 -> "Network Authentication Required" _ -> int.to_string(status) } } fn try( promise: Promise(Result(a, e)), then continue_with: fn(a) -> Promise(Result(b, e)), ) -> Promise(Result(b, e)) { use result <- promise.await(promise) case result { Ok(value) -> continue_with(value) Error(e) -> promise.resolve(Error(e)) } } fn return(value: a) -> Promise(Result(a, e)) { promise.resolve(Ok(value)) } fn fail(error: e) -> Promise(Result(a, e)) { promise.resolve(Error(error)) }