@datagauge/edge

Last updated Aug 12, 2026View as MarkdownAgent setup

DataGauge for serverless & edge runtimes: Cloudflare Workers, Vercel Edge/Functions, Deno Deploy, Lambda.

The problem it solves

In these runtimes the process can be frozen the moment the response returns, so a naive fire-and-forget fetch is silently dropped. Every send must ride the platform's lifetime-extension hook: ctx.waitUntil(...) on Workers, waitUntil from @vercel/functions, event.waitUntil in service-worker-style runtimes. This wrapper does exactly that and nothing more; a failed send never throws, so analytics cannot break the caller's request path, but from 0.2.0 it is never silent either.

Install

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

This page documents 0.2.0, which is where the wrapper started reading ingest's response: the failure table below and onError arrive with it. On 0.1.0 the response is discarded and every refusal is silent. 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/edge/index.ts

Usage

import { datagauge } from "@datagauge/edge";
const dg = datagauge({ key: env.DATAGAUGE_KEY });
 
export default {
  async fetch(req, env, ctx) {
    dg.track({ name: "api_hit", user: userId }, ctx.waitUntil.bind(ctx));
    return new Response("ok"); // the send survives this return
  },
};

Pass the runtime's waitUntil and track returns immediately. Without one, the returned promise must be awaited before your handler resolves.

API

datagauge(opts: EdgeOptions): DataGaugeEdge

Creates a tracker.

OptionTypeNotes
keystringRequired. sk_live_ on servers; pk_live_ only for behavioral events.
urlstringOptional ingest base. Defaults to https://in.datagauge.dev.
fetchertypeof fetchOptional. Injected in tests; defaults to the global fetch.
onError(f: SendFailure) => voidOptional. Called instead of console.error when a send fails.

dg.track(event, waitUntil?)

track(event: DataGaugeEvent | DataGaugeEvent[], waitUntil?: (p: Promise<unknown>) => void): Promise<void>

Queue an event (or array of events) to send. Pass the runtime's waitUntil so the send outlives the response; otherwise await the returned promise.

A DataGaugeEvent is { name, user?, id?, at?, props? }, where name is the only required field. See POST /v1/events for the full field reference.

What happens when ingest refuses the batch

fetch() rejects only on transport failure, so a wrapper that ignores the response cannot tell a 401 from success. This one reads it:

ResponseWhat the wrapper does
202Nothing, unless the body carries a non-empty errors[], in which case the per-event rejections are reported. A 202 with rejections inside it is the only place you ever learn an event name or a prop was wrong.
Any 4xxReports the failure with the likely cause named, and returns.
429, 5xxReports it as retriable. The batch was not stored.
Transport failureReports it with status: 0 and the underlying error in the message.

Nothing here throws. This wrapper runs inside your request: the promise usually goes to waitUntil, where a rejection is an unhandled error in your Worker, and when it doesn't it is awaited on the request path itself. So the events in a refused batch are still gone, and a runtime that can freeze the moment the response returns has nowhere to hold them for a retry. What changed in 0.2.0 is that you are told. If these events must survive an outage, send them through @datagauge/node or @datagauge/client, which keep a buffer.

onError

Route failures somewhere other than console.error: your own reporter, an Analytics Engine dataset, a log line you already parse:

const dg = datagauge({
  key: env.DATAGAUGE_KEY,
  onError: (f) => {
    // f: { status, body, events, message, retriable }
    env.ERRORS.writeDataPoint({ blobs: [f.message], doubles: [f.status, f.events] });
  },
});

The default lands in wrangler tail and in Logpush, which is where someone debugging "my events stopped" is already looking. If your reporter itself throws, the failure it was called about is logged to the console instead, and the reporter's own error alongside it.

A Worker with no key

env is only readable inside the handler, so a module-scope datagauge({ key: process.env.DATAGAUGE_KEY }) is constructed with undefined and every event goes out as Bearer undefined. That is reported before anything is sent, rather than becoming a wall of 401s you have to go looking for.

Key hygiene

Use an sk_live_ server key here for anything involving money. A pk_live_ key works for behavioral events but is rejected for payment events, and that's by design; see Keys & scoping. The sk_test_ / pk_test_ prefixes do the same in your isolated test environment.