├── .czrc
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── README.md
├── Taskfile
├── eslint.config.mjs
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── prettier.config.mjs
├── release.config.cjs
├── schema.prisma
├── src
├── index.spec.ts
└── index.ts
├── stryker.conf.mjs
└── tsconfig.json
/.czrc:
--------------------------------------------------------------------------------
1 | {
2 | "path": "./node_modules/cz-conventional-changelog",
3 | "disableScopeLowerCase": false,
4 | "disableSubjectLowerCase": true
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 | on:
3 | - push
4 | - pull_request
5 |
6 | jobs:
7 | build:
8 | name: 'Test on Node ${{ matrix.node }} and ${{ matrix.os }}'
9 | runs-on: '${{ matrix.os }}'
10 | strategy:
11 | # max-parallel: 1
12 | matrix:
13 | node:
14 | - 22
15 | os:
16 | - ubuntu-latest
17 | # - windows-latest
18 | # - macOS-latest
19 | steps:
20 | - name: 'Checkout Repository'
21 | uses: actions/checkout@v3
22 |
23 | - name: 'Setup Node ${{ matrix.node }}'
24 | uses: actions/setup-node@v3
25 | with:
26 | node-version: '${{ matrix.node }}'
27 |
28 | - name: 'Install Depependencies'
29 | run: npm install --force
30 |
31 | - name: 'Tests'
32 | run: |
33 | npm run test
34 |
35 | - name: 'Mutation Tests'
36 | run: |
37 | npm run test:m
38 |
39 | release:
40 | name: 'Release'
41 | runs-on: ubuntu-latest
42 | needs: build
43 | if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/next')
44 |
45 | steps:
46 | - name: 'Checkout Repository'
47 | uses: actions/checkout@v3
48 |
49 | - name: 'Setup Node'
50 | uses: actions/setup-node@v3
51 | with:
52 | node-version: 22
53 |
54 | - name: 'Install Depependencies'
55 | run: |
56 | npm install --force
57 |
58 | - name: 'Build'
59 | run: |
60 | npm run build
61 |
62 | - name: 'Release'
63 | run: |
64 | npx semantic-release
65 | env:
66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### https://raw.github.com/github/gitignore/5d896f6791c4257b74696714c66b2530b8d95a51/Node.gitignore
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 | # nyc test coverage
18 | .nyc_output
19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20 | .grunt
21 | # Bower dependency directory (https://bower.io/)
22 | bower_components
23 | # node-waf configuration
24 | .lock-wscript
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 | # Dependency directories
28 | node_modules/
29 | jspm_packages/
30 | # Typescript v1 declaration files
31 | typings/
32 | # Optional npm cache directory
33 | .npm
34 | # Optional eslint cache
35 | .eslintcache
36 | # Optional REPL history
37 | .node_repl_history
38 | # Output of 'npm pack'
39 | *.tgz
40 | # Yarn Integrity file
41 | .yarn-integrity
42 | # dotenv environment variables file
43 | .env
44 | # Custom
45 | dist/
46 | ~*
47 | .idea
48 | .awcache
49 | .vscode
50 | .rts2_cache_*
51 | .stryker-tmp
52 | reports
53 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 3.0.1 (2025-04-05)
2 |
3 | * build: Update scripts ([06c067b](https://github.com/unlight/prisma-graphql-type-decimal/commit/06c067b))
4 | * fix: Update peer dependencies ([4248fe0](https://github.com/unlight/prisma-graphql-type-decimal/commit/4248fe0))
5 | * chore: Updated packages ([774917b](https://github.com/unlight/prisma-graphql-type-decimal/commit/774917b))
6 |
7 | ## [3.0.0](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.6...v3.0.0) (2023-04-02)
8 |
9 |
10 | ### ⚠ BREAKING CHANGES
11 |
12 | * `Decimal` imports from `@prisma/client/runtime/library`, require Prisma v4.12+
13 |
14 | ### Bug Fixes
15 |
16 | * Deprecation warning `imports from "@prisma/client/runtime" are deprecated` ([76ffa5d](https://github.com/unlight/prisma-graphql-type-decimal/commit/76ffa5d3e663b6b9befcbaf1a6e2d0e7ee2875d8))
17 |
18 | ## [2.0.6](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.5...v2.0.6) (2023-01-19)
19 |
20 |
21 | ### Bug Fixes
22 |
23 | * Correct semver range ([7b7bf2e](https://github.com/unlight/prisma-graphql-type-decimal/commit/7b7bf2ef15ff3274299209851031d7c756d13a57))
24 |
25 | ## [2.0.5](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.4...v2.0.5) (2023-01-12)
26 |
27 |
28 | ### Bug Fixes
29 |
30 | * Decimal can be null ([04a27f3](https://github.com/unlight/prisma-graphql-type-decimal/commit/04a27f31582bb70a407ded5bb76897bebb53b6f0)), closes [#7](https://github.com/unlight/prisma-graphql-type-decimal/issues/7)
31 |
32 | ## [2.0.4](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.3...v2.0.4) (2022-07-09)
33 |
34 |
35 | ### Bug Fixes
36 |
37 | * **esm:** Targets must start with `./` ([8ff34b6](https://github.com/unlight/prisma-graphql-type-decimal/commit/8ff34b6d07b1cdf245cc0548ef4c86ac897045b1)), closes [#4](https://github.com/unlight/prisma-graphql-type-decimal/issues/4)
38 |
39 | ## [2.0.3](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.2...v2.0.3) (2022-07-08)
40 |
41 |
42 | ### Bug Fixes
43 |
44 | * **esm:** Fix import path ([8cdae9e](https://github.com/unlight/prisma-graphql-type-decimal/commit/8cdae9ed1f7b1307d4777933a74068b4a17e0d7a)), closes [#3](https://github.com/unlight/prisma-graphql-type-decimal/issues/3)
45 |
46 | ### [2.0.2](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.1...v2.0.2) (2022-05-22)
47 |
48 |
49 | ### Bug Fixes
50 |
51 | * Transform to decimal ([ab2805d](https://github.com/unlight/prisma-graphql-type-decimal/commit/ab2805d989e9a346af80a9f7184a71ec00c32241))
52 |
53 | ### [2.0.1](https://github.com/unlight/prisma-graphql-type-decimal/compare/v2.0.0...v2.0.1) (2022-05-21)
54 |
55 |
56 | ### Bug Fixes
57 |
58 | * **class-transform:** Decimal value object factory ([edf2723](https://github.com/unlight/prisma-graphql-type-decimal/commit/edf27235769e7368a5caac314cfcecb168af70fc))
59 |
60 | ## [2.0.0](https://github.com/unlight/prisma-graphql-type-decimal/compare/v1.0.0...v2.0.0) (2022-02-20)
61 |
62 |
63 | ### ⚠ BREAKING CHANGES
64 |
65 | * GraphQL >= 16, Prisma >= 3.8
66 |
67 | ### Miscellaneous Chores
68 |
69 | * Updated dependencies ([7e2cdf5](https://github.com/unlight/prisma-graphql-type-decimal/commit/7e2cdf5a57c284eb1fcbadf26b19d172c2901221))
70 |
71 | ## 1.0.0 (2021-04-23)
72 |
73 |
74 | ### Features
75 |
76 | * First implementation ([2164365](https://github.com/unlight/prisma-graphql-type-decimal/commit/2164365465bb6b780c14a9dfbddad4f9eeb49e70))
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # prisma-graphql-type-decimal
2 |
3 | GraphQL type for Prisma's Decimal scalar, wrapper around [decimal.js](https://github.com/MikeMcl/decimal.js/)
4 | Created because `@prisma/client` has bundled `decimal.js`
5 |
6 | ## Install
7 |
8 | ```sh
9 | npm install prisma-graphql-type-decimal
10 |
11 | ```
12 |
13 | ## Usage
14 |
15 | Example usage with NestJS GraphQL code first approach:
16 |
17 | ```ts
18 | import { Decimal } from '@prisma/client/runtime/library.js';
19 | import { transformToDecimal } from 'prisma-graphql-type-decimal';
20 | import { Type, Transform } from 'class-transformer';
21 |
22 | @ObjectType()
23 | export class User {
24 | /**
25 | * Trick to avoid error when using `@Field(() => GraphQLDecimal)`
26 | */
27 | @Field(() => GraphQLDecimal)
28 | @Type(() => Object)
29 | @Transform(transformToDecimal)
30 | money: Decimal;
31 |
32 | @Type(() => Object)
33 | @Transform(transformToDecimal)
34 | moneys!: Array;
35 | }
36 |
37 | // In nested object
38 | class Transfers {
39 | @Type(() => Object)
40 | @Transform(transformToDecimal)
41 | moneys!: Array;
42 | }
43 | class Container {
44 | @Type(() => Transfers)
45 | set!: Transfers;
46 | }
47 | ```
48 |
49 | ## License
50 |
51 | [MIT License](https://opensource.org/licenses/MIT) (c) 2023
52 |
--------------------------------------------------------------------------------
/Taskfile:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | PATH="$PWD/node_modules/.bin":$PATH
3 | set -e
4 |
5 | buildMicrobundle4() {
6 | set -x
7 | rm -rfv dist
8 | microbundle -i src/index.ts --tsconfig tsconfig.json --no-sourcemap --no-compress --target node -f cjs,esm -o dist
9 | name=$(cat package.json | jq -r '.name')
10 | cp -v README.md package.json dist
11 | cd dist
12 | mv "$name.js" "$name.cjs"
13 | mv index.d.ts "$name.d.ts"
14 | cat ../package.json | jq --arg name "$name" '
15 | .main = "./\($name).cjs" |
16 | .module = "./\($name).mjs" |
17 | .types = "./\($name).d.ts" |
18 | .exports.".".import = "./\($name).mjs" |
19 | .exports.".".require = "./\($name).cjs"
20 | ' > package.json
21 | cd ..
22 | set +x
23 | }
24 |
25 | "$@"
26 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import 'eslint-plugin-only-warn';
2 |
3 | import globals from 'globals';
4 | import pluginJs from '@eslint/js';
5 | import tseslint from 'typescript-eslint';
6 | import prettier from 'eslint-plugin-prettier/recommended';
7 | import unicorn from 'eslint-plugin-unicorn';
8 | import perfectionist from 'eslint-plugin-perfectionist';
9 | import simpleImportSort from 'eslint-plugin-simple-import-sort';
10 | import wixEditor from 'eslint-plugin-wix-editor';
11 | import { fixupPluginRules } from '@eslint/compat';
12 |
13 | /** @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigFile} */
14 | export default [
15 | pluginJs.configs.recommended,
16 | ...tseslint.configs.recommended,
17 | ...tseslint.configs.recommendedTypeChecked,
18 | prettier,
19 | {
20 | ignores: [
21 | 'dist/',
22 | 'coverage/',
23 | '@generated/**',
24 | '*.config.[cm]js',
25 | '.*rc.js',
26 | ],
27 | languageOptions: {
28 | globals: globals.node,
29 | parserOptions: {
30 | project: ['./tsconfig.json'],
31 | warnOnUnsupportedTypeScriptVersion: false,
32 | tsconfigRootDir: import.meta.dirname,
33 | },
34 | },
35 | rules: {
36 | 'max-lines': [1, { max: 300 }],
37 | 'max-params': [1, { max: 5 }],
38 | 'no-unneeded-ternary': [1],
39 | },
40 | },
41 | {
42 | plugins: {
43 | 'wix-editor': fixupPluginRules(wixEditor),
44 | },
45 | rules: {
46 | 'wix-editor/no-instanceof-array': 1,
47 | 'wix-editor/no-not-not': 1,
48 | 'wix-editor/no-unneeded-match': 1,
49 | 'wix-editor/prefer-filter': 1,
50 | 'wix-editor/prefer-ternary': 1,
51 | 'wix-editor/return-boolean': 1,
52 | 'wix-editor/simplify-boolean-expression': 1,
53 | },
54 | },
55 | {
56 | ...unicorn.configs.recommended,
57 | rules: {
58 | 'unicorn/prevent-abbreviations': [
59 | 'warn',
60 | {
61 | replacements: {
62 | args: false,
63 | },
64 | },
65 | ],
66 | },
67 | },
68 | {
69 | plugins: {
70 | perfectionist,
71 | },
72 | rules: {
73 | 'perfectionist/sort-objects': [
74 | 'warn',
75 | {
76 | type: 'natural',
77 | order: 'asc',
78 | },
79 | ],
80 | },
81 | },
82 | {
83 | plugins: {
84 | 'simple-import-sort': simpleImportSort,
85 | },
86 | rules: {
87 | 'simple-import-sort/imports': 'warn',
88 | 'simple-import-sort/exports': 'warn',
89 | },
90 | },
91 | {
92 | files: ['**/*.spec.ts', '**/*.e2e-spec.ts'],
93 | rules: {
94 | 'consistent-return': 0,
95 | 'max-lines': 0,
96 | '@typescript-eslint/no-explicit-any': 0,
97 | '@typescript-eslint/no-floating-promises': 0,
98 | '@typescript-eslint/no-non-null-assertion': 0,
99 | '@typescript-eslint/camelcase': 0,
100 | 'import/max-dependencies': 0,
101 | },
102 | },
103 | ];
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prisma-graphql-type-decimal",
3 | "version": "0.0.0-dev",
4 | "license": "MIT",
5 | "description": "GraphQL type for Prisma's Decimal scalar, wrapper around decimal.js",
6 | "main": "./dist/index.cjs",
7 | "module": "./dist/index.mjs",
8 | "types": "./dist/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "import": "./dist/index.mjs",
12 | "require": "./dist/index.cjs"
13 | }
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/unlight/prisma-graphql-type-decimal.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/unlight/prisma-graphql-type-decimal/issues"
21 | },
22 | "homepage": "https://github.com/unlight/prisma-graphql-type-decimal#readme",
23 | "scripts": {
24 | "test": "npm run eslint && npm run tscheck && npm run test:cov",
25 | "test:m": "node node_modules/@stryker-mutator/core/bin/stryker.js run",
26 | "test:r": "node -r ts-node/register node_modules/mocha/bin/mocha src/**/*.spec.ts",
27 | "test:cov": "c8 --reporter text --exclude \"**/*.spec.ts\" --exclude \"**/testing/**\" npm run test:r",
28 | "test:w": "npm run test:r -- --watch --watch-files src",
29 | "lint": "npm run eslint && npm run tscheck",
30 | "eslint": "node node_modules/eslint/bin/eslint \"src/**/*.{ts,tsx}\"",
31 | "eslint:w": "watchexec -w src \"npm run eslint\"",
32 | "eslint:fix": "npm run eslint -- --fix",
33 | "tscheck": "tsc --noEmit",
34 | "tscheck:w": "npm run tscheck -- --watch",
35 | "format:src": "prettier src --write",
36 | "commit_lint": "sh Taskfile commit_lint",
37 | "commit": "cz",
38 | "build": "sh Taskfile buildMicrobundle4",
39 | "test:d": "ndb -r ts-node/register/transpile-only node_modules/mocha/bin/mocha --no-timeouts --watch-files src/**/*.ts --watch src/**/*.spec.ts"
40 | },
41 | "peerDependencies": {
42 | "@prisma/client": "4.12 - 6",
43 | "graphql": "16"
44 | },
45 | "devDependencies": {
46 | "@commitlint/cli": "^19.8.0",
47 | "@commitlint/config-conventional": "^19.8.0",
48 | "@eslint/compat": "^1.2.8",
49 | "@eslint/js": "^9.24.0",
50 | "@prisma/client": "^5.0.0",
51 | "@semantic-release/changelog": "^6.0.3",
52 | "@semantic-release/git": "^10.0.1",
53 | "@stryker-mutator/api": "^8.7.1",
54 | "@stryker-mutator/core": "^8.7.1",
55 | "@stryker-mutator/mocha-runner": "^8.7.1",
56 | "@swc/core": "^1.11.16",
57 | "@swc/helpers": "^0.5.15",
58 | "@types/mocha": "^10.0.10",
59 | "@types/node": "^22.14.0",
60 | "@typescript-eslint/eslint-plugin": "^8.29.0",
61 | "@typescript-eslint/parser": "^8.29.0",
62 | "c8": "^10.1.3",
63 | "class-transformer": "^0.5.1",
64 | "commitizen": "^4.3.1",
65 | "cz-conventional-changelog": "^3.3.0",
66 | "cz-customizable": "^7.4.0",
67 | "eslint": "^9.24.0",
68 | "eslint-config-prettier": "^10.1.1",
69 | "eslint-plugin-etc": "^2.0.3",
70 | "eslint-plugin-only-warn": "^1.1.0",
71 | "eslint-plugin-perfectionist": "^4.11.0",
72 | "eslint-plugin-prettier": "^5.2.6",
73 | "eslint-plugin-simple-import-sort": "^12.1.1",
74 | "eslint-plugin-unicorn": "^58.0.0",
75 | "eslint-plugin-wix-editor": "^3.3.0",
76 | "expect": "^29.7.0",
77 | "globals": "^16.0.0",
78 | "graphql": "^16.6.0",
79 | "microbundle": "^0.15.1",
80 | "mocha": "^11.1.0",
81 | "precise-commits": "^1.0.2",
82 | "prettier": "^3.5.3",
83 | "prisma": "^6.5.0",
84 | "reflect-metadata": "^0.2.2",
85 | "semantic-release": "^24.2.3",
86 | "simplytyped": "^3.3.0",
87 | "stryker-cli": "^1.0.2",
88 | "ts-node": "^10.9.2",
89 | "tslib": "^2.8.1",
90 | "typescript": "^5.8.3",
91 | "typescript-eslint": "^8.29.0"
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/prettier.config.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * @see https://prettier.io/docs/configuration
3 | * @type {import('prettier').Config}
4 | */
5 | export default {
6 | printWidth: 80,
7 | trailingComma: 'all',
8 | tabWidth: 2,
9 | semi: true,
10 | singleQuote: true,
11 | arrowParens: 'avoid',
12 | };
13 |
--------------------------------------------------------------------------------
/release.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | [
4 | '@semantic-release/commit-analyzer',
5 | {
6 | preset: 'conventionalcommits',
7 | },
8 | ],
9 | [
10 | '@semantic-release/release-notes-generator',
11 | {
12 | preset: 'conventionalcommits',
13 | },
14 | ],
15 | '@semantic-release/changelog',
16 | [
17 | '@semantic-release/npm',
18 | {
19 | pkgRoot: 'dist',
20 | },
21 | ],
22 | '@semantic-release/github',
23 | '@semantic-release/git',
24 | ],
25 | };
26 |
--------------------------------------------------------------------------------
/schema.prisma:
--------------------------------------------------------------------------------
1 | datasource database {
2 | provider = "postgres"
3 | url = env("DATABASE_URL")
4 | }
5 |
6 | generator client {
7 | provider = "prisma-client-js"
8 | }
9 |
10 | model Dummy {
11 | id String @id
12 | created DateTime @default(now())
13 | floaty Float
14 | int Int?
15 | float Float?
16 | bytes Bytes?
17 | decimal Decimal?
18 | bigInt BigInt?
19 | json Json?
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.spec.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 |
3 | import { Decimal } from '@prisma/client/runtime/library';
4 | import { plainToClass, Transform, Type } from 'class-transformer';
5 | import expect from 'expect';
6 | import { graphql, GraphQLObjectType, GraphQLSchema } from 'graphql';
7 |
8 | import { createDecimalFromObject, GraphQLDecimal, transformToDecimal } from '.';
9 |
10 | it('smoke', async () => {
11 | const schema = new GraphQLSchema({
12 | query: new GraphQLObjectType({
13 | name: 'Query',
14 | fields: {
15 | decimal: {
16 | type: GraphQLDecimal,
17 | resolve: () => 1,
18 | },
19 | },
20 | }),
21 | });
22 |
23 | expect(
24 | await graphql({
25 | schema,
26 | source: /* GraphQL */ `
27 | query {
28 | decimal
29 | }
30 | `,
31 | }),
32 | ).toBeTruthy();
33 | });
34 |
35 | it('echo', async () => {
36 | const schema = new GraphQLSchema({
37 | query: new GraphQLObjectType({
38 | name: 'Query',
39 | fields: {
40 | echo: {
41 | type: GraphQLDecimal,
42 | args: {
43 | num: { type: GraphQLDecimal },
44 | },
45 | resolve: (_root, args) => args.num,
46 | },
47 | },
48 | }),
49 | });
50 | expect(
51 | await graphql({
52 | schema,
53 | source: /* GraphQL */ `
54 | query {
55 | float: echo(num: 0.1)
56 | int: echo(num: 2)
57 | string: echo(num: "3")
58 | }
59 | `,
60 | }),
61 | ).toEqual({
62 | data: {
63 | float: '0.1',
64 | int: '2',
65 | string: '3',
66 | },
67 | });
68 | });
69 |
70 | it('inc', async () => {
71 | const schema = new GraphQLSchema({
72 | query: new GraphQLObjectType({
73 | name: 'Query',
74 | fields: {
75 | inc: {
76 | type: GraphQLDecimal,
77 | args: {
78 | num: { type: GraphQLDecimal },
79 | },
80 | resolve: (_root, args) => new Decimal(0.1).add(args.num),
81 | },
82 | },
83 | }),
84 | });
85 | expect(
86 | await graphql({
87 | schema,
88 | source: /* GraphQL */ `
89 | query {
90 | inc(num: 0.2)
91 | }
92 | `,
93 | }),
94 | ).toEqual({
95 | data: {
96 | inc: '0.3',
97 | },
98 | });
99 | });
100 |
101 | it('parse value', async () => {
102 | const schema = new GraphQLSchema({
103 | query: new GraphQLObjectType({
104 | name: 'Query',
105 | fields: {
106 | sum: {
107 | type: GraphQLDecimal,
108 | args: {
109 | a: {
110 | type: GraphQLDecimal,
111 | },
112 | b: {
113 | type: GraphQLDecimal,
114 | },
115 | },
116 | resolve: (_root, args) => {
117 | return Decimal.add(args.a, args.b);
118 | },
119 | },
120 | },
121 | }),
122 | });
123 | expect(
124 | await graphql({
125 | schema,
126 | source: /* GraphQL */ `
127 | query ($a: Decimal, $b: Decimal) {
128 | sum(a: $a, b: $b)
129 | }
130 | `,
131 | variableValues: {
132 | a: new Decimal(0.1),
133 | b: '0.2',
134 | },
135 | }),
136 | ).toEqual({
137 | data: {
138 | sum: '0.3',
139 | },
140 | });
141 | });
142 |
143 | it('null', async () => {
144 | const schema = new GraphQLSchema({
145 | query: new GraphQLObjectType({
146 | name: 'Query',
147 | fields: {
148 | decimal: {
149 | type: GraphQLDecimal,
150 | resolve: () => null,
151 | },
152 | },
153 | }),
154 | });
155 |
156 | expect(
157 | await graphql({
158 | schema,
159 | source: /* GraphQL */ `
160 | query {
161 | decimal
162 | }
163 | `,
164 | }),
165 | ).toEqual({
166 | data: {
167 | decimal: null,
168 | },
169 | });
170 | });
171 |
172 | it('unknown value to parse', async () => {
173 | const schema = new GraphQLSchema({
174 | query: new GraphQLObjectType({
175 | name: 'Query',
176 | fields: {
177 | field: {
178 | type: GraphQLDecimal,
179 | args: {
180 | arg1: { type: GraphQLDecimal },
181 | },
182 | resolve: (_root, args) => {
183 | return args.arg1 === null ? 'failed to parse' : args.arg1;
184 | },
185 | },
186 | },
187 | }),
188 | });
189 |
190 | expect(
191 | await graphql({
192 | schema,
193 | source: /* GraphQL */ `
194 | query {
195 | field(arg1: false)
196 | }
197 | `,
198 | }),
199 | ).toEqual({
200 | data: {
201 | field: 'failed to parse',
202 | },
203 | });
204 | });
205 |
206 | describe('decimal create from object', () => {
207 | it('instanceof', () => {
208 | const decimal = new Decimal(0.123);
209 | // eslint-disable-next-line total-functions/no-unsafe-type-assertion
210 | const o = createDecimalFromObject({
211 | d: decimal.d,
212 | e: decimal.e,
213 | s: decimal.s,
214 | });
215 |
216 | expect(o).toBeInstanceOf(Decimal);
217 | expect(o instanceof Decimal).toBeTruthy();
218 | });
219 |
220 | for (const { decimal, string } of [
221 | { decimal: new Decimal(0.123), string: '0.123' },
222 | { decimal: new Decimal(1.234_567_89), string: '1.23456789' },
223 | { decimal: new Decimal(4.6875e-2), string: '0.046875' },
224 | { decimal: new Decimal('1.79e+308'), string: '1.79e+308' },
225 | { decimal: new Decimal('9007199254741991'), string: '9007199254741991' },
226 | ]) {
227 | it(`${decimal.toString()}`, () => {
228 | // eslint-disable-next-line total-functions/no-unsafe-type-assertion
229 | const o = Object.create(Decimal.prototype, {
230 | d: { value: decimal.d },
231 | e: { value: decimal.e },
232 | s: { value: decimal.s },
233 | }) as Decimal;
234 |
235 | expect(o.toString()).toEqual(string);
236 | });
237 | }
238 | });
239 |
240 | describe('class transformer', () => {
241 | it('check type', () => {
242 | class Transfer {
243 | @Type(() => Object)
244 | @Transform(transformToDecimal)
245 | money!: Decimal;
246 | }
247 | });
248 |
249 | it('transformToDecimal main case', () => {
250 | class Transfer {
251 | @Type(() => Object)
252 | @Transform(transformToDecimal)
253 | money!: Decimal;
254 | }
255 |
256 | const transfer = plainToClass(Transfer, { money: new Decimal(1) });
257 | expect(transfer.money).toBeInstanceOf(Decimal);
258 | expect(transfer.money.isInteger()).toBe(true);
259 | });
260 |
261 | it('transformToDecimal duck type', () => {
262 | class Transfer {
263 | @Type(() => Object)
264 | @Transform(transformToDecimal)
265 | money!: any;
266 | }
267 |
268 | const transfer = plainToClass(Transfer, { money: { toString: () => '1' } });
269 | expect(transfer.money).toBeInstanceOf(Decimal);
270 | });
271 |
272 | it('transformToDecimal array', () => {
273 | class Transfer {
274 | @Type(() => Object)
275 | @Transform(transformToDecimal)
276 | moneys!: Array;
277 | }
278 |
279 | const transfer = plainToClass(Transfer, { moneys: [new Decimal(1)] });
280 | expect(transfer.moneys[0]).toBeInstanceOf(Decimal);
281 | });
282 |
283 | it('array in nested object', () => {
284 | class Transfers {
285 | @Type(() => Object)
286 | @Transform(transformToDecimal)
287 | moneys!: Array;
288 | }
289 | class Container {
290 | @Type(() => Transfers)
291 | set!: Transfers;
292 | }
293 |
294 | const container = plainToClass(Container, {
295 | set: { moneys: [new Decimal(1)] },
296 | });
297 | expect(container.set.moneys[0]).toBeInstanceOf(Decimal);
298 | });
299 |
300 | it('null should not be transformed', () => {
301 | class Transfer {
302 | @Type(() => Object)
303 | @Transform(transformToDecimal)
304 | money?: Decimal;
305 | }
306 |
307 | const transfer = plainToClass(Transfer, { money: null });
308 | expect(transfer).toEqual({ money: null });
309 | });
310 |
311 | it('undefined should not be transformed', () => {
312 | class Transfer {
313 | @Type(() => Object)
314 | @Transform(transformToDecimal)
315 | money?: Decimal;
316 | }
317 |
318 | const transfer = plainToClass(Transfer, { money: undefined });
319 | expect(transfer).toEqual({ money: undefined });
320 | });
321 | });
322 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Decimal } from '@prisma/client/runtime/library.js';
2 | import { GraphQLScalarType, GraphQLScalarTypeConfig, Kind } from 'graphql';
3 |
4 | const config: GraphQLScalarTypeConfig<
5 | null | string | number | Decimal,
6 | string
7 | > = {
8 | name: 'Decimal',
9 | description: 'An arbitrary-precision Decimal type',
10 | /**
11 | * Value sent to the client
12 | */
13 | serialize(value) {
14 | // console.log('serialize value', value.constructor.name);
15 | return String(value);
16 | },
17 | /**
18 | * Value from the client
19 | */
20 | parseValue(value) {
21 | return new Decimal(value as Decimal.Value);
22 | },
23 | parseLiteral(ast) {
24 | if (
25 | ast.kind === Kind.INT ||
26 | ast.kind === Kind.FLOAT ||
27 | ast.kind === Kind.STRING
28 | ) {
29 | return new Decimal(ast.value);
30 | }
31 | // eslint-disable-next-line unicorn/no-null
32 | return null;
33 | },
34 | };
35 |
36 | export const GraphQLDecimal = new GraphQLScalarType(config);
37 |
38 | export function createDecimalFromObject(object: any) {
39 | // eslint-disable-next-line total-functions/no-unsafe-type-assertion
40 | return Object.create(Decimal.prototype, {
41 | d: { value: object.d },
42 | e: { value: object.e },
43 | s: { value: object.s },
44 | }) as Decimal;
45 | }
46 |
47 | interface TransformFunctionParams {
48 | value: any;
49 | }
50 |
51 | export function transformToDecimal({ value }: TransformFunctionParams) {
52 | if (value == null) return value;
53 | return Array.isArray(value)
54 | ? value.map(createDecimalFromObject)
55 | : createDecimalFromObject(value);
56 | }
57 |
--------------------------------------------------------------------------------
/stryker.conf.mjs:
--------------------------------------------------------------------------------
1 | // Take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information
2 | /** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
3 | export default {
4 | packageManager: 'npm',
5 | commandRunner: {
6 | command:
7 | 'node -r ts-node/register node_modules/mocha/bin/mocha src/**/*.spec.ts',
8 | },
9 | coverageAnalysis: 'perTest',
10 | concurrency: 2,
11 | mutate: ['src/**/*.ts', '!src/**/*.spec.ts'],
12 | reporters: ['clear-text', 'dots'],
13 | tsconfigFile: 'tsconfig.json',
14 | };
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "ts-node": {
3 | "swc": true,
4 | "transpileOnly": true,
5 | "moduleTypes": {
6 | "webpack.config.js": "cjs"
7 | },
8 | "require": []
9 | },
10 | "compilerOptions": {
11 | "target": "ES2020",
12 | "module": "commonjs",
13 | "moduleResolution": "node",
14 | "importHelpers": true,
15 | "strict": true,
16 | "noUncheckedIndexedAccess": true,
17 | "noImplicitAny": false,
18 | "experimentalDecorators": true,
19 | "emitDecoratorMetadata": true,
20 | "outDir": "dist",
21 | "pretty": true,
22 | "esModuleInterop": true,
23 | "removeComments": false,
24 | "sourceMap": true,
25 | "declaration": true,
26 | "declarationMap": false,
27 | "skipLibCheck": true,
28 | "lib": ["esnext"],
29 | "paths": {},
30 | "plugins": []
31 | },
32 | "include": ["src"]
33 | }
34 |
--------------------------------------------------------------------------------