Consider removing the GraphQL transport until a concrete use case is defined
The GraphQL transport was added as part of the multi-controller foundation. Before it ships as a supported interface, we should agree on who the consumers are — because the two plausible use cases pull in opposite directions and neither is fully served by what we have today.
Use case A — Human explorer
A developer or operator opens a GraphQL playground (e.g. GraphiQL or Apollo Sandbox), introspects the auto-generated schema to discover what the MAIN temperature controller exposes, and reads the current temperature.
With the current typed schema:
# Step 1: discover the schema via introspection (free, built into all GraphQL clients)
query IntrospectMain {
__type(name: "MAINQuery") {
fields { name type { name } }
}
}
# → fields: [temperature, setpoint, rampRate, ...]
# Step 2: fetch the value
query GetTemperature {
MAIN {
temperature
}
}
# → {"data": {"MAIN": {"temperature": 25.3}}}
This use case is well served by the current design. The typed schema is the natural medium for exploratory human use: field names are discoverable, types are checked, and the playground can auto-complete. The extra MAIN wrapper is a minor ergonomic cost.
With a generic getAttribute API:
query GetTemperature {
getAttribute(path: "MAIN/temperature")
}
# → {"data": {"getAttribute": "25.3"}} # stringly-typed; no schema guidance
This is actively worse for the human case: the playground cannot auto-complete "MAIN/temperature", the return is untyped, and there is no way to introspect what paths are valid without a separate introspectController call.
Use case B — Machine subscriber (e.g. a TextRead widget)
A display panel has a TextRead widget bound to the MAIN controller's temperature attribute. It needs to receive a pushed update every time the value changes, without polling.
With the current typed schema:
There are no subscriptions. The only option is polling:
# repeated every N seconds — not a subscription
query PollTemperature {
MAIN { temperature }
}
A strawberry.subscription could be added — it is supported by the library — but it requires a WebSocket transport and an async generator that bridges FastCS's internal attribute-change callbacks to the subscription stream. That plumbing does not exist yet.
With a generic subscribe API:
subscription WatchTemperature {
subscribe(path: "MAIN/temperature")
}
# → stream of {"data": {"subscribe": "25.3"}} on each change
The generic path-based subscription is actually more practical here than the typed approach: a widget framework driven by string paths (like CSS selectors) can subscribe to any attribute without needing a code-generated client SDK. The loss of static typing matters less when the consumer is machine code that already knows the attribute's type from a separate schema fetch or configuration.
The tension
|
Typed schema (current) |
Generic getAttribute |
| Human explorer |
✅ Natural fit; introspection, auto-complete |
❌ No schema guidance |
| Machine bulk-read |
✅ One query, many typed fields |
✅ Could also work |
| Machine subscription |
❌ Not implemented; hard to express per-field |
✅ Simpler to wire to attr callbacks |
| Code-gen client SDK |
✅ Industry standard (Apollo, graphql-codegen) |
❌ SDK is useless without types |
| Odin-control drop-in |
❌ Different mental model entirely |
✅ Closer to ParameterTree |
The core problem is that GraphQL's type system is only a benefit when clients are built against it. We have no such clients. Meanwhile, the one machine use case we can anticipate (subscription updates for display widgets) is not implemented at all — and when it is, the generic path-based form is easier to connect to FastCS's attribute-change infrastructure than a typed field-per-attribute subscription.
Proposal
Remove the GraphQL transport from this PR (or gate it behind an extras install that is explicitly marked experimental) until at least one of the following is true:
- A real human-facing client exists that benefits from typed schema introspection.
- The subscription mechanism (WebSocket transport + attribute-change → async generator bridge) is implemented and tested, confirming which schema shape is ergonomically correct for that use case.
Shipping the transport now locks in a schema shape ({ MAIN { temperature } }) that may need to change again once subscriptions land, and creates migration burden for consumers we don't yet have.
If the transport is kept, the minimum necessary addition before it can be considered stable is:
strawberry.subscription fields that stream attribute value changes, either typed per-field or via a generic subscribe(path: str) — with a concrete display-widget integration validating whichever choice is made.
Consider removing the GraphQL transport until a concrete use case is defined
The GraphQL transport was added as part of the multi-controller foundation. Before it ships as a supported interface, we should agree on who the consumers are — because the two plausible use cases pull in opposite directions and neither is fully served by what we have today.
Use case A — Human explorer
A developer or operator opens a GraphQL playground (e.g. GraphiQL or Apollo Sandbox), introspects the auto-generated schema to discover what the
MAINtemperature controller exposes, and reads the current temperature.With the current typed schema:
This use case is well served by the current design. The typed schema is the natural medium for exploratory human use: field names are discoverable, types are checked, and the playground can auto-complete. The extra
MAINwrapper is a minor ergonomic cost.With a generic
getAttributeAPI:This is actively worse for the human case: the playground cannot auto-complete
"MAIN/temperature", the return is untyped, and there is no way to introspect what paths are valid without a separateintrospectControllercall.Use case B — Machine subscriber (e.g. a TextRead widget)
A display panel has a
TextReadwidget bound to theMAINcontroller's temperature attribute. It needs to receive a pushed update every time the value changes, without polling.With the current typed schema:
There are no subscriptions. The only option is polling:
A
strawberry.subscriptioncould be added — it is supported by the library — but it requires a WebSocket transport and an async generator that bridges FastCS's internal attribute-change callbacks to the subscription stream. That plumbing does not exist yet.With a generic
subscribeAPI:The generic path-based subscription is actually more practical here than the typed approach: a widget framework driven by string paths (like CSS selectors) can subscribe to any attribute without needing a code-generated client SDK. The loss of static typing matters less when the consumer is machine code that already knows the attribute's type from a separate schema fetch or configuration.
The tension
getAttributeThe core problem is that GraphQL's type system is only a benefit when clients are built against it. We have no such clients. Meanwhile, the one machine use case we can anticipate (subscription updates for display widgets) is not implemented at all — and when it is, the generic path-based form is easier to connect to FastCS's attribute-change infrastructure than a typed field-per-attribute subscription.
Proposal
Remove the GraphQL transport from this PR (or gate it behind an
extrasinstall that is explicitly marked experimental) until at least one of the following is true:Shipping the transport now locks in a schema shape (
{ MAIN { temperature } }) that may need to change again once subscriptions land, and creates migration burden for consumers we don't yet have.If the transport is kept, the minimum necessary addition before it can be considered stable is:
strawberry.subscriptionfields that stream attribute value changes, either typed per-field or via a genericsubscribe(path: str)— with a concrete display-widget integration validating whichever choice is made.