Skip to content
Draft
Show file tree
Hide file tree
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
71 changes: 21 additions & 50 deletions packages/graphql/src/registry.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { UsageFlags, type Enum, type Model } from "@typespec/compiler";
import { UsageFlags, type Enum } from "@typespec/compiler";
import {
GraphQLBoolean,
GraphQLEnumType,
GraphQLObjectType,
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<UsageFlags>;
// 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.
Expand All @@ -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<string, TSPTypeContext> = new Map();
// TypeMap for enum types
private enumTypeMap = new EnumTypeMap();

// Stores materialized GraphQL types, keyed by their GraphQL name.
private materializedGraphQLTypes: Map<string, GraphQLNamedType> = new Map();
// Track all registered names to detect cross-TypeMap name collisions
private allRegisteredNames = new Set<string>();

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",
Expand Down
7 changes: 3 additions & 4 deletions packages/graphql/src/type-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import type { GraphQLType } from "graphql";
* @template T - The TypeSpec type
*/
export interface TSPContext<T extends Type> {
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.)
graphqlName?: string; // Optional GraphQL type name override (e.g., "ModelInput" for input types)
metadata: Record<string, any>; // Additional metadata
metadata?: Record<string, any>; // 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
Expand Down
36 changes: 36 additions & 0 deletions packages/graphql/src/type-maps/enum.ts
Original file line number Diff line number Diff line change
@@ -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<Enum, GraphQLEnumType> {
/**
* Derives the type key from the mutated enum's name.
*/
protected getNameFromContext(context: TSPContext<Enum>): TypeKey {
return context.type.name as TypeKey;
}

/**
* Materializes a TypeSpec Enum into a GraphQL EnumType.
*/
protected materialize(context: TSPContext<Enum>): 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 },
]),
),
});
}
}
2 changes: 2 additions & 0 deletions packages/graphql/src/type-maps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { TypeMap, type TSPContext, type TypeKey } from "../type-maps.js";
export { EnumTypeMap } from "./enum.js";