DocsProduction

JavaScript SDK

Error tracking and request logs for ProjectVerse. One dependency-free ES module that runs in browsers, Node ≥ 18, Bun, Deno and edge runtimes (Cloudflare Workers, Vercel Edge, Next.js).

Create a service in ProjectVerse to get its ingest key (pvi_...).

Install

Pick one:

  • npm (from the monorepo): npm i <path-to-monorepo>/packages/sdk-js, then import * as pv from "projectverse".
  • Copy the file: projectverse.js (and projectverse.d.ts for types) into your project.
  • Browsers, no build step: import it from your ProjectVerse server:
    html
    <script type="module">
      import * as pv from "https://projectverse.example.com/sdk/projectverse.js";
      pv.init({ key: "pvi_...", endpoint: "https://projectverse.example.com" });
    </script>

Init

js
import * as pv from "projectverse";

pv.init({
  key: process.env.PROJECTVERSE_KEY,          // the service's ingest key
  endpoint: "https://projectverse.example.com", // the ProjectVerse origin
  environment: "production",
  release: "api@1.4.2",
  tags: { region: "eu-west-1" },              // added to every error event
  // sampleRate: 0.1,     // keep 10% of request logs (5xx are always kept; errors are never sampled)
  // autoCapture: true,   // uncaught errors and unhandled rejections
  // captureFetch: false, // browsers: log the page's fetch calls
  // flushIntervalMs: 5000, maxBatch: 100, debug: false,
  // beforeSend: (item, kind) => item, // edit an item, or return null to drop it ("event" | "request")
});

pv.captureException(err, { tags: { order: "42" }, request: { method: "POST", url: "/checkout" } });
pv.captureMessage("Payment provider timed out", { level: "warning" });
await pv.flush(); // send now (otherwise every flushIntervalMs, or when a batch is full)
await pv.close(); // flush and stop timers and listeners

Without key and endpoint (or before init), every call is a no-op. Nothing the SDK does throws into your app: network errors are swallowed (429 and 5xx are retried once, then dropped).

Express

js
import express from "express";
import * as pv from "projectverse";

pv.init({ key: process.env.PROJECTVERSE_KEY, endpoint: process.env.PROJECTVERSE_URL });

const app = express();
app.use(pv.requestLogger({ ignore: (req) => req.path === "/health" })); // first
// ... your routes ...
app.use(pv.errorHandler()); // after the routes, before your own error handler

requestLogger logs method, route (/orders/:id, from Express's matched route), status and duration for every response. errorHandler captures the error with the request (errors carrying a 4xx status are skipped) and passes it on with next(err).

Next.js

instrumentation.ts at the project root (or in src/):

ts
import * as pv from "projectverse";

export function register() {
  pv.init({ key: process.env.PROJECTVERSE_KEY!, endpoint: process.env.PROJECTVERSE_URL!, environment: process.env.NODE_ENV });
}

// Server Components, Route Handlers, Server Actions and Proxy errors.
export const onRequestError = pv.onRequestError;

Request logs for a Route Handler:

ts
import { withProjectVerse } from "projectverse";

export const GET = withProjectVerse(async (request: Request, { params }: { params: Promise<{ id: string }> }) => {
  const { id } = await params;
  return Response.json({ id });
}, { route: "/api/orders/:id" });

Browsers

js
pv.init({ key: "pvi_...", endpoint: "https://projectverse.example.com", captureFetch: true });
  • error and unhandledrejection are captured; events get a url tag (the page, without query).
  • When the page is hidden or closed, what's queued is sent with navigator.sendBeacon.
  • captureFetch: true logs every fetch the page makes (except calls to ProjectVerse itself).

Edge runtimes and fetch handlers

withProjectVerse wraps any (request, ...rest) => Response handler: it logs every response and, when the handler throws, captures the error, logs a 500 and rethrows. If an argument has waitUntil (Cloudflare Workers' ctx), the flush is handed to it so it outlives the response.

js
import * as pv from "projectverse";

const handler = pv.withProjectVerse(async (request, env, ctx) => new Response("ok"));
let started = false;

export default {
  fetch(request, env, ctx) {
    if (!started) {
      pv.init({ key: env.PROJECTVERSE_KEY, endpoint: env.PROJECTVERSE_URL });
      started = true;
    }
    return handler(request, env, ctx); // ctx.waitUntil gets the flush
  },
};

In other short-lived environments (serverless functions, scripts), await pv.flush() before returning or exiting.

Request logs

js
pv.logRequest({ method: "GET", url: "/orders/42", route: "/orders/:id", status: 200, duration_ms: 18 });

Sent to /api/ingest/requests in batches. Send route when you know it; otherwise ProjectVerse derives one from the path (ids become :id). 5xx request logs also become error groups on the server, so you don't need to capture them twice.

Node

With autoCapture, uncaughtException is captured as fatal, flushed, and the process still exits with code 1 (unless your app has its own uncaughtException listener). Unhandled rejections are captured; under Node's default mode the process still crashes as it would without the SDK. Queued items are flushed on beforeExit; call await pv.close() before process.exit().

Privacy

  • Ingest keys can only send data, never read it. A key used in a browser is visible to anyone: rotate it in ProjectVerse if it's abused.
  • Query strings, fragments and user:password@ are stripped from every URL before sending.
  • Cookies, headers and request bodies are never sent.
  • Use beforeSend to scrub or drop anything else.