@datagauge/node

Last updated Aug 12, 2026View as MarkdownAgent setup

DataGauge for short-lived processes: CLIs, cron jobs, one-shot scripts.

The problem it solves

A CLI exits before any timer-based batcher fires, so a background queue would silently lose its buffer at exit. This SDK buffers events synchronously and makes flush() the one obligation before the process ends: no timers, no daemon threads, nothing to lose. A metrics failure never fails the CLI.

Install

npm install @datagauge/node@^0.2.0
0.2.0 and later

This page documents 0.2.0, which is where flush() started returning reason: the failure table below and onError arrive with it. On 0.1.0 a refused batch returns { ok: false } with nothing attached to say why. Upgrading is a version bump and nothing else.

Prefer zero dependencies? Every SDK is a single file, so vendor it and point your import at the local copy:

curl -o lib/datagauge.ts \
  https://raw.githubusercontent.com/raymond-UI/datagauge/main/sdks/node/index.ts

Usage

import { datagauge } from "@datagauge/node";
const dg = datagauge({ key: process.env.DATAGAUGE_KEY! });
dg.track({ name: "cli_run", user: machineId });
await dg.flush(); // the one obligation, before exit

In Rust, Go, or anything else without this wrapper: send synchronously before exit; it's one HTTP call.

API

datagauge(opts: NodeOptions): DataGaugeNode

OptionTypeNotes
keystringRequired. Use an sk_live_ server key.
urlstringIngest base. Defaults to https://in.datagauge.dev.
fetchertypeof fetchInjectable for tests.
onError(f: SendFailure) => voidOptional. Called for every failure, in addition to reason on the result. Nothing is printed by default: a CLI's stderr is its own product.

dg.track(event): void

Buffer an event synchronously. event is { name, user?, id?, at?, props? }; see POST /v1/events for the field reference.

dg.flush(): Promise<FlushResult>

Send the whole buffer, in requests of up to 500 events each. await this before the process exits.

FieldTypeNotes
sentnumberEvents the server accepted this flush.
okbooleanTrue only when the buffer is now empty. A flush that sends 500 of 700 and is then refused returns { sent: 500, ok: false }.
capped{ resetsAt: number | null }Your organization's hard cap refused the batch. resetsAt is epoch ms.
suspendedtrueIngest is paused because the account has no live subscription: either an unpaid invoice's grace period expired, or the subscription ended. Nothing stored has been deleted.
reasonSendFailureWhy the flush stopped. Present whenever ok is false, including for capped and suspended, so there is never a failure with no explanation attached.

A failed or errored send returns { ok: false } without throwing, so a metrics problem can't crash the CLI, and the events stay buffered. capped and suspended are the two refusals a script should branch on; reason is the one a human reads.

Why a flush failed

fetch() rejects only on transport failure, so an SDK that ignores the response cannot tell a 401 from success. reason is { status, body, events, message, retriable }, where status is 0 for a transport failure and retriable says whether sending the identical bytes again could work:

const res = await dg.flush();
if (!res.ok) {
  console.error(res.reason?.message);
  // → ingest returned 403 for 12 event(s): the key was refused for this
  //   write: either the wrong plane (an sk_test_ key cannot write live data,
  //   or the reverse), a missing scope, or the organization's own hard event
  //   cap. Response: {"ok":false,"error":"…"}
  if (res.reason?.retriable) process.exitCode = 75; // EX_TEMPFAIL
}

retriable is true for 429, 5xx and transport failures, and the events are still in the buffer, so a wrapper script can flush again or exit in a way its scheduler will retry. It is false for every other refusal: the same bytes get the same answer, and the message names the fix instead.

A 202 never sets reason, because the batch was accepted. If it carried per-event rejections in errors[], those go to onError, because ok: false would be a lie about the buffer.

dg.pending: number

Number of buffered events not yet sent.

installExitFlush(dg): void

Registers a best-effort flush on process.on("beforeExit"). A safety net, not a substitute for an explicit await dg.flush(); the awaited flush remains the contract.

Key hygiene

Short-lived processes run server-side (or on a developer's machine), so use an sk_live_ key here: it can write payments and read metrics. An sk_test_ key behaves identically against your isolated test environment; swap the prefix to rehearse a job before pointing it at live.

Never ship a secret key in a binary

Don't bundle this SDK with an sk_live_ key inside a distributed CLI: a secret key can write revenue. For anything users can extract the key from, use a publishable pk_live_ key. See Keys & scoping.