Skip to content

Commit

Permalink
improve: cleanup client legacy
Browse files Browse the repository at this point in the history
  • Loading branch information
patroza committed Nov 13, 2024
1 parent 9301abb commit a30da33
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 145 deletions.
6 changes: 6 additions & 0 deletions .changeset/gentle-laws-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"effect-app": minor
"@effect-app/vue": minor
---

improve: cleanup client legacy
20 changes: 10 additions & 10 deletions packages/effect-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,24 +408,24 @@
"default": "./_cjs/client.cjs"
}
},
"./client/clientFor": {
"./client/apiClient": {
"import": {
"types": "./dist/client/clientFor.d.ts",
"default": "./dist/client/clientFor.js"
"types": "./dist/client/apiClient.d.ts",
"default": "./dist/client/apiClient.js"
},
"require": {
"types": "./dist/client/clientFor.d.ts",
"default": "./_cjs/client/clientFor.cjs"
"types": "./dist/client/apiClient.d.ts",
"default": "./_cjs/client/apiClient.cjs"
}
},
"./client/config": {
"./client/clientFor": {
"import": {
"types": "./dist/client/config.d.ts",
"default": "./dist/client/config.js"
"types": "./dist/client/clientFor.d.ts",
"default": "./dist/client/clientFor.js"
},
"require": {
"types": "./dist/client/config.d.ts",
"default": "./_cjs/client/config.cjs"
"types": "./dist/client/clientFor.d.ts",
"default": "./_cjs/client/clientFor.cjs"
}
},
"./client/errors": {
Expand Down
2 changes: 1 addition & 1 deletion packages/effect-app/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// codegen:start {preset: barrel, include: ./client/*.ts}
export * from "./client/apiClient.js"
export * from "./client/clientFor.js"
export * from "./client/config.js"
export * from "./client/errors.js"
export * from "./client/req.js"
// codegen:end
36 changes: 36 additions & 0 deletions packages/effect-app/src/client/apiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Config, Context, Effect, HashMap, Layer, Option } from "../internal/lib.js"

import { HttpClient, HttpClientRequest } from "../http.js"

export interface ApiConfig {
url: string
headers: Option<HashMap<string, string>>
}

export const DefaultApiConfig = Config.all({
url: Config.string("apiUrl").pipe(Config.withDefault("/api")),
headers: Config
.hashMap(
Config.string(),
"headers"
)
.pipe(Config.option)
})

const apiClient = (config: ApiConfig) =>
Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
return {
client: client.pipe(
HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
HttpClient.mapRequest(
HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => HashMap.empty())))
)
)
}
})

export class ApiClient extends Context.TagId("ApiClient")<ApiClient, Effect.Success<ReturnType<typeof apiClient>>>() {
static layer = (apiConfig: ApiConfig) => this.toLayer(apiClient(apiConfig))
static layerFromConfig = DefaultApiConfig.pipe(Effect.map(this.layer), Layer.unwrapEffect)
}
178 changes: 82 additions & 96 deletions packages/effect-app/src/client/clientFor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-explicit-any */

import type { Rpc } from "@effect/rpc"
import { RpcResolver } from "@effect/rpc"
import { HttpRpcResolver } from "@effect/rpc-http"
import type { RpcRouter } from "@effect/rpc/RpcRouter"
Expand All @@ -10,9 +11,9 @@ import type * as Request from "effect/Request"
import type { Path } from "path-parser"
import qs from "query-string"
import type { Schema } from "../internal/lib.js"
import { Effect, flow, HashMap, Layer, Option, Predicate, Record, Struct } from "../internal/lib.js"
import { Effect, flow, Layer, Predicate, Record, Struct } from "../internal/lib.js"
import * as S from "../Schema.js"
import { ApiConfig } from "./config.js"
import { ApiClient } from "./apiClient.js"

export function makePathWithQuery(
path: Path,
Expand Down Expand Up @@ -55,37 +56,27 @@ export function makePathWithBody(

type Requests = Record<string, any>

const apiClient = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const config = yield* ApiConfig.Tag
return client.pipe(
HttpClient.mapRequest(HttpClientRequest.prependUrl(config.apiUrl + "/rpc")),
HttpClient.mapRequest(
HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => HashMap.empty())))
)
)
})

export type Client<M extends Requests> = RequestHandlers<
ApiConfig | HttpClient.HttpClient,
never, // SupportedErrors | FetchError | ResError,
never,
never,
M
>

export function makeClientFor(layers: Layer.Layer<never, never, never>) {
export function makeClientFor(requestLevelLayers: Layer.Layer<never, never, never>) {
const cache = new Map<any, Client<any>>()

return <M extends Requests>(
models: M
): Client<Omit<M, "meta">> => {
const found = cache.get(models)
if (found) {
return found
}
const m = clientFor_(models, layers)
cache.set(models, m)
return m
}
): Effect<Client<Omit<M, "meta">>, never, ApiClient> =>
Effect.gen(function*() {
const found = cache.get(models)
if (found) {
return found
}
const m = yield* clientFor_(models, requestLevelLayers)
cache.set(models, m)
return m
})
}

type Req = S.Schema.All & {
Expand All @@ -97,113 +88,108 @@ type Req = S.Schema.All & {
config?: Record<string, any>
}

function clientFor_<M extends Requests>(models: M, layers = Layer.empty) {
type Filtered = {
[K in keyof Requests as Requests[K] extends Req ? K : never]: Requests[K] extends Req ? Requests[K] : never
}
const filtered = typedKeysOf(models).reduce((acc, cur) => {
if (
Predicate.isObject(models[cur])
&& (models[cur].success)
) {
acc[cur as keyof Filtered] = models[cur]
const clientFor_ = <M extends Requests>(models: M, requestLevelLayers = Layer.empty) =>
Effect.gen(function*() {
type Filtered = {
[K in keyof Requests as Requests[K] extends Req ? K : never]: Requests[K] extends Req ? Requests[K] : never
}
return acc
}, {} as Record<keyof Filtered, Req>)

const meta = (models as any).meta as { moduleName: string }
if (!meta) throw new Error("No meta defined in Resource!")
// TODO: Record.filter
const filtered = typedKeysOf(models).reduce((acc, cur) => {
if (
Predicate.isObject(models[cur])
&& (models[cur].success)
) {
acc[cur as keyof Filtered] = models[cur]
}
return acc
}, {} as Record<keyof Filtered, Req>)

const resolver = flow(
HttpRpcResolver.make<RpcRouter<any, any>>,
(_) => RpcResolver.toClient(_ as any)
)
const meta = (models as any).meta as { moduleName: string }
if (!meta) throw new Error("No meta defined in Resource!")

const baseClient = apiClient.pipe(
Effect.andThen(HttpClient.mapRequest(HttpClientRequest.appendUrl("/" + meta.moduleName)))
)
const resolver = flow(
HttpRpcResolver.make<RpcRouter<any, any>>,
(_) => RpcResolver.toClient(_ as any)
)

return (typedKeysOf(filtered)
.reduce((prev, cur) => {
const h = filtered[cur]!
const baseClient = yield* ApiClient.pipe(
Effect.andThen((_) => HttpClient.mapRequest(_.client, HttpClientRequest.appendUrl("/" + meta.moduleName)))
)

const Request = h
const Response = h.success
return (typedKeysOf(filtered)
.reduce((prev, cur) => {
const h = filtered[cur]!

const requestName = `${meta.moduleName}.${cur as string}`
.replaceAll(".js", "")
const Request = h
const Response = h.success

const requestMeta = {
Request,
name: requestName
}
const requestName = `${meta.moduleName}.${cur as string}`
.replaceAll(".js", "")

const client = baseClient.pipe(
Effect.andThen(HttpClient.mapRequest(HttpClientRequest.appendUrlParam("action", cur as string))),
Effect.andThen(resolver)
)
const requestMeta = {
Request,
name: requestName
}

const fields = Struct.omit(Request.fields, "_tag")
// @ts-expect-error doc
prev[cur] = Object.keys(fields).length === 0
? {
handler: client
.pipe(
Effect.andThen((cl) => cl(new Request())),
const client: <Req extends Schema.TaggedRequest.All>(request: Req) => Rpc.Rpc.Result<Req, unknown> = baseClient
.pipe(
HttpClient.mapRequest(HttpClientRequest.appendUrlParam("action", cur as string)),
resolver
)

const fields = Struct.omit(Request.fields, "_tag")
// @ts-expect-error doc
prev[cur] = Object.keys(fields).length === 0
? {
handler: client(new Request() as Schema.TaggedRequest.All).pipe(
Effect.withSpan("client.request " + requestName, {
captureStackTrace: false,
attributes: { "request.name": requestName }
}),
Effect.provide(layers)
Effect.provide(requestLevelLayers)
),
...requestMeta,
raw: {
handler: client
.pipe(
Effect.andThen((cl) => cl(new Request())),
...requestMeta,
raw: {
handler: client(new Request() as Schema.TaggedRequest.All).pipe(
Effect.flatMap((res) => S.encode(Response)(res)), // TODO,
Effect.withSpan("client.request " + requestName, {
captureStackTrace: false,
attributes: { "request.name": requestName }
}),
Effect.provide(layers)
Effect.provide(requestLevelLayers)
),
...requestMeta
...requestMeta
}
}
}
: {
handler: (req: any) =>
client
.pipe(
Effect.andThen((cl) => cl(new Request(req))),
: {
handler: (req: any) =>
client(new Request(req) as Schema.TaggedRequest.All).pipe(
Effect.withSpan("client.request " + requestName, {
captureStackTrace: false,
attributes: { "request.name": requestName }
}),
Effect.provide(layers)
Effect.provide(requestLevelLayers)
),

...requestMeta,
raw: {
handler: (req: any) =>
client
.pipe(
Effect.andThen((cl) => cl(new Request(req))),
...requestMeta,
raw: {
handler: (req: any) =>
client(new Request(req) as Schema.TaggedRequest.All).pipe(
Effect.flatMap((res) => S.encode(Response)(res)), // TODO,
Effect.withSpan("client.request " + requestName, {
captureStackTrace: false,
attributes: { "request.name": requestName }
}),
Effect.provide(layers)
Effect.provide(requestLevelLayers)
),

...requestMeta
...requestMeta
}
}
}

return prev
}, {} as Client<M>))
}
return prev
}, {} as Client<M>))
})

export type ExtractResponse<T> = T extends Schema<any, any, any> ? Schema.Type<T>
: T extends unknown ? void
Expand Down
15 changes: 0 additions & 15 deletions packages/effect-app/src/client/config.ts

This file was deleted.

Loading

0 comments on commit a30da33

Please sign in to comment.