@newly/sdk-server
v0.1.0
Published
Newly analytics SDK for backends — runtime-neutral (Node >= 20, Cloudflare Workers, Bun)
Readme
@newly/sdk-server
Newly analytics for backends. Runtime-neutral: Node ≥ 20, Cloudflare Workers,
Bun — fetch + crypto.randomUUID + timers only, no Node built-ins
(ESLint-enforced).
Install
npm install @newly/sdk-serverUse (Hono on Cloudflare Workers — the Newly generated-backend shape)
import { createNewlyServer } from '@newly/sdk-server';
const newly = createNewlyServer({ ingestKey: env.NEWLY_INGEST_KEY, apiUrl: env.NEWLY_API_URL });
app.post('/purchase', async (c) => {
// distinctId is explicit per call — servers have no ambient identity.
newly.track(userId, 'purchase_confirmed', { amount_cents: 999 });
c.executionCtx.waitUntil(newly.flush()); // Workers: timers don't survive the request
return c.json({ ok: true });
});flush() settles with a FlushResult when you want the delivery signal inline:
const { delivered, pending } = await newly.flush();
if (!delivered) console.warn(`ingest did not ack — ${pending} events still pending`);On long-lived Node servers the 2 s batch timer flushes on its own; call
await newly.shutdown() on graceful exit.
Semantics (vs the client SDKs)
- Explicit
distinctIdper call; no anonymous ids, sessions, first-touch, orscreen. Server rows join client rows on the same person key without remaps. - In-memory batching only (≤ 20 events / 2 s): servers are stateless; a crash
loses at most one batch.
await newly.flush()after critical events. flush()drives retries inline (jittered backoff, 30 s cap,Retry-Afterwins) to a terminal outcome and resolves withFlushResult = { delivered: boolean; pending: number }:delivered: trueonly when every flushed event was acked (202/207);delivered: false— after emittingonError— when the batch was dropped (400/413/422), sends are halted (401), or the 30 s inline retry budget ran out. It never rejects, and it never resolves while a retry is still secretly pending on a background timer (which a Workers request would orphan — that was silent loss).context.platform = "server"so panels can split server-emitted events.
