diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index edf6a28b..6d018e86 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -96,6 +96,10 @@ jobs:
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' doc'
+ - name: Check scalafix lints
+ if: matrix.java == 'temurin@8' && !startsWith(matrix.scala, '3.')
+ run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' ''scalafixAll --check'''
+
- name: Check unused compile dependencies
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' unusedCompileDependenciesTest'
diff --git a/.scalafix.conf b/.scalafix.conf
new file mode 100644
index 00000000..ab1b91a6
--- /dev/null
+++ b/.scalafix.conf
@@ -0,0 +1,15 @@
+rules = [
+ Http4sFs2Linters
+ Http4sGeneralLinters
+ Http4sUseLiteralsSyntax
+ LeakingImplicitClassVal
+ ExplicitResultTypes
+ OrganizeImports
+]
+
+triggered.rules = [
+ Http4sFs2Linters
+ Http4sGeneralLinters
+ Http4sUseLiteralsSyntax
+ LeakingImplicitClassVal
+]
diff --git a/.scalafmt.conf b/.scalafmt.conf
index 7e57088d..e1c3a6dd 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,4 +1,4 @@
-version = 3.5.2
+version = 3.5.8
runner.dialect = Scala213Source3
diff --git a/README.md b/README.md
index a1ba0993..d2f66858 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@ Use http4s in your browser with Scala.js! Check out the [interactive examples](h
Features:
-* A [`Client` implementation](fetch.md) backed by [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
-* A [`WSClient` implementation](websocket.md) backed by [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket$.html)
-* A [`Service Worker` integration](serviceworker.md) to install your `HttpRoutes` as a [`FetchEvent` handler](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/onfetch)
+* A [`Client` implementation](https://http4s.github.io/http4s-dom/fetch.html) backed by [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
+* A [`WSClient` implementation](https://http4s.github.io/http4s-dom/websocket.html) backed by [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
+* A [`Service Worker` integration](https://http4s.github.io/http4s-dom/serviceworker.html) to install your `HttpRoutes` as a [`FetchEvent` handler](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/fetch_event)
* Encoders for [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
Notably, http4s-dom can also be used to create _serverless_ apps with [Cloudflare Workers](https://workers.cloudflare.com) which have adopted the same APIs used in the browser!
@@ -18,5 +18,5 @@ It is also possible to use the `FetchClient` in Node.js v18+, which added [exper
[](https://index.scala-lang.org/http4s/http4s-dom/http4s-dom)
```scala
-libraryDependencies += "org.http4s" %%% "http4s-dom" % "0.2.2"
+libraryDependencies += "org.http4s" %%% "http4s-dom" % "0.2.3"
```
diff --git a/build.sbt b/build.sbt
index 3884514d..3cc16ac1 100644
--- a/build.sbt
+++ b/build.sbt
@@ -120,9 +120,9 @@ ThisBuild / Test / jsEnv := {
}
}
-val catsEffectVersion = "3.3.12"
-val fs2Version = "3.2.7"
-val http4sVersion = "1.0.0-M33"
+val catsEffectVersion = "3.3.13"
+val fs2Version = "3.2.9"
+val http4sVersion = "1.0.0-M34"
val scalaJSDomVersion = "2.2.0"
val circeVersion = "0.14.2"
val munitVersion = "0.7.29"
diff --git a/docs/fetch.md b/docs/fetch.md
index cb7e86c8..d9050a02 100644
--- a/docs/fetch.md
+++ b/docs/fetch.md
@@ -4,13 +4,17 @@ The @:api(org.http4s.dom.FetchClientBuilder) creates a standard http4s @:api(org
## Example
+```scala
+libraryDependencies += "org.http4s" %%% "http4s-circe" % "@HTTP4S_VERSION@"
+libraryDependencies += "io.circe" %%% "circe-generic" % "@CIRCE_VERSION@"
+```
+
```scala mdoc:js
-
-
- I'm bored.
-
-
-
+
+
How many stars?
+
+
+
---
import cats.effect._
@@ -22,17 +26,24 @@ import org.scalajs.dom._
val client = FetchClientBuilder[IO].create
-val activityElement = document.getElementById("activity")
-
-case class Activity(activity: String)
-
-val fetchActivity: IO[Unit] = for {
- _ <- IO(activityElement.innerHTML = "
fetching...")
- activity <- client.expect[Activity]("https://www.boredapi.com/api/activity")
- _ <- IO(activityElement.innerHTML = activity.activity)
+val repoName = document.getElementById("repo").asInstanceOf[HTMLInputElement]
+val repoStars = document.getElementById("stars").asInstanceOf[HTMLElement]
+
+case class Repo(stargazers_count: Int)
+
+val fetchRepo: IO[Unit] = for {
+ _ <- IO(repoStars.innerHTML = "
fetching...")
+ name <- IO(repoName.value)
+ repo <- client.expect[Repo](s"https://api.github.com/repos/$name").attempt
+ _ <- IO {
+ repo match {
+ case Right(Repo(stars)) => repoStars.innerHTML = s"$stars ★"
+ case Left(_) => repoStars.innerHTML = s"Not found :("
+ }
+ }
} yield ()
val button = document.getElementById("button").asInstanceOf[HTMLButtonElement]
-button.onclick = _ => fetchActivity.unsafeRunAndForget()
+button.onclick = _ => fetchRepo.unsafeRunAndForget()
```
diff --git a/docs/index.md b/docs/index.md
index 354adfca..f46516ef 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -21,8 +21,4 @@ libraryDependencies += "org.http4s" %%% "http4s-dom" % "@VERSION@"
// recommended, brings in the latest client module
libraryDependencies += "org.http4s" %%% "http4s-client" % "@HTTP4S_VERSION@"
-
-// optional, for JSON support
-libraryDependencies += "org.http4s" %%% "http4s-circe" % "@HTTP4S_VERSION@"
-libraryDependencies += "io.circe" %%% "circe-generic" % "@CIRCE_VERSION@"
```
diff --git a/dom/src/main/scala/org/http4s/dom/FetchClient.scala b/dom/src/main/scala/org/http4s/dom/FetchClient.scala
index 71fcce9f..11848da5 100644
--- a/dom/src/main/scala/org/http4s/dom/FetchClient.scala
+++ b/dom/src/main/scala/org/http4s/dom/FetchClient.scala
@@ -41,13 +41,13 @@ private[dom] object FetchClient {
requestTimeout: Duration,
options: FetchOptions
)(implicit F: Async[F]): Client[F] = Client[F] { (req: Request[F]) =>
- Resource.eval {
- req.entity match {
- case Entity.Empty => None.pure
- case Entity.Strict(chunk) => Some(chunk).pure
+ Resource.eval(req.toStrict(None)).flatMap { req =>
+ val body = req.entity match {
+ case Entity.Empty => None
+ case Entity.Strict(chunk) => Some(chunk)
case default => default.body.chunkAll.filter(_.nonEmpty).compile.last
}
- } flatMap { body =>
+
Resource
.makeCaseFull { (poll: Poll[F]) =>
F.delay(new AbortController()).flatMap { abortController =>
@@ -77,8 +77,9 @@ private[dom] object FetchClient {
.foreach(referrer => init.referrer = referrer.renderString)
mergedOptions.referrerPolicy.foreach(init.referrerPolicy = _)
- val fetch = poll(F.fromPromise(F.delay(Fetch.fetch(req.uri.renderString, init))))
- .onCancel(F.delay(abortController.abort()))
+ val fetch =
+ poll(F.fromPromise(F.delay(Fetch.fetch(req.uri.renderString, init))))
+ .onCancel(F.delay(abortController.abort()))
requestTimeout match {
case d: FiniteDuration =>
@@ -95,7 +96,7 @@ private[dom] object FetchClient {
case (r, exitCase) =>
OptionT.fromOption(Option(r.body)).foreachF(closeReadableStream(_, exitCase))
}
- .evalMap(fromDomResponse[F])
+ .evalMap(fromDomResponse[F](_))
}
}
diff --git a/dom/src/main/scala/org/http4s/dom/ServiceWorker.scala b/dom/src/main/scala/org/http4s/dom/ServiceWorker.scala
index ccd710a3..17611d46 100644
--- a/dom/src/main/scala/org/http4s/dom/ServiceWorker.scala
+++ b/dom/src/main/scala/org/http4s/dom/ServiceWorker.scala
@@ -28,8 +28,8 @@ import cats.effect.unsafe.IORuntime
import cats.syntax.all._
import fs2.Chunk
import org.scalajs.dom.Fetch
-import org.scalajs.dom.ResponseInit
import org.scalajs.dom.FetchEvent
+import org.scalajs.dom.ResponseInit
import org.scalajs.dom.ServiceWorkerGlobalScope
import org.scalajs.dom.{Response => DomResponse}
import org.typelevel.vault.Key
diff --git a/dom/src/main/scala/org/http4s/dom/WebSocketClient.scala b/dom/src/main/scala/org/http4s/dom/WebSocketClient.scala
index 41ce3dfb..3a96d225 100644
--- a/dom/src/main/scala/org/http4s/dom/WebSocketClient.scala
+++ b/dom/src/main/scala/org/http4s/dom/WebSocketClient.scala
@@ -26,7 +26,6 @@ import cats.effect.std.Queue
import cats.effect.std.Semaphore
import cats.effect.syntax.all._
import cats.syntax.all._
-import fs2.INothing
import fs2.Stream
import org.http4s.Method
import org.http4s.client.websocket.WSClientHighLevel
@@ -51,7 +50,7 @@ object WebSocketClient {
dispatcher <- Dispatcher[F]
messages <- Queue.unbounded[F, Option[MessageEvent]].toResource
semaphore <- Semaphore[F](1).toResource
- error <- F.deferred[Either[Throwable, INothing]].toResource
+ error <- F.deferred[Throwable].toResource
close <- F.deferred[CloseEvent].toResource
ws <- Resource.makeCase {
F.async_[WebSocket] { cb =>
@@ -69,8 +68,7 @@ object WebSocketClient {
ws.onopen = { _ =>
ws.onerror = // replace the error handler
- e =>
- dispatcher.unsafeRunAndForget(error.complete(Left(js.JavaScriptException(e))))
+ e => dispatcher.unsafeRunAndForget(error.complete(js.JavaScriptException(e)))
cb(Right(ws))
}
@@ -122,7 +120,7 @@ object WebSocketClient {
def receive: F[Option[WSDataFrame]] = semaphore
.permit
.surround(OptionT(messages.take).map(decodeMessage).value)
- .race(error.get.rethrow)
+ .race(error.get.flatMap(F.raiseError[Option[WSDataFrame]]))
.map(_.merge)
override def receiveStream: Stream[F, WSDataFrame] =
@@ -130,7 +128,7 @@ object WebSocketClient {
.resource(semaphore.permit)
.flatMap(_ => Stream.fromQueueNoneTerminated(messages))
.map(decodeMessage)
- .concurrently(Stream.exec(error.get.rethrow.widen))
+ .concurrently(Stream.exec(error.get.flatMap(F.raiseError)))
private def decodeMessage(e: MessageEvent): WSDataFrame =
e.data match {
@@ -156,7 +154,7 @@ object WebSocketClient {
}
private def errorOr(fu: F[Unit]): F[Unit] = error.tryGet.flatMap {
- case Some(error) => F.fromEither[Unit](error)
+ case Some(error) => F.raiseError(error)
case None => fu
}
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 4feb1d15..b838f0b8 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,4 +1,4 @@
-val http4sVersion = "0.23.12"
+val http4sVersion = "0.23.13"
enablePlugins(BuildInfoPlugin)
buildInfoKeys += "http4sVersion" -> http4sVersion
@@ -7,7 +7,7 @@ libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1"
libraryDependencies += "org.http4s" %% "http4s-dsl" % http4sVersion
libraryDependencies += "org.http4s" %% "http4s-blaze-server" % "0.23.12"
-addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.13.2")
+addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.14.3")
addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.2")
-addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.0")
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0")
diff --git a/tests-nodejs/src/test/scala/org/http4s/dom/NodeJSFetchSuite.scala b/tests-nodejs/src/test/scala/org/http4s/dom/NodeJSFetchSuite.scala
index 52007a93..bdea5225 100644
--- a/tests-nodejs/src/test/scala/org/http4s/dom/NodeJSFetchSuite.scala
+++ b/tests-nodejs/src/test/scala/org/http4s/dom/NodeJSFetchSuite.scala
@@ -18,8 +18,10 @@ package org.http4s
package dom
import cats.effect.IO
+import cats.effect.Resource
+import org.http4s.client.Client
import org.http4s.client.testkit.ClientRouteTestBattery
class NodeJSFetchSuite extends ClientRouteTestBattery("FetchClient") {
- def clientResource = FetchClientBuilder[IO].resource
+ def clientResource: Resource[IO, Client[IO]] = FetchClientBuilder[IO].resource
}
diff --git a/tests/src/test/scala/org/http4s/dom/FetchServiceWorkerSuite.scala b/tests/src/test/scala/org/http4s/dom/FetchServiceWorkerSuite.scala
index 55b93736..cd7f4d9a 100644
--- a/tests/src/test/scala/org/http4s/dom/FetchServiceWorkerSuite.scala
+++ b/tests/src/test/scala/org/http4s/dom/FetchServiceWorkerSuite.scala
@@ -22,6 +22,7 @@ import cats.syntax.all._
import fs2.Stream
import munit.CatsEffectSuite
import org.http4s.Method._
+import org.http4s.client.Client
import org.http4s.client.dsl.io._
import org.http4s.client.testkit.testroutes.GetRoutes
import org.http4s.multipart.Multiparts
@@ -35,9 +36,9 @@ import scala.scalajs.js
class FetchServiceWorkerSuite extends CatsEffectSuite {
- val client = FetchClientBuilder[IO].create
+ val client: Client[IO] = FetchClientBuilder[IO].create
- val baseUrl = uri"/"
+ val baseUrl: Uri = uri"/"
test("Install service worker") {
IO.fromPromise {