prisma-labs / graphql-framework-experiment Goto Github PK
View Code? Open in Web Editor NEWCode-First Type-Safe GraphQL Framework
Home Page: https://nexusjs.org
License: MIT License
Code-First Type-Safe GraphQL Framework
Home Page: https://nexusjs.org
License: MIT License
We need a way to run the app for just its typegen.
We can build in support for an ENV VAR that changes how pumpkins runs.
When PUMPKINS_TYPEGEN='true'
then pumpkins will exit 0 after nexus typegen has run.
If is PUMPKINS_TYPEGEN
specified (process.env.PUMPKINS_TYPEGEN !== undefined
) but its value is not in "true" | "false"
then raise a runtime error.
PUMPKINS_TYPEGEN='true'
const app = createApp() // process exits before this returns
app.startServer() // never runs
Related #3
Currently we ignore node_modules and watch everything else in the project for changes. This is probably not optimal and will not scale/be good both in terms of performance and in terms of dx.
To further underscore the high-level-ness of project dir, I was thinking of outputting the build to node_modules/.build (.pumpkins_build pumpkins_app_build, ...?) and then revisiting the idea of $ pumpkins boot which 1) knows to look into node_modules/.build and 2) knows to call the start module $node node_modules/.pumpkins_app_build/start
This helps because:
After this feature, we will need support from the CLI to make it easy to boot the app. While convention node_modules/.build/start
is easy enough to remember, we can lower the bar further.
Needs #10
Users must apply a specific combination of tools and configurations thereof to successfully build their app "the nexus way".
For example:
ts-node
ts-node
the right flags (--transpile-only
)At a high level:
In Detail
Assume that entrypoint is src/server.ts
Set environment variable PUMPKINS_TYPEGEN='true'
there should be an entrypoint flag for running typegen
if user provides nexus entrypoint config in package json and user does not pass entrypoint cli flag then use the package json variant
Use the Compiler API to find the user's tsconfig.json
and honour it
peer dep on typescript, using a range as wide as possible
We may want to use https://github.com/davidtheclark/cosmiconfig
src/schema/index.ts
src/schema.ts
src/server.ts
src/main.ts
src/index.ts
schema/index.ts
schema.ts
server.ts
main.ts
index.ts
ERROR (please pass entrypoint)
Some original build design started in #272 (comment) and original cli work began in #275.
Setup the project:
Start with version
and help
commands + flags.
Defining principals for pumpkins will help clarify for us how to make optimal tradeoffs. It will also give users better transparency into if the tool aligns with their needs/wants, and what they can expect out of the project mid/long-term.
During our brainstorming we sketched various sounds-good-maybe-maybe-not principals.
This issue is to take the next pass(es) distilling toward the essence.
I think we should limit ourselves on the principals we take on. Three? The goal is to simplify as much as we can tradeoff flashpoints, reduce the number of things we should be aiming to satisfy. In some ways more principals means owning more complexity, e.g. something akin to this:
We can treat the following as a living document. As we discuss and revise, we can re-edit the following to stay up to date.
As much as possible users should be working with code. Activities like fiddling with the file system, navigating docs, writing deploy scripts, checking terminals, and more is energy lost. Our job is to provide an API where the leverage of every line of code is as high as it can be for the developer. The less time users have to pend outside TypeScript files the better.
We leverage declarative APIs for CRUD, field projection, auth, and more to produce schematic overviews of the application. By doing this we make it easier to onboard collaborators and understand your architecture. We are paying back the user for using our API, aka. we are giving them leverage.
Generated artifacts are invisible to the developer
Example Applications
@types
, import ... from ...
, ...)Other Detail
We have a significant amount of user non-success with the nexus and nexus-prisma workflow. We want our framework to allow users to never have to think about:
We are aggregating user DX frustration about this issue here.
...
Instead of static guides that require the user to build a delta from their current position to we should move guides to the front lines. Think $ <thing> doctor
on steroids.
$ <thing> what do I do next?
We start with ideal developer experiences. We avoid premature modularization. When in doubt, we do not modularize. We value finding natural wholistic seams over time.
We envision our ideal developer experience and then work backwards to where we are.
Anything we expose our users to (cli commands, config, api exports, ...) we take responsibility for owning the mental model and approachability for. We are not a disjointed bag of features that users piece together a mental model for themselves by traversing half a dozen different docs. If we re-export nexus core building blocks but the docs for those building blocks are poor or cumbersome to access from our framework docs then we take it upon ourselves to resolve that (be it upstream contributions, creating our own docs, etc.).
...
We bake a lot of patterns into our examples. We can bring these patterns closer to users with an init command. It could:
We don't want to invest too much into this, probably. Scaffolding just automates what we couldn't figure out how to abstract in libraries or frameworks. I would rather focus on that. Still some basic scaffolding is a nice thing to help users get going.
Inspiration? https://github.com/yeoman/yo and prisma init
?
Suggested by @schickling
Newer versions of Photon.JS introduce a delegate API. See this issue for an example difference between before and after prisma/prisma-client-js#290.
pumpkins
is in a position to handle this better. Simply, it can add resolver middleware that will .then()
photon delegates returned in a resolver.
(Note: maybe we can push the solution down the stack but anyways we'll start here.)
Before:
t.field('blog', {
type: 'Blog',
args: {
id: intArg({ required: true }),
},
resolve(_root, args, ctx) {
return ctx.photon.blogs
.findOne({
where: {
id: args.id,
},
})
.then()
},
})
After:
t.field('blog', {
type: 'Blog',
args: {
id: intArg({ required: true }),
},
resolve(_root, args, ctx) {
return ctx.photon.blogs.findOne({
where: {
id: args.id,
},
})
},
})
The goal, is users can:
Example of prior art on how a nexus generate command could look.
We may also want to consider breaking the generate command into two:
nexus typegen
nexus schema
nexus generate-schema
nexus generate-typegen
...
etc. but not sure.
Scalable, strongly typed GraphQL schema development
VERSION
nexus/0.0.0-pr.275.004e62d darwin-x64 node-v12.12.0
USAGE
$ nexus [COMMAND]
COMMANDS
generate Generate Nexus artifacts. By default your config.outputs settings will be used but you can
override per output type via the respective flag.
❯ node bin/run generate --help
Generate Nexus artifacts. By default your config.outputs settings will be used but you can override per output type via the respective flag.
USAGE
$ nexus generate
OPTIONS
-e, --entrypoint=path
Path to your app's TypeScript module that will run directly or indirectly Nexus.makeSchema. By
default the following paths will be searched, picking the first match, or erroring out
* src/schema/index.ts
* src/schema.ts
* src/server.ts
* src/main.ts
* src/index.ts
* schema/index.ts
* schema.ts
* server.ts
* main.ts
* index.ts
ERROR
-g, --output-graphql-schema=path | 'true' (alias: 'default')
If and where to generate the GraphQL schema as a file in SDL form. Useful for debugging or teams
desiring SDL in their peer review process.
-t, --output-typescript-types=path | 'true' (alias: 'default')
If and where to generate the derived TypeScript types (aka. typegen). Useful for providing
autocompletion and type safety across all your definition blocks and resolvers.
ALIASES
$ nexus tg
We currently have a large part of the ts-node-dev codebase effectively disabled because we are not using the child hook module system. The complexity of this sub-system has something to do with TSC compilation perf at scale, IIRC. While things are functional as-is we should revisit this topic. We are not deleting the code yet, just commenting out.
dist is a folder name that makes sense for libraries or other tool-y things that will be "distributed". Pumpkins is for apps, and should be outputting a build to a build folder that will be deployed.
Not 100% clear what this will mean. Requires some research to understand all the things we can do to make deployment to heroku as easy as possible.
At a minimum it is documentation.
Not all users will want to work with a global singleton. We don't even know all the use-cases yet. How a large-scale app will look/feel. How testing will look/feel. However it is a great mode for certain scenarios, like a prototype or experiment, at least. It allows the DX to be that much closer to a DSL, some kind of purpose-built environment.
We have a spectrum of workflow modes to play with:
Declarative/Simple Imperative/Flexible
global singleton --------- exported singleton --------- exported constructors
app.schema.*
app
is globally availableapp
is a singletonapp
is not importableobjectType({
name: "User",
defintion(t) {
t.id('id')
}
})
app.schema.
to see intellisense. Schema func types can of course still be autocompleted but b/c they are global there is no organizing root parent property to filter for them as a menu (without, again, doing app.schema.
).app
is importable as named export from pumpkins
app
is a singletonimport { app } from 'pumpkins'
app.objectType({
name: "User",
defintion(t) {
t.id('id')
}
})
app
does not existimport { createApp, objectType } from 'pumpkins'
const User = objectType({
name: 'User',
defintion(t) {
t.id('id')
}
})
const app = createApp()
const app.addTypes(User)
First an important principal:
Constraints:
to achieve our principal means having optimized autocomplete. Having optimized autocomplete means codegen that "physically" changes the API surface of pumpkins.
Codegen means we need to think about out of box experience; postinstall
script hook has our back here.
codegen means we need to think beyond the api surface, into higher order config land; package.json
and ~/.config/pumpkuns
. The former is project-local settings, while the latter is system wide settings (aka. changing the default).
Other:
we need to pick a default 😬. I think exported constructors
is a bad default, because its the maximum complexity option which users should grow into/choose, not be forced to start with out of the box.
I think global singleton
or singleton
are eligible. @schickling has stated distaste for the former. But I'm not sure yet. I think we should get more feedback. If we take optional complexity into account, then starting from global singleton
seems to be the right starting point, and user gradually opts into more complexity as they go.
🚲 🏠
For example in a project using pumpkins
today:
❯ yarn pumpkins dev
yarn run v1.19.1
$ /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/.bin/pumpkins dev
Error: Cannot find module 'tar'
Require stack:
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@prisma/sdk/dist/getPackedPackage.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@prisma/sdk/dist/index.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/pumpkins/dist/framework/plugins/prisma.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/pumpkins/dist/framework/plugins/index.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/pumpkins/dist/cli/commands/dev.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@oclif/config/lib/plugin.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@oclif/config/lib/config.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@oclif/config/lib/index.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@oclif/command/lib/command.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/@oclif/command/lib/index.js
- /Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/pumpkins/dist/cli/index.js
at Object.<anonymous> (~/projects/prisma-labs/foobar/node_modules/@prisma/sdk/dist/getPackedPackage.js:19:31)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
But pumpkins
does not use tar
directly.
We can fix it for users now by including tar
but real fix is probably somewhere else.
Users should ignore pumpkins dot folder, but may:
.pumpkins
Then
Depends on #3
Motivation proposed in #292
Spec:
process.env.NEXUS_CONFIG
around the app entrypoint so that shouldGenerateArtifacts
is hardcoded to false--no-defuse-dev-mode
)NEXUS_CONFIG
and if found perform a merge. Ours should overwrite the shouldGenerateArtifacts
option.Vanilla project:
❯ yarn -s pumpkins build
🎃 Generating Nexus artifacts ...
🎃 Compiling ...
Error: node_modules/pumpkins/dist/utils/log.d.ts:1:26 - error TS7016: Could not find a declaration file for module 'debug'. '/Users/jasonkuhrt/projects/prisma-labs/foobar/node_modules/debug/src/index.js' implicitly has an 'any' type.
Try `npm install @types/debug` if it exists or add a new declaration (.d.ts) file containing `declare module 'debug';`
1 import createLogger from 'debug';
~~~~~~~
at Object.compile (~/projects/prisma-labs/foobar/node_modules/pumpkins/src/utils/tsc.ts:102:11)
at Build.compileProject (~/projects/prisma-labs/foobar/node_modules/pumpkins/src/cli/commands/build.ts:66:5)
at Build.run (~/projects/prisma-labs/foobar/node_modules/pumpkins/src/cli/commands/build.ts:40:47)
at Build._run (~/projects/prisma-labs/foobar/node_modules/@oclif/command/lib/command.js:44:20)
at Config.runCommand (~/projects/prisma-labs/foobar/node_modules/@oclif/config/lib/config.js:151:9)
at Main.run (~/projects/prisma-labs/foobar/node_modules/@oclif/command/lib/main.js:21:9)
at Main._run (~/projects/prisma-labs/foobar/node_modules/@oclif/command/lib/command.js:44:20)
Solution right now is to add ignore lib errors in tsconfig:
{
"compilerOptions": {
"skipLibCheck": true,
}
}
We need a place to encapsulate the bringing together of various application concerns. For example nexus makeSchema
and apollo server launch.
We can have a class/class-like representation of App
.
This will accumulate, probably, many features over time, so this issue is about the foundation, and making sure it can more or less support the kind of things we can expect to more or less want to be doing in the near future.
type createApp = (options:AppOptions) => App
App {
}
...stub issue
Had a meaty discussion with @Weakky on what our relationship to tsconfig is. It has some fields that we could:
ignore
rely on
use if present, otherwise fallback
Examples include:
compilerOptions.outDir
compilerOptions.rootDir
excludes
includes
Things to keep in mind:
JS users will not have tsconfig
JS users will still have a build step because of, at least, start
module that pumpkins codegens
pumpkins is the top level concept, the more its config is distributed and inconsistent (e.g what TS calls rootDir pumpkins calls sourceDir, because pumpkins has the concept of projectDir which rootDir is too ambiguous a name to have as peer) and owned by unaware lower level tools the less power we have to tackle DX, more room for error, for bad combinations of settings, etc.
pumpkins is a top level concept, having ways to configure it in tsconfig that JS users have to use package.json to configure just makes pumpkins projects more inconsistent, breaks the StackOverflow knowledge shareability, etc.
Otherwise build folders can end up with mixes of previous and current source folder structure/modules.
singleton
global
explicit
All commands and APIs should work even if prisma is not present.
typegen in dev mode does not block the sever boot so that feedback latency is reduced. In build this is not good, as it means the server boots during typegen, leading to weird potential things like port in use errors if dev mode is running on the same machine etc.
To solve this we need to await typegen rather than doing in the background.
Today user needs to manage additional deps (ts-node-dev
) and remember to use flags correctly (--tree-kill
, --transpile-only
). We should simplify this with a nexus dev
command. In the future we can integrate optionally prisma dev
seamlessly. Also in the future we could seamlessly support JS projects by just being a wrapper around e.g. nodemon.
Instead of doing spawn
s tsmon
or ts-node
let's try working with the TS compiler API directly? If there could be value in using ts-node
as library or tsmorph
library let's explore that too.
first version:
Instances
Given tsconfig:
{
"compilerOptions": {
"skipLibCheck": true,
"rootDir": "app",
"strict": true
}
}
build leads to incorrect folder structure
❯ tree dist
dist
└── app
├── app.js
├── app__original__.js
└── schema.js
There should be no app nesting there.
Underlying nexus-prisma feature graphql-nexus/nexus-plugin-prisma#544
Encode a doctor check for the --types
gotchya #284.
Prisma dev workflow requires user to enter into a different and less informed workflow.
Different workflow means user has to start a new terminal, use a different cli, interact with different styles/patterns of feedback be it status updates or error messages.
Less informed workflow means pumpkins
knows more about the user's intent because pumpkins
is the user's application framework. In contrast Prisma knows nothing about the app. So for example:
pumpkins
does know. pumpkins
knows that Photon should be generated and because it controls the consumption side of photon also therefore knows the path it should be generated at.Use a Prisma SDK within the implementation of pumpkins
to control Prisma during pumpkins dev
.
Be able to host studio ourselves
Be able to detect when a semantic change to PSL has occurred
Be able to programatically generate photon without its needing to be in schema.prisma
Be able to ask users to confirm a destructive database migration. This means an API that makes it easy to:
Be able to easily extract PSL content, manipulate it, represent it (see example error message above)
We Host Studio
$ pumpkins dev
==> Good Morning! I'm launching your app now at http://localhost:3000
Some stuff to checkout:
/topology ..... Your app crud, mapping, and other topology
/studio ....... The Prisma control plane for your data layer
/playground ... The GraphQL Playground, make queries and see results
/graphql ...... Standard schema introspection endpoint
We watch for meaningful changes to the PSL file
We regenerate photon when the PSL file changes
We migrate the dev database when the PSL file changes
==> Data Layer Change
Hey I saw some changes in your data layer:
1. A new Book model
2. Three fields removed from User model
+ model Book {
+ id Int
+ title String
+ }
model User {
- location Location
- middleName String
- lastActive DateTime
...
}
I'll propagate these changes for you now!
Regenerating your photon client...
Migrating your database...
React to meaningful changes (example 2)
==> REBOOT (546ms)
--> SCAFFOLD OPTION
Hey It looks like you added a new model Car to your PSL. Would you like
to generate a GraphQL Car Object definition?
objectType({
name: "Car"
definition(t) {
t.model.model()
t.model.year()
t.model.make()
}
})
> Copy into your clipboard (c)
Generate file (f) /server/types/Car.ts
The user does not have to declare photon themselves
// What user has to write
// schema.prisma
model User {
// ...
}
model Book {
// ...
}
// What pumpkins takes care of
// User does NOT have to write this
// User does NOT see this
generator photonjs {
provider = "photonjs"
path = ".pumpkins/photon"
}
The user receives rich inline/in-context PSL content rendering so that we can do things like so:
==> REBOOT (145ms)
--> WARNING
I found that some of your field projections do not have
corresponding prisma model fields:
User Object @ types/User.ts
120| defintion(t) {
121| t.model.name()
122| t.model.role()
| ~~~~ ---------------------
123| }) |
124| |
|
API Layer |
--------------------------------------------------------------------------------
Data Layer |
|
User Model @ prisma/schema.prisma |
|
130| model User { |
131| address String |
132| birthday Date |
133| friends Friend[] |
134| name String |
| <------------- missing |
135| sex Sex
136| }
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.