This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
clue is a GraphQL client library for Scala and Scala.js, built on cats-effect, fs2, circe, and http4s. It provides typed clients for queries, mutations, and subscriptions, plus tooling that generates the boilerplate (case classes, circe codecs, lenses, typeclass instances) for GraphQL operations from a schema.
Published under edu.gemini. Scala 3 (3.8.4) is the primary version; some modules cross-compile or pin other versions (see below).
This is an sbt build. A vendored launcher (sbt-launch.jar) and a Nix flake (flake.nix, direnv) are present, but the standard entry point is sbt.
- Compile everything:
sbt compile(orsbt Test/compile) - Run all tests:
sbt test - Test a single module:
sbt model/test,sbt core/test,sbt http4s/test, etc. Cross-built modules need a platform suffix:sbt modelJVM/test,sbt coreJS/test. - Run a single test (MUnit):
sbt 'core/testOnly clue.SomeSuite'— MUnit also supports*Suite -- --tests=substringto filter test cases. - Format / format-check:
sbt scalafmtAll/sbt scalafmtCheckAll(scalafmt 3.x, config generated by sbt-lucuma — do not hand-edit.scalafmt.conf). - Lint (scalafix):
sbt 'scalafixAll'/sbt 'scalafixAll --check'. - sbt plugin tests:
sbt sbtPlugin/scripted(scripted tests;sbtPlugin/testis wired to run them and publishes the rules/model/core modules locally first).
CI (.github/workflows/ci.yml) runs on Java 17 and 25; the build targets JDK 17 (tlJdkRelease = 17). This is a Typelevel-style build (tl* settings, tlCrossRootProject).
The build aggregates these modules (see build.sbt):
model(JVM+JS, pure) —clue-model. GraphQL wire types and JSON codecs:GraphQLQuery,GraphQLRequest,GraphQLResponse,StreamingMessage, and theclue.data.Inputtype (assigned / unassigned / ignore — the tri-state used for nullable GraphQL input fields). No dependency on the rest of the library.core(JVM+JS, pure) —clue-core. The client/backend abstractions and the WebSocket Apollo client. Depends onmodel.scalajs(JS only) —clue-scalajs. Browser backends (FetchJsBackend,WebSocketJsBackend) using scala-js-dom. Depends oncore.js.http4s(JVM+JS, pure) —clue-http4s. http4s-based backends and client constructors (Http4sHttpBackend/Http4sHttpClient,Http4sWebSocketBackend/Http4sWebSocketClient). Depends oncore.otel4s(JVM+JS, pure) —clue-otel4s. Wraps any client with OpenTelemetry tracing via otel4s; emits aSpanKind.Clientspan per request/subscription and propagates W3C trace context. Depends oncore.http4s-jdk-demo— runnable demo (NoPublish) using the JDK http client behind http4s.gen/*— the code generator (see below).sbt-plugin—sbt-clue, the sbt plugin that runs the generator (CluePlugin). It is cross-built for sbt 1.x (Scala 2.12) and sbt 2.x (Scala 3).
The client API is layered as a set of traits in core/src/main/scala/clue/clients.scala:
FetchClientWithPars[F, P, S]→ one-shot queries/mutations.Sis a phantom schema type used only to type-match clients and operations against each other;Pis implementation-specific request params.StreamingClient[F, S]addssubscribe(returnsResource[F, fs2.Stream[F, GraphQLResponse[D]]]).PersistentClient/PersistentStreamingClientadd an explicitconnect/disconnectlifecycle and a status stream — this is what the WebSocket Apollo client implements.
Operations are values extending GraphQLOperation[S], supplying document: String, type Variables/type Data, and a circe Encoder.AsObject[Variables] / Decoder[Data]. Calling client.request(op) / client.subscribe(op) returns a builder (RequestApplied / SubscriptionApplied) with .withInput(...), error-handling combinators (raiseGraphQLErrors, ignoreGraphQLErrors, etc.), and implicit conversions to the underlying F/Resource.
Backends (core/src/main/scala/clue/backends.scala) are the transport layer, kept separate from clients and provided as given instances:
FetchBackend[F, P]— one-shot HTTP transport.PersistentBackend[F, P, CP, CE]+PersistentConnection+PersistentBackendHandler— connection-oriented (WebSocket) transport, parameterized over close-parameters (CP) and close-event (CE) types.
The Apollo WebSocket client (core/src/main/scala/clue/websocket/) implements the persistent/streaming client over the GraphQL-over-WS (Apollo) protocol, with its own connection State and per-subscription Emitters. Concrete backends live in scalajs (browser) and http4s (JVM/JS via http4s clients).
Generating the case classes and codecs for operations is a central feature. There are two mechanisms, and which one applies depends on the Scala version — read CLASS_GENERATION.md, MACROS.md, and clue-plugin-integration.md before touching this code:
-
Scala 2 macro annotations —
@GraphQLSchema(on an object matching a<Name>.graphqlschema file) and@GraphQL(on aGraphQLOperationobject, expanding from itsdocument). Documented inMACROS.md. Scala 3 does not support macro annotations, so for Scala 3 the project uses mechanism 2. -
Scalafix-based source generator (
gen/) — the production path for Scala 3. The rule isGraphQLGen(a ScalafixSemanticRuleingen/rules, registered viaMETA-INF/services/scalafix.v1.Rule), built against Scala 2.13 and using grackle to parse schemas/queries. Thesbt-clueplugin (CluePlugin) runs this rule to emitsrc_managedsources at compile time.gen/rules— the rule implementation (SchemaGen,QueryGen,Generator, config inGraphQLGenConfig).gen/input/gen/output— fixture sources: hand-written inputs and the expected generated outputs.gen/tests— aScalafixTestkitsuite that runs the rule ongen/inputand diffs againstgen/output. When you change generator behavior, updategen/outputto match (these are golden/snapshot tests).
Per clue-plugin-integration.md: add sbt-clue to project/plugins.sbt, .enablePlugins(CluePlugin), put .graphql operation files under <project>/src/clue/scala/[package]/, and point the generator at the schema with GraphQLGen.schemaDirs=[...] in .scalafix.conf (schemas can come from a dependency such as lucuma-schemas, added via scalafixDependencies). Generated sources land in target/scala-<v>/src_managed/.
Different modules deliberately use different Scala versions: most are Scala 3 (3.8.4), gen/rules is Scala 2.13.18 (Scalafix rules run on 2.13), and sbt-plugin cross-builds Scala 2.12.20 (for sbt 1.13.0) and Scala 3.8.4 (for sbt 2.0.7). Don't assume a single version across the build.
(pluginCrossBuild / sbtVersion) maps the Scala binary version of sbt-plugin to the sbt version. The default is Scala 2.12 / sbt 1, so a plain publishLocal publishes the Scala 3 libraries plus the sbt 1 plugin. Use sbt '++ 3.8.4' sbtPlugin/test for the sbt 2 side.
The two sbt versions differ in API. sbt-plugin/src/main/scala/clue/sbt/CluePlugin.scala is shared, and the per-version shims are in sbt-plugin/src/main/scala-2.12/ and sbt-plugin/src/main/scala-3/. The sbt2-compat plugin supplies Def.uncached, which every side-effecting task needs, because sbt 2 caches task results by default.
- Source files carry the AURA BSD-3-Clause copyright header — keep it on new files.
- Formatting is enforced (scalafmt,
maxColumn = 100, aligned tokens, Scala 3 dialect). RunscalafmtAllbefore considering work done. - Dependency versions are centralized in
project/(Settings/LibraryVersions); scala-steward keeps them updated via automated PRs.