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, thenimport * as pv from "projectverse". - Copy the file:
projectverse.js(andprojectverse.d.tsfor types) into your project. - Browsers, no build step: import it from your ProjectVerse server:
<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
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 listenersWithout 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
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 handlerrequestLogger 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/):
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:
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
pv.init({ key: "pvi_...", endpoint: "https://projectverse.example.com", captureFetch: true });errorandunhandledrejectionare captured; events get aurltag (the page, without query).- When the page is hidden or closed, what's queued is sent with
navigator.sendBeacon. captureFetch: truelogs everyfetchthe 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.
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
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
beforeSendto scrub or drop anything else.