Skip to content

Commit

Permalink
refactor /platform HttpClient (#3537)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Sep 4, 2024
1 parent 8a1120c commit 9c4ba21
Show file tree
Hide file tree
Showing 36 changed files with 1,029 additions and 1,209 deletions.
87 changes: 87 additions & 0 deletions .changeset/tender-foxes-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
"@effect/platform-browser": minor
"@effect/platform-node": minor
"@effect/platform-bun": minor
"@effect/platform": minor
"@effect/rpc-http": minor
---

refactor /platform HttpClient

#### HttpClient.fetch removed

The `HttpClient.fetch` client implementation has been removed. Instead, you can
access a `HttpClient` using the corresponding `Context.Tag`.

```ts
import { FetchHttpClient, HttpClient } from "@effect/platform"
import { Effect } from "effect"

Effect.gen(function* () {
const client = yield* HttpClient.HttpClient

// make a get request
yield* client.get("https://jsonplaceholder.typicode.com/todos/1")
}).pipe(
Effect.scoped,
// the fetch client has been moved to the `FetchHttpClient` module
Effect.provide(FetchHttpClient.layer)
)
```

#### `HttpClient` interface now uses methods

Instead of being a function that returns the response, the `HttpClient`
interface now uses methods to make requests.

Some shorthand methods have been added to the `HttpClient` interface to make
less complex requests easier.

```ts
import {
FetchHttpClient,
HttpClient,
HttpClientRequest
} from "@effect/platform"
import { Effect } from "effect"

Effect.gen(function* () {
const client = yield* HttpClient.HttpClient

// make a get request
yield* client.get("https://jsonplaceholder.typicode.com/todos/1")
// make a post request
yield* client.post("https://jsonplaceholder.typicode.com/todos")

// execute a request instance
yield* client.execute(
HttpClientRequest.get("https://jsonplaceholder.typicode.com/todos/1")
)
})
```

#### Scoped `HttpClientResponse` helpers removed

The `HttpClientResponse` helpers that also eliminated the `Scope` have been removed.

Instead, you can use the `HttpClientResponse` methods directly, and explicitly
add a `Effect.scoped` to the pipeline.

```ts
import { FetchHttpClient, HttpClient } from "@effect/platform"
import { Effect } from "effect"

Effect.gen(function* () {
const client = yield* HttpClient.HttpClient

yield* client.get("https://jsonplaceholder.typicode.com/todos/1").pipe(
Effect.flatMap((response) => response.json),
Effect.scoped // eliminate the `Scope`
)
})
```

#### Some apis have been renamed

Including the `HttpClientRequest` body apis, which is to make them more
discoverable.
36 changes: 22 additions & 14 deletions packages/cluster-node/examples/sample-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,33 @@ const liveLayer = Effect.gen(function*() {
Layer.effectDiscard,
Layer.provide(Sharding.live),
Layer.provide(StorageFile.storageFile),
Layer.provide(PodsRpc.podsRpc<never>((podAddress) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
HttpClient.fetchOk.pipe(
HttpClient.mapRequest(
HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
).pipe(RpcResolver.toClient)
)),
Layer.provide(ShardManagerClientRpc.shardManagerClientRpc(
(shardManagerUri) =>
Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
return PodsRpc.podsRpc<never>((podAddress) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
HttpClient.fetchOk.pipe(
client.pipe(
HttpClient.filterStatusOk,
HttpClient.mapRequest(
HttpClientRequest.prependUrl(shardManagerUri)
HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
).pipe(RpcResolver.toClient)
)),
)
}))),
Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
return ShardManagerClientRpc.shardManagerClientRpc(
(shardManagerUri) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
client.pipe(
HttpClient.filterStatusOk,
HttpClient.mapRequest(
HttpClientRequest.prependUrl(shardManagerUri)
)
)
).pipe(RpcResolver.toClient)
)
}))),
Layer.provide(ShardingConfig.withDefaults({ shardingPort: 54322 })),
Layer.provide(Serialization.json),
Layer.provide(NodeHttpClient.layerUndici)
Expand Down
31 changes: 21 additions & 10 deletions packages/cluster-node/examples/sample-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import * as StorageFile from "@effect/cluster-node/StorageFile"
import * as ManagerConfig from "@effect/cluster/ManagerConfig"
import * as PodsHealth from "@effect/cluster/PodsHealth"
import * as ShardManager from "@effect/cluster/ShardManager"
import { HttpClient, HttpClientRequest, HttpMiddleware, HttpRouter, HttpServer } from "@effect/platform"
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpMiddleware,
HttpRouter,
HttpServer
} from "@effect/platform"
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { RpcResolver } from "@effect/rpc"
import { HttpRpcResolver, HttpRpcRouter } from "@effect/rpc-http"
Expand Down Expand Up @@ -34,17 +41,21 @@ const liveShardingManager = Effect.never.pipe(
Layer.provide(ShardManager.live),
Layer.provide(StorageFile.storageFile),
Layer.provide(PodsHealth.local),
Layer.provide(PodsRpc.podsRpc<never>((podAddress) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
HttpClient.fetchOk.pipe(
HttpClient.mapRequest(
HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
return PodsRpc.podsRpc<never>((podAddress) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
client.pipe(
HttpClient.filterStatusOk,
HttpClient.mapRequest(
HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
)
).pipe(RpcResolver.toClient)
)),
).pipe(RpcResolver.toClient)
)
}))),
Layer.provide(ManagerConfig.fromConfig),
Layer.provide(HttpClient.layer)
Layer.provide(FetchHttpClient.layer)
)

Layer.launch(liveShardingManager).pipe(
Expand Down
36 changes: 22 additions & 14 deletions packages/cluster-node/examples/sample-shard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,33 @@ const liveLayer = Sharding.registerEntity(
Layer.provide(HttpLive),
Layer.provideMerge(Sharding.live),
Layer.provide(StorageFile.storageFile),
Layer.provide(PodsRpc.podsRpc<never>((podAddress) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
HttpClient.fetchOk.pipe(
HttpClient.mapRequest(
HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
).pipe(RpcResolver.toClient)
)),
Layer.provide(ShardManagerClientRpc.shardManagerClientRpc(
(shardManagerUri) =>
Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
return PodsRpc.podsRpc<never>((podAddress) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
HttpClient.fetchOk.pipe(
client.pipe(
HttpClient.filterStatusOk,
HttpClient.mapRequest(
HttpClientRequest.prependUrl(shardManagerUri)
HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
).pipe(RpcResolver.toClient)
)),
)
}))),
Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
return ShardManagerClientRpc.shardManagerClientRpc(
(shardManagerUri) =>
HttpRpcResolver.make<ShardingServiceRpc.ShardingServiceRpc>(
client.pipe(
HttpClient.filterStatusOk,
HttpClient.mapRequest(
HttpClientRequest.prependUrl(shardManagerUri)
)
)
).pipe(RpcResolver.toClient)
)
}))),
Layer.provide(Serialization.json),
Layer.provide(NodeHttpClient.layerUndici),
Layer.provide(ShardingConfig.fromConfig)
Expand Down
17 changes: 7 additions & 10 deletions packages/platform-browser/src/BrowserHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@
* @since 1.0.0
*/
import type * as HttpClient from "@effect/platform/HttpClient"
import * as Context from "effect/Context"
import type { Effect } from "effect/Effect"
import type * as FiberRef from "effect/FiberRef"
import type { LazyArg } from "effect/Function"
import type * as Layer from "effect/Layer"
import * as internal from "./internal/httpClient.js"

/**
* @since 1.0.0
* @category clients
*/
export const xmlHttpRequest: HttpClient.HttpClient.Default = internal.makeXMLHttpRequest

/**
* @since 1.0.0
* @category layers
*/
export const layerXMLHttpRequest: Layer.Layer<HttpClient.HttpClient.Default, never, never> =
internal.layerXMLHttpRequest
export const layerXMLHttpRequest: Layer.Layer<HttpClient.HttpClient.Service> = internal.layerXMLHttpRequest

/**
* @since 1.0.0
* @category fiber refs
* @category tags
*/
export const currentXMLHttpRequest: FiberRef.FiberRef<LazyArg<XMLHttpRequest>> = internal.currentXMLHttpRequest
export class XMLHttpRequest extends Context.Tag(internal.xhrTagKey)<
XMLHttpRequest,
LazyArg<globalThis.XMLHttpRequest>
>() {}

/**
* @since 1.0.0
Expand Down
23 changes: 14 additions & 9 deletions packages/platform-browser/src/internal/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@ import type * as ClientRequest from "@effect/platform/HttpClientRequest"
import * as ClientResponse from "@effect/platform/HttpClientResponse"
import * as IncomingMessage from "@effect/platform/HttpIncomingMessage"
import * as UrlParams from "@effect/platform/UrlParams"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as FiberRef from "effect/FiberRef"
import { type LazyArg } from "effect/Function"
import { globalValue } from "effect/GlobalValue"
import * as Inspectable from "effect/Inspectable"
import * as Layer from "effect/Layer"
import * as Option from "effect/Option"
import * as Stream from "effect/Stream"
import * as HeaderParser from "multipasta/HeadersParser"

/** @internal */
export const currentXMLHttpRequest = globalValue(
"@effect/platform-browser/BrowserHttpClient/currentXMLHttpRequest",
() => FiberRef.unsafeMake<LazyArg<XMLHttpRequest>>(() => new XMLHttpRequest())
)
export const xhrTagKey = "@effect/platform-browser/BrowserHttpClient/XMLHttpRequest"

const xhrTag = Context.GenericTag<LazyArg<XMLHttpRequest>>(xhrTagKey)

/** @internal */
export const currentXHRResponseType = globalValue(
Expand All @@ -36,10 +35,15 @@ export const withXHRArrayBuffer = <A, E, R>(effect: Effect.Effect<A, E, R>): Eff
"arraybuffer"
)

/** @internal */
export const makeXMLHttpRequest = Client.makeDefault((request, url, signal, fiber) =>
const makeXhr = () => new XMLHttpRequest()

const makeXMLHttpRequest = Client.makeService((request, url, signal, fiber) =>
Effect.suspend(() => {
const xhr = fiber.getFiberRef(currentXMLHttpRequest)()
const xhr = Context.getOrElse(
fiber.getFiberRef(FiberRef.currentContext),
xhrTag,
() => makeXhr
)()
signal.addEventListener("abort", () => {
xhr.abort()
xhr.onreadystatechange = null
Expand Down Expand Up @@ -70,6 +74,7 @@ export const makeXMLHttpRequest = Client.makeDefault((request, url, signal, fibe
))
}
onChange()
return Effect.void
})
)
})
Expand Down Expand Up @@ -332,4 +337,4 @@ class ClientResponseImpl extends IncomingMessageImpl<Error.ResponseError> implem
}

/** @internal */
export const layerXMLHttpRequest = Layer.succeed(Client.HttpClient, makeXMLHttpRequest)
export const layerXMLHttpRequest = Client.layerMergedContext(Effect.succeed(makeXMLHttpRequest))
Loading

0 comments on commit 9c4ba21

Please sign in to comment.