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/.
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 }) })})