Effect Integration
Effect Examples
Complete examples of using the Betalink API client with Effect
Complete Setup Example
import {
BetalinkApiClient,
BetalinkApiClientLive,
makeConfigLayer,
UnauthorizedApiError,
CompanyNotFoundApiError,
} from "@betalink/api-client/effect";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Config from "effect/Config";
// Create config from environment
const configLayer = Layer.unwrapEffect(
Effect.gen(function* () {
const apiKey = yield* Config.string("BETALINK_API_KEY");
const baseUrl = yield* Config.string("BETALINK_API_URL").pipe(
Config.withDefault("https://betalink.dev/api")
);
return makeConfigLayer({ baseUrl, apiKey });
})
);
// Full client layer
const clientLayer = Layer.provide(BetalinkApiClientLive, configLayer);
// Your program
const main = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
const companies = yield* client.listCompanies({ limit: 10 });
console.log(`Found ${companies.meta.total} companies`);
for (const company of companies.data) {
console.log(`- ${company.cvr}: ${company.name}`);
}
});
// Run
Effect.runPromise(Effect.provide(main, clientLayer));Fetching All Pages
const fetchAllCompanies = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
const allCompanies: Company[] = [];
let after: string | undefined;
while (true) {
const response = yield* client.listCompanies({ limit: 100, after });
allCompanies.push(...response.data);
if (!response.meta.hasMore) break;
after = response.data[response.data.length - 1].id;
}
return allCompanies;
});Parallel Requests
const fetchMultipleCompanies = (cvrs: string[]) =>
Effect.gen(function* () {
const client = yield* BetalinkApiClient;
// Fetch all companies in parallel
const companies = yield* Effect.all(
cvrs.map((cvr) => client.getCompany(cvr)),
{ concurrency: 5 } // Limit concurrent requests
);
return companies;
});Error Recovery with Fallback
const getCompanyOrDefault = (cvr: string) =>
Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.getCompany(cvr);
}).pipe(
Effect.catchTag("CompanyNotFoundApiError", () =>
Effect.succeed({
id: `unknown-${cvr}`,
cvr,
name: "Unknown Company",
address: null,
startDate: new Date().toISOString(),
endDate: new Date().toISOString(),
status: "CREATED" as const,
senderRegNumber: null,
senderAccountNumber: null,
receiverRegNumber: null,
receiverAccountNumber: null,
defaultAmountCents: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
)
);Retry with Exponential Backoff
import * as Schedule from "effect/Schedule";
import * as Duration from "effect/Duration";
const fetchWithRetry = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.listCompanies();
}).pipe(
Effect.retry(
Schedule.exponential(Duration.seconds(1)).pipe(
Schedule.compose(Schedule.recurs(3))
)
)
);Rate Limit Handling
const handleRateLimit = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.listCompanies();
}).pipe(
Effect.catchTag("RateLimitExceededApiError", (error) =>
Effect.gen(function* () {
const waitTime = error.retryAfter ?? 60;
yield* Effect.sleep(Duration.seconds(waitTime));
const client = yield* BetalinkApiClient;
return yield* client.listCompanies();
})
)
);Testing with Mocks
import { TestBetalinkApiClient } from "@betalink/api-client/effect";
const testProgram = Effect.gen(function* () {
const testClient = yield* TestBetalinkApiClient;
// Configure mock responses
yield* testClient.setCompanies([
{
id: "test-1",
cvr: "12345678",
name: "Test Company",
status: "REGISTERED",
// ... other fields
},
]);
// Now BetalinkApiClient will return the mock data
const client = yield* BetalinkApiClient;
const companies = yield* client.listCompanies();
return companies.data[0].name; // "Test Company"
});
// Run with test layer
const result = await Effect.runPromise(
Effect.provide(testProgram, BetalinkApiClient.Test)
);Building a Service Layer
import * as Context from "effect/Context";
// Define your service interface
interface CompanyAnalytics {
readonly getCompanyStats: (cvr: string) => Effect.Effect<
{
company: Company;
transactionCount: number;
totalAmount: number;
},
UnauthorizedApiError | CompanyNotFoundApiError | ApiRequestError
>;
}
class CompanyAnalyticsService extends Context.Tag("CompanyAnalyticsService")<
CompanyAnalyticsService,
CompanyAnalytics
>() {}
// Implement the service
const CompanyAnalyticsLive = Layer.effect(
CompanyAnalyticsService,
Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return {
getCompanyStats: (cvr: string) =>
Effect.gen(function* () {
const company = yield* client.getCompany(cvr);
// Fetch all transactions
const allTransactions: Transaction[] = [];
let after: string | undefined;
while (true) {
const response = yield* client.getCompanyTransactions(cvr, {
limit: 100,
after,
});
allTransactions.push(...response.data);
if (!response.meta.hasMore) break;
after = response.data[response.data.length - 1].id;
}
// Calculate stats
const totalAmount = allTransactions.reduce(
(sum, tx) => sum + parseFloat(tx.amountDkk),
0
);
return {
company,
transactionCount: allTransactions.length,
totalAmount,
};
}),
};
})
);
// Compose layers
const appLayer = Layer.provide(
CompanyAnalyticsLive,
Layer.provide(BetalinkApiClientLive, configLayer)
);
// Use in your app
const main = Effect.gen(function* () {
const analytics = yield* CompanyAnalyticsService;
const stats = yield* analytics.getCompanyStats("12345678");
console.log(`Company: ${stats.company.name}`);
console.log(`Transactions: ${stats.transactionCount}`);
console.log(`Total Amount: ${stats.totalAmount} DKK`);
});
Effect.runPromise(Effect.provide(main, appLayer));Integration with Effect Runtime
For applications with an existing Effect runtime:
import * as Runtime from "effect/Runtime";
import * as ManagedRuntime from "effect/ManagedRuntime";
// Create a managed runtime with the client layer
const runtime = ManagedRuntime.make(clientLayer);
// Use in your application
async function handleRequest() {
return runtime.runPromise(
Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.listCompanies();
})
);
}
// Cleanup on shutdown
process.on("SIGTERM", () => {
runtime.dispose();
});