8000 clue/CLAUDE.md at gql-lexer · gemini-hlsw/clue · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
81 lines (52 loc) · 7.98 KB

CLAUDE.md

File metadata and controls

81 lines (52 loc) · 7.98 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

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).

Build & test commands

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 (or sbt 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=substring to 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/test is 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).

Module layout

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 the clue.data.Input type (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 on model.
  • scalajs (JS only) — clue-scalajs. Browser backends (FetchJsBackend, WebSocketJsBackend) using scala-js-dom. Depends on core.js.
  • http4s (JVM+JS, pure) — clue-http4s. http4s-based backends and client constructors (Http4sHttpBackend/Http4sHttpClient, Http4sWebSocketBackend/Http4sWebSocketClient). Depends on core.
  • otel4s (JVM+JS, pure) — clue-otel4s. Wraps any client with OpenTelemetry tracing via otel4s; emits a SpanKind.Client span per request/subscription and propagates W3C trace context. Depends on core.
  • 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).

Core architecture

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. S is a phantom schema type used only to type-match clients and operations against each other; P is implementation-specific request params.
  • StreamingClient[F, S] adds subscribe (returns Resource[F, fs2.Stream[F, GraphQLResponse[D]]]).
  • PersistentClient / PersistentStreamingClient add an explicit connect/disconnect lifecycle 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).

Code generation (important, two distinct mechanisms)

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:

  1. Scala 2 macro annotations — @GraphQLSchema (on an object matching a <Name>.graphql schema file) and @GraphQL (on a GraphQLOperation object, expanding from its document). Documented in MACROS.md. Scala 3 does not support macro annotations, so for Scala 3 the project uses mechanism 2.

  2. Scalafix-based source generator (gen/) — the production path for Scala 3. The rule is GraphQLGen (a Scalafix SemanticRule in gen/rules, registered via META-INF/services/scalafix.v1.Rule), built against Scala 2.13 and using grackle to parse schemas/queries. The sbt-clue plugin (CluePlugin) runs this rule to emit src_managed sources at compile time.

    • gen/rules — the rule implementation (SchemaGen, QueryGen, Generator, config in GraphQLGenConfig).
    • gen/input / gen/output — fixture sources: hand-written inputs and the expected generated outputs.
    • gen/tests — a ScalafixTestkit suite that runs the rule on gen/input and diffs against gen/output. When you change generator behavior, update gen/output to match (these are golden/snapshot tests).

Using the sbt plugin in a downstream project

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/.

Version pinning (gotcha)

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.

Conventions

  • 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). Run scalafmtAll before considering work done.
  • Dependency versions are centralized in project/ (Settings/LibraryVersions); scala-steward keeps them updated via automated PRs.
0