Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions REPO_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Linq2GraphQL.Client — High-Level Repo Report

*Analysed at `main` @ `a17c22b` (2026-09-07)*

## 1. What the repo is

A **LINQ-to-GraphQL client for .NET**, shipped as three NuGet artifacts:

| Artifact | Kind | Role |
| --- | --- | --- |
| `Linq2GraphQL.Generator` | `dotnet tool` (`Linq2GraphQL`) | Introspects a GraphQL endpoint, emits a strongly-typed C# client via T4 templates |
| `Linq2GraphQL.Client` | library | Runtime: turns `Include`/`Select` lambdas into a query tree, executes, deserializes |
| `Linq2GraphQL.Client.Subscriptions` | library | Optional subscriptions transport (graphql-ws + SSE) |

Everything else in the tree — `test/`, `docs/`, `StartGG/` — exists to exercise or demonstrate those three.

## 2. Size and shape

Hand-written C# (excluding `bin`/`obj`):

```
src/Linq2GraphQL.Generator 6 701 lines (32 files) <- mostly T4 output
src/Linq2GraphQL.Client 2 584 lines (45 files)
src/Linq2GraphQL.Client.Subscriptions 383 lines ( 9 files)
test/Linq2GraphQL.Tests 2 190 lines (19 files, 126 [Fact]/[Theory])
test/Linq2GraphQL.TestClient* 6 523 lines (checked-in generated output)
docs/ + StartGG/ 15 465 lines (Blazor site + sample clients)
```

**The product itself is small — under 3 000 lines of runtime code.** That is the headline: a compact, focused library where the generator's line count is inflated by machine-generated template partials, not by complexity.

Largest runtime files: `Converters/EnumConverter.cs` (343), `Visitors/QueryExpressionVisitor.cs` (332), `QueryNode.cs` (244), `Utilities.cs` (185).

## 3. Architecture in one paragraph

Three stages, and understanding them is usually the whole job:

1. **Build** — generated `QueryMethods`/`MutationMethods` return `GraphQuery<T>` seeded with a root `QueryNode` (field name + `ArgumentValue`s).
2. **Parse** — `Include(...)`/`Select(...)` lambdas go through `Utilities.ParseExpression` -> `QueryExpressionVisitor`. Two modes: `ResolvePath` for expressions that *name* a field (member chains, `[GraphQLMember]` methods, LINQ operators), and plain `ExpressionVisitor` walking for everything else so every mentioned field still lands in the query. Lambda parameters bind to nodes keyed **by `ParameterExpression` reference**, so nested lambdas reusing a name stay distinct.
3. **Execute** — `GraphBaseExecute` lazily assigns unique variable names and auto-adds primitive children, renders query text from the `QueryNode` tree; `QueryExecutor<T>` POSTs and unwraps `data`/`errors`/`extensions`.

`QueryNode` is the entire intermediate representation. Everything interesting happens between it and the visitor.

## 4. Highlights — the good

- **The test strategy is unusually strong for a library this size.** 126 tests, and most are genuinely end-to-end: `WebApplicationFactory<Program>` boots a real HotChocolate server in-process (`Linq2GraphQL.TestServer`) and the *checked-in generated client* queries it. A passing suite proves the generator, the parser, the query text and the deserializer all agree — not just that a unit returns the expected string.
- **The nullable variant is a first-class citizen**, not an afterthought: a parallel `TestServerNullable` + `TestClientNullable` + fixture pair.
- **Build hygiene is modern and tight.** .NET 10 throughout, central package management (`Directory.Packages.props`), `RestorePackagesWithLockFile` with `--locked-mode` in CI, Nerdbank.GitVersioning driving versions from git, and solution filters splitting CI (`Linq2GraphQL.CI.slnf`) from pack/publish (`Linq2GraphQL.Release.slnf`). No hand-edited version numbers anywhere.
- **Release is one button.** `release.yml` is `workflow_dispatch`-only: nbgv -> pack -> push to NuGet -> GitHub release. Low ceremony, hard to trigger by accident.
- **Deliberate design details that show maturity:** argument-hash-derived GraphQL aliases so the same field can be requested twice with different arguments; opt-in "safe mode" that validates auto-included primitives against a cached introspection result; two error surfaces (`ExecuteAsync` throws, `ExecuteWithResultAsync` returns `GraphResult<T>`) funnelled through a single `ProcessResponseFull`.
- **Generated files are normalised to LF** (`ReplaceLineEndings("\n")` in `Program.cs`) — a small thing that prevents a lot of cross-platform diff noise.

## 5. Highlights — the risks

- **T4 is the sharpest edge in the repo.** Each template is three files: `X.tt` (source), `X.tt.cs` (hand-written partial), `X.cs` (**preprocessed output, checked in**). Regeneration happens only via Visual Studio's *Run Custom Tool*; there is no CLI equivalent wired up. A template edit made outside VS **builds and runs the old logic silently**. This is the single most likely way to lose an afternoon here.
- **Generated test clients are checked in but never regenerated by the build.** `TestClient`/`TestClientNullable` must be re-generated by hand against a locally running TestServer after any template or schema change. Nothing in CI detects the drift — the tests keep passing against stale output.
- **Coupling between alias hashing and deserialization.** `Utilities.GetArgumentsId` must produce the *same* hash at write time and read time. Any change to argument hashing breaks reads as well as writes, and the failure mode is a silently missing field rather than an exception.
- **Ambient static generator state.** `GeneratorSettings.Current` is read from inside templates, so nullable/non-nullable output depends on global mutable state rather than a passed parameter. Fine today; awkward if generation ever needs to run concurrently.
- **`docs/` and `StartGG/` are outside the CI filter and can drift.** They are in `Linq2GraphQL.sln` but not `Linq2GraphQL.CI.slnf`, so nothing verifies they still compile — roughly 15k lines CI never touches.
- **Subscriptions are only partly covered.** Two transports exist (`WSClient` for graphql-ws, `SSEClient` for SSE), but only SSE works under the test host — and there is exactly **1** subscription test. The WebSocket path is effectively untested.
- **Minor tidy-ups:** `nuget.config.backup` is committed alongside `nuget.config`; a stray root-level `StarWars.Client/` containing only `obj/` shadows the real `docs/StarWars.Client`; the README options list has a few typos (`--nullabel`, "Exprimental").

## 6. Activity and ownership

- **255 commits**, active since 2023: 110 (2023), 60 (2024), 73 (2025), 12 (2026 YTD).
- **Concentrated ownership.** Joakim Dangården holds 180 of 255 commits across two identities; Magnus Ahlberg 62; a long tail of 4 outside contributors with ~19 between them. Bus factor is effectively one.
- **Recent work is squarely in the core.** The last six months of churn lands on `src/Linq2GraphQL.Client` (10 touches, 5 of them in `Visitors/`), Subscriptions (7), and the tests (6). Notable recent commits: the **expression parser rewrite** (#92), the .NET 10 upgrade with CVE/warning cleanup (#90), and structured GraphQL error handling contributed externally (#88).
- **PR-driven workflow.** Nearly every change lands as a merge commit from a named branch — the history reads cleanly.

## 7. If I were picking up work here

1. Wire up **CLI T4 regeneration** (or a CI check that regenerating produces no diff). This removes the repo's one silent-failure trap.
2. Add a **CI job that regenerates the test clients and diffs them** — it closes the stale-generated-output gap for free.
3. Pull `docs/` and `StartGG/` into a **build-only CI job** so they cannot rot unnoticed.
4. Get the **WebSocket subscription transport under test**, even against a standalone host outside `WebApplicationFactory`.
5. Delete `nuget.config.backup` and the stray root `StarWars.Client/`.

## Appendix — commands that matter

```powershell
# What CI runs
dotnet restore Linq2GraphQL.CI.slnf --locked-mode
dotnet build Linq2GraphQL.CI.slnf --no-restore
dotnet test Linq2GraphQL.CI.slnf --no-build

# Generate a client against a live endpoint
dotnet run --project src/Linq2GraphQL.Generator -- <endpoint> -c=ClientName -n=Namespace -o=Generated
```
Loading