agentsclimarketplace

Effect ts batching caching

Skill marzun9620/agent_skills/effect-ts/skills/effect-ts-batching-caching

My personal Claude Code skills — 59 of them. Use any, fork the repo, or contribute yours. Install with /plugin marketplace add marzun9620/agent_skills

Install
npx -y skills add marzun9620/agent_skills --skill effect-ts-batching-caching

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

This skill should be used when the user asks about "Effect batching", "request batching", "Effect caching", "Cache", "Request", "RequestResolver", "Effect.cached", "Effect.cachedWithTTL", "automatic batching", "N+1 problem", "data loader pattern", "deduplication", or needs to understand how Effect optimizes API calls through batching and caching.

SKILL.md

7.7 KB, as published. Nobody here has run it

Batching and Caching in Effect

Overview

Effect provides automatic optimization for API calls:

  • Batching - Combine multiple requests into single API calls
  • Caching - Avoid redundant requests with smart caching
  • Deduplication - Prevent duplicate concurrent requests

This solves the N+1 query problem automatically.

The Problem: N+1 Queries

const program = Effect.gen(function* () {
  const todos = yield* getTodos();

  const owners = yield* Effect.forEach(todos, (todo) => getUserById(todo.ownerId), { concurrency: "unbounded" });
});

Effect's batching transforms this into optimized batch calls.

Request-Based Batching

Step 1: Define Request Types

import { Request } from "effect";

// Define request shape
interface GetUserById extends Request.Request<User, UserNotFound> {
  readonly _tag: "GetUserById";
  readonly id: number;
}

// Create tagged constructor
const GetUserById = Request.tagged<GetUserById>("GetUserById");

Step 2: Create Resolver

import { RequestResolver, Effect } from "effect";

// Batched resolver - handles multiple requests at once
const GetUserByIdResolver = RequestResolver.makeBatched((requests: ReadonlyArray<GetUserById>) =>
  Effect.gen(function* () {
    // Single batch API call
    const users = yield* Effect.tryPromise(() =>
      fetch("/api/users/batch", {
        method: "POST",
        body: JSON.stringify({ ids: requests.map((r) => r.id) }),
      }).then((res) => res.json()),
    );

    // Complete each request with its result
    yield* Effect.forEach(requests, (request, index) => Request.completeEffect(request, Effect.succeed(users[index])));
  }),
);

Step 3: Define Query

const getUserById = (id: number) => Effect.request(GetUserById({ id }), GetUserByIdResolver);

Step 4: Use with Automatic Batching

const program = Effect.gen(function* () {
  const todos = yield* getTodos();

  const owners = yield* Effect.forEach(todos, (todo) => getUserById(todo.ownerId), { concurrency: "unbounded" });
});

Resolver Types

Standard Resolver (No Batching)

const SingleUserResolver = RequestResolver.fromEffect((request: GetUserById) =>
  Effect.tryPromise(() => fetch(`/api/users/${request.id}`).then((r) => r.json())),
);

Batched Resolver

const BatchedUserResolver = RequestResolver.makeBatched((requests: ReadonlyArray<GetUserById>) =>
  // Handle all requests in one call
  batchFetch(requests),
);

Resolver with Context

const UserResolverWithContext = RequestResolver.makeBatched((requests: ReadonlyArray<GetUserById>) =>
  Effect.gen(function* () {
    // Access services from context
    const httpClient = yield* HttpClient;
    const logger = yield* Logger;

    yield* logger.info(`Batching ${requests.length} user requests`);

    return yield* httpClient.post("/api/users/batch", {
      ids: requests.map((r) => r.id),
    });
  }),
);

// Provide context to resolver
const ContextualResolver = UserResolverWithContext.pipe(RequestResolver.provideContext(context));

Caching

Effect.cached - Memoize Effect Result

import { Effect } from "effect";

const fetchConfig = Effect.promise(() => fetch("/api/config").then((r) => r.json()));

const cachedConfig = yield * Effect.cached(fetchConfig);

const config1 = yield * cachedConfig;
const config2 = yield * cachedConfig;

Effect.cachedWithTTL - Time-Based Expiry

const cachedUser = yield * Effect.cachedWithTTL(fetchCurrentUser, "5 minutes");

const user1 = yield * cachedUser;
yield * Effect.sleep("6 minutes");
const user2 = yield * cachedUser;

Effect.cachedInvalidateWithTTL - Manual Invalidation

const [cachedUser, invalidate] = yield * Effect.cachedInvalidateWithTTL(fetchCurrentUser, "5 minutes");

const user = yield * cachedUser;
yield * invalidate;
const freshUser = yield * cachedUser;

Cache Service

For more control, use the Cache service:

import { Cache } from "effect";

const program = Effect.gen(function* () {
  const cache = yield* Cache.make({
    capacity: 100,
    timeToLive: "10 minutes",
    lookup: (userId: string) => fetchUser(userId),
  });

  const user1 = yield* cache.get("user-1");
  const user2 = yield* cache.get("user-1");

  const isCached = yield* cache.contains("user-1");

  yield* cache.invalidate("user-1");

  const stats = yield* cache.cacheStats;
});

Request Caching

Requests are automatically cached within a query context:

const program = Effect.gen(function* () {
  const user1 = yield* getUserById(1);
  const user2 = yield* getUserById(1);

  const user3 = yield* getUserById(2);
});

Disabling Request Caching

const noCaching = getUserById(1).pipe(Effect.withRequestCaching(false));

Custom Cache for Requests

const customCache =
  yield *
  Request.makeCache({
    capacity: 1000,
    timeToLive: "30 minutes",
  });

const program = getUserById(1).pipe(Effect.withRequestCache(customCache));

Disabling Batching

const noBatching = program.pipe(Effect.withRequestBatching(false));

Complete Example

import { Effect, Request, RequestResolver, Schema } from "effect";

// Error types
class UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", { id: Schema.Number }) {}

// Request type
interface GetUserById extends Request.Request<User, UserNotFound> {
  readonly _tag: "GetUserById";
  readonly id: number;
}
const GetUserById = Request.tagged<GetUserById>("GetUserById");

// Batched resolver
const UserResolver = RequestResolver.makeBatched((requests: ReadonlyArray<GetUserById>) =>
  Effect.gen(function* () {
    const ids = requests.map((r) => r.id);

    const response = yield* Effect.tryPromise({
      try: () =>
        fetch("/api/users/batch", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ ids }),
        }).then((r) => r.json() as Promise<User[]>),
      catch: () => new Error("Batch fetch failed"),
    });

    yield* Effect.forEach(requests, (request, index) => {
      const user = response[index];
      return user
        ? Request.completeEffect(request, Effect.succeed(user))
        : Request.completeEffect(request, Effect.fail(new UserNotFound({ id: request.id })));
    });
  }),
);

// Query function
const getUserById = (id: number) => Effect.request(GetUserById({ id }), UserResolver);

// Usage - automatically batched
const program = Effect.gen(function* () {
  const todos = yield* getTodos();

  const owners = yield* Effect.forEach(todos, (todo) => getUserById(todo.ownerId), { concurrency: "unbounded" });

  return owners;
});

Best Practices

  1. Use batching for N+1 scenarios - Especially with databases/APIs
  2. Cache expensive computations - Use Effect.cached
  3. Set appropriate TTLs - Balance freshness vs performance
  4. Use Request deduplication - Automatic with Effect.request
  5. Batch at API boundaries - Group related requests

Additional Resources

For comprehensive batching and caching documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.

Search for these sections:

  • "Batching" for complete batching guide
  • "Caching" for caching patterns
  • "Cache" for Cache service
  • "Caching Effects" for Effect.cached patterns

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.