Dashboard
JavaScript (server)

Effect API

Use the Toggld client as an Effect service, provided by a self-contained Layer.

@toggld/js/effect exposes the native Effect API. The client is a service, and layer(options) provides it. The service wraps the same client as the Promise API in @toggld/js, so both APIs behave the same.

On Cloudflare Workers, Vercel, and AWS Lambda, use @toggld/js/serverless/effect instead. It provides the same ToggldClient service, so your program code does not change. Only the layer changes. See Serverless client: Effect API.

The Effect API requires Effect version 4 (effect@^4.0.5). effect is an optional peer dependency of @toggld/js, so install it next to the SDK:

pnpm add @toggld/js effect

Without effect, importing @toggld/js/effect fails with ERR_MODULE_NOT_FOUND. The Promise API does not need it.

Evaluate a flag

import { Effect } from "effect";
import { ToggldClient, layer } from "@toggld/js/effect";

const program = Effect.gen(function* () {
  const flags = yield* ToggldClient;
  return yield* flags.getBooleanValue("new-checkout", false, { targetingKey: "user-123" });
});

const enabled = await program.pipe(
  Effect.provide(
    layer({
      sdkKey: process.env.TOGGLD_SDK_KEY!,
      endpoint: process.env.TOGGLD_API_URL!,
    }),
  ),
  Effect.runPromise,
);

The layer function takes the same options as ToggldClient.create. See Configuration. The layer creates its own client, so it is the only layer you need to provide.

Like create, the layer runs in sync mode by default. Building it waits for the first flag snapshot, so the program above already evaluates with real data. It takes the same mode and file options as create: pass mode: "file" to evaluate from a flag file, or mode: "remote" to send each evaluation to the API. See When to use remote mode. For serverless functions and edge runtimes, use the serverless layer.

This example builds the layer for one program, which is fine for a script. In a server, build the layer once. See Long-lived applications.

The service

yield* ToggldClient gives you a service with these members. Every effect has the error type never: evaluation failures resolve to your fallback value, like the Promise API.

MemberType
getBooleanValue(key, fallback, context?)Effect<boolean>
getStringValue(key, fallback, context?)Effect<string>
getNumberValue(key, fallback, context?)Effect<number>
getObjectValue<T>(key, fallback, context?)Effect<T>
getBooleanDetails, getStringDetails, getNumberDetails, getObjectDetailsEffect<EvaluationDetails<T>>
evaluate(key, fallback, context?)Effect<EvaluationDetails<FlagValue>>
evaluateAll(context?)Effect<ReadonlyArray<EvaluationDetails<FlagValue>>>
readyEffect<void>. Completes when the client can evaluate.
flushTelemetryEffect<void>. Sends buffered telemetry now.

In sync and file modes, evaluations are synchronous effects. In remote mode, interrupting an evaluation aborts its request.

The package also exports the types ToggldClientOptions, EvaluationDetails, EvaluationContext, FlagValue, ErrorCode, and Reason.

Readiness

Building the layer waits for the first snapshot, for up to sync.readyTimeoutMs (default 5000 ms), like ToggldClient.create in the Promise API. Then the layer is ready even if no snapshot has arrived. The SDK logs one warning with the last error, and synchronization keeps trying in the background. Until the first snapshot arrives, evaluations return your fallback value with PROVIDER_NOT_READY. In file mode, the layer waits for the first read of the file instead. See Readiness.

To build the layer without waiting, set sync: { readyTimeoutMs: 0 }. Then wait for ready yourself where you need the data. ready has no time limit, so add a timeout:

import { Duration, Effect } from "effect";
import { ToggldClient } from "@toggld/js/effect";

const awaitFlags = Effect.gen(function* () {
  const flags = yield* ToggldClient;
  // Continue after 5 seconds even if no snapshot has arrived.
  yield* flags.ready.pipe(Effect.timeout(Duration.seconds(5)), Effect.ignore);
});

If the options can never synchronize, the layer fails with a defect: a TypeError for a missing key, a public token (ffp_) in sync mode, or an endpoint that is not an http or https URL. Options from @toggld/node that no longer apply fail the same way: mode: "local", sync.file, and file without mode: "file".

Long-lived applications

In sync mode, the background synchronization runs in the layer's scope. Build the layer once for the life of your application. If you provide the layer to each request separately, every request starts a new client and a new synchronization.

Use a ManagedRuntime to keep one client for the whole process:

runtime.ts
import { ManagedRuntime } from "effect";
import { layer } from "@toggld/js/effect";

export const runtime = ManagedRuntime.make(
  layer({
    sdkKey: process.env.TOGGLD_SDK_KEY!,
    endpoint: process.env.TOGGLD_API_URL!,
  }),
);

// On shutdown: stops synchronization and sends the remaining telemetry.
process.on("SIGTERM", () => {
  void runtime.dispose();
});
import { Effect } from "effect";
import { ToggldClient } from "@toggld/js/effect";
import { runtime } from "./runtime";

const enabled = await runtime.runPromise(
  Effect.flatMap(ToggldClient, (flags) =>
    flags.getBooleanValue("new-checkout", false, { targetingKey: "user-123" }),
  ),
);

You can also merge the Toggld layer with your application's other layers, for example with Layer.mergeAll, and provide them together.

Shut down

When the layer's scope closes, for example through ManagedRuntime.dispose:

  • Synchronization stops, and the stream closes.
  • The SDK makes one final attempt to send buffered telemetry, limited to timeoutMs.

To send telemetry before that, yield* flags.flushTelemetry. A failed flush keeps the batch queued for the next attempt.

On this page