Skip to content

Commit 2d89b4d

Browse files
committed
RFC: SchemaExtension
This adds support for graphql/graphql-spec#428 spec proposal. So far this just adds language support and updates validation rules to be aware of this new ast node. I'll follow up with support in `extendSchema()` and tests.
1 parent d708da2 commit 2d89b4d

11 files changed

+77
-7
lines changed

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ export type {
245245
EnumTypeDefinitionNode,
246246
EnumValueDefinitionNode,
247247
InputObjectTypeDefinitionNode,
248+
TypeSystemExtensionNode,
248249
TypeExtensionNode,
250+
SchemaExtensionNode,
249251
ObjectTypeExtensionNode,
250252
DirectiveDefinitionNode,
251253
KindEnum,

src/language/__tests__/schema-kitchen-sink.graphql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,9 @@ directive @include2(if: Boolean!) on
123123
| FIELD
124124
| FRAGMENT_SPREAD
125125
| INLINE_FRAGMENT
126+
127+
extend schema @onSchema
128+
129+
extend schema @onSchema {
130+
subscription: SubscriptionType
131+
}

src/language/__tests__/schema-printer-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ describe('Printer: SDL document', () => {
161161
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
162162
163163
directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
164+
165+
extend schema @onSchema
166+
167+
extend schema @onSchema {
168+
subscription: SubscriptionType
169+
}
164170
`);
165171
});
166172
});

src/language/ast.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export type ASTNode =
124124
| EnumTypeDefinitionNode
125125
| EnumValueDefinitionNode
126126
| InputObjectTypeDefinitionNode
127+
| SchemaExtensionNode
127128
| ScalarTypeExtensionNode
128129
| ObjectTypeExtensionNode
129130
| InterfaceTypeExtensionNode
@@ -171,6 +172,7 @@ export type ASTKindToNode = {
171172
EnumTypeDefinition: EnumTypeDefinitionNode,
172173
EnumValueDefinition: EnumValueDefinitionNode,
173174
InputObjectTypeDefinition: InputObjectTypeDefinitionNode,
175+
SchemaExtension: SchemaExtensionNode,
174176
ScalarTypeExtension: ScalarTypeExtensionNode,
175177
ObjectTypeExtension: ObjectTypeExtensionNode,
176178
InterfaceTypeExtension: InterfaceTypeExtensionNode,
@@ -388,7 +390,7 @@ export type NonNullTypeNode = {
388390
export type TypeSystemDefinitionNode =
389391
| SchemaDefinitionNode
390392
| TypeDefinitionNode
391-
| TypeExtensionNode
393+
| TypeSystemExtensionNode
392394
| DirectiveDefinitionNode;
393395

394396
export type SchemaDefinitionNode = {
@@ -499,6 +501,8 @@ export type InputObjectTypeDefinitionNode = {
499501

500502
// Type Extensions
501503

504+
export type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode;
505+
502506
export type TypeExtensionNode =
503507
| ScalarTypeExtensionNode
504508
| ObjectTypeExtensionNode
@@ -507,6 +511,13 @@ export type TypeExtensionNode =
507511
| EnumTypeExtensionNode
508512
| InputObjectTypeExtensionNode;
509513

514+
export type SchemaExtensionNode = {
515+
+kind: 'SchemaExtension',
516+
+loc?: Location,
517+
+directives: $ReadOnlyArray<DirectiveNode>,
518+
+operationTypes: $ReadOnlyArray<OperationTypeDefinitionNode>,
519+
};
520+
510521
export type ScalarTypeExtensionNode = {
511522
+kind: 'ScalarTypeExtension',
512523
+loc?: Location,

src/language/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ export type {
7676
EnumTypeDefinitionNode,
7777
EnumValueDefinitionNode,
7878
InputObjectTypeDefinitionNode,
79+
TypeSystemExtensionNode,
7980
TypeExtensionNode,
81+
SchemaExtensionNode,
8082
ObjectTypeExtensionNode,
8183
DirectiveDefinitionNode,
8284
} from './ast';

src/language/kinds.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export const Kind = Object.freeze({
6262
ENUM_VALUE_DEFINITION: 'EnumValueDefinition',
6363
INPUT_OBJECT_TYPE_DEFINITION: 'InputObjectTypeDefinition',
6464

65-
// Type Extensions
65+
// Type System Extensions
66+
SCHEMA_EXTENSION: 'SchemaExtension',
6667
SCALAR_TYPE_EXTENSION: 'ScalarTypeExtension',
6768
OBJECT_TYPE_EXTENSION: 'ObjectTypeExtension',
6869
INTERFACE_TYPE_EXTENSION: 'InterfaceTypeExtension',

src/language/parser.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ import type {
5252
EnumTypeDefinitionNode,
5353
EnumValueDefinitionNode,
5454
InputObjectTypeDefinitionNode,
55-
TypeExtensionNode,
55+
TypeSystemExtensionNode,
56+
SchemaExtensionNode,
5657
ScalarTypeExtensionNode,
5758
ObjectTypeExtensionNode,
5859
InterfaceTypeExtensionNode,
@@ -751,9 +752,9 @@ export function parseNamedType(lexer: Lexer<*>): NamedTypeNode {
751752

752753
/**
753754
* TypeSystemDefinition :
755+
* - TypeSystemExtension
754756
* - SchemaDefinition
755757
* - TypeDefinition
756-
* - TypeExtension
757758
* - DirectiveDefinition
758759
*
759760
* TypeDefinition :
@@ -785,7 +786,7 @@ function parseTypeSystemDefinition(lexer: Lexer<*>): TypeSystemDefinitionNode {
785786
case 'input':
786787
return parseInputObjectTypeDefinition(lexer);
787788
case 'extend':
788-
return parseTypeExtension(lexer);
789+
return parseTypeSystemExtension(lexer);
789790
case 'directive':
790791
return parseDirectiveDefinition(lexer);
791792
}
@@ -1141,6 +1142,10 @@ function parseInputFieldsDefinition(
11411142
}
11421143

11431144
/**
1145+
* TypeSystemExtension :
1146+
* - SchemaExtension
1147+
* - TypeExtension
1148+
*
11441149
* TypeExtension :
11451150
* - ScalarTypeExtension
11461151
* - ObjectTypeExtension
@@ -1149,11 +1154,13 @@ function parseInputFieldsDefinition(
11491154
* - EnumTypeExtension
11501155
* - InputObjectTypeDefinition
11511156
*/
1152-
function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode {
1157+
function parseTypeSystemExtension(lexer: Lexer<*>): TypeSystemExtensionNode {
11531158
const keywordToken = lexer.lookahead();
11541159

11551160
if (keywordToken.kind === TokenKind.NAME) {
11561161
switch (keywordToken.value) {
1162+
case 'schema':
1163+
return parseSchemaExtension(lexer);
11571164
case 'scalar':
11581165
return parseScalarTypeExtension(lexer);
11591166
case 'type':
@@ -1172,6 +1179,35 @@ function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode {
11721179
throw unexpected(lexer, keywordToken);
11731180
}
11741181

1182+
/**
1183+
* SchemaExtension :
1184+
* - extend schema Directives[Const]? { OperationTypeDefinition+ }
1185+
* - extend schema Directives[Const]
1186+
*/
1187+
function parseSchemaExtension(lexer: Lexer<*>): SchemaExtensionNode {
1188+
const start = lexer.token;
1189+
expectKeyword(lexer, 'extend');
1190+
expectKeyword(lexer, 'schema');
1191+
const directives = parseDirectives(lexer, true);
1192+
const operationTypes = peek(lexer, TokenKind.BRACE_L)
1193+
? many(
1194+
lexer,
1195+
TokenKind.BRACE_L,
1196+
parseOperationTypeDefinition,
1197+
TokenKind.BRACE_R,
1198+
)
1199+
: [];
1200+
if (directives.length === 0 && operationTypes.length === 0) {
1201+
throw unexpected(lexer);
1202+
}
1203+
return {
1204+
kind: Kind.SCHEMA_EXTENSION,
1205+
directives,
1206+
operationTypes,
1207+
loc: loc(lexer, start),
1208+
};
1209+
}
1210+
11751211
/**
11761212
* ScalarTypeExtension :
11771213
* - extend scalar Name Directives[Const]

src/language/printer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ const printDocASTReducer = {
175175
join(['input', name, join(directives, ' '), block(fields)], ' '),
176176
),
177177

178+
SchemaExtension: ({ directives, operationTypes }) =>
179+
join(['extend schema', join(directives, ' '), block(operationTypes)], ' '),
180+
178181
ScalarTypeExtension: ({ name, directives }) =>
179182
join(['extend scalar', name, join(directives, ' ')], ' '),
180183

src/language/visitor.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export const QueryDocumentKeys = {
9999
NonNullType: ['type'],
100100

101101
SchemaDefinition: ['directives', 'operationTypes'],
102+
SchemaExtension: ['directives', 'operationTypes'],
102103
OperationTypeDefinition: ['type'],
103104

104105
ScalarTypeDefinition: ['description', 'name', 'directives'],

src/validation/rules/ExecutableDefinitions.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export function ExecutableDefinitions(context: ValidationContext): ASTVisitor {
3333
context.reportError(
3434
new GraphQLError(
3535
nonExecutableDefinitionMessage(
36-
definition.kind === Kind.SCHEMA_DEFINITION
36+
definition.kind === Kind.SCHEMA_DEFINITION ||
37+
definition.kind === Kind.SCHEMA_EXTENSION
3738
? 'schema'
3839
: definition.name.value,
3940
),

src/validation/rules/KnownDirectives.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ function getDirectiveLocationForASTPath(ancestors) {
8383
case Kind.FRAGMENT_DEFINITION:
8484
return DirectiveLocation.FRAGMENT_DEFINITION;
8585
case Kind.SCHEMA_DEFINITION:
86+
case Kind.SCHEMA_EXTENSION:
8687
return DirectiveLocation.SCHEMA;
8788
case Kind.SCALAR_TYPE_DEFINITION:
8889
case Kind.SCALAR_TYPE_EXTENSION:

0 commit comments

Comments
 (0)