Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const defaultConfig: Omit<Required<GQtyConfig>, 'transformSchema'> &
enumsAsStrings: false,
enumsAsConst: false,
preImport: '',
disableUndefinedScalar: false,
};

function warnConfig(
Expand Down
18 changes: 17 additions & 1 deletion packages/cli/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ export interface GenerateOptions {
schema: GraphQLSchema,
graphql_js: typeof graphql
) => Promise<GraphQLSchema> | GraphQLSchema;

/**
* Disable making all scalar fields undefined, only use when:
* 1. using `useQuery()` with `prepare` and `suspense` enabled.
* 2. `useTransactionQuery()` with suspense enabled.
* 3. `useLazyQuery()` and `useMutation()` after invoked.
*
* Be sure to be using only of such combination when using this flag on true.
*/
disableUndefinedScalar?: boolean;
}

export interface TransformSchemaOptions {
Expand All @@ -109,6 +119,7 @@ export async function generate(
endpoint,
enumsAsStrings,
enumsAsConst,
disableUndefinedScalar,
subscriptions,
javascriptOutput,
transformSchema,
Expand Down Expand Up @@ -150,6 +161,9 @@ export async function generate(
}
enumsAsConst ??= gqtyConfig.enumsAsConst ?? defaultConfig.enumsAsConst;

disableUndefinedScalar ??=
gqtyConfig.disableUndefinedScalar ?? defaultConfig.disableUndefinedScalar;

scalarTypes ||= gqtyConfig.scalarTypes || defaultConfig.scalarTypes;
endpoint ||=
gqtyConfig.introspection?.endpoint ?? defaultConfig.introspection.endpoint;
Expand Down Expand Up @@ -782,7 +796,9 @@ export async function generate(
[K in keyof T]: T[K] | undefined;
};

export interface ScalarsEnums extends MakeNullable<Scalars> {
export interface ScalarsEnums extends ${
disableUndefinedScalar ? 'Scalars' : 'MakeNullable<Scalars>'
} {
${deps.sortBy(enumsNames).reduce((acum, enumName) => {
acum += `${enumName}: ${enumName} | undefined;`;
return acum;
Expand Down
275 changes: 275 additions & 0 deletions packages/cli/test/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,281 @@ test('basic functionality works', async () => {
).toBeTruthy();
});

test('disable scalar works', async () => {
const { getEnveloped } = await createTestApp({
schema: {
typeDefs: gql`
"Query"
type Query {
"Hello field"
hello: String!
deprecatedArg(arg: Int = 123): Int @deprecated
}
`,
resolvers: {
Query: {
hello() {
return 'hello world';
},
},
},
},
});

const shouldBeIncluded = '// This should be included';

const { schemaCode, clientCode, generatedSchema, scalarsEnumsHash } =
await generate(getEnveloped().schema, {
preImport: `
${shouldBeIncluded}
`,
react: true,
subscriptions: true,
});

expect(schemaCode).toMatchInlineSnapshot(`
"/**
* GQTY AUTO-GENERATED CODE: PLEASE DO NOT MODIFY MANUALLY
*/

// This should be included

export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = {
[K in keyof T]: T[K];
};
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
[SubKey in K]?: Maybe<T[SubKey]>;
};
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
[SubKey in K]: Maybe<T[SubKey]>;
};
/** All built-in and custom scalars, mapped to their actual values */
export interface Scalars {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
}

export const scalarsEnumsHash: import('gqty').ScalarsEnumsHash = {
Boolean: true,
Int: true,
String: true,
};
export const generatedSchema = {
mutation: {},
query: {
__typename: { __type: 'String!' },
deprecatedArg: { __type: 'Int', __args: { arg: 'Int' } },
hello: { __type: 'String!' },
},
subscription: {},
} as const;

export interface Mutation {
__typename?: 'Mutation';
}

/**
* Query
*/
export interface Query {
__typename?: 'Query';
/**
* @deprecated No longer supported
*/
deprecatedArg: (args?: {
/**
* @defaultValue \`123\`
*/
arg?: Maybe<Scalars['Int']>;
}) => Maybe<ScalarsEnums['Int']>;
/**
* Hello field
*/
hello: ScalarsEnums['String'];
}

export interface Subscription {
__typename?: 'Subscription';
}

export interface SchemaObjectTypes {
Mutation: Mutation;
Query: Query;
Subscription: Subscription;
}
export type SchemaObjectTypesNames = 'Mutation' | 'Query' | 'Subscription';

export interface GeneratedSchema {
query: Query;
mutation: Mutation;
subscription: Subscription;
}

export type MakeNullable<T> = {
[K in keyof T]: T[K] | undefined;
};

export interface ScalarsEnums extends Scalars {}
"
`);

expect(clientCode).toMatchInlineSnapshot(`
"/**
* GQTY: You can safely modify this file and Query Fetcher based on your needs
*/

import { createReactClient } from '@gqty/react';
import { createSubscriptionsClient } from '@gqty/subscriptions';
import type { QueryFetcher } from 'gqty';
import { createClient } from 'gqty';
import type {
GeneratedSchema,
SchemaObjectTypes,
SchemaObjectTypesNames,
} from './schema.generated';
import { generatedSchema, scalarsEnumsHash } from './schema.generated';

const queryFetcher: QueryFetcher = async function (
query,
variables,
fetchOptions
) {
// Modify "/api/graphql" if needed
const response = await fetch('/api/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
mode: 'cors',
...fetchOptions,
});

const json = await response.json();

return json;
};

const subscriptionsClient =
typeof window !== 'undefined'
? createSubscriptionsClient({
wsEndpoint: () => {
// Modify if needed
const url = new URL('/api/graphql', window.location.href);
url.protocol = url.protocol.replace('http', 'ws');
return url.href;
},
})
: undefined;

export const client = createClient<
GeneratedSchema,
SchemaObjectTypesNames,
SchemaObjectTypes
>({
schema: generatedSchema,
scalarsEnumsHash,
queryFetcher,
subscriptionsClient,
});

const { query, mutation, mutate, subscription, resolved, refetch, track } =
client;

export { query, mutation, mutate, subscription, resolved, refetch, track };

const {
graphql,
useQuery,
usePaginatedQuery,
useTransactionQuery,
useLazyQuery,
useRefetch,
useMutation,
useMetaState,
prepareReactRender,
useHydrateCache,
prepareQuery,
useSubscription,
} = createReactClient<GeneratedSchema>(client, {
defaults: {
// Set this flag as "true" if your usage involves React Suspense
// Keep in mind that you can overwrite it in a per-hook basis
suspense: false,

// Set this flag based on your needs
staleWhileRevalidate: false,
},
});

export {
graphql,
useQuery,
usePaginatedQuery,
useTransactionQuery,
useLazyQuery,
useRefetch,
useMutation,
useMetaState,
prepareReactRender,
useHydrateCache,
prepareQuery,
useSubscription,
};

export * from './schema.generated';
"
`);

expect(JSON.stringify(generatedSchema, null, 2)).toMatchInlineSnapshot(`
"{
"query": {
"__typename": {
"__type": "String!"
},
"deprecatedArg": {
"__type": "Int",
"__args": {
"arg": "Int"
}
},
"hello": {
"__type": "String!"
}
},
"mutation": {},
"subscription": {}
}"
`);

expect(JSON.stringify(scalarsEnumsHash, null, 2)).toMatchInlineSnapshot(`
"{
"Boolean": true,
"Int": true,
"String": true
}"
`);

expect(clientCode.includes('= createReactClient')).toBeTruthy();

expect(
schemaCode
.split('\n')
.slice(3)
.join('\n')
.trim()
.startsWith(shouldBeIncluded)
).toBeTruthy();
});

test('custom scalars works', async () => {
const { getEnveloped } = await createTestApp({
schema: {
Expand Down