Add mutation engine for TypeSpec-to-GraphQL type transformation#62
Add mutation engine for TypeSpec-to-GraphQL type transformation#62FionaBronwen wants to merge 16 commits intofeature/graphqlfrom
Conversation
0dda7d3 to
4964137
Compare
0f7b9e1 to
139f961
Compare
a05a236 to
79ea773
Compare
| /** | ||
| * Get the wrapper models that were created for scalar variants | ||
| */ | ||
| get wrapperModels() { | ||
| return this.#wrapperModels; | ||
| } |
There was a problem hiding this comment.
The emitter uses wrapperModels to register the synthetic wrapper types (e.g. TextUnionVariant { value: String! }) in the schema alongside the union. Without this, the wrapper objects would be
created but never emitted. But like I mentioned below, it sounds like the GraphQL Spec doesn't want us to do this 😅 I'll come up with a better appraoch.
| const isScalar = variant.type.kind === "Scalar" || variant.type.kind === "Intrinsic"; | ||
|
|
||
| if (isScalar) { | ||
| // Create a synthetic wrapper model for this scalar variant |
There was a problem hiding this comment.
Did we say in the design doc we'd support this this way? I can see it being very surprising that this be the result (and not in a good way). Maybe it needs to be configurable.
There was a problem hiding this comment.
yep! In he union section, it proposes putting wrapper types on scalars since all union member in GraphQL must be models. Although, after looking at the GraphQL spec I see that "The member types of a Union type must all be Object base types; Scalar, Interface and Union types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union." Soooo, maybe we need to rethink this.
## Summary Adds utility functions for transforming TypeSpec names into valid GraphQL identifiers. These utilities form the foundation for name handling throughout the GraphQL emitter. ## Changes - **`src/lib/type-utils.ts`** - Core utility functions for GraphQL name transformations - **`test/lib/type-utils.test.ts`** - Unit tests for `sanitizeNameForGraphQL` ## Utilities Added | Function | Purpose | |----------|---------| | `sanitizeNameForGraphQL` | Sanitize names to be valid GraphQL identifiers | | `toTypeName` | Convert to PascalCase for type names | | `toFieldName` | Convert to camelCase for field names | | `toEnumMemberName` | Convert to CONSTANT_CASE for enum members | | `getUnionName` | Generate names for anonymous unions | | `getTemplatedModelName` | Generate names for templated models (e.g., `ListOfString`) | | `isArray`, `isRecordType` | Type guards for array/record models | | `unwrapModel`, `unwrapType` | Extract element types from arrays | | `isTrueModel` | Check if a model should emit as GraphQL object type | | `getGraphQLDoc` | Extract doc comments for GraphQL descriptions |
Introduce a mutation engine that transforms TypeSpec types into GraphQL-compatible forms using the mutator framework. Includes mutations for enums, models, scalars, unions, and operations. Also includes package hygiene: add tspMain, update node engine to >=20, add api-extractor.json, CHANGELOG.md, fix testing casing, and clean up dead code.
Operations automatically propagate input context to parameters and output context to return types via GraphQLMutationOptions. The framework's cache and options propagation handle nested types, so the same source model produces separate input and output mutations without any custom type-graph walking.
Co-authored-by: Steve Rice <srice@pinterest.com>
Co-authored-by: Steve Rice <srice@pinterest.com>
Co-authored-by: Steve Rice <srice@pinterest.com>
Co-authored-by: Steve Rice <srice@pinterest.com>
Co-authored-by: Steve Rice <srice@pinterest.com>
Use scalars.graphql.org hosted specs per review feedback: - PlainDate → andimarek/local-date - PlainTime → apollographql/localtime-v0.1 - BigDecimal (decimal, decimal128) → chillicream/decimal
42df8a9 to
c204b15
Compare
70e7589 to
84b7764
Compare
This PR introduces a mutation engine that bridges the gap between TypeSpec's type system and
GraphQL's naming/structural constraints. It builds on
@typespec/mutator-frameworkto applyGraphQL-specific transformations to TypeSpec types before they're emitted as SDL.
What this does
TypeSpec and GraphQL have different conventions and rules around type naming and structure. For
example:
camelCasefor properties, GraphQL enum values useSCREAMING_SNAKE_CASEList<string>need readable names likeListOfStringThe mutation engine handles these transformations by defining per-type mutation classes that hook
into the mutator framework's traversal:
PascalCase(types) andSCREAMING_SNAKE_CASE(values)output context to return types
T | null) and flattens them; wraps scalar variants in syntheticobject types (since GraphQL unions only allow object members)
Input/output type context splitting
GraphQL distinguishes between input types (used in operation arguments) and output types (used in
return values). The mutation engine handles this via GraphQLMutationOptions, which overrides the
framework's mutationKey to produce separate cached mutations per context ("input" vs "output").
When an operation is mutated, GraphQLOperationMutation overrides mutateParameters() and
mutateReturnType() to inject the appropriate context. The framework's built-in options propagation
carries that context to all nested types automatically.
Also included
src/lib/type-utils.ts— Shared utilities for name sanitization (toTypeName,toFieldName,sanitizeNameForGraphQL), nullable union detection, template name generation, andsplitWithAcronymsfor handling acronyms in casing conversions
src/lib/scalar-mappings.ts— Complete mapping table from TypeSpec scalars to GraphQL scalar types(including
int64→BigInt,float32→Float, custom scalar specs, etc.)tspMain, node engine bump to>=20,api-extractor.json, consistent casing in testhelpers
Testing
npx vitest run