Node.js
Not released yet. The Node.js SDK for e-volv Launch is built and conformance-tested, but its first release with flags is not on the registry yet. This page documents that release: until it ships, npm install @e-volv/logs finds either nothing or an earlier Observer-only version without flags. The SDKs index shows what can be installed today.
@e-volv/logs is the e-volv Launch SDK for Node.js 18 and later. It is a server SDK: with a server key (evk_…) it receives the environment’s full ruleset and evaluates flags locally, so a flag check never performs I/O, never blocks and never throws. Delivery is a stream of change notifications (SSE) with a polling fallback, a last-known cache on disk, and exposure tracking that rides the same transport as your logs. The behaviour is pinned by the Launch SDK contract and both of its conformance suites run in CI.
OpenFeature provider
If your stack already speaks OpenFeature, install the provider alongside the SDK:
npm install @e-volv/logs @e-volv/openfeature-node @openfeature/server-sdk
import { OpenFeature } from '@openfeature/server-sdk';
import { init } from '@e-volv/logs';
import { EvolveProvider } from '@e-volv/openfeature-node';
const evolve = init({ key: process.env.EVOLVE_KEY });
await OpenFeature.setProviderAndWait(new EvolveProvider(evolve.flags));
const flags = OpenFeature.getClient();
const checkout = await flags.getBooleanDetails('checkout.new', false, {
targetingKey: user.id,
plan: user.plan,
});
// checkout.value / checkout.variant / checkout.reason — and
// checkout.flagMetadata.evolveReason always names the native e-volv reason.Reason mapping: RULE:* and TARGET_MATCH become TARGETING_MATCH, ROLLOUT becomes SPLIT, OFF/KILLED become DISABLED, and error reasons carry the matching OpenFeature error code. The native e-volv reason is always available on flagMetadata.evolveReason.
Native API
import { init } from '@e-volv/logs';
const evolve = init({ key: process.env.EVOLVE_KEY });
await evolve.flags.ready(); // optional: resolves true once the first ruleset arrived
if (
evolve.flags.bool('checkout.new', false, {
targetingKey: user.id,
plan: user.plan,
})
) {
renderNewCheckout();
}
// The full detail when you need the variant and the reason:
const d = evolve.flags.detail('checkout.banner', 'default', {
targetingKey: user.id,
});
// d.value, d.variant ('on' | 'off' | 'control' | …), d.reason
// ('RULE:0' | 'ROLLOUT' | 'TARGET_MATCH' | 'DEFAULT' | 'FLAG_NOT_FOUND' | …)Evaluation methods: bool, string, number, json and detail — all synchronous, all safe. A flag whose served value is not of the requested type returns your default with reason TYPE_MISMATCH; a flag absent from the ruleset returns your default with FLAG_NOT_FOUND.
Options
Flags are configured under the flags key of the existing init() options:
| Option | Default | Meaning |
|---|---|---|
flags.enabled | true | Start the flags client. False gives a defaults-only stub. |
flags.mode | 'stream' | stream | poll | offline. Offline never contacts the control plane (bootstrap snapshot or defaults only). |
flags.pollIntervalSeconds | 30 (min 15) | Poll period once streaming has fallen back. |
flags.url | derived | Base URL ending in /api/public/v1/flags; defaults to the origin of the Observer url option, else the e-volv cloud. |
flags.cache | platform tmp dir | Where the last-known ruleset is kept (one file per key, hashed name, atomic write). false disables. |
flags.bootstrap | none | A bundled ruleset used before the first fetch completes. |
flags.exposures.enabled | true | Record an exposure on each evaluation of a flag present in the ruleset. |
flags.exposures.sampleRate | 1 | Probability an evaluation is recorded; sent as sampleRate. |
flags.exposures.dedupeWindowSeconds | 60 | Suppress repeats of the same (flag, variant, subject) within the window; suppressed counts ride the next exposure. |
flags.exposures.sendAttributes | false | Send context attributes with exposures (private attributes are always removed). |
flags.privateAttributes | [] | Attribute names never sent anywhere. |
Install check and lifecycle
// Install check: is this key bound, and to which environment?
const ping = await evolve.flags.verify();
// { environment: 'production', keyKind: 'server', flags: 42, etag: '3f2a…' }
// null when the control plane did not answer.flags.ready(timeoutMs?) waits for the first ruleset (only if you choose to), flags.lastUpdatedAt reports the last confirmed update, and flags.onChange(fn) notifies with the keys whose values changed.
// On shutdown (e.g. serverless teardown): stop timers, flush exposures. await evolve.close();
Troubleshooting by status code
Evaluation never breaks because of the control plane — it degrades. What each response means for the SDK (contract §2.6):
| Response | SDK behaviour |
|---|---|
401 | Key invalid or revoked. The SDK keeps serving held values or defaults, logs once, and retries bootstrap every 5 minutes. |
403 — lacks the scope flags:read | Flags are disabled for this key with one warning; telemetry is unaffected. No retry until re-init. |
404 | Launch is not enabled for the workspace, or the key is not bound to an environment. Same handling as 401. |
429 | Honours Retry-After (seconds). The delivery rate limit is 3,000 requests/min per key. |
5xx, timeout, network error | Retries with full-jitter exponential backoff while serving the last held values. |
Guarantees
- A flag check is local and synchronous — bounded by CPU, not the network;
ready()is the only call that waits, and only when you ask it to. - An unreachable or slow control plane never changes an answer mid-flag: the last held ruleset (or a valid cache, or your defaults) keeps serving while the SDK backs off and reconnects.
- Cold start reads the on-disk cache instantly and swaps to a fresh ruleset without a flicker for unchanged flags; corrupt or other-environment caches are ignored and overwritten.
- Exposures are de-duplicated, sampled, capped and retried with the same backoff as your logs — telemetry you can trust as much as the flag answers themselves.
The SDKs index lists every platform, and the HTTP API page documents the wire format the SDK speaks.