Effect services and runtime

We are going to configure @paddle/paddle-js using an effect service.

@paddle/paddle-js export a initializePaddle function. initializePaddle requires a valid client token and allows to provide the inline checkout configuration options.

The service defines a function that requires a clientToken and returns an effect calling initializePaddle:

export class Paddle extends Effect.Service<Paddle>()("Paddle", {
  succeed: ({ clientToken }: { clientToken: string }) =>
    Effect.tryPromise(() =>
      initializePaddle({
        token: clientToken,
        environment: "sandbox",
        debug: true,
        checkout: {
          settings: {
            displayMode: "inline",
            frameInitialHeight: 450,
            frameTarget: PADDLE_CONTAINER_CLASS,
            frameStyle:
              "width: 100%; min-width: 312px; background-color: transparent; border: none;",
            locale: "en",
          },
        },
      })
    ).pipe(
      Effect.flatMap(Effect.fromNullable),
      Effect.mapError((cause) => new ErrorPaddle({ cause }))
    ),
}) {}

We also need an Api service responsible to interact with the server API. Api uses HttpApiClient to derive a type-safe HTTP client from the shared api-client definition:

export class Api extends Effect.Service<Api>()("Api", {
  effect: Effect.gen(function* () {
    const baseUrl = yield* Config.string("API_BASE_URL").pipe(
      Config.withDefault("http://localhost:3000")
    );
    return yield* HttpApiClient.make(MainApi, {
      baseUrl,
    });
  }),
  dependencies: [FetchHttpClient.layer],
}) {}

By sharing the api-client definition, the HTTP client provides auto-complete for each endpoint parameters, success, and error types.

We then build a ManagedRuntime containing both Api and Paddle services:

import { Layer, ManagedRuntime } from "effect";
import { Api } from "./api";
import { Paddle } from "./paddle";

const MainLayer = Layer.mergeAll(Api.Default, Paddle.Default);

export const RuntimeClient = ManagedRuntime.make(MainLayer);

We can now use RuntimeClient to execute all the effects inside the client.