Docs / Node / TypeScript SDK

Node / TypeScript SDK

Read variables that change while your process runs, and rebuild the things made from them, without a restart.

Install

sh
npm i @dynv/sdk

How it works

The SDK never talks to Dynv. It talks to the small agent that dynv run starts next to your app, over loopback, using two variables dynv run puts in your environment: DYNV_AGENT_URL and DYNV_AGENT_KEY. The agent has already verified and decrypted the data, so the SDK holds no token and does no cryptography.

Not running under dynv run (local development, tests, a migration in progress)? Then get() just reads process.env and no change events ever fire, so the same code works everywhere.

Read variables

Mark the variables you want live as hot (dynv admin mode web hot PROXY_SERVER or in the dashboard), start your app with dynv run, then:

src/fetch.ts
import { dynv } from "@dynv/sdk";

const d = dynv();        // one shared instance per process
await d.ready();         // resolves after the first snapshot (immediately without an agent)

d.get("PROXY_SERVER");   // string | undefined, synchronous, always current
d.require("API_KEY");    // same, but throws "dynv: API_KEY is not set"
d.pick(["PROXY_SERVER", "PROXY_USERNAME"]);  // several keys from one consistent snapshot
d.version;               // the document version you are looking at
d.live;                  // true when running under dynv run
  • get() returns the current value. For a key Dynv has never managed it falls back to process.env; once Dynv has managed a key, removing it makes it read as undefined, never as a stale inherited value.
  • Read on every use. Do not cache d.get(...) in a module-level constant, or you will pin the old value.

React to changes

src/index.ts
import { dynv } from "@dynv/sdk";

const d = dynv();

const off = d.on("change", ({ version, changed }) => {
  // changed = names of variables added, changed or removed. Never values.
  console.log("env changed", version, changed);
});

d.on("error", (err) => console.warn("dynv agent:", err));

// later: off(); d.close();

The first snapshot is the starting point, not a change, so you do not get an event at startup. If the agent is briefly unreachable the SDK keeps retrying with backoff (500 ms up to 30 s) and emits error each time.

By default, hot changes are also written into process.env so libraries that read it lazily see them. Turn that off with new Dynv({ mirrorToProcessEnv: false }).

Rebuild long-lived objects: resource()

A proxy agent, an API client or a connection pool is built from variables once and used for a long time. resource() rebuilds it when its inputs change, and lets in-flight work finish on the old one.

src/egress.ts
import { dynv } from "@dynv/sdk";
import { ProxyAgent, request } from "undici";

const proxy = dynv().resource(
  ["PROXY_SERVER", "PROXY_USERNAME", "PROXY_PASSWORD"],
  (env) =>
    new ProxyAgent({
      uri: env.PROXY_SERVER!,
      token: "Basic " + Buffer.from(`${env.PROXY_USERNAME}:${env.PROXY_PASSWORD}`).toString("base64"),
    }),
  { dispose: (old) => old.destroy(), drainTimeoutMs: 30_000 },
);

export function get(url: string) {
  // The lease keeps this agent alive until the request is done,
  // even if the proxy is rotated in the meantime.
  return proxy.use((agent) => request(url, { dispatcher: agent }));
}
  • The new instance is built first. If your factory throws, the old one stays current and an error event fires.
  • The swap is atomic: every use() or acquire() after it gets the new instance.
  • The old instance is disposed after its in-flight work drains (at most drainTimeoutMs, default 30 s) and after overlapMs (default 0).
  • During an overlapMs window, previous() returns the old instance, so you can retry with the old credential while a provider propagates a new one.
  • generation and isStale(gen) let pools recycle a stale item at its next lease instead of mid-job.
MemberWhat it does
use(fn)Runs fn(value, generation) holding a lease; releases it when done.
acquire()Returns { value, generation, release }. Call release() yourself.
current()The current instance, without a lease.
previous()The retired instance during an overlap window, else undefined.
generation, isStale(gen)Bumps on every swap; true if something built at gen is out of date.
on("swap" | "error", fn)A swap event carries { generation, changed }.
close()Stops listening and disposes the current instance.

Options

ts
new Dynv({
  agentUrl,            // default process.env.DYNV_AGENT_URL
  agentKey,            // default process.env.DYNV_AGENT_KEY
  mirrorToProcessEnv,  // default true
  onError,             // called on agent errors, in addition to the "error" event
});

The agent's own HTTP endpoints, if you are on another language, are on the HTTP API page. Other SDKs are Coming soon.