import { IncomingMessage, ServerResponse } from 'node:http'; /** * Public types for the Node server SDK (`nohmo/server`). * * Kept separate from src/core/types.ts on purpose: nothing here may import browser or * React types, because this entry point is bundled for Node with no DOM lib. */ interface NohmoServerOptions { /** Nohmo project id, from the dashboard. */ projectId: string; /** Project API key. Ingest authenticates on this ALONE — the projectId routes nothing. */ apiKey: string; /** Override the ingest URL. Only useful for self-hosted or tests. */ endpoint?: string; /** Tags every event, so staging errors stay distinguishable from production ones. */ environment?: string; /** Ties errors to a deploy, so a spike can be attributed to a release. */ release?: string; /** Groups errors per host. Defaults to os.hostname(). */ serverName?: string; /** Fraction of errors actually sent, 0..1. Defaults to 1 (everything). */ sampleRate?: number; /** Seconds before an identical error is reported again. Defaults to 5. */ dedupWindow?: number; /** Bounded, so a crash loop cannot turn the SDK into the outage. Defaults to 1000. */ queueSize?: number; /** Events per request. Defaults to 50. */ batchSize?: number; /** Seconds before a partial batch ships. Defaults to 5. */ flushInterval?: number; /** * Attach request headers, query strings and the user's email. Off by default — * turning it on sends PII to Nohmo, which should be a considered decision. */ sendDefaultPii?: boolean; /** Verbose logging to the console. */ debug?: boolean; } /** The subset of a request worth reporting. Framework-agnostic on purpose. */ interface RequestContext { path?: string; method?: string; /** Becomes the event's sessionId, so an error can be tied to a request trace. */ requestId?: string; headers?: Record; query?: Record; ip?: string; } interface UserContext { id?: string | number; email?: string; } interface CaptureOptions { request?: RequestContext; user?: UserContext; /** Arbitrary extra context. Scrubbed like everything else. */ extra?: Record; /** false when the error escaped to the framework's handler. */ handled?: boolean; } /** Exactly the shape the ingest endpoint expects — matches the browser SDK and nohmo-sdk (Python). */ interface ServerEvent { deviceId: string; userId: string | null; sessionId: string; event: 'SERVER_ERROR'; data: Record; page: string; referrer: string; ts: number; platform: 'server'; } declare const DEFAULT_ENDPOINT = "https://www.nohmo.in/api/tracker/track/"; declare class ServerClient { private projectId; private apiKey; private environment; private release; private serverName; private sampleRate; private dedupWindow; private queueSize; private batchSize; private sendDefaultPii; private debug; private transport; private queue; private timer; /** Signature -> last-sent epoch ms, for the dedup window. */ private recent; private closed; /** Number of in-flight sends, so flush() can wait for real completion. */ private inFlight; readonly instanceId: string; constructor(opts: NohmoServerOptions); captureException(err: unknown, opts?: CaptureOptions): void; captureMessage(message: string, opts?: CaptureOptions): void; /** * Send everything queued and wait for it. Resolves false if anything was dropped. * * Node has no background thread, so unlike the Python SDK there is nothing to join — * what we wait on is the in-flight request promises. */ flush(timeoutMs?: number): Promise; /** Flush and stop the timer. After this the client accepts nothing further. */ close(timeoutMs?: number): Promise; private enqueue; private buildEvent; /** Ship whatever is queued, in batches. Returns false if any batch was rejected. */ private drain; private pruneRecent; private log; } /** * The process-wide client singleton and the functions that use it. * * Split out from index.ts so the integrations can reach captureException without * importing the barrel that re-exports them. Going through index.ts created a genuine * import cycle (index -> express -> index), which in a CJS bundle can leave the binding * undefined at module-init time — the integration would silently report nothing. */ /** * Initialise the global client. Returns it, or null if configuration was missing. * * Never throws: a missing env var must not stop a deploy. It logs once and disables * itself, the same posture the Django integration takes. */ declare function init(options: NohmoServerOptions): ServerClient | null; declare function isInitialised(): boolean; /** Report an exception. Safe to call before init() — it is then a no-op. */ declare function captureException(err: unknown, options?: CaptureOptions): void; /** Report a message with no exception attached. */ declare function captureMessage(message: string, options?: CaptureOptions): void; /** * Send everything queued and wait for it. * * Call this before a short-lived process exits — a cron job, a Lambda, a one-off script. * Events are otherwise shipped on a timer that is deliberately unref'd, so it will not * hold the process open and will not get a chance to fire on the way out. */ declare function flush(timeoutMs?: number): Promise; /** Flush and stop. The client accepts nothing afterwards. */ declare function close(timeoutMs?: number): Promise; /** Escape hatch for tests and for apps that want more than one client. */ declare function getClient(): ServerClient | null; /** Structurally typed so the SDK never needs @types/express as a dependency. */ interface ReqLike { path?: string; originalUrl?: string; url?: string; method?: string; headers?: Record; query?: Record; ip?: string; user?: unknown; id?: unknown; } type NextLike = (err?: unknown) => void; interface ExpressHandlerOptions { /** * Decide whether an error is worth reporting. Return false to skip. * Handy for 404s or validation errors that are expected traffic, not defects. */ shouldReport?: (err: unknown, req: unknown) => boolean; } declare function requestContext(req: ReqLike): RequestContext; declare function userContext(req: ReqLike): UserContext | undefined; /** * Express error-handling middleware. Reports, then hands the error straight on — it never * swallows, so the app's own error page or JSON response is unchanged. */ declare function expressErrorHandler(options?: ExpressHandlerOptions): (err: unknown, req: ReqLike, _res: unknown, next: NextLike) => void; /** * Raw node:http integration — the catch-all for anything without a dedicated adapter. * * import { init, wrapHandler } from 'nohmo/server' * * init({ projectId: '...', apiKey: process.env.NOHMO_API_KEY }) * http.createServer(wrapHandler(async (req, res) => { ... })).listen(3000) * * This is the Node counterpart of the Python SDK's WSGI wrapper: it works with Connect, * Koa's raw layer, Next's custom server, or a hand-rolled http server. */ type Handler = (req: IncomingMessage, res: ServerResponse) => void | Promise; declare function httpRequestContext(req: IncomingMessage): RequestContext; /** * Wrap a handler so anything it throws — synchronously or from a rejected promise — is * reported and then rethrown. * * Rethrowing matters: swallowing here would leave the socket hanging open forever with no * response, turning an error the app could have handled into a stalled request. */ declare function wrapHandler(handler: Handler): Handler; export { CaptureOptions, DEFAULT_ENDPOINT, ExpressHandlerOptions, NohmoServerOptions, RequestContext, ServerClient, ServerEvent, UserContext, captureException, captureMessage, close, expressErrorHandler, flush, getClient, httpRequestContext, init, isInitialised, requestContext, userContext, wrapHandler };