Skip to content

feat(studio-server): implement effect-orpc router, mount /rpc, and add typed client with TanStack Query utils - #1609

Merged
mateicanavra merged 1 commit into
mainfrom
design/server-orpc
Jun 12, 2026
Merged

feat(studio-server): implement effect-orpc router, mount /rpc, and add typed client with TanStack Query utils#1609
mateicanavra merged 1 commit into
mainfrom
design/server-orpc

Conversation

@mateicanavra

Copy link
Copy Markdown
Owner

Implement the studio's own effect-orpc server (/rpc) alongside the existing /api handlers, giving the app a typed end-to-end RPC surface without changing any current behavior.

What this does

Stands up @civ7/studio-server's effect-orpc router for all 16 contract procedures and mounts it at /rpc inside the existing Vite dev middleware. The legacy /api/* handlers remain fully alive (coexistence); both transports share the same serialized operation queue, dual-store 409 mutex, and process singletons so state never diverges.

The stateful surface (autoplay, run-in-game, save/deploy) is backed by a host-injected StudioServerContext seam — the engine bodies are lifted verbatim out of vite.config.ts into shared functions that both /api and /rpc call. The read surface (status, mapSummary, gameInfo, live.*, setupConfig, savedConfigs, setupCatalog) is backed by a Civ7TunerClient Effect service wrapping @civ7/direct-control.

Parity preserved

  • Non-uniform error status codes: civ7.gameInfo / civ7.live.* → 400, civ7.setupConfig → 503, most others → 500, mapped via orpcError per procedure.
  • civ7.live.status 200-with-embedded-{error}: four reads run under Effect.either; a rejected read becomes { error: String(reason) } on that field rather than a transport error.
  • Run-in-game status 404 echoes serverInstanceId/serverStartedAt; map-config status 404 does not (asymmetry reproduced on the oRPC data payload).
  • assertNoRawControlFields security scan, fingerprint dedup → 202, sha256 proof identity, finally cleanup + gen:maps regen, and write-then-deploy rollback all preserved by construction (shared engine functions).

Contract refinements

civ7.gameInfo.rows and civ7.live.gameInfo.tables[*] are refined from bare row arrays to the opaque Civ7GameInfoRowsResult record — the A1 contract did not match what the /api handlers actually returned.

Client wiring

Adds apps/mapgen-studio/src/lib/orpc.ts: an RPCLink → /rpc client typed off StudioContract via ContractRouterClient, plus createTanstackQueryUtils-derived query utils. Existing /api fetch call sites are not touched; switching them is the next slice.

Infrastructure fixes

  • Connect's path-mounted middleware strips the /rpc prefix from req.url; nodeRequestToWebRequest now reads req.originalUrl so the oRPC handler's prefix match works correctly.
  • effect-orpc ships raw TypeScript via exports that Node cannot type-strip under node_modules; @civ7/studio-server bundles it into its dist via tsup noExternal so any Node runtime consumer loads a self-contained module.
  • tsconfig.json for studio-server switches to moduleResolution: bundler to match how tsup and Vite consume the package.
  • deploy functions are simplified: buildSwooperMapsStudioDeployPlanbuildSwooperMapsStudioDeployCommand, with targetDir parsed from stdout rather than returned by a separate deployMod call, removing the @civ7/plugin-mods and @civ7/map-policy dependencies from the app.

mateicanavra commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

Copy link
Copy Markdown

Railway preview (MapGen Studio): not provisioned for this PR.

Policy (Graphite stacks): previews are created only for the top-of-stack PR by default.

  • To force a preview for this PR: add label railway-preview
  • To suppress a preview: add label no-railway-preview

Debug: {"isClosed":false,"isFork":false,"suppress":false,"force":false,"isTopOfStack":false,"hasToken":true,"draft":false}

This was referenced Jun 12, 2026

mateicanavra commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

Merge activity

  • Jun 12, 8:50 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 12, 9:00 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 12, 9:01 PM UTC: @mateicanavra merged this pull request with Graphite.

@mateicanavra
mateicanavra changed the base branch from design/app-decompose to graphite-base/1609 June 12, 2026 20:57
@mateicanavra
mateicanavra changed the base branch from graphite-base/1609 to main June 12, 2026 20:58
Implement the studio's own effect-orpc server for all 16 @civ7/studio-server
contract procedures and mount an oRPC RPCHandler at /rpc inside the existing Vite
dev middleware, keeping the legacy /api/* handlers alive (coexistence; cutover is
a later supervised step). EVERYTHING talks oRPC (FRAME §4.7).

Package (@civ7/studio-server):
- services/Civ7TunerClient (direct-control reads), services/StudioConfig (host seam)
- runtime.ts (ManagedRuntime + Layer), router/index.ts (implementEffect, the only
  effect-orpc import site), errors.ts (non-uniform ORPCError status mapping),
  handler.ts (RPCHandler factory), context.ts (StudioServerContext seam)
- bundle effect-orpc into dist (it ships raw TS Node cannot type-strip)
- depend on @civ7/direct-control

App:
- vite.config.ts: lift autoplay/run-in-game/save-deploy handler bodies VERBATIM
  into shared engines; BOTH /api and /rpc call them (one serialized queue + dual
  store mutex, no state divergence); mount /rpc (Connect originalUrl prefix fix)
- src/lib/orpc.ts: typed oRPC client (RPCLink -> /rpc) + createTanstackQueryUtils
  (call sites NOT switched yet — next slice)
- wire @civ7/studio-server + @orpc/* + @tanstack/react-query deps

Parity registry preserved + verified live through /rpc: non-uniform status codes
(gameInfo/live 400, setupConfig 503, most 500); live.status 200-with-embedded
{error}; run-in-game 404 echoes serverInstanceId/serverStartedAt while map-config
404 does not; assertNoRawControlFields rejects raw-control keys (400); shared
serialized queue + dual mutex. Contract refinement: civ7.gameInfo.rows and
civ7.live.gameInfo.tables hold the opaque result record (the A1 array shape did
not match /api). Deferred: standalone Bun server, prod /api parity, removing /api,
switching client call sites.

OpenSpec: mapgen-studio-server-orpc (validated --strict).
Verify: studio-server tsc clean; app tsc + vite build + worker-bundle pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mateicanavra
mateicanavra merged commit 611770b into main Jun 12, 2026
This was referenced Jun 12, 2026
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