Trouble Creating JS Bindings

I’m trying to create bindings for @slack/bolt and I’m having some trouble creating a Module that feels natural in ReScript/ReasonML. In general, I have a couple exports I need to create bindings for that do something like this (in JS)

import { Foo, Bar } from '@slack/bolt'

const foo = new Foo({ param: 'string' })
foo.method1()
foo.property1
foo.method2()

I’ve tried something like this but it’s not behaving how I would like…

module ExpressReceiver = {
  type t

  type app: Express.App.t

  @module("@slack/bolt") @new
  external make: (~signingSecret: string) => t = "ExpressReceiver"
}

And I want to use it like this:

let expressReciever = SlackBolt.ExpressReceiver.make(~signingSecret=Const.slackSigningSecret)

Express.App.get(
  expressReciever.app,
  ~path="/",
  Express.Middleware.from((_next, _req, res) =>
    Express.Response.sendString("mic check 12 12", res)
  ),
)

I can’t find much documentation to achieve that I’m looking for. Ay help would be greatly appreciated!

I’m currently doing this right now but was wondering if there’s a way to just put the instance methods (for lack of a better term) on the module itself

module ExpressReceiver = {
  type expressReceiver = {app: Express.App.t}

  @module("@slack/bolt") @new
  external make: (~signingSecret: string) => expressReceiver = "ExpressReceiver"
}

There is, but I recommend not doing that. Properties are fine - methods are more flexible when they’re done in a static style that uses @send to convert them to instanced at runtime.

Here’s how I do this sort of thing:

https://rescript-lang.org/try?code=LYewJgrgNgpgBAUQB4AcBOMDOmBKMDGMAlgG4xpwC8cA3gFBxwAuAnivE1bQ43OiOzSsAjAC44mJmiIA7AOY8Avj1bs4MVBmx5CpcgAUAhmkPAu9XhKJyZsuQGUCGJuMnT5SngAFQkWAAoAIi9MKEN8AGsAegAjECgmQIBKOC8ZGAB3Hg0mchlDKDhgQwiYcX8NdCxcAmIyNCMTYBTKAD5mLkDkKu1avTRAum9MGBkwbKRctHzC4BgmAAtwMQ62uAhbTmpAucXlweU6WE4MXXqubq0as-IAOmLS-xorGztHfGdxQMwQOYkneaBOCKJJDU51cgAWlal2qOghaHu8yWYGEQwAUphblAQHJ-OD+rd+IIREkgA

3 Likes

Can you show a specific example of what you need in terms of JS code? Your first JS code is talking about ‘Foo’ and ‘Bar’ and then your ReScript code is talking about ‘ExpressReceiver’, so I’m not being able to understand how they’re related.

If we assume for a second that the ‘Foo’/‘Bar’ example code is the JS you need to output from ReScript, then you could bind it like this:

// Bolt.res

module Foo = {
  type t
  type options = {param: string}

  @module("@slack/bolt") @new external make: options => t = "Foo"
  @send external method1: t => unit = "method1"
  @get external property1: t => int = "property1" // e.g.
  @send external method2: t => unit = "method2"
}

let foo1 = Foo.make({param: "string"})
Foo.method1(foo1)
let x = Foo.property1(foo1)
Foo.method2(foo1)
1 Like

Is there a reason why you don’t use fast pipe in your usage examples? I mean, it was designed to look like method calls:

let foo1 = Foo.make({param: "string"})
foo1->Foo.method1
let x = foo1->Foo.property1
foo1->Foo.method2
2 Likes

Couple of reasons. For a single function call I usually don’t use pipes. And I wanted to make it simple and obvious that the binding is giving us a function.

4 Likes

Ah, thank you! I’m reading up on send now and I love the trick of using the pipe first operator. I was passing in the instance, which was working as well. Thanks so much for the reply and code snippet! :pray:

Thanks for the reply- the snippet is pretty much exactly what @spyder suggested in his solution.

Because I forgot about it!

I can appreciate that at a technical level, but when teaching JS programers telling them that foo->method compiles to foo.method (which isn’t always true but my app works with a ton of bindings) they find it much easier to get started.

1 Like

Using pipe with a single function call is indeed a grey area.

However, the new editor integration finally lets us achieve one important thing we’ve always wanted: -> pipe autocompletion. So it’s understandable that you wanna write myValue->methodSomewhere. Or not. Doesn’t matter too much either way. Just keep things readable.

1 Like

Don’t JS programmers get confused by the fact that in ReScript, you don’t need parentheses (you don’t write foo->method())?

If they’re being mentored then just pass a word. If they’re not then they don’t know this symbol anyway.

Alternatively, treat it as some special getter but without the runtime magic =D

I guess I should’ve been more specific - I don’t tend to encourage pipe for methods that take one argument. Once there are multiple arguments it looks a lot more normal.

@send external join: (array<'a>, string) => string = "join"

let message = ["hello", "world"]->join(" ")
2 Likes