# Getting Started

> Wire up the diagnostic hook, replace your TODOs, write tests.

Source: https://utils.n.yatsyk.com/docs/core/getting-started

# Getting Started

## 1. Install

```bash
npm i @dev-bench/utils
```

The package is ESM-only, Node ≥ 20, with no runtime deps.

## 2. Replace `// TODO` and ad-hoc `console.warn`

Pick the function that matches the signal:

```ts
import { warnNotImplemented, warnDegraded } from "@dev-bench/utils";

// Was: // TODO: implement reconnect backoff
warnNotImplemented("client.reconnect", "Exponential backoff not yet tuned");

// Was: console.warn("falling back to /")
warnDegraded("polling.getPaneCurrentPath", "tmux display-message failed");
```

Both kinds are dedup'd by `kind:category`, so a hot loop emits exactly once.

## 3. Promote should-be-impossible branches

`warnUnexpected` is the sink for type-system invariants you can't prove at
compile time — Zod `safeParse` failures, `match(...).otherwise()` arms,
`if (!('foo' in x))` fall-throughs. Never dedup'd; every occurrence is a
distinct event.

```ts
import { warnUnexpected } from "@dev-bench/utils";

const r = SseEventSchema.safeParse(payload);
if (!r.success) {
  warnUnexpected("relay.parseSse", "schema validation failed", {
    issues: r.error.issues,
  });
  return;
}
```

## 4. Wrap throws with `tryCatchWarn`

Always async, returns a discriminated union. Pair with `ts-pattern` so the
caller is forced to handle both arms:

```ts
import { tryCatchWarn } from "@dev-bench/utils";
import { match } from "ts-pattern";

const r = await tryCatchWarn("git.diff", () => runGitDiff(ref));
return match(r)
  .with({ ok: true }, ({ value }) => parseDiff(value))
  .with({ ok: false }, () => null)
  .exhaustive();
```

The throw is routed through `logError(category, err)` automatically — you do
not need a separate catch block to surface it.

## 5. Surface events in your UI / TUI

`setDiagnosticHook` wires every emission into your renderer. Pass `null` to
unsubscribe.

```ts
import { setDiagnosticHook, type DiagnosticEvent } from "@dev-bench/utils";

const events: DiagnosticEvent[] = [];
setDiagnosticHook((e) => {
  events.push(e);
  redrawDebugPanel(events);
});

// later, e.g. on shutdown:
setDiagnosticHook(null);
```

The hook fires before the underlying `console.{warn,error,log}` call, so a
custom hook can suppress, route, or annotate without losing the original
signal.

## 6. In tests

`resetDiagnostics()` clears the dedup set so each test sees a fresh
`warnNotImplemented`/`warnDegraded` for the same category:

```ts
import { beforeEach, expect, test, vi } from "vitest";
import {
  resetDiagnostics,
  setDiagnosticHook,
  warnDegraded,
  type DiagnosticEvent,
} from "@dev-bench/utils";

beforeEach(() => {
  resetDiagnostics();
  setDiagnosticHook(null);
});

test("warnDegraded is dedup'd by category", () => {
  const events: DiagnosticEvent[] = [];
  setDiagnosticHook((e) => events.push(e));

  warnDegraded("polling.tick", "first");
  warnDegraded("polling.tick", "second");

  expect(events).toHaveLength(1);
  expect(events[0]?.kind).toBe("degraded");
});
```
