06·03Source Patterns

Keep platform clients behind a small `Context.Service`. At the boundary, apply resilience policy, decode the returned `unknown` with Schema, and map infrastructure failures into application-owned errors.

33 — Source boundary patterns

Keep platform clients behind a small Context.Service. At the boundary, apply resilience policy, decode the returned unknown with Schema, and map infrastructure failures into application-owned errors.

The exercise combines an HTTP-shaped port, bounded retry, a deadline, Schema decoding, and a fake Layer. Business logic never imports a concrete client or trusts an unvalidated response.


The full test suite

The index.test.ts for this lesson, verbatim from steps/33-source-patterns/.

index.test.ts
import { describe, expect, it } from "vite-plus/test"
import { Context, Data, Effect, Layer, Schedule, Schema } from "effect"
class HttpError extends Data.TaggedError("HttpError")<{ readonly status: number }> {}
class HttpClient extends Context.Service<HttpClient, { readonly get: (url: string) => Effect.Effect<unknown, HttpError> }>()("HttpClient") {}
const Payload = Schema.Struct({ value: Schema.Number })
const fetchValue = (url: string) => Effect.gen(function*() {
const http = yield* HttpClient
const unknown = yield* http.get(url).pipe(Effect.retry(Schedule.recurs(2)), Effect.timeout("1 second"))
return yield* Schema.decodeUnknownEffect(Payload)(unknown)
})
describe("source patterns", () => {
it("keeps source, resilience, and decoding at the boundary", async () => {
const fake = Layer.succeed(HttpClient, { get: () => Effect.succeed({ value: 42 }) })
expect(await Effect.runPromise(fetchValue("/value").pipe(Effect.provide(fake)))).toEqual({ value: 42 })
})
})
← PreviousRetry & TimeoutNext →Streams