|
1 | 1 | import { UsageFlags, type Enum, type Model } from "@typespec/compiler"; |
2 | | -import { |
3 | | - GraphQLBoolean, |
4 | | - GraphQLEnumType, |
5 | | - GraphQLObjectType, |
6 | | - type GraphQLNamedType, |
7 | | - type GraphQLSchemaConfig, |
8 | | -} from "graphql"; |
| 2 | +import { GraphQLBoolean, GraphQLObjectType, type GraphQLSchemaConfig } from "graphql"; |
| 3 | +import { EnumTypeMap, ModelTypeMap, type TypeKey } from "./type-maps/index.js"; |
9 | 4 |
|
10 | | -// The TSPTypeContext interface represents the intermediate TSP type information before materialization. |
11 | | -// It stores the raw TSP type and any extracted metadata relevant for GraphQL generation. |
12 | | -interface TSPTypeContext { |
13 | | - tspType: Enum | Model; // Extend with other TSP types like Operation, Interface, TSP Union, etc. |
14 | | - name: string; |
15 | | - usageFlags?: Set<UsageFlags>; |
16 | | - // TODO: Add any other TSP-specific metadata here. |
17 | | -} |
18 | 5 | /** |
19 | 6 | * GraphQLTypeRegistry manages the registration and materialization of TypeSpec (TSP) |
20 | 7 | * types into their corresponding GraphQL type definitions. |
21 | 8 | * |
22 | 9 | * The registry operates in a two-stage process: |
23 | 10 | * 1. Registration: TSP types (like Enums, Models, etc.) are first registered |
24 | 11 | * along with relevant metadata (e.g., name, usage flags). This stores an |
25 | | - * intermediate representation (`TSPTypeContext`) without immediately creating |
26 | | - * GraphQL types. This stage is typically performed while traversing the TSP AST. |
27 | | - * Register type by calling the appropriate method (e.g., `addEnum`). |
| 12 | + * intermediate representation without immediately creating GraphQL types. |
28 | 13 | * |
29 | 14 | * 2. Materialization: When a GraphQL type is needed (e.g., to build the final |
30 | | - * schema or resolve a field type), the registry can materialize the TSP type |
| 15 | + * schema or resolve a field type), the registry materializes the TSP type |
31 | 16 | * into its GraphQL counterpart (e.g., `GraphQLEnumType`, `GraphQLObjectType`). |
32 | | - * Materialize types by calling the appropriate method (e.g., `materializeEnum`). |
33 | 17 | * |
34 | 18 | * This approach helps in: |
35 | 19 | * - Decoupling TSP AST traversal from GraphQL object instantiation. |
36 | 20 | * - Caching materialized GraphQL types to avoid redundant work and ensure object identity. |
37 | 21 | * - Handling forward references and circular dependencies, as types can be |
38 | | - * registered first and materialized later when all dependencies are known or |
39 | | - * by using thunks for fields/arguments. |
| 22 | + * registered first and materialized later when all dependencies are known. |
40 | 23 | */ |
41 | 24 | export class GraphQLTypeRegistry { |
42 | | - // Stores intermediate TSP type information, keyed by TSP type name. |
43 | | - // TODO: make this more of a seen set |
44 | | - private TSPTypeContextRegistry: Map<string, TSPTypeContext> = new Map(); |
45 | | - |
46 | | - // Stores materialized GraphQL types, keyed by their GraphQL name. |
47 | | - private materializedGraphQLTypes: Map<string, GraphQLNamedType> = new Map(); |
| 25 | + // TypeMaps for each GraphQL type kind |
| 26 | + private enumMap = new EnumTypeMap(); |
| 27 | + private modelMap = new ModelTypeMap(); |
48 | 28 |
|
| 29 | + /** |
| 30 | + * Register a TypeSpec Enum for later materialization. |
| 31 | + */ |
49 | 32 | addEnum(tspEnum: Enum): void { |
50 | | - const enumName = tspEnum.name; |
51 | | - if (this.TSPTypeContextRegistry.has(enumName)) { |
52 | | - // Optionally, log a warning or update if new information is more complete. |
53 | | - return; |
| 33 | + if (!this.enumMap.isRegistered(tspEnum.name)) { |
| 34 | + this.enumMap.register({ |
| 35 | + type: tspEnum, |
| 36 | + usageFlag: UsageFlags.None, |
| 37 | + metadata: {}, |
| 38 | + }); |
54 | 39 | } |
55 | | - |
56 | | - this.TSPTypeContextRegistry.set(enumName, { |
57 | | - tspType: tspEnum, |
58 | | - name: enumName, |
59 | | - // TODO: Populate usageFlags based on TSP context and other decorator context. |
60 | | - }); |
61 | 40 | } |
62 | 41 |
|
63 | | - // Materializes a TSP Enum into a GraphQLEnumType. |
64 | | - materializeEnum(enumName: string): GraphQLEnumType | undefined { |
65 | | - // Check if the GraphQL type is already materialized. |
66 | | - if (this.materializedGraphQLTypes.has(enumName)) { |
67 | | - return this.materializedGraphQLTypes.get(enumName) as GraphQLEnumType; |
68 | | - } |
69 | | - |
70 | | - const context = this.TSPTypeContextRegistry.get(enumName); |
71 | | - if (!context || context.tspType.kind !== "Enum") { |
72 | | - // TODO: Handle error or warning for missing context. |
73 | | - return undefined; |
| 42 | + /** |
| 43 | + * Register a TypeSpec Model for later materialization. |
| 44 | + */ |
| 45 | + addModel(tspModel: Model): void { |
| 46 | + if (!this.modelMap.isRegistered(tspModel.name)) { |
| 47 | + this.modelMap.register({ |
| 48 | + type: tspModel, |
| 49 | + usageFlag: UsageFlags.None, |
| 50 | + metadata: {}, |
| 51 | + }); |
74 | 52 | } |
| 53 | + } |
75 | 54 |
|
76 | | - const tspEnum = context.tspType as Enum; |
77 | | - |
78 | | - const gqlEnum = new GraphQLEnumType({ |
79 | | - name: context.name, |
80 | | - values: Object.fromEntries( |
81 | | - Array.from(tspEnum.members.values()).map((member) => [ |
82 | | - member.name, |
83 | | - { |
84 | | - value: member.value ?? member.name, |
85 | | - }, |
86 | | - ]), |
87 | | - ), |
88 | | - }); |
| 55 | + /** |
| 56 | + * Materialize a registered enum into a GraphQLEnumType. |
| 57 | + */ |
| 58 | + materializeEnum(enumName: string): void { |
| 59 | + this.enumMap.get(enumName as TypeKey); |
| 60 | + } |
89 | 61 |
|
90 | | - this.materializedGraphQLTypes.set(enumName, gqlEnum); |
91 | | - return gqlEnum; |
| 62 | + /** |
| 63 | + * Materialize a registered model into a GraphQLObjectType. |
| 64 | + */ |
| 65 | + materializeModel(modelName: string): void { |
| 66 | + this.modelMap.get(modelName as TypeKey); |
92 | 67 | } |
93 | 68 |
|
| 69 | + /** |
| 70 | + * Build the final GraphQL schema configuration. |
| 71 | + */ |
94 | 72 | materializeSchemaConfig(): GraphQLSchemaConfig { |
95 | | - const allMaterializedGqlTypes = Array.from(this.materializedGraphQLTypes.values()); |
96 | | - let queryType = this.materializedGraphQLTypes.get("Query") as GraphQLObjectType | undefined; |
| 73 | + // Collect all materialized types from all type maps |
| 74 | + const allMaterializedGqlTypes = [ |
| 75 | + ...this.enumMap.getAllMaterialized(), |
| 76 | + ...this.modelMap.getAllMaterialized(), |
| 77 | + ]; |
| 78 | + |
| 79 | + let queryType = undefined as GraphQLObjectType | undefined; |
| 80 | + // TODO: Get query type from operations when implemented |
| 81 | + |
97 | 82 | if (!queryType) { |
98 | 83 | queryType = new GraphQLObjectType({ |
99 | 84 | name: "Query", |
|
0 commit comments