Skip to content

Add Alloy infrastructure, context system, and field components#77

Merged
swatkatz merged 1 commit into
feature/graphql-structural-transformsfrom
fionabronwen/graphql-alloy-foundation-v2
Jun 9, 2026
Merged

Add Alloy infrastructure, context system, and field components#77
swatkatz merged 1 commit into
feature/graphql-structural-transformsfrom
fionabronwen/graphql-alloy-foundation-v2

Conversation

@FionaBronwen

@FionaBronwen FionaBronwen commented Apr 12, 2026

Copy link
Copy Markdown

Summary

Adds the Alloy component infrastructure for the GraphQL emitter, stacked on PR #89 (structural transforms):

  • Alloy JSX dependencies (@alloy-js/core, @alloy-js/graphql), build tooling (alloy build, rollup plugin)
  • GraphQLSchemaContext with TypeGraph — imports the interface from mutation-engine/type-graph.ts (single source of truth)
  • Field and OperationField components — thin translators from mutated types to Alloy GraphQL SDL components
  • Uses resolveGraphQLTypeName (shared lib) for type name resolution and decorator-based nullable state

No GraphQLTypeExpression render-prop component — the mutation pipeline (PRs #88, #89) already handles name composition, nullable unwrapping, record-to-scalar, and union transforms. Components just read type.name and nullable metadata.

Key design decisions

  • TypeGraph as the context contract — the mutation pipeline produces a flat namespace of mutated types. Components query the namespace for what they need rather than depending on pre-sorted buckets.
  • No type resolution in the renderer — all transforms happen in Phase 1 (mutation engine). Rendering is a direct mapping from mutated types to SDL.
  • resolveGraphQLTypeName handles the one exception: std scalars (stringString, int32Int) which the mutation engine intentionally leaves untouched to avoid mutating shared compiler singletons.

Test plan

  • Field components will be tested end-to-end when the schema orchestrator is wired up (next PR in the chain)
  • Existing 201 mutation engine tests pass unchanged
  • Type check clean

Stacked on #89.

🤖 Generated with Claude Code

@FionaBronwen
FionaBronwen changed the base branch from fionabronwen/graphql-nullable-tracking to feature/graphql April 14, 2026 16:12
@FionaBronwen
FionaBronwen force-pushed the fionabronwen/graphql-alloy-foundation-v2 branch 8 times, most recently from 8f1da87 to 8b51ca4 Compare April 14, 2026 21:04
@FionaBronwen
FionaBronwen marked this pull request as ready for review April 14, 2026 21:14
@FionaBronwen
FionaBronwen changed the base branch from feature/graphql to fionabronwen/graphql-statemap-to-decorators April 21, 2026 16:00
@FionaBronwen
FionaBronwen force-pushed the fionabronwen/graphql-alloy-foundation-v2 branch 2 times, most recently from 8f411e1 to 5f26c25 Compare April 28, 2026 15:28
Comment thread packages/graphql/src/context/graphql-schema-context.tsx Outdated
Comment thread packages/graphql/src/context/graphql-schema-context.tsx Outdated
Comment thread packages/graphql/src/context/graphql-schema-context.tsx Outdated
FionaBronwen added a commit that referenced this pull request Jun 3, 2026
After the TypeGraph refactor in PR #77, ScalarVariant was removed from the
context module. Move it to schema-mutator.ts where it belongs since it's
part of the MutatedSchema interface.
@swatkatz

swatkatz commented Jun 4, 2026

Copy link
Copy Markdown

TypeGraph.globalNamespace is populated with the pre-mutation namespace (schema.type) in the emitter wiring, but the interface should contain the mutated types instead."

After engine.mutateSchema() runs, the mutated types exist in the MutatedSchema buckets but are never packaged into the TypeGraph. The pinboard pipeline has buildTypeGraph(program, tk, mutatedTypes) which correctly constructs a TypeGraph from mutated types — the open-source emitter should do the same.

This matters because:

  • Pinterest's pipeline needs to chain Stage 4 on top of Stage 3's TypeGraph output
  • A resolver emitter consuming useGraphQLSchema() would get unmutated types
  • useGraphQLSchema() is never called by any component in the chain — symptom of this being wired incorrectly

Fix: after engine.mutateSchema() returns, build a proper TypeGraph from the mutated types and put that on the context. Either move buildTypeGraph into @typespec/graphql or use an equivalent.

@swatkatz

swatkatz commented Jun 8, 2026

Copy link
Copy Markdown

Architecture discussion

After tracing the full PR chain (#77#85), here's how I think the pieces should fit together:

Data flow

mutateSchema(program, engine, namespace)
    → MutatedSchema (bucketed mutated types)
    → buildTypeGraph(program, tk, allMutatedTypes)
    → TypeGraph { globalNamespace }  (post-mutation)
    → Renderer walks TypeGraph, renders via Alloy components

TypeGraph wiring

buildTypeGraph (from the pinboard pipeline PR) packages all mutated types into a clean namespace. The emitter should call it after mutateSchema and put the result on context. Currently PR #84 puts schema.type (pre-mutation) which means useGraphQLSchema() returns unmutated types, explaining why no component ever calls it.

Type resolution

GraphQLTypeExpression as a render-prop returning GraphQLTypeInfo adds indirection that doesn't carry its weight. Alloy's <gql.Field> takes a type string + nonNull + nested <gql.Field.List> children. A helper function returning { typeName, nonNull, listLayers: boolean[] } would let Field render Alloy components directly:

const resolved = resolveGraphQLType(property.type, ...);
<gql.Field name={property.name} type={resolved.typeName} nonNull={resolved.nonNull}>
  {resolved.listLayers.reduceRight(
    (inner, layerNonNull) => <gql.Field.List nonNull={layerNonNull}>{inner}</gql.Field.List>,
    undefined
  )}
</gql.Field>

This also fixes the nested list issue (string[][][[String!]!]!) since listLayers is an array, not a boolean and other such subtle bugs that might be introduced by adding this indirection.

Component data flow

Components should get their types from TypeGraph.globalNamespace (via useGraphQLSchema() + walking/filtering), not from MutatedSchema buckets threaded through props from the emitter. This keeps the renderer self-contained and makes TypeGraph the real shared contract for downstream consumers (resolver emitter, Pinterest pipeline Stage 4).

What stays the same

  • Mutation engine + all mutations (model renaming, input suffix, scalar mapping, union wrapping, etc.)
  • mutateSchema orchestration
  • Alloy GraphQL components
  • Two-phase principle (all naming in mutation, rendering uses type.name)

We should also add tests in each PR to catch edge cases, errors, rather than doing it all at once at the end.

@swatkatz
swatkatz force-pushed the fionabronwen/graphql-alloy-foundation-v2 branch from ea85714 to 7edda3d Compare June 8, 2026 20:13
@swatkatz
swatkatz changed the base branch from fionabronwen/graphql-statemap-to-decorators to feature/graphql-structural-transforms June 8, 2026 20:13
@swatkatz
swatkatz requested a review from mauriciogardini June 8, 2026 20:15
@swatkatz
swatkatz force-pushed the fionabronwen/graphql-alloy-foundation-v2 branch from 7edda3d to 3f74c91 Compare June 8, 2026 20:15
Comment thread packages/graphql/src/components/fields/operation-field.tsx Outdated
- Build: @alloy-js/graphql (linked local), JSX transpilation via alloy
  build, rollup plugin for vitest
- Context: GraphQLSchemaContext provider with TypeGraph from mutation
  engine (single source of truth, no duplicate interface)
- Components: Field and OperationField — thin translators from mutated
  types to Alloy GraphQL SDL components. Use resolveGraphQLTypeName for
  type names and decorator-based nullable state. No type-expression
  render-prop indirection (mutation pipeline handles all transforms).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@swatkatz
swatkatz force-pushed the fionabronwen/graphql-alloy-foundation-v2 branch from 3f74c91 to 21abccf Compare June 8, 2026 20:24
@FionaBronwen

Copy link
Copy Markdown
Author

@swatkatz Additions LGTM! thanks!

@swatkatz swatkatz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Fiona is the author, she can't officially approve this PR, so I am approving it on her behalf as I wrote the code, so it is fine to swap roles: https://pinterest.slack.com/archives/C08UD3MR5QT/p1781035265891089?thread_ts=1780928581.664069&cid=C08UD3MR5QT

@swatkatz
swatkatz merged commit f18c6e2 into feature/graphql-structural-transforms Jun 9, 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.

2 participants