# Utils API reference

> Exports of @dev-bench/utils.

Source: https://utils.n.yatsyk.com/docs/core/jsdoc

## Functions

### setDiagnosticHook

```ts
setDiagnosticHook(fn: typeof diagnosticHook): void
```

### addDiagnosticListener

Subscribe an additional listener (e.g. a dev console store). Returns a
dispose function — call it to unsubscribe.

Additive to `setDiagnosticHook` — both the singleton hook and every
registered listener receive every event, regardless of `kind`. Listeners
are invoked in insertion order, after the singleton hook.

Removing a listener during emission is safe: `Set.forEach` will not
revisit items removed while iterating.

```ts
addDiagnosticListener(fn: (e: DiagnosticEvent) => void): () => void
```

### clearDiagnosticListeners

Test-only: drop every registered listener.

```ts
clearDiagnosticListeners(): void
```

### resetDiagnostics

Test-only: clear the dedup set between tests.

```ts
resetDiagnostics(): void
```

### warnDegraded

```ts
warnDegraded(context: string, detail: string): void
```

### warnNotImplemented

```ts
warnNotImplemented(category: string, detail: string): void
```

### warnUnexpected

```ts
warnUnexpected(context: string, detail: string, data?: Record<string, unknown>): void
```

### logInfo

Emit an informational lifecycle message. Never dedup'd — boot and ops
messages typically fire at most a handful of times per process and operators
want to see every one.

Prefer a stable `context` (e.g. `"server.boot"`, `"relay.subscribe"`) so the
UI debug panel and greps downstream can slice by call site.

```ts
logInfo(context: string, detail: string, data?: Record<string, unknown>): void
```

### logError

Report a caught error (or any thrown value). Never dedup'd — every error is
potentially its own story.

If `err` is an `Error`, its `name`, `message`, and `stack` are captured as
structured data on the diagnostic event. Otherwise the raw value is passed
through under `data.err`.

`detail` is optional — when omitted, the error's `.message` (or its string
coercion) is used, which is the right default for `.catch(logError)`-style
call sites.

```ts
logError(context: string, err: unknown, detail?: string): void
```

### tryCatchWarn

Run `fn` and capture any throw into a discriminated union. Routes failures
through `logError(context, err)` so the diagnostic sink sees the error
regardless of how the caller handles it.

Always returns a `Promise` — `fn` may be sync or async, both work. This
keeps the type signature monomorphic (no overload explosion) and forces
every call site to `await`, which is almost always what callers want when
the wrapper exists specifically to capture errors.

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

```ts
<T>(context: string, fn: () => T | Promise<T>): Promise<TryCatchResult<T>>
```

## Types

### DiagnosticKind

Runtime diagnostics — deferred-work tracking, unexpected-state reporting,
and the general structured-log sink for production code.

Five functions, grouped by the kind they emit:
  - `warnNotImplemented` — feature exists upstream, port has nothing. Dedup'd.
  - `warnDegraded`       — fallback running; usable but not faithful. Dedup'd.
  - `warnUnexpected`     — value that should be impossible under the type
                           system (Zod `safeParse` failure, non-exhaustive
                           `match` arm, type-assertion guard). Never dedup'd.
  - `logInfo`            — lifecycle / boot / ops messages that callers want
                           visible but carry no "something is wrong" signal.
                           Never dedup'd.
  - `logError`           — something threw or errored. Serialises Error
                           instances (name/message/stack). Never dedup'd.

And a thin wrapper that turns throws into a discriminated-union result:
  - `tryCatchWarn<T>(context, fn)` — always async; returns
    `{ ok: true, value: T } | { ok: false, error: unknown }`. Routes throws
    through `logError`. Callers MUST `.exhaustive()` on `{ok}`.

All five kinds flow through an optional `diagnosticHook` so the TUI / web
debug panel can surface them. Tests call `resetDiagnostics()` in
`beforeEach` to clear the dedup set.

Usage:
  warnDegraded('polling.getPaneCurrentPath', 'Falling back to "/" — tmux display-message failed')
  warnNotImplemented('client.reconnect', 'Exponential backoff not yet tuned')
  warnUnexpected('connection.handleSseBlock', 'SSE message failed schema validation', { issues })
  logInfo('server.boot', 'listening on port 7800', { port: 7800 })
  logError('polling.tick', err)
  const result = await tryCatchWarn('some.op', () => doWork())
  match(result)
    .with({ ok: true }, ({ value }) => ...)
    .with({ ok: false }, ({ error }) => ...)
    .exhaustive()

```ts
type DiagnosticKind = | "not-implemented"
	| "degraded"
	| "unexpected"
	| "info"
	| "error"
```

### TryCatchResult

Discriminated result returned by `tryCatchWarn`. Callers MUST branch with
`match(result).with({ ok: true }, ...).with({ ok: false }, ...).exhaustive()`
— the whole point is that "succeeded" and "failed" are distinct types.

```ts
type TryCatchResult = TryCatchResult<T>
```

## Interfaces

### DiagnosticEvent

```ts
interface DiagnosticEvent
```

- `kind: DiagnosticKind`
- `category: string`
- `detail: string`
- `stack: string`
- `data: Record<string, unknown>`
- `timestamp: number`
