From 69629168e4426773c341df5f937b175e5aaf63b6 Mon Sep 17 00:00:00 2001 From: Fiona Date: Mon, 5 Jan 2026 17:53:36 -0500 Subject: [PATCH 1/2] feat(graphql): add EnumTypeMap for GraphQL enum materialization --- packages/graphql/src/type-maps.ts | 5 ++-- packages/graphql/src/type-maps/enum.ts | 36 +++++++++++++++++++++++++ packages/graphql/src/type-maps/index.ts | 2 ++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 packages/graphql/src/type-maps/enum.ts create mode 100644 packages/graphql/src/type-maps/index.ts diff --git a/packages/graphql/src/type-maps.ts b/packages/graphql/src/type-maps.ts index eefb2b8aa33..e801a2b6cfc 100644 --- a/packages/graphql/src/type-maps.ts +++ b/packages/graphql/src/type-maps.ts @@ -8,14 +8,13 @@ import type { GraphQLType } from "graphql"; export interface TSPContext { type: T; // The TypeSpec type usageFlag: UsageFlags; // How the type is being used (input, output, etc.) - graphqlName?: string; // Optional GraphQL type name override (e.g., "ModelInput" for input types) - metadata: Record; // Additional metadata + metadata?: Record; // Optional additional metadata } /** * Nominal type for keys in the TypeMap */ -type TypeKey = string & { __typeKey: any }; +export type TypeKey = string & { __typeKey: any }; /** * Base TypeMap for all GraphQL type mappings diff --git a/packages/graphql/src/type-maps/enum.ts b/packages/graphql/src/type-maps/enum.ts new file mode 100644 index 00000000000..5fdb7158438 --- /dev/null +++ b/packages/graphql/src/type-maps/enum.ts @@ -0,0 +1,36 @@ +import type { Enum } from "@typespec/compiler"; +import { GraphQLEnumType } from "graphql"; +import { TypeMap, type TSPContext, type TypeKey } from "../type-maps.js"; + +/** + * TypeMap for converting TypeSpec Enums to GraphQL EnumTypes. + * + * Handles registration of TSP enums and lazy materialization into + * GraphQLEnumType instances. + */ +export class EnumTypeMap extends TypeMap { + /** + * Derives the type key from the mutated enum's name. + */ + protected getNameFromContext(context: TSPContext): TypeKey { + return context.type.name as TypeKey; + } + + /** + * Materializes a TypeSpec Enum into a GraphQL EnumType. + */ + protected materialize(context: TSPContext): GraphQLEnumType { + const tspEnum = context.type; + const name = tspEnum.name; + + return new GraphQLEnumType({ + name, + values: Object.fromEntries( + Array.from(tspEnum.members.values()).map((member) => [ + member.name, + { value: member.value ?? member.name }, + ]), + ), + }); + } +} diff --git a/packages/graphql/src/type-maps/index.ts b/packages/graphql/src/type-maps/index.ts new file mode 100644 index 00000000000..09d87be5490 --- /dev/null +++ b/packages/graphql/src/type-maps/index.ts @@ -0,0 +1,2 @@ +export { TypeMap, type TSPContext, type TypeKey } from "../type-maps.js"; +export { EnumTypeMap } from "./enum.js"; From 17049002edcc0ce1ee3bdffd09772dd54d789026 Mon Sep 17 00:00:00 2001 From: Fiona Date: Thu, 8 Jan 2026 10:39:49 -0500 Subject: [PATCH 2/2] refactor(graphql): use EnumTypeMap in registry --- packages/graphql/src/registry.ts | 71 +++++++++---------------------- packages/graphql/src/type-maps.ts | 2 +- 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/packages/graphql/src/registry.ts b/packages/graphql/src/registry.ts index 12e7d59ddd3..e4ae4c57f3b 100644 --- a/packages/graphql/src/registry.ts +++ b/packages/graphql/src/registry.ts @@ -1,4 +1,4 @@ -import { UsageFlags, type Enum, type Model } from "@typespec/compiler"; +import { UsageFlags, type Enum } from "@typespec/compiler"; import { GraphQLBoolean, GraphQLEnumType, @@ -6,15 +6,8 @@ import { type GraphQLNamedType, type GraphQLSchemaConfig, } from "graphql"; - -// The TSPTypeContext interface represents the intermediate TSP type information before materialization. -// It stores the raw TSP type and any extracted metadata relevant for GraphQL generation. -interface TSPTypeContext { - tspType: Enum | Model; // Extend with other TSP types like Operation, Interface, TSP Union, etc. - name: string; - usageFlags?: Set; - // TODO: Add any other TSP-specific metadata here. -} +import { type TypeKey } from "./type-maps.js"; +import { EnumTypeMap } from "./type-maps/index.js"; /** * GraphQLTypeRegistry manages the registration and materialization of TypeSpec (TSP) * types into their corresponding GraphQL type definitions. @@ -39,61 +32,39 @@ interface TSPTypeContext { * by using thunks for fields/arguments. */ export class GraphQLTypeRegistry { - // Stores intermediate TSP type information, keyed by TSP type name. - // TODO: make this more of a seen set - private TSPTypeContextRegistry: Map = new Map(); + // TypeMap for enum types + private enumTypeMap = new EnumTypeMap(); - // Stores materialized GraphQL types, keyed by their GraphQL name. - private materializedGraphQLTypes: Map = new Map(); + // Track all registered names to detect cross-TypeMap name collisions + private allRegisteredNames = new Set(); addEnum(tspEnum: Enum): void { const enumName = tspEnum.name; - if (this.TSPTypeContextRegistry.has(enumName)) { - // Optionally, log a warning or update if new information is more complete. + + // Check for duplicate names across all type maps + if (this.allRegisteredNames.has(enumName)) { + // Already registered (could be same enum or name collision) + // TODO: Add a warning to the diagnostics return; } - this.TSPTypeContextRegistry.set(enumName, { - tspType: tspEnum, - name: enumName, - // TODO: Populate usageFlags based on TSP context and other decorator context. + this.enumTypeMap.register({ + type: tspEnum, + usageFlag: UsageFlags.Output, // Enums are same for input/output }); + this.allRegisteredNames.add(enumName); } // Materializes a TSP Enum into a GraphQLEnumType. materializeEnum(enumName: string): GraphQLEnumType | undefined { - // Check if the GraphQL type is already materialized. - if (this.materializedGraphQLTypes.has(enumName)) { - return this.materializedGraphQLTypes.get(enumName) as GraphQLEnumType; - } - - const context = this.TSPTypeContextRegistry.get(enumName); - if (!context || context.tspType.kind !== "Enum") { - // TODO: Handle error or warning for missing context. - return undefined; - } - - const tspEnum = context.tspType as Enum; - - const gqlEnum = new GraphQLEnumType({ - name: context.name, - values: Object.fromEntries( - Array.from(tspEnum.members.values()).map((member) => [ - member.name, - { - value: member.value ?? member.name, - }, - ]), - ), - }); - - this.materializedGraphQLTypes.set(enumName, gqlEnum); - return gqlEnum; + return this.enumTypeMap.get(enumName as TypeKey); } materializeSchemaConfig(): GraphQLSchemaConfig { - const allMaterializedGqlTypes = Array.from(this.materializedGraphQLTypes.values()); - let queryType = this.materializedGraphQLTypes.get("Query") as GraphQLObjectType | undefined; + // Collect all materialized types from all TypeMaps + const allMaterializedGqlTypes: GraphQLNamedType[] = [...this.enumTypeMap.getAllMaterialized()]; + // TODO: Query type will come from operations + let queryType: GraphQLObjectType | undefined = undefined; if (!queryType) { queryType = new GraphQLObjectType({ name: "Query", diff --git a/packages/graphql/src/type-maps.ts b/packages/graphql/src/type-maps.ts index e801a2b6cfc..9f6f10c2c62 100644 --- a/packages/graphql/src/type-maps.ts +++ b/packages/graphql/src/type-maps.ts @@ -6,7 +6,7 @@ import type { GraphQLType } from "graphql"; * @template T - The TypeSpec type */ export interface TSPContext { - type: T; // The TypeSpec type + type: T; // The TypeSpec type (mutations should have already been applied) usageFlag: UsageFlags; // How the type is being used (input, output, etc.) metadata?: Record; // Optional additional metadata }