From 51b546c50d24313be4071ee3036d5c88acaac160 Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Sat, 21 Oct 2023 14:06:23 +0200 Subject: [PATCH] R/use-platform-http-client (#53) * extend * convert to use platform client * Remove legacy exports * add changeset --- .changeset/weak-knives-remain.md | 7 ++ packages/core/_src/_global.ts | 5 -- packages/core/_src/http.ts | 4 - packages/core/package.json | 10 --- .../core/vendor/effect-platform-tsplus.json | 21 +++++ packages/prelude/_src/client/fetch.ts | 81 +++++++++++++++---- packages/vue/_src/runtime.ts | 5 +- packages/vue/package.json | 3 +- pnpm-lock.yaml | 14 ++++ 9 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 .changeset/weak-knives-remain.md delete mode 100644 packages/core/_src/http.ts diff --git a/.changeset/weak-knives-remain.md b/.changeset/weak-knives-remain.md new file mode 100644 index 000000000..1749c8585 --- /dev/null +++ b/.changeset/weak-knives-remain.md @@ -0,0 +1,7 @@ +--- +"@effect-app/prelude": minor +"@effect-app/core": minor +"@effect-app/vue": minor +--- + +feat: Convert clientFor to use @effect/platform HttpClient diff --git a/packages/core/_src/_global.ts b/packages/core/_src/_global.ts index a3d435a62..7e9ca90ac 100644 --- a/packages/core/_src/_global.ts +++ b/packages/core/_src/_global.ts @@ -233,11 +233,6 @@ import { Lens, lens, Optic } from "@fp-ts/optic" */ import type { lazyGetter } from "@effect-app/core/utils" -/** - * @tsplus global - */ -import { HttpClient, HttpClientFetch } from "@effect-app/core/http" - // TODO: these may be problematic global imports causing bundling issues? // "import type {} from" doesn't work outside this package import "./_global.ext.js" diff --git a/packages/core/_src/http.ts b/packages/core/_src/http.ts deleted file mode 100644 index 9aa124c36..000000000 --- a/packages/core/_src/http.ts +++ /dev/null @@ -1,4 +0,0 @@ -// TODO: should eventually be replaced by @effect/platform - -export * as HttpClientFetch from "./http/http-client-fetch.js" -export * as HttpClient from "./http/http-client.js" diff --git a/packages/core/package.json b/packages/core/package.json index 8883f62ca..d60c20d52 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -294,16 +294,6 @@ "default": "./_cjs/global.cjs" } }, - "./http": { - "import": { - "types": "./dist/http.d.ts", - "default": "./dist/http.js" - }, - "require": { - "types": "./dist/http.d.ts", - "default": "./_cjs/http.cjs" - } - }, "./http/http-client": { "import": { "types": "./dist/http/http-client.d.ts", diff --git a/packages/core/vendor/effect-platform-tsplus.json b/packages/core/vendor/effect-platform-tsplus.json index eab58fb42..49da43069 100644 --- a/packages/core/vendor/effect-platform-tsplus.json +++ b/packages/core/vendor/effect-platform-tsplus.json @@ -124,6 +124,17 @@ } ] }, + { + "definitionName": "patch", + "definitionKind": "const", + "extensions": [ + { + "kind": "static", + "typeName": "effect/platform/Http/ClientRequest.Ops", + "name": "patch" + } + ] + }, { "definitionName": "put", "definitionKind": "const", @@ -183,6 +194,11 @@ "definitionName": "jsonBody", "definitionKind": "const", "extensions": [ + { + "kind": "pipeable", + "typeName": "effect/platform/Http/ClientRequest", + "name": "jsonBody" + }, { "kind": "static", "typeName": "effect/platform/Http/ClientRequest.Ops", @@ -194,6 +210,11 @@ "definitionName": "unsafeJsonBody", "definitionKind": "const", "extensions": [ + { + "kind": "pipeable", + "typeName": "effect/platform/Http/ClientRequest", + "name": "unsafeJsonBody" + }, { "kind": "static", "typeName": "effect/platform/Http/ClientRequest.Ops", diff --git a/packages/prelude/_src/client/fetch.ts b/packages/prelude/_src/client/fetch.ts index 85a0f3a9a..568c872a8 100644 --- a/packages/prelude/_src/client/fetch.ts +++ b/packages/prelude/_src/client/fetch.ts @@ -1,36 +1,85 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { HttpClient } from "@effect-app/core/http" +import type { HttpClient as HttpClientLegacy } from "@effect-app/core/http" import { constant, flow } from "@effect-app/prelude/Function" import type { ReqRes, RequestSchemed } from "@effect-app/prelude/schema" -import { StringId } from "@effect-app/prelude/schema" import { Path } from "path-parser" import qs from "query-string" -import { getConfig } from "./config.js" +import { ApiConfig } from "./config.js" -export type FetchError = HttpClient.HttpError +export type FetchError = HttpClientLegacy.HttpError export class ResponseError { public readonly _tag = "ResponseError" constructor(public readonly error: unknown) {} } -export function fetchApi(method: HttpClient.Method, path: string, body?: unknown) { - const request = HttpClient.request(method, "JSON", "JSON") - return getConfig(({ apiUrl, headers }) => - HttpClient - .withHeaders({ - "request-id": headers.flatMap((_) => _.get("request-id")).value ?? StringId.make(), - ...headers.map((_) => Object.fromEntries(_)).value - })(request(`${apiUrl}${path}`, body)) - .map((x) => ({ ...x, body: x.body.value ?? null })) - ) +const getClient = HttpClient.flatMap((defaultClient) => + ApiConfig + .Tag + .map(({ apiUrl, headers }) => + defaultClient + .filterStatusOk + .mapRequest(ClientRequest.acceptJson) + .mapRequest(ClientRequest.prependUrl(apiUrl)) + .mapRequest(ClientRequest.setHeaders({ + "request-id": headers.flatMap((_) => _.get("request-id")).value ?? StringId.make(), + ...headers.map((_) => Object.fromEntries(_)).value + })) + .tapRequest((r) => + Effect + .logDebug(`[HTTP] ${r.method}`) + .annotateLogs("url", r.url) + .annotateLogs("body", r.body._tag === "Uint8Array" ? new TextDecoder().decode(r.body.body) : r.body._tag) + .annotateLogs("headers", r.headers) + ) + .mapEffect((_) => _.json.map((body) => ({ status: _.status, body, headers: _.headers }))) + .catchTags({ + "ResponseError": (err) => + err + .response + .text + // TODO + .orDie + .flatMap((_) => + Effect.fail({ + _tag: "HttpErrorResponse" as const, + response: { body: Option.fromNullable(_), status: err.response.status, headers: err.response.headers } + } as HttpClientLegacy.HttpResponseError) + ), + "RequestError": (err) => + Effect.fail({ _tag: "HttpErrorRequest", error: err.error } as HttpClientLegacy.HttpRequestError) + }) + ) +) + +export function fetchApi( + method: HttpClientLegacy.Method, + path: string, + body?: unknown +) { + return getClient + .flatMap((client) => { + const req = method === "DELETE" + ? ClientRequest.del + : method === "GET" + ? ClientRequest.get + : method === "POST" + ? ClientRequest.post + : method === "PUT" + ? ClientRequest.put + : ClientRequest.patch + + return client(req(path).unsafeJsonBody(body)) + .map((x) => ({ ...x, body: x.body ?? null })) + }) } + export function fetchApi2S( encodeRequest: (a: RequestA) => RequestE, decodeResponse: (u: unknown) => Effect ) { const decodeRes = (u: unknown) => decodeResponse(u).mapError((err) => new ResponseError(err)) - return (method: HttpClient.Method, path: Path) => (req: RequestA) => + return (method: HttpClientLegacy.Method, path: Path) => (req: RequestA) => fetchApi( method, method === "DELETE" @@ -132,7 +181,7 @@ export function mapResponseM(map: (t: T) => Effect) { }) } } -export type FetchResponse = { body: T; headers: HttpClient.Headers; status: number } +export type FetchResponse = { body: T; headers: HttpClientLegacy.Headers; status: number } export const EmptyResponse = Object.freeze({ body: null, headers: {}, status: 404 }) export const EmptyResponseM = Effect(EmptyResponse) diff --git a/packages/vue/_src/runtime.ts b/packages/vue/_src/runtime.ts index 285d30ea6..671a449ed 100644 --- a/packages/vue/_src/runtime.ts +++ b/packages/vue/_src/runtime.ts @@ -1,9 +1,10 @@ import type { Http } from "@effect-app/core/http/http-client" import { ApiConfig } from "@effect-app/prelude/client" -import { fetch } from "cross-fetch" import * as Scope from "effect/Scope" import { initRuntime } from "./internal.js" +import * as HttpClientBrowser from "@effect/platform-browser/HttpClient" + export { initRuntime } from "./internal.js" const DefaultApiConfig = Config.all({ @@ -15,7 +16,7 @@ const DefaultApiConfig = Config.all({ }) export function makeApiLayers(config: Config = DefaultApiConfig) { - return HttpClientFetch.Client(fetch) + ApiConfig.Live(config) + return HttpClientBrowser.client.layer + ApiConfig.Live(config) } export function makeAppRuntime(layer: Layer) { diff --git a/packages/vue/package.json b/packages/vue/package.json index 2f6ef5f56..c421367bd 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -4,13 +4,14 @@ "license": "MIT", "type": "module", "dependencies": { + "@effect/platform-browser": "^0.12.0", "@effect-app/prelude": "workspace:*", "@effect-app/vue": "workspace:*", "@effect-app/core": "workspace:*", "@effect-app/schema": "workspace:*", - "effect": "^2.0.0-next.50", "@fp-ts/optic": "^0.13.0", "@formatjs/intl": "^2.9.4", + "effect": "^2.0.0-next.50", "query-string": "^8.1.0", "swrv": "^1.0.4", "vue": "^3.3.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 007d00463..93c735d18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -725,6 +725,9 @@ importers: '@effect-app/vue': specifier: workspace:* version: 'link:' + '@effect/platform-browser': + specifier: ^0.12.0 + version: 0.12.0(@effect/schema@0.45.5)(effect@2.0.0-next.50) '@formatjs/intl': specifier: ^2.9.4 version: 2.9.4(@effect-app/typescript@5.3.0-tsplus.20231009) @@ -1566,6 +1569,17 @@ packages: dev: true patched: true + /@effect/platform-browser@0.12.0(@effect/schema@0.45.5)(effect@2.0.0-next.50): + resolution: {integrity: sha512-VB7Lj/6sO4avWZWcDQy2yPsJU7GAvBFr5BtunWjM2OYH6AfS0aq3gRTxKd+xdRqIh1Lbq1VHoJudBMtaMdSOPQ==} + peerDependencies: + effect: 2.0.0-next.50 + dependencies: + '@effect/platform': 0.24.0(@effect/schema@0.45.5)(effect@2.0.0-next.50) + effect: 2.0.0-next.50 + transitivePeerDependencies: + - '@effect/schema' + dev: false + /@effect/platform@0.24.0(@effect/schema@0.45.5)(effect@2.0.0-next.50): resolution: {integrity: sha512-IDfdNxgfEMOlONXL2/cQ9/Mx7z5jz68ufeOkofo12xkzUOAZzw4c96BPQ9K7kmwaEjIve9YNF52dv1HO3z78AA==} peerDependencies: