diff --git a/README.md b/README.md index 7ffa4f24f..4cc402c8e 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ export const schema = makeSchema({ ##### Shortterm - [x] ([#59](https://github.com/prisma/nexus-prisma/issues/59)) Support for Prisma Model field type `BigInt` -- [ ] ([#94](https://github.com/prisma/nexus-prisma/issues/94)) Support for Prisma Model field type `Decimal` +- [x] ([#94](https://github.com/prisma/nexus-prisma/issues/94)) Support for Prisma Model field type `Decimal` - [ ] Improved JSDoc for relation 1:1 & 1:n fields ##### Midterm @@ -227,8 +227,9 @@ However some of the Prisma scalars do not have a natural standard representation | `DateTime` | `DateTime` | `dateTime` | [DateTime](https://www.graphql-scalars.dev/docs/scalars/datetime) | | | `BigInt` | `BigInt` | `bigInt` | [BigInt](https://www.graphql-scalars.dev/docs/scalars/big-int) | [JavaScript BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) | | `Bytes` | `Bytes` | `bytes` | [Byte](https://www.graphql-scalars.dev/docs/scalars/byte/) | [Node.js Buffer](https://nodejs.org/api/buffer.html#buffer_buffer) | +| `Decimal` | `Decimal` | `decimal` | (internal) | Uses [Decimal.js](https://github.com/MikeMcl/decimal.js) | -> **Note:** Not all Prisma scalar mappings are implemented yet: `Decimal`, `Unsupported` +> **Note:** Not all Prisma scalar mappings are implemented yet: `Unsupported` > **Note:** BigInt is supported in Node.js since version [10.4.0](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility) however to support BigInt in `JSON.parse`/`JSON.stringify` you must use [`json-bigint-patch`](https://github.com/ardatan/json-bigint-patch) otherwise BigInt values will be serialized as strings. diff --git a/package.json b/package.json index a82296941..133f4558d 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "dependencies": { "@prisma/generator-helper": "^2.27.0", "debug": "^4.3.2", + "decimal.js": "^10.3.1", "dindist": "^1.0.2", "expand-tilde": "^2.0.2", "fs-jetpack": "^4.1.0", diff --git a/src/entrypoints/scalars.ts b/src/entrypoints/scalars.ts index 9ede229c3..d41add3eb 100644 --- a/src/entrypoints/scalars.ts +++ b/src/entrypoints/scalars.ts @@ -1,6 +1,7 @@ import { BigInt } from '../scalars/BigInt' import { Bytes } from '../scalars/Bytes' import { DateTime } from '../scalars/DateTime' +import { Decimal } from '../scalars/Decimal' import { Json } from '../scalars/Json' /** @@ -13,6 +14,8 @@ import { Json } from '../scalars/Json' * | `DateTime` | `DateTime` | `dateTime` | [DateTime](https://www.graphql-scalars.dev/docs/scalars/datetime) | | * | `BigInt` | `BigInt` | `bigInt` | [BigInt](https://www.graphql-scalars.dev/docs/scalars/big-int) | [JavaScript BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) | * | `Bytes` | `Bytes` | `bytes` | [Byte](https://www.graphql-scalars.dev/docs/scalars/byte/) | [Node.js Buffer](https://nodejs.org/api/buffer.html#buffer_buffer) | + * | `Decimal` | `Decimal` | `decimal` | (internal) | Uses [Decimal.js](https://github.com/MikeMcl/decimal.js) + * |. * * @example * @@ -76,9 +79,10 @@ const NexusPrismaScalars = { BigInt, Bytes, DateTime, + Decimal, Json, } export default NexusPrismaScalars -export { BigInt, Bytes, DateTime, Json } +export { BigInt, Bytes, DateTime, Decimal, Json } diff --git a/src/generator/models/declaration.ts b/src/generator/models/declaration.ts index 606eea445..98a0be10f 100644 --- a/src/generator/models/declaration.ts +++ b/src/generator/models/declaration.ts @@ -306,7 +306,7 @@ export function fieldTypeToGraphQLType( return 'Bytes' } case 'Decimal': { - return StandardGraphQLScalarTypes.String + return 'Decimal' } default: { return allCasesHandled(typeName) diff --git a/src/scalars/Decimal.ts b/src/scalars/Decimal.ts new file mode 100644 index 000000000..a6e0ee127 --- /dev/null +++ b/src/scalars/Decimal.ts @@ -0,0 +1,62 @@ +import { asNexusMethod } from 'nexus' +import * as DecimalJs from 'decimal.js' + +import { GraphQLScalarType, Kind } from 'graphql' + +/** + * A Nexus scalar type definition for arbitrary-precision Decimal type. + * + * Contributes a scalar to your GraphQL schema called `Decimal`. + * + * Contributes a `t` `[1]` helper method called `decimal` + * + * `[1]` A `t` helper method refers to a method on the argument given to a `definition` method. Helper methods + * here typically help you quickly create new fields. + * + * @example + * + * import { makeSchema, objectType } from 'nexus' + * import { Decimal } from 'nexus-prisma/scalars' + * + * SomeObject = objectType({ + * name: 'SomeObject', + * definition(t) { + * t.decimal('someDecimalField') + * }, + * }) + * + * makeSchema({ + * types: [Decimal, SomeObject], + * }) + * + */ +export const Decimal = asNexusMethod( + /** + * Copied from prisma-graphql-type-decimal. + * + * @see https://github.com/unlight/prisma-graphql-type-decimal + */ + new GraphQLScalarType({ + name: 'Decimal', + description: 'An arbitrary-precision Decimal type', + /** + * Value sent to the client + */ + serialize(value: DecimalJs.Decimal) { + return value.toString() + }, + /** + * Value from the client + */ + parseValue(value: DecimalJs.Decimal.Value) { + return new DecimalJs.Decimal(value) + }, + parseLiteral(ast) { + if (ast.kind === Kind.INT || ast.kind === Kind.FLOAT || ast.kind === Kind.STRING) { + return new DecimalJs.Decimal(ast.value) + } + return null + }, + }), + 'decimal' +) diff --git a/tests/e2e/__snapshots__/e2e.test.ts.snap b/tests/e2e/__snapshots__/e2e.test.ts.snap index a23cf93da..8ba5abe5c 100644 --- a/tests/e2e/__snapshots__/e2e.test.ts.snap +++ b/tests/e2e/__snapshots__/e2e.test.ts.snap @@ -8,6 +8,7 @@ Object { "BigIntManually": null, "BytesManually": null, "DateTimeManually": null, + "DecimalManually": null, "JsonManually": null, "someBigIntField": "9007199254740991", "someBytesField": Object { @@ -15,6 +16,7 @@ Object { "type": "Buffer", }, "someDateTimeField": "2021-05-10T20:42:46.609Z", + "someDecimalField": "24.454545", "someEnumA": "alpha", "someJsonField": Object {}, }, @@ -46,14 +48,19 @@ A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the \`da \\"\\"\\" scalar DateTime +\\"\\"\\"An arbitrary-precision Decimal type\\"\\"\\" +scalar Decimal + type Foo { BigIntManually: BigInt BytesManually: Bytes DateTimeManually: DateTime + DecimalManually: Decimal JsonManually: Json someBigIntField: BigInt! someBytesField: Bytes! someDateTimeField: DateTime! + someDecimalField: Decimal! someEnumA: SomeEnumA someJsonField: Json! } @@ -98,6 +105,10 @@ declare global { * A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the \`date-time\` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ dateTime(fieldName: FieldName, opts?: core.CommonInputFieldConfig): void // \\"DateTime\\"; + /** + * An arbitrary-precision Decimal type + */ + decimal(fieldName: FieldName, opts?: core.CommonInputFieldConfig): void // \\"Decimal\\"; /** * The \`JSONObject\` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ @@ -119,6 +130,10 @@ declare global { * A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the \`date-time\` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ dateTime(fieldName: FieldName, ...opts: core.ScalarOutSpread): void // \\"DateTime\\"; + /** + * An arbitrary-precision Decimal type + */ + decimal(fieldName: FieldName, ...opts: core.ScalarOutSpread): void // \\"Decimal\\"; /** * The \`JSONObject\` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ @@ -147,6 +162,7 @@ export interface NexusGenScalars { BigInt: any Bytes: any DateTime: any + Decimal: any Json: any } @@ -174,10 +190,12 @@ export interface NexusGenFieldTypes { BigIntManually: NexusGenScalars['BigInt'] | null; // BigInt BytesManually: NexusGenScalars['Bytes'] | null; // Bytes DateTimeManually: NexusGenScalars['DateTime'] | null; // DateTime + DecimalManually: NexusGenScalars['Decimal'] | null; // Decimal JsonManually: NexusGenScalars['Json'] | null; // Json someBigIntField: NexusGenScalars['BigInt']; // BigInt! someBytesField: NexusGenScalars['Bytes']; // Bytes! someDateTimeField: NexusGenScalars['DateTime']; // DateTime! + someDecimalField: NexusGenScalars['Decimal']; // Decimal! someEnumA: NexusGenEnums['SomeEnumA'] | null; // SomeEnumA someJsonField: NexusGenScalars['Json']; // Json! } @@ -194,10 +212,12 @@ export interface NexusGenFieldTypeNames { BigIntManually: 'BigInt' BytesManually: 'Bytes' DateTimeManually: 'DateTime' + DecimalManually: 'Decimal' JsonManually: 'Json' someBigIntField: 'BigInt' someBytesField: 'Bytes' someDateTimeField: 'DateTime' + someDecimalField: 'Decimal' someEnumA: 'SomeEnumA' someJsonField: 'Json' } diff --git a/tests/e2e/e2e.test.ts b/tests/e2e/e2e.test.ts index 8e62faac0..0e278c6ec 100644 --- a/tests/e2e/e2e.test.ts +++ b/tests/e2e/e2e.test.ts @@ -120,6 +120,7 @@ it('When bundled custom scalars are used the project type checks and generates e id String @id someJsonField Json someDateTimeField DateTime + someDecimalField Decimal someBytesField Bytes someBigIntField BigInt someEnumA SomeEnumA @@ -143,7 +144,7 @@ it('When bundled custom scalars are used the project type checks and generates e { filePath: `prisma/seed.ts`, content: dedent/*ts*/ ` - import { PrismaClient } from '@prisma/client' + import { PrismaClient, Prisma } from '@prisma/client' main() @@ -155,6 +156,7 @@ it('When bundled custom scalars are used the project type checks and generates e data: { id: 'foo1', someDateTimeField: new Date("2021-05-10T20:42:46.609Z"), + someDecimalField: 24.454545, someBytesField: Buffer.from([]), someJsonField: {}, someBigIntField: BigInt(9007199254740991), @@ -214,11 +216,13 @@ it('When bundled custom scalars are used the project type checks and generates e t.json('JsonManually') t.dateTime('DateTimeManually') t.bytes('BytesManually') + t.decimal('DecimalManually') t.bigInt('BigIntManually') t.field(Foo.someBigIntField) t.field(Foo.someJsonField) t.field(Foo.someDateTimeField) t.field(Foo.someBytesField) + t.field(Foo.someDecimalField) }, }), ] @@ -380,11 +384,13 @@ it('When bundled custom scalars are used the project type checks and generates e JsonManually DateTimeManually BytesManually + DecimalManually BigIntManually someEnumA someJsonField someDateTimeField someBytesField + someDecimalField someBigIntField } } diff --git a/tests/integration/json.test.ts b/tests/integration/json.test.ts index 44bda6ff0..a5ded9d06 100644 --- a/tests/integration/json.test.ts +++ b/tests/integration/json.test.ts @@ -18,6 +18,7 @@ testIntegration({ NexusPrismaScalars.Bytes, NexusPrismaScalars.BigInt, NexusPrismaScalars.DateTime, + NexusPrismaScalars.Decimal, NexusPrismaScalars.Json, objectType({ name: Foo.$name, diff --git a/tests/unit/customScalarsModule.test.ts b/tests/unit/customScalarsModule.test.ts index 9fb361da6..23b12ea91 100644 --- a/tests/unit/customScalarsModule.test.ts +++ b/tests/unit/customScalarsModule.test.ts @@ -9,6 +9,7 @@ Array [ "BigInt", "Bytes", "DateTime", + "Decimal", "Json", "default", ] @@ -21,6 +22,7 @@ it('scalars can be accessed via a default import', () => { "BigInt", "Bytes", "DateTime", + "Decimal", "Json", ] `) diff --git a/yarn.lock b/yarn.lock index 901259228..6327df124 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2080,6 +2080,11 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== +decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"