forked from microsoft/typespec
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit sets up the basic skeleton for the GraphQL emitter, capable of handling multiple defined GraphQL schemas. The approach here is slightly different from before. Here, we are limiting the functionality of `GraphQLEmitter` to handling the schema definitions, and not participating in any direct parsing of the TSP program. Instead, the `GraphQLTypeRegistry` is responsible for handling its own interpretation of the TSP program in order to provide the types in its registry. This primarily allows for two things: 1. The `GraphQLTypeRegistry` can encapsulate its own functionality, instead of being a "bucket of state" that must be modified and managed externally. 2. The "bucket of state" responsibility can be primarily handled by the StateMap library, with the `GraphQLTypeRegistry` being the orchestrator of that state The `GraphQLTypeRegistry` uses a `Proxy` object to ensure that the program navigation has taken place before any of its public properties are accessed.
- Loading branch information
Showing
5 changed files
with
255 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { navigateProgram, type Program, type SemanticNodeListener } from "@typespec/compiler"; | ||
import type { GraphQLObjectType } from "graphql"; | ||
|
||
type Mutable<T> = { | ||
-readonly [k in keyof T]: T[k]; | ||
}; | ||
|
||
// This class contains the registry of all the GraphQL types that are being used in the program | ||
export class GraphQLTypeRegistry { | ||
program: Program; | ||
readonly programNavigated: boolean = false; | ||
|
||
constructor(program: Program) { | ||
this.program = program; | ||
return new Proxy(this, { | ||
get(target: GraphQLTypeRegistry, prop: string, receiver) { | ||
if (GraphQLTypeRegistry.#publicGetters.includes(prop)) { | ||
if (!target.programNavigated) { | ||
const mutableThis = target as Mutable<GraphQLTypeRegistry>; | ||
navigateProgram(target.program, target.#semanticNodeListener); | ||
mutableThis.programNavigated = true; | ||
} | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
}, | ||
}); | ||
} | ||
|
||
static get #publicGetters() { | ||
return Object.entries(Object.getOwnPropertyDescriptors(GraphQLTypeRegistry.prototype)) | ||
.filter(([key, descriptor]) => { | ||
return typeof descriptor.get === "function" && key !== "constructor"; | ||
}) | ||
.map(([key]) => key); | ||
} | ||
|
||
get rootQueryType(): GraphQLObjectType | undefined { | ||
return; | ||
} | ||
|
||
// This is the listener based on navigateProgram that will walk the TSP AST and register the types, | ||
// deferred in some cases, and then materialize them in exitXXX functions | ||
get #semanticNodeListener(): SemanticNodeListener { | ||
return {}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { isType, type GraphQLType } from "graphql"; | ||
import { expect } from "vitest"; | ||
|
||
interface GraphQLAssertions<R = unknown> { | ||
toEqualType: (expected: GraphQLType) => R; | ||
} | ||
|
||
declare module "vitest" { | ||
interface Assertion<T = any> extends GraphQLAssertions<T> {} | ||
interface AsymmetricMatchersContaining extends GraphQLAssertions {} | ||
} | ||
|
||
expect.extend({ | ||
toEqualType(received: GraphQLType, expected: GraphQLType) { | ||
if (!isType(expected)) { | ||
return { | ||
pass: false, | ||
message: () => `Expected value ${expected} is not a GraphQLType.`, | ||
}; | ||
} | ||
|
||
if (!isType(received)) { | ||
return { | ||
pass: false, | ||
message: () => `Received value ${received} is not a GraphQLType.`, | ||
}; | ||
} | ||
|
||
const { isNot } = this; | ||
return { | ||
pass: received.toJSON() === expected.toJSON(), | ||
message: () => `${received} is${isNot ? " not" : ""} the same as ${expected}`, | ||
}; | ||
}, | ||
}); | ||
|
||
export { expect }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { expectDiagnosticEmpty } from "@typespec/compiler/testing"; | ||
import { GraphQLSchema, printSchema } from "graphql"; | ||
import { describe, it } from "vitest"; | ||
import { GraphQLEmitter } from "../src/schema-emitter.js"; | ||
import { expect } from "./assertions.js"; | ||
import { emitSingleSchemaWithDiagnostics } from "./test-host.js"; | ||
|
||
describe("GraphQL emitter", () => { | ||
it("Can produce a placeholder GraphQL schema", async () => { | ||
const result = await emitSingleSchemaWithDiagnostics(""); | ||
expectDiagnosticEmpty(result.diagnostics); | ||
expect(result.graphQLSchema).toBeInstanceOf(GraphQLSchema); | ||
expect(result.graphQLSchema?.getQueryType()).toEqualType(GraphQLEmitter.placeholderQuery); | ||
}); | ||
|
||
it("Can produce an SDL output", async () => { | ||
const result = await emitSingleSchemaWithDiagnostics(""); | ||
expectDiagnosticEmpty(result.diagnostics); | ||
expect(result.graphQLOutput).toEqual(printSchema(result.graphQLSchema!)); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { beforeEach, describe, it } from "vitest"; | ||
import { GraphQLTypeRegistry } from "../src/registry.js"; | ||
import { expect } from "./assertions.js"; | ||
import { createGraphqlTestRunner } from "./test-host.js"; | ||
|
||
describe("GraphQL Type Registry", () => { | ||
let registry: GraphQLTypeRegistry; | ||
|
||
beforeEach(async () => { | ||
const runner = await createGraphqlTestRunner(); | ||
await runner.diagnose(""); | ||
registry = new GraphQLTypeRegistry(runner.program); | ||
}); | ||
|
||
it("Will navigate program when state is accessed", () => { | ||
expect(registry.programNavigated).toBe(false); | ||
expect(registry.rootQueryType).toBeUndefined(); | ||
expect(registry.programNavigated).toBe(true); | ||
}); | ||
}); |