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

feat: Support disableMasking: true option to disable fragment masking globally #69

Merged
merged 5 commits into from
Feb 22, 2024
Merged
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
5 changes: 5 additions & 0 deletions .changeset/rude-houses-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gql.tada': patch
---

Add `disableMasking` flag to allow fragment masking to be disabled. When this is set to `true` on the `setupSchema` interface, fragments won’t be masked, which imitates the behaviour you’d see when adding `@_unmask` to every single one of your fragments. This is currently considered a preview feature.
41 changes: 41 additions & 0 deletions src/__tests__/api.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,47 @@ describe('graphql()', () => {
});
});

describe('graphql() with `disableMasking: true`', () => {
const graphql = initGraphQLTada<{ introspection: simpleIntrospection; disableMasking: true }>();
it('should support unmasked fragments via the `disableMasking` option', () => {
const fragment = graphql(`
fragment Fields on Todo {
id
text
}
`);

const query = graphql(
`
query Test($limit: Int) {
todos(limit: $limit) {
...Fields
}
}
`,
[fragment]
);

expectTypeOf<FragmentOf<typeof fragment>>().toEqualTypeOf<{
id: string | number;
text: string;
}>();

expectTypeOf<ResultOf<typeof query>>().toEqualTypeOf<{
todos:
| ({
id: string | number;
text: string;
} | null)[]
| null;
}>();
Comment on lines +132 to +139

Choose a reason for hiding this comment

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

This looks to be the exact output needed to make this work with normalized store cache updates.


expectTypeOf<VariablesOf<typeof query>>().toEqualTypeOf<{
limit?: number | null;
}>();
});
});

describe('graphql.scalar()', () => {
const graphql = initGraphQLTada<{ introspection: simpleIntrospection }>();

Expand Down
36 changes: 29 additions & 7 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ import type { stringLiteral, obj, matchOr, writable, DocumentDecoration } from '
interface AbstractSetupSchema {
introspection: IntrospectionQuery;
scalars?: ScalarsLike;
disableMasking?: boolean;
}

/** Abstract type for internal configuration
* @internal
*/
interface AbstractConfig {
isMaskingDisabled: boolean;
}

/** This is used to configure gql.tada with your introspection data and scalars.
Expand Down Expand Up @@ -75,7 +83,7 @@ interface setupSchema extends AbstractSetupSchema {
/*empty*/
}

interface GraphQLTadaAPI<Schema extends IntrospectionLikeType> {
interface GraphQLTadaAPI<Schema extends IntrospectionLikeType, Config extends AbstractConfig> {
/** Function to create and compose GraphQL documents with result and variable types.
*
* @param input - A string of a GraphQL document.
Expand Down Expand Up @@ -121,7 +129,12 @@ interface GraphQLTadaAPI<Schema extends IntrospectionLikeType> {
>(
input: In,
fragments?: Fragments
): getDocumentNode<parseDocument<In>, Schema, getFragmentsOfDocumentsRec<Fragments>>;
): getDocumentNode<
parseDocument<In>,
Schema,
getFragmentsOfDocumentsRec<Fragments>,
Config['isMaskingDisabled']
>;

/** Function to validate the type of a given scalar or enum value.
*
Expand Down Expand Up @@ -163,11 +176,15 @@ interface GraphQLTadaAPI<Schema extends IntrospectionLikeType> {
): getScalarType<Schema, Name>;
}

type schemaOfConfig<Setup extends AbstractSetupSchema> = mapIntrospection<
type schemaOfSetup<Setup extends AbstractSetupSchema> = mapIntrospection<
matchOr<IntrospectionQuery, Setup['introspection'], never>,
matchOr<ScalarsLike, Setup['scalars'], {}>
>;

type configOfSetup<Setup extends AbstractSetupSchema> = {
isMaskingDisabled: Setup['disableMasking'] extends true ? true : false;
};

/** Setup function to create a typed `graphql` document function with.
*
* @remarks
Expand Down Expand Up @@ -195,7 +212,8 @@ type schemaOfConfig<Setup extends AbstractSetupSchema> = mapIntrospection<
* ```
*/
function initGraphQLTada<const Setup extends AbstractSetupSchema>() {
type Schema = schemaOfConfig<Setup>;
type Schema = schemaOfSetup<Setup>;
type Config = configOfSetup<Setup>;

function graphql(input: string, fragments?: readonly TadaDocumentNode[]): any {
const definitions = _parse(input).definitions as writable<DefinitionNode>[];
Expand All @@ -222,7 +240,7 @@ function initGraphQLTada<const Setup extends AbstractSetupSchema>() {
return value;
};

return graphql as GraphQLTadaAPI<Schema>;
return graphql as GraphQLTadaAPI<Schema, Config>;
}

/** Alias to a GraphQL parse function returning an exact document type.
Expand All @@ -243,13 +261,14 @@ export type getDocumentNode<
Document extends DocumentNodeLike,
Introspection extends IntrospectionLikeType,
Fragments extends { [name: string]: any } = {},
isMaskingDisabled = false,
> = getDocumentType<Document, Introspection, Fragments> extends infer Result
? Result extends never
? never
: TadaDocumentNode<
Result,
getVariablesType<Document, Introspection>,
decorateFragmentDef<Document>
decorateFragmentDef<Document, isMaskingDisabled>
>
: never;

Expand Down Expand Up @@ -506,7 +525,10 @@ function unsafe_readResult<
return data as any;
}

const graphql: GraphQLTadaAPI<schemaOfConfig<setupSchema>> = initGraphQLTada();
const graphql: GraphQLTadaAPI<
schemaOfSetup<setupSchema>,
configOfSetup<setupSchema>
> = initGraphQLTada();

export { parse, graphql, readFragment, maskFragments, unsafe_readResult, initGraphQLTada };

Expand Down
9 changes: 7 additions & 2 deletions src/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ type isMaskedRec<Directives extends readonly unknown[] | undefined> = Directives
: isMaskedRec<Rest>
: true;

type decorateFragmentDef<Document extends DocumentNodeLike> = Document['definitions'][0] extends {
type decorateFragmentDef<
Document extends DocumentNodeLike,
isMaskingDisabled = false,
> = Document['definitions'][0] extends {
kind: Kind.FRAGMENT_DEFINITION;
name: any;
}
? {
fragment: Document['definitions'][0]['name']['value'];
on: Document['definitions'][0]['typeCondition']['name']['value'];
masked: isMaskedRec<Document['definitions'][0]['directives']>;
masked: isMaskingDisabled extends true
? false
: isMaskedRec<Document['definitions'][0]['directives']>;
}
: void;

Expand Down
2 changes: 0 additions & 2 deletions website/src/content/docs/guides/fragment-colocation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -288,5 +288,3 @@ type maskedAuthor = {
};
};
```


4 changes: 4 additions & 0 deletions website/src/content/docs/reference/gql-tada-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,14 @@ declare module 'gql.tada' {
| ----------- | ----------- |
| `introspection` property | Introspection of your schema in the `IntrospectionQuery` format. |
| `scalars` property | An optional object type with scalar names as keys and the corresponding scalar types as values. |
| `disableMasking` flag | This may be set to `true` to disable fragment masking globally. |

This is used either via [`setupSchema`](#setupschema) or [`initGraphQLTada()`](#initgraphqltada) to set
up your schema and scalars. Your configuration objects must match the shape of this type.

The `scalars` option is optional and can be used to set up more scalars, apart
from the default ones (like: Int, Float, String, Boolean).
It must be an object map of scalar names to their desired TypeScript types.

The `disableMasking` flag may be set to `true` instead of using `@_unmask` on individual fragments
and allows fragment masking to be disabled globally.
Loading