nojaf
May 15, 2024, 12:22pm
1
Hi,
I’m using ReScript to work with Firebase functions.
I have two parts:
A callable function defined in Node.js
A frontend application calling the function in the browser.
The way Firebase works is that you can export functions from your index.js
in Node:
import {onCall, HttpsError} from "firebase-functions/v2/https";
export const myAction = onCall(request => { return 42 })
And then the client can do something like:
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions();
const callMyAction = httpsCallable(functions, 'myAction ');
Notice that myAction
needs to line up in both the server and the client.
So it is a bit of a magic string deal here.
Can I define my function name as a constant string in ReScript (in a shared file that both the client and server use)?
Using it on the client is easy, but can I somehow tell rescript that it needs to output my export const
with the same constant name?
One option that comes to mind is to bind to module
as an external
, which would let you treat index.js
’s exports
as a dictionary:
// Firebase.res
type request
type response = JSON.t
type apiCallback
@module("firebase-functions/v2/https")
external onCall: (request => response) => apiCallback = "onCall"
// ApiDefinition.res
let myAction = "myAction"
let myOtherAction = "myOtherAction"
// ApiImpl.res
let myAction = Firebase.onCall(_ => JSON.Number(42.0))
let myOtherAction = Firebase.onCall(_ => JSON.String("Hello!"))
// Index.res
type module_ = {
mutable exports: Js.Dict.t<Firebase.apiCallback>
}
external module_: module_ = "module"
module_.exports = Js.Dict.fromArray([
(ApiDefinition.myAction, ApiImpl.myAction),
(ApiDefinition.myOtherAction, ApiImpl.myOtherAction),
])
nojaf
May 15, 2024, 2:59pm
3
Thanks, works but forces me to use commonjs. Some of my other code is esm, any chance I can do something similar there?
That’ll depend somewhat on firebase. One possibility would be to try a default export, since that’s what cjs modules are interpreted as in esm. In other words, replace
type module_ = {
mutable exports: Js.Dict.t<Firebase.apiCallback>
}
external module_: module_ = "module"
module_.exports = Js.Dict.fromArray([
(ApiDefinition.myAction, ApiImpl.myAction),
(ApiDefinition.myOtherAction, ApiImpl.myOtherAction),
])
with
let default = Js.Dict.fromArray([
(ApiDefinition.myAction, ApiImpl.myAction),
(ApiDefinition.myOtherAction, ApiImpl.myOtherAction),
])
and see if firebase can still interpret what you’re doing.
nojaf
May 16, 2024, 7:33am
5
Yeah firebase is quite brittle when it some to this.
What I need is:
// exposes createOrder
export * from "./CreateOrder.mjs"
leads to
functions[europe-west3-createOrder]
When trying:
import * as Domain from "../shared/Domain.mjs";
import * as Js_dict from "rescript/lib/es6/js_dict.js";
import * as CreateOrder from "./CreateOrder.mjs";
var $$default = Js_dict.fromArray([[
Domain.FunctionNames.createOrder,
CreateOrder.createOrder
]]);
export {
$$default as default,
}
it leads to
functions[europe-west3-default-createOrder]
nojaf
May 16, 2024, 11:18am
6
Actually, I probably want to generate some code for my client based on my Rescript function files.
On the server side I have something like:
let createOrder: Firebase.Functions.callableFunction<
Domain.createOrderRequest,
string,
> = Firebase.Functions.Https.onCall(
~opts={
region: Domain.firebaseRegion,
allowedCors: Common.allowedCors,
},
~handler=request => {
let requestData: createOrderRequest = request.data
Console.log(requestData)
Promise.resolve("https://nojaf.com")
},
)
If I can somehow process all files and detect any let binding that has type callableFunction
and generate my client side API.
Something like:
let createOrder: Firebase.Functions.httpsCallable<
Domain.createOrderRequest,
string,
> = Functions.httpsCallable(functions, ~name="createOrder")
if I can generate this, it will always match my server code.
Is there any way to get the ReScript (typed) AST?
This might actually be fixed by tweaking the import, though at that point it’d be kinda moot.
// exposes createOrder
export createOrder from "./CreateOrder.mjs"
On the codegen side, you might be able to make use of the generic transform of GitHub - zth/rescript-embed-lang: A general purpose PPX and library for embedding other languages into ReScript, via code generation. to define an API. Would need to be felt out, though. More generally, ppx
s were the typical was to work with the AST, but it involves dipping down into ocaml land.