From 6e16f30b1448397d8c4f3ffad82d47366361b744 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Fri, 9 Jun 2023 15:20:13 -0500 Subject: [PATCH 01/13] Rewrite library: allow usages via unsafe calls --- .github/workflows/ci.yml | 52 ++- .gitignore | 1 + spago.dhall | 7 +- src/Node/EventEmitter.js | 47 +-- src/Node/EventEmitter.purs | 483 ++++++------------------ src/Node/EventEmitter/TypedEmitter.purs | 218 ----------- test.dhall | 18 + test/Test/Main.purs | 13 + test/Test/Node/EventEmitter.purs | 114 ++++++ 9 files changed, 321 insertions(+), 632 deletions(-) delete mode 100644 src/Node/EventEmitter/TypedEmitter.purs create mode 100644 test.dhall create mode 100644 test/Test/Main.purs create mode 100644 test/Test/Node/EventEmitter.purs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 175edcb..cd0d749 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,53 +1,69 @@ 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: Install NPM dependencies + run: npm install - 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 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..9e333ed 100644 --- a/spago.dhall +++ b/spago.dhall @@ -1,11 +1,6 @@ { name = "node-event-emitters" , dependencies = - [ "effect" - , "functions" - , "prelude" - , "safe-coerce" - , "unsafe-coerce" - ] + [ "effect", "either", "functions", "prelude" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs" ] } diff --git a/src/Node/EventEmitter.js b/src/Node/EventEmitter.js index ac668e2..6a00ba3 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 strOrSymbol = (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..9e0aa45 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -1,370 +1,131 @@ -module Node.EventEmitter where +module Node.EventEmitter + ( EventEmitter + , new + , unsafeEmitFn + , JsSymbol + , eventNames + , getMaxListeners + , listenerCount + , unsafeOff + , unsafeOn + , unsafeSubscribe + , unsafePrependListener + , unsafePrependSubscribe + , unsafePrependOnceListener + , setMaxListeners + , setUnlimitedListeners + ) 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 Unsafe.Coerce (unsafeCoerce) - -data Handlers - -foreign import data CanHandle :: Handlers -foreign import data NoHandle :: Handlers - -data Emittable - -foreign import data CanEmit :: Emittable -foreign import data NoEmit :: Emittable - -foreign import data EventEmitter :: Emittable -> Handlers -> Type - -type role EventEmitter nominal nominal - -foreign import new :: Effect (EventEmitter CanEmit CanHandle) - -asEmitterOnly :: forall handler. EventEmitter CanEmit handler -> EventEmitter NoEmit handler -asEmitterOnly = unsafeCoerce - -asHandlerOnly :: forall emit. EventEmitter emit CanHandle -> EventEmitter emit NoHandle -asHandlerOnly = unsafeCoerce - -setMaxListeners :: forall a b. Int -> EventEmitter a b -> Effect Unit +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, runEffectFn1, runEffectFn2, runEffectFn3) + +foreign import data EventEmitter :: Type + +-- | Create a new event emitter +foreign import new :: Effect EventEmitter + +-- | THIS IS UNSAFE! REALLY UNSAFE! +-- | Gets the `emit` function for a particular `EventEmitter`. +-- | Intended usage is to prevent the need to write redundant FFI: +-- | +-- | `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 -> +-- | runEffectFn3 (unsafeEmitFn h2s :: EffectFn3 String Code LastStreamId Unit) "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 + +-- | THIS IS UNSAFE! REALLY UNSAFE! +-- | Intended usage is to prevent the need to write redundant FFI: +-- | +-- | `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 type-safe idiomatic function that handles all four cases: +-- | ``` +-- | onGoAway +-- | :: Http2Session +-- | -> (Maybe Code -> Maybe LastStreamId -> Maybe OpaqueData -> Effect Unit) +-- | -> Effect Unit +-- | onGoAway h2s cb = +-- | runEffectFn3 +-- | -- This could be written +-- | -- (unsafeOn :: _ _ _ (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) Unit +-- | (unsafeOn :: EffectFn3 EventEmitter String (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) Unit +-- | h2s +-- | "goaway" +-- | (mkEffectFn3 \c id d -> cb (toMaybe c) (toMaybe id) (toMaybe d)) +-- | ``` +foreign import unsafeOn :: forall f. EffectFn3 EventEmitter String f EventEmitter + +foreign import unsafeOff :: forall f. EffectFn3 EventEmitter String f EventEmitter + +unsafeSubscribe :: forall f. EventEmitter -> String -> f -> Effect (Effect Unit) +unsafeSubscribe ee eventName f = do + _ <- runEffectFn3 unsafeOn ee eventName f + pure $ void $ runEffectFn3 unsafeOff ee eventName f + +foreign import unsafeOnce :: forall f. EffectFn3 EventEmitter String f EventEmitter +foreign import unsafePrependListener :: forall f. EffectFn3 EventEmitter String f EventEmitter + +unsafePrependSubscribe :: forall f. EventEmitter -> String -> f -> Effect (Effect Unit) +unsafePrependSubscribe ee eventName f = do + _ <- runEffectFn3 unsafePrependListener ee eventName f + pure $ void $ runEffectFn3 unsafeOff ee eventName f + +foreign import unsafePrependOnceListener :: forall f. EffectFn3 EventEmitter String f EventEmitter + +foreign import data StrOrSymbol :: Type + +foreign import eventNamesImpl :: EventEmitter -> Array StrOrSymbol + +foreign import data JsSymbol :: Type + +eventNames :: EventEmitter -> Array (Either JsSymbol String) +eventNames ee = map (\x -> runFn3 strOrSymbol Left Right x) $ eventNamesImpl ee + +foreign import strOrSymbol :: Fn3 (forall a. JsSymbol -> Either JsSymbol a) (forall b. String -> Either b String) StrOrSymbol (Either JsSymbol String) + +foreign import getMaxListenersImpl :: EffectFn1 EventEmitter Int + +getMaxListeners :: EventEmitter -> Effect Int +getMaxListeners = runEffectFn1 getMaxListenersImpl + +foreign import listenerCountImpl :: EffectFn2 EventEmitter String Int + +listenerCount :: EventEmitter -> String -> Effect Int +listenerCount = runEffectFn2 listenerCountImpl + +setMaxListeners :: Int -> EventEmitter -> Effect Unit setMaxListeners max emitter = runEffectFn2 setMaxListenersImpl emitter max -setUnlimitedListeners :: forall a b. EventEmitter a b -> Effect Unit +setUnlimitedListeners :: EventEmitter -> 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 onImpl :: forall a cb. EffectFn3 (EventEmitter a CanHandle) String cb cb -foreign import offImpl :: forall a cb. EffectFn3 (EventEmitter a CanHandle) String cb Unit - -class UnsafeOnEvent callbackFn callbackEffectFn | callbackFn -> callbackEffectFn where - unsafeOn :: forall a. String -> callbackFn -> (EventEmitter a CanHandle) -> Effect callbackEffectFn - -unsafeAddEventListener - :: forall a callbackFn callbackEffectFn - . UnsafeOnEvent callbackFn callbackEffectFn - => String - -> callbackFn - -> EventEmitter a CanHandle - -> Effect callbackEffectFn -unsafeAddEventListener = unsafeOn - -class UnsafeOffEvent callbackFn where - unsafeOff :: forall a. String -> callbackFn -> EventEmitter a CanHandle -> Effect Unit - -unsafeRemoveEventListener - :: forall a callbackFn - . UnsafeOffEvent callbackFn - => String - -> callbackFn - -> EventEmitter a CanHandle - -> Effect Unit -unsafeRemoveEventListener = unsafeOff - -unsafeSubscribe - :: forall a callbackFn callbackEffectFn - . UnsafeOnEvent callbackFn callbackEffectFn - => UnsafeOffEvent callbackEffectFn - => String - -> callbackFn - -> EventEmitter a CanHandle - -> 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 +foreign import setMaxListenersImpl :: EffectFn2 (EventEmitter) Int Unit -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 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/test.dhall b/test.dhall new file mode 100644 index 0000000..8e4b364 --- /dev/null +++ b/test.dhall @@ -0,0 +1,18 @@ +{ name = "node-event-emitters-test" +, dependencies = + [ "aff" + , "arrays" + , "control" + , "effect" + , "either" + , "foldable-traversable" + , "functions" + , "maybe" + , "nullable" + , "prelude" + , "refs" + , "spec" + ] +, 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..1b410e7 --- /dev/null +++ b/test/Test/Node/EventEmitter.purs @@ -0,0 +1,114 @@ +module Test.Node.EventEmitter where + +import Prelude + +import Control.Alternative (guard) +import Data.Array as Array +import Data.Foldable (fold, for_, foldMap) +import Data.Maybe (Maybe(..)) +import Data.Nullable (Nullable, toMaybe) +import Effect (Effect) +import Effect.Class (liftEffect) +import Effect.Ref as Ref +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn3, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4) +import Node.EventEmitter (EventEmitter, unsafeEmitFn, unsafeOn, unsafeSubscribe) +import Node.EventEmitter as EventEmitter +import Test.Spec (Spec, describe, it) +import Test.Spec.Assertions (shouldEqual) + +spec :: Spec Unit +spec = describe "event-emitter" do + it "init works" do + liftEffect $ void $ EventEmitter.new + it "emit works" do + liftEffect do + ee <- EventEmitter.new + void $ runEffectFn1 (unsafeEmitFn ee :: EffectFn1 String Boolean) "foo" + it "handle works" do + liftEffect do + ee <- EventEmitter.new + void $ runEffectFn3 unsafeOn ee "foo" $ mkEffectFn1 \_ -> pure unit + void $ runEffectFn1 (unsafeEmitFn ee) "foo" + + it "subscribe works" do + let expected = "aaa" + actual <- liftEffect do + ref <- Ref.new "" + ee <- EventEmitter.new + unsub <- unsafeSubscribe ee "foo" $ mkEffectFn1 \s -> Ref.modify_ (\r -> r <> s) ref + void $ runEffectFn2 (unsafeEmitFn ee) "foo" expected + unsub + void $ runEffectFn2 (unsafeEmitFn ee) "foo" "bbb" + Ref.read ref + expected `shouldEqual` actual + + let + i = 20 + s = "str" + arr = [ "foo", "bar" ] + for_ [ 0, 1, 2, 3 ] \argAmt -> do + it ("emit/handle roundtrips on " <> show argAmt <> " args") do + ref <- liftEffect $ Ref.new "" + ee <- liftEffect $ EventEmitter.new + let + expected = fold $ Array.take argAmt + [ show i + , show s + , show arr + ] + liftEffect $ handleVArgs ee \a b c -> do + let + actual = fold + [ foldMap show a + , foldMap show b + , foldMap show c + ] + Ref.write actual ref + liftEffect $ void $ emitVArgs ee + (i <$ guard (argAmt >= 1)) + (s <$ guard (argAmt >= 2)) + (arr <$ guard (argAmt >= 3)) + actual' <- liftEffect $ Ref.read ref + expected `shouldEqual` actual' + +eventName = "someEvent" :: String + +emitVArgs + :: EventEmitter + -> Maybe Int + -> Maybe String + -> Maybe (Array String) + -> Effect Boolean +emitVArgs ee = case _, _, _ of + Just i, Just s, Just a -> + runEffectFn4 (unsafeEmitFn ee :: EffectFn4 String Int String (Array String) Boolean) eventName i s a + Just i, Just s, Nothing -> + runEffectFn3 (unsafeEmitFn ee :: EffectFn3 String Int String Boolean) eventName i s + Just i, Nothing, Nothing -> + runEffectFn2 (unsafeEmitFn ee :: EffectFn2 String Int Boolean) eventName i + _, _, _ -> + runEffectFn1 (unsafeEmitFn ee :: EffectFn1 String Boolean) eventName + +handleVArgs + :: EventEmitter + -> (Maybe Int -> Maybe String -> Maybe (Array String) -> Effect Unit) + -> Effect Unit +handleVArgs ee cb = void do + runEffectFn3 + -- This could be written + -- (unsafeOn :: _ _ _ (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) Unit + ( unsafeOn + :: EffectFn3 + EventEmitter + String + ( EffectFn3 + (Nullable Int) + (Nullable String) + (Nullable (Array String)) + Unit + ) + EventEmitter + ) + ee + eventName + (mkEffectFn3 \i s a -> cb (toMaybe i) (toMaybe s) (toMaybe a)) From afb6e6e1f1396c8da6974d6a892191354022f684 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Fri, 9 Jun 2023 15:25:18 -0500 Subject: [PATCH 02/13] Verify type inference works --- test/Test/Node/EventEmitter.purs | 111 +++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 26 deletions(-) diff --git a/test/Test/Node/EventEmitter.purs b/test/Test/Node/EventEmitter.purs index 1b410e7..ad6b391 100644 --- a/test/Test/Node/EventEmitter.purs +++ b/test/Test/Node/EventEmitter.purs @@ -42,34 +42,65 @@ spec = describe "event-emitter" do Ref.read ref expected `shouldEqual` actual - let - i = 20 - s = "str" - arr = [ "foo", "bar" ] - for_ [ 0, 1, 2, 3 ] \argAmt -> do - it ("emit/handle roundtrips on " <> show argAmt <> " args") do - ref <- liftEffect $ Ref.new "" - ee <- liftEffect $ EventEmitter.new - let - expected = fold $ Array.take argAmt - [ show i - , show s - , show arr - ] - liftEffect $ handleVArgs ee \a b c -> do + describe "explicit types" do + let + i = 20 + s = "str" + arr = [ "foo", "bar" ] + for_ [ 0, 1, 2, 3 ] \argAmt -> do + it ("emit/handle roundtrips on " <> show argAmt <> " args") do + ref <- liftEffect $ Ref.new "" + ee <- liftEffect $ EventEmitter.new let - actual = fold - [ foldMap show a - , foldMap show b - , foldMap show c + expected = fold $ Array.take argAmt + [ show i + , show s + , show arr ] - Ref.write actual ref - liftEffect $ void $ emitVArgs ee - (i <$ guard (argAmt >= 1)) - (s <$ guard (argAmt >= 2)) - (arr <$ guard (argAmt >= 3)) - actual' <- liftEffect $ Ref.read ref - expected `shouldEqual` actual' + liftEffect $ handleVArgs ee \a b c -> do + let + actual = fold + [ foldMap show a + , foldMap show b + , foldMap show c + ] + Ref.write actual ref + liftEffect $ void $ emitVArgs ee + (i <$ guard (argAmt >= 1)) + (s <$ guard (argAmt >= 2)) + (arr <$ guard (argAmt >= 3)) + actual' <- liftEffect $ Ref.read ref + expected `shouldEqual` actual' + + describe "types inferred" do + let + i = 20 + s = "str" + arr = [ "foo", "bar" ] + for_ [ 0, 1, 2, 3 ] \argAmt -> do + it ("emit/handle roundtrips on " <> show argAmt <> " args - verify type inference") do + ref <- liftEffect $ Ref.new "" + ee <- liftEffect $ EventEmitter.new + let + expected = fold $ Array.take argAmt + [ show i + , show s + , show arr + ] + liftEffect $ handleVArgs_checkTypeInference ee \a b c -> do + let + actual = fold + [ foldMap show a + , foldMap show b + , foldMap show c + ] + Ref.write actual ref + liftEffect $ void $ emitVArgs_checkTypeInference ee + (i <$ guard (argAmt >= 1)) + (s <$ guard (argAmt >= 2)) + (arr <$ guard (argAmt >= 3)) + actual' <- liftEffect $ Ref.read ref + expected `shouldEqual` actual' eventName = "someEvent" :: String @@ -112,3 +143,31 @@ handleVArgs ee cb = void do ee eventName (mkEffectFn3 \i s a -> cb (toMaybe i) (toMaybe s) (toMaybe a)) + +-- Haha! Type inference works on this monstrosity. +emitVArgs_checkTypeInference + :: EventEmitter + -> Maybe Int + -> Maybe String + -> Maybe (Array String) + -> Effect Boolean +emitVArgs_checkTypeInference ee = case _, _, _ of + Just i, Just s, Just a -> + runEffectFn4 (unsafeEmitFn ee) eventName i s a + Just i, Just s, Nothing -> + runEffectFn3 (unsafeEmitFn ee) eventName i s + Just i, Nothing, Nothing -> + runEffectFn2 (unsafeEmitFn ee) eventName i + _, _, _ -> + runEffectFn1 (unsafeEmitFn ee) eventName + +handleVArgs_checkTypeInference + :: EventEmitter + -> (Maybe Int -> Maybe String -> Maybe (Array String) -> Effect Unit) + -> Effect Unit +handleVArgs_checkTypeInference ee cb = void do + runEffectFn3 + unsafeOn + ee + eventName + (mkEffectFn3 \i s a -> cb (toMaybe i) (toMaybe s) (toMaybe a)) From 06a508849d428fa863a5f19d3fada252ffae8566 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Fri, 9 Jun 2023 15:38:23 -0500 Subject: [PATCH 03/13] Update docs around type annotations --- src/Node/EventEmitter.purs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index 9e0aa45..787b68a 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -48,7 +48,8 @@ foreign import new :: Effect EventEmitter -- | Just c, Just id, Just d -> -- | runEffectFn4 (unsafeEmitFn h2s :: EffectFn4 String Code LastStreamId OpaqueData Unit) "goaway" c id d -- | Just c, Just id, Nothing -> --- | runEffectFn3 (unsafeEmitFn h2s :: EffectFn3 String Code LastStreamId Unit) "goaway" c id +-- | -- 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 -- | _, _, _ -> @@ -71,13 +72,12 @@ foreign import unsafeEmitFn :: forall f. EventEmitter -> f Boolean -- | onGoAway -- | :: Http2Session -- | -> (Maybe Code -> Maybe LastStreamId -> Maybe OpaqueData -> Effect Unit) --- | -> Effect Unit --- | onGoAway h2s cb = +-- | -> Effect Void +-- | onGoAway h2s cb = void $ -- | runEffectFn3 --- | -- This could be written --- | -- (unsafeOn :: _ _ _ (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) Unit --- | (unsafeOn :: EffectFn3 EventEmitter String (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) Unit --- | h2s +-- | -- If you're feeling lucky, omit the type annotations on `unsafeOn` completely. +-- | (unsafeOn :: EffectFn3 EventEmitter String (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) EventEmitter +-- | (unsafeCoerce h2s :: EventEmitter) -- | "goaway" -- | (mkEffectFn3 \c id d -> cb (toMaybe c) (toMaybe id) (toMaybe d)) -- | ``` From 287077c8310fe262a3ace9578c579fe7682e8a4a Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Tue, 13 Jun 2023 17:08:25 -0500 Subject: [PATCH 04/13] Drop EventEmitter return type This JS pattern isn't useful in PS most of the time --- src/Node/EventEmitter.purs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index 787b68a..55fb486 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -81,22 +81,22 @@ foreign import unsafeEmitFn :: forall f. EventEmitter -> f Boolean -- | "goaway" -- | (mkEffectFn3 \c id d -> cb (toMaybe c) (toMaybe id) (toMaybe d)) -- | ``` -foreign import unsafeOn :: forall f. EffectFn3 EventEmitter String f EventEmitter +foreign import unsafeOn :: forall f. EffectFn3 EventEmitter String f Unit -foreign import unsafeOff :: forall f. EffectFn3 EventEmitter String f EventEmitter +foreign import unsafeOff :: forall f. EffectFn3 EventEmitter String f Unit unsafeSubscribe :: forall f. EventEmitter -> String -> f -> Effect (Effect Unit) unsafeSubscribe ee eventName f = do - _ <- runEffectFn3 unsafeOn ee eventName f - pure $ void $ runEffectFn3 unsafeOff ee eventName f + runEffectFn3 unsafeOn ee eventName f + pure $ runEffectFn3 unsafeOff ee eventName f -foreign import unsafeOnce :: forall f. EffectFn3 EventEmitter String f EventEmitter -foreign import unsafePrependListener :: forall f. EffectFn3 EventEmitter String f EventEmitter +foreign import unsafeOnce :: forall f. EffectFn3 EventEmitter String f Unit +foreign import unsafePrependListener :: forall f. EffectFn3 EventEmitter String f Unit unsafePrependSubscribe :: forall f. EventEmitter -> String -> f -> Effect (Effect Unit) unsafePrependSubscribe ee eventName f = do - _ <- runEffectFn3 unsafePrependListener ee eventName f - pure $ void $ runEffectFn3 unsafeOff ee eventName f + runEffectFn3 unsafePrependListener ee eventName f + pure $ runEffectFn3 unsafeOff ee eventName f foreign import unsafePrependOnceListener :: forall f. EffectFn3 EventEmitter String f EventEmitter From e97a50f5b499dcae33feeccfd6ca4bb29b77ee4a Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 10:37:02 -0500 Subject: [PATCH 05/13] Use HandleEvent-based approach --- src/Node/EventEmitter.purs | 367 +++++++++++++++++++++++++++++++------ 1 file changed, 306 insertions(+), 61 deletions(-) diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index 55fb486..92e215c 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -1,19 +1,76 @@ +-- | ## 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 - , unsafeEmitFn , JsSymbol , eventNames , getMaxListeners , listenerCount - , unsafeOff - , unsafeOn - , unsafeSubscribe - , unsafePrependListener - , unsafePrependSubscribe - , unsafePrependOnceListener , setMaxListeners , setUnlimitedListeners + , unsafeEmitFn + , EventHandle + , errorHandle + , on + , onVia + , onSubscribe + , onSubscribeVia + , once + , onceVia + , onceSubscribe + , onceSubscribeVia + , prependListener + , prependListenerVia + , prependListenerSubscribe + , prependListenerSubscribeVia + , prependOnceListener + , prependOnceListenerVia + , prependOnceListenerSubscribe + , prependOnceListenerSubscribeVia ) where import Prelude @@ -21,18 +78,50 @@ import Prelude import Data.Either (Either(..)) import Data.Function.Uncurried (Fn3, runFn3) import Effect (Effect) -import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, runEffectFn1, runEffectFn2, runEffectFn3) +import Effect.Exception (Error) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn4, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4) +import Unsafe.Coerce (unsafeCoerce) foreign import data EventEmitter :: Type -- | Create a new event emitter foreign import new :: Effect EventEmitter +foreign import data StrOrSymbol :: Type + +foreign import eventNamesImpl :: EventEmitter -> Array StrOrSymbol + +foreign import data JsSymbol :: Type + +eventNames :: EventEmitter -> Array (Either JsSymbol String) +eventNames ee = map (\x -> runFn3 strOrSymbol Left Right x) $ eventNamesImpl ee + +foreign import strOrSymbol :: Fn3 (forall a. JsSymbol -> Either JsSymbol a) (forall b. String -> Either b String) StrOrSymbol (Either JsSymbol String) + +foreign import getMaxListenersImpl :: EffectFn1 EventEmitter Int + +-- | By default, an event emitter can only have a maximum of 10 listeners +-- | for a given event. +getMaxListeners :: EventEmitter -> Effect Int +getMaxListeners = runEffectFn1 getMaxListenersImpl + +foreign import listenerCountImpl :: EffectFn2 EventEmitter String Int + +listenerCount :: EventEmitter -> String -> Effect Int +listenerCount emitter eventName = runEffectFn2 listenerCountImpl emitter eventName + +foreign import setMaxListenersImpl :: EffectFn2 EventEmitter Int Unit + +setMaxListeners :: Int -> EventEmitter -> Effect Unit +setMaxListeners max emitter = runEffectFn2 setMaxListenersImpl emitter max + +setUnlimitedListeners :: EventEmitter -> Effect Unit +setUnlimitedListeners = setMaxListeners 0 + -- | THIS IS UNSAFE! REALLY UNSAFE! --- | Gets the `emit` function for a particular `EventEmitter`. --- | Intended usage is to prevent the need to write redundant FFI: +-- | Gets the `emit` function for a particular `EventEmitter`, so that one can call n-ary functions. -- | --- | `http2session.goaway([code[, lastStreamID[, opaqueData]]])` as an example... +-- | 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 -- | @@ -56,76 +145,232 @@ foreign import new :: Effect EventEmitter -- | 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. +-- | 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 --- | THIS IS UNSAFE! REALLY UNSAFE! --- | Intended usage is to prevent the need to write redundant FFI: +-- | Packs all the type information we need to call `on`/`once`/`prependListener`/`prependOnceListener` +-- | with the correct callback function type. -- | --- | `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 type-safe idiomatic function that handles all four cases: +-- | 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 + +-- | Handler for the `error` event. Every `EventEmitter` seems to use this at some point, +-- | though some may change the type signature. +errorHandle :: EventHandle EventEmitter (Error -> Effect Unit) (EffectFn1 Error Unit) +errorHandle = EventHandle "error" $ \cb -> mkEffectFn1 cb + +-- | Adds the callback to the end of the `listeners` array and provides no way to remove it in the future. +-- | Intended usage: -- | ``` --- | onGoAway --- | :: Http2Session --- | -> (Maybe Code -> Maybe LastStreamId -> Maybe OpaqueData -> Effect Unit) --- | -> Effect Void --- | onGoAway h2s cb = void $ --- | runEffectFn3 --- | -- If you're feeling lucky, omit the type annotations on `unsafeOn` completely. --- | (unsafeOn :: EffectFn3 EventEmitter String (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) EventEmitter --- | (unsafeCoerce h2s :: EventEmitter) --- | "goaway" --- | (mkEffectFn3 \c id d -> cb (toMaybe c) (toMaybe id) (toMaybe d)) +-- | on errorHandle eventEmitter \error -> do +-- | log $ "Got error: " <> Exception.message error -- | ``` -foreign import unsafeOn :: forall f. EffectFn3 EventEmitter String f Unit - -foreign import unsafeOff :: forall f. EffectFn3 EventEmitter String f Unit +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 -unsafeSubscribe :: forall f. EventEmitter -> String -> f -> Effect (Effect Unit) -unsafeSubscribe ee eventName f = do - runEffectFn3 unsafeOn ee eventName f - pure $ runEffectFn3 unsafeOff ee eventName f +-- | 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 -foreign import unsafeOnce :: forall f. EffectFn3 EventEmitter String f Unit -foreign import unsafePrependListener :: forall f. EffectFn3 EventEmitter String f Unit +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 -unsafePrependSubscribe :: forall f. EventEmitter -> String -> f -> Effect (Effect Unit) -unsafePrependSubscribe ee eventName f = do - runEffectFn3 unsafePrependListener ee eventName f - pure $ runEffectFn3 unsafeOff ee eventName f +onceVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect Unit +onceVia _ (EventHandle eventName toJsCb) eventEmitter psCb = + runEffectFn3 unsafeOnce (unsafeCoerce eventEmitter) eventName $ toJsCb psCb -foreign import unsafePrependOnceListener :: forall f. EffectFn3 EventEmitter String f EventEmitter +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 -foreign import data StrOrSymbol :: Type +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 -foreign import eventNamesImpl :: EventEmitter -> Array StrOrSymbol +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 -foreign import data JsSymbol :: Type +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 -eventNames :: EventEmitter -> Array (Either JsSymbol String) -eventNames ee = map (\x -> runFn3 strOrSymbol Left Right x) $ eventNamesImpl ee +-- | 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 -foreign import strOrSymbol :: Fn3 (forall a. JsSymbol -> Either JsSymbol a) (forall b. String -> Either b String) StrOrSymbol (Either JsSymbol String) +-- | 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 -foreign import getMaxListenersImpl :: EffectFn1 EventEmitter Int +-- | 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 -getMaxListeners :: EventEmitter -> Effect Int -getMaxListeners = runEffectFn1 getMaxListenersImpl +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 -foreign import listenerCountImpl :: EffectFn2 EventEmitter String Int +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 -listenerCount :: EventEmitter -> String -> Effect Int -listenerCount = runEffectFn2 listenerCountImpl +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 -setMaxListeners :: Int -> EventEmitter -> Effect Unit -setMaxListeners max emitter = runEffectFn2 setMaxListenersImpl emitter max +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 -setUnlimitedListeners :: EventEmitter -> Effect Unit -setUnlimitedListeners = setMaxListeners 0 +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 -foreign import setMaxListenersImpl :: EffectFn2 (EventEmitter) Int Unit +prependOnceListenerSubscribeVia + :: forall a emitter psCb jsCb + . (a -> emitter) + -> EventHandle emitter psCb jsCb + -> a + -> psCb + -> Effect (Effect Unit) +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 From f75fe47f6c397009089ceb1841795ff06afdea75 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 10:56:27 -0500 Subject: [PATCH 06/13] Export constructor --- src/Node/EventEmitter.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index 92e215c..3d9bd00 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -53,7 +53,7 @@ module Node.EventEmitter , setMaxListeners , setUnlimitedListeners , unsafeEmitFn - , EventHandle + , EventHandle(..) , errorHandle , on , onVia From 25f59c6a5a57476055d61c4cacc340ffb138dee2 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 10:57:11 -0500 Subject: [PATCH 07/13] Define utility types --- src/Node/EventEmitter/UtilTypes.purs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/Node/EventEmitter/UtilTypes.purs 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) From dd3df3664803392ba6d0d3ed790c63840b4d884e Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 10:58:03 -0500 Subject: [PATCH 08/13] Update tests --- test.dhall | 7 +- test/Test/Node/EventEmitter.purs | 213 +++++++++---------------------- 2 files changed, 63 insertions(+), 157 deletions(-) diff --git a/test.dhall b/test.dhall index 8e4b364..1ab888a 100644 --- a/test.dhall +++ b/test.dhall @@ -1,17 +1,16 @@ { name = "node-event-emitters-test" , dependencies = [ "aff" - , "arrays" - , "control" , "effect" , "either" + , "exceptions" , "foldable-traversable" , "functions" - , "maybe" - , "nullable" , "prelude" , "refs" , "spec" + , "tuples" + , "unsafe-coerce" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs", "test/**/*.purs" ] diff --git a/test/Test/Node/EventEmitter.purs b/test/Test/Node/EventEmitter.purs index ad6b391..adafc2a 100644 --- a/test/Test/Node/EventEmitter.purs +++ b/test/Test/Node/EventEmitter.purs @@ -2,172 +2,79 @@ module Test.Node.EventEmitter where import Prelude -import Control.Alternative (guard) -import Data.Array as Array -import Data.Foldable (fold, for_, foldMap) -import Data.Maybe (Maybe(..)) -import Data.Nullable (Nullable, toMaybe) -import Effect (Effect) +import Data.Foldable (for_) +import Data.Tuple.Nested ((/\)) import Effect.Class (liftEffect) import Effect.Ref as Ref -import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn3, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4) -import Node.EventEmitter (EventEmitter, unsafeEmitFn, unsafeOn, unsafeSubscribe) +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 "init works" do + it "`new` does not throw" do liftEffect $ void $ EventEmitter.new - it "emit works" do + it "`emit` does not throw" do liftEffect do ee <- EventEmitter.new void $ runEffectFn1 (unsafeEmitFn ee :: EffectFn1 String Boolean) "foo" - it "handle works" do - liftEffect do - ee <- EventEmitter.new - void $ runEffectFn3 unsafeOn ee "foo" $ mkEffectFn1 \_ -> pure unit + void $ runEffectFn2 (unsafeEmitFn ee :: EffectFn2 String String Boolean) "foo" "baz" void $ runEffectFn1 (unsafeEmitFn ee) "foo" - - it "subscribe works" do - let expected = "aaa" - actual <- liftEffect do - ref <- Ref.new "" - ee <- EventEmitter.new - unsub <- unsafeSubscribe ee "foo" $ mkEffectFn1 \s -> Ref.modify_ (\r -> r <> s) ref - void $ runEffectFn2 (unsafeEmitFn ee) "foo" expected - unsub - void $ runEffectFn2 (unsafeEmitFn ee) "foo" "bbb" - Ref.read ref - expected `shouldEqual` actual - - describe "explicit types" do + void $ runEffectFn2 (unsafeEmitFn ee) "foo" "bar" + void $ runEffectFn3 (unsafeEmitFn ee) "foo" "bar" "baz" + describe "standard functions" do let - i = 20 - s = "str" - arr = [ "foo", "bar" ] - for_ [ 0, 1, 2, 3 ] \argAmt -> do - it ("emit/handle roundtrips on " <> show argAmt <> " args") do - ref <- liftEffect $ Ref.new "" - ee <- liftEffect $ EventEmitter.new - let - expected = fold $ Array.take argAmt - [ show i - , show s - , show arr - ] - liftEffect $ handleVArgs ee \a b c -> do - let - actual = fold - [ foldMap show a - , foldMap show b - , foldMap show c - ] - Ref.write actual ref - liftEffect $ void $ emitVArgs ee - (i <$ guard (argAmt >= 1)) - (s <$ guard (argAmt >= 2)) - (arr <$ guard (argAmt >= 3)) - actual' <- liftEffect $ Ref.read ref - expected `shouldEqual` actual' + 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 "types inferred" do + describe "subscribe functions" do let - i = 20 - s = "str" - arr = [ "foo", "bar" ] - for_ [ 0, 1, 2, 3 ] \argAmt -> do - it ("emit/handle roundtrips on " <> show argAmt <> " args - verify type inference") do - ref <- liftEffect $ Ref.new "" - ee <- liftEffect $ EventEmitter.new - let - expected = fold $ Array.take argAmt - [ show i - , show s - , show arr - ] - liftEffect $ handleVArgs_checkTypeInference ee \a b c -> do - let - actual = fold - [ foldMap show a - , foldMap show b - , foldMap show c - ] - Ref.write actual ref - liftEffect $ void $ emitVArgs_checkTypeInference ee - (i <$ guard (argAmt >= 1)) - (s <$ guard (argAmt >= 2)) - (arr <$ guard (argAmt >= 3)) - actual' <- liftEffect $ Ref.read ref - expected `shouldEqual` actual' - -eventName = "someEvent" :: String - -emitVArgs - :: EventEmitter - -> Maybe Int - -> Maybe String - -> Maybe (Array String) - -> Effect Boolean -emitVArgs ee = case _, _, _ of - Just i, Just s, Just a -> - runEffectFn4 (unsafeEmitFn ee :: EffectFn4 String Int String (Array String) Boolean) eventName i s a - Just i, Just s, Nothing -> - runEffectFn3 (unsafeEmitFn ee :: EffectFn3 String Int String Boolean) eventName i s - Just i, Nothing, Nothing -> - runEffectFn2 (unsafeEmitFn ee :: EffectFn2 String Int Boolean) eventName i - _, _, _ -> - runEffectFn1 (unsafeEmitFn ee :: EffectFn1 String Boolean) eventName - -handleVArgs - :: EventEmitter - -> (Maybe Int -> Maybe String -> Maybe (Array String) -> Effect Unit) - -> Effect Unit -handleVArgs ee cb = void do - runEffectFn3 - -- This could be written - -- (unsafeOn :: _ _ _ (EffectFn3 (Nullable Code) (Nullable LastStreamId) (Nullable OpaqueData) Unit)) Unit - ( unsafeOn - :: EffectFn3 - EventEmitter - String - ( EffectFn3 - (Nullable Int) - (Nullable String) - (Nullable (Array String)) - Unit - ) - EventEmitter - ) - ee - eventName - (mkEffectFn3 \i s a -> cb (toMaybe i) (toMaybe s) (toMaybe a)) - --- Haha! Type inference works on this monstrosity. -emitVArgs_checkTypeInference - :: EventEmitter - -> Maybe Int - -> Maybe String - -> Maybe (Array String) - -> Effect Boolean -emitVArgs_checkTypeInference ee = case _, _, _ of - Just i, Just s, Just a -> - runEffectFn4 (unsafeEmitFn ee) eventName i s a - Just i, Just s, Nothing -> - runEffectFn3 (unsafeEmitFn ee) eventName i s - Just i, Nothing, Nothing -> - runEffectFn2 (unsafeEmitFn ee) eventName i - _, _, _ -> - runEffectFn1 (unsafeEmitFn ee) eventName - -handleVArgs_checkTypeInference - :: EventEmitter - -> (Maybe Int -> Maybe String -> Maybe (Array String) -> Effect Unit) - -> Effect Unit -handleVArgs_checkTypeInference ee cb = void do - runEffectFn3 - unsafeOn - ee - eventName - (mkEffectFn3 \i s a -> cb (toMaybe i) (toMaybe s) (toMaybe a)) + 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` "" From 3722c85c663d1ae772579698cb7dd71ba96e54f4 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 11:00:38 -0500 Subject: [PATCH 09/13] No npm deps to install --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd0d749..c04ee6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,9 +40,6 @@ jobs: run: npm i -g purescript@latest purs-tidy@latest purescript-psa@latest spago@latest - - name: Install NPM dependencies - run: npm install - - name: Cache PureScript dependencies uses: actions/cache@v3 with: From ea5b2ac31a1b3aa1117c4f181c5b1c05461e7949 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 11:02:57 -0500 Subject: [PATCH 10/13] Add missing spago deps --- spago.dhall | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spago.dhall b/spago.dhall index 9e333ed..ed16a4d 100644 --- a/spago.dhall +++ b/spago.dhall @@ -1,6 +1,6 @@ { name = "node-event-emitters" , dependencies = - [ "effect", "either", "functions", "prelude" ] + [ "effect", "either", "exceptions", "functions", "prelude", "unsafe-coerce" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs" ] } From 8d51bb9c7e3eb672450c7d0087a00d3fff8047b4 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 11:04:45 -0500 Subject: [PATCH 11/13] Use test.dhall in CI spago test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c04ee6a..7f0fbd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: - name: Run tests run: | - npx spago test + npx spago -x test.dhall test - name: Check Formatting if: runner.os == 'Linux' From 13af2957f6600489ea1c9e5b41086685ba735c75 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 11:31:23 -0500 Subject: [PATCH 12/13] Add newListener/removeListener events --- src/Node/EventEmitter.js | 2 +- src/Node/EventEmitter.purs | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Node/EventEmitter.js b/src/Node/EventEmitter.js index 6a00ba3..2f3b6a4 100644 --- a/src/Node/EventEmitter.js +++ b/src/Node/EventEmitter.js @@ -8,7 +8,7 @@ export { newImpl as new }; // addEventListener - not implemented; alias to `on` export const unsafeEmitFn = (emitter) => emitter.emit.bind(emitter); export const eventNamesImpl = (emitter) => emitter.eventNames(); -export const strOrSymbol = (left, right, sym) => typeof sym == "symbol" ? left(sym) : right(sym); +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. diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index 3d9bd00..9e0be1a 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -47,6 +47,7 @@ module Node.EventEmitter ( EventEmitter , new , JsSymbol + , SymbolOrStr , eventNames , getMaxListeners , listenerCount @@ -54,6 +55,8 @@ module Node.EventEmitter , setUnlimitedListeners , unsafeEmitFn , EventHandle(..) + , newListenerHandle + , removeListenerHandle , errorHandle , on , onVia @@ -87,16 +90,16 @@ foreign import data EventEmitter :: Type -- | Create a new event emitter foreign import new :: Effect EventEmitter -foreign import data StrOrSymbol :: Type +foreign import data SymbolOrStr :: Type -foreign import eventNamesImpl :: EventEmitter -> Array StrOrSymbol +foreign import eventNamesImpl :: EventEmitter -> Array SymbolOrStr foreign import data JsSymbol :: Type eventNames :: EventEmitter -> Array (Either JsSymbol String) -eventNames ee = map (\x -> runFn3 strOrSymbol Left Right x) $ eventNamesImpl ee +eventNames ee = map (\x -> runFn3 symbolOrStr Left Right x) $ eventNamesImpl ee -foreign import strOrSymbol :: Fn3 (forall a. JsSymbol -> Either JsSymbol a) (forall b. String -> Either b String) StrOrSymbol (Either JsSymbol String) +foreign import symbolOrStr :: Fn3 (forall a. JsSymbol -> Either JsSymbol a) (forall b. String -> Either b String) SymbolOrStr (Either JsSymbol String) foreign import getMaxListenersImpl :: EffectFn1 EventEmitter Int @@ -161,6 +164,14 @@ data EventHandle emitterType 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 + -- | Handler for the `error` event. Every `EventEmitter` seems to use this at some point, -- | though some may change the type signature. errorHandle :: EventHandle EventEmitter (Error -> Effect Unit) (EffectFn1 Error Unit) From 9b46ff46a0b890443e8e247426b4abd2908ab5de Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Wed, 14 Jun 2023 11:33:08 -0500 Subject: [PATCH 13/13] Drop 'error' event --- spago.dhall | 7 ++++++- src/Node/EventEmitter.purs | 7 ------- test.dhall | 1 - 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/spago.dhall b/spago.dhall index ed16a4d..e1250ec 100644 --- a/spago.dhall +++ b/spago.dhall @@ -1,6 +1,11 @@ { name = "node-event-emitters" , dependencies = - [ "effect", "either", "exceptions", "functions", "prelude", "unsafe-coerce" ] + [ "effect" + , "either" + , "functions" + , "prelude" + , "unsafe-coerce" + ] , packages = ./packages.dhall , sources = [ "src/**/*.purs" ] } diff --git a/src/Node/EventEmitter.purs b/src/Node/EventEmitter.purs index 9e0be1a..ed5a262 100644 --- a/src/Node/EventEmitter.purs +++ b/src/Node/EventEmitter.purs @@ -57,7 +57,6 @@ module Node.EventEmitter , EventHandle(..) , newListenerHandle , removeListenerHandle - , errorHandle , on , onVia , onSubscribe @@ -81,7 +80,6 @@ import Prelude import Data.Either (Either(..)) import Data.Function.Uncurried (Fn3, runFn3) import Effect (Effect) -import Effect.Exception (Error) import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, mkEffectFn1, mkEffectFn4, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4) import Unsafe.Coerce (unsafeCoerce) @@ -172,11 +170,6 @@ removeListenerHandle :: EventHandle EventEmitter (Either JsSymbol String -> Effe removeListenerHandle = EventHandle "removeListener" $ \cb -> mkEffectFn1 \jsSymbol -> cb $ runFn3 symbolOrStr Left Right jsSymbol --- | Handler for the `error` event. Every `EventEmitter` seems to use this at some point, --- | though some may change the type signature. -errorHandle :: EventHandle EventEmitter (Error -> Effect Unit) (EffectFn1 Error Unit) -errorHandle = EventHandle "error" $ \cb -> mkEffectFn1 cb - -- | Adds the callback to the end of the `listeners` array and provides no way to remove it in the future. -- | Intended usage: -- | ``` diff --git a/test.dhall b/test.dhall index 1ab888a..fbceb40 100644 --- a/test.dhall +++ b/test.dhall @@ -3,7 +3,6 @@ [ "aff" , "effect" , "either" - , "exceptions" , "foldable-traversable" , "functions" , "prelude"