@getfluxly/node

Node SDK

Server-side ingest from any Node.js runtime, Vercel Functions, AWS Lambda, long-running services, scripts. Batches events with retry, jitter, and X-Idempotency-Key so transient network failures never double-count.

Use this package from trusted backend code. Browser code should use @getfluxly/browser with a publishable key.

Install

npm install @getfluxly/node

Quick start

import { initGFluxNode } from "@getfluxly/node";

const gflux = initGFluxNode({
  token: process.env.GFLUX_SERVER_TOKEN,
  apiHost: "https://api.getfluxly.com",
});

await gflux.track("subscription_started", {
  userId: "user_42",
  properties: { plan: "pro" },
});

await gflux.identify({
  anonymousId: "anon_a8f3c2",
  userId: "user_42",
  traits: { email: "x@y.com", plan: "pro" },
});

await gflux.alias({
  userId: "user_42",
  anonymousId: "anon_a8f3c2",
});

await gflux.flush();

Init config

initGFluxNode(config) creates and returns a server-side client.

KeyDefaultDescription
tokenrequiredAPI key. Use a gflux_secret_... key for backend code.
apiHosthttps://api.getfluxly.comAPI origin. Validated with new URL() at startup.
defaultContext{}Context object merged into every event before SDK audit fields are added. Per-call context is merged on top of this.
includeRuntimeContextfalseOpt-in: attach node_version, platform, and arch to every event. Off by default to avoid sending a runtime fingerprint to SaaS backends.
fetchglobal fetchCustom fetch implementation for tests or non-standard runtimes.
loggernoneLogger invoked for retries (debug) and HTTP rejections, flush failures, and exhausted retries (warn). See Logging.
flushAt20Number of queued events that triggers an automatic flush. Set to 1 for synchronous per-event delivery.
flushIntervalMs5000Background flush interval in milliseconds. 0 disables the background timer.
maxRetries2Retries after network errors, 408, 425, 429, and 5xx responses.
timeoutMs5000Per-request timeout in milliseconds. 0 disables the timeout.
maxQueueSize1000Hard cap on queued events. New events beyond this limit throw queue_overflow.

track()

Send a backend event.

await gflux.track("invoice_paid", {
  userId: "user_123",
  properties: {
    invoice_id: "inv_456",
    total_usd: 99.99,
  },
});

Identity rules:

identify()

Link anonymous activity to a known user. Both anonymousId and userId are required.

await gflux.identify({
  anonymousId: "anon_abc",
  userId: "user_123",
  traits: {
    email: "jane@example.com",
    plan: "pro",
  },
});

The event shape sent to the API mirrors the browser SDK: anonymous_id and external_id are both required, and traits travel under properties.traits. Earlier anonymous activity is stitched to the known user on the backend.

alias()

Call the server-only alias endpoint. Use a gflux_secret_... key. Publishable keys are rejected by the API.

await gflux.alias({
  userId: "user_123",
  anonymousId: "anon_current",
  previousId: "anon_old",
});

At least one of anonymousId or previousId is required. Both are accepted when an anonymous session spanned multiple devices.

flush()

Flush all queued events immediately.

const result = await gflux.flush();
console.log(result.accepted, result.rejected);

flush() always returns a FlushResult with accepted and rejected counts. Batches are automatically split into chunks of 50 events because the API rejects larger payloads. Every chunk carries an X-Idempotency-Key header that persists across retries.

In short-lived runtimes (Lambda, Vercel Functions) call flush() before the handler returns, otherwise the batch may never ship.

shutdown()

Stop the background flush timer and drain the queue.

await gflux.shutdown();

Call this before a CLI, worker, or test process exits when batching is enabled. Queued events on the last interval are dropped if you skip this.

Batching

const gflux = initGFluxNode({
  token: process.env.GFLUX_SERVER_TOKEN,
  flushAt: 50,
  flushIntervalMs: 10_000,
});

track() and identify() return null while events are queued. flush() and shutdown() always return a FlushResult.

Idempotency

Every batch and every alias request carries X-Idempotency-Key: <uuid>. The same key is reused across all retries of the same batch so the backend deduplicates them. The SDK retries 408, 425, 429, and 5xx responses, plus network failures, with exponential backoff and ±25% jitter.

Errors

Always check instanceof GFluxNodeError before inspecting fields.

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; the batch is full.
      await wait(error.retryAfterMs ?? 1000);
    } else if (error.retryable) {
      // Network/timeout/5xx: the SDK already exhausted maxRetries.
      // Retry at your application boundary if the event is business-critical.
    }
  }
}

GFluxNodeError fields:

FieldTypeDescription
codestringStable error code: validation_error, queue_overflow, network_error, request_timeout, invalid_response, http_4xx, client_closed, and others.
statusnumber | undefinedHTTP status code when applicable.
retryablebooleantrue for transient transport errors and 408/425/429/5xx responses.
retryAfterMsnumber | undefinedSuggested wait before re-enqueueing on queue_overflow.
detailsunknownBackend error body when the server returned one.
causeunknownOriginal error preserved when wrapping lower-level failures.

Validation errors, 401, 403, and other permanent client errors are not retried.

Full stable code list at errors.

Event context

Every event includes minimal SDK audit fields:

{
  "library": "gflux-node/0.2.0",
  "runtime": "node"
}

These fields always win over any caller-supplied values so backend logs can reliably identify which package sent the event.

Pass includeRuntimeContext: true to also attach node_version, platform, and arch. Add your own context with defaultContext (merged into every event at init time) or with the per-call context field. Merge order: defaultContext first, then per-call context, then SDK audit fields on top.

Logging

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: