ProvenPin Install

Effect 4 Schema classes, typed errors and retries, tested with @effect/vitest: working versions (tested 2026-10-15)

Effect 4 Schema.Class models with checks, brands, FiniteFromString and fromJsonString for decode and encode, TaggedError failures, and Effect.retry with exponential backoff verified on the TestClock using @effect/vitest.

setup_failed0 passed · 1 failed2026-10-15 14:12 UTC
Status
setup_failed
Last passed
2026-10-15 14:12 UTC
Tests
0 / 1
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
effect@4.0.5@effect/vitest@4.0.5vitest@5.0.4@types/node@26.6.5
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38755782594

The code

Exactly the files that passed. Your agent can pull them with get_recipe("effect-schema-vitest") (install), or see them on GitHub.

package.json
{
  "name": "effect-schema-vitest",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
  },
  "dependencies": {
    "effect": "4.0.5"
  },
  "devDependencies": {
    "@effect/vitest": "4.0.5",
    "vitest": "5.0.4",
    "@types/node": "26.6.5"
  }
}
src/orders.ts
import { Effect, Schedule, Schema } from "effect";

/** Wire format: JSON with string numbers, as many legacy APIs send them. */
export class LineItem extends Schema.Class<LineItem>("LineItem")({
  sku: Schema.String.check(Schema.isPattern(/^[A-Z]{3}-\d{3}$/)),
  qty: Schema.Int.check(Schema.isGreaterThan(0)),
  // FiniteFromString rejects "x"; NumberFromString would decode it to NaN
  unitCents: Schema.FiniteFromString,
}) {
  get totalCents() { return this.qty * this.unitCents; }
}

export class Order extends Schema.Class<Order>("Order")({
  id: Schema.String.pipe(Schema.brand("OrderId")),
  customer: Schema.Trim.check(Schema.isMinLength(1)),
  status: Schema.Literals(["pending", "paid", "shipped"]),
  items: Schema.Array(LineItem).check(Schema.isMinLength(1)),
  note: Schema.optionalKey(Schema.String),
}) {
  get totalCents() { return this.items.reduce((s, i) => s + i.totalCents, 0); }
}

/** Decodes a raw JSON string straight into an Order instance. */
export const OrderFromJson = Schema.fromJsonString(Order);

export class InvalidOrder extends Schema.TaggedError<InvalidOrder>()("InvalidOrder", { message: Schema.String }) {}
export class UpstreamUnavailable extends Schema.TaggedError<UpstreamUnavailable>()("UpstreamUnavailable", { attempt: Schema.Number }) {}

export const parseOrder = (json: string) =>
  // errors: "all" collects every issue instead of stopping at the first one
  Schema.decodeUnknownEffect(OrderFromJson)(json, { errors: "all" }).pipe(Effect.mapError((e) => new InvalidOrder({ message: e.message })));

export const encodeOrder = (o: Order) => Schema.encodeEffect(OrderFromJson)(o);

/**
 * Fetches an order through a flaky upstream, retrying UpstreamUnavailable with exponential
 * backoff (100ms, 200ms, ...) up to 3 retries. InvalidOrder is never retried.
 */
export const fetchOrder = (fetchRaw: (attempt: number) => Effect.Effect<string, UpstreamUnavailable>) => {
  let attempt = 0;
  return Effect.suspend(() => fetchRaw(++attempt)).pipe(
    Effect.retry({ schedule: Schedule.exponential("100 millis"), times: 3 }),
    Effect.flatMap(parseOrder),
  );
};
test/orders.test.ts
import { assert, describe, it } from "@effect/vitest";
import { Effect, Exit, Fiber } from "effect";
import { TestClock } from "effect/testing";
import { parseOrder, encodeOrder, fetchOrder, Order, UpstreamUnavailable } from "../src/orders.ts";

const raw = JSON.stringify({ id: "o-1", customer: "  Ada  ", status: "paid", items: [{ sku: "HAT-001", qty: 2, unitCents: "2499" }, { sku: "GLV-010", qty: 1, unitCents: "899" }] });

describe("Effect Schema orders", () => {
  it.effect("decodes JSON into class instances with transformations applied", () =>
    Effect.gen(function* () {
      const order = yield* parseOrder(raw);
      assert.isTrue(order instanceof Order);
      assert.strictEqual(order.customer, "Ada");
      assert.strictEqual(order.items[0].unitCents, 2499);
      assert.strictEqual(order.totalCents, 2 * 2499 + 899);
      assert.isFalse("note" in order);
    }));

  it.effect("encodes back to the wire format", () =>
    Effect.gen(function* () {
      const order = yield* parseOrder(raw);
      const json = JSON.parse(yield* encodeOrder(order));
      assert.strictEqual(json.items[0].unitCents, "2499");
      assert.strictEqual(json.customer, "Ada");
    }));

  it.effect("invalid input fails with a typed InvalidOrder error", () =>
    Effect.gen(function* () {
      const bad = JSON.stringify({ id: "o-2", customer: " ", status: "lost", items: [{ sku: "bad", qty: 0, unitCents: "x" }] });
      const exit = yield* Effect.exit(parseOrder(bad));
      assert.isTrue(Exit.isFailure(exit));
      const err = yield* Effect.flip(parseOrder(bad));
      assert.strictEqual(err._tag, "InvalidOrder");
      for (const field of ["customer", "status", "sku", "qty", "unitCents"]) assert.include(err.message, field);
      const notJson = yield* Effect.flip(parseOrder("{oops"));
      assert.strictEqual(notJson._tag, "InvalidOrder");
    }));

  it.effect("retries the upstream with backoff on the TestClock", () =>
    Effect.gen(function* () {
      const calls: number[] = [];
      const upstream = (attempt: number) => {
        calls.push(attempt);
        return attempt < 3 ? Effect.fail(new UpstreamUnavailable({ attempt })) : Effect.succeed(raw);
      };
      const fiber = yield* Effect.forkChild(fetchOrder(upstream));
      yield* TestClock.adjust("100 millis");
      yield* TestClock.adjust("200 millis");
      const order = yield* Fiber.join(fiber);
      assert.deepStrictEqual(calls, [1, 2, 3]);
      assert.strictEqual(order.status, "paid");
    }));

  it.effect("gives up after the retry budget with the last upstream error", () =>
    Effect.gen(function* () {
      const fiber = yield* Effect.forkChild(Effect.flip(fetchOrder((attempt) => Effect.fail(new UpstreamUnavailable({ attempt })))));
      yield* TestClock.adjust("10 seconds");
      const err = yield* Fiber.join(fiber);
      assert.strictEqual(err._tag, "UpstreamUnavailable");
      assert.strictEqual((err as UpstreamUnavailable).attempt, 4);
    }));
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.