@@ -5,6 +5,8 @@ import autoBind from 'auto-bind';
55import { pascalCase } from 'change-case-all' ;
66import { DepGraph } from 'dependency-graph' ;
77import {
8+ DefinitionNode ,
9+ DocumentNode ,
810 FragmentDefinitionNode ,
911 FragmentSpreadNode ,
1012 GraphQLSchema ,
@@ -228,8 +230,14 @@ export interface ClientSideBasePluginConfig extends ParsedConfig {
228230 pureMagicComment ?: boolean ;
229231 optimizeDocumentNode : boolean ;
230232 experimentalFragmentVariables ?: boolean ;
233+ unstable_onExecutableDocumentNode ?: Unstable_OnExecutableDocumentNode ;
234+ unstable_omitDefinitions ?: boolean ;
231235}
232236
237+ type ExecutableDocumentNodeMeta = Record < string , unknown > ;
238+
239+ type Unstable_OnExecutableDocumentNode = ( documentNode : DocumentNode ) => void | ExecutableDocumentNodeMeta ;
240+
233241export class ClientSideBaseVisitor <
234242 TRawConfig extends RawClientSideBasePluginConfig = RawClientSideBasePluginConfig ,
235243 TPluginConfig extends ClientSideBasePluginConfig = ClientSideBasePluginConfig
@@ -239,9 +247,13 @@ export class ClientSideBaseVisitor<
239247 protected _additionalImports : string [ ] = [ ] ;
240248 protected _imports = new Set < string > ( ) ;
241249
250+ private _onExecutableDocumentNode ?: Unstable_OnExecutableDocumentNode ;
251+ private _omitDefinitions ?: boolean ;
252+ private _fragments : Map < string , LoadedFragment > ;
253+
242254 constructor (
243255 protected _schema : GraphQLSchema ,
244- protected _fragments : LoadedFragment [ ] ,
256+ fragments : LoadedFragment [ ] ,
245257 rawConfig : TRawConfig ,
246258 additionalConfig : Partial < TPluginConfig > ,
247259 documents ?: Types . DocumentFile [ ]
@@ -271,9 +283,10 @@ export class ClientSideBaseVisitor<
271283 experimentalFragmentVariables : getConfigValue ( rawConfig . experimentalFragmentVariables , false ) ,
272284 ...additionalConfig ,
273285 } as any ) ;
274-
275286 this . _documents = documents ;
276-
287+ this . _onExecutableDocumentNode = ( rawConfig as any ) . unstable_onExecutableDocumentNode ;
288+ this . _omitDefinitions = ( rawConfig as any ) . unstable_omitDefinitions ;
289+ this . _fragments = new Map ( fragments . map ( fragment => [ fragment . name , fragment ] ) ) ;
277290 autoBind ( this ) ;
278291 }
279292
@@ -293,7 +306,7 @@ export class ClientSideBaseVisitor<
293306 names . add ( node . name . value ) ;
294307
295308 if ( withNested ) {
296- const foundFragment = this . _fragments . find ( f => f . name === node . name . value ) ;
309+ const foundFragment = this . _fragments . get ( node . name . value ) ;
297310
298311 if ( foundFragment ) {
299312 const childItems = this . _extractFragments ( foundFragment . node , true ) ;
@@ -312,20 +325,14 @@ export class ClientSideBaseVisitor<
312325 return Array . from ( names ) ;
313326 }
314327
315- protected _transformFragments ( document : FragmentDefinitionNode | OperationDefinitionNode ) : string [ ] {
316- const includeNestedFragments =
317- this . config . documentMode === DocumentMode . documentNode ||
318- ( this . config . dedupeFragments && document . kind === 'OperationDefinition' ) ;
319-
320- return this . _extractFragments ( document , includeNestedFragments ) . map ( document =>
321- this . getFragmentVariableName ( document )
322- ) ;
328+ protected _transformFragments ( fragmentNames : Array < string > ) : string [ ] {
329+ return fragmentNames . map ( document => this . getFragmentVariableName ( document ) ) ;
323330 }
324331
325332 protected _includeFragments ( fragments : string [ ] , nodeKind : 'FragmentDefinition' | 'OperationDefinition' ) : string {
326333 if ( fragments && fragments . length > 0 ) {
327334 if ( this . config . documentMode === DocumentMode . documentNode ) {
328- return this . _fragments
335+ return Array . from ( this . _fragments . values ( ) )
329336 . filter ( f => fragments . includes ( this . getFragmentVariableName ( f . name ) ) )
330337 . map ( fragment => print ( fragment . node ) )
331338 . join ( '\n' ) ;
@@ -346,8 +353,35 @@ export class ClientSideBaseVisitor<
346353 return documentStr ;
347354 }
348355
356+ private _generateDocumentNodeMeta (
357+ definitions : ReadonlyArray < DefinitionNode > ,
358+ fragmentNames : Array < string >
359+ ) : ExecutableDocumentNodeMeta | void {
360+ // If the document does not contain any executable operation, we don't need to hash it
361+ if ( definitions . every ( def => def . kind !== Kind . OPERATION_DEFINITION ) ) {
362+ return undefined ;
363+ }
364+
365+ const allDefinitions = [ ...definitions ] ;
366+
367+ for ( const fragment of fragmentNames ) {
368+ const fragmentRecord = this . _fragments . get ( fragment ) ;
369+ if ( fragmentRecord ) {
370+ allDefinitions . push ( fragmentRecord . node ) ;
371+ }
372+ }
373+
374+ const documentNode : DocumentNode = { kind : Kind . DOCUMENT , definitions : allDefinitions } ;
375+
376+ return this . _onExecutableDocumentNode ( documentNode ) ;
377+ }
378+
349379 protected _gql ( node : FragmentDefinitionNode | OperationDefinitionNode ) : string {
350- const fragments = this . _transformFragments ( node ) ;
380+ const includeNestedFragments =
381+ this . config . documentMode === DocumentMode . documentNode ||
382+ ( this . config . dedupeFragments && node . kind === 'OperationDefinition' ) ;
383+ const fragmentNames = this . _extractFragments ( node , includeNestedFragments ) ;
384+ const fragments = this . _transformFragments ( fragmentNames ) ;
351385
352386 const doc = this . _prepareDocument ( `
353387 ${ print ( node ) . split ( '\\' ) . join ( '\\\\' ) /* Re-escape escaped values in GraphQL syntax */ }
@@ -375,11 +409,39 @@ export class ClientSideBaseVisitor<
375409 ...fragments . map ( name => `...${ name } .definitions` ) ,
376410 ] . join ( ) ;
377411
378- return `{"kind":"${ Kind . DOCUMENT } ","definitions":[${ definitions } ]}` ;
412+ let hashPropertyStr = '' ;
413+
414+ if ( this . _onExecutableDocumentNode ) {
415+ const meta = this . _generateDocumentNodeMeta ( gqlObj . definitions , fragmentNames ) ;
416+ if ( meta ) {
417+ hashPropertyStr = `"__meta__": ${ JSON . stringify ( meta ) } , ` ;
418+ if ( this . _omitDefinitions === true ) {
419+ return `{${ hashPropertyStr } }` ;
420+ }
421+ }
422+ }
423+
424+ return `{${ hashPropertyStr } "kind":"${ Kind . DOCUMENT } ", "definitions":[${ definitions } ]}` ;
425+ }
426+
427+ let meta : ExecutableDocumentNodeMeta | void ;
428+
429+ if ( this . _onExecutableDocumentNode ) {
430+ meta = this . _generateDocumentNodeMeta ( gqlObj . definitions , fragmentNames ) ;
431+ const metaNodePartial = { [ '__meta__' ] : meta } ;
432+
433+ if ( this . _omitDefinitions === true ) {
434+ return JSON . stringify ( metaNodePartial ) ;
435+ }
436+
437+ if ( meta ) {
438+ return JSON . stringify ( { ...metaNodePartial , ...gqlObj } ) ;
439+ }
379440 }
380441
381442 return JSON . stringify ( gqlObj ) ;
382443 }
444+
383445 if ( this . config . documentMode === DocumentMode . string ) {
384446 return '`' + doc + '`' ;
385447 }
@@ -411,7 +473,7 @@ export class ClientSideBaseVisitor<
411473 private get fragmentsGraph ( ) : DepGraph < LoadedFragment > {
412474 const graph = new DepGraph < LoadedFragment > ( { circular : true } ) ;
413475
414- for ( const fragment of this . _fragments ) {
476+ for ( const fragment of this . _fragments . values ( ) ) {
415477 if ( graph . hasNode ( fragment . name ) ) {
416478 const cachedAsString = print ( graph . getNodeData ( fragment . name ) . node ) ;
417479 const asString = print ( fragment . node ) ;
@@ -438,7 +500,7 @@ export class ClientSideBaseVisitor<
438500 }
439501
440502 public get fragments ( ) : string {
441- if ( this . _fragments . length === 0 || this . config . documentMode === DocumentMode . external ) {
503+ if ( this . _fragments . size === 0 || this . config . documentMode === DocumentMode . external ) {
442504 return '' ;
443505 }
444506
0 commit comments