Skip to content

Commit 5ef2ace

Browse files
committed
Add @schema decorator to mark namespaces as GraphQL schemas
Using the `TypeSpec.GraphQL.@schema` decorator on a namespace indicates that the decorated namespace represents a GraphQL schema that should be generated by the GraphQL emitter. Because this allows for multiple schemas to be specified in a TypeSpec source, our test host is reworked to provide a `GraphQLTestResult` corresponding to each schema produced. This commit does not actually implement any emitter functionality, but populates a state map that will be used by the emitter in the future.
1 parent 73e3fd0 commit 5ef2ace

File tree

14 files changed

+265
-21
lines changed

14 files changed

+265
-21
lines changed

packages/graphql/lib/main.tsp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "./schema.tsp";

packages/graphql/lib/schema.tsp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import "../dist/src/lib/schema.js";
2+
3+
using TypeSpec.Reflection;
4+
5+
namespace TypeSpec.GraphQL;
6+
7+
namespace Schema {
8+
model SchemaOptions {
9+
name?: string;
10+
}
11+
}
12+
13+
/**
14+
* Mark this namespace as describing a GraphQL schema and configure schema properties.
15+
*
16+
* @example
17+
*
18+
* ```typespec
19+
* @schema(#{name: "MySchema"})
20+
* namespace MySchema {};
21+
* ```
22+
*/
23+
extern dec schema(target: Namespace, options?: valueof Schema.SchemaOptions);

packages/graphql/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"main": "dist/src/index.js",
2121
"exports": {
2222
".": {
23+
"typespec": "./lib/main.tsp",
2324
"types": "./dist/src/index.d.ts",
2425
"default": "./dist/src/index.js"
2526
},
@@ -31,6 +32,12 @@
3132
"engines": {
3233
"node": ">=18.0.0"
3334
},
35+
"graphql": {
36+
"documents": "test/**/*.{js,ts}"
37+
},
38+
"dependencies": {
39+
"graphql": "^16.9.0"
40+
},
3441
"scripts": {
3542
"clean": "rimraf ./dist ./temp",
3643
"build": "tsc",

packages/graphql/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { $onEmit } from "./emitter.js";
22
export { $lib } from "./lib.js";
3+
export { $decorators } from "./tsp-index.js";

packages/graphql/src/lib.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { createTypeSpecLibrary, type JSONSchemaType } from "@typespec/compiler";
22

3+
export const NAMESPACE = "TypeSpec.GraphQL";
4+
35
export interface GraphQLEmitterOptions {
46
/**
57
* Name of the output file.
@@ -95,8 +97,11 @@ export const libDef = {
9597
emitter: {
9698
options: EmitterOptionsSchema as JSONSchemaType<GraphQLEmitterOptions>,
9799
},
100+
state: {
101+
schema: { description: "State for the @schema decorator." },
102+
},
98103
} as const;
99104

100105
export const $lib = createTypeSpecLibrary(libDef);
101106

102-
export const { reportDiagnostic, createDiagnostic } = $lib;
107+
export const { reportDiagnostic, createDiagnostic, stateKeys: GraphQLKeys } = $lib;

packages/graphql/src/lib/schema.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
type DecoratorContext,
3+
type DecoratorFunction,
4+
type Namespace,
5+
type Program,
6+
validateDecoratorUniqueOnNode,
7+
} from "@typespec/compiler";
8+
9+
import { GraphQLKeys, NAMESPACE } from "../lib.js";
10+
import { useStateMap } from "./state-map.js";
11+
12+
// This will set the namespace for decorators implemented in this file
13+
export const namespace = NAMESPACE;
14+
15+
export interface SchemaDetails {
16+
name?: string;
17+
}
18+
19+
export interface Schema extends SchemaDetails {
20+
type: Namespace;
21+
}
22+
23+
const [getSchema, setSchema, getSchemaMap] = useStateMap<Namespace, Schema>(GraphQLKeys.schema);
24+
25+
/**
26+
* List all the schemas defined in the TypeSpec program
27+
* @param program Program
28+
* @returns List of schemas.
29+
*/
30+
export function listSchemas(program: Program): Schema[] {
31+
return [...getSchemaMap(program).values()];
32+
}
33+
34+
export {
35+
/**
36+
* Get the schema information for the given namespace.
37+
* @param program Program
38+
* @param namespace Schema namespace
39+
* @returns Schema information or undefined if namespace is not a schema namespace.
40+
*/
41+
getSchema,
42+
};
43+
44+
/**
45+
* Check if the namespace is defined as a schema.
46+
* @param program Program
47+
* @param namespace Namespace
48+
* @returns Boolean
49+
*/
50+
export function isSchema(program: Program, namespace: Namespace): boolean {
51+
return getSchemaMap(program).has(namespace);
52+
}
53+
54+
/**
55+
* Mark the given namespace as a schema.
56+
* @param program Program
57+
* @param namespace Namespace
58+
* @param details Schema details
59+
*/
60+
export function addSchema(
61+
program: Program,
62+
namespace: Namespace,
63+
details: SchemaDetails = {},
64+
): void {
65+
const schemaMap = getSchemaMap(program);
66+
const existing = schemaMap.get(namespace) ?? {};
67+
setSchema(program, namespace, { ...existing, ...details, type: namespace });
68+
}
69+
70+
export const $schema: DecoratorFunction = (
71+
context: DecoratorContext,
72+
target: Namespace,
73+
options: SchemaDetails = {},
74+
) => {
75+
validateDecoratorUniqueOnNode(context, target, $schema);
76+
addSchema(context.program, target, options);
77+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Type } from "@typespec/compiler";
2+
import { unsafe_useStateMap, unsafe_useStateSet } from "@typespec/compiler/experimental";
3+
4+
export function useStateMap<K extends Type, V>(key: symbol) {
5+
return unsafe_useStateMap<K, V>(key);
6+
}
7+
8+
export function useStateSet<K extends Type>(key: symbol) {
9+
return unsafe_useStateSet<K>(key);
10+
}

packages/graphql/src/schema-emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function createGraphQLEmitter(
1818
const filePath = interpolatePath(options.outputFile, { "schema-name": "schema" });
1919
await emitFile(program, {
2020
path: filePath,
21-
content: "Hello world",
21+
content: "",
2222
newLine: options.newLine,
2323
});
2424
}

packages/graphql/src/tsp-index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { DecoratorImplementations } from "@typespec/compiler";
2+
import { NAMESPACE } from "./lib.js";
3+
import { $schema } from "./lib/schema.js";
4+
5+
export const $decorators: DecoratorImplementations = {
6+
[NAMESPACE]: {
7+
schema: $schema,
8+
},
9+
};

packages/graphql/src/types.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Diagnostic } from "@typespec/compiler";
2+
import type { GraphQLSchema } from "graphql";
3+
import type { Schema } from "./lib/schema.ts";
4+
5+
/**
6+
* A record containing the GraphQL schema corresponding to
7+
* a particular schema definition.
8+
*/
9+
export interface GraphQLSchemaRecord {
10+
/** The declared schema that generated this GraphQL schema */
11+
readonly schema: Schema;
12+
13+
/** The GraphQLSchema */
14+
readonly graphQLSchema: GraphQLSchema;
15+
16+
/** The diagnostics created for this schema */
17+
readonly diagnostics: readonly Diagnostic[];
18+
}

0 commit comments

Comments
 (0)