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

Add debug dispatches for fetch + fallback exchanges #659

Merged
merged 4 commits into from Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/client.ts
Expand Up @@ -22,7 +22,7 @@ import {
import {
composeExchanges,
defaultExchanges,
fallbackExchangeIO,
fallbackExchange,
} from './exchanges';

import {
Expand Down Expand Up @@ -147,7 +147,7 @@ export class Client {
this.results$ = share(
this.exchange({
client: this,
forward: fallbackExchangeIO,
forward: fallbackExchange({ client: this }),
})(this.operations$)
);

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/exchanges/dedup.ts
Expand Up @@ -3,6 +3,7 @@ import { Exchange, Operation, OperationResult } from '../types';

/** A default exchange for debouncing GraphQL requests. */
export const dedupExchange: Exchange = ({ forward, client }) => {
const dispatchEvent = client.debugTarget!.dispatchEvent;
JoviDeCroock marked this conversation as resolved.
Show resolved Hide resolved
const inFlightKeys = new Set<number>();

const filterIncomingOperation = (operation: Operation) => {
Expand All @@ -20,7 +21,7 @@ export const dedupExchange: Exchange = ({ forward, client }) => {
inFlightKeys.add(key);

if (isInFlight) {
client.debugTarget!.dispatchEvent({
dispatchEvent({
type: 'dedup',
message: 'An operation has been deduped.',
operation,
Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/exchanges/fallback.test.ts
@@ -1,9 +1,15 @@
import { forEach, fromValue, pipe } from 'wonka';
import { queryOperation, teardownOperation } from '../test-utils';
import { fallbackExchangeIO } from './fallback';
import { fallbackExchange } from './fallback';

const consoleWarn = console.warn;

const client = {
debugTarget: {
dispatchEvent: jest.fn(),
},
} as any;

beforeEach(() => {
console.warn = jest.fn();
});
Expand All @@ -16,7 +22,7 @@ it('filters all results and warns about input', () => {
const res: any[] = [];

pipe(
fallbackExchangeIO(fromValue(queryOperation)),
fallbackExchange({ client })(fromValue(queryOperation)),
forEach(x => res.push(x))
);

Expand All @@ -28,7 +34,7 @@ it('filters all results and warns about input', () => {
const res: any[] = [];

pipe(
fallbackExchangeIO(fromValue(teardownOperation)),
fallbackExchange({ client })(fromValue(teardownOperation)),
forEach(x => res.push(x))
);

Expand Down
22 changes: 15 additions & 7 deletions packages/core/src/exchanges/fallback.ts
@@ -1,18 +1,26 @@
import { filter, pipe, tap } from 'wonka';
import { ExchangeIO, Operation } from '../types';
import { Operation, ExchangeIO } from '../types';
import { Client } from 'src/client';

/** This is always the last exchange in the chain; No operation should ever reach it */
export const fallbackExchangeIO: ExchangeIO = ops$ =>
export const fallbackExchange: ({ client: Client }) => ExchangeIO = ({
client,
}) => ops$ =>
pipe(
ops$,
tap<Operation>(({ operationName }) => {
tap<Operation>(operation => {
if (
operationName !== 'teardown' &&
operation.operationName !== 'teardown' &&
process.env.NODE_ENV !== 'production'
) {
console.warn(
`No exchange has handled operations of type "${operationName}". Check whether you've added an exchange responsible for these operations.`
);
const message = `No exchange has handled operations of type "${operation.operationName}". Check whether you've added an exchange responsible for these operations.`;

client.debugTarget?.dispatchEvent({
JoviDeCroock marked this conversation as resolved.
Show resolved Hide resolved
type: 'fallbackCatch',
message,
operation,
});
console.warn(message);
}
}),
/* All operations that skipped through the entire exchange chain should be filtered from the output */
Expand Down
58 changes: 52 additions & 6 deletions packages/core/src/exchanges/fetch.ts
Expand Up @@ -3,6 +3,7 @@ import { Kind, DocumentNode, OperationDefinitionNode, print } from 'graphql';
import { filter, make, merge, mergeMap, pipe, share, takeUntil } from 'wonka';
import { Exchange, Operation, OperationResult } from '../types';
import { makeResult, makeErrorResult } from '../utils';
import { Client } from '../client';

interface Body {
query: string;
Expand All @@ -11,7 +12,7 @@ interface Body {
}

/** A default exchange for fetching GraphQL requests. */
export const fetchExchange: Exchange = ({ forward }) => {
export const fetchExchange: Exchange = ({ forward, client }) => {
const isOperationFetchable = (operation: Operation) => {
const { operationName } = operation;
return operationName === 'query' || operationName === 'mutation';
Expand All @@ -31,6 +32,7 @@ export const fetchExchange: Exchange = ({ forward }) => {

return pipe(
createFetchSource(
client,
operation,
operation.operationName === 'query' &&
!!operation.context.preferGetMethod
Expand Down Expand Up @@ -60,7 +62,11 @@ const getOperationName = (query: DocumentNode): string | null => {
return node !== undefined && node.name ? node.name.value : null;
};

const createFetchSource = (operation: Operation, shouldUseGet: boolean) => {
const createFetchSource = (
client: Client,
operation: Operation,
shouldUseGet: boolean
) => {
if (
process.env.NODE_ENV !== 'production' &&
operation.operationName === 'subscription'
Expand Down Expand Up @@ -113,7 +119,9 @@ const createFetchSource = (operation: Operation, shouldUseGet: boolean) => {
let ended = false;

Promise.resolve()
.then(() => (ended ? undefined : executeFetch(operation, fetchOptions)))
.then(() =>
ended ? undefined : executeFetch(client, operation, fetchOptions)
)
.then((result: OperationResult | undefined) => {
if (!ended) {
ended = true;
Expand All @@ -132,12 +140,24 @@ const createFetchSource = (operation: Operation, shouldUseGet: boolean) => {
};

const executeFetch = (
client: Client,
operation: Operation,
opts: RequestInit
): Promise<OperationResult> => {
const { url, fetch: fetcher } = operation.context;
const dispatchEvent = client.debugTarget!.dispatchEvent;
JoviDeCroock marked this conversation as resolved.
Show resolved Hide resolved
let response: Response | undefined;

dispatchEvent({
type: 'fetchRequest',
message: 'A fetch request is being executed.',
operation,
data: {
url,
fetchOptions: opts,
},
});

return (fetcher || fetch)(url, opts)
.then(res => {
const { status } = res;
Expand All @@ -150,11 +170,37 @@ const executeFetch = (
return res.json();
}
})
.then(result => makeResult(operation, result, response))
.then(result => {
dispatchEvent({
type: 'fetchSuccess',
message: 'A successful fetch response has been returned.',
operation,
data: {
url,
fetchOptions: opts,
value: result,
},
});

return makeResult(operation, result, response);
})
.catch(err => {
if (err.name !== 'AbortError') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep this condition and just move dispatchEvent in here

return makeErrorResult(operation, err, response);
if (err.name === 'AbortError') {
return;
}

dispatchEvent({
type: 'fetchError',
message: err.name,
operation,
data: {
url,
fetchOptions: opts,
value: err,
},
});

return makeErrorResult(operation, err, response);
});
};

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/exchanges/index.ts
Expand Up @@ -4,7 +4,7 @@ export { subscriptionExchange } from './subscription';
export { debugExchange } from './debug';
export { dedupExchange } from './dedup';
export { fetchExchange } from './fetch';
export { fallbackExchangeIO } from './fallback';
export { fallbackExchange } from './fallback';
export { composeExchanges } from './compose';

import { cacheExchange } from './cache';
Expand Down
18 changes: 17 additions & 1 deletion packages/core/src/types.ts
Expand Up @@ -88,17 +88,33 @@ export type ExchangeIO = (ops$: Source<Operation>) => Source<OperationResult>;

/** Debug event types (interfaced for declaration merging). */
export interface DebugEventTypes {
// Cache exchange
cacheHit: { value: any };
cacheInvalidation: {
typenames: string[];
response: OperationResult;
};
// Fetch exchange
fetchRequest: {
url: string;
fetchOptions: RequestInit;
};
fetchSuccess: {
url: string;
fetchOptions: RequestInit;
value: object;
};
fetchError: {
url: string;
fetchOptions: RequestInit;
value: Error;
};
}

export type DebugEvent<T extends keyof DebugEventTypes | string> = {
type: T;
message: string;
operation: Operation;
} & (T extends keyof DebugEventTypes
? { data: T extends keyof DebugEventTypes ? DebugEventTypes[T] : never }
? { data: DebugEventTypes[T] }
: { data?: any });