From effect-ts
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.
npx claudepluginhub andrueandersoncs/claude-skill-effect-ts --plugin effect-tsThis skill uses the workspace's default tool permissions.
Effect provides automatic optimization for API calls:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Effect provides automatic optimization for API calls:
This solves the N+1 query problem automatically.
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.
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");
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])));
}),
);
const getUserById = (id: number) => Effect.request(GetUserById({ id }), GetUserByIdResolver);
const program = Effect.gen(function* () {
const todos = yield* getTodos();
const owners = yield* Effect.forEach(todos, (todo) => getUserById(todo.ownerId), { concurrency: "unbounded" });
});
const SingleUserResolver = RequestResolver.fromEffect((request: GetUserById) =>
Effect.tryPromise(() => fetch(`/api/users/${request.id}`).then((r) => r.json())),
);
const BatchedUserResolver = RequestResolver.makeBatched((requests: ReadonlyArray<GetUserById>) =>
// Handle all requests in one call
batchFetch(requests),
);
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));
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;
const cachedUser = yield * Effect.cachedWithTTL(fetchCurrentUser, "5 minutes");
const user1 = yield * cachedUser;
yield * Effect.sleep("6 minutes");
const user2 = yield * cachedUser;
const [cachedUser, invalidate] = yield * Effect.cachedInvalidateWithTTL(fetchCurrentUser, "5 minutes");
const user = yield * cachedUser;
yield * invalidate;
const freshUser = yield * cachedUser;
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;
});
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);
});
const noCaching = getUserById(1).pipe(Effect.withRequestCaching(false));
const customCache =
yield *
Request.makeCache({
capacity: 1000,
timeToLive: "30 minutes",
});
const program = getUserById(1).pipe(Effect.withRequestCache(customCache));
const noBatching = program.pipe(Effect.withRequestBatching(false));
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;
});
For comprehensive batching and caching documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.
Search for these sections: