Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor /platform HttpClient #3537

Merged
merged 6 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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