Crowdhailer

Crowdhailer

Creator of Raxx

Advice for developing a library with Elixir and JavaScript parts. API/Documentation/Publishing/Redux

I am developing a library that unusually has a significant client part. In summary GenBrowser aims to give clients/browser an identifier that can be used to send messages to/from/between clients. In essence a pid.

For example

# browser 1
const { address, mailbox, send } = await GenBrowser.start('http://gen_browser.dev')
console.log(address)

# server
iex> {:ok, client} = GenBrowser.decode_address(address)
iex> GenBrowser.send(client, %{text: "From the server"})

# browser 2
send(address, {text: 'From browser 2'})

# browser 1
var message1 = await mailbox.receive({timeout: 2000})
message1.text
# From the server
var message2 = await mailbox.receive({timeout: 2000})
message2.text
# From browser 2

The security model relies on signing addresses that are sent out of the server, that is why the signed address needs decoding on the server.

I am not very up to date on the front end world and so want some advice on how to proceed with this project.
Ideally I want to keep the whole thing in one project and use as much of the Elixir ecosystem as possible. However that might be limiting.

Advice on the JavaScript API

There are two options for working with messages received.

  1. mailbox.receive() that takes an optional timeout an returns a promise that completes on the next message
  2. mailbox.setHandler(messageCallback, closeCallback) The first callback is called whenever a message is received, the second when the mailbox has been closed permanently.

These names api’s come from their erlang world equivalent receive and handle_*. They look reasonably sensible in the JavaScript world but could probably be more idiomatic

Npm publishing

The project has a JavaScript build step and the code is always to be used in a client.

  • Would you expect this to be available on npm as well as a CDN
  • If so should only the source (or only the bundle) be published to npm

JS Documentation

ExDoc has spoiled me for ease of setting up documentation. In these cases I am probably just looking for the most standard/simple way of doing things

  • What is the recommended way to document a JavaScript library?
  • Is there a way to integrate this documentation into the hex documentation?

Redux as Actors

With my (limited) knowledge of redux I think that redux and GenServers look quite similar.
Hence the name of this project.
Do you think this is a helpful analogy when describing processes to JavaScript developers.

Within a single process or store there is a single state tree.
I often say that sending a message is like dispatching on a remote store.

Is there any better way of explaining things? Should I just say actor model and not confuse the issue with mentioning redux

Most Liked

peerreynders

peerreynders

Given the nature of a mailbox as a potential source of an infinite stream of messages (events) a more contemporary interface like Observable seems more appropriate (active implementations RxJS, xstream, Bacon.js, Kefir) - Learning Observable By Building Observable.

While I realize that await is a popular construct to make asynchronous code look more sequential, I believe that this is ultimately barking up the wrong tree given the mercilessly single-threaded nature of JavaScript runtimes.

On the BEAM strictly sequential code makes sense given that you can have millions of tiny processes for concurrency. Pretending on a single threaded platform that sequential flow of control programming is sufficient for more complex interactions will in my opinion ultimately run into a brick wall.

The real answer is to start writing code that composes event streams and let the underlying platform schedule what gets processed when.

Can you please expand on this line of thinking - i.e. what has lead you to this conclusion?

  • The Redux implementation was inspired by the Flux Architecture
  • In the end the Redux analogy may not be that helpful to that many people. It is my impression that Redux adoption may have peaked since the introduction of the new Context API and since more people have started to rely on GraphQL clients (lifting whatever was left of their state up) and of course the general notion that You Might Not Need Redux. People may have adopted Redux for very different reasons and some are now only holding out because they aren’t yet ready to let go of the concomitant development tooling.
LostKobrakai

LostKobrakai

You could take a look at how phoenix handles their js dependency.

  • It’s on npm, published out if the elixir repo
  • It has it’s javascript docs on hexdocs.pm

As for the API:
I’d expect a callback driven interface. Promises are better for async results for a single action/task.

namelos

namelos

Redux is more like Agent since most of the effectful operations are strongly discouraged.
Most of the effects are handled with redux-saga or thunk. Redux-saga is a little bit like a worker, but there’s no mailbox.

The key difference is Redux simplified everything by dispatching the event to every possible listener, so there’s no such thing like pid at all. And it’s acceptable because in front-end we usually don’t have too much data.

And also all redux operation happens in a synchronous manner so there’s no need for a mailbox.

Last Post!

peerreynders

peerreynders

Painful reminder why I dislike mutability by default (which TypeScript does not fix).

test('stuff', t => {
  let listeners = new Map()
  let source = {
    addEventListener(type, listener) {
      listeners.set(listener, listener)
      console.info('added')
    },
    removeEventListener(type, listener) {
      if(listeners.delete(listener)) {
        console.info('removed')
      }
    },
    send (e) {
      console.info('size: %i', listeners.size)
      listeners.forEach((listener, _key, _map) => listener(e)) // !!!MUTABILITY!!!
      /* FIX:
      let handlers = Array.from(listeners.values())
      handlers.forEach((listener, _index, _array) => listener(e))
      */
      console.info('sent %s', e)
    }
  }

  let nextSource = fromEvent(source,'').pipe(take(1))
  var nextSub

  const subNext = () => {
    nextSub = nextSource.subscribe({
      next (value) {
        console.info('next %s', value)
      },
      error (err) {
        console.error('next Error %o', err)
      },
      complete () {
        console.info('next COMPLETE')
      }
    })
  }

  let sub = fromEvent(source,'').pipe(take(1)).subscribe({
    next (value) {
      console.info('first %s', value)
    },
    error (err) {
      console.error('first Error %o',err)
    },
    complete () {
      console.info('first COMPLETE')
      subNext() // gets 'next 1' with uncopied listeners
    }
  })

  source.send('1')
  source.send('2')

  t.pass()
})

Before fix:

> ava


added
size: 1
first 1
first COMPLETE
added
removed
next 1
next COMPLETE
removed
sent 1
size: 0
sent 2
  ✔ stuff

  1 test passed

i.e. the next handler installs itself before the dispatch loop processing the first event has completed - so the next handler sees the first event.

After fix:

> ava


added
size: 1
first 1
first COMPLETE
added
removed
sent 1
size: 1
next 2
next COMPLETE
removed
sent 2
  ✔ stuff

  1 test passed

Where Next?

Popular in Discussions Top

CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
pillaiindu
In django there is a cache framework backed by memcached. Rails also puts a lot of emphasis on caching, and even the idea of russian-doll...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
cvkmohan
The upcoming Phoenix 1.6 release looks very interesting. Became a habit to watch the commits - and - what they are bringing in. phx.gen...
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
matthias_toepp
I’d love to hear what people think about Wisp, the new Gleam web framework started by Gleam’s primary creator Louis Pilfold. Gleam, alon...
New

Other popular topics Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54260 488
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New

We're in Beta

About us Mission Statement