@getfluxly/node
v0.2.1
Published
GetFluxly Node.js SDK for backend lifecycle events — batched ingest, automatic retries, idempotent delivery, and alias support.
Maintainers
Readme
@getfluxly/node
Official GetFluxly Node.js SDK for backend lifecycle events. Batched ingest, automatic retries with jittered backoff, idempotent delivery via X-Idempotency-Key, and support for the server-only alias endpoint.
- Runtime: Node 18+.
- Module: ESM.
- Types: shipped in
dist/index.d.ts. - Zero runtime dependencies.
Use this package from trusted backend code. Browser code should use @getfluxly/browser with a publishable key.
Install
npm install @getfluxly/nodeQuick Start
import { initGFluxNode } from "@getfluxly/node";
const gflux = initGFluxNode({
token: process.env.GFLUX_SERVER_TOKEN!,
apiHost: "https://api.getfluxly.com",
});
await gflux.track("invoice_paid", {
userId: "user_123",
properties: {
invoice_id: "inv_456",
total_usd: 99.99,
},
});By default the client batches: events are queued until 20 are pending or 5 seconds elapse, then flushed in a single request. Customers who need synchronous per-event delivery can pass flushAt: 1.
Always call await gflux.shutdown() before your process exits if you are using batching; otherwise queued events on the last interval can be dropped.
API
initGFluxNode(config)
Create a server-side client.
const gflux = initGFluxNode({
token: "gflux_secret_live_xxx",
});Config:
| Key | Default | Description |
| --- | --- | --- |
| token | required | API key. Use a server key for backend code. |
| apiHost | https://api.getfluxly.com | API origin. Validated with new URL(). |
| flushAt | 20 | Number of queued events that triggers a flush. Set to 1 for per-event delivery. |
| flushIntervalMs | 5000 | Background flush interval. 0 disables it. |
| maxRetries | 2 | Retries after network errors, 408/425/429, and 5xx. |
| timeoutMs | 5000 | Per-request timeout. 0 disables it. |
| maxQueueSize | 1000 | Maximum queued events before new events are rejected with queue_overflow. |
| defaultContext | {} | Context merged into every event before SDK audit fields are added. |
| includeRuntimeContext | false | Opt-in: include node_version, platform, arch on every event. Off by default to avoid sending a runtime fingerprint to SaaS backends. |
| fetch | global fetch | Custom fetch for tests or non-standard runtimes. |
| logger | none | Logger invoked for retries (debug), HTTP rejections, flush failures, and exhausted retries (warn). |
gflux.track(eventName, input)
Send a backend event.
await gflux.track("subscription_started", {
userId: "user_123",
properties: {
plan: "pro",
subscription_id: "sub_456",
},
});Identity rules match the public ingest API:
- Use
userIdorexternalIdfor a known user. - Use
anonymousIdfor anonymous backend events. - You can send both when you already know they belong together.
propertiesandcontextmust be objects.
userId is a friendly alias for external_id. If you pass both, they must match.
gflux.identify(input)
Link anonymous activity to a known user.
await gflux.identify({
anonymousId: "anon_abc",
userId: "user_123",
traits: {
email: "[email protected]",
plan: "pro",
},
});identify stitches earlier anonymous activity to the known user. It sends the same identify event shape used by the browser SDK: both anonymous_id and external_id are required, and traits are sent under properties.traits.
gflux.alias(input)
Call the server-only alias endpoint.
await gflux.alias({
userId: "user_123",
anonymousId: "anon_current",
previousId: "anon_old",
});Use a gflux_secret_... key. Publishable keys are rejected by the API. At least one of anonymousId or previousId is required.
gflux.flush()
Flush queued events.
const result = await gflux.flush();
console.log(result.accepted, result.rejected);Batches are automatically split into chunks of 50 events because the API rejects larger batches. Every batch carries an X-Idempotency-Key header so a retried request will not double-write events on the backend.
gflux.shutdown()
Stop the background timer and flush queued events.
await gflux.shutdown();Call this before a CLI, worker, or test process exits when you use batching.
Batching
const gflux = initGFluxNode({
token: process.env.GFLUX_SERVER_TOKEN!,
flushAt: 50,
flushIntervalMs: 10_000,
});With batching enabled, track() and identify() return null until a flush happens. flush() and shutdown() always return a FlushResult.
Idempotency
Every batch and every alias request carries X-Idempotency-Key: <uuid> so the backend can dedupe retries. The SDK retries 408, 425, 429, and 5xx responses, plus network failures, with exponential backoff and ±25% jitter. The same key survives a retry — the backend will see the second attempt as a duplicate of the first and respond accordingly.
Errors
The SDK throws GFluxNodeError for validation, HTTP, timeout, and network failures.
import { GFluxNodeError } from "@getfluxly/node";
try {
await gflux.track("invoice_paid", { userId: "user_123" });
} catch (error) {
if (error instanceof GFluxNodeError) {
if (error.code === "queue_overflow") {
// Apply your own backoff using error.retryAfterMs.
await wait(error.retryAfterMs ?? 1000);
} else if (error.retryable) {
// Network/timeout/5xx — retry at your application boundary if the
// event is business-critical.
}
}
}GFluxNodeError fields:
| Field | Type | Description |
| --- | --- | --- |
| code | string | Stable error code (validation_error, queue_overflow, network_error, request_timeout, invalid_response, http_4xx, client_closed, ...). |
| status | number? | HTTP status when applicable. |
| retryable | boolean | true for transient transport errors and 408/425/429/5xx. |
| retryAfterMs | number? | Suggested wait before re-enqueueing on queue_overflow. |
| details | unknown | Backend error body when the server returned one. |
| cause | unknown | Original error preserved when wrapping. |
Validation errors, 401, 403, and other client errors are not retried.
Event Context
Every event includes minimal SDK audit context:
{
"library": "gflux-node/0.2.0",
"runtime": "node"
}Pass includeRuntimeContext: true to add node_version, platform, and arch. Add your own context with defaultContext (merged into every event) or per call with context.
SDK audit fields always win so backend logs can tell which package sent the event.
Logging
import { initGFluxNode } from "@getfluxly/node";
const gflux = initGFluxNode({
token: process.env.GFLUX_SERVER_TOKEN!,
logger: {
debug: (msg, ctx) => myLogger.debug(msg, ctx),
warn: (msg, ctx) => myLogger.warn(msg, ctx),
},
});Hooks invoked:
debug— every transient retry (HTTP and network).warn— HTTP rejections, exhausted retries, scheduled flush failures, requeues after retryable flush errors.
License
MIT
