11import { DocumentNode , GraphQLError , parse , print , SourceLocation } from 'graphql' ;
22import { z } from 'zod' ;
33import type { Logger } from '@graphql-hive/logger' ;
4+ import { generateChangeHash } from '@graphql-inspector/compare-changes' ;
45import type { Change } from '@graphql-inspector/core' ;
56import { errors , patch } from '@graphql-inspector/patch' ;
67import type { Project , SchemaObject } from '@hive/api' ;
78import type { ComposeAndValidateResult } from '@hive/api/shared/entities' ;
8- import { PostgresDatabasePool , psql } from '@hive/postgres' ;
9+ import { CommonQueryMethods , PostgresDatabasePool , psql } from '@hive/postgres' ;
910import type { ContractsInputType , SchemaBuilderApi } from '@hive/schema' ;
1011import { decodeCreatedAtAndUUIDIdBasedCursor , HiveSchemaChangeModel } from '@hive/storage' ;
1112import { createTRPCProxyClient , httpLink } from '@trpc/client' ;
@@ -32,6 +33,8 @@ const SchemaProposalChangesModel = z.object({
3233 createdAt : z . string ( ) ,
3334} ) ;
3435
36+ type SchemaProposalChangesModelType = z . TypeOf < typeof SchemaProposalChangesModel > ;
37+
3538function createExternalConfig ( config : Project [ 'externalComposition' ] ) {
3639 // : ExternalCompositionConfig {
3740 if ( config && config . enabled ) {
@@ -270,12 +273,25 @@ export function schemaProvider(providerConfig: SchemaProviderConfig) {
270273 return baseSchema ;
271274 } ,
272275
273- async proposedSchemas ( args : {
274- targetId : string ;
275- proposalId : string ;
276- cursor ?: string | null ;
277- pool : PostgresDatabasePool ;
278- } ) {
276+ /**
277+ * Loops through all schema checks that are part of the schema proposal. This uses pagination
278+ * to avoid loading too much at once
279+ *
280+ * This is hard capped at 2_000 subgraphs for safety.
281+ **/
282+ async forEachProposalCheck (
283+ args : {
284+ targetId : string ;
285+ proposalId : string ;
286+ cursor ?: string | null ;
287+ pool : PostgresDatabasePool ;
288+ } ,
289+ callback : (
290+ change : Omit < SchemaProposalChangesModelType , 'schemaProposalChanges' > & {
291+ schemaProposalChanges : Change [ ] ;
292+ } ,
293+ ) => void | Promise < void > ,
294+ ) {
279295 const now = new Date ( ) . toISOString ( ) ;
280296 let cursor : {
281297 createdAt : string ;
@@ -288,11 +304,6 @@ export function schemaProvider(providerConfig: SchemaProviderConfig) {
288304
289305 // fetch all latest schemas. Support up to 2_000 subgraphs.
290306 const maxLoops = 100 ;
291- const services = await this . latestComposableSchemas ( {
292- targetId : args . targetId ,
293- pool : args . pool ,
294- } ) ;
295-
296307 let nextCursor = cursor ;
297308 // collect changes in paginated requests to avoid stalling the db
298309 let i = 0 ;
@@ -350,48 +361,96 @@ export function schemaProvider(providerConfig: SchemaProviderConfig) {
350361 LIMIT 20
351362 ` ) ;
352363
353- const changes = result . map ( row => {
354- const value = SchemaProposalChangesModel . parse ( row ) ;
364+ const checks = result . map ( row => {
365+ const check = SchemaProposalChangesModel . parse ( row ) ;
355366 return {
356- ...value ,
357- schemaProposalChanges : value . schemaProposalChanges . map ( c => {
358- const change : Change < any > = {
359- ...c ,
367+ ...check ,
368+ schemaProposalChanges : check . schemaProposalChanges . map ( ( c ) : Change < any > => {
369+ return {
370+ message : c . message ,
371+ meta : c . meta ,
372+ type : c . type ,
360373 path : c . path ?? undefined ,
361374 criticality : {
362375 level : c . criticality ,
363376 } ,
364- } ;
365- return change ;
377+ } satisfies Change < any > ;
366378 } ) ,
367379 } ;
368380 } ) ;
369381
370- if ( changes . length === 20 ) {
382+ for ( const check of checks ) {
383+ await callback ( check ) ;
384+ }
385+
386+ if ( checks . length === 20 ) {
371387 nextCursor = {
372388 // Keep the created at because we want the same set of checks when joining on the "latest".
373- createdAt : nextCursor ?. createdAt ?? changes [ 0 ] ?. createdAt ?? now ,
374- id : changes [ changes . length - 1 ] . id ,
389+ createdAt : nextCursor ?. createdAt ?? checks [ 0 ] ?. createdAt ?? now ,
390+ id : checks [ checks . length - 1 ] . id ,
375391 } ;
376392 } else {
377393 nextCursor = null ;
378394 }
395+ } while ( nextCursor && ++ i < maxLoops ) ;
396+ } ,
379397
380- for ( const change of changes ) {
381- const service = services . find ( s => change . serviceName === s . serviceName ) ;
382- if ( service ) {
383- const ast = parse ( service . sdl , { noLocation : true } ) ;
384- service . sdl = print (
385- patch ( ast , change . schemaProposalChanges , { onError : errors . looseErrorHandler } ) ,
386- ) ;
387- if ( change . serviceUrl ) {
388- service . serviceUrl = change . serviceUrl ;
389- }
398+ async proposedSchemas ( args : {
399+ targetId : string ;
400+ proposalId : string ;
401+ cursor ?: string | null ;
402+ pool : PostgresDatabasePool ;
403+ } ) {
404+ const services = await this . latestComposableSchemas ( {
405+ targetId : args . targetId ,
406+ pool : args . pool ,
407+ } ) ;
408+
409+ await this . forEachProposalCheck ( args , change => {
410+ const service = services . find ( s => change . serviceName === s . serviceName ) ;
411+ if ( service ) {
412+ const ast = parse ( service . sdl , { noLocation : true } ) ;
413+ service . sdl = print (
414+ patch ( ast , change . schemaProposalChanges , { onError : errors . looseErrorHandler } ) ,
415+ ) ;
416+ if ( change . serviceUrl ) {
417+ service . serviceUrl = change . serviceUrl ;
390418 }
391419 }
392- } while ( nextCursor && ++ i < maxLoops ) ;
420+ } ) ;
393421
394422 return services ;
395423 } ,
424+
425+ async approveChanges (
426+ conn : CommonQueryMethods ,
427+ records : {
428+ change : Change ;
429+ proposalId : string ;
430+ service ?: string ;
431+ targetId : string ;
432+ } [ ] ,
433+ ) {
434+ const values = records . map (
435+ r => psql `(
436+ ${ generateChangeHash ( r . change ) }
437+ ,${ JSON . stringify ( r . change ) }
438+ ,${ r . proposalId }
439+ ,${ r . service ?? null }
440+ ,${ r . targetId }
441+ )` ,
442+ ) ;
443+
444+ await conn . query ( psql `
445+ INSERT INTO "proposal_approved_changes" (
446+ hash
447+ ,change
448+ ,proposal_id
449+ ,service
450+ ,target_id
451+ )
452+ VALUES ${ psql . join ( values , psql . fragment `,` ) }
453+ ` ) ;
454+ } ,
396455 } ;
397456}
0 commit comments