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
npx -y skills add marzun9620/agent_skills --skill effect-ts-batching-cachingAssembled 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
- Use batching for N+1 scenarios - Especially with databases/APIs
- Cache expensive computations - Use Effect.cached
- Set appropriate TTLs - Balance freshness vs performance
- Use Request deduplication - Automatic with Effect.request
- 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