So at CCA we are mostly using implementation records for dependency injection.
Don’t know if it’s idiomatic ReScript, but it does not need any concepts that JS devs are unfamiliar with.
Here is a realworld example for such a module
// PlatformFunctions.res
type impl = {keepAwakeWhile: 'a. (string, unit => promise<'a>) => promise<'a>}
let defaultImpl: impl = {
keepAwakeWhile: (_, promiseCreator) => promiseCreator(),
}
let impl = ref(defaultImpl)
let setImpl = theImpl => impl := theImpl
let keepAwakeWhile = (name, promiseCreator) => impl.contents.keepAwakeWhile(name, promiseCreator)
// IOSPlatformFunctions.res
let impl: PlatformFunctions.impl = {
keepAwakeWhile: async (name, promiseCreator) => { ... }
Then (usually on app startup) we set the impl:
PlatformFunctions.setImpl(IOSPlatformFunctions.impl)
and anywhere in the app then call it:
await PlatformFunctions.keepAwakeWhile("confirmPushReceived", onPushReceived)
Previously we used first-class modules for that but it turned out to be overkill when we found out about Scoped Polymorphic Types | ReScript Language Manual.