diff --git a/bun.lockb b/bun.lockb index ce824cd..bc52cfd 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/spago.lock b/spago.lock index 402409d..319e704 100644 --- a/spago.lock +++ b/spago.lock @@ -18,6 +18,7 @@ workspace: - maybe - newtype - node-buffer + - node-streams - nullable - numbers - ordered-collections @@ -28,6 +29,7 @@ workspace: - unsafe-coerce - url - web-file + - web-streams test_dependencies: [] build_plan: - aff @@ -57,12 +59,15 @@ workspace: - identity - integers - invariant + - js-promise - lazy - lists - maybe - media-types - newtype - node-buffer + - node-event-emitter + - node-streams - nonempty - nullable - numbers @@ -91,6 +96,7 @@ workspace: - web-dom - web-events - web-file + - web-streams package_set: address: hash: sha256-nTsd44o7/hrTdk0c6dh0wyBqhFFDJJIeKdQU6L1zv/A= @@ -6036,6 +6042,17 @@ packages: dependencies: - control - prelude + js-promise: + type: git + url: https://github.com/purescript-contrib/purescript-js-promise.git + rev: ff731bceb7f22827322d5cabdb50f4427dbe9940 + dependencies: + - effect + - exceptions + - foldable-traversable + - functions + - maybe + - prelude lazy: type: git url: https://github.com/purescript/purescript-lazy.git @@ -6096,6 +6113,31 @@ packages: - nullable - st - unsafe-coerce + node-event-emitter: + type: git + url: https://github.com/purescript-node/purescript-node-event-emitter.git + rev: b283c3eb6abc32a88fd8876af746b9548e78d93f + dependencies: + - effect + - either + - functions + - maybe + - nullable + - prelude + - unsafe-coerce + node-streams: + type: git + url: https://github.com/purescript-node/purescript-node-streams.git + rev: 8aaec35f1c6316924e360274cd248edee01eae4f + dependencies: + - aff + - effect + - either + - exceptions + - node-buffer + - node-event-emitter + - nullable + - prelude nonempty: type: git url: https://github.com/purescript/purescript-nonempty.git @@ -6380,3 +6422,15 @@ packages: - foreign - media-types - web-dom + web-streams: + type: git + url: https://github.com/purescript-web/purescript-web-streams.git + rev: b89cd269b71e818cc32a9452c590f14cfaf50c59 + dependencies: + - arraybuffer-types + - effect + - exceptions + - js-promise + - nullable + - prelude + - tuples diff --git a/spago.yaml b/spago.yaml index e4a4568..8a6a490 100644 --- a/spago.yaml +++ b/spago.yaml @@ -15,7 +15,7 @@ package: - maybe - newtype - node-buffer - - url + - node-streams - nullable - numbers - ordered-collections @@ -24,10 +24,12 @@ package: - transformers - tuples - unsafe-coerce + - url - web-file + - web-streams name: fetch workspace: - extra_packages: + extraPackages: url: git: 'https://git.orionkindel.com/thunderstrike/purescript-url-immutable.git' ref: 'dbfa3b6' @@ -44,6 +46,6 @@ workspace: - prelude - strings - tuples - package_set: + packageSet: url: https://raw.githubusercontent.com/purescript/package-sets/psc-0.15.10-20230930/packages.json hash: sha256-nTsd44o7/hrTdk0c6dh0wyBqhFFDJJIeKdQU6L1zv/A= diff --git a/src/HTTP/Response.Node.js b/src/HTTP/Response.Node.js new file mode 100644 index 0000000..3dad01c --- /dev/null +++ b/src/HTTP/Response.Node.js @@ -0,0 +1,30 @@ +import { Readable } from 'stream'; +import { Buffer } from 'buffer'; + +/** @type {(r: Response) => () => Promise} */ +export const bufferImpl = r => async () => Buffer.from(await r.arrayBuffer()) + +/** @type {(r: Response) => () => Readable} */ +export const streamImpl = r => () => { + if (!r.body) { + throw new Error('Response body is empty') + } + + const reader = r.body.getReader(); + return new Readable({ + read() { + reader + .read() + .then(chunk => { + if (chunk.value) { + this.push(Buffer.from(chunk.value)) + } + + if (chunk.done) { + this.push(null) + } + }) + .catch(e => this.destroy(e)) + }, + }) +} diff --git a/src/HTTP/Response.Node.purs b/src/HTTP/Response.Node.purs new file mode 100644 index 0000000..1eaabd1 --- /dev/null +++ b/src/HTTP/Response.Node.purs @@ -0,0 +1,26 @@ +module HTTP.Response.Node + ( module X + , buffer + , stream + ) where + +import Prelude + +import Control.Promise (Promise) +import Control.Promise as Promise +import Effect (Effect) +import Effect.Aff.Class (class MonadAff, liftAff) +import Effect.Class (class MonadEffect, liftEffect) +import HTTP.Response (Response, stream) +import HTTP.Response hiding (stream) as X +import Node.Buffer (Buffer) +import Node.Stream (Stream, Readable) + +foreign import bufferImpl :: Response -> Effect (Promise Buffer) +foreign import streamImpl :: Response -> Effect (Readable ()) + +buffer :: forall m. MonadAff m => Response -> m Buffer +buffer = liftAff <<< Promise.toAffE <<< bufferImpl + +stream :: forall m. MonadEffect m => Response -> m (Readable ()) +stream = liftEffect <<< streamImpl diff --git a/src/HTTP/Response.js b/src/HTTP/Response.js index 2077eb2..a7c29dc 100644 --- a/src/HTTP/Response.js +++ b/src/HTTP/Response.js @@ -3,6 +3,9 @@ /** @type {(_: Response) => () => Response} */ export const cloneImpl = rep => () => rep.clone() +/** @type {(_: Response) => () => ReadableStream} */ +export const streamImpl = rep => () => rep.body + /** @type {(_: Response) => () => Promise} */ export const jsonImpl = rep => () => rep.json() diff --git a/src/HTTP/Response.purs b/src/HTTP/Response.purs index 92f1d6a..f906b22 100644 --- a/src/HTTP/Response.purs +++ b/src/HTTP/Response.purs @@ -1,5 +1,6 @@ module HTTP.Response ( Response(..) + , stream , clone , json , text @@ -18,7 +19,7 @@ import Control.Monad.Error.Class (class MonadThrow, liftEither, throwError) import Control.Monad.Except (runExcept) import Control.Promise (Promise) import Control.Promise as Promise -import Data.ArrayBuffer.Types (ArrayBuffer) +import Data.ArrayBuffer.Types (ArrayBuffer, ArrayView, Uint8) import Data.Bifunctor (lmap) import Data.Int as Int import Data.Map (Map) @@ -34,6 +35,7 @@ import HTTP.Form (Form, RawFormData) import HTTP.Form as Form import Simple.JSON (class ReadForeign, readImpl) import Web.File.Blob (Blob) +import Web.Streams.ReadableStream (ReadableStream) foreign import data Response :: Type @@ -46,6 +48,7 @@ foreign import textImpl :: Response -> Effect (Promise String) foreign import abImpl :: Response -> Effect (Promise ArrayBuffer) foreign import blobImpl :: Response -> Effect (Promise Blob) foreign import formImpl :: Response -> Effect (Promise RawFormData) +foreign import streamImpl :: Response -> Effect (ReadableStream (ArrayView Uint8)) guardStatusOk :: forall m. MonadAff m => MonadThrow Error m => Response -> m Unit guardStatusOk rep = do @@ -68,6 +71,9 @@ text = liftAff <<< Promise.toAffE <<< textImpl blob :: forall m. MonadAff m => Response -> m Blob blob = liftAff <<< Promise.toAffE <<< blobImpl +stream :: forall m. MonadEffect m => Response -> m (ReadableStream (ArrayView Uint8)) +stream = liftEffect <<< streamImpl + arrayBuffer :: forall m. MonadAff m => Response -> m ArrayBuffer arrayBuffer = liftAff <<< Promise.toAffE <<< abImpl