Skip to content

Commit 1e3ec84

Browse files
authored
graphiql 5: support externalFragments prop and remove validationRules prop (#4005)
* upd * upd * upd * externalFragments * externalFragments * externalFragments * remove unused types BaseSchemaConfig IDisposable JSONDiagnosticOptions IEvent FilePointer * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * add changeset * cspell * lint * lint * lint
1 parent 16fdd6a commit 1e3ec84

File tree

14 files changed

+178
-209
lines changed

14 files changed

+178
-209
lines changed

.changeset/honest-clouds-brush.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'monaco-graphql': patch
3+
---
4+
5+
remove unused types
6+
7+
- `BaseSchemaConfig`
8+
- `IDisposable`
9+
- `JSONDiagnosticOptions`
10+
- `IEvent`
11+
- `FilePointer`

.changeset/kind-timers-exist.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphiql/react': minor
3+
'graphiql': major
4+
---
5+
6+
support `externalFragments` prop and remove `validationRules` prop

docs/migration/graphiql-5.0.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
- Removed props
1818
- `keyMap`. To use Vim or Emacs keybindings in Monaco, you can use community plugins. Monaco Vim: https://github.com/brijeshb42/monaco-vim. Monaco Emacs: https://github.com/aioutecism/monaco-emacs
1919
- `readOnly`
20+
- `validationRules`. Use custom GraphQL worker, see https://github.com/graphql/graphiql/tree/main/packages/monaco-graphql#custom-webworker-for-passing-non-static-config-to-worker.'

packages/graphiql-react/src/components/provider.tsx

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ export const GraphiQLProvider: FC<GraphiQLProviderProps> = ({
6464
'The `GraphiQLProvider` component requires a `fetcher` function to be passed as prop.',
6565
);
6666
}
67+
// @ts-expect-error -- runtime check
6768
if (props.validationRules) {
6869
throw new Error(
69-
"`validationRules` isn't yet implemented, submit an issue in GitHub",
70+
'`validationRules` prop is removed. Use custom GraphQL worker, see https://github.com/graphql/graphiql/tree/main/packages/monaco-graphql#custom-webworker-for-passing-non-static-config-to-worker.',
7071
);
7172
}
7273
return (
@@ -91,7 +92,6 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
9192
onEditOperationName,
9293
onTabChange,
9394
shouldPersistHeaders = false,
94-
validationRules = [],
9595
onCopyQuery,
9696
onPrettifyQuery = DEFAULT_PRETTIFY_QUERY,
9797

@@ -143,14 +143,42 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
143143
? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true'
144144
: shouldPersistHeaders;
145145

146+
const $externalFragments = (() => {
147+
const map = new Map<string, FragmentDefinitionNode>();
148+
if (Array.isArray(externalFragments)) {
149+
for (const fragment of externalFragments) {
150+
map.set(fragment.name.value, fragment);
151+
}
152+
} else if (typeof externalFragments === 'string') {
153+
visit(parse(externalFragments, {}), {
154+
FragmentDefinition(fragment) {
155+
map.set(fragment.name.value, fragment);
156+
},
157+
});
158+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime check
159+
} else if (externalFragments) {
160+
throw new TypeError(
161+
'The `externalFragments` prop must either be a string that contains the fragment definitions in SDL or a list of FragmentDefinitionNode objects.',
162+
);
163+
}
164+
return map;
165+
})();
166+
146167
const store = create<AllSlices>((...args) => ({
147168
...createEditorSlice({
148169
activeTabIndex,
170+
defaultHeaders,
171+
defaultQuery,
172+
externalFragments: $externalFragments,
149173
initialHeaders: headers ?? defaultHeaders ?? '',
150174
initialQuery:
151175
query ?? (activeTabIndex === 0 ? tabs[0]!.query : null) ?? '',
152176
initialResponse: response,
153177
initialVariables: variables ?? '',
178+
onCopyQuery,
179+
onEditOperationName,
180+
onPrettifyQuery,
181+
onTabChange,
154182
shouldPersistHeaders: $shouldPersistHeaders,
155183
tabs,
156184
})(...args),
@@ -174,48 +202,6 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
174202
// }
175203
// }, [shouldPersistHeaders]);
176204

177-
const $externalFragments = (() => {
178-
const map = new Map<string, FragmentDefinitionNode>();
179-
if (Array.isArray(externalFragments)) {
180-
for (const fragment of externalFragments) {
181-
map.set(fragment.name.value, fragment);
182-
}
183-
} else if (typeof externalFragments === 'string') {
184-
visit(parse(externalFragments, {}), {
185-
FragmentDefinition(fragment) {
186-
map.set(fragment.name.value, fragment);
187-
},
188-
});
189-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime check
190-
} else if (externalFragments) {
191-
throw new Error(
192-
'The `externalFragments` prop must either be a string that contains the fragment definitions in SDL or a list of FragmentDefinitionNode objects.',
193-
);
194-
}
195-
return map;
196-
})();
197-
// Editor sync
198-
useEffect(() => {
199-
storeRef.current.setState({
200-
defaultHeaders,
201-
defaultQuery,
202-
externalFragments: $externalFragments,
203-
onCopyQuery,
204-
onEditOperationName,
205-
onPrettifyQuery,
206-
onTabChange,
207-
validationRules,
208-
});
209-
}, [
210-
$externalFragments,
211-
onTabChange,
212-
onEditOperationName,
213-
defaultQuery,
214-
defaultHeaders,
215-
validationRules,
216-
onCopyQuery,
217-
onPrettifyQuery,
218-
]);
219205
// Execution sync
220206
useEffect(() => {
221207
storeRef.current.setState({

packages/graphiql-react/src/components/query-editor.tsx

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const QueryEditor: FC<QueryEditorProps> = ({
6161
copyQuery,
6262
prettifyEditors,
6363
mergeQuery,
64+
externalFragments,
6465
} = useGraphiQL(
6566
pick(
6667
'initialQuery',
@@ -78,6 +79,7 @@ export const QueryEditor: FC<QueryEditorProps> = ({
7879
'copyQuery',
7980
'prettifyEditors',
8081
'mergeQuery',
82+
'externalFragments',
8183
),
8284
);
8385
const storage = useStorage();
@@ -258,24 +260,27 @@ export const QueryEditor: FC<QueryEditorProps> = ({
258260
if (!schema) {
259261
return;
260262
}
261-
// eslint-disable-next-line no-console
262-
console.log('setting setSchemaConfig');
263263
MONACO_GRAPHQL_API.setSchemaConfig([{ uri: 'schema.graphql', schema }]);
264+
MONACO_GRAPHQL_API.setExternalFragmentDefinitions([
265+
...externalFragments.values(),
266+
]);
264267
if (!referencePlugin) {
265268
return;
266269
}
267270

271+
let currentSchemaReference: SchemaReference | null = null;
272+
268273
const disposables = [
269274
languages.registerDefinitionProvider('graphql', {
270-
provideDefinition(model, p, _token) {
271-
const graphQLPosition = toGraphQLPosition(p);
275+
provideDefinition(model, position, _token) {
276+
const graphQLPosition = toGraphQLPosition(position);
272277
const context = getContextAtPosition(
273278
model.getValue(),
274279
graphQLPosition,
275280
schema,
276281
);
277282
if (!context) {
278-
return null;
283+
return;
279284
}
280285
const { typeInfo, token } = context;
281286
const { kind, step } = token.state;
@@ -294,47 +299,36 @@ export const QueryEditor: FC<QueryEditorProps> = ({
294299
typeInfo.type &&
295300
'description' in typeInfo.type)
296301
) {
297-
return {
298-
uri: model.uri,
299-
range: new Range(p.lineNumber, p.column, p.lineNumber, p.column),
300-
};
302+
currentSchemaReference = { kind, typeInfo };
303+
const { lineNumber, column } = position;
304+
const range = new Range(lineNumber, column, lineNumber, column);
305+
return [{ uri: model.uri, range }];
301306
}
302-
303-
return null;
307+
currentSchemaReference = null;
304308
},
305309
}),
306310
languages.registerReferenceProvider('graphql', {
307-
provideReferences(model, p, _context, _token) {
308-
const graphQLPosition = toGraphQLPosition(p);
309-
const context = getContextAtPosition(
310-
model.getValue(),
311-
graphQLPosition,
312-
schema,
313-
);
314-
if (!context) {
315-
return null;
311+
provideReferences(model, { lineNumber, column }, _context, _token) {
312+
if (!currentSchemaReference) {
313+
return;
316314
}
317-
const { typeInfo, token } = context;
318-
const { kind } = token.state;
319-
if (!kind) {
320-
return null;
321-
}
322-
323315
setVisiblePlugin(referencePlugin);
324-
setSchemaReference({ kind, typeInfo });
325-
onClickReferenceRef.current?.({ kind, typeInfo });
316+
setSchemaReference(currentSchemaReference);
317+
onClickReferenceRef.current?.(currentSchemaReference);
326318

327-
return [
328-
{
329-
uri: model.uri,
330-
range: new Range(p.lineNumber, p.column, p.lineNumber, p.column),
331-
},
332-
];
319+
const range = new Range(lineNumber, column, lineNumber, column);
320+
return [{ uri: model.uri, range }];
333321
},
334322
}),
335323
];
336324
return cleanupDisposables(disposables);
337-
}, [schema, referencePlugin, setSchemaReference, setVisiblePlugin]);
325+
}, [
326+
schema,
327+
referencePlugin,
328+
setSchemaReference,
329+
setVisiblePlugin,
330+
externalFragments,
331+
]);
338332

339333
return (
340334
<div

packages/graphiql-react/src/stores/editor.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { StateCreator } from 'zustand';
22
import type {
33
FragmentDefinitionNode,
4-
ValidationRule,
54
OperationDefinitionNode,
65
DocumentNode,
76
} from 'graphql';
@@ -21,7 +20,6 @@ import {
2120
STORAGE_KEY as STORAGE_KEY_TABS,
2221
} from '../utility/tabs';
2322
import { AllSlices, MonacoEditor } from '../types';
24-
import { DEFAULT_PRETTIFY_QUERY } from '../constants';
2523
import { debounce, formatJSONC } from '../utility';
2624
import { print } from 'graphql';
2725

@@ -120,7 +118,7 @@ export interface EditorSlice extends TabsState {
120118
initialVariables: string;
121119

122120
/**
123-
* A map of fragment definitions using the fragment name as key which are
121+
* A map of fragment definitions using the fragment name as a key which are
124122
* made available to include in the query.
125123
*/
126124
externalFragments: Map<string, FragmentDefinitionNode>;
@@ -141,7 +139,7 @@ export interface EditorSlice extends TabsState {
141139
* - The `query` prop
142140
* - The value persisted in storage
143141
* These default contents will only be used for the first tab. When opening
144-
* more tabs the query editor will start out empty.
142+
* more tabs, the query editor will start out empty.
145143
*/
146144
defaultQuery?: string;
147145

@@ -165,13 +163,7 @@ export interface EditorSlice extends TabsState {
165163
onTabChange?(tabState: TabsState): void;
166164

167165
/**
168-
* A list of custom validation rules that are run in addition to the rules
169-
* provided by the GraphQL spec.
170-
*/
171-
validationRules: ValidationRule[];
172-
173-
/**
174-
* Headers to be set when opening a new tab
166+
* Headers to be set when opening a new tab.
175167
*/
176168
defaultHeaders?: string;
177169

@@ -291,12 +283,7 @@ export interface EditorProps
291283
* @default false
292284
*/
293285
shouldPersistHeaders?: boolean;
294-
/**
295-
* This prop accepts custom validation rules for GraphQL documents that are
296-
* run against the contents of the query editor (in addition to the rules
297-
* that are specified in the GraphQL spec).
298-
*/
299-
validationRules?: ValidationRule[];
286+
300287
/**
301288
* This prop can be used to set the contents of the variables editor. Every
302289
* time this prop changes, the contents of the variables editor are replaced.
@@ -317,6 +304,13 @@ type CreateEditorSlice = (
317304
| 'initialVariables'
318305
| 'initialHeaders'
319306
| 'initialResponse'
307+
| 'onEditOperationName'
308+
| 'externalFragments'
309+
| 'onTabChange'
310+
| 'defaultQuery'
311+
| 'defaultHeaders'
312+
| 'onPrettifyQuery'
313+
| 'onCopyQuery'
320314
>,
321315
) => StateCreator<AllSlices, [], [], EditorSlice>;
322316

@@ -364,7 +358,6 @@ export const createEditorSlice: CreateEditorSlice = initial => (set, get) => {
364358

365359
return {
366360
...initial,
367-
368361
addTab() {
369362
set(current => {
370363
const { defaultQuery, defaultHeaders, onTabChange, storeTabs } = get();
@@ -476,13 +469,6 @@ export const createEditorSlice: CreateEditorSlice = initial => (set, get) => {
476469
set({ shouldPersistHeaders: persist });
477470
storage.set(PERSIST_HEADERS_STORAGE_KEY, persist.toString());
478471
},
479-
onEditOperationName: undefined,
480-
externalFragments: null!,
481-
onTabChange: undefined,
482-
defaultQuery: undefined,
483-
defaultHeaders: undefined,
484-
validationRules: null!,
485-
onPrettifyQuery: DEFAULT_PRETTIFY_QUERY,
486472
storeTabs({ tabs, activeTabIndex }) {
487473
const { storage } = storageStore.getState();
488474
const { shouldPersistHeaders } = get();

packages/graphiql-react/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import {
77
PluginSlice,
88
SchemaSlice,
99
} from './stores';
10+
import { RuleKind } from 'graphql-language-service';
1011

1112
export type EditorProps = ComponentPropsWithoutRef<'div'>;
1213

1314
export interface SchemaReference {
14-
kind: string;
15+
kind: RuleKind;
1516
typeInfo: AllTypeInfo;
1617
}
1718

0 commit comments

Comments
 (0)