Overview
wrapClient creates a reusable wrapper around any Promise-based client (like API clients, database clients, etc.) that converts operations into Effects with consistent error handling.
Signature
function wrapClient<CLIENT, ERROR extends Error>(opts: {
client: CLIENT;
error: (ctx: { cause: Cause; message?: Message }) => ERROR;
}): <OUTPUT, OVERRIDEN_ERROR>(
func: (client: CLIENT) => Promise<OUTPUT>,
overrides?: {
errorHandler?: (cause: Cause) => OVERRIDEN_ERROR;
errorMessage?: ((cause: Cause) => Message) | Message;
}
) => Effect.Effect<OUTPUT, ERROR | OVERRIDEN_ERROR>
Parameters
The client instance to wrap. Can be any object with Promise-returning methods.
opts.error
(ctx: { cause: unknown; message?: string }) => ERROR
required
Factory function that creates your custom error type from a cause and optional message.
Returns
Returns a wrapper function that accepts:
func
(client: CLIENT) => Promise<OUTPUT>
required
A function that uses the client to perform an operation.
overrides.errorHandler
(cause: unknown) => OVERRIDEN_ERROR
Optional custom error handler for this specific operation. Overrides the default error factory.
overrides.errorMessage
string | (cause: unknown) => string
Optional error message (static string or function). Passed to the error factory.
The wrapper returns an Effect.Effect<OUTPUT, ERROR | OVERRIDEN_ERROR>.
Basic Usage
Simple Client Wrapper
import { wrapClient } from 'ff-effect';
import { Effect } from 'effect';
class ApiError extends Error {
constructor(
public cause: unknown,
message?: string
) {
super(message ?? 'API request failed');
}
}
const apiClient = {
fetchUser: (id: number) => fetch(`/api/users/${id}`).then(r => r.json()),
createUser: (data: UserData) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data)
}).then(r => r.json())
};
// Create the wrapper once
const wrap = wrapClient({
client: apiClient,
error: ({ cause, message }) => new ApiError(cause, message)
});
// Use it for multiple operations
const program = Effect.gen(function* () {
// Fetch with custom error message
const user = yield* wrap(
(client) => client.fetchUser(1),
{ errorMessage: 'Failed to fetch user' }
);
// Create without custom message
const newUser = yield* wrap(
(client) => client.createUser({ name: 'Alice' })
);
return { user, newUser };
});
Error Handling Options
Default Error
Without overrides, uses the error factory provided to wrapClient:
const wrap = wrapClient({
client: myClient,
error: ({ cause, message }) => new MyError(cause, message)
});
const effect = wrap((client) => client.fetchData());
// On failure: throws MyError with no custom message
Static Error Message
Provide a static string message:
const effect = wrap(
(client) => client.fetchData(),
{ errorMessage: 'Failed to fetch data' }
);
// On failure: throws MyError with message 'Failed to fetch data'
Dynamic Error Message
Compute the error message from the cause:
const effect = wrap(
(client) => client.fetchData(),
{
errorMessage: (cause) => {
if (cause instanceof Error) {
return `API Error: ${cause.message}`;
}
return 'Unknown error occurred';
}
}
);
Custom Error Handler
Completely override the error handling for specific operations:
import { Data } from 'effect';
class ValidationError extends Data.TaggedError('ValidationError')<{
cause: unknown;
}> {}
const effect = wrap(
(client) => client.validateUser(data),
{
errorHandler: (cause) => new ValidationError({ cause })
}
);
// On failure: throws ValidationError instead of the default error type
Real-World Examples
HTTP Client Wrapper
import { wrapClient } from 'ff-effect';
import { Data, Effect } from 'effect';
class HttpError extends Data.TaggedError('HttpError')<{
status?: number;
cause: unknown;
message: string;
}> {}
interface HttpClient {
get: (url: string) => Promise<Response>;
post: (url: string, body: unknown) => Promise<Response>;
put: (url: string, body: unknown) => Promise<Response>;
delete: (url: string) => Promise<Response>;
}
const createHttpWrapper = (client: HttpClient) => {
return wrapClient({
client,
error: ({ cause, message }) => {
const status = cause instanceof Response ? cause.status : undefined;
return new HttpError({
status,
cause,
message: message ?? 'HTTP request failed'
});
}
});
};
// Usage
const http = createHttpWrapper(myHttpClient);
const program = Effect.gen(function* () {
const users = yield* http(
(client) => client.get('/api/users').then(r => r.json()),
{ errorMessage: 'Failed to fetch users' }
);
const created = yield* http(
(client) => client.post('/api/users', { name: 'Alice' }).then(r => r.json()),
{ errorMessage: (cause) => `User creation failed: ${cause}` }
);
return { users, created };
});
Database Client Wrapper
import { wrapClient } from 'ff-effect';
import { Data, Effect } from 'effect';
class DbError extends Data.TaggedError('DbError')<{
query?: string;
cause: unknown;
message: string;
}> {}
interface DbClient {
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
execute: (sql: string, params?: unknown[]) => Promise<{ affectedRows: number }>;
}
const createDbWrapper = (client: DbClient) => {
return wrapClient({
client,
error: ({ cause, message }) => new DbError({
cause,
message: message ?? 'Database operation failed'
})
});
};
const db = createDbWrapper(myDbClient);
const program = Effect.gen(function* () {
const users = yield* db(
(client) => client.query('SELECT * FROM users WHERE id = ?', [1]),
{ errorMessage: 'Failed to query users' }
);
return users;
});
Third-Party API Client
import { wrapClient } from 'ff-effect';
import { Effect } from 'effect';
import { OpenAI } from 'openai';
class OpenAIError extends Error {
constructor(
public cause: unknown,
message?: string
) {
super(message ?? 'OpenAI API error');
}
}
const createOpenAIWrapper = (client: OpenAI) => {
return wrapClient({
client,
error: ({ cause, message }) => new OpenAIError(cause, message)
});
};
const openai = createOpenAIWrapper(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
const program = Effect.gen(function* () {
const completion = yield* openai(
(client) => client.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }]
}),
{ errorMessage: 'Failed to generate completion' }
);
return completion.choices[0].message.content;
});
Type Safety
Generic Client Types
The wrapper preserves full type information:
interface MyClient {
getString: () => Promise<string>;
getNumber: () => Promise<number>;
getUser: () => Promise<{ id: number; name: string }>;
}
const wrap = wrapClient<MyClient, MyError>({
client: myClient,
error: ({ cause, message }) => new MyError(cause, message)
});
// Return types are fully inferred
const stringEffect: Effect.Effect<string, MyError> = wrap(
(client) => client.getString()
);
const userEffect: Effect.Effect<{ id: number; name: string }, MyError> = wrap(
(client) => client.getUser()
);
Error Type Overrides
When using errorHandler, the error type is updated:
class CustomError extends Error {}
const wrap = wrapClient<Client, DefaultError>({
client,
error: ({ cause }) => new DefaultError(cause)
});
// Type: Effect.Effect<string, CustomError>
const effect = wrap(
(client) => client.getData(),
{ errorHandler: (cause) => new CustomError('custom') }
);
See Also