Skip to content

Commit

Permalink
Move createSupergraphSDLFetcher to /core package (#5367)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela authored Aug 13, 2024
1 parent 1eb06c2 commit a896642
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 215 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-snails-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-hive/yoga': minor
---

Add createSupergraphSDLFetcher to /yoga
6 changes: 6 additions & 0 deletions .changeset/tough-rice-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-hive/apollo': patch
'@graphql-hive/core': patch
---

Move createSupergraphSDLFetcher to @graphql-hive/core package
82 changes: 9 additions & 73 deletions packages/libraries/apollo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,85 +2,21 @@ import { GraphQLError, type DocumentNode } from 'graphql';
import type { ApolloServerPlugin, HTTPGraphQLRequest } from '@apollo/server';
import {
autoDisposeSymbol,
createHash,
createHive as createHiveClient,
createSupergraphSDLFetcher,
HiveClient,
HivePluginOptions,
http,
isHiveClient,
joinUrl,
Logger,
SupergraphSDLFetcherOptions,
} from '@graphql-hive/core';
import { version } from './version.js';

export { atLeastOnceSampler, createSchemaFetcher, createServicesFetcher } from '@graphql-hive/core';

export interface SupergraphSDLFetcherOptions {
endpoint: string;
key: string;
logger?: Logger;
}

export function createSupergraphSDLFetcher(options: SupergraphSDLFetcherOptions) {
let cacheETag: string | null = null;
let cached: {
id: string;
supergraphSdl: string;
} | null = null;
const endpoint = options.endpoint.endsWith('/supergraph')
? options.endpoint
: joinUrl(options.endpoint, 'supergraph');

return function supergraphSDLFetcher(): Promise<{ id: string; supergraphSdl: string }> {
const headers: {
[key: string]: string;
} = {
'X-Hive-CDN-Key': options.key,
'User-Agent': `hive-client/${version}`,
};

if (cacheETag) {
headers['If-None-Match'] = cacheETag;
}

return http
.get(endpoint, {
headers,
isRequestOk: response => response.status === 304 || response.ok,
retry: {
retries: 10,
maxTimeout: 200,
minTimeout: 1,
},
logger: options.logger,
})
.then(async response => {
if (response.ok) {
const supergraphSdl = await response.text();
const result = {
id: await createHash('SHA-256').update(supergraphSdl).digest('base64'),
supergraphSdl,
};

const etag = response.headers.get('etag');
if (etag) {
cached = result;
cacheETag = etag;
}

return result;
}

if (response.status === 304 && cached !== null) {
return cached;
}

throw new Error(
`Failed to GET ${endpoint}, received: ${response.status} ${response.statusText ?? 'Internal Server Error'}`,
);
});
};
}
export {
atLeastOnceSampler,
createSchemaFetcher,
createServicesFetcher,
createSupergraphSDLFetcher,
} from '@graphql-hive/core';
export type { SupergraphSDLFetcherOptions } from '@graphql-hive/core';

export function createSupergraphManager(
options: { pollIntervalInMs?: number } & SupergraphSDLFetcherOptions,
Expand Down
139 changes: 1 addition & 138 deletions packages/libraries/apollo/tests/apollo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHt
import { startStandaloneServer } from '@apollo/server/standalone';
import { http } from '@graphql-hive/core';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { createHive, createSupergraphSDLFetcher, useHive } from '../src';
import { version } from '../src/version';
import { createHive, useHive } from '../src';

function createLogger() {
return {
Expand Down Expand Up @@ -280,142 +279,6 @@ test('send usage reports in intervals', async () => {
clean();
}, 1_000);

describe('supergraph SDL fetcher', async () => {
test('createSupergraphSDLFetcher without ETag', async () => {
const supergraphSdl = 'type SuperQuery { sdl: String }';
const newSupergraphSdl = 'type NewSuperQuery { sdl: String }';
const key = 'secret-key';
nock('http://localhost')
.get('/supergraph')
.once()
.matchHeader('X-Hive-CDN-Key', key)
.reply(200, supergraphSdl, {
ETag: 'first',
})
.get('/supergraph')
.once()
.matchHeader('X-Hive-CDN-Key', key)
.matchHeader('User-Agent', `hive-client/${version}`)
.reply(200, newSupergraphSdl, {
ETag: 'second',
});

const fetcher = createSupergraphSDLFetcher({
endpoint: 'http://localhost',
key,
});

const result = await fetcher();

expect(result.id).toBeDefined();
expect(result.supergraphSdl).toEqual(supergraphSdl);

const secondResult = await fetcher();

expect(secondResult.id).toBeDefined();
expect(secondResult.supergraphSdl).toEqual(newSupergraphSdl);
});

test('createSupergraphSDLFetcher', async () => {
const supergraphSdl = 'type SuperQuery { sdl: String }';
const newSupergraphSdl = 'type Query { sdl: String }';
const key = 'secret-key';
nock('http://localhost')
.get('/supergraph')
.once()
.matchHeader('X-Hive-CDN-Key', key)
.reply(200, supergraphSdl, {
ETag: 'first',
})
.get('/supergraph')
.once()
.matchHeader('X-Hive-CDN-Key', key)
.matchHeader('If-None-Match', 'first')
.reply(304)
.get('/supergraph')
.matchHeader('X-Hive-CDN-Key', key)
.matchHeader('User-Agent', `hive-client/${version}`)
.matchHeader('If-None-Match', 'first')
.reply(200, newSupergraphSdl, {
ETag: 'changed',
});

const fetcher = createSupergraphSDLFetcher({
endpoint: 'http://localhost',
key,
});

const result = await fetcher();

expect(result.id).toBeDefined();
expect(result.supergraphSdl).toEqual(supergraphSdl);

const cachedResult = await fetcher();

expect(cachedResult.id).toBeDefined();
expect(cachedResult.supergraphSdl).toEqual(supergraphSdl);

const staleResult = await fetcher();

expect(staleResult.id).toBeDefined();
expect(staleResult.supergraphSdl).toEqual(newSupergraphSdl);
});

test('createSupergraphSDLFetcher retry with unexpected status code (nRetryCount=10)', async () => {
const supergraphSdl = 'type SuperQuery { sdl: String }';
const key = 'secret-key';
nock('http://localhost')
.get('/supergraph')
.times(10)
.reply(500)
.get('/supergraph')
.once()
.matchHeader('X-Hive-CDN-Key', key)
.reply(200, supergraphSdl, {
ETag: 'first',
});

const fetcher = createSupergraphSDLFetcher({
endpoint: 'http://localhost',
key,
});

const result = await fetcher();

expect(result.id).toBeDefined();
expect(result.supergraphSdl).toEqual(supergraphSdl);
});

test('createSupergraphSDLFetcher retry with unexpected status code (nRetryCount=11)', async () => {
expect.assertions(1);
const supergraphSdl = 'type SuperQuery { sdl: String }';
const key = 'secret-key';
nock('http://localhost')
.get('/supergraph')
.times(11)
.reply(500)
.get('/supergraph')
.once()
.matchHeader('X-Hive-CDN-Key', key)
.reply(200, supergraphSdl, {
ETag: 'first',
});

const fetcher = createSupergraphSDLFetcher({
endpoint: 'http://localhost',
key,
});

try {
await fetcher();
} catch (err) {
expect(err).toMatchInlineSnapshot(
`[Error: GET http://localhost/supergraph failed with status 500.]`,
);
}
});
});

describe('built-in HTTP usage reporting', async () => {
test('successful query operation is reported', async () => {
const graphqlScope = nock('http://localhost')
Expand Down
71 changes: 71 additions & 0 deletions packages/libraries/core/src/client/supergraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { version } from '../version.js';
import { http } from './http-client.js';
import type { Logger } from './types.js';
import { createHash, joinUrl } from './utils.js';

export interface SupergraphSDLFetcherOptions {
endpoint: string;
key: string;
logger?: Logger;
}

export function createSupergraphSDLFetcher(options: SupergraphSDLFetcherOptions) {
let cacheETag: string | null = null;
let cached: {
id: string;
supergraphSdl: string;
} | null = null;
const endpoint = options.endpoint.endsWith('/supergraph')
? options.endpoint
: joinUrl(options.endpoint, 'supergraph');

return function supergraphSDLFetcher(): Promise<{ id: string; supergraphSdl: string }> {
const headers: {
[key: string]: string;
} = {
'X-Hive-CDN-Key': options.key,
'User-Agent': `hive-client/${version}`,
};

if (cacheETag) {
headers['If-None-Match'] = cacheETag;
}

return http
.get(endpoint, {
headers,
isRequestOk: response => response.status === 304 || response.ok,
retry: {
retries: 10,
maxTimeout: 200,
minTimeout: 1,
},
logger: options.logger,
})
.then(async response => {
if (response.ok) {
const supergraphSdl = await response.text();
const result = {
id: await createHash('SHA-256').update(supergraphSdl).digest('base64'),
supergraphSdl,
};

const etag = response.headers.get('etag');
if (etag) {
cached = result;
cacheETag = etag;
}

return result;
}

if (response.status === 304 && cached !== null) {
return cached;
}

throw new Error(
`Failed to GET ${endpoint}, received: ${response.status} ${response.statusText ?? 'Internal Server Error'}`,
);
});
};
}
10 changes: 8 additions & 2 deletions packages/libraries/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
export * from './normalize/operation.js';
export type { HivePluginOptions, HiveClient, CollectUsageCallback } from './client/types.js';
export type {
HivePluginOptions,
HiveClient,
CollectUsageCallback,
Logger,
} from './client/types.js';
export { createSchemaFetcher, createServicesFetcher } from './client/gateways.js';
export { createHive, autoDisposeSymbol } from './client/client.js';
export { atLeastOnceSampler } from './client/samplers.js';
export { isHiveClient, isAsyncIterable, createHash, joinUrl } from './client/utils.js';
export { http, URL } from './client/http-client.js';
export type { Logger } from './client/types.js';
export { createSupergraphSDLFetcher } from './client/supergraph.js';
export type { SupergraphSDLFetcherOptions } from './client/supergraph.js';
2 changes: 1 addition & 1 deletion packages/libraries/core/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const version = '0.5.0';
export const version = '0.7.0';
Loading

0 comments on commit a896642

Please sign in to comment.