import { Effect, Layer, Schedule, Schema, ServiceMap } from "effect"
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpClientResponse
} from "effect/unstable/http"
import { flow } from "effect"
class Todo extends Schema.Class<Todo>("Todo")({
userId: Schema.Number,
id: Schema.Number,
title: Schema.String,
completed: Schema.Boolean
}) {}
export class JsonPlaceholder extends ServiceMap.Service<JsonPlaceholder, {
readonly allTodos: Effect.Effect<ReadonlyArray<Todo>, JsonPlaceholderError>
getTodo(id: number): Effect.Effect<Todo, JsonPlaceholderError>
createTodo(todo: Omit<Todo, "id">): Effect.Effect<Todo, JsonPlaceholderError>
}>()("app/JsonPlaceholder") {
static readonly layer = Layer.effect(
JsonPlaceholder,
Effect.gen(function*() {
// Configure client with common middleware
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.mapRequest(flow(
HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"),
HttpClientRequest.acceptJson
)),
HttpClient.filterStatusOk,
HttpClient.retryTransient({
schedule: Schedule.exponential(100),
times: 3
})
)
const allTodos = client.get("/todos").pipe(
Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Array(Todo))),
Effect.mapError((cause) => new JsonPlaceholderError({ cause })),
Effect.withSpan("JsonPlaceholder.allTodos")
)
const getTodo = Effect.fn("JsonPlaceholder.getTodo")(function*(id: number) {
yield* Effect.annotateCurrentSpan({ id })
const todo = yield* client.get(`/todos/${id}`, {
urlParams: { format: "json" }
}).pipe(
Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)),
Effect.mapError((cause) => new JsonPlaceholderError({ cause }))
)
return todo
})
const createTodo = Effect.fn("JsonPlaceholder.createTodo")(function*(todo: Omit<Todo, "id">) {
yield* Effect.annotateCurrentSpan({ title: todo.title })
const createdTodo = yield* HttpClientRequest.post("/todos").pipe(
HttpClientRequest.setUrlParams({ format: "json" }),
HttpClientRequest.bodyJsonUnsafe(todo),
client.execute,
Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)),
Effect.mapError((cause) => new JsonPlaceholderError({ cause }))
)
return createdTodo
})
return JsonPlaceholder.of({
allTodos,
getTodo,
createTodo
})
})
).pipe(
Layer.provide(FetchHttpClient.layer)
)
}
export class JsonPlaceholderError extends Schema.TaggedErrorClass<JsonPlaceholderError>()("JsonPlaceholderError", {
cause: Schema.Defect
}) {}