@ingestlayer/sdk
v0.0.2
Published
TypeScript SDK for ingestlayer.com — emit events from any JS or TS runtime.
Maintainers
Readme
@ingestlayer/sdk
Tiny TypeScript SDK for ingestlayer.com. Emit events from any JS runtime — Node, Bun, Deno, Workers, browser.
import { init, track, flush } from "@ingestlayer/sdk";
init({ apiKey: process.env.INGESTLAYER_KEY! });
track({
type: "user.signup",
payload: { email: "[email protected]", plan: "pro" },
entity: { kind: "person", id: "[email protected]" },
});
// On shutdown
await flush();Behaviour
- Events are batched (default: 50 events or 2s).
- Failed batches retry with exponential backoff + jitter.
- 4xx errors don't retry (except 408 / 429).
- On final failure,
onErroris invoked so you can persist offline. - No dependencies; uses global
fetch.
Delivery guarantees
flush()is the only hard delivery guarantee. Awaiting it sends every queued event (or surfaces failure viaonError) before you proceed. Use it on any code path where losing events is unacceptable.
To avoid silently dropping sub-batch events on shutdown, the client also
installs a best-effort flush-on-exit safety net, enabled by default
(flushOnExit: true — set false to opt out):
- Node (CLI / serverless): flushes on
process.beforeExit. The flush timer isunref'd and the exit hook never blocks shutdown, so this won't keep your process alive. - Browser: flushes on
visibilitychange→hidden andpagehide. It uses akeepalivefetch(which can carry the auth header), falling back tonavigator.sendBeacon. This is best-effort: a hard kill (crash, force-quit, OS swap-out) can still drop the final batch, and asendBeaconfallback is sent without the auth header.
const il = new Ingestlayer({ apiKey, flushOnExit: false }); // disable the net
// ...
await il.close(); // detach exit hooks + final flush when disposing a clientFor anything you truly cannot lose, call await flush() explicitly — don't rely
on the exit hooks.
Entities
Events are history; entities are state — one row per (kind, externalId) in
your org's identity graph, with open traits. Upsert one with the same API key:
import { Ingestlayer } from "@ingestlayer/sdk";
const il = new Ingestlayer({ apiKey: process.env.INGESTLAYER_KEY! });
const user = await il.upsertEntity({
kind: "user",
externalId: "[email protected]",
traits: { plan: "pro", country: "GB" },
});
// → { id, kind, externalId, traits, firstSeen, lastSeen }
// identify() is an alias that defaults kind to "user":
await il.identify({ externalId: "[email protected]", traits: { plan: "enterprise" } });Unlike track(), this is not queued or batched — it's an awaited,
canonical-state write that resolves with the merged row:
- Traits deep-merge one level server-side, so a partial upsert keeps prior
fields. The same
externalIdtwice updates the same row. - It retries
5xx/408/429like event delivery, but throws on a 4xx (validation / auth) so you see the failure directly. - Entities post to the app host, a different origin than event ingest.
Override it with
appEndpoint(defaulthttps://app.ingestlayer.com):
new Ingestlayer({ apiKey, appEndpoint: "http://localhost:3001" });License
Apache-2.0.
