Skip to content

Commit

Permalink
fix!: mark return type for custom fields as optional (#294)
Browse files Browse the repository at this point in the history
  • Loading branch information
LiamMartens committed Oct 7, 2022
1 parent 73137dd commit 3f34410
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 64 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
undefined

### [0.8.1](https://github.com/danielroe/sanity-typed-queries/compare/0.8.0...0.8.1) (2022-02-28)undefined
### [0.8.1](https://github.com/danielroe/sanity-typed-queries/compare/0.8.0...0.8.1) (2022-02-28)

## [0.8.0](https://github.com/danielroe/sanity-typed-queries/compare/0.7.5...0.8.0) (2022-02-28)

Expand Down
4 changes: 2 additions & 2 deletions src/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type ExtractDocumentType<
SchemaName extends string,
CustomTypes extends CustomType<string>
> = {
[P in keyof Schema]: FieldType<Schema[P], CustomTypes>
[P in keyof Schema]?: FieldType<Schema[P], CustomTypes>
} & {
_createdAt: string
_updatedAt: string
Expand Down Expand Up @@ -174,7 +174,7 @@ type ExtractObjectType<
SchemaName extends string,
CustomTypes extends CustomType<string>
> = {
[P in keyof Schema]: FieldType<Schema[P], CustomTypes>
[P in keyof Schema]?: FieldType<Schema[P], CustomTypes>
} & { _type: SchemaName }

export function defineObject<
Expand Down
44 changes: 23 additions & 21 deletions src/query/builder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { Reference } from '../types'
import { UndefinedAsOptional } from '../types/util'
import { inArray, createProxy } from '../utils'

type QueryReturnType<T> = [string, T]

type Single<T> = T
type Multiple<T> = T[]

type ResolveFieldType<T> = T extends Record<string, any>
? MapResolver<T>
: ResolverAction<T>
type ResolveFieldType<T> = NonNullable<T> extends Record<string, any>
? MapResolver<NonNullable<T>>
: ResolverAction<NonNullable<T>>

interface ResolverFunction<T, Arr = false> {
<P extends keyof T>(props: P[]): ResolverAction<Pick<T, P>>
Expand All @@ -20,37 +21,38 @@ interface ResolverFunction<T, Arr = false> {
}

type BaseResolverAction<T> = {
use: (defaultValue?: T) => T
use: (defaultValue?: T) => T | undefined
}

type ResolverAction<T> = BaseResolverAction<T> &
(T extends Reference<infer A>
(NonNullable<T> extends Reference<infer A>
? { resolve: ResolverFunction<A> }
: Record<string, unknown>) &
(T extends Array<any>
(NonNullable<T> extends Array<any>
? {
count: () => number
}
: Record<string, unknown>) &
(T extends Array<Record<string, any>>
(NonNullable<T> extends Array<Record<string, any>>
? {
pick: <K extends keyof T[0]>(
pick: <K extends keyof NonNullable<T>[0]>(
props: K[] | K
) => { use: () => Pick<T[0], K> }
) => { use: () => Pick<NonNullable<T>[0], K> }
}
: Record<string, unknown>) &
(T extends Array<Reference<infer A>>
(NonNullable<T> extends Array<Reference<infer A>>
? {
resolveIn: ResolverFunction<A, true>
}
: Record<string, unknown>)

type MapResolver<T extends Record<string, any>> = (T extends Array<any>
? Record<string, unknown>
: {
[P in keyof T]: ResolveFieldType<T[P]>
}) &
ResolverAction<T>
type MapResolver<T extends Record<string, any>> =
(NonNullable<T> extends Array<any>
? Record<string, unknown>
: {
[P in keyof T]: ResolveFieldType<T[P]>
}) &
ResolverAction<T>

type Combine<
Original extends Record<string, any>,
Expand Down Expand Up @@ -235,11 +237,11 @@ export class QueryBuilder<
}

map<NewMapping extends Record<string, any>>(
map: NewMapping | ((resolver: MapResolver<Schema>) => NewMapping)
map: NewMapping | ((resolver: Required<MapResolver<Schema>>) => NewMapping)
): Omit<
QueryBuilder<
Schema,
Combine<Mappings, NewMapping>,
UndefinedAsOptional<Combine<Mappings, NewMapping>>,
Subqueries,
Type,
Project,
Expand All @@ -250,8 +252,8 @@ export class QueryBuilder<
let mappings: Combine<Mappings, NewMapping>

function checkCallable(
m: NewMapping | ((resolver: MapResolver<Schema>) => NewMapping)
): m is (resolver: MapResolver<Schema>) => NewMapping {
m: NewMapping | ((resolver: Required<MapResolver<Schema>>) => NewMapping)
): m is (resolver: Required<MapResolver<Schema>>) => NewMapping {
return typeof m === 'function'
}

Expand All @@ -274,7 +276,7 @@ export class QueryBuilder<
) as unknown as Omit<
QueryBuilder<
Schema,
Combine<Mappings, NewMapping>,
UndefinedAsOptional<Combine<Mappings, NewMapping>>,
Subqueries,
Type,
Project,
Expand Down
3 changes: 2 additions & 1 deletion src/schema/fields.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Block, File, Geopoint, Image, Reference, Slug } from '../types'
import type { UndefinedAsOptional } from '../types/util'

import type {
ArrayRule,
Expand Down Expand Up @@ -478,7 +479,7 @@ export type FieldType<
: T extends PureType<'number'>
? number
: T extends Nameless<ObjectField<infer A>>
? A
? UndefinedAsOptional<A>
: T extends PureType<'reference'> & { to: Array<infer B> }
? Reference<FieldType<B, CustomObjects>>
: T extends PureType<'slug'>
Expand Down
14 changes: 14 additions & 0 deletions src/types/util/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type UndefinedAsOptional<T> = Partial<
Pick<
T,
{
[K in keyof T]: Extract<T[K], undefined> extends never ? never : K
}[keyof T]
>
> &
Pick<
T,
{
[K in keyof T]: Extract<T[K], undefined> extends never ? K : never
}[keyof T]
>
52 changes: 31 additions & 21 deletions test/types/builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,32 @@ describe('builder types', () => {
})

const a = builder.pick('description').use()[1]
expectTypeOf(a).toEqualTypeOf<string[]>()
expectTypeOf(a).toEqualTypeOf<(string | undefined)[]>()

const b = builder.first().pick('description').use()[1]
expectTypeOf(b).toEqualTypeOf<string>()
expectTypeOf(b).toEqualTypeOf<string | undefined>()

const c = builder.pick('description').first().use()[1]
expectTypeOf(c).toEqualTypeOf<string>()
expectTypeOf(c).toEqualTypeOf<string | undefined>()

const type = builder.pick('tags').first().use()[1]
expectTypeOf(type).toEqualTypeOf<Array<string | number>>()
expectTypeOf(type).toEqualTypeOf<Array<string | number> | undefined>()

const d = builder.use()[1]
expectTypeOf<typeof d[number]['tags']>().toEqualTypeOf<
Array<string | number>
Array<string | number> | undefined
>()

const e = builder.pick('_updatedAt').first().use()[1]
expectTypeOf(e).toEqualTypeOf<string>()

const f = builder.pick(['_type', 'name']).first().use()[1]
expectTypeOf(f).toEqualTypeOf<{ _type: 'author'; name: string }>()
expectTypeOf(f).toEqualTypeOf<{ _type: 'author'; name?: string }>()

const filterType = defineDocument('test', { title: { type: 'string' } })
.builder.filter('')
.use()[1][0]
expectTypeOf(filterType).toMatchTypeOf<{ title: string }>()
expectTypeOf(filterType).toMatchTypeOf<{ title?: string }>()

// @ts-expect-error
expectTypeOf(builder.pick('nothere'))
Expand All @@ -66,7 +66,7 @@ describe('builder types', () => {
},
})
const g = testObj.pick('testObject').first().use()[1]
expectTypeOf(g).toEqualTypeOf<{ subfield: string | undefined }>()
expectTypeOf(g).toEqualTypeOf<{ subfield?: string } | undefined>()

const mapper = defineDocument('author', {
num: {
Expand All @@ -85,44 +85,49 @@ describe('builder types', () => {

const h = mapper.pick(['test', 'testObject']).use()[1]
expectTypeOf(h).toEqualTypeOf<
{ test: string; testObject: { subfield: string | undefined } }[]
{ test?: string; testObject?: { subfield?: string } }[]
>()

const i = mapper
.map(r => ({ test: r.num.use(), bagel: r.testObject.use() }))
.pick(['test', 'num'])
.use()[1]
expectTypeOf(i).toEqualTypeOf<{ test: number; num: number }[]>()
expectTypeOf(i).toEqualTypeOf<{ test?: number; num?: number }[]>()

const j = mapper
.map(r => ({ test: r.num.use(), bagel: r.testObject.use() }))
.pick(['test', 'bagel'])
.use()[1]
expectTypeOf(j).toEqualTypeOf<
{ test: number; bagel: { subfield: string | undefined } }[]
{ test?: number; bagel?: { subfield?: string } }[]
>()

const k = mapper
.map(r => ({ test: r.num.use(), bagel: r.testObject.use() }))
.pick('bagel')
.use()[1]
expectTypeOf(k).toEqualTypeOf<{ subfield: string | undefined }[]>()
expectTypeOf(k).toEqualTypeOf<({ subfield?: string } | undefined)[]>()

const l = mapper
.map(r => ({ test: r.num.use(), bagel: r.testObject.use() }))
.pick('bagel')
.first()
.use()[1]
expectTypeOf(l).toEqualTypeOf<{ subfield: string | undefined }>()
expectTypeOf(l).toEqualTypeOf<
{ subfield?: string | undefined } | undefined
>()

const m = mapper
.map(r => ({ test: r.num.use(), bagel: r.testObject.use() }))
.first()
.use()[1]

expectTypeOf<typeof m['bagel']>().toEqualTypeOf<{
subfield: string | undefined
}>()
expectTypeOf<typeof m['bagel']>().toEqualTypeOf<
| {
subfield?: string | undefined
}
| undefined
>()

const resolver = defineDocument('author', {
image: {
Expand All @@ -135,7 +140,7 @@ describe('builder types', () => {
.pick('testImage')
.first()
.use()[1]
expectTypeOf(resolvedId).toEqualTypeOf<string>()
expectTypeOf(resolvedId).toEqualTypeOf<string | undefined>()

const { tag } = defineDocument('tag', {
title: {
Expand Down Expand Up @@ -186,12 +191,16 @@ describe('builder types', () => {
)

const inter = objectBuilder.pick('num').first().use()[1]
expectTypeOf<typeof inter['title']>().toEqualTypeOf<string>()
expectTypeOf<NonNullable<typeof inter>['title']>().toEqualTypeOf<
string | undefined
>()
const inter2 = objectBuilder
.map(h => ({ bingTitle: h.bing.resolve('title').use() }))
.first()
.use()[1]
expectTypeOf<typeof inter2['bingTitle']>().toEqualTypeOf<number>()
expectTypeOf<NonNullable<typeof inter2>['bingTitle']>().toEqualTypeOf<
number | undefined
>()
const inter3 = objectBuilder
.map(h => ({ count: h.more.count() }))
.first()
Expand Down Expand Up @@ -221,7 +230,7 @@ describe('builder types', () => {
.pick('fields')
.first()
.use()[1]
).toEqualTypeOf<string[][]>()
).toEqualTypeOf<(string[] | undefined)[] | undefined>()

const subqueryType = defineDocument('test', { title: { type: 'string' } })
.builder.subquery({
Expand All @@ -230,14 +239,15 @@ describe('builder types', () => {
}).builder.use(),
})
.use()[1][0]?.children

expectTypeOf(subqueryType).toEqualTypeOf<
{
_createdAt: string
_updatedAt: string
_id: string
_rev: string
_type: 'child'
title: string
title?: string
}[]
>()

Expand Down
Loading

0 comments on commit 3f34410

Please sign in to comment.