Skip to content

[discuss] Per-view tRPC instances for DocumentDB webviews (remove ctx casts) #796

Description

@tnaum-ms

Context

@microsoft/vscode-ext-webview supports either one shared tRPC instance for all webviews or one instance per webview (initWebviewTrpc<ViewContext>()). The DocumentDB extension currently uses a single shared instance bound to the framework BaseRouterContext:

// src/webviews/_integration/trpc.ts
const trpc = initWebviewTrpc<FrameworkBaseRouterContext>();

Because that one instance serves CollectionView, DocumentView, QueryInsights, IndexView, etc., two consumer-side patterns fall out of the design (surfaced during review of #795):

  1. Per-procedure narrowing cast — every procedure re-narrows ctx to its view type:
    const myCtx = ctx as RouterContext; // ~30 sites
  2. Omit on the root context — controllers build the initial context, but actionContext is injected per-call by the telemetry runner, so the root object can't be a full RouterContext:
    const trpcContext: Omit<RouterContext, 'actionContext'> = { ... };

Neither is a package limitation — both are how this extension wires the package. vscode-cosmosdb avoids the cast by building a separate instance per webview, each initTRPC.context<QueryEditorRouterContext>().

Proposal

Move DocumentDB to per-view tRPC instances — one initWebviewTrpc<ThisViewContext>() per webview — so ctx is already the precise per-view type inside procedures.

Pros

  • Removes ~30 ctx as RouterContext casts. Procedures read ctx.sessionId, ctx.actionContext, etc. directly, fully typed.
  • Type safety instead of casts. A wrong field access becomes a compile error rather than being masked by the as cast.
  • Matches the framework's intended usage and the vscode-cosmosdb shape, making the two extensions' integration layers converge (easier shared learnings / future extraction).
  • Clearer per-view boundaries — each view owns its context type end to end; no giant shared BaseRouterContext union of concerns.

Cons

  • More wiring / boilerplate. N instances instead of one: each needs its own publicProcedure / router / createCallerFactory (and the telemetry middleware applied per instance). The framework note warns against hiding the .use() chain behind a generic helper (it collapses tRPC's ProcedureBuilder inference to any), so the wiring is repeated per instance by design.
  • Circular-import care. The current single-leaf trpc.ts deliberately breaks an appRouter -> per-view router -> appRouter cycle. Per-view instances need the same discipline replicated N times.
  • Does not remove the Omit (or optional) on the root context. actionContext is still injected per call, so the controller-built root context still lacks it regardless of instance count. This refactor fixes the cast, not the injected-field modelling.
  • Churn for limited functional gain. It's an ergonomics/typing improvement, not a behavior change; touches every router + controller.

Decision needed

  • Do we want per-view instances (remove casts) vs. keep the single instance (one cast pattern, less wiring)?
  • Independently: how to model the injected actionContext on the root context — keep Omit<RouterContext, 'actionContext'>, make it optional, or a WithActionContext<T> wrapper at read sites?

Follow-up to #795 (kept out of that PR to stay scoped to the package API change).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions