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.0This 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.tsUsage
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 exitIn Rust, Go, or anything else without this wrapper: send synchronously before exit; it's one HTTP call.
API
datagauge(opts: NodeOptions): DataGaugeNode
| Option | Type | Notes |
|---|---|---|
key | string | Required. Use an sk_live_ server key. |
url | string | Ingest base. Defaults to https://in.datagauge.dev. |
fetcher | typeof fetch | Injectable for tests. |
onError | (f: SendFailure) => void | Optional. 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.
| Field | Type | Notes |
|---|---|---|
sent | number | Events the server accepted this flush. |
ok | boolean | True 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. |
suspended | true | Ingest 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. |
reason | SendFailure | Why 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.
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.