Skip to content

Commit

Permalink
feat: Json & DateTime custom scalar support (#9)
Browse files Browse the repository at this point in the history
closes #8
  • Loading branch information
Jason Kuhrt authored Mar 8, 2021
1 parent 4f83b26 commit df51143
Show file tree
Hide file tree
Showing 20 changed files with 979 additions and 342 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn global add yalc
- run: yarn --frozen-lockfile
- run: yarn -s build
- run: yarn -s test
2 changes: 1 addition & 1 deletion .github/workflows/trunk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
branches: [main]

jobs:

test:
strategy:
matrix:
Expand All @@ -18,6 +17,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn global add yalc
- run: yarn --frozen-lockfile
- run: yarn -s build

Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,9 @@ dist

# TernJS port file
.tern-port

# Facades
scalars.d.ts
scalars.js
plugin.js
plugin.d.ts
81 changes: 79 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,22 @@
Official Prisma plugin for Nexus.
**Currently in development - not to be used in Production.** Follow the progress from [here](https://github.com/graphql-nexus/nexus-plugin-prisma/issues/1039).

## Example
## Installation

```
npm add nexus-prisma graphql @prisma/client
npm add --dev prisma
```

> `graphql` and `@prisma/client` are peer dependencies. `prisma` is for the Prisma CLI which you'll probably want during development.
## Usage

1. Add a `nexus-prisma` generator block to your Prisma Schema.
1. Run `prisma generate` in your terminal.
1. Import models from `nexus-prisma` and then pass them to your Nexus type definition and field definition configurations. In this way you will be effectively projecting models from your data layer into GraphQL types in your API layer.

### Example

```prisma
Expand All @@ -15,7 +30,7 @@ generator client {
generator nexusPrisma {
// This is a temporary name, soon will be just "nexus-prisma" (pending a change in Prisma core).
provider = "nexus-prisma-2"
provider = "nexus-prisma"
}
Expand Down Expand Up @@ -77,6 +92,64 @@ objectType({
})
```

### Scalar Mapping & Custom GraphQL Scalars for Native Prisma Scalars

Like GraphQL [Prisma has the concept of scalar types](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference/#model-field-scalar-types). Some of the Prisma scalars can be naturally mapped to standard GraphQL scalars. The mapping is as follows:

**Prisma Standard Scalar to GraphQL Standard Scalar Mapping**

| Prisma | GraphQL |
| ------------------- | --------- |
| `Boolean` | `Boolean` |
| `String` | `String` |
| `Int` | `Int` |
| `Float` | `Float` |
| `String` with `@id` | `ID` |

However some of the Prisma scalars do not have a natural standard representation in GraphQL. For these cases Nexus Prisma generates code that references type names matching those scalar names in Prisma. Then, you are expected to define those custom scalar types in your GraphQL API. Nexus Prisma ships with pre-defined mappings in `nexus-prisma/scalars` you _can_ use for convenience. The mapping is as follows:

**Prisma Standard Scalar to GraphQL Custom Scalar Mapping**

| Prisma | GraphQL | GraphQL Scalar Implementation |
| ---------- | ---------- | ----------------------------------------------------------------- |
| `Json` | `Json` | [JsonObject](https://github.com/Urigo/graphql-scalars#jsonobject) |
| `DateTime` | `DateTime` | [DateTime](https://github.com/Urigo/graphql-scalars#datetime) |

> **Note:** Not all Prisma scalar mappings are implemented yet: `Bytes`, `BigInt`, `Decimal`, `Unsupported`
While you are not required to use the implementations supplied by Nexus Prisma, you _are required to define custom scalars whose name matches the above mapping_.

Here is an example using the Nexus Prisma pre-defined custom scalars:

```ts
import * as customScalars from 'nexus-prisma/scalars'
import { makeSchema } from 'nexus'

makeSchema({
types: [customScalars],
})
```

The following is a brief example how you could roll the implementations yourself:

```ts
import { GraphQLScalarType } from 'graphql'
import { JSONObjectResolver, DateTimeResolver } from 'graphql-scalars'
import { asNexusMethod, makeSchema } from 'nexus'

const jsonScalar = new GraphQLScalarType({
...JSONObjectResolver,
// Override the default 'JsonObject' name with one that matches what Nexus Prisma expects.
name: 'Json',
})

const dateTimeScalar = new GraphQLScalarType(DateTimeResolver)

makeSchema({
types: [asNexusMethod(jsonScalar, 'json'), asNexusMethod(dateTimeScalar, 'dateTime')],
})
```

### Prisma ID field to GraphQL ID scalar type mapping

All `@id` fields in your Prisma Schema get projected as `ID` types, not `String` types.
Expand Down Expand Up @@ -185,3 +258,7 @@ If a peer dependenvy is not installed it `nexus-prisma` will log an error and th
NO_PEER_DEPENDENCY_CHECK=true|1
PEER_DEPENDENCY_CHECK=false|0
```

## Notes

- Versions of `nexus-prisma` package prior to `0.20` were a completely different version of the API, and had also become deprecated at one point to migrate to `nexus-plugi-prisma` when Nexus Framework was being worked on. All of that is history.
33 changes: 20 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"license": "MIT",
"files": [
"dist",
"plugin.js"
"plugin.js",
"plugin.d.ts",
"scalars.js",
"scalars.d.ts"
],
"bin": {
"nexus-prisma": "./dist/cli/nexus-prisma.js",
"nexus-prisma-2": "./dist/cli/nexus-prisma.js"
"nexus-prisma": "./dist/cli/nexus-prisma.js"
},
"scripts": {
"format": "prettier --write .",
Expand All @@ -20,8 +22,9 @@
"lint:check": "eslint . --ext .ts,.tsx --max-warnings 0",
"dev": "tsc --build --watch",
"dev:ts": "yarn dev",
"dev:yalc": "nodemon --exec 'yalc push --no-scripts && chmod +x /Users/jasonkuhrt/projects/prisma/cloud/node_modules/.bin/nexus-prisma-2' --watch 'dist/**/*'",
"build": "yarn clean && tsc",
"dev:yalc": "nodemon --exec 'yalc push --no-scripts' --watch 'dist/**/*'",
"build:module-facades": "ts-node scripts/build-module-facades",
"build": "yarn clean && yarn build:module-facades && tsc",
"test": "jest",
"tdd": "jest --watch",
"clean": "rm -rf dist && rm -rf node_modules/.cache",
Expand All @@ -32,13 +35,14 @@
},
"devDependencies": {
"@prisma-labs/prettier-config": "0.1.0",
"@prisma/client": "2.17.0",
"@prisma/sdk": "^2.17.0",
"@prisma/client": "2.18.0",
"@prisma/sdk": "^2.18.0",
"@types/debug": "^4.1.5",
"@types/jest": "26.0.20",
"@types/lodash": "^4.14.168",
"@types/pluralize": "^0.0.29",
"@types/semver": "^7.3.4",
"@types/strip-ansi": "^5.2.1",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"dripip": "0.10.0",
Expand All @@ -53,29 +57,32 @@
"nexus": "^1.0.0",
"nodemon": "^2.0.7",
"prettier": "2.2.1",
"prettier-plugin-jsdoc": "^0.3.12",
"prisma": "2.17.0",
"ts-jest": "26.5.1",
"prettier-plugin-jsdoc": "^0.3.13",
"prisma": "2.18.0",
"strip-ansi": "^6.0.0",
"ts-jest": "26.5.3",
"ts-node": "^9.1.1",
"type-fest": "^0.21.2",
"typescript": "^4.2.2"
"typescript": "^4.2.3"
},
"prettier": "@prisma-labs/prettier-config",
"peerDependencies": {
"@prisma/client": "2.17.x",
"nexus": "^1.0.0"
},
"dependencies": {
"@prisma/generator-helper": "^2.17.0",
"@prisma/generator-helper": "^2.18.0",
"debug": "^4.3.1",
"endent": "^2.0.1",
"fs-jetpack": "^4.1.0",
"graphql-scalars": "^1.9.0",
"kleur": "^4.1.4",
"ono": "^7.1.3",
"pkg-up": "^3.1.0",
"pluralize": "^8.0.0",
"semver": "^7.3.4",
"setset": "^0.0.6"
"setset": "^0.0.6",
"tslib": "^2.1.0"
},
"nodemonConfig": {
"events": {
Expand Down
1 change: 0 additions & 1 deletion plugin.js

This file was deleted.

52 changes: 52 additions & 0 deletions scripts/build-module-facades.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Module Facades
*
* This script builds the modules that will be consumed publically. They front the actual code inside ./dist.
* The problem being solved here is that it allows consumers to do e.g. this:
*
* Import { ... } from 'nexus/testing'
*
* Instead of:
*
* Import { ... } from 'nexus/dist/testing'
*
* Whatever modules are written here should be:
*
* 1. ignored in .gitignore.
* 2. added to the package.json files array
*/

import * as fs from 'fs-jetpack'
import * as lo from 'lodash'
import * as os from 'os'
import * as path from 'path'
import { PackageJson } from 'type-fest'

generateModuleFacades([
['scalars.d.ts', "export * from './dist/scalars'"],
['scalars.js', "module.exports = require('./dist/scalars')"],

['plugin.d.ts', "export * from './dist/plugin'"],
['plugin.js', "exports.plugin = reuqire('./dist/plugin')"],
])

function generateModuleFacades(facades: ModuleFacade[]) {
// Write facade files

for (const facade of facades) {
fs.write(facade[0], facade[1] + os.EOL)
}

// Handle package.json files array

const packageJsonPath = path.join(__dirname, '..', 'package.json')
const packageJson = fs.read(packageJsonPath, 'json') as PackageJson

packageJson.files = lo.uniq([...(packageJson.files ?? []), ...facades.map((facade) => facade[0])])

const packageJsonString = JSON.stringify(packageJson, null, 2) + os.EOL

fs.write(packageJsonPath, packageJsonString)
}

type ModuleFacade = [filePath: string, fileContents: string]
26 changes: 13 additions & 13 deletions src/generator/models/declaration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DMMF } from '@prisma/generator-helper'
import endent from 'endent'
import { LiteralUnion } from 'type-fest'
import { GraphQLScalarType, graphQLScalarTypes } from '../../helpers/graphql'
import { StandardGraphQLScalarType, StandardgraphQLScalarTypes } from '../../helpers/graphql'
import { PrismaScalarType } from '../../helpers/prisma'
import { allCasesHandled } from '../../helpers/utils'
import { jsDocForField, jsDocForModel } from '../helpers/JSDocTemplates'
Expand All @@ -27,7 +27,7 @@ export function renderTypeScriptDeclarationForDocumentModels(dmmf: DMMF.Document
// Types
//
namespace $Types {
declare namespace $Types {
${models.map(renderTypeScriptDeclarationForModel).join('\n\n')}
}
Expand Down Expand Up @@ -113,7 +113,7 @@ function renderNexusType2(field: DMMF.Field): string {
}

/** Map the fields type to a GraphQL type */
export function fieldTypeToGraphQLType(field: DMMF.Field): LiteralUnion<GraphQLScalarType, string> {
export function fieldTypeToGraphQLType(field: DMMF.Field): LiteralUnion<StandardGraphQLScalarType, string> {
const fieldKind = field.kind

switch (fieldKind) {
Expand All @@ -123,33 +123,33 @@ export function fieldTypeToGraphQLType(field: DMMF.Field): LiteralUnion<GraphQLS
switch (typeName) {
case 'String': {
if (field.isId) {
return graphQLScalarTypes.ID
return StandardgraphQLScalarTypes.ID
}
return graphQLScalarTypes.String
return StandardgraphQLScalarTypes.String
}
case 'Int': {
return graphQLScalarTypes.Int
return StandardgraphQLScalarTypes.Int
}
case 'Boolean': {
return graphQLScalarTypes.Boolean
return StandardgraphQLScalarTypes.Boolean
}
case 'Float': {
return graphQLScalarTypes.Float
return StandardgraphQLScalarTypes.Float
}
case 'BigInt': {
return graphQLScalarTypes.String
return StandardgraphQLScalarTypes.String
}
case 'DateTime': {
return graphQLScalarTypes.String
return 'DateTime'
}
case 'Json': {
return graphQLScalarTypes.String
return 'Json'
}
case 'Bytes': {
return graphQLScalarTypes.String
return StandardgraphQLScalarTypes.String
}
case 'Decimal': {
return graphQLScalarTypes.String
return StandardgraphQLScalarTypes.String
}
default: {
return allCasesHandled(typeName)
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type GraphQLScalarType = 'ID' | 'String' | 'Int' | 'Float' | 'Boolean'
export type StandardGraphQLScalarType = 'ID' | 'String' | 'Int' | 'Float' | 'Boolean'

export const graphQLScalarTypes = {
export const StandardgraphQLScalarTypes = {
ID: 'ID',
String: 'String',
Float: 'Float',
Expand Down
5 changes: 5 additions & 0 deletions src/scalars/DateTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GraphQLScalarType } from 'graphql'
import { DateTimeResolver } from 'graphql-scalars'
import { asNexusMethod } from 'nexus'

export const dateTimeScalar = asNexusMethod(new GraphQLScalarType(DateTimeResolver), 'dateTime')
12 changes: 12 additions & 0 deletions src/scalars/Json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { GraphQLScalarType } from 'graphql'
import { JSONObjectResolver } from 'graphql-scalars'
import { asNexusMethod } from 'nexus'

export const jsonScalar = asNexusMethod(
new GraphQLScalarType({
...JSONObjectResolver,
// Override the default 'JsonObject' name with one that matches what Nexus Prisma expects.
name: 'Json',
}),
'json'
)
2 changes: 2 additions & 0 deletions src/scalars/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './DateTime'
export * from './Json'
Loading

0 comments on commit df51143

Please sign in to comment.