Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,14 @@
|
||||
import { ApolloProvider as ApolloProviderBase } from '@apollo/client';
|
||||
|
||||
import { useApolloFactory } from '@/apollo/hooks/useApolloFactory';
|
||||
|
||||
export const ApolloProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const apolloClient = useApolloFactory();
|
||||
|
||||
// This will attach the right apollo client to Apollo Dev Tools
|
||||
window.__APOLLO_CLIENT__ = apolloClient;
|
||||
|
||||
return (
|
||||
<ApolloProviderBase client={apolloClient}>{children}</ApolloProviderBase>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||
|
||||
import { ApolloFactory } from '../services/apollo.factory';
|
||||
|
||||
export const useApolloFactory = () => {
|
||||
// eslint-disable-next-line twenty/no-state-useref
|
||||
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
|
||||
const [isDebugMode] = useRecoilState(isDebugModeState);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const isMatchingLocation = useIsMatchingLocation();
|
||||
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState);
|
||||
|
||||
const apolloClient = useMemo(() => {
|
||||
apolloRef.current = new ApolloFactory({
|
||||
uri: `${REACT_APP_SERVER_BASE_URL}/graphql`,
|
||||
cache: new InMemoryCache(),
|
||||
defaultOptions: {
|
||||
query: {
|
||||
fetchPolicy: 'cache-first',
|
||||
},
|
||||
},
|
||||
connectToDevTools: isDebugMode,
|
||||
// We don't want to re-create the client on token change or it will cause infinite loop
|
||||
initialTokenPair: tokenPair,
|
||||
onTokenPairChange: (tokenPair) => {
|
||||
setTokenPair(tokenPair);
|
||||
},
|
||||
onUnauthenticatedError: () => {
|
||||
setTokenPair(null);
|
||||
if (
|
||||
!isMatchingLocation(AppPath.Verify) &&
|
||||
!isMatchingLocation(AppPath.SignIn) &&
|
||||
!isMatchingLocation(AppPath.SignUp) &&
|
||||
!isMatchingLocation(AppPath.Invite)
|
||||
) {
|
||||
navigate(AppPath.SignIn);
|
||||
}
|
||||
},
|
||||
extraLinks: [],
|
||||
isDebugMode,
|
||||
});
|
||||
|
||||
return apolloRef.current.getClient();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [setTokenPair, isDebugMode]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (apolloRef.current) {
|
||||
apolloRef.current.updateTokenPair(tokenPair);
|
||||
}
|
||||
}, [tokenPair]);
|
||||
|
||||
return apolloClient;
|
||||
};
|
||||
@ -0,0 +1,156 @@
|
||||
import {
|
||||
ApolloCache,
|
||||
DocumentNode,
|
||||
OperationVariables,
|
||||
useApolloClient,
|
||||
} from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import {
|
||||
EMPTY_QUERY,
|
||||
useObjectMetadataItem,
|
||||
} from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
|
||||
import { optimisticEffectState } from '../states/optimisticEffectState';
|
||||
import { OptimisticEffect } from '../types/internal/OptimisticEffect';
|
||||
import { OptimisticEffectDefinition } from '../types/OptimisticEffectDefinition';
|
||||
|
||||
export const useOptimisticEffect = ({
|
||||
objectNameSingular,
|
||||
}: ObjectMetadataItemIdentifier) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { findManyRecordsQuery } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const registerOptimisticEffect = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
<T>({
|
||||
variables,
|
||||
definition,
|
||||
}: {
|
||||
variables: OperationVariables;
|
||||
definition: OptimisticEffectDefinition;
|
||||
}) => {
|
||||
if (findManyRecordsQuery === EMPTY_QUERY) {
|
||||
throw new Error(
|
||||
`Trying to register an optimistic effect for unknown object ${objectNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
const optimisticEffects = snapshot
|
||||
.getLoadable(optimisticEffectState)
|
||||
.getValue();
|
||||
|
||||
const optimisticEffectWriter = ({
|
||||
cache,
|
||||
newData,
|
||||
deletedRecordIds,
|
||||
query,
|
||||
variables,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
newData: unknown;
|
||||
deletedRecordIds?: string[];
|
||||
variables: OperationVariables;
|
||||
query: DocumentNode;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
}) => {
|
||||
if (objectMetadataItem) {
|
||||
const existingData = cache.readQuery({
|
||||
query: findManyRecordsQuery,
|
||||
variables,
|
||||
});
|
||||
|
||||
if (!existingData) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.writeQuery({
|
||||
query: findManyRecordsQuery,
|
||||
variables,
|
||||
data: {
|
||||
[objectMetadataItem.namePlural]: definition.resolver({
|
||||
currentData: (existingData as any)?.[
|
||||
objectMetadataItem.namePlural
|
||||
],
|
||||
newData,
|
||||
deletedRecordIds,
|
||||
variables,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const existingData = cache.readQuery({
|
||||
query,
|
||||
variables,
|
||||
});
|
||||
|
||||
if (!existingData) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const optimisticEffect = {
|
||||
key: definition.key,
|
||||
variables,
|
||||
typename: definition.typename,
|
||||
query: definition.query,
|
||||
writer: optimisticEffectWriter,
|
||||
objectMetadataItem: definition.objectMetadataItem,
|
||||
isUsingFlexibleBackend: definition.isUsingFlexibleBackend,
|
||||
} satisfies OptimisticEffect<T>;
|
||||
|
||||
set(optimisticEffectState, {
|
||||
...optimisticEffects,
|
||||
[definition.key]: optimisticEffect,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const triggerOptimisticEffects = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(typename: string, newData: unknown, deletedRecordIds?: string[]) => {
|
||||
const optimisticEffects = snapshot
|
||||
.getLoadable(optimisticEffectState)
|
||||
.getValue();
|
||||
|
||||
for (const optimisticEffect of Object.values(optimisticEffects)) {
|
||||
// We need to update the typename when createObject type differs from listObject types
|
||||
// It is the case for apiKey, where the creation route returns an ApiKeyToken type
|
||||
const formattedNewData = isNonEmptyArray(newData)
|
||||
? newData.map((data: any) => {
|
||||
return { ...data, __typename: typename };
|
||||
})
|
||||
: newData;
|
||||
|
||||
if (optimisticEffect.typename === typename) {
|
||||
optimisticEffect.writer({
|
||||
cache: apolloClient.cache,
|
||||
query: optimisticEffect.query ?? ({} as DocumentNode),
|
||||
newData: formattedNewData,
|
||||
deletedRecordIds,
|
||||
variables: optimisticEffect.variables,
|
||||
isUsingFlexibleBackend: optimisticEffect.isUsingFlexibleBackend,
|
||||
objectMetadataItem: optimisticEffect.objectMetadataItem,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[apolloClient.cache],
|
||||
);
|
||||
|
||||
return {
|
||||
registerOptimisticEffect,
|
||||
triggerOptimisticEffects,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
export const useOptimisticEvict = () => {
|
||||
const cache = useApolloClient().cache;
|
||||
|
||||
const performOptimisticEvict = (
|
||||
typename: string,
|
||||
fieldName: string,
|
||||
fieldValue: string,
|
||||
) => {
|
||||
const serializedCache = cache.extract();
|
||||
|
||||
Object.values(serializedCache)
|
||||
.filter((item) => item.__typename === typename)
|
||||
.forEach((item) => {
|
||||
if (item[fieldName] === fieldValue) {
|
||||
cache.evict({ id: cache.identify(item) });
|
||||
}
|
||||
});
|
||||
};
|
||||
return {
|
||||
performOptimisticEvict,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { OptimisticEffect } from '../types/internal/OptimisticEffect';
|
||||
|
||||
export const optimisticEffectState = atom<
|
||||
Record<string, OptimisticEffect<unknown>>
|
||||
>({
|
||||
key: 'optimisticEffectState',
|
||||
default: {},
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { DocumentNode } from 'graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
import { OptimisticEffectResolver } from './OptimisticEffectResolver';
|
||||
|
||||
export type OptimisticEffectDefinition = {
|
||||
key: string;
|
||||
query?: DocumentNode;
|
||||
typename: string;
|
||||
resolver: OptimisticEffectResolver;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { OperationVariables } from '@apollo/client';
|
||||
|
||||
export type OptimisticEffectResolver = ({
|
||||
currentData,
|
||||
newData,
|
||||
deletedRecordIds,
|
||||
variables,
|
||||
}: {
|
||||
currentData: any; //TODO: Change when decommissioning v1
|
||||
newData: any; //TODO: Change when decommissioning v1
|
||||
deletedRecordIds?: string[];
|
||||
variables: OperationVariables;
|
||||
}) => void;
|
||||
@ -0,0 +1,28 @@
|
||||
import { ApolloCache, DocumentNode, OperationVariables } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
type OptimisticEffectWriter<T> = ({
|
||||
cache,
|
||||
newData,
|
||||
variables,
|
||||
query,
|
||||
}: {
|
||||
cache: ApolloCache<T>;
|
||||
query: DocumentNode;
|
||||
newData: T;
|
||||
deletedRecordIds?: string[];
|
||||
variables: OperationVariables;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
}) => void;
|
||||
|
||||
export type OptimisticEffect<T> = {
|
||||
key: string;
|
||||
query?: DocumentNode;
|
||||
typename: string;
|
||||
variables: OperationVariables;
|
||||
writer: OptimisticEffectWriter<T>;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
};
|
||||
@ -0,0 +1,159 @@
|
||||
import {
|
||||
ApolloClient,
|
||||
ApolloClientOptions,
|
||||
ApolloLink,
|
||||
fromPromise,
|
||||
ServerError,
|
||||
ServerParseError,
|
||||
} from '@apollo/client';
|
||||
import { GraphQLErrors } from '@apollo/client/errors';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
import { RetryLink } from '@apollo/client/link/retry';
|
||||
import { createUploadLink } from 'apollo-upload-client';
|
||||
|
||||
import { renewToken } from '@/auth/services/AuthService';
|
||||
import { AuthTokenPair } from '~/generated/graphql';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
import { logDebug } from '~/utils/logDebug';
|
||||
|
||||
import { ApolloManager } from '../types/apolloManager.interface';
|
||||
import { loggerLink } from '../utils';
|
||||
|
||||
const logger = loggerLink(() => 'Twenty');
|
||||
|
||||
export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
|
||||
onError?: (err: GraphQLErrors | undefined) => void;
|
||||
onNetworkError?: (err: Error | ServerParseError | ServerError) => void;
|
||||
onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
|
||||
onUnauthenticatedError?: () => void;
|
||||
initialTokenPair: AuthTokenPair | null;
|
||||
extraLinks?: ApolloLink[];
|
||||
isDebugMode?: boolean;
|
||||
}
|
||||
|
||||
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
private client: ApolloClient<TCacheShape>;
|
||||
private tokenPair: AuthTokenPair | null = null;
|
||||
|
||||
constructor(opts: Options<TCacheShape>) {
|
||||
const {
|
||||
uri,
|
||||
onError: onErrorCb,
|
||||
onNetworkError,
|
||||
onTokenPairChange,
|
||||
onUnauthenticatedError,
|
||||
initialTokenPair,
|
||||
extraLinks,
|
||||
isDebugMode,
|
||||
...options
|
||||
} = opts;
|
||||
|
||||
this.tokenPair = initialTokenPair;
|
||||
|
||||
const buildApolloLink = (): ApolloLink => {
|
||||
const httpLink = createUploadLink({
|
||||
uri,
|
||||
});
|
||||
|
||||
const authLink = setContext(async (_, { headers }) => {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: this.tokenPair?.accessToken.token
|
||||
? `Bearer ${this.tokenPair?.accessToken.token}`
|
||||
: '',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const retryLink = new RetryLink({
|
||||
delay: {
|
||||
initial: 3000,
|
||||
},
|
||||
attempts: {
|
||||
max: 2,
|
||||
retryIf: (error) => !!error,
|
||||
},
|
||||
});
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, forward, operation }) => {
|
||||
if (graphQLErrors) {
|
||||
onErrorCb?.(graphQLErrors);
|
||||
|
||||
for (const graphQLError of graphQLErrors) {
|
||||
if (graphQLError.message === 'Unauthorized') {
|
||||
return fromPromise(
|
||||
renewToken(uri, this.tokenPair)
|
||||
.then((tokens) => {
|
||||
onTokenPairChange?.(tokens);
|
||||
})
|
||||
.catch(() => {
|
||||
onUnauthenticatedError?.();
|
||||
}),
|
||||
).flatMap(() => forward(operation));
|
||||
}
|
||||
|
||||
switch (graphQLError?.extensions?.code) {
|
||||
case 'UNAUTHENTICATED': {
|
||||
return fromPromise(
|
||||
renewToken(uri, this.tokenPair)
|
||||
.then((tokens) => {
|
||||
onTokenPairChange?.(tokens);
|
||||
})
|
||||
.catch(() => {
|
||||
onUnauthenticatedError?.();
|
||||
}),
|
||||
).flatMap(() => forward(operation));
|
||||
}
|
||||
default:
|
||||
if (isDebugMode) {
|
||||
logDebug(
|
||||
`[GraphQL error]: Message: ${
|
||||
graphQLError.message
|
||||
}, Location: ${
|
||||
graphQLError.locations
|
||||
? JSON.stringify(graphQLError.locations)
|
||||
: graphQLError.locations
|
||||
}, Path: ${graphQLError.path}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
if (isDebugMode) {
|
||||
logDebug(`[Network error]: ${networkError}`);
|
||||
}
|
||||
onNetworkError?.(networkError);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return ApolloLink.from(
|
||||
[
|
||||
errorLink,
|
||||
authLink,
|
||||
...(extraLinks || []),
|
||||
isDebugMode ? logger : null,
|
||||
retryLink,
|
||||
httpLink,
|
||||
].filter(assertNotNull),
|
||||
);
|
||||
};
|
||||
|
||||
this.client = new ApolloClient({
|
||||
...options,
|
||||
link: buildApolloLink(),
|
||||
});
|
||||
}
|
||||
|
||||
updateTokenPair(tokenPair: AuthTokenPair | null) {
|
||||
this.tokenPair = tokenPair;
|
||||
}
|
||||
|
||||
getClient() {
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import { ApolloClient } from '@apollo/client';
|
||||
|
||||
import { AuthTokenPair } from '~/generated/graphql';
|
||||
|
||||
export interface ApolloManager<TCacheShape> {
|
||||
getClient(): ApolloClient<TCacheShape>;
|
||||
updateTokenPair(tokenPair: AuthTokenPair | null): void;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export enum OperationType {
|
||||
Query = 'query',
|
||||
Mutation = 'mutation',
|
||||
Subscription = 'subscription',
|
||||
Error = 'error',
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
import { OperationType } from '../types/operation-type';
|
||||
|
||||
const operationTypeColors = {
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
query: '#03A9F4',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
mutation: '#61A600',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
subscription: '#61A600',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
error: '#F51818',
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
default: '#61A600',
|
||||
};
|
||||
|
||||
const getOperationColor = (operationType: OperationType) => {
|
||||
return operationTypeColors[operationType] ?? operationTypeColors.default;
|
||||
};
|
||||
|
||||
const formatTitle = (
|
||||
operationType: OperationType,
|
||||
schemaName: string,
|
||||
queryName: string,
|
||||
time: string | number,
|
||||
) => {
|
||||
const headerCss = [
|
||||
'color: gray; font-weight: lighter', // title
|
||||
`color: ${getOperationColor(operationType)}; font-weight: bold;`, // operationType
|
||||
'color: gray; font-weight: lighter;', // schemaName
|
||||
'color: black; font-weight: bold;', // queryName
|
||||
];
|
||||
|
||||
const parts = [
|
||||
'%c apollo',
|
||||
`%c${operationType}`,
|
||||
`%c${schemaName}::%c${queryName}`,
|
||||
];
|
||||
|
||||
if (operationType !== OperationType.Subscription) {
|
||||
parts.push(`%c(in ${time} ms)`);
|
||||
headerCss.push('color: gray; font-weight: lighter;'); // time
|
||||
} else {
|
||||
parts.push(`%c(@ ${time})`);
|
||||
headerCss.push('color: gray; font-weight: lighter;'); // time
|
||||
}
|
||||
|
||||
return [parts.join(' '), ...headerCss];
|
||||
};
|
||||
|
||||
export default formatTitle;
|
||||
105
packages/twenty-front/src/modules/apollo/utils/index.ts
Normal file
105
packages/twenty-front/src/modules/apollo/utils/index.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { ApolloLink, gql, Operation } from '@apollo/client';
|
||||
|
||||
import { logDebug } from '~/utils/logDebug';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
import formatTitle from './format-title';
|
||||
|
||||
const getGroup = (collapsed: boolean) =>
|
||||
collapsed
|
||||
? console.groupCollapsed.bind(console)
|
||||
: console.group.bind(console);
|
||||
|
||||
const parseQuery = (queryString: string) => {
|
||||
const queryObj = gql`
|
||||
${queryString}
|
||||
`;
|
||||
|
||||
const { name } = queryObj.definitions[0] as any;
|
||||
return [name ? name.value : 'Generic', queryString.trim()];
|
||||
};
|
||||
|
||||
export const loggerLink = (getSchemaName: (operation: Operation) => string) =>
|
||||
new ApolloLink((operation, forward) => {
|
||||
const schemaName = getSchemaName(operation);
|
||||
operation.setContext({ start: Date.now() });
|
||||
|
||||
const { variables } = operation;
|
||||
|
||||
const operationType = (operation.query.definitions[0] as any).operation;
|
||||
const headers = operation.getContext().headers;
|
||||
|
||||
const [queryName, query] = parseQuery(operation.query.loc!.source.body);
|
||||
|
||||
if (operationType === 'subscription') {
|
||||
const date = new Date().toLocaleTimeString();
|
||||
|
||||
const titleArgs = formatTitle(operationType, schemaName, queryName, date);
|
||||
|
||||
console.groupCollapsed(...titleArgs);
|
||||
|
||||
if (variables && Object.keys(variables).length !== 0) {
|
||||
logDebug('VARIABLES', variables);
|
||||
}
|
||||
|
||||
logDebug('QUERY', query);
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
return forward(operation);
|
||||
}
|
||||
|
||||
return forward(operation).map((result) => {
|
||||
const time = Date.now() - operation.getContext().start;
|
||||
const errors = result.errors ?? result.data?.[queryName]?.errors;
|
||||
const hasError = Boolean(errors);
|
||||
|
||||
try {
|
||||
const titleArgs = formatTitle(
|
||||
operationType,
|
||||
schemaName,
|
||||
queryName,
|
||||
time,
|
||||
);
|
||||
|
||||
getGroup(!hasError)(...titleArgs);
|
||||
|
||||
if (errors) {
|
||||
errors.forEach((err: any) => {
|
||||
logDebug(
|
||||
`%c${err.message}`,
|
||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
||||
'color: #F51818; font-weight: lighter',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
logDebug('HEADERS: ', headers);
|
||||
|
||||
if (variables && Object.keys(variables).length !== 0) {
|
||||
logDebug('VARIABLES', variables);
|
||||
}
|
||||
|
||||
logDebug('QUERY', query);
|
||||
|
||||
if (result.data) {
|
||||
logDebug('RESULT', result.data);
|
||||
}
|
||||
if (errors) {
|
||||
logDebug('ERRORS', errors);
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
} catch {
|
||||
// this may happen if console group is not supported
|
||||
logDebug(
|
||||
`${operationType} ${schemaName}::${queryName} (in ${time} ms)`,
|
||||
);
|
||||
if (errors) {
|
||||
logError(errors);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user