Przejdź do treści
Biblioteka skilli
Programowanie · Budowanie i kod

vscode-webview-trpc-messaging

/vscode-webview-trpc-messaging

Buduje komunikację tRPC między hostem rozszerzenia VS Code a webview w React: zapytania, mutacje, subskrypcje i anulowanie.

Repozytorium
rayfin ↗
Licencja
MIT
Ostatnia zmiana repozytorium
13.10.2026

Ocena i bezpieczeństwo

100/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść35 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
1
Skrypty
0
Komentarze HTML
0
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 13.10.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co robi i kiedy

Buduje komunikację tRPC między hostem rozszerzenia VS Code a webview w React: zapytania, mutacje, subskrypcje i anulowanie.

Oryginalny opis (EN)

Implements tRPC-based communication between the Rayfin VS Code extension host and React webviews (packages/tools/vscode). Use when creating new webview procedures (queries, mutations, subscriptions), adding a new webview router, wiring up a webview controller, using the tRPC client from React components, applying telemetry middleware (trpcToTelemetry), or supporting AbortSignal-based cancellation in webview operations.

Jak działa

  1. Architecture Overview
  2. Creating a New Router
  3. Telemetry: publicProcedure vs publicProcedureWithTelemetry
  4. AbortSignal Support
  5. Subscriptions
  6. Client-Side Hook Usage
  7. Common Pitfalls
Sekcje z nagłówków SKILL.md, w oryginale · 251 linii, 998 słów
Podgląd SKILL.md
---
name: vscode-webview-trpc-messaging
description: Implements tRPC-based communication between the Rayfin VS Code extension host and React webviews (packages/tools/vscode). Use when creating new webview procedures (queries, mutations, subscriptions), adding a new webview router, wiring up a webview controller, using the tRPC client from React components, applying telemetry middleware (trpcToTelemetry), or supporting AbortSignal-based cancellation in webview operations.
---

# Webview tRPC Messaging

Type-safe RPC communication between the VS Code extension host (server) and React webviews (client) using tRPC.

## Architecture Overview

```
React Webview (client)                    Extension Host (server)
─────────────────────                     ──────────────────────
useTrpcClient() hook                      WebviewController
  └─ createTRPCClient                       └─ setupTrpc()
       └─ vscodeLink ──── postMessage ────►     ├─ callerFactory(appRouter)
            (send/onReceive)              ◄─────┤   └─ procedure(input)
                                                └─ abort/subscription.stop
```

**Key files** (read as needed for implementation details):

| File                                                                          | Purpose                                                                                    |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `packages/tools/vscode/src/webviews/api/extension-server/trpc.ts`            | tRPC init, `publicProcedure`, `publicProcedureWithTelemetry`                               |
| `packages/tools/vscode/src/webviews/api/configuration/appRouter.ts`          | Root router bundling all view routers + `commonRouter`                                     |
| `packages/tools/vscode/src/webviews/api/extension-server/WebviewController.ts` | WebviewPanel lifecycle, tRPC message dispatcher (queries, mutations, subscriptions, abort) |
| `packages/tools/vscode/src/webviews/api/webview-client/useTrpcClient.ts`     | React hook providing the tRPC client                                                       |
| `packages/tools/vscode/src/webviews/api/webview-client/vscodeLink.ts`        | Custom tRPC link bridging `postMessage` transport                                          |

## Creating a New Router

Each webview maintains its own router. Follow this pattern:

### 1. Define the router context

Extend `BaseRouterContext` with view-specific fields:

```typescript
// packages/tools/vscode/src/webviews/myView/myViewRouter.ts
import { type BaseRouterContext } from '../api/configuration/appRouter';

export type RouterContext = BaseRouterContext & {
  clusterId: string;
  viewId: string;
  databaseName: string;
  // add view-specific fields
};
```

### 2. Define procedures

```typescript
import { z } from 'zod';
import { publicProcedureWithTelemetry, router, type WithTelemetry } from '../../api/extension-server/trpc';

export const myViewRouter = router({
  // Query with telemetry (preferred for operations that touch external services)
  getData: publicProcedureWithTelemetry.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
    const myCtx = ctx as WithTelemetry<RouterContext>;
    // myCtx.telemetry is guaranteed present
    // myCtx.signal is the AbortSignal for cancellation
    return { data: 'result' };
  }),

  // Mutation without telemetry (rare, use for fire-and-forget)
  doAction: publicProcedure.input(z.string()).mutation(({ input }) => {
    // lightweight operation
  }),
});
```

### 3. Register in appRouter

```typescript
// packages/tools/vscode/src/webviews/api/configuration/appRouter.ts
import { myViewRouter } from '../../myView/myViewRouter';

export const appRouter = router({
  common: commonRouter,
  myView: myViewRouter, // <-- add here
});
```

### 4. Create the controller

```typescript
// packages/tools/vscode/src/webviews/myView/myViewController.ts
import { WebviewController } from '../api/extension-server/WebviewController';
import { type RouterContext } from './myViewRouter';

export class MyViewController extends WebviewController<MyViewConfig> {
  constructor(initialData: MyViewConfig) {
    super(ext.context, title, 'myViewName', initialData);

    const trpcContext: RouterContext = {
      webviewName: 'myView',
      clusterId: initialData.clusterId,
      viewId: initialData.viewId,
    };

    this.setupTrpc(trpcContext);
  }
}
```

> **Important:** The `webviewName` in the `WebviewController` constructor is the **registry key** (must match a key in `WebviewRegistry`, e.g. `myViewName`). The `webviewName` in the tRPC context is a **telemetry label** used in telemetry event names (e.g. `myView`). These are intentionally different values — do not confuse them.

### 5. Register in WebviewRegistry

Add your React component to the registry. The key must match the `webviewName` passed to `WebviewController`'s constructor. The `WebviewName` type (exported from the same file) ensures compile-time validation of webview names.

```typescript
// packages/tools/vscode/src/webviews/api/configuration/WebviewRegistry.ts
import { MyView } from '../../myView/MyView';

export const WebviewRegistry = {
  myViewName: MyView, // <-- add your entry
} as const;

export type WebviewName = keyof typeof WebviewRegistry;
```

## Telemetry: `publicProcedure` vs `publicProcedureWithTelemetry`

| Base                           | When to use                                                                  | `ctx.telemetry`                             |
| ------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------- |
| `publicProcedure`              | Fire-and-forget, no external calls, telemetry reported separately            | `undefined`                                 |
| `publicProcedureWithTelemetry` | **Default choice.** Any procedure touching DB, network, or user-visible work | Guaranteed via `trpcToTelemetry` middleware |

`trpcToTelemetry` (file-local, not exported) wraps the procedure in `callWithTelemetryAndErrorHandling`, auto-generating a telemetry event named `rayfin.rpc.{type}.{path}` and recording errors, duration, and abort status.

Access telemetry safely:

```typescript
const myCtx = ctx as WithTelemetry<RouterContext>;
myCtx.telemetry.properties.myCustomProp = 'value';
myCtx.telemetry.measurements.itemCount = items.length;
```

## AbortSignal Support

Every tRPC operation (query, mutation, subscription) receives its own `AbortController`. Cancellation flows:

```
Client (React)                              Server (Extension Host)
──────────────                              ──────────────────────
// Queries/Mutations:
ac = new AbortController()
trpcClient.myProc.query(input,
  { signal: ac.signal })
ac.abort()  →  sends 'abort' msg  →  abortController.abort()
                                        → ctx.signal.aborted = true

// Subscriptions:
sub = trpcClient.mySub.subscribe(...)
sub.unsubscribe()  →  'subscription.stop'  →  abortController.abort()
```

### Using abort in procedures

```typescript
getData: publicProcedureWithTelemetry
    .input(z.object({ filter: z.record(z.unknown()) }))
    .query(async ({ input, ctx }) => {
        const myCtx = ctx as WithTelemetry<RouterContext>;

        // Option 1: Pass to APIs that accept AbortSignal
        const response = await fetch(url, { signal: myCtx.signal });

        // Option 2: Manual check in loops
        for (const item of items) {
            if (myCtx.signal?.aborted) return;
            await processItem(item);
        }
    }),
```

### Client-side abort

```tsx
const { trpcClient } = useTrpcClient();
const abortControllerRef = useRef<AbortController>();

const runQuery = async () => {
  abortControllerRef.current?.abort(); // cancel previous
  const ac = new AbortController();
  abortControllerRef.current = ac;

  const result = await trpcClient.myView.myQuery.query(input, { signal: ac.signal });
};
```

When `trpcToTelemetry` detects an aborted signal, it sets `telemetry.properties.aborted = 'true'` and `result = 'Canceled'` automatically.

## Subscriptions

Subscriptions stream multiple values from server to client using async generators:

```typescript
// Server (router)
streamData: publicProcedureWithTelemetry
    .input(z.object({ batchSize: z.number() }))
    .subscription(async function* ({ input, ctx }) {
        const myCtx = ctx as WithTelemetry<RouterContext>;

        for (let i = 0; i < total; i += input.batchSize) {
            if (myCtx.signal?.aborted) return; // check before each yield
            const batch = await fetchBatch(i, input.batchSize);
            yield batch;
        }
    }),

// Client (React)
const sub = trpcClient.myView.streamData.subscribe(
    { batchSize: 100 },
    {
        onData(batch) { /* handle each batch */ },
        onComplete() { /* all done */ },
        onError(err) { /* handle error */ },
    },
);

// To stop:
sub.unsubscribe();
```

## Client-Side Hook Usage

```tsx
import { useTrpcClient } from '../api/webview-client/useTrpcClient';
import { useConfiguration } from '../api/webview-client/useConfiguration';

export const MyComponent = () => {
  const { trpcClient } = useTrpcClient();
  const config = useConfiguration<MyViewConfig>();

  useEffect(() => {
    trpcClient.myView.getData.query({ id: config.documentId }).then(setData);
  }, []);
};
```

`useConfiguration<T>()` retrieves the initial config passed to `WebviewController` constructor (serialized via `encodeURIComponent(JSON.stringify(...))`).

## Common Pitfalls

- **Never use `any`** in procedure context casts — use `WithTelemetry<RouterContext>` or `RouterContext`
- **Always prefer `publicProcedureWithTelemetry`** unless you have a specific reason not to
- **Always check `myCtx.signal?.aborted`** in long-running loops — not checking causes wasted work after client cancels
- **Do not mutate the shared `context` object** — `WebviewController` clones it per-operation already, but router code should treat `ctx` as read-only
- **Input validation uses `zod`** — always define `.input(z.object({...}))` for type safety
- **The `commonRouter`** handles cross-cutting concerns (error reporting, telemetry events, surveys, URL opening) — do not duplicate these in view-specific routers

Wersje

W bazie nie ma innych skilli o nazwie vscode-webview-trpc-messaging.

Migawka bazy z 28.10.2026 · wersja paczki 306ec9c43459 (skrót SHA-256 całej paczki)

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 100vscode-react-webview-architecture
    Opisuje wzorce architektury webview w React dla rozszerzenia Rayfin VS Code: Context API, Fluent UI i błędy stale closure.
  • 95vscode-webview-expert
    Doradza przy panelach WebView w VS Code: polityki CSP, komunikacja z rozszerzeniem, stan i wydajność.
  • 95trpc
    Buduje w pełni typowane API w tRPC v11 z Next.js App Router: routery, procedury, middleware, subskrypcje i integrację z React Query, bez REST i GraphQL.
  • 100vscode-extension-screenshot-evidence
    Robi zrzuty przed i po z prawdziwego rozszerzenia VS Code lub podglądu webview do PR LikeC4 i dowodów błędów.
  • 100trpc
    Pomaga budować typowane end-to-end API w tRPC: routery, procedury, middleware, kontekst i integrację z React Query oraz Next.js.
  • 95vscode-extension-expert
    Doradza przy rozwoju rozszerzeń VS Code: punkty aktywacji, API, WebView, LSP, bezpieczeństwo i publikacja.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.