Open huv1k opened 3 years ago
If I understand you correctly you could rename the model to Note
and map it to @@(name: "notes")
https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference/#map-1
This is similar to @@map
, but I believe @huv1k wants it only to apply to the nexus models. Maybe there could be another attribute called @@nexus_map
?
I would like to consider this problem now as an incremental test/progress of forthcoming Nexus Prisma issues.
This issue feels modest and shouldn't be too hard to implement.
But doing it will stretch both the internal implementation and design thinking hopefully in ways that prepare for features demanding significantly more dynamanism later.
As a start I'm thinking this:
settings({
nameMap: {
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
},
})
But there might be patterns that the developer wants to automate. Not only is that a big time saver its helpful for maintenance and communicating with team members in the sense that patterns encode a kind of intent, whereas the pure static approach might hide it (actual results here vary by complexity/sensability of pattern etc.).
So imagine this:
settings({
nameMap: {
static: {
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
},
patterns: {
'<RegExp with Capturing Groups>':
'<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>',
},
},
})
There are Prisma Schema characteristics that a developer might want to draw upon. If we think of the above so far as a shorthand, here's how more sophisticated patterns might be expressed:
settings({
nameMap: {
static: {
"<Prisma Model Name>": "<Desired GraphQL Type Name>",
},
patterns: [{
{
description: 'Optional pretty title here',
matchName: "<RegExp with Capturing Groups>",
matchModels: boolean
matchEnums: boolean
projectAs: "<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>",
}
}]
},
})
We could use Prisma types to give autocomplete for the static
name map.
We could use some helpful validation to sanity check things like:
Static name maps would overrule patterns.
We could consider an API instead of configuration. At some point it all boils down to configuration so I see the following as sugar. But its more than just addidtive. Choices in the API may motivate removing sugar from the configuration schema. Let's see.
settings({
transformations: [
map.names.static('<Prisma Model Name>', '<Desired GraphQL Type Name>'),
map.names.static({
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
}),
map.names.pattern(
'<RegExp with Capturing Groups>',
'<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>'
),
map.names.pattern({
prisma: '<RegExp with Capturing Groups>',
graphql:
'<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>',
description: 'Optional pretty title here',
includeModels: boolean,
includeEnums: boolean,
}),
],
})
Maybe a chaining API?:
settings({
transformations: [
map.names.static({
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
}),
map.names.pattern(
'<RegExp with Capturing Groups>',
'<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>'
),
map.names
.pattern()
.description('Optional pretty title here')
.prisma('<RegExp with Capturing Groups>')
.graphql(
'<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>'
)
.models(false),
],
})
And we could also get away from the settings configuration object to clean things up further:
import { map } from 'nexus-prisma/generator/settings'
map.names.static({
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
})
map.names.pattern(
'<RegExp with Capturing Groups>',
'<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>'
)
map.names
.pattern()
.description('Optional pretty title here')
.prisma('<RegExp with Capturing Groups>')
.graphql('<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>')
.models(false)
I could imagine a lot more functionality extending this over time. Imagine to reduce risk the developer wanted to omit sensitive fields from being projected.
import { omit } from 'nexus-prisma/generator/settings'
omit.fields
.at('Foo.bar')
.at('*.password')
omit.fields
.pattern(/^password$|^.+Password$/)
.description('Never project passwords')
Back to name mapping... maybe we merge .prisma
with .pattern
constructor, but we lose the .prisma/.graphql
symmetry then.
import { map } from 'nexus-prisma/generator/settings'
map.names.table({
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
})
map.names
.pattern('<RegExp with Capturing Groups>')
.graphql('<Desired GraphQL Type name maybe using capturing groups $1 $2 $3>')
.description('Optional pretty title here')
.enums(false)
I am intrigued by the API approach. I think it might be pretty nice to work with and scale up to some of the quite complex problems that we encountered when designing the CRUD aspects of the previous version of Nexus Prisma.
I'll leave these thoughts here for a bit, please leave feedback if you have any, its welcome!
Thinking we could have a selector API.
Also rename description
to comment
.
Also drop enum(true)
in favour of clearer only(...)
and skip(...)
APIs.
import { map, $ } from 'nexus-prisma/generator/settings'
map.names.table({
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
})
const stripeEnums = $.name(/Stripe(.+)/)
.only({ enum: true })
.comment('Optional comment here')
map.names.from(stripeModels).to('Fin$1')
Also thining that we can use named capture groups with a sprinkle of typegen to do something like transform capture groups to merge with other text. I think this would be a very common need. In order for this to work the following needs to happen:
to
has any
params typeimport { map, $ } from 'nexus-prisma/generator/settings'
map.names.table({
'<Prisma Model Name>': '<Desired GraphQL Type Name>',
})
const stripeEnums = $.name(/Stripe(?<name>.+)/)
.only({ enum: true })
.comment('Optional pretty title here')
map.names.from(stripeModels).to(({ name }) => `Fin${pascalCase(name)}`)
However... in order to target the function calls with the right types we'll need a targeting mechanism. A string literal is typically how this is done. But we'll need to tweak the API for that then...
Idea (1) allow a pattern title...
const stripeEnums = $.name(/Stripe(?<name>.+)/)
.only({ enum: true })
.comment('Optional pretty title here')
map.names
.pattern('unique title')
.from(stripeModels)
.to(({ name }) => `Fin${pascalCase(name)}`)
Idea (2) force regular expressions to be represented as strings...
const stripeEnums = $.name('Stripe(?<name>.+)')
.only({ enum: true })
.comment('Optional pretty title here')
map.names.from(stripeModels).to(({ name }) => `Fin${pascalCase(name)}`)
Idea 2 feels a lot more seamless from an API perspective but the loss of RegExp tooling (at least syntax highlighting but maybe there are IDE plugins etc. too going on) is unfortunate. However it seems the less confusing way forward here.
We could make $
sugar for $.name
const stripeEnums = $('Stripe(?<name>.+)')
.only({ enum: true })
.comment('Optional pretty title here')
map.names.from(stripeModels).to((groups) => `Fin${pascalCase(groups.name)}`)
Instead of overloading to
we could introduce a transformer:
const stripeEnums = $('Stripe(?<name>.+)')
.only({ enum: true })
.comment('Optional pretty title here')
map.names
.from(stripeModels)
.transform(({ name }) => ({ name: pascalCase(name) }))
.to('Fin<name>')
Actually I think the function form of to
is still useful to have, but I think transform
is a useful addition.
So I would consider the to
template form as shordhand for the to
function form. Aka. sugar.
Basically .to('Fin<name>')
would be sugar for .to((groups) => \
Fin${groups.name}`)`.
A downside of to
sugar is that it doesn't type check e.g. no static error on 'Fin<naem>'
however we could of course do runtime validation on that.
Please no chaining APIs. They are much harder to interact with programmatically.
@iddan Will need a stronger reason than that to dismiss it. Examples etc. and counter proposal. Chaining APIs are a primary way to get type safety in many cases too, although things can be achieved with a pipe(funcA, funcB, funcC)
style too I'm less familiar with it and there is less discoverability built into that API.
E.g. this:
However... in order to target the function calls with the right types we'll need a targeting mechanism. A string literal is typically how this is done. But we'll need to tweak the API for that then...
Which parts of the chaining API are actual separate combinators vs which are separated config steps is also another aspect to consider.
Don't get me wrong, I'm all for a statics set of combinators that can be accessed for "programatic" usage (quotes b/c there is not enough information here yet to conclude the chaining API doesn't serve that) but I would not get rid of the chaining API for it probably, at the very least because right now we don't actually know what it means/is/works semantically.
import { $ } from 'nexus-prisma/generator/settings'
$.funcA().funcB().funcC()
import { funcA, funcB, funcC, pipe } from 'nexus-prisma/generator/settings'
pipe(funcA(), funcB(), funcC())
I don't think we should waste too many cycles about which style is better right now. More important to focus on what the primitives even are, what mutates, what is immutable, etc.
I'll probably for now and would suggest to others to just alternate between the styles to keep a lightweight fresh perspectives of ergonomics.
I think this kind of feature would largely cover my proposal for relay connection support: https://github.com/prisma/nexus-prisma/issues/212 Or at least I believe it should be possible to build some utilities on top of this to support that.
We want to use
nexus-prisma
for our GraphQL server, but we need to adjust how it generates models. We have 170+ data models and it would require a lot of handwork to manually remap each data model. Currently, we have the name of tables asnotes
and we want to expose models asNote
. Right now we can do something like thisWe would like to do something like this:
Ideas / Proposed Solution(s)
I would like to enhance
nexus-generator
with model mapping and this would enable rename generated names of models.I don't know what could be ideal mapping object and where should configuration live.