Dashboard
Node.js

Effect API

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

@toggld/node/effect exposes the native Effect API. The client is a service, and layer(options) provides it. The Promise API in @toggld/node is a thin wrapper over this implementation, so both APIs behave the same.

The Effect API requires Effect version 4 (effect@^4.0.5). Install it as described in Install.

Evaluate a flag

import { Effect } from "effect";
import { ToggldClient, layer } from "@toggld/node/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 builds its own HTTP client and WebSocket support, so it is the only layer you need to provide.

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.

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

Wait for readiness in local mode

ToggldClient.create in the Promise API waits for the first snapshot for up to sync.readyTimeoutMs. The Effect layer does not wait. Until the first snapshot arrives, local evaluations return your fallback value with PROVIDER_NOT_READY.

Wait for ready yourself, with a timeout, before you serve traffic:

import { Duration, Effect } from "effect";
import { ToggldClient } from "@toggld/node/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);
});

Long-lived applications

In local 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/node/effect";

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

// On shutdown: stops synchronization and sends the remaining telemetry.
process.on("SIGTERM", () => {
  void runtime.dispose();
});
import { Effect } from "effect";
import { ToggldClient } from "@toggld/node/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