Betalink Docs
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/platform

Basic 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 TagDescription
ApiRequestErrorHTTP request failed
ApiResponseErrorNon-2xx response received
UnauthorizedApiError401 - Invalid API key
CompanyNotFoundApiError404 - Company not found
RateLimitExceededApiError429 - Rate limit exceeded
SchemaValidationApiErrorResponse validation failed
BadRequestApiError400 - 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 };
        }),
    };
  })
);

Next Steps

On this page