e-volv
Docs menu

Edge runtimes

@e-volv/logs/edge is the e-volv Launch SDK for Cloudflare Workers, Vercel Edge and similar isolates. It is a server SDK: it takes a server (evk_…) key — edge code runs on the server side of a request and never ships to a browser, so it receives the environment’s full ruleset and evaluates flags locally, within microseconds, with the same Launch SDK contract every SDK implements.

An isolate cannot keep a timer running between requests — it may be frozen the moment the response is sent — so delivery is driven per request: flags.refreshIfStale(ctx) at the start hands a conditional refetch to the runtime’s waitUntil when the held ruleset is older than pollIntervalSeconds, and flushOnEnd(ctx) at the end carries the last log batch and the exposures past the response. There is deliberately no stream: nothing client-side ever streams (contract §2.3), and an isolate cannot hold a connection open between requests anyway.

Install

shell
npm install @e-volv/logs

Cloudflare Workers

typescript
import { createEdgeClient } from '@e-volv/logs/edge';

const evolve = createEdgeClient({
  key: env.EVOLVE_KEY, // a server (evk_…) key
  service: 'edge-router',
  flags: { pollIntervalSeconds: 30 },
});

export default {
  async fetch(request, env, ctx) {
    // Start of request: refresh the ruleset through the runtime's waitUntil
    // when it is older than pollIntervalSeconds. Fire-and-forget.
    evolve.flags.refreshIfStale(ctx);

    const checkout = evolve.flags.bool(
      'checkout.new',
      false,
      { targetingKey: user.id, plan: user.plan },
    );

    // … render the response …

    // End of request: keep the last log batch and the exposures alive past
    // the response — the isolate may freeze the moment it is sent.
    evolve.flushOnEnd(ctx);
    return response;
  },
};

Vercel Edge

typescript
import { createEdgeClient } from '@e-volv/logs/edge';

const evolve = createEdgeClient({ key: process.env.EVOLVE_KEY, flags: {} });

export async function GET(request: Request) {
  const flags = evolve.flags;
  flags.refreshIfStale({
    waitUntil: (p: Promise<unknown>) => waitUntil(p),
  });

  const copy = flags.string('banner.copy', 'Hello', { targetingKey: user.id });

  evolve.flushOnEnd({ waitUntil });
  return new Response(render(copy));
}

Warm isolates

A module-level cache keyed by a hash of your key shares the held ruleset between every client built in the same isolate: a warm isolate does not refetch per request, and nothing is ever written to a filesystem isolates do not have. When the ruleset changes, the next refreshIfStale past the freshness window picks it up with one conditional request — a 304 costs nothing.

Options

OptionDefaultMeaning
flags.pollIntervalSeconds30 (min 15)Per-request freshness window: refreshIfStale refetches when the held ruleset is older than this.
flags.urlderivedBase URL ending in /api/public/v1/flags; defaults to the origin of the Observer url option, else the e-volv cloud.
flags.bootstrapnoneA bundled ruleset served before the first fetch completes.
flags.exposures.dedupeWindowSeconds60Suppress repeats of the same (flag, variant, subject) within the window.
flags.privateAttributes[]Attribute names never sent anywhere, including with exposures.

The SDKs index lists every platform — including the browser SDK for pages — and the HTTP API page documents the wire format the SDK speaks.