Skip to content

Commit 0ea6238

Browse files
committed
persisted documents as well?
1 parent 62b4246 commit 0ea6238

File tree

5 files changed

+56
-71
lines changed

5 files changed

+56
-71
lines changed

packages/libraries/apollo/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { GraphQLError, type DocumentNode } from 'graphql';
22
import type { ApolloServerPlugin, HTTPGraphQLRequest } from '@apollo/server';
33
import {
44
autoDisposeSymbol,
5-
CDNArtifactFetcherCircuitBreakerConfiguration,
65
createCDNArtifactFetcher,
76
createHive as createHiveClient,
87
HiveClient,
98
HivePluginOptions,
109
isHiveClient,
1110
joinUrl,
1211
Logger,
12+
type CDNArtifactFetcherCircuitBreakerConfiguration,
1313
} from '@graphql-hive/core';
1414
import { version } from './version.js';
1515

packages/libraries/core/src/client/agent.ts

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
11
import CircuitBreaker from '../circuit-breaker/circuit.js';
22
import { version } from '../version.js';
3+
import {
4+
CircuitBreakerConfiguration,
5+
defaultCircuitBreakerConfiguration,
6+
} from './circuit-breaker.js';
37
import { http } from './http-client.js';
48
import type { Logger } from './types.js';
59
import { createHiveLogger } from './utils.js';
610

711
type ReadOnlyResponse = Pick<Response, 'status' | 'text' | 'json' | 'statusText'>;
812

9-
export type AgentCircuitBreakerConfiguration = {
10-
/**
11-
* Percentage after what the circuit breaker should kick in.
12-
* Default: 50
13-
*/
14-
errorThresholdPercentage: number;
15-
/**
16-
* Count of requests before starting evaluating.
17-
* Default: 5
18-
*/
19-
volumeThreshold: number;
20-
/**
21-
* After what time the circuit breaker is attempting to retry sending requests in milliseconds
22-
* Default: 30_000
23-
*/
24-
resetTimeout: number;
25-
};
26-
27-
const defaultCircuitBreakerConfiguration: AgentCircuitBreakerConfiguration = {
28-
errorThresholdPercentage: 50,
29-
volumeThreshold: 10,
30-
resetTimeout: 30_000,
31-
};
32-
3313
export interface AgentOptions {
3414
enabled?: boolean;
3515
name?: string;
@@ -76,7 +56,7 @@ export interface AgentOptions {
7656
* false -> Disable
7757
* object -> use custom configuration see {AgentCircuitBreakerConfiguration}
7858
*/
79-
circuitBreaker?: boolean | AgentCircuitBreakerConfiguration;
59+
circuitBreaker?: boolean | CircuitBreakerConfiguration;
8060
/**
8161
* WHATWG Compatible fetch implementation
8262
* used by the agent to send reports
@@ -101,7 +81,7 @@ export function createAgent<TEvent>(
10181
},
10282
) {
10383
const options: Required<Omit<AgentOptions, 'fetch' | 'debug' | 'logger' | 'circuitBreaker'>> & {
104-
circuitBreaker: AgentCircuitBreakerConfiguration | null;
84+
circuitBreaker: CircuitBreakerConfiguration | null;
10585
} = {
10686
timeout: 30_000,
10787
enabled: true,

packages/libraries/core/src/client/artifacts.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,13 @@
11
import CircuitBreaker from '../circuit-breaker/circuit.js';
22
import { version } from '../version.js';
3+
import {
4+
CircuitBreakerConfiguration,
5+
defaultCircuitBreakerConfiguration,
6+
} from './circuit-breaker.js';
37
import { http } from './http-client.js';
48
import type { Logger } from './types.js';
59
import { createHash, createHiveLogger } from './utils.js';
610

7-
export type CDNArtifactFetcherCircuitBreakerConfiguration = {
8-
/**
9-
* Percentage after what the circuit breaker should kick in.
10-
* Default: 50
11-
*/
12-
errorThresholdPercentage: number;
13-
/**
14-
* Count of requests before starting evaluating.
15-
* Default: 5
16-
*/
17-
volumeThreshold: number;
18-
/**
19-
* After what time the circuit breaker is attempting to retry sending requests in milliseconds
20-
* Default: 30_000
21-
*/
22-
resetTimeout: number;
23-
};
24-
25-
const defaultCircuitBreakerConfiguration: CDNArtifactFetcherCircuitBreakerConfiguration = {
26-
errorThresholdPercentage: 50,
27-
volumeThreshold: 10,
28-
resetTimeout: 30_000,
29-
};
30-
3111
type CreateCDNArtifactFetcherArgs = {
3212
endpoint: string;
3313
accessKey: string;
@@ -36,7 +16,7 @@ type CreateCDNArtifactFetcherArgs = {
3616
name: string;
3717
version: string;
3818
};
39-
circuitBreaker?: CDNArtifactFetcherCircuitBreakerConfiguration;
19+
circuitBreaker?: CircuitBreakerConfiguration;
4020
logger?: Logger;
4121
fetch?: typeof fetch;
4222
};

packages/libraries/core/src/client/persisted-documents.ts

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue.js';
22
import LRU from 'tiny-lru';
3+
import CircuitBreaker from '../circuit-breaker/circuit.js';
4+
import { defaultCircuitBreakerConfiguration } from './circuit-breaker.js';
35
import { http } from './http-client.js';
46
import type { PersistedDocumentsConfiguration } from './types';
57
import type { HiveLogger } from './utils.js';
@@ -8,6 +10,10 @@ type HeadersObject = {
810
get(name: string): string | null;
911
};
1012

13+
function isRequestOk(response: Response) {
14+
return response.status === 200 || response.status === 404;
15+
}
16+
1117
export function createPersistedDocuments(
1218
config: PersistedDocumentsConfiguration & {
1319
logger: HiveLogger;
@@ -33,44 +39,61 @@ export function createPersistedDocuments(
3339
/** if there is already a in-flight request for a document, we re-use it. */
3440
const fetchCache = new Map<string, Promise<string | null>>();
3541

36-
/** Batch load a persisted documents */
37-
function loadPersistedDocument(documentId: string) {
38-
const document = persistedDocumentsCache.get(documentId);
39-
if (document) {
40-
return document;
41-
}
42-
43-
const cdnDocumentId = documentId.replaceAll('~', '/');
44-
45-
const url = config.cdn.endpoint + '/apps/' + cdnDocumentId;
46-
let promise = fetchCache.get(url);
42+
const circuitBreaker = new CircuitBreaker(
43+
async function doFetch(args: { url: string; documentId: string }) {
44+
const signal = circuitBreaker.getSignal();
4745

48-
if (!promise) {
49-
promise = http
50-
.get(url, {
46+
const promise = http
47+
.get(args.url, {
5148
headers: {
5249
'X-Hive-CDN-Key': config.cdn.accessToken,
5350
},
5451
logger: config.logger,
55-
isRequestOk: response => response.status === 200 || response.status === 404,
52+
isRequestOk,
5653
fetchImplementation: config.fetch,
54+
signal,
5755
})
5856
.then(async response => {
5957
if (response.status !== 200) {
6058
return null;
6159
}
6260
const text = await response.text();
63-
persistedDocumentsCache.set(documentId, text);
61+
persistedDocumentsCache.set(args.documentId, text);
6462
return text;
6563
})
6664
.finally(() => {
67-
fetchCache.delete(url);
65+
fetchCache.delete(args.url);
6866
});
67+
fetchCache.set(args.url, promise);
6968

70-
fetchCache.set(url, promise);
69+
return await promise;
70+
},
71+
{
72+
...(config.circuitBreaker ?? defaultCircuitBreakerConfiguration),
73+
timeout: false,
74+
autoRenewAbortController: true,
75+
},
76+
);
77+
78+
/** Batch load a persisted documents */
79+
function loadPersistedDocument(documentId: string) {
80+
const document = persistedDocumentsCache.get(documentId);
81+
if (document) {
82+
return document;
83+
}
84+
85+
const cdnDocumentId = documentId.replaceAll('~', '/');
86+
87+
const url = config.cdn.endpoint + '/apps/' + cdnDocumentId;
88+
const promise = fetchCache.get(url);
89+
if (promise) {
90+
return promise;
7191
}
7292

73-
return promise;
93+
return circuitBreaker.fire({
94+
url,
95+
documentId,
96+
});
7497
}
7598

7699
return {

packages/libraries/core/src/client/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ExecutionArgs } from 'graphql';
22
import type { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue.js';
33
import type { AgentOptions } from './agent.js';
4+
import { CircuitBreakerConfiguration } from './circuit-breaker.js';
45
import type { autoDisposeSymbol, hiveClientSymbol } from './client.js';
56
import type { SchemaReporter } from './reporting.js';
67
import { HiveLogger } from './utils.js';
@@ -318,6 +319,7 @@ export type PersistedDocumentsConfiguration = {
318319
* used for doing HTTP requests.
319320
*/
320321
fetch?: typeof fetch;
322+
circuitBreaker?: CircuitBreakerConfiguration;
321323
};
322324

323325
export type AllowArbitraryDocumentsFunction = (context: {

0 commit comments

Comments
 (0)