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

@usekamori/sdk

v1.1.0

Published

Kamori SDK — drop-in console shim and logger integrations

Downloads

175

Readme

@usekamori/sdk

Kamori client SDK. Provides a batching HTTP client for shipping log events to a Kamori ingest server, plus a console shim and integrations for popular logging libraries.

KamoriClient

The core building block — a fire-and-forget HTTP client with internal batching.

import { KamoriClient } from "@usekamori/sdk";

const kamori = new KamoriClient({
  url: "https://your-kamori-server.example.com",
  token: process.env.INGEST_TOKEN, // must match INGEST_TOKEN on the server
  batchSize: 50, // flush when buffer reaches this size (default: 50)
  flushInterval: 2000, // flush every N ms even if buffer isn't full (default: 2000)
  flushOnExit: true, // register SIGINT/SIGTERM/exit handlers
  captureSource: "auto", // append _source: "file:line" to every event
  offlineQueue: true, // spool failed batches to localStorage (browser only)
});

// Log a single event (non-blocking)
kamori.log({
  service: "myapp",
  level: "info",
  message: "user signed in",
  userId: "u_123",
});

// Flush immediately (e.g. at process exit)
kamori.flush();

Options

| Option | Type | Default | Description | | --------------- | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | url | string | required | Base URL of your Kamori server | | token | string? | — | Auth token (sent as Authorization: Bearer) | | batchSize | number | 50 | Flush when buffer reaches this size | | flushInterval | number | 2000 | Flush interval in milliseconds | | flushOnExit | boolean | false | Registers SIGINT, SIGTERM, and exit handlers to drain the buffer before the process terminates | | captureSource | boolean | "auto" | false | Appends _source: "file:line" to every event. "auto" enables it only when NODE_ENV !== "production" | | offlineQueue | boolean | false | Spools failed batches to localStorage (browser only) and retries automatically on reconnect. Silently ignored in Node.js. |

Behaviour

  • log() is synchronous and never throws. Events are buffered in memory.
  • flush() is fire-and-forget. Network errors are silently dropped — logging must never crash the caller.
  • Trailing events at process exit: use flushOnExit: true or call kamori.flush() in your shutdown handler.

scoped() and ScopedKamoriClient

scoped() returns a lightweight child client that shares the parent's buffer and flush cycle. Use it to stamp a fixed set of fields (e.g. service, env, requestId) onto every event without repeating them at each call site.

import { KamoriClient, ScopedKamoriClient } from "@usekamori/sdk";

const root = new KamoriClient({ url: "...", token: "..." });

const api = root.scoped({ service: "api", env: "production" });
api.log({ level: "info", message: "request received", path: "/checkout" });
// → { service: "api", env: "production", level: "info", message: "request received", path: "/checkout" }

// Per-request child: nest scopes
const reqScope = api.scoped({ requestId: "req_abc123" });
reqScope.log({ level: "error", message: "payment failed" });
// → { service: "api", env: "production", requestId: "req_abc123", level: "error", message: "payment failed" }

ScopedKamoriClient is also exported as a named class if you need to type it explicitly:

import { ScopedKamoriClient } from "@usekamori/sdk";

installShim

Patches the global console so every console.log, console.warn, console.error, and console.debug call is forwarded to Kamori automatically, while still printing to the terminal (or browser devtools) normally.

import { installShim } from "@usekamori/sdk";

installShim({
  url: "https://your-kamori-server.example.com",
  token: process.env.INGEST_TOKEN,
});

// From this point on, all console output is captured and shipped to Kamori.
console.log("server started", { port: 3110 });
console.error("payment failed", { orderId: 99 });

Call installShim once, at the very top of your entry point, before any other imports.

Level mapping

| Console method | Kamori level | | --------------- | -------------- | | console.log | info | | console.warn | warn | | console.error | error | | console.debug | debug |

Event shape

The shim builds a log event from the arguments passed to the console method:

| Arguments | Event shape | | ------------------------------ | ----------------------------------------------------- | | console.log("message") | { level: "info", message: "message" } | | console.log("message", a, b) | { level: "info", message: "message", args: [a, b] } | | console.log({ obj }) | { level: "info", args: [{ obj }] } |

When the first argument is a string, it becomes message. Any additional arguments are collected into args[]. When the first argument is not a string, all arguments go into args[].

Browser usage

installShim works in both Node.js and browser environments — it only uses fetch and console, which are available everywhere.

CORS: When calling Kamori from a browser, the server must respond with appropriate Access-Control-Allow-Origin headers. Configure @fastify/cors on your Kamori server:

// packages/ingest/src/ingest.ts
await server.register(import("@fastify/cors"), {
  origin: "https://your-app.example.com",
});

Or allow all origins during development:

await server.register(import("@fastify/cors"), { origin: true });

Browser example

<script type="module">
  import { installShim } from "https://cdn.jsdelivr.net/npm/@usekamori/sdk/src/index.js";

  installShim({ url: "https://your-kamori-server.example.com" });

  console.error("JS error", { page: location.pathname });
  // ^ shipped to Kamori and visible in devtools
</script>

Pino transport — @usekamori/sdk/pino

createKamoriStream returns a Node.js Writable stream that pino can write to. Each NDJSON line is parsed and forwarded to Kamori via KamoriClient.

import pino from "pino";
import { createKamoriStream } from "@usekamori/sdk/pino";

const stream = createKamoriStream({
  url: "https://your-kamori-server.example.com",
  token: process.env.INGEST_TOKEN,
});

const logger = pino(stream);
logger.error({ orderId: 99 }, "payment failed");

The stream flushes and tears down the underlying KamoriClient when destroyed (e.g. stream.end() or process exit via pino.destination().destroy()).


Winston transport — @usekamori/sdk/winston

import winston from "winston";
import { KamoriTransport } from "@usekamori/sdk/winston";

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new KamoriTransport({
      url: "https://your-kamori-server.example.com",
      token: process.env.INGEST_TOKEN,
    }),
  ],
});

logger.error("payment failed", { orderId: 99 });

KamoriTransport accepts the same options as KamoriClient (batchSize, flushInterval, etc.).


Browser SDK — @usekamori/sdk/browser

A separate entry point optimised for browser environments:

  • Uses navigator.sendBeacon for reliable delivery on page unload
  • Falls back to fetch when Beacon is not available
  • No Node.js-specific APIs
import { KamoriClient, installErrorCapture } from "@usekamori/sdk/browser";

const client = new KamoriClient({
  url: "https://your-kamori-server.example.com",
  token: "...",
});
client.log({
  service: "web",
  level: "error",
  message: "JS error",
  page: location.pathname,
});

// Automatically capture window.onerror and window.onunhandledrejection
const cleanup = installErrorCapture(client);
// cleanup() removes the handlers (useful during hot-module replacement or test teardown)

installErrorCapture(client, opts) options

| Option | Type | Default | Description | | ---------------------------- | ------- | ------- | ----------------------------------------------------------------------------- | | captureUnhandledRejections | boolean | true | Whether to hook window.onunhandledrejection in addition to window.onerror |


Sensitive data & PII

@usekamori/sdk ships events verbatim. Redaction is your responsibility — strip sensitive values before calling client.log() or before they reach a logger transport, not after.

Pino users — use pino's built-in redact option. It costs nothing extra and runs before serialisation:

const logger = pino({
  redact: ["password", "token", "user.email", "headers.authorization"],
  transport: { target: "@usekamori/sdk/pino", options: { url: "...", token: "..." } },
});

All other transports — wrap client.log() with bluestreak for GDPR / PCI-DSS / HIPAA coverage:

import { compileRecommendedPolicy, redactLine } from "bluestreak";
const policy = compileRecommendedPolicy(); // compile once

function log(event: Record<string, unknown>) {
  kamori.log(JSON.parse(redactLine(JSON.stringify(event), policy)));
}

See docs/SDK.md for Winston, stream pipeline, and custom policy examples.


Next.js middleware — @usekamori/sdk/next

Wrap any Next.js API route or Edge handler with withKamori to automatically log request errors to Kamori.

// middleware.ts  (or app/api/route/route.ts)
import { withKamori } from "@usekamori/sdk/next";
import { NextResponse } from "next/server";

export default withKamori(async (req) => NextResponse.next(), {
  url: process.env.KAMORI_URL!,
  token: process.env.INGEST_TOKEN,
});

export const config = { matcher: "/((?!_next|favicon.ico).*)" };

withKamori logs one info event per request (method, path, status, duration_ms). Thrown errors are logged at level: "error" and re-thrown so Next.js error handling continues normally.

withKamori(handler, opts) options

opts is a KamoriClientOptions object (url, token, batchSize, flushInterval, etc.) — the same options accepted by KamoriClient. A single client instance is created per withKamori call and shared across all requests.

Trace correlation — @usekamori/sdk

Kamori auto-attaches a trace_id to every event so logs from different services join into one request chain (query them with the trace_logs MCP tool). Resolution order: an explicit trace_id on the event wins, then the ambient context, then the active OpenTelemetry span.

Ambient context (Tier 0)

Set an id once per request; every log inside the callback carries it:

import { KamoriClient, withTrace, generateTraceId } from "@usekamori/sdk";

const kamori = new KamoriClient({ url: process.env.KAMORI_URL! });

withTrace(generateTraceId(), () => {
  kamori.log({ service: "api", level: "info", message: "request received" });
  kamori.log({ service: "api", level: "error", message: "payment failed" });
  // both events share the same trace_id
});

setTraceContext({ trace_id }) sets the id without wrapping a callback (for middleware).

OpenTelemetry (Tier 2)

If @opentelemetry/api is installed and a span is active, its trace_id and span_id are attached automatically — no code changes:

import { trace } from "@opentelemetry/api";

trace.getTracer("app").startActiveSpan("checkout", (span) => {
  kamori.log({ service: "api", level: "info", message: "charging card" });
  // → event carries the span's trace_id + span_id
  span.end();
});

OpenTelemetry is an optional peer — the SDK installs and runs without it.

Cross-service propagation (Tier 1)

Reuse an inbound W3C traceparent (or x-request-id) so a request keeps one id across services, or generate one at the edge:

import { traceFromHeaders, generateTraceId, withTrace } from "@usekamori/sdk";

const trace_id =
  traceFromHeaders((n) => req.headers.get(n))?.trace_id ?? generateTraceId();
withTrace(trace_id, () => handle(req));

The Next.js middleware (@usekamori/sdk/next) does this automatically. With pino, add the id to your console output too via mixin: kamoriMixin().