diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 175edcb..7f0fbd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,53 +1,66 @@ name: CI -# Run CI when a PR is opened against the branch `main` -# and when one pushes a commit to `main`. +# Run CI when a PR is opened against the branch `master` +# and when one pushes a commit to `master`. on: push: branches: [master] pull_request: branches: [master] +# Run CI on all 3 latest OSes jobs: build: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: purescript-contrib/setup-purescript@main + - name: Set up Node toolchain + uses: actions/setup-node@v3 with: - purescript: "0.15.7" - purs-tidy: "0.9.2" - psa: "0.8.2" - spago: "0.20.9" - psa: "0.7.2" + node-version: "16" + + - name: Cache NPM dependencies + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Setup PureScript tooling + run: + npm i -g purescript@latest purs-tidy@latest purescript-psa@latest spago@latest - name: Cache PureScript dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} path: | .spago output - - name: Set up Node toolchain - uses: actions/setup-node@v2 - with: - node-version: "16" - # Compile the library/project # censor-lib: ignore warnings emitted by dependencies # strict: convert warnings into errors # Note: `purs-args` actually forwards these args to `psa` - name: Build the project run: | - spago build --purs-args "--censor-lib --strict" + npx spago build --purs-args "--censor-lib --strict" - name: Run tests run: | - spago test + npx spago -x test.dhall test - name: Check Formatting + if: runner.os == 'Linux' run: | purs-tidy check src test diff --git a/.gitignore b/.gitignore index 30efe19..24fc830 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /.purs* /.psa* /.spago +/.vscode diff --git a/spago.dhall b/spago.dhall index e3353dc..e1250ec 100644 --- a/spago.dhall +++ b/spago.dhall @@ -1,10 +1,10 @@ { name = "node-event-emitters" , dependencies = [ "effect" + , "either" , "functions" , "prelude" - , "safe-coerce" - , "unsafe-coerce" + , "unsafe-coerce" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs" ] diff --git a/src/Node/EventEmitter.js b/src/Node/EventEmitter.js index ac668e2..2f3b6a4 100644 --- a/src/Node/EventEmitter.js +++ b/src/Node/EventEmitter.js @@ -1,35 +1,24 @@ -import events from "node:events"; +import EventEmitter from "node:events"; const newImpl = function () { - return new events.EventEmitter(); + return new EventEmitter(); } export { newImpl as new }; -export function listenersLengthImpl(emitter, event) { - return emitter.listeners(event).length; -} - -export function setMaxListenersImpl(emitter, max) { - emitter.setMaxListeners(max); -} - -export function onImpl(emitter, eventName, cb) { - emitter.on(eventName, cb); - return cb; -} +// addEventListener - not implemented; alias to `on` +export const unsafeEmitFn = (emitter) => emitter.emit.bind(emitter); +export const eventNamesImpl = (emitter) => emitter.eventNames(); +export const symbolOrStr = (left, right, sym) => typeof sym == "symbol" ? left(sym) : right(sym); +export const getMaxListenersImpl = (emitter) => emitter.getMaxListeners(); +export const listenerCountImpl = (emitter, eventName) => emitter.listenerCount(eventName); +// listeners - not implemented; returned functions cannot be used in type-safe way. +export const unsafeOff = (emitter, eventName, cb) => emitter.off(eventName, cb); +export const unsafeOn = (emitter, eventName, cb) => emitter.on(eventName, cb); +export const unsafeOnce = (emitter, eventName, cb) => emitter.once(eventName, cb); +export const unsafePrependListener = (emitter, eventName, cb) => emitter.prependListener(eventName, cb); +export const unsafePrependOnceListener = (emitter, eventName, cb) => emitter.prependOnceListener(eventName, cb); +// removeAllListeners - not implemented; bad practice +// removeEventListener - not implemented; alias to `off` +export const setMaxListenersImpl = (emitter, max) => emitter.setMaxListeners(max); +// rawListeners - not implemented; returned functions cannot be used in type-safe way. -export function offImpl(emitter, eventName, cb) { - emitter.off(eventName, cb); -} - -export function onceEventListener(emitter, eventName, cb) { - emitter.once(eventName, cb); -} - -export function emitImpl(emitter, eventName) { - return function (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { - emitter.emit(eventName, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) - }; -} -const undefined_ = undefined -export { undefined_ as undefined } diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index b0c3502..ed5a262 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -1,370 +1,380 @@ -module Node.EventEmitter where +-- | ## Handling events emitted by an `EventEmitter` +-- | +-- | One can add callbacks to an `EventEmitter` on two major axes: +-- | - whether listener is added to the front (i.e. `on`) or back (i.e. `prependListener`) of the array +-- | - whether a listener is automatically removed after the first event (i.e. `once` or `prependOnceListener`). +-- | +-- | Moreover, some types are in a chain of subclasses. For example, `Http2Server` extends `net.Server`, which extends `EventEmitter`. +-- | This means some types (e.g. `Http2Server`) can use events defined in their superclass (e.g. `net.Server` and `EventEmitter`). +-- | +-- | This module provides functions for each of the 4 callback-adding functions above while accounting for the subtype problem above. +-- | If `` is either `on`, `once`, `prependListener`, or `prependOnceListener`, then this module exposes +-- | 1. `` - the standard function; there's no programmable way to remove the listener +-- | 2. `Via` - same as 1 but accounts for subclass reuse +-- | 3. `Subscribe` - the standard function; returns a callback that removes the listener +-- | 4. `SubscribeVia` - same as 3 but accounts for subclass reuse +-- | +-- | The documentation for the `on*` functions provide an example of how to handle events. +-- | +-- | ## Defining events emitted by an `EventEmitter` +-- | +-- | Below, we'll provide an example for how to define an event handler for a type. Let's assume the following: +-- | - There is a type `Foo` that exends `EventEmitter` +-- | - `Foo` values can handle "bar" events +-- | - a "bar" event takes the following callback: `EffectFn2 (Nullable Error) String Unit` +-- | - the `String` value is always either "red", "green", or "blue" +-- | +-- | Then we would write +-- | ``` +-- | data Color = Red | Green | Blue +-- | +-- | barHandle :: EventHandle Foo (Maybe Error -> Color -> Effect Unit) (EffectFn1 (Nullable Error) String Unit) +-- | barHandle = EventHandle "bar" $ \psCb -> mkEffectFn2 \nullableError str -> +-- | psCb (toMaybe nullableError) case str of +-- | "red" -> Red +-- | "green" -> Green +-- | "blue" -> Blue +-- | _ -> unsafeCrashWith $ "Impossible String value for event 'bar': " <> show str +-- | ``` +-- | +-- | ## Emitting events via an `EventEmitter` +-- | +-- | Unfortunately, there isn't a good way to emit events safely in PureScript. If one wants to emit an event +-- | in PureScript code that will be consumed by PureScript code, there are better abstractions to use than `EventEmitter`. +-- | If one wants to emit an event in PureScript code that will be consumed by JavaScript code, then +-- | the `unsafeEmitFn` function can be used to call n-ary functions. However, this is very unsafe. See its docs for more context. +module Node.EventEmitter + ( EventEmitter + , new + , JsSymbol + , SymbolOrStr + , eventNames + , getMaxListeners + , listenerCount + , setMaxListeners + , setUnlimitedListeners + , unsafeEmitFn + , EventHandle(..) + , newListenerHandle + , removeListenerHandle + , on + , onVia + , onSubscribe + , onSubscribeVia + , once + , onceVia + , onceSubscribe + , onceSubscribeVia + , prependListener + , prependListenerVia + , prependListenerSubscribe + , prependListenerSubscribeVia + , prependOnceListener + , prependOnceListenerVia + , prependOnceListenerSubscribe + , prependOnceListenerSubscribeVia + ) where import Prelude -import Data.Function.Uncurried (Fn2, runFn2) +import Data.Either (Either(..)) +import Data.Function.Uncurried (Fn3, runFn3) import Effect (Effect) -import Effect.Uncurried (EffectFn1, EffectFn10, EffectFn2, EffectFn3, EffectFn4, EffectFn5, EffectFn6, EffectFn7, EffectFn8, EffectFn9, mkEffectFn1, mkEffectFn10, mkEffectFn2, mkEffectFn3, mkEffectFn4, mkEffectFn5, mkEffectFn6, mkEffectFn7, mkEffectFn8, mkEffectFn9, runEffectFn10, runEffectFn2, runEffectFn3) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn4, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4) import Unsafe.Coerce (unsafeCoerce) -data Handlers +foreign import data EventEmitter :: Type -foreign import data CanHandle :: Handlers -foreign import data NoHandle :: Handlers +-- | Create a new event emitter +foreign import new :: Effect EventEmitter -data Emittable +foreign import data SymbolOrStr :: Type -foreign import data CanEmit :: Emittable -foreign import data NoEmit :: Emittable +foreign import eventNamesImpl :: EventEmitter -> Array SymbolOrStr -foreign import data EventEmitter :: Emittable -> Handlers -> Type +foreign import data JsSymbol :: Type -type role EventEmitter nominal nominal +eventNames :: EventEmitter -> Array (Either JsSymbol String) +eventNames ee = map (\x -> runFn3 symbolOrStr Left Right x) $ eventNamesImpl ee -foreign import new :: Effect (EventEmitter CanEmit CanHandle) +foreign import symbolOrStr :: Fn3 (forall a. JsSymbol -> Either JsSymbol a) (forall b. String -> Either b String) SymbolOrStr (Either JsSymbol String) -asEmitterOnly :: forall handler. EventEmitter CanEmit handler -> EventEmitter NoEmit handler -asEmitterOnly = unsafeCoerce +foreign import getMaxListenersImpl :: EffectFn1 EventEmitter Int -asHandlerOnly :: forall emit. EventEmitter emit CanHandle -> EventEmitter emit NoHandle -asHandlerOnly = unsafeCoerce +-- | By default, an event emitter can only have a maximum of 10 listeners +-- | for a given event. +getMaxListeners :: EventEmitter -> Effect Int +getMaxListeners = runEffectFn1 getMaxListenersImpl -setMaxListeners :: forall a b. Int -> EventEmitter a b -> Effect Unit -setMaxListeners max emitter = runEffectFn2 setMaxListenersImpl emitter max - -setUnlimitedListeners :: forall a b. EventEmitter a b -> Effect Unit -setUnlimitedListeners = setMaxListeners 0 - -listenersLength :: forall a b. String -> EventEmitter a b -> Effect Int -listenersLength eventName emitter = runEffectFn2 listenersLengthImpl emitter eventName - -foreign import listenersLengthImpl :: forall a b. EffectFn2 (EventEmitter a b) String Int - -foreign import setMaxListenersImpl :: forall a b. EffectFn2 (EventEmitter a b) Int Unit +foreign import listenerCountImpl :: EffectFn2 EventEmitter String Int -foreign import onImpl :: forall a cb. EffectFn3 (EventEmitter a CanHandle) String cb cb -foreign import offImpl :: forall a cb. EffectFn3 (EventEmitter a CanHandle) String cb Unit +listenerCount :: EventEmitter -> String -> Effect Int +listenerCount emitter eventName = runEffectFn2 listenerCountImpl emitter eventName -class UnsafeOnEvent callbackFn callbackEffectFn | callbackFn -> callbackEffectFn where - unsafeOn :: forall a. String -> callbackFn -> (EventEmitter a CanHandle) -> Effect callbackEffectFn +foreign import setMaxListenersImpl :: EffectFn2 EventEmitter Int Unit -unsafeAddEventListener - :: forall a callbackFn callbackEffectFn - . UnsafeOnEvent callbackFn callbackEffectFn - => String - -> callbackFn - -> EventEmitter a CanHandle - -> Effect callbackEffectFn -unsafeAddEventListener = unsafeOn +setMaxListeners :: Int -> EventEmitter -> Effect Unit +setMaxListeners max emitter = runEffectFn2 setMaxListenersImpl emitter max -class UnsafeOffEvent callbackFn where - unsafeOff :: forall a. String -> callbackFn -> EventEmitter a CanHandle -> Effect Unit +setUnlimitedListeners :: EventEmitter -> Effect Unit +setUnlimitedListeners = setMaxListeners 0 -unsafeRemoveEventListener - :: forall a callbackFn - . UnsafeOffEvent callbackFn - => String - -> callbackFn - -> EventEmitter a CanHandle +-- | THIS IS UNSAFE! REALLY UNSAFE! +-- | Gets the `emit` function for a particular `EventEmitter`, so that one can call n-ary functions. +-- | +-- | Given `http2session.goaway([code[, lastStreamID[, opaqueData]]])` as an example... +-- | - https://nodejs.org/dist/latest-v18.x/docs/api/http2.html#event-goaway +-- | - https://nodejs.org/dist/latest-v18.x/docs/api/http2.html#http2sessiongoawaycode-laststreamid-opaquedata +-- | +-- | We can then write a single function that handles all four cases: +-- | ``` +-- | goAway +-- | :: Http2Session +-- | -> Maybe Code +-- | -> Maybe LastStreamId +-- | -> Maybe OpaqueData +-- | -> Effect Unit +-- | goAway h2s = case _, _, _ of +-- | Just c, Just id, Just d -> +-- | runEffectFn4 (unsafeEmitFn h2s :: EffectFn4 String Code LastStreamId OpaqueData Unit) "goaway" c id d +-- | Just c, Just id, Nothing -> +-- | -- If you're feeling lucky, omit the type annotations completely +-- | runEffectFn3 (unsafeEmitFn h2s) "goaway" c id +-- | Just c, Nothing, Nothing -> +-- | runEffectFn2 (unsafeEmitFn h2s :: EffectFn2 String Code LastStreamId Unit) "goaway" c +-- | _, _, _ -> +-- | runEffectFn1 (unsafeEmitFn h2s :: EffectFn1 String Unit) "goaway" +-- | ``` +-- | +-- | Synchronously calls each of the listeners registered for the event named `eventName`, +-- | in the order they were registered, passing the supplied arguments to each. +-- | Returns `true` if the event had listeners, `false` otherwise. +foreign import unsafeEmitFn :: forall f. EventEmitter -> f Boolean + +-- | Packs all the type information we need to call `on`/`once`/`prependListener`/`prependOnceListener` +-- | with the correct callback function type. +-- | +-- | Naming convention: If the name of an event is `foo`, +-- | the corresponding PureScript `EventHandle` value should be called `fooHandle`. +data EventHandle :: Type -> Type -> Type -> Type +data EventHandle emitterType pureScriptCallback javaScriptCallback = + EventHandle String (pureScriptCallback -> javaScriptCallback) + +type role EventHandle representational representational representational + +newListenerHandle :: EventHandle EventEmitter (Either JsSymbol String -> Effect Unit) (EffectFn1 SymbolOrStr Unit) +newListenerHandle = EventHandle "newListener" $ \cb -> mkEffectFn1 \jsSymbol -> + cb $ runFn3 symbolOrStr Left Right jsSymbol + +removeListenerHandle :: EventHandle EventEmitter (Either JsSymbol String -> Effect Unit) (EffectFn1 SymbolOrStr Unit) +removeListenerHandle = EventHandle "removeListener" $ \cb -> mkEffectFn1 \jsSymbol -> + cb $ runFn3 symbolOrStr Left Right jsSymbol + +-- | Adds the callback to the end of the `listeners` array and provides no way to remove it in the future. +-- | Intended usage: +-- | ``` +-- | on errorHandle eventEmitter \error -> do +-- | log $ "Got error: " <> Exception.message error +-- | ``` +on + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect Unit +on (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafeOn (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +-- | A variant of `on` that works for subtypes. If a value has type `Foo` +-- | and `Foo` is a class that extends `EventEmitter`, `Foo` can still use the `error` event. +-- | If we provide a proof that `Foo` can be converted back to an `EventEmitter`, then we can +-- | handle the `error` event as though the value that has type `Foo` had the type `EventEmitter`. +-- | +-- | Note: the proof function acts only as a witness of truth. It's not used to convert the +-- | value of type `Foo` to a value of type `EventEmitter`. +-- | +-- | Intended usage: +-- | ``` +-- | let proof = fooToEventEmitter eventEmitter +-- | onVia proof errorHandle eventEmitter \error -> do +-- | log $ "Got error: " <> Exception.message error +-- | ``` +onVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect Unit +onVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafeOn (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +once + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect Unit +once (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafeOnce (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +onceVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb -> Effect Unit -unsafeRemoveEventListener = unsafeOff - -unsafeSubscribe - :: forall a callbackFn callbackEffectFn - . UnsafeOnEvent callbackFn callbackEffectFn - => UnsafeOffEvent callbackEffectFn - => String - -> callbackFn - -> EventEmitter a CanHandle +onceVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafeOnce (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependListener + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect Unit +prependListener (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafePrependListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependListenerVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect Unit +prependListenerVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafePrependListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependOnceListener + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect Unit +prependOnceListener (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafePrependOnceListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependOnceListenerVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect Unit +prependOnceListenerVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafePrependOnceListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +-- | Internal function that ensures the JS callback function is the same one +-- | used when both adding it and removing it from the listeners array. +-- | Do not export this. +subscribeSameFunction + :: forall emitter jsCb + . EffectFn4 + (EffectFn3 emitter String jsCb Unit) + emitter + String + jsCb + (Effect Unit) +subscribeSameFunction = mkEffectFn4 \onXFn eventEmitter eventName jsCb -> do + runEffectFn3 onXFn (unsafeCoerce eventEmitter) eventName jsCb + pure $ runEffectFn3 unsafeOff (unsafeCoerce eventEmitter) eventName jsCb + +-- | A variant of `on` that returns a callback that will remove the listener from the event emitter's listeners array. +-- | Intended usage: +-- | ``` +-- | removeLoggerCallback <- onSubscribe errorHandle eventEmitter \error -> do +-- | log $ "Got error: " <> Exception.message error +-- | -- sometime later... +-- | removeLoggerCallback +-- | ``` +onSubscribe + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect (Effect Unit) +onSubscribe (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafeOn (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +-- | A variant of `on` that returns a callback that will remove the listener from the event emitter's listeners array. +-- | Intended usage: +-- | ``` +-- | removeLoggerCallback <- onSubscribe errorHandle eventEmitter \error -> do +-- | log $ "Got error: " <> Exception.message error +-- | -- sometime later... +-- | removeLoggerCallback +-- | ``` +onSubscribeVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect (Effect Unit) +onSubscribeVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafeOn (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +onceSubscribe + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect (Effect Unit) +onceSubscribe (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafeOnce (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +onceSubscribeVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect (Effect Unit) +onceSubscribeVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafeOnce (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependListenerSubscribe + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect (Effect Unit) +prependListenerSubscribe (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafePrependListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependListenerSubscribeVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect (Effect Unit) +prependListenerSubscribeVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafePrependListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependOnceListenerSubscribe + :: forall emitter psCb jsCb + . EventHandle emitter psCb jsCb + -> emitter + -> psCb + -> Effect (Effect Unit) +prependOnceListenerSubscribe (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafePrependOnceListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +prependOnceListenerSubscribeVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb -> Effect (Effect Unit) -unsafeSubscribe event cb emitter = do - cb' <- unsafeOn event cb emitter - pure $ unsafeOff event cb' emitter - -instance UnsafeOnEvent (EffectFn10 a b c d e f g h i j Unit) (EffectFn10 a b c d e f g h i j Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn9 a b c d e f g h i Unit) (EffectFn9 a b c d e f g h i Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn8 a b c d e f g h Unit) (EffectFn8 a b c d e f g h Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn7 a b c d e f g Unit) (EffectFn7 a b c d e f g Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn6 a b c d e f Unit) (EffectFn6 a b c d e f Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn5 a b c d e Unit) (EffectFn5 a b c d e Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn4 a b c d Unit) (EffectFn4 a b c d Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn3 a b c Unit) (EffectFn3 a b c Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn2 a b Unit) (EffectFn2 a b Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn -else instance UnsafeOnEvent (EffectFn1 a Unit) (EffectFn1 a Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn - -else instance UnsafeOnEvent (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Unit) (EffectFn10 a b c d e f g h i j Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn10 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Unit) (EffectFn9 a b c d e f g h i Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn9 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> d -> e -> f -> g -> h -> Effect Unit) (EffectFn8 a b c d e f g h Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn8 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> d -> e -> f -> g -> Effect Unit) (EffectFn7 a b c d e f g Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn7 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> d -> e -> f -> Effect Unit) (EffectFn6 a b c d e f Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn6 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> d -> e -> Effect Unit) (EffectFn5 a b c d e Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn5 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> d -> Effect Unit) (EffectFn4 a b c d Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn4 fn) emitter -else instance UnsafeOnEvent (a -> b -> c -> Effect Unit) (EffectFn3 a b c Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn3 fn) emitter -else instance UnsafeOnEvent (a -> b -> Effect Unit) (EffectFn2 a b Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn2 fn) emitter -else instance UnsafeOnEvent (a -> Effect Unit) (EffectFn1 a Unit) where - unsafeOn eventName fn emitter = unsafeOn eventName (mkEffectFn1 fn) emitter -else instance UnsafeOnEvent (Effect Unit) (Effect Unit) where - unsafeOn eventName fn emitter = runEffectFn3 onImpl emitter eventName fn - -instance UnsafeOffEvent (EffectFn10 a b c d e f g h i j Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn9 a b c d e f g h i Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn8 a b c d e f g h Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn7 a b c d e f g Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn6 a b c d e f Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn5 a b c d e Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn4 a b c d Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn3 a b c Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn2 a b Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (EffectFn1 a Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn -else instance UnsafeOffEvent (Effect Unit) where - unsafeOff eventName fn emitter = runEffectFn3 offImpl emitter eventName fn - -foreign import onceEventListener :: forall a cb. EffectFn3 (EventEmitter a CanHandle) String cb Unit - -class UnsafeOnceListener callbackFn where - unsafeOnce :: forall a. String -> callbackFn -> EventEmitter a CanHandle -> Effect Unit - -instance UnsafeOnceListener (EffectFn10 a b c d e f g h i j Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn9 a b c d e f g h i Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn8 a b c d e f g h Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn7 a b c d e f g Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn6 a b c d e f Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn5 a b c d e Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn4 a b c d Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn3 a b c Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn2 a b Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn -else instance UnsafeOnceListener (EffectFn1 a Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn - -else instance UnsafeOnceListener (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn10 fn) -else instance UnsafeOnceListener (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn9 fn) -else instance UnsafeOnceListener (a -> b -> c -> d -> e -> f -> g -> h -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn8 fn) -else instance UnsafeOnceListener (a -> b -> c -> d -> e -> f -> g -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn7 fn) -else instance UnsafeOnceListener (a -> b -> c -> d -> e -> f -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn6 fn) -else instance UnsafeOnceListener (a -> b -> c -> d -> e -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn5 fn) -else instance UnsafeOnceListener (a -> b -> c -> d -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn4 fn) -else instance UnsafeOnceListener (a -> b -> c -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn3 fn) -else instance UnsafeOnceListener (a -> b -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn2 fn) -else instance UnsafeOnceListener (a -> Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName (mkEffectFn1 fn) -else instance UnsafeOnceListener (Effect Unit) where - unsafeOnce eventName fn emitter = - runEffectFn3 onceEventListener emitter eventName fn - -foreign import undefined :: forall a. a - -foreign import emitImpl :: forall x a b c d e f g h i j. Fn2 (EventEmitter CanEmit x) String (EffectFn10 a b c d e f g h i j Boolean) - -class UnsafeEmit a where - unsafeEmit :: forall x. (EventEmitter CanEmit x) -> String -> a - -instance UnsafeEmit (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - a5 - a6 - a7 - a8 - a9 - a10 -else instance UnsafeEmit (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 a5 a6 a7 a8 a9 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - a5 - a6 - a7 - a8 - a9 - undefined -else instance UnsafeEmit (a -> b -> c -> d -> e -> f -> g -> h -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 a5 a6 a7 a8 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - a5 - a6 - a7 - a8 - undefined - undefined -else instance UnsafeEmit (a -> b -> c -> d -> e -> f -> g -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 a5 a6 a7 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - a5 - a6 - a7 - undefined - undefined - undefined -else instance UnsafeEmit (a -> b -> c -> d -> e -> f -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 a5 a6 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - a5 - a6 - undefined - undefined - undefined - undefined -else instance UnsafeEmit (a -> b -> c -> d -> e -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 a5 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - a5 - undefined - undefined - undefined - undefined - undefined -else instance UnsafeEmit (a -> b -> c -> d -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 a4 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - a4 - undefined - undefined - undefined - undefined - undefined - undefined -else instance UnsafeEmit (a -> b -> c -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 a3 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - a3 - undefined - undefined - undefined - undefined - undefined - undefined - undefined -else instance UnsafeEmit (a -> b -> Effect Boolean) where - unsafeEmit emitter eventName a1 a2 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - a2 - undefined - undefined - undefined - undefined - undefined - undefined - undefined - undefined -else instance UnsafeEmit (a -> Effect Boolean) where - unsafeEmit emitter eventName a1 = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - a1 - undefined - undefined - undefined - undefined - undefined - undefined - undefined - undefined - undefined -else instance UnsafeEmit (Effect Boolean) where - unsafeEmit emitter eventName = do - runEffectFn10 (runFn2 emitImpl emitter eventName) - undefined - undefined - undefined - undefined - undefined - undefined - undefined - undefined - undefined - undefined +prependOnceListenerSubscribeVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn4 subscribeSameFunction unsafePrependOnceListener (unsafeCoerce eventEmitter) eventName $ toJsCb psCb + +foreign import unsafeOn :: forall f. EffectFn3 EventEmitter String f Unit +foreign import unsafeOff :: forall f. EffectFn3 EventEmitter String f Unit +foreign import unsafeOnce :: forall f. EffectFn3 EventEmitter String f Unit +foreign import unsafePrependListener :: forall f. EffectFn3 EventEmitter String f Unit +foreign import unsafePrependOnceListener :: forall f. EffectFn3 EventEmitter String f Unit diff --git a/src/Node/EventEmitter/TypedEmitter.purs b/src/Node/EventEmitter/TypedEmitter.purs deleted file mode 100644 index 10ce3c9..0000000 --- a/src/Node/EventEmitter/TypedEmitter.purs +++ /dev/null @@ -1,218 +0,0 @@ -module Node.EventEmitter.TypedEmitter - ( TypedEmitter - , new - , asEmitterOnly - , asHandlerOnly - , setMaxListeners - , setUnlimitedListeners - , listenersLength - , on - , addEventListener - , off - , removeEventListener - , subscribe - , once - , withEmit - , emit - , class EmitterFunction - , class HandlerFunction - , module Exports - ) where - -import Prelude - -import Data.Symbol (class IsSymbol, reflectSymbol) -import Effect (Effect) -import Effect.Uncurried (EffectFn1, EffectFn10, EffectFn2, EffectFn3, EffectFn4, EffectFn5, EffectFn6, EffectFn7, EffectFn8, EffectFn9) -import Node.EventEmitter (class UnsafeEmit, class UnsafeOffEvent, class UnsafeOnEvent, class UnsafeOnceListener, CanEmit, CanHandle, Emittable, EventEmitter, Handlers, NoEmit, NoHandle, unsafeEmit, unsafeOff, unsafeOn, unsafeOnce, unsafeSubscribe) -import Node.EventEmitter (class UnsafeEmit, class UnsafeOffEvent, class UnsafeOnEvent, class UnsafeOnceListener, CanEmit, CanHandle, Emittable, Handlers, NoEmit, NoHandle) as Exports -import Node.EventEmitter as EventEmitter -import Prim.Row as Row -import Safe.Coerce (coerce) -import Type.Proxy (Proxy) - -newtype TypedEmitter :: Emittable -> Handlers -> Row Type -> Type -newtype TypedEmitter e h r = TypedEmitter (EventEmitter e h) - -type role TypedEmitter nominal nominal representational - -new - :: forall r all - . Proxy r - -> Effect (TypedEmitter CanEmit CanHandle all) -new _ = (coerce :: Effect (EventEmitter CanEmit CanHandle) -> Effect (TypedEmitter CanEmit CanHandle all)) EventEmitter.new - -asEmitterOnly :: forall handler all. TypedEmitter CanEmit handler all -> TypedEmitter NoEmit handler all -asEmitterOnly (TypedEmitter emitter) = TypedEmitter $ EventEmitter.asEmitterOnly emitter - -asHandlerOnly :: forall emit all. TypedEmitter emit CanHandle all -> TypedEmitter emit NoHandle all -asHandlerOnly (TypedEmitter emitter) = TypedEmitter $ EventEmitter.asHandlerOnly emitter - -setMaxListeners :: forall emit handler row. Int -> TypedEmitter emit handler row -> Effect Unit -setMaxListeners max (TypedEmitter emitter) = EventEmitter.setMaxListeners max emitter - -setUnlimitedListeners :: forall emit handler row. TypedEmitter emit handler row -> Effect Unit -setUnlimitedListeners (TypedEmitter emitter) = EventEmitter.setUnlimitedListeners emitter - -listenersLength - :: forall e h sym a tail row - . Row.Cons sym a tail row - => IsSymbol sym - => Proxy sym - -> TypedEmitter e h row - -> Effect Int -listenersLength _sym (TypedEmitter emitter) = EventEmitter.listenersLength (reflectSymbol _sym) emitter - -on - :: forall emit sym fn tail row effectFn - . Row.Cons sym fn tail row - => UnsafeOnEvent fn effectFn - => IsSymbol sym - => Proxy sym - -> fn - -> TypedEmitter emit CanHandle row - -> Effect effectFn -on _sym fn (TypedEmitter emitter) = unsafeOn (reflectSymbol _sym) fn emitter - -addEventListener - :: forall emit sym fn tail row effectFn - . Row.Cons sym fn tail row - => UnsafeOnEvent fn effectFn - => IsSymbol sym - => Proxy sym - -> fn - -> TypedEmitter emit CanHandle row - -> Effect effectFn -addEventListener = on - -off - :: forall emit sym fn tail row effectFn - . Row.Cons sym fn tail row - => HandlerFunction fn effectFn - => UnsafeOffEvent effectFn - => IsSymbol sym - => Proxy sym - -> effectFn - -> TypedEmitter emit CanHandle row - -> Effect Unit -off _sym fn (TypedEmitter emitter) = unsafeOff (reflectSymbol _sym) fn emitter - -removeEventListener - :: forall emit sym fn tail row effectFn - . Row.Cons sym fn tail row - => HandlerFunction fn effectFn - => UnsafeOffEvent effectFn - => IsSymbol sym - => Proxy sym - -> effectFn - -> TypedEmitter emit CanHandle row - -> Effect Unit -removeEventListener = off - -subscribe - :: forall emit sym fn tail row effectFn - . Row.Cons sym fn tail row - => UnsafeOnEvent fn effectFn - => UnsafeOffEvent effectFn - => IsSymbol sym - => Proxy sym - -> fn - -> TypedEmitter emit CanHandle row - -> Effect (Effect Unit) -subscribe _sym fn (TypedEmitter emitter) = unsafeSubscribe (reflectSymbol _sym) fn emitter - -once - :: forall emit sym fn tail row - . Row.Cons sym fn tail row - => UnsafeOnceListener fn - => IsSymbol sym - => Proxy sym - -> fn - -> TypedEmitter emit CanHandle row - -> Effect Unit -once _sym fn (TypedEmitter emitter) = unsafeOnce (reflectSymbol _sym) fn emitter - --- | Helps with type inference --- | due to the overloaded nature of `emit`. -withEmit :: Effect Boolean -> Effect Boolean -withEmit = identity - --- | Since the return type is polymorphic to allow one to --- | easily add as many arguments as are needed --- | using the same function, --- | type inference suffers. To workaround that --- | issue, precede all calls with `withEmit`. --- | For example --- | ``` --- | b <- withEmit $ emit (Proxy :: _ "eventName") emitter arg1 arg2 arg3 --- | void $ withEmit $ emit (Proxy :: _ "eventName") emitter arg1 arg2 arg3 --- | ``` --- | Otherwise, you'll have to write this to deal with compiler errors: --- | ``` --- | (_ :: Boolean) <- emit (Proxy :: _ "eventName") emitter arg1 arg2 arg3 --- | map (\(_ :: Boolean) -> unit) $ emit (Proxy :: _ "eventName") emitter arg1 arg2 arg3 --- | ``` -emit - :: forall handler sym fn tail row argsThenEffectBoolean - . Row.Cons sym fn tail row - => EmitterFunction fn argsThenEffectBoolean - => UnsafeEmit argsThenEffectBoolean - => IsSymbol sym - => Proxy sym - -> TypedEmitter CanEmit handler row - -> argsThenEffectBoolean -emit _sym (TypedEmitter emitter) = unsafeEmit emitter (reflectSymbol _sym) - --- | Determines the number and type of args to pass into the `emit` function --- | based on the number and type of args received by the callback type --- | associated with that label in the row. -class EmitterFunction :: Type -> Type -> Constraint -class EmitterFunction callbackFn emitterArgs | callbackFn -> emitterArgs - -instance EmitterFunction (EffectFn10 a b c d e f g h i j Unit) (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Boolean) -else instance EmitterFunction (EffectFn9 a b c d e f g h i Unit) (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Boolean) -else instance EmitterFunction (EffectFn8 a b c d e f g h Unit) (a -> b -> c -> d -> e -> f -> g -> h -> Effect Boolean) -else instance EmitterFunction (EffectFn7 a b c d e f g Unit) (a -> b -> c -> d -> e -> f -> g -> Effect Boolean) -else instance EmitterFunction (EffectFn6 a b c d e f Unit) (a -> b -> c -> d -> e -> f -> Effect Boolean) -else instance EmitterFunction (EffectFn5 a b c d e Unit) (a -> b -> c -> d -> e -> Effect Boolean) -else instance EmitterFunction (EffectFn4 a b c d Unit) (a -> b -> c -> d -> Effect Boolean) -else instance EmitterFunction (EffectFn3 a b c Unit) (a -> b -> c -> Effect Boolean) -else instance EmitterFunction (EffectFn2 a b Unit) (a -> b -> Effect Boolean) -else instance EmitterFunction (EffectFn1 a Unit) (a -> Effect Boolean) - -else instance EmitterFunction (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Unit) (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Unit) (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> d -> e -> f -> g -> h -> Effect Unit) (a -> b -> c -> d -> e -> f -> g -> h -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> d -> e -> f -> g -> Effect Unit) (a -> b -> c -> d -> e -> f -> g -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> d -> e -> f -> Effect Unit) (a -> b -> c -> d -> e -> f -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> d -> e -> Effect Unit) (a -> b -> c -> d -> e -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> d -> Effect Unit) (a -> b -> c -> d -> Effect Boolean) -else instance EmitterFunction (a -> b -> c -> Effect Unit) (a -> b -> c -> Effect Boolean) -else instance EmitterFunction (a -> b -> Effect Unit) (a -> b -> Effect Boolean) -else instance EmitterFunction (a -> Effect Unit) (a -> Effect Boolean) -else instance EmitterFunction (Effect Unit) (Effect Boolean) - -class HandlerFunction :: Type -> Type -> Constraint -class HandlerFunction callbackFn handlerFn | callbackFn -> handlerFn - -instance HandlerFunction (EffectFn10 a b c d e f g h i j Unit) (EffectFn10 a b c d e f g h i j Unit) -else instance HandlerFunction (EffectFn9 a b c d e f g h i Unit) (EffectFn9 a b c d e f g h i Unit) -else instance HandlerFunction (EffectFn8 a b c d e f g h Unit) (EffectFn8 a b c d e f g h Unit) -else instance HandlerFunction (EffectFn7 a b c d e f g Unit) (EffectFn7 a b c d e f g Unit) -else instance HandlerFunction (EffectFn6 a b c d e f Unit) (EffectFn6 a b c d e f Unit) -else instance HandlerFunction (EffectFn5 a b c d e Unit) (EffectFn5 a b c d e Unit) -else instance HandlerFunction (EffectFn4 a b c d Unit) (EffectFn4 a b c d Unit) -else instance HandlerFunction (EffectFn3 a b c Unit) (EffectFn3 a b c Unit) -else instance HandlerFunction (EffectFn2 a b Unit) (EffectFn2 a b Unit) -else instance HandlerFunction (EffectFn1 a Unit) (EffectFn1 a Unit) -else instance HandlerFunction (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Effect Boolean) (EffectFn10 a b c d e f g h i j Unit) -else instance HandlerFunction (a -> b -> c -> d -> e -> f -> g -> h -> i -> Effect Boolean) (EffectFn9 a b c d e f g h i Unit) -else instance HandlerFunction (a -> b -> c -> d -> e -> f -> g -> h -> Effect Boolean) (EffectFn8 a b c d e f g h Unit) -else instance HandlerFunction (a -> b -> c -> d -> e -> f -> g -> Effect Boolean) (EffectFn7 a b c d e f g Unit) -else instance HandlerFunction (a -> b -> c -> d -> e -> f -> Effect Boolean) (EffectFn6 a b c d e f Unit) -else instance HandlerFunction (a -> b -> c -> d -> e -> Effect Boolean) (EffectFn5 a b c d e Unit) -else instance HandlerFunction (a -> b -> c -> d -> Effect Boolean) (EffectFn4 a b c d Unit) -else instance HandlerFunction (a -> b -> c -> Effect Boolean) (EffectFn3 a b c Unit) -else instance HandlerFunction (a -> b -> Effect Boolean) (EffectFn2 a b Unit) -else instance HandlerFunction (a -> Effect Boolean) (EffectFn1 a Unit) -else instance HandlerFunction (Effect Unit) (Effect Unit) diff --git a/src/Node/EventEmitter/UtilTypes.purs b/src/Node/EventEmitter/UtilTypes.purs new file mode 100644 index 0000000..cf54295 --- /dev/null +++ b/src/Node/EventEmitter/UtilTypes.purs @@ -0,0 +1,13 @@ +module Node.EventEmitter.UtilTypes where + +import Prelude + +import Effect (Effect) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4) +import Node.EventEmitter (EventHandle) + +type EventHandle0 eventEmitter = EventHandle eventEmitter (Effect Unit) (Effect Unit) +type EventHandle1 eventEmitter a = EventHandle eventEmitter (a -> Effect Unit) (EffectFn1 a Unit) +type EventHandle2 eventEmitter a b = EventHandle eventEmitter (a -> b -> Effect Unit) (EffectFn2 a b Unit) +type EventHandle3 eventEmitter a b c = EventHandle eventEmitter (a -> b -> c -> Effect Unit) (EffectFn3 a b c Unit) +type EventHandle4 eventEmitter a b c d = EventHandle eventEmitter (a -> b -> c -> d -> Effect Unit) (EffectFn4 a b c d Unit) diff --git a/test.dhall b/test.dhall new file mode 100644 index 0000000..fbceb40 --- /dev/null +++ b/test.dhall @@ -0,0 +1,16 @@ +{ name = "node-event-emitters-test" +, dependencies = + [ "aff" + , "effect" + , "either" + , "foldable-traversable" + , "functions" + , "prelude" + , "refs" + , "spec" + , "tuples" + , "unsafe-coerce" + ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/Test/Main.purs b/test/Test/Main.purs new file mode 100644 index 0000000..a5836b3 --- /dev/null +++ b/test/Test/Main.purs @@ -0,0 +1,13 @@ +module Test.Main where + +import Prelude + +import Effect (Effect) +import Effect.Aff (launchAff_) +import Test.Node.EventEmitter as EventEmitter +import Test.Spec.Reporter (consoleReporter) +import Test.Spec.Runner (runSpec) + +main :: Effect Unit +main = launchAff_ $ runSpec [ consoleReporter ] do + EventEmitter.spec diff --git a/test/Test/Node/EventEmitter.purs b/test/Test/Node/EventEmitter.purs new file mode 100644 index 0000000..adafc2a --- /dev/null +++ b/test/Test/Node/EventEmitter.purs @@ -0,0 +1,80 @@ +module Test.Node.EventEmitter where + +import Prelude + +import Data.Foldable (for_) +import Data.Tuple.Nested ((/\)) +import Effect.Class (liftEffect) +import Effect.Ref as Ref +import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, runEffectFn1, runEffectFn2, runEffectFn3) +import Node.EventEmitter (EventEmitter, EventHandle(..), on, onSubscribe, once, onceSubscribe, prependListener, prependListenerSubscribe, prependOnceListener, prependOnceListenerSubscribe, unsafeEmitFn) +import Node.EventEmitter as EventEmitter +import Node.EventEmitter.UtilTypes (EventHandle1) +import Test.Spec (Spec, describe, it) +import Test.Spec.Assertions (shouldEqual) + +fooHandle :: EventHandle1 EventEmitter String +fooHandle = EventHandle "foo" mkEffectFn1 + +spec :: Spec Unit +spec = describe "event-emitter" do + it "`new` does not throw" do + liftEffect $ void $ EventEmitter.new + it "`emit` does not throw" do + liftEffect do + ee <- EventEmitter.new + void $ runEffectFn1 (unsafeEmitFn ee :: EffectFn1 String Boolean) "foo" + void $ runEffectFn2 (unsafeEmitFn ee :: EffectFn2 String String Boolean) "foo" "baz" + void $ runEffectFn1 (unsafeEmitFn ee) "foo" + void $ runEffectFn2 (unsafeEmitFn ee) "foo" "bar" + void $ runEffectFn3 (unsafeEmitFn ee) "foo" "bar" "baz" + describe "standard functions" do + let + fns = + [ "on" /\ on + , "once" /\ once + , "prependListener" /\ prependListener + , "prependOnceListener" /\ prependOnceListener + ] + for_ fns \(fnName /\ fn) -> do + it (fnName <> " works") do + liftEffect do + let expected = "bar" + ref <- Ref.new "" + ee <- EventEmitter.new + fn fooHandle ee \val -> do + Ref.write val ref + void $ runEffectFn2 (unsafeEmitFn ee) "foo" expected + val <- Ref.read ref + val `shouldEqual` expected + + describe "subscribe functions" do + let + fns = + [ "onSubscribe" /\ onSubscribe + , "onceSubscribe" /\ onceSubscribe + , "prependListenerSubscribe" /\ prependListenerSubscribe + , "prependOnceListenerSubscribe" /\ prependOnceListenerSubscribe + ] + for_ fns \(fnName /\ fn) -> do + it (fnName <> " - normal call works") do + liftEffect do + let expected = "bar" + ref <- Ref.new "" + ee <- EventEmitter.new + void $ fn fooHandle ee \val -> do + Ref.write val ref + void $ runEffectFn2 (unsafeEmitFn ee) "foo" expected + val <- Ref.read ref + val `shouldEqual` expected + for_ fns \(fnName /\ fn) -> do + it (fnName <> " - unsubscribing before call works") do + liftEffect do + ref <- Ref.new "" + ee <- EventEmitter.new + remove <- fn fooHandle ee \val -> do + Ref.write val ref + remove + void $ runEffectFn2 (unsafeEmitFn ee) "foo" "bar" + val <- Ref.read ref + val `shouldEqual` ""