Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(client): fix isolated data proxy issues [DPGA, 3] #13599

Merged
merged 9 commits into from Jun 7, 2022
51 changes: 13 additions & 38 deletions packages/engine-core/src/data-proxy/DataProxyEngine.ts
Expand Up @@ -18,7 +18,6 @@ import { request } from './utils/request'
const MAX_RETRIES = 10

export class DataProxyEngine extends Engine {
private pushPromise: Promise<void>
private inlineSchema: string
private inlineSchemaHash: string
private inlineDatasources: any
Expand Down Expand Up @@ -48,24 +47,6 @@ export class DataProxyEngine extends Engine {
this.remoteClientVersion = getClientVersion(this.config)
this.headers = { Authorization: `Bearer ${apiKey}` }
this.host = host

millsp marked this conversation as resolved.
Show resolved Hide resolved
// hack for Cloudflare
// That's because we instantiate the client outside of the request handler. This essentially prevents immediate execution of the promise.
// Removing this will produce the following error
// [Error] Some functionality, such as asynchronous I/O, timeouts, and generating random values, can only be performed while handling a request.
const promise = Promise.resolve()
this.pushPromise = promise.then(() => this.pushSchema())
}

private async pushSchema() {
const response = await request(this.url('schema'), {
method: 'GET',
headers: this.headers,
})

if (response.status === 404) {
await this.uploadSchema()
}
}

version() {
Expand Down Expand Up @@ -108,6 +89,7 @@ export class DataProxyEngine extends Engine {
method: 'PUT',
headers: this.headers,
body: this.inlineSchema,
clientVersion: this.clientVersion,
})

const err = await responseToError(response, this.clientVersion)
Expand Down Expand Up @@ -144,8 +126,6 @@ export class DataProxyEngine extends Engine {
}

private async requestInternal<T>(body: Record<string, any>, headers: Record<string, string>, attempt: number) {
await this.pushPromise

try {
this.logEmitter.emit('info', {
message: `Calling ${this.url('graphql')} (n=${attempt})`,
Expand All @@ -155,21 +135,20 @@ export class DataProxyEngine extends Engine {
method: 'POST',
headers: { ...headers, ...this.headers },
body: JSON.stringify(body),
clientVersion: this.clientVersion,
})

const err = await responseToError(response, this.clientVersion)
const e = await responseToError(response, this.clientVersion)

if (err instanceof SchemaMissingError) {
if (e instanceof SchemaMissingError) {
await this.uploadSchema()
throw new ForcedRetryError({
clientVersion: this.clientVersion,
cause: err,
cause: e,
})
}

if (err) {
throw err
}
if (e) throw e

const data = await response.json()

Expand All @@ -180,22 +159,18 @@ export class DataProxyEngine extends Engine {
}

return data
} catch (err) {
} catch (e) {
this.logEmitter.emit('error', {
message: `Error while querying: ${err.message ?? '(unknown)'}`,
message: `Error while querying: ${e.message ?? '(unknown)'}`,
})

if (!(err instanceof DataProxyError)) {
throw err
}
if (!err.isRetryable) {
throw err
}
if (!(e instanceof DataProxyError)) throw e
if (!e.isRetryable) throw e
if (attempt >= MAX_RETRIES) {
if (err instanceof ForcedRetryError) {
throw err.cause
if (e instanceof ForcedRetryError) {
throw e.cause
} else {
throw err
throw e
}
}

Expand Down
14 changes: 14 additions & 0 deletions packages/engine-core/src/data-proxy/errors/NetworkError.ts
@@ -0,0 +1,14 @@
import type { DataProxyErrorInfo } from './DataProxyError'
import { DataProxyError } from './DataProxyError'
import { setRetryable } from './utils/setRetryable'

export interface NetworkErrorInfo extends DataProxyErrorInfo {}

export class NetworkError extends DataProxyError {
public name = 'NetworkError'
public code = 'P5010'

constructor(info: NetworkErrorInfo) {
super('Cannot fetch data from service', setRetryable(info, true))
millsp marked this conversation as resolved.
Show resolved Hide resolved
}
}
22 changes: 15 additions & 7 deletions packages/engine-core/src/data-proxy/utils/request.ts
@@ -1,8 +1,9 @@
import { IncomingMessage } from 'http'
import type { IncomingMessage } from 'http'
import type Https from 'https'
import type { RequestInit, Response } from 'node-fetch'
import { O } from 'ts-toolbelt'
import type { O } from 'ts-toolbelt'

import { NetworkError } from '../errors/NetworkError'
import { getJSRuntimeName } from './getJSRuntimeName'

// our implementation handles less
Expand All @@ -18,13 +19,20 @@ declare let fetch: typeof nodeFetch
* @param options
* @returns
*/
export async function request(url: string, options: RequestOptions = {}): Promise<RequestResponse> {
export async function request(
url: string,
options: RequestOptions & { clientVersion: string },
): Promise<RequestResponse> {
const jsRuntimeName = getJSRuntimeName()

if (jsRuntimeName === 'browser') {
return fetch(url, options)
} else {
return nodeFetch(url, options)
try {
if (jsRuntimeName === 'browser') {
return await fetch(url, options)
} else {
return await nodeFetch(url, options)
}
} catch (e) {
throw new NetworkError({ clientVersion: options.clientVersion })
millsp marked this conversation as resolved.
Show resolved Hide resolved
millsp marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down