question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Improve nullability & list API

See original GitHub issue

Motivation

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:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:11 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
Weakkycommented, Oct 30, 2020

After a call with @jasonkuhrt, here are all the proposal we’ve found (some overlapping with the current ones):

// Option 1
t.null.list.nonNull.field('a', {
  type: 'Foo',
  resolve() {}
})

// Option 2
import { nonNull, list } from '@nexus/schema'

t.field('a', {
  type: nonNull(list(nonNull('Foo'))),
  resolve() {}
})

// Option 3
import { type } from '@nexus/schema'

t.field('a', {
  type: type.nonNull.list.nonNull.ref('Foo'),
  resolve() {}
})

// Option 4 (using proxy)
t.nonNull.list.nonNull.Foo('a', {
  resolve() {}
})

Current preferred choices: 1 & 2 (given the ):

  • We think option 1 (t.list.nonNull.*) should be what most users should be using so that the API stays symmetrical between scalars shorthand and t.field
  • Option 2 (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.

Also we’ll need an inverse. For example imagine the user has global default to be non-nullable.

We’ve also agreed that t.null.* could be a great antonym of nonNull.

@tgriesser WDYT?

0reactions
jasonkuhrtcommented, Nov 17, 2020

@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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Improve nullability & list API · Issue #591 · graphql-nexus/nexus
Current API tries to describe nullability & list using booleans. ... Here's how the current API can be used: import { objectType }...
Read more >
Using Optional and Nullable Properties in API Requests
Learn how optional and nullable properties can be used flexibly in combinations in each parameter of your API requests made from a SDK....
Read more >
Using nullability in GraphQL
GraphQL improves on that by having a complete schema of your available data, ... Now, let's say we defined a Non-Null List of...
Read more >
Designating Nullability in Objective-C APIs - Apple Developer
You can use nullability annotations in your Objective-C code to designate whether a parameter type, property type, or return type is nullable. Annotate...
Read more >
Update your codebase to use nullable reference types
You turn on nullable reference types as you annotate APIs. When you've finished, you enable nullable reference types for the entire project.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found