Improve nullability & list API
See original GitHub issueMotivation
Current API tries to describe nullability & list using booleans.
t.field('foo', {
nullable: boolean,
list: boolean | boolean[]
})
Here’s how the current API can be used:
import { objectType } from '@nexus/schema'
export const Test = objectType({
name: 'Foo',
definition(t) {
t.string('a') // String
t.string('b', { nullable: false }) // String!
t.string('c', { list: true }) // [String]
t.string('d', { list: true, nullable: false }) // [String]!
t.string('e', { list: [true] }) // [String!]
t.string('f', { nullable: false, list: [true] }) // [String!]!
}
})
This can quickly become very unintuitive. I actually had to quickly jump in a playground to remember the API.
Proposal n°1
Replace the current API with the following two simple constructors: nonNull()
& list()
, inspired from https://docs.graphene-python.org/en/latest/types/list-and-nonnull/.
While its usage is slightly more verbose, it’s also much clearer.
Usage example:
import { objectType, list, nonNull } from '@nexus/schema'
objectType({
name: 'Foo',
definition(t) {
t.field('a', { type: 'String' }) // String
t.field('b', { type: nonNull('String') }) // String!
t.field('c', { type: list('String') }) // [String]
t.field('d', { type: list(nonNull('String')) }) // [String!]
t.field('e', { type: nonNull(list('String')) }) // [String]!
t.field('f', { type: nonNull(list(nonNull('String'))) }) // [String!]!
}
})
The biggest con with this proposal is that it doesn’t seem to support the t.{scalars}
shorthands at all since the types are now wrapped in list()
and nonNull()
types. Here’s a quick illustration:
t.string('myField', { /*no type property, so we can't express list or nullability*/ })
Did I maybe miss something that would keep this proposal usable with the scalar shorthand? We could enable something like:
t.string('somefield', {
listOrNullability: list(nonNull()) // [String!] - Please, ignore the terrible `listOrNullability` field name, it doesn't matter here
})
But I’m not sure that’s intuitive either…
Proposal n°2
The current API already supports shorthand for list fields, using the t.list.*
. We could expand on the previous idea and allow the following expressions:
import { objectType } from '@nexus/schema'
objectType({
name: 'Foo',
definition(t) {
t.string('a') // String
t.nonNull.string('b') // String!
t.list.string('c') // [String]
t.list.nonNull.string('d') // [String!]
t.nonNull.list.string('e') // [String]!
t.nonNull.list.nonNull.string('f') // [String!]!
},
})
I honestly don’t like very much the chaining style of this API, which makes the readability fairly hard. But at least it keeps supporting the scalar shorthands which I find very useful to reduce the boilerplate already created by the code-first paradigm.
Proposal n°3
It’s very unlikely that we’ll want that, but just to be exhaustive, we could as well allow the SDL representations:
import { objectType, list, nonNull } from '@nexus/schema'
objectType({
name: 'Foo',
definition(t) {
t.field('a', { type: 'String' }) // String
t.field('b', { type: 'String!' }) // String!
t.field('c', { type: '[String]' }) // [String]
t.field('d', { type: '[String!]' }) // [String!]
t.field('e', { type: '[String]!' }) // [String]!
t.field('f', { type: '[String!]!' }) // [String!]!
}
})
Cons:
- No support for scalar shorthands either
- No support for type reference using the actual values (as opposed to references using type names)
- Could blow up the union type member threshold very quick (which is around 100k I think)
- Cannot type-safely support nested arrays (which could be infinitely nested) without blowing up the union type member threshold even quicker
Let us know if you have better proposals and/or which one you prefer here 🙌
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:11 (9 by maintainers)
After a call with @jasonkuhrt, here are all the proposal we’ve found (some overlapping with the current ones):
Current preferred choices: 1 & 2 (given the ):
t.list.nonNull.*
) should be what most users should be using so that the API stays symmetrical between scalars shorthand andt.field
list(nonNull(...))
) will be kept as lower-level primitives that Option 1 will use and that plugins and/or custom use-cases should be using.We’ve also agreed that
t.null.*
could be a great antonym ofnonNull
.@tgriesser WDYT?
@Sytten Hey, Composable functions are the way this is going to go for
type
property configuration. I think there’s some strong points in its favour that @tgriesser is fond of. IMO the strongest points are its flexibility and simplicity, very amenable to building higher abstractions with.At the same time the short hand chaining will remain in place.