diff --git a/.changeset/quiet-panthers-wink.md b/.changeset/quiet-panthers-wink.md new file mode 100644 index 00000000..7a78578f --- /dev/null +++ b/.changeset/quiet-panthers-wink.md @@ -0,0 +1,5 @@ +--- +"gql.tada": patch +--- + +Extend `readFragment` types to allow `| {}` optional fragments to be matched. When a fragment is annotated with a directive making it optional (such as `@include`, `@skip`, or `@defer`) then its typed as optional. `readFragment` previously didn't know how to match these types, but it will now match `T | {}` and infer the type as such. diff --git a/src/__tests__/api.test-d.ts b/src/__tests__/api.test-d.ts index c33bfe1f..23815ee4 100644 --- a/src/__tests__/api.test-d.ts +++ b/src/__tests__/api.test-d.ts @@ -543,6 +543,130 @@ describe('readFragment', () => { const result = readFragment({} as document, {} as FragmentOf); expectTypeOf().toEqualTypeOf>(); }); + + it('should unmask nullable, undefined, and optional data', () => { + type fragment = parseDocument<` + fragment Fields on Todo @_unmask { + id + } + `>; + + type document = getDocumentNode; + + const inputA: FragmentOf | null = {} as any; + const resultA = readFragment({} as document, inputA); + expectTypeOf().toEqualTypeOf | null>(); + + const inputB: FragmentOf | undefined = {} as any; + const resultB = readFragment({} as document, inputB); + expectTypeOf().toEqualTypeOf | undefined>(); + + const inputC: FragmentOf | undefined | null = {} as any; + const resultC = readFragment({} as document, inputC); + expectTypeOf().toEqualTypeOf | undefined | null>(); + + const inputD: FragmentOf | {} = {} as any; + const resultD = readFragment({} as document, inputD); + expectTypeOf().toEqualTypeOf | {}>(); + + const inputE: FragmentOf | {} | null = {} as any; + const resultE = readFragment({} as document, inputE); + expectTypeOf().toEqualTypeOf | {} | null>(); + }); + + it('should unmask arrays of nullable, undefined, and optional data', () => { + type fragment = parseDocument<` + fragment Fields on Todo @_unmask { + id + } + `>; + + type document = getDocumentNode; + + const inputA: (FragmentOf | null)[] = []; + const resultA = readFragment({} as document, inputA); + expectTypeOf().toEqualTypeOf | null)[]>(); + + const inputB: (FragmentOf | undefined)[] = []; + const resultB = readFragment({} as document, inputB); + expectTypeOf().toEqualTypeOf | undefined)[]>(); + + const inputC: (FragmentOf | undefined | null)[] = []; + const resultC = readFragment({} as document, inputC); + expectTypeOf().toEqualTypeOf< + readonly (ResultOf | undefined | null)[] + >(); + + const inputD: (FragmentOf | {})[] = []; + const resultD = readFragment({} as document, inputD); + expectTypeOf().toEqualTypeOf | {})[]>(); + + const inputE: (FragmentOf | {} | null)[] = []; + const resultE = readFragment({} as document, inputE); + expectTypeOf().toEqualTypeOf | {} | null)[]>(); + }); + + it('should unmask nullable, undefined, and optional data (with passed generic)', () => { + type fragment = parseDocument<` + fragment Fields on Todo @_unmask { + id + } + `>; + + type document = getDocumentNode; + + const inputA: FragmentOf | null = {} as any; + const resultA = readFragment(inputA); + expectTypeOf().toEqualTypeOf | null>(); + + const inputB: FragmentOf | undefined = {} as any; + const resultB = readFragment(inputB); + expectTypeOf().toEqualTypeOf | undefined>(); + + const inputC: FragmentOf | undefined | null = {} as any; + const resultC = readFragment(inputC); + expectTypeOf().toEqualTypeOf | undefined | null>(); + + const inputD: FragmentOf | {} = {} as any; + const resultD = readFragment(inputD); + expectTypeOf().toEqualTypeOf | {}>(); + + const inputE: FragmentOf | {} | null = {} as any; + const resultE = readFragment(inputE); + expectTypeOf().toEqualTypeOf | {} | null>(); + }); + + it('should unmask arrays of nullable, undefined, and optional data (with passed generic)', () => { + type fragment = parseDocument<` + fragment Fields on Todo @_unmask { + id + } + `>; + + type document = getDocumentNode; + + const inputA: (FragmentOf | null)[] = []; + const resultA = readFragment(inputA); + expectTypeOf().toEqualTypeOf | null)[]>(); + + const inputB: (FragmentOf | undefined)[] = []; + const resultB = readFragment(inputB); + expectTypeOf().toEqualTypeOf | undefined)[]>(); + + const inputC: (FragmentOf | undefined | null)[] = []; + const resultC = readFragment(inputC); + expectTypeOf().toEqualTypeOf< + readonly (ResultOf | undefined | null)[] + >(); + + const inputD: (FragmentOf | {})[] = []; + const resultD = readFragment(inputD); + expectTypeOf().toEqualTypeOf | {})[]>(); + + const inputE: (FragmentOf | {} | null)[] = []; + const resultE = readFragment(inputE); + expectTypeOf().toEqualTypeOf | {} | null)[]>(); + }); }); describe('maskFragments', () => { diff --git a/src/api.ts b/src/api.ts index 2bbf1cbd..cee26623 100644 --- a/src/api.ts +++ b/src/api.ts @@ -512,17 +512,27 @@ type fragmentRefsOfFragmentsRec< * @see {@link readFragment} for how to read from fragment masks. */ function readFragment( + _document: Document, fragment: resultOrFragmentOf ): ResultOf; -function readFragment( - fragment: resultOrFragmentOf | null -): ResultOf | null; -function readFragment( - fragment: resultOrFragmentOf | undefined -): ResultOf | undefined; -function readFragment( - fragment: resultOrFragmentOf | null | undefined -): ResultOf | null | undefined; +// Reading fragments where input data is an array and nullable +function readFragment< + const Document extends FragmentShape, + const T extends resultOrFragmentOf | null | undefined | {}, +>( + _document: Document, + fragments: readonly T[] +): readonly (T extends resultOrFragmentOf ? ResultOf : T)[]; +// Reading fragments where input data is nullable +function readFragment< + const Document extends FragmentShape, + const T extends resultOrFragmentOf | null | undefined | {}, +>( + _document: Document, + fragment: T +): T extends resultOrFragmentOf ? ResultOf : T; + +// Reading arrays of fragments with required generic function readFragment( fragment: readonly resultOrFragmentOf[] ): readonly ResultOf[]; @@ -535,38 +545,46 @@ function readFragment( function readFragment( fragment: readonly (resultOrFragmentOf | null | undefined)[] ): readonly (ResultOf | null | undefined)[]; +// Reading arrays of fragments with required generic with optional `{}` type +function readFragment( + fragment: readonly (resultOrFragmentOf | {})[] +): readonly (ResultOf | {})[]; +function readFragment( + fragment: readonly (resultOrFragmentOf | null | {})[] +): readonly (ResultOf | null | {})[]; +function readFragment( + fragment: readonly (resultOrFragmentOf | undefined | {})[] +): readonly (ResultOf | undefined | {})[]; +function readFragment( + fragment: readonly (resultOrFragmentOf | null | undefined | {})[] +): readonly (ResultOf | null | undefined | {})[]; +// Reading fragments with required generic function readFragment( - _document: Document, fragment: resultOrFragmentOf ): ResultOf; -function readFragment( - _document: Document, +function readFragment( fragment: resultOrFragmentOf | null ): ResultOf | null; -function readFragment( - _document: Document, +function readFragment( fragment: resultOrFragmentOf | undefined ): ResultOf | undefined; -function readFragment( - _document: Document, +function readFragment( fragment: resultOrFragmentOf | null | undefined ): ResultOf | null | undefined; -function readFragment( - _document: Document, - fragment: readonly resultOrFragmentOf[] -): readonly ResultOf[]; -function readFragment( - _document: Document, - fragment: readonly (resultOrFragmentOf | null)[] -): readonly (ResultOf | null)[]; -function readFragment( - _document: Document, - fragment: readonly (resultOrFragmentOf | undefined)[] -): readonly (ResultOf | undefined)[]; -function readFragment( - _document: Document, - fragment: readonly (resultOrFragmentOf | null | undefined)[] -): readonly (ResultOf | null | undefined)[]; +// Reading fragments with required generic with optional `{}` type +function readFragment( + fragment: resultOrFragmentOf | {} +): ResultOf | {}; +function readFragment( + fragment: resultOrFragmentOf | null | {} +): ResultOf | null | {}; +function readFragment( + fragment: resultOrFragmentOf | undefined | {} +): ResultOf | undefined | {}; +function readFragment( + fragment: resultOrFragmentOf | null | undefined | {} +): ResultOf | null | undefined | {}; + function readFragment(...args: [unknown] | [unknown, unknown]) { return args.length === 2 ? args[1] : args[0]; }