Effect Integration
Effect Integration
Use the Betalink API client with Effect for type-safe error handling
The Betalink API client provides a first-class Effect integration for applications using the Effect library.
Why Effect?
Effect provides several benefits for API client usage:
- Type-safe errors: All possible errors are tracked in the type system
- Dependency injection: Services are injected via Effect Layers
- Composable: Chain multiple API calls with proper error handling
- Testable: Easy to mock services for testing
Installation
The Effect integration is included in the main package. Effect is an optional peer dependency:
npm install @betalink/api-client effect @effect/platformBasic Setup
Import from the /effect subpath:
import {
BetalinkApiClient,
BetalinkApiClientLive,
makeConfigLayer,
} from "@betalink/api-client/effect";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";Create the Configuration Layer
const configLayer = makeConfigLayer({
baseUrl: "https://betalink.dev/api",
apiKey: process.env.BETALINK_API_KEY!,
timeout: 30000, // optional
});Create the Client Layer
const clientLayer = Layer.provide(BetalinkApiClientLive, configLayer);Use the Client
const program = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
const companies = yield* client.listCompanies();
const company = yield* client.getCompany("12345678");
const transactions = yield* client.getCompanyTransactions("12345678");
return { companies, company, transactions };
});
// Run the program
const result = await Effect.runPromise(
Effect.provide(program, clientLayer)
);Error Types
The Effect API uses tagged errors for type-safe error handling:
| Error Tag | Description |
|---|---|
ApiRequestError | HTTP request failed |
ApiResponseError | Non-2xx response received |
UnauthorizedApiError | 401 - Invalid API key |
CompanyNotFoundApiError | 404 - Company not found |
RateLimitExceededApiError | 429 - Rate limit exceeded |
SchemaValidationApiError | Response validation failed |
BadRequestApiError | 400 - Invalid request |
Error Handling
import {
UnauthorizedApiError,
CompanyNotFoundApiError,
RateLimitExceededApiError,
} from "@betalink/api-client/effect";
const program = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.getCompany("12345678");
}).pipe(
Effect.catchTag("UnauthorizedApiError", (error) =>
Effect.fail(new Error("Invalid API key"))
),
Effect.catchTag("CompanyNotFoundApiError", (error) =>
Effect.succeed(null)
),
Effect.catchTag("RateLimitExceededApiError", (error) =>
Effect.fail(new Error(`Rate limited. Retry after ${error.retryAfter}s`))
)
);Recovering from Errors
const programWithFallback = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.getCompany("12345678");
}).pipe(
Effect.catchTag("CompanyNotFoundApiError", () =>
Effect.succeed({
id: "unknown",
cvr: "12345678",
name: "Unknown Company",
status: "UNKNOWN" as const,
})
)
);Service Pattern
For larger applications, create a dedicated service layer:
import * as Context from "effect/Context";
class CompanyService extends Context.Tag("CompanyService")<
CompanyService,
{
readonly getCompanyWithTransactions: (
cvr: string
) => Effect.Effect<
{ company: Company; transactions: Transaction[] },
UnauthorizedApiError | CompanyNotFoundApiError | ApiRequestError
>;
}
>() {}
const CompanyServiceLive = Layer.effect(
CompanyService,
Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return {
getCompanyWithTransactions: (cvr: string) =>
Effect.gen(function* () {
const company = yield* client.getCompany(cvr);
const { data: transactions } = yield* client.getCompanyTransactions(cvr);
return { company, transactions };
}),
};
})
);