trpcLink.ts2.0 KBView on GitHub
import type { TRPCLink } from '@trpc/client';
import { observable } from '@trpc/server/observable';
import type { AnyRouter } from '@trpc/server';
import { push, isCaptureBodies } from './buffer';
import { redact } from './redact';

let opCounter = 0;
function nextRequestId(): string {
  opCounter++;
  return `req_${Date.now().toString(36)}_${opCounter.toString(36)}`;
}

export function createDeepDebuggerLink<TRouter extends AnyRouter>(): TRPCLink<TRouter> {
  return () =>
    ({ next, op }) =>
      observable((observer) => {
        const requestId = nextRequestId();
        const start = performance.now();
        push({
          source: 'trpc.request',
          path: op.path,
          type: op.type,
          input: redact(op.input, { captureBodies: isCaptureBodies() }),
          requestId,
        });

        const sub = next(op).subscribe({
          next(value) {
            push({
              source: 'trpc.response',
              path: op.path,
              type: op.type,
              output: redact((value as { result?: { data?: unknown } }).result?.data, { captureBodies: isCaptureBodies() }),
              durationMs: performance.now() - start,
              requestId,
            });
            observer.next(value);
          },
          error(err) {
            push({
              source: 'trpc.error',
              path: op.path,
              type: op.type,
              error: {
                message: err?.message ?? String(err),
                code: (err as { data?: { code?: string } })?.data?.code,
                data: redact((err as { data?: unknown })?.data, { captureBodies: isCaptureBodies() }),
                stack: (err as { stack?: string })?.stack,
              },
              durationMs: performance.now() - start,
              requestId,
            });
            observer.error(err);
          },
          complete() {
            observer.complete();
          },
        });
        return () => sub.unsubscribe();
      });
}