npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@inkronik/node-sdk

v1.3.0

Published

Inkronik telemetry SDK for Node.js, Bun, Express, and NestJS applications.

Readme

Inkronik Node SDK

Telemetry SDK for sending Node.js and Bun application signals to the Inkronik Collector.

Installation

bun add @inkronik/node-sdk

The package requires Node.js 20 or newer. Bun is supported for preload-based automatic PostgreSQL instrumentation.

Both ESM import and CommonJS require() are supported. Applications may load the core, preload, Express, and NestJS entrypoints independently; they share one process-level client and trace context so child spans remain correlated across package entrypoints.

Configuration

Create an ingest API key in your Inkronik workspace and configure the SDK through environment variables:

export INKRONIK_COLLECTOR_URL=https://collector.inkronik.codemask.dev
export INKRONIK_INGEST_API_KEY=your_ingest_api_key
export INKRONIK_SERVICE_NAME=orders-api

INKRONIK_APPLICATION_ID, INKRONIK_SERVICE_VERSION, INKRONIK_POD_NAME, and INKRONIK_ENVIRONMENT are optional. Keep the ingest API key server-side and never expose it in browser bundles or source control.

The package is split into:

  • core client for logs, events, gauges, and manual telemetry;
  • Express middleware for automatic HTTP span and request/response capture;
  • NestJS interceptor for automatic HTTP span and request/response capture;
  • NestJS logger adapter for forwarding application logs while preserving console output.
  • runtime metrics for Node memory, uptime, and event loop lag;
  • trace context propagation with W3C traceparent;
  • automatic fetch instrumentation for downstream HTTP client spans in framework adapters.
  • automatic postgres-js and pg query instrumentation under the Bun preload agent.

Core

import { createInkronikClientFromEnv } from '@inkronik/node-sdk'

const inkronik = createInkronikClientFromEnv()

inkronik.log({
    severityText: 'INFO',
    severityNumber: 9,
    message: 'Invoice created',
})

inkronik.event({
    name: 'invoice_created',
    category: 'billing',
    level: 'info',
    message: 'Invoice was created and queued for delivery',
    attributes: { invoice_id: invoice.uuid },
})

try {
    await capturePayment()
} catch (error) {
    inkronik.captureError(error, {
        name: 'payment_capture_failed',
        message: 'Payment remained pending and a retry was scheduled',
        attributes: { provider: 'stripe' },
    })
}

inkronik.startRuntimeMetrics()
const tracedFetch = inkronik.instrumentFetch()

Use withSpan() to trace work that does not begin with an incoming request, such as a cron run, startup task, or application-level operation:

const reconciled = await inkronik.withSpan({
    name: 'billing.reconcile',
    category: 'scheduled',
    attributes: {
        'job.schedule': '0 * * * *',
    },
    callback: () => reconcileBilling(),
})

The operation becomes a root span when no trace is active and a child span otherwise. Its callback runs inside the active Inkronik trace context, so instrumented fetch, PostgreSQL queries, logs, events, and nested withSpan() calls remain correlated. Synchronous return values and promises are preserved; thrown errors and rejected promises mark the span as failed and are rethrown unchanged. Manual spans default to the internal kind and category. Short-lived processes such as Kubernetes CronJobs should call await inkronik.shutdown() before exiting so queued telemetry is flushed.

Log messages, log attributes, and log resource attributes are redacted in the SDK before they are queued. Sensitive keys such as setupToken, access_token, password, and authorization are replaced with [REDACTED] by default, including when they appear in a JSON-formatted message. Add application-specific keys or patterns, change the replacement, or explicitly disable log redaction when creating the client:

const inkronik = createInkronikClientFromEnv({
    logRedaction: {
        fieldNames: ['merchantPrivateCode'],
        fieldPatterns: [/^internalCredential$/i],
        redactedValue: '<hidden>',
        // enabled: false, // explicit opt-out; avoid this for production telemetry
    },
})

Events support info, warning, and error levels and default to info. captureError records a handled error with its bounded type, message, stack, and string code when available; it does not turn a successful request span into a failed span.

Inside Express or NestJS request instrumentation, events automatically inherit the active trace, span, session, and user ID. Configure getUserContext on the adapter when events also need safe user attributes. An explicit event user overrides the inherited request user.

Initialization

For environment-based configuration, add Inkronik as the first application import:

import '@inkronik/node-sdk/init'

import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module.js'

Environment or secrets loaders may run before Inkronik when they provide its configuration:

import 'dotenv/config'
import '@inkronik/node-sdk/init'

This initializes the default client, starts runtime metrics, and instruments global fetch, pg, and integrations loaded later. The application start command does not need to change. Keep the Inkronik import ahead of framework, database, queue, and application imports.

Full Postgres.js auto-instrumentation

Static Postgres.js imports are resolved before application imports execute. Bun therefore needs the preload hook to instrument Postgres.js or Drizzle using the postgres-js driver transparently.

Load Inkronik before the application entrypoint:

// inkronik-trace.ts
import { initInkronik } from '@inkronik/node-sdk/auto'

export const inkronik = initInkronik()
bun --preload ./inkronik-trace.ts src/main.ts

For environment-only setup, preload the init entrypoint directly:

bun --preload @inkronik/node-sdk/init src/main.ts

The existing @inkronik/node-sdk/register entrypoint remains available as a backwards-compatible alias.

Use instrumentFetch() or instrumentGlobalFetch() in standalone Node processes. Express and NestJS adapters enable global fetch instrumentation by default, so outbound fetch calls made while handling a request appear as child client spans and propagate traceparent downstream.

Express

import express from 'express'
import { createInkronikClientFromEnv } from '@inkronik/node-sdk'
import { createInkronikExpressMiddleware } from '@inkronik/node-sdk/express'

const app = express()
const inkronik = createInkronikClientFromEnv()

app.use(express.json())
app.use(createInkronikExpressMiddleware({ client: inkronik }))

NestJS

import { APP_INTERCEPTOR } from '@nestjs/core'
import { createInkronikClientFromEnv } from '@inkronik/node-sdk'
import { createInkronikNestMiddleware, InkronikNestInterceptor, InkronikNestLogger } from '@inkronik/node-sdk/nest'

const inkronik = createInkronikClientFromEnv()
const logger = new InkronikNestLogger({ client: inkronik })

export const inkronikInterceptorProvider = {
    provide: APP_INTERCEPTOR,
    useValue: new InkronikNestInterceptor(inkronik),
}

Register the early HTTP middleware immediately after creating the Nest application, while keeping the interceptor provider above:

const app = await NestFactory.create(AppModule, { bufferLogs: true })

app.use(createInkronikNestMiddleware({ client: inkronik }))

Scheduled Nest methods can use the @InkronikSpan() decorator. It uses the process-level default client configured by createInkronikClientFromEnv() or initInkronik():

import { Cron, CronExpression } from '@nestjs/schedule'
import { InkronikSpan } from '@inkronik/node-sdk/nest'

export class BillingScheduler {
    @Cron(CronExpression.EVERY_HOUR)
    @InkronikSpan({
        name: 'billing.reconcile',
        category: 'scheduled',
        attributes: { 'job.schedule': 'hourly' },
    })
    async reconcile(): Promise<void> {
        await reconcileBilling()
    }
}

The decorator preserves existing method metadata, arguments, this, return values, and errors, so it can be combined with Nest scheduling decorators.

The middleware covers responses produced before interceptors run, including guard failures, unmatched routes, and request parser errors. The interceptor keeps framework exception details and stack traces for controller and pipe failures. Both adapters share request state, so a request produces one server span rather than duplicate middleware and interceptor spans.

Pass { autoInstrumentFetch: false } to the Express middleware or NestJS interceptor options when another tracer already patches global fetch.

PostgreSQL

The SDK supports two PostgreSQL drivers with different initialization requirements:

  • pg works with import-first initialization for TypeORM, Drizzle's node-postgres driver, direct Client queries, and direct Pool queries;
  • postgres works transparently with Bun preload for Postgres.js and Drizzle's postgres-js driver.

TypeORM does not need Inkronik-specific database configuration. Its existing pg client and pool queries become child database spans under the active request trace.

Postgres.js / Drizzle

With the Bun preload agent, application database code remains unchanged:

import postgres from 'postgres'
import { drizzle } from 'drizzle-orm/postgres-js'

const sql = postgres(process.env.DATABASE_URL!)

export const db = drizzle(sql)

Every Postgres.js client created after initInkronik() is automatically instrumented. The database name is read from the resolved Postgres.js options. Each query becomes a child database span under the active request trace; tagged-template parameters are replaced with ?, while unsafe() statements are normalized and truncated before capture.

Configure or disable the automatic integration in the preload file:

import { initInkronik } from '@inkronik/node-sdk/auto'

initInkronik({
    instrumentations: {
        pg: {
            captureStatement: true,
            maxStatementLength: 2_000,
            peerService: 'postgres-primary',
        },
        postgres: {
            captureStatement: true,
            maxStatementLength: 2_000,
            peerService: 'postgres-primary',
        },
    },
})

// Use `pg: false` to disable automatic node-postgres / TypeORM instrumentation.
// Use `postgres: false` to disable automatic Postgres.js instrumentation.

Transparent Postgres.js module loading currently targets Bun preload. When running without it, use the manual API:

import postgres from 'postgres'
import { drizzle } from 'drizzle-orm/postgres-js'
import { createInkronikClientFromEnv } from '@inkronik/node-sdk'

const inkronik = createInkronikClientFromEnv()
const sql = inkronik.instrumentPostgres({
    sql: postgres(process.env.DATABASE_URL!),
    databaseName: 'app',
    peerService: 'postgres-primary',
})
const db = drizzle(sql)

Pass { bufferLogs: true } to NestFactory.create, then call app.useLogger(logger) and app.flushLogs() to forward Nest startup logs as well.

Request bodies and successful response samples are captured by default with a 16 KiB body limit. Response samples keep object fields, truncate strings to 10 characters, keep numbers/booleans, and keep only the first array item plus ... when more items exist. Successful raw response bodies require captureResponseBody: true; error response bodies are captured automatically. Before telemetry is queued, the SDK recursively redacts sensitive JSON fields, token-bearing URL parameters, and JWT-like values. The Collector repeats server-side redaction before persistence.

Use redaction.fieldNames and redaction.fieldPatterns to add application-specific sensitive JSON fields, and redaction.redactedValue to change the replacement marker. These options only broaden redaction; built-in token patterns cannot be allowlisted for capture.

The NestJS interceptor also captures the Observable error path automatically. HttpException responses such as 400 validation errors retain their HTTP status and public response body. Other thrown values are recorded as 500 responses. Every response with status 400 or higher is marked as a failed request; thrown errors additionally attach their bounded type, message, code, and stack trace to the server span. The original exception continues through NestJS unchanged, so existing exception filters keep working without application-level Inkronik code.

Framework adapters resolve common trace user IDs from request.user or request.currentAccount by default. Fields outside the recognized set, such as userUUID, require an explicit resolver. Configure getUserContext as well when correlated events need safe user attributes:

import type { EventUserContext, HttpLikeRequest } from '@inkronik/node-sdk'

const getUserContext = (request: HttpLikeRequest): EventUserContext | undefined => {
    const account = request.currentAccount as { readonly role?: string; readonly uuid?: string } | undefined

    if (account?.uuid === undefined || account.uuid === '') {
        return undefined
    }

    return {
        id: account.uuid,
        attributes: account.role === undefined ? {} : { role: account.role },
    }
}

new InkronikNestInterceptor(inkronik, { getUserContext })

The same option is available under options in createInkronikExpressMiddleware. The resolver runs lazily, so authentication middleware or guards can attach the principal after tracing starts. Return only a stable user ID and allowlisted, non-sensitive string attributes; do not copy tokens, authorization headers, or arbitrary principal fields into telemetry. Authenticated integration tests should verify user.id on the server span and inherited user_id on events emitted inside the request.

Exclude health checks, metrics endpoints, or other requests before tracing:

new InkronikNestInterceptor(inkronik, {
    exclude: request => request.originalUrl === '/health',
})

The same exclude(request) option is available in createInkronikExpressMiddleware.

HTTP middleware emits:

  • server span;
  • http.server.requests sum;
  • http.server.duration histogram;
  • http.server.errors sum for 5xx responses.

Disable request/response capture explicitly for sensitive routes:

createInkronikExpressMiddleware({
    client: inkronik,
    options: {
        captureRequestResponse: false,
    },
})

License

MIT License. See LICENSE.