npm package: @mattman240/mission-control
Description: AI-oriented implementation guide for wiring Mission Control into an app at every layer.
Latest version: 0.2.3

# 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

```ts
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

```ts
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:

```ts
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

```ts
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

```ts
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:

```tsx
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`.

```ts
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:

```ts
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

```ts
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:

```ts
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:

```ts
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:

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

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

## Event Shape Sent To Mission Control

```ts
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:

```txt
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

```ts
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`