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

@mattman240/mission-control

v0.2.3

Published

AI-oriented implementation guide for wiring Mission Control into an app at every layer.

Downloads

867

Readme

Mission Control SDK

AI-oriented implementation guide for wiring Mission Control into an app at every layer.

This README is intentionally optimized for coding agents and fast project integration.

What This SDK Does

The SDK sends batched observability events to Mission Control through the Convex HTTP Actions endpoint:

  • https://bold-dolphin-472.convex.site/api/ingest

It supports:

  • browser logging
  • Node/server logging
  • React error boundary reporting
  • global browser error capture
  • Next.js route handler wrapping
  • Convex-safe capture helpers
  • batching and retry
  • source tagging for client, server, convex, and db

Public API

import {
  MissionControlErrorBoundary,
  createMissionControlConvexCaptureHandler,
  createMissionControlConvexLogger,
  initMissionControl,
  instrumentBrowserErrors,
  withMissionControlConvexMutation,
  withMissionControlRouteHandler
} from "@mattman240/mission-control";

Primary exports:

  • initMissionControl(config)
  • missionControl.createLogger({ source, metadata? })
  • logger.info(message, metadata?)
  • logger.warn(message, metadata?)
  • logger.error(message, metadata?)
  • logger.debug(message, metadata?)
  • logger.captureException(error, metadata?)
  • instrumentBrowserErrors(missionControl)
  • <MissionControlErrorBoundary />
  • withMissionControlRouteHandler(missionControl, handler, options?)
  • createMissionControlConvexCaptureHandler(missionControl)
  • createMissionControlConvexLogger({ scheduler, captureReference, metadata? })
  • withMissionControlConvexMutation(handler, options)
  • withMissionControlConvexAction(handler, options)

Config Shape

type MissionControlConfig = {
  apiKey: string;
  endpoint?: string;
  flushIntervalMs?: number;
  maxBatchSize?: number;
  retryAttempts?: number;
  retryBaseDelayMs?: number;
  fetch?: typeof fetch;
  headers?: Record<string, string>;
  defaultMetadata?: Record<string, string | number | boolean | null>;
};

Required:

  • apiKey

Usually optional:

  • endpoint Override this only if you want to point the SDK at a different Mission Control deployment or a local proxy. By default the SDK sends to https://bold-dolphin-472.convex.site/api/ingest.

Default Behavior

  • events are queued in memory
  • batches flush every 5000ms
  • max batch size is 25
  • retry attempts default to 3
  • exponential backoff starts at 500ms
  • browser page exit attempts a flush
  • client/server/convex helpers preserve the correct source

Fastest Integration Order

If an AI agent is implementing this into a project, do it in this order:

  1. Create one shared missionControl client instance.
  2. Add browser/global error instrumentation.
  3. Add a React error boundary near the app root.
  4. Wrap Next.js route handlers.
  5. Add the Convex capture action and wrappers.
  6. Add targeted loggers where business events matter.

Canonical Setup

Create one shared instance:

import { initMissionControl } from "@mattman240/mission-control";

export const missionControl = initMissionControl({
  apiKey: process.env.NEXT_PUBLIC_MISSION_CONTROL_API_KEY ?? "",
  defaultMetadata: {
    service: "mission-control-web",
    runtime: typeof window === "undefined" ? "server" : "browser"
  }
});

Important:

  • create this once per runtime, not once per request or component render
  • let the default Convex HTTP Actions endpoint handle ingestion unless you explicitly need an override
  • for older Node runtimes without global fetch, pass fetch explicitly

Browser Layer

Global browser errors

import { instrumentBrowserErrors } from "@mattman240/mission-control";
import { missionControl } from "./mission-control";

instrumentBrowserErrors(missionControl);

This captures:

  • window.onerror
  • unhandledrejection

All of these are tagged with source: "client".

Manual browser logger

const logger = missionControl.createLogger({
  source: "client",
  metadata: {
    area: "checkout"
  }
});

logger.info("Checkout opened", { plan: "pro" });
logger.warn("Payment form slow", { durationMs: 1200 });
logger.error("Payment submit failed", { provider: "stripe" });
logger.debug("Checkout render metrics", { widgets: 4 });

React Layer

Wrap your app with the provided error boundary:

import { MissionControlErrorBoundary } from "@mattman240/mission-control";
import { missionControl } from "./mission-control";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <MissionControlErrorBoundary
      missionControl={missionControl}
      fallback={<div>Something went wrong.</div>}
      metadata={{ boundary: "root-app" }}
    >
      {children}
    </MissionControlErrorBoundary>
  );
}

This captures React render/lifecycle errors as:

  • source: "client"
  • level: "error"

Next.js Server Layer

Wrap route handlers with withMissionControlRouteHandler.

import { withMissionControlRouteHandler } from "@mattman240/mission-control";
import { missionControl } from "@/lib/mission-control";

async function getHealth() {
  return Response.json({ ok: true });
}

export const GET = withMissionControlRouteHandler(missionControl, async (request) => {
  return getHealth();
});

If the route throws, the SDK:

  • captures the exception
  • tags it with source: "server"
  • includes request method and URL
  • flushes before rethrowing

With extra metadata:

export const POST = withMissionControlRouteHandler(
  missionControl,
  async (request) => {
    throw new Error("Webhook failed");
  },
  {
    metadata: (request) => ({
      route: "/api/webhooks/stripe",
      method: request.method
    })
  }
);

Convex Layer

Convex support uses a scheduled internal action, not direct network transport or timer-based batching inside a mutation or action.

Why:

  • Convex mutations cannot use timers
  • Convex mutations cannot make arbitrary external network calls
  • a failed mutation rolls back its writes

The SDK’s safe Convex pattern is:

  1. define one internal capture action
  2. build its handler with createMissionControlConvexCaptureHandler(...)
  3. wrap thrown-error capture with withMissionControlConvexMutation(...) or withMissionControlConvexAction(...)
  4. for non-throwing Convex logs, use createMissionControlConvexLogger(...)
  5. pass the internal action reference as captureReference

Important:

  • do not call missionControl.createLogger(...) inside Convex code
  • do not log from Convex module scope during import time
  • schedule Convex-originated events through ctx.scheduler.runAfter(...) only

Convex setup

import { v } from "convex/values";
import { api, internal } from "./_generated/api";
import { internalAction, mutation } from "./_generated/server";
import {
  createMissionControlConvexCaptureHandler,
  createMissionControlConvexLogger,
  initMissionControl,
  withMissionControlConvexMutation
} from "@mattman240/mission-control";

const missionControl = initMissionControl({
  apiKey: process.env.MISSION_CONTROL_API_KEY ?? ""
});

export const ingestCapturedConvexError = internalAction({
  args: {
    message: v.string(),
    metadata: v.any(),
    timestamp: v.string()
  },
  handler: createMissionControlConvexCaptureHandler(missionControl)
});

export const updateStatus = mutation({
  args: {},
  handler: withMissionControlConvexMutation(
    async (ctx) => {
      const logger = createMissionControlConvexLogger({
        scheduler: ctx.scheduler,
        captureReference: internal.missionControl.ingestCapturedConvexError,
        metadata: {
          feature: "status-sync"
        }
      });

      await logger.info("Status sync started");
      throw new Error("Mutation failed");
    },
    {
      captureReference: internal.missionControl.ingestCapturedConvexError,
      name: "updateStatus",
      metadata: () => ({
        feature: "status-sync"
      })
    }
  )
});

Events captured this way should be tagged as:

  • source: "convex"

If you need business-event logging from Convex, use the Convex logger:

const logger = createMissionControlConvexLogger({
  scheduler: ctx.scheduler,
  captureReference: internal.missionControl.ingestCapturedConvexError,
  metadata: {
    job: "reconcileSubscriptions"
  }
});

await logger.info("Subscription reconciliation started");
await logger.warn("Stripe returned stale state", { subscriptionId });

Server and Worker Logging

For non-Next Node code, use the plain logger:

const logger = missionControl.createLogger({
  source: "server",
  metadata: {
    worker: "email-dispatch"
  }
});

logger.info("Worker started");
logger.captureException(new Error("SMTP timeout"), {
  provider: "postmark"
});

If you are logging database-adjacent operational events from scripts or workers:

const dbLogger = missionControl.createLogger({
  source: "db",
  metadata: {
    cluster: "primary"
  }
});

dbLogger.warn("Replication lag elevated", { lagMs: 2400 });

Event Shape Sent To Mission Control

type IngestEvent = {
  environment?: "production" | "staging" | "development";
  source: "client" | "server" | "convex" | "db";
  level: "debug" | "info" | "warn" | "error";
  message: string;
  metadata: Record<string, string | number | boolean | null>;
  timestamp?: string;
};

Error Grouping Notes

Mission Control groups errors by fingerprint:

  • message + stack

To make grouping useful, ensure thrown errors preserve a stable message and stack.

Best practice:

  • use captureException(error) with a real Error object
  • do not stringify errors before passing them in
  • include contextual metadata like route, job, workerId, or tenantId

AI Implementation Rules

If an AI agent is integrating this SDK into another codebase, follow these rules:

  1. Do not create multiple client instances in the same runtime unless environments are intentionally separate.
  2. Use source: "client" only for browser/React events.
  3. Use source: "server" for Next.js routes, API handlers, Node jobs, and server utilities.
  4. Use source: "convex" only for errors that originate in Convex.
  5. Use source: "db" only for database or replica/connection/latency style operational events.
  6. Prefer captureException(error) over error("...", { stack: ... }) when an actual exception exists.
  7. Wrap framework boundaries first, then add manual business logging.
  8. Always set apiKey from deployment-specific configuration, not hardcoded literals in production.
  9. For Convex, always provide a captureReference internal action to the SDK wrappers.

Recommended File Layout In A Consumer App

For a Next.js app:

src/
  lib/
    mission-control.ts
  components/
    providers.tsx

Suggested responsibilities:

  • src/lib/mission-control.ts Export the shared SDK client instance.
  • src/components/providers.tsx Install instrumentBrowserErrors and wrap app UI with MissionControlErrorBoundary.
  • app/api/*/route.ts Wrap route handlers with withMissionControlRouteHandler.
  • convex/*.ts Define one internal capture action, then pass its reference to the Convex wrappers.

Common Mistakes

  • overriding endpoint when the default Convex ingestion URL would already work
  • creating the SDK client inside React components
  • swallowing exceptions after logging them when framework semantics expect rethrow
  • passing undefined metadata values and expecting them to persist
  • using the wrong source
  • forgetting to define and pass captureReference for Convex wrappers

Minimal End-To-End Example

import {
  MissionControlErrorBoundary,
  initMissionControl,
  instrumentBrowserErrors,
  withMissionControlRouteHandler
} from "@mattman240/mission-control";

export const missionControl = initMissionControl({
  apiKey: process.env.NEXT_PUBLIC_MISSION_CONTROL_API_KEY ?? ""
});

instrumentBrowserErrors(missionControl);

export const GET = withMissionControlRouteHandler(missionControl, async () => {
  throw new Error("boom");
});

Verification Checklist

After integration, verify:

  • browser console errors show up in Mission Control
  • rejected promises show up in Mission Control
  • React render errors show up in Mission Control
  • thrown Next route errors show up as source: "server"
  • thrown Convex errors show up as source: "convex" through the capture action
  • manual logs show up with expected metadata
  • batched events arrive through https://bold-dolphin-472.convex.site/api/ingest