Skip to content

Commit

Permalink
✨ feat: Treat nullable types as optional ones in GraphQL variables
Browse files Browse the repository at this point in the history
  • Loading branch information
Snowflyt committed Mar 21, 2024
1 parent 2afb322 commit 8d1605a
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 18 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-intuitive-request",
"version": "0.1.5-dev",
"version": "0.1.5",
"private": true,
"description": "Intuitive and (more importantly) TS-friendly GraphQL client for queries, mutations and subscriptions",
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,14 @@ export type ObjectLength<T> = TuplifyLiteralStringUnion<keyof T>['length'];
* Get keys not ends with `?` from an object.
*/
export type RequiredFields<T extends Record<string, string>> = keyof {
[K in keyof T as K extends `${any}?` ? never : K]: void;
[K in keyof T as K extends `${any}?` ? never : T[K] extends `${any}!` ? K : never]: void;
};

/**
* Get the count of keys not ends with `?` from an object.
*/
export type RequiredFieldsCount<T extends Record<string, string>> = ObjectLength<{
[K in keyof T as K extends `${any}?` ? never : K]: void;
[K in keyof T as K extends `${any}?` ? never : T[K] extends `${any}!` ? K : never]: void;
}>;

/**
Expand Down
6 changes: 5 additions & 1 deletion src/types/graphql-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ export type WrapByType<
? T | null
: T;

export type VariablesOf<TVariables, $> = Parse<TVariables, $ & BaseEnvironment>;
export type VariablesOf<TVariables, $> = Parse<
TVariables,
$ & BaseEnvironment,
{ treatNullableTypeAsOptional: true }
>;
49 changes: 36 additions & 13 deletions src/types/parser.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
import type { Merge, StringKeyOf, StringLiteral } from './common';
import type { GraphQLEnum, GraphQLList, GraphQLNonNull } from './graphql-types';

type TryResolve<T extends StringKeyOf<$>, $> = $[T] extends
| StringLiteral
| Record<string, StringLiteral>
? Parse<$[T], $>
type TryResolve<
T extends StringKeyOf<$>,
$,
TFlags extends { treatNullableTypeAsOptional: boolean } = { treatNullableTypeAsOptional: false },
> = $[T] extends StringLiteral | Record<string, StringLiteral>
? Parse<$[T], $, TFlags>
: $[T] extends GraphQLEnum<infer S>
? S
: $[T];

export type Parse<T, $> = T extends Record<string, StringLiteral>
? Merge<
{ [P in keyof T as P extends `${string}?` ? never : P]: Parse<T[P], $> },
{ [P in keyof T as P extends `${infer K}?` ? K : never]?: Parse<T[P], $> }
>
export type Parse<
T,
$,
TFlags extends { treatNullableTypeAsOptional: boolean } = { treatNullableTypeAsOptional: false },
> = T extends Record<string, StringLiteral>
? TFlags['treatNullableTypeAsOptional'] extends true
? Merge<
{
[P in keyof T as P extends `${string}?`
? never
: T[P] extends `${string}!`
? P
: never]: Parse<T[P], $, TFlags>;
},
{
[P in keyof T as P extends `${infer K}?`
? K
: T[P] extends `${string}!`
? never
: P]?: Parse<T[P], $, TFlags>;
}
>
: Merge<
{ [P in keyof T as P extends `${string}?` ? never : P]: Parse<T[P], $, TFlags> },
{ [P in keyof T as P extends `${infer K}?` ? K : never]?: Parse<T[P], $, TFlags> }
>
: T extends GraphQLNonNull<infer U extends StringKeyOf<$>>
? // HACK: I use this ugly `infer R` instead of just `Exclude<..., null>` because the latter will
// cause a infinite loop when compiling—I don't know why, but it works.
TryResolve<U, $> extends infer R
TryResolve<U, $, TFlags> extends infer R
? R extends U | null
? U
: R
: never
: T extends StringKeyOf<$>
? TryResolve<T, $> | null
? TryResolve<T, $, TFlags> | null
: T extends GraphQLNonNull<GraphQLList<infer U>>
? // HACK: I use this ugly `infer R` instead of just `Exclude<..., null>` because the latter will
// cause a infinite loop when compiling—I don't know why, but it works.
Parse<U, $>[] extends infer R
Parse<U, $, TFlags>[] extends infer R
? R extends U | null
? U
: R
: never
: T extends GraphQLList<infer U>
? Parse<U, $>[] | null
? Parse<U, $, TFlags>[] | null
: T;
3 changes: 2 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const objectLength = <T extends object>(obj: T) =>
Object.keys(obj).length as ObjectLength<T>;

export const requiredKeysCount = <T extends Record<string, string>>(obj: T): number =>
(objectLength(obj) as number) - Object.keys(obj).filter((v) => v.endsWith('?')).length;
(objectLength(obj) as number) -
Object.entries(obj).filter(([k, v]) => k.endsWith('?') || !v.endsWith('!')).length;

export const capitalize = (str: string) => `${str[0].toUpperCase()}${str.slice(1)}`;

Expand Down

0 comments on commit 8d1605a

Please sign in to comment.