Skip to content

Commit

Permalink
feat: Support disableMasking: true option to disable fragment maski…
Browse files Browse the repository at this point in the history
…ng globally (#69)
  • Loading branch information
kitten authored Feb 22, 2024
1 parent d104fc9 commit 1dc8cf5
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 11 deletions.
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;
}>();

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.

0 comments on commit 1dc8cf5

Please sign in to comment.