npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

chadtech-mail

v1.0.0

Published

Elm ports modelled in a request-response format

Readme

Mail

Heres a common story: you are working on an Elm project, but you really need some value thats only obtainable from the JavaScript side of things. Im talking about values from..

  • The api client you use to make http requests, maybe to firebase or aws, or your old in-house api client written in JavaScript.
  • That one weird JavaScript package that is super useful and stable but also hard to port into Elm given your project constraints.
  • The application your Elm code is embedded into.

The answer to all these problems is ports. Your Elm app should send a message out to the JS-side of things through a port, telling the JavaScript to do such-and-such behavior, whereafter the JavaScript sends the resulting value back into Elm through another port.

My estimation is that about 75% of the time people use ports in Elm projects, they are doing so in a request-response kind of way: they are requesting a value, and they are waiting for a value in response. The problem is Elm ports arent really built in a request-response kind of way. Outgoing and incoming ports are completely de-coupled without any assumption of a value coming back. Since Elm developers often need response values, they are often deliberately coupling outgoing and incoming ports manually. Here is a step by step of what code you would have to write to build a complete circuit from Elm to JS and back:

  1. Build an outgoing port for your request that routes your outgoing value to the right JavaScript function
  2. Write the code that builds a payload and passes it through that outgoing port
  3. Listen for the outgoing port on the JS side of your app, consume the payload, and return the value
  4. Build the incoimng port that routes your incoming value to the right place

To add a single port you are necessarily touching four parts of your code base, merely to add really tedious lines of code like "incomingMsg" -> IncomingMsg. This doesnt scale very well.

Chadtech/mail eliminates steps 1 and 4 in that process. Mail treats Elm ports like http requests and handles all the routing internally. Heres how the code in practice looks

-- Login.elm
import Ports.Mail as Mail exposing (Mail)


mailLogin : Model -> Mail Msg
mailLogin model =
    [ ( "username", Encode.string model.username )
    , ( "password", Encode.string model.password )
    ]
        |> Encode.object
        |> Mail.letter "login"
        |> Mail.expectResponse loginDecoder LoginResult
        |> Mail.send

    -- ..

    SubmitClicked ->
        ( model, mailLogin model )

    LoginResult (Ok login) ->
        -- ..
// app.js
var PortsMail = require("ports-mail");
var app = Elm.Main.fullscreen();

PortsMail(app, { login: apiClient.login });

In the code above Mail.letter "login" json says mail this json to the address "login". Mail.expectResponse says we expect json in reply in the shape of loginDecoder, and we want it routed to come in via the Msg LoginResult. The entire specification of what is going on is handled in these few lines of code with no ports or subscriptions.

On the JavaScript side of things PortsMail initializes the elm app. The address is really just the key in a javascript object. The value of that key is a function, whos first argument is the payload from Elm, and whos second argument is the call back to send the value back into Elm. Something like..

PortsMail(app, { 
    getCurrentTime: function(payload, reply){
        reply(new Date().getTime());
    }
});