New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@layers/node

Package Overview
Dependencies
Maintainers
4
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@layers/node

Layers Analytics Node.js SDK — thin wrapper over Rust core via WASM

latest
Source
npmnpm
Version
2.2.0
Version published
Maintainers
4
Created
Source

Layers Node.js SDK

@layers/node is the Layers analytics SDK for server-side Node.js applications. It provides event tracking, screen tracking, user property management, consent management, Express middleware for automatic HTTP request tracking, and Next.js integration with AsyncLocalStorage for request context propagation.

Unlike client-side SDKs that store a user ID per session, the Node.js SDK requires a distinctId on every call. This is the correct pattern for servers handling concurrent requests from many users.

Requirements

  • Node.js 18+

Installation

npm install @layers/node
# or
yarn add @layers/node
# or
pnpm add @layers/node

Quick Start

import { LayersNode } from '@layers/node';

const layers = new LayersNode({
  appId: 'your-app-id',
  environment: 'production'
});

// Track events with a distinctId
layers.track('user_123', 'page_view', { path: '/dashboard' });

// Track screen views
layers.screen('user_123', 'Dashboard');

// Set user properties
layers.setUserProperties('user_123', { plan: 'premium' });

// Flush on demand
await layers.flush();

// Graceful shutdown
await layers.shutdown();

Configuration

LayersNodeConfig

interface LayersNodeConfig {
  appId: string;
  environment: 'development' | 'staging' | 'production';
  enableDebug?: boolean;
  baseUrl?: string;
  flushIntervalMs?: number;
  flushThreshold?: number;
  maxQueueSize?: number;
  maxBatchSize?: number;
  shutdownFlushTimeoutMs?: number;
  handleSignals?: boolean;
  persistenceDir?: string;
}
OptionTypeDefaultDescription
appIdstringrequiredYour Layers application identifier.
environmentEnvironmentrequired'development', 'staging', or 'production'.
enableDebugbooleanfalseEnable verbose console logging.
baseUrlstring"https://in.layers.com"Custom ingest API endpoint.
flushIntervalMsnumber10000Automatic flush interval in milliseconds. Server SDKs default to 10s for lower latency.
flushThresholdnumber20Queue size that triggers an automatic flush.
maxQueueSizenumber10000Maximum events in the queue before dropping.
maxBatchSizenumberundefinedMaximum events sent in a single HTTP batch.
shutdownFlushTimeoutMsnumber5000Maximum time (ms) to wait for a final flush during shutdown.
handleSignalsbooleantrueRegister SIGTERM/SIGINT handlers for graceful shutdown.
persistenceDirstringos.tmpdir()/layers-sdk/<appId>Directory for event persistence files.
const layers = new LayersNode({
  appId: 'your-app-id',
  environment: 'production',
  flushIntervalMs: 5000,
  flushThreshold: 50,
  maxQueueSize: 50000,
  shutdownFlushTimeoutMs: 10000,
  handleSignals: true
});

Core API

Event Tracking

track(distinctId: string, eventName: string, properties?: EventProperties): void

Track an event for a specific user. The distinctId is required on every call.

layers.track('user_123', 'purchase_completed', {
  product_id: 'sku_123',
  price: 9.99,
  currency: 'USD'
});

Screen Tracking

screen(distinctId: string, screenName: string, properties?: EventProperties): void
layers.screen('user_123', 'Dashboard', { tab: 'overview' });

User Properties

setUserProperties(distinctId: string, properties: UserProperties): void
layers.setUserProperties('user_123', {
  email: 'user@example.com',
  plan: 'premium',
  company: 'Acme Corp'
});
setConsent(consent: ConsentState): void
getConsentState(): ConsentState

Consent applies globally to the SDK instance, not per-user.

layers.setConsent({ analytics: true, advertising: false });

Session & Queue

getSessionId(): string
queueDepth(): number

Flush & Shutdown

// Flush all queued events
async flush(): Promise<void>

// Graceful shutdown: flush remaining events (with timeout), then stop
async shutdown(): Promise<void>

Error Handling

on(event: 'error', listener: (error: Error) => void): this
off(event: 'error', listener: (error: Error) => void): this
layers.on('error', (error) => {
  console.error('Layers error:', error.message);
});

Express Middleware

Import from @layers/node/express:

import { LayersNode } from '@layers/node';
import { layersExpressMiddleware } from '@layers/node/express';
import express from 'express';

const app = express();
const layers = new LayersNode({
  appId: 'your-app-id',
  environment: 'production'
});

app.use(layersExpressMiddleware(layers));

Middleware Options

interface LayersExpressOptions {
  trackRequests?: boolean; // Track each HTTP request. Default: true
  trackResponseTime?: boolean; // Include response_time_ms. Default: true
  ignorePaths?: string[]; // Paths to skip. Default: ['/health', '/healthz', '/ready', '/favicon.ico']
}
app.use(
  layersExpressMiddleware(layers, {
    trackRequests: true,
    trackResponseTime: true,
    ignorePaths: ['/health', '/healthz', '/ready', '/favicon.ico', '/metrics']
  })
);

What It Tracks

For each HTTP request (excluding ignored paths), the middleware tracks an http_request event with:

  • method -- HTTP method (GET, POST, etc.)
  • path -- Request path
  • status_code -- Response status code
  • response_time_ms -- Response time in milliseconds (if trackResponseTime is true)

User Identification

The middleware resolves the distinctId from request headers:

  • X-User-Id header
  • X-App-User-Id header
  • Falls back to 'anonymous'

The header value is sanitized: must match [a-zA-Z0-9_@.+-] and be at most 128 characters.

Next.js Integration

Import from @layers/node/nextjs:

import { LayersNode } from '@layers/node';
import {
  getLayersContext,
  trackServerAction,
  trackServerPageView,
  withLayersContext
} from '@layers/node/nextjs';

AsyncLocalStorage Context

The Next.js integration uses AsyncLocalStorage to propagate request context (user identity and properties) through the request lifecycle.

// In middleware or API route
withLayersContext({ distinctId: userId, properties: { role: 'admin' } }, async () => {
  // Inside this callback, getLayersContext() returns the context
  const ctx = getLayersContext();
  layers.track(ctx!.distinctId, 'api_call', { endpoint: '/users' });
});

Request Context

interface RequestContext {
  distinctId: string;
  properties: Record<string, unknown>;
}

function withLayersContext<T>(context: RequestContext, fn: () => T): T;
function getLayersContext(): RequestContext | undefined;

Server Page View Tracking

function trackServerPageView(layers: LayersNode, path: string, distinctId?: string): void;

Tracks a page_view event. Resolves distinctId from the current AsyncLocalStorage context, falling back to the provided distinctId or 'anonymous'. Automatically adds server_rendered: true and any context properties.

// In a Next.js Server Component or API route
trackServerPageView(layers, '/dashboard');

// With explicit distinctId
trackServerPageView(layers, '/dashboard', 'user_123');

Server Action Tracking

function trackServerAction(
  layers: LayersNode,
  actionName: string,
  properties?: Record<string, unknown>,
  distinctId?: string
): void;

Tracks a server_action event with action_name as a property.

// In a Next.js Server Action
'use server';

export async function createPost(formData: FormData) {
  trackServerAction(layers, 'create_post', {
    title: formData.get('title')
  });
  // ... create post
}

Next.js Middleware Example

// middleware.ts
import { withLayersContext } from '@layers/node/nextjs';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const userId = request.headers.get('x-user-id') ?? 'anonymous';

  return withLayersContext(
    { distinctId: userId, properties: { path: request.nextUrl.pathname } },
    () => NextResponse.next()
  );
}

Next.js API Route Example

// app/api/users/route.ts
import { layers } from '@/lib/layers';

export async function GET(request: Request) {
  const userId = request.headers.get('x-user-id') ?? 'anonymous';

  layers.track(userId, 'api_call', {
    method: 'GET',
    path: '/api/users'
  });

  return Response.json({ users: [] });
}

Signal Handling

By default (handleSignals: true), the SDK registers SIGTERM and SIGINT handlers that flush remaining events before the process exits. This is critical for:

  • Kubernetes pod termination
  • Docker container shutdown
  • Ctrl+C during development

All LayersNode instances are flushed on signal receipt. Set handleSignals: false if you manage process signals yourself.

// Disable automatic signal handling
const layers = new LayersNode({
  appId: 'your-app-id',
  environment: 'production',
  handleSignals: false
});

// Handle shutdown yourself
process.on('SIGTERM', async () => {
  await layers.shutdown();
  process.exit(0);
});

Event Persistence

Events are persisted to the filesystem (default: os.tmpdir()/layers-sdk/<appId>) so they survive process restarts. Configure the directory:

const layers = new LayersNode({
  appId: 'your-app-id',
  environment: 'production',
  persistenceDir: '/var/data/layers'
});

Automatic Behaviors

  • Periodic flush: Events are flushed every flushIntervalMs (default 10s).
  • Signal handling: SIGTERM/SIGINT trigger graceful shutdown with flush.
  • Event persistence: Events are persisted to disk and rehydrated on restart.
  • Retry with backoff: Failed network requests are retried automatically.
  • Circuit breaker: Repeated failures temporarily disable network calls.

Multiple Instances

You can create multiple LayersNode instances for different apps:

const layersApp1 = new LayersNode({
  appId: 'app-1',
  environment: 'production'
});

const layersApp2 = new LayersNode({
  appId: 'app-2',
  environment: 'production'
});

// Both are flushed on SIGTERM if handleSignals is true

TypeScript Types

import type {
  ConsentState,
  Environment,
  ErrorListener,
  EventProperties,
  LayersNodeConfig,
  UserProperties
} from '@layers/node';
import type { LayersExpressOptions } from '@layers/node/express';
import type { RequestContext } from '@layers/node/nextjs';

Keywords

layers

FAQs

Package last updated on 03 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts