Skip to content

refactor: migrate runtime context from global store to scoped RuntimeClient#8932

Closed
ericpgreen2 wants to merge 27 commits intomainfrom
ericgreen/runtime-context-v2-feature-migration
Closed

refactor: migrate runtime context from global store to scoped RuntimeClient#8932
ericpgreen2 wants to merge 27 commits intomainfrom
ericgreen/runtime-context-v2-feature-migration

Conversation

@ericpgreen2
Copy link
Contributor

Replaces the global mutable runtime store with a scoped RuntimeClient provided via Svelte context. This is the infrastructure and consumer migration half of the runtime context refactor described in the tech design.

  • Adds RuntimeClient class, RuntimeProvider component, and ConnectRPC-based code generator that produces TanStack Query hooks from protobuf definitions
  • Adds JSON bridge layer so v2 hooks return the same shapes as the Orval-generated hooks (enabling incremental migration)
  • Ports request queue to ConnectRPC transport interceptor
  • Updates invalidation and query matching for dual key formats
  • Migrates ~300 consumer files from $runtime.instanceIduseRuntimeClient() across web-common, web-admin, and web-local

305 files changed. No API hook changes yet — all files still call Orval-generated hooks, just with client instead of instanceId as the first argument. The Orval → v2/gen hook migration and legacy code deletion happen in the follow-up PR.

See tech design: #8590

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

Developed in collaboration with Claude Code

…hase 1)

Introduce the foundation for replacing the global mutable `runtime`
store with scoped Svelte context.

- Generate ConnectRPC TypeScript service descriptors for RuntimeService,
  QueryService, and ConnectorService via buf (new buf.gen.runtime.yaml)
- Add RuntimeClient class that encapsulates transport, JWT lifecycle,
  and lazy service client creation
- Add RuntimeProvider component that creates a RuntimeClient, sets it in
  Svelte context, and bridges to the global store for unmigrated consumers
- Add useRuntimeClient() context helper
- Wire RuntimeProvider into web-admin project layout with {#key} on
  host::instanceId for correct re-mounting on project navigation
- Wire RuntimeProvider into web-local root layout
- Extract JWT expiry constants to shared constants.ts
Add a build-time code generator that reads ConnectRPC *_connect.ts
service descriptors and produces TanStack Query hooks for Svelte.

For each unary query method, generates 4 tiers: raw RPC function,
query key factory, query options factory, and convenience createQuery
hook. Mutation methods get 3 tiers: raw function, mutation options,
and createMutation hook.

Output: 59 queries + 28 mutations across QueryService, RuntimeService,
and ConnectorService. Streaming methods (watchFiles, watchResources,
watchLogs, completeStreaming, queryBatch) are skipped.

Generated hooks take RuntimeClient as first argument and inject
instanceId automatically, matching the Phase 1 context architecture.
…ase 3)

Update cache invalidation and query matchers to support both the old
URL-path query key format ("/v1/instances/{id}/...") and the new
service/method format ("QueryService", "metricsViewAggregation",
instanceId, request).

This enables incremental migration: as consumers switch from Orval
hooks to v2 generated hooks, both key formats are correctly matched
for invalidation, profiling, metrics view, and component queries.
The generator was producing `PartialMessage<Omit<Request, "instanceId">>`,
but `Omit<...>` strips the `Message<T>` constraint that `PartialMessage`
requires. Fix by swapping to `Omit<PartialMessage<Request>, "instanceId">`.

Also detect at generation time whether each request type actually has an
`instanceId` field; methods like `ListInstances` and `IssueDevJWT` don't
have it and should not receive auto-injection.
When RuntimeProvider calls setRuntime with no JWT (local dev), the
equality check compared authContext ("user") against current.jwt?.authContext
(undefined), always returning false. This caused the store to emit a new
value on every call, triggering an infinite reactive loop:
RuntimeProvider bridge → store update → layout re-render → bridge again.

Fix by treating JWT as unchanged when both old and new JWT are absent,
regardless of authContext.
Generated hooks now accept/return Orval-compatible types (V1*) in their
public API, converting to/from proto internally via fromJson/toJson.
This lets consumers switch to v2 hooks without changing their existing
type usage (V1Expression, V1Resource, etc.).

The generator reads the Orval schemas file at generation time to
determine which V1 types exist. Response types always use V1 (100%
coverage). Request types use V1 when available (~25%, POST endpoints)
and fall back to PartialMessage<ProtoType> for GET endpoints.
The generated hooks now accept a TData type parameter (defaulting to the
response type), enabling select transforms that narrow the return type.
This mirrors the Orval pattern and avoids forcing consumers to use
derived stores as workarounds.
Proto fromJson() rejects undefined field values, but callers may pass
them (e.g., TanStack Query's pageParam starts as undefined, or reactive
props that resolve asynchronously). Orval's HTTP client silently omitted
undefined values; the JSON bridge must do the same.
Proto3 toJson() omits fields with default values (0, "", false) by
default. gRPC-Gateway includes them. This caused missing fields like
low: 0 in histogram bins, breaking D3 scale computations and rendering
blank histograms for columns with data starting near zero.
…st objects

The shallow `stripUndefined` only removed top-level undefined values.
Nested objects like `timeRange: { start: undefined }` passed through
to `fromJson()` which rejects undefined, causing JSON decode errors.
Port the two-level heap request queue as a ConnectRPC interceptor.
Maps method names (instead of URLs) to priorities and controls
concurrency via the same architecture as HttpRequestQueue.
Migrate 8 files from the global runtime store to useRuntimeClient():
- ConnectorExplorer, ConnectorEntry, TableEntry, TableSchema,
  TableMenuItems, TableWorkspaceHeader, ConnectorRefreshButton
- ColumnProfile

Exercises all key migration patterns:
- $runtime.instanceId → useRuntimeClient() + client.instanceId
- Orval createQuery hooks → v2 generated hooks (request object style)
- Orval createMutation → v2 generated mutation hooks
- Structural type compatibility between proto and Orval response types
Migrate DatabaseExplorer, DatabaseEntry, DatabaseSchemaEntry,
TableInspector, and References to use useRuntimeClient() from context.

This demonstrates eliminating instanceId prop drilling: child components
now get the RuntimeClient from Svelte context instead of receiving
instanceId as a prop from parents.
… v2 RuntimeClient

- column-profile/queries.ts: all functions accept RuntimeClient instead of instanceId
- connectors/selectors.ts: useListDatabaseSchemas, useInfiniteListTables, useGetTable
  accept RuntimeClient; useIsModelingSupported* functions left as-is (callers out of scope)
- Column profile components (NumericProfile, TimestampProfile, VarcharProfile,
  NestedProfile): replace $runtime with useRuntimeClient()
- WorkspaceInspector: replace $runtime with useRuntimeClient(), switch Orval hooks to v2
- Canvas components (CanvasInitialization, KPIProvider, PageEditor): replace $runtime
  with useRuntimeClient(), switch Orval hooks to v2
- Update already-migrated callers to pass client instead of client.instanceId
Reverts the derived-store workarounds in queries.ts and selectors.ts,
using select transforms directly as the v2 code generator now supports
generic TData inference.
…ntimeClient

Chart providers (batch 1): all 6 providers now accept RuntimeClient
instead of Writable<Runtime>, use v2 query hooks, and remove runtime
from derived store inputs since the client is a stable object.

Canvas leaf components (batch 2): 13 Svelte components migrated from
$runtime store to useRuntimeClient() context. CanvasStore extended
with runtimeClient field to bridge chart providers in canvas context.
…re stores to v2

Bridge pattern: adds runtimeClient alongside runtime in StateManagers.
Migrates validSpecStore and timeRangeSummaryStore to v2 hooks,
removing runtime from their derived() dependencies.
…ntimeClient

Migrates 5 TS modules that receive StateManagers: timeseries-data-store,
totals-data-store, multiple-dimension-queries, dimension-filters selector,
and dashboard selectors. Removes ctx.runtime from derived() dependencies
and switches to v2 query hooks.
…seRuntimeClient

Replaces direct `runtime` store imports with `useRuntimeClient()` across
13 dashboard Svelte components. Uses stable `client.instanceId` instead
of reactive `$runtime.instanceId`.
- Add ConnectRPC URL routing to `DashboardFetchMocks` (handles
  `/rill.runtime.v1.{Service}/{Method}` requests with proper body
  decoding and RFC 3339 timestamp normalization)
- Inject `RuntimeClient` context in test renders that mount components
  calling `useRuntimeClient()`
…o v2 RuntimeClient

Replace `get(runtime).instanceId` with explicit `instanceId` parameters in
tdd-export, pivot-export, and dimension-table-export. Switch pivot-queries
from Orval hook to v2 `createQueryServiceMetricsViewAggregation`, threading
`runtimeClient` through `PivotDashboardContext`.
… explicit instanceId

Add `instanceId` parameter to `deriveInterval()` and `resolveTimeRanges()`
instead of reading from the global `runtime` store. Thread `instanceId`
through callers: DashboardStateSync, Filters, FiltersForm, canvas TimeState,
and explore-mappers utils.
…tore to v2 RuntimeClient

- Remove unused `runtime` property from dashboard and canvas StateManagers
- Delete dead code: `getValidDashboardsQueryOptions` and `getMetricsViewSchemaOptions`
- Migrate 10 canvas Svelte components to `useRuntimeClient()`
- Migrate canvas markdown `util.ts` to accept explicit `instanceId` parameter
- Migrate 2 explores Svelte components to `useRuntimeClient()`
…move dead code

Migrate leaf query option factories, intermediate callers, and module-level
singletons to accept `client: RuntimeClient` instead of reading the global
`runtime` store. Delete dead code (`getCanvasQueryOptions`, `utils.ts`).
Chat context functions updated as necessary follow-through from leaf factory
signature changes.
…eClient`

Migrate ~100 files across chat, alerts, scheduled reports, file management,
entity management, workspaces, sources, models, metrics-views, connectors,
explore-mappers, exports, and other features from the global `runtime` store
to `useRuntimeClient()` / `RuntimeClient` parameter threading.

Key changes:
- Add `host` property to `RuntimeClient` class
- Convert `canvasChatConfig` to `createCanvasChatConfig(client)` factory
- Thread `RuntimeClient` through `Conversation` and `ConversationManager`
- Migrate `resource-selectors.ts` query option factories to accept `client`
- Migrate chat picker data (models, canvases) to accept `client`
- Migrate `connectors/code-utils.ts`, `file-artifact.ts`, `new-files.ts`,
  `submitAddDataForm.ts`, `explore-mappers/*.ts` to accept instanceId/client
- Migrate `vega-embed-options.ts` to accept `RuntimeClient`
- Migrate `RillIntakeClient` to accept `host` parameter
- All `.svelte` callers updated to use `useRuntimeClient()`
Replace the old `runtime-store` mock with a `RuntimeClient` stub,
matching the updated `Conversation` constructor signature.
…meClient

- Create `local-runtime-config.ts` with shared `LOCAL_HOST` and
  `LOCAL_INSTANCE_ID` constants for web-local
- Migrate web-local load functions and layout to use constants instead
  of reading from the runtime store
- Add `getJwt` parameter to `SSEFetchClient.start()`, removing its
  direct import of the runtime store
- Thread `RuntimeClient` through `updateDevJWT` (dual-write bridge)
- Decouple `local-service.ts` from runtime store via `setLocalServiceHost()`
- Replace `Runtime` type import in `selectors.ts` with inline type
- Rewrite `query-options.ts` to use `fetch` instead of `httpClient`
- Rewrite `open-query.ts` to use `fetch` instead of `httpClient`
- Rewrite `getFeatureFlags()` to use `fetch` instead of `httpClient`
- Migrate `download-report.ts` to accept `host` via mutation data
@ericpgreen2 ericpgreen2 force-pushed the ericgreen/runtime-context-v2-feature-migration branch from e2e0ca5 to 2102ddd Compare February 26, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant