Skip to content

Commit e095bdf

Browse files
committed
feat: add support for Decimal128 type
1 parent fa5d764 commit e095bdf

File tree

10 files changed

+146
-9
lines changed

10 files changed

+146
-9
lines changed

src/__mocks__/userModel.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ const UserSchema: SchemaType<any> = new Schema(
9090
periods: [{ from: Number, to: Number }],
9191
},
9292

93+
salary: {
94+
type: Schema.Types.Decimal128,
95+
},
96+
9397
// createdAt, created via option `timastamp: true` (see bottom)
9498
// updatedAt, created via option `timastamp: true` (see bottom)
9599
},

src/__tests__/fieldConverter-test.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
referenceToGraphQL,
1717
} from '../fieldsConverter';
1818
import GraphQLMongoID from '../types/mongoid';
19+
import GraphQLBSONDecimal from '../types/bsonDecimal';
1920

2021
describe('fieldConverter', () => {
2122
const fields: { [key: string]: any } = getFieldsFromModel(UserModel);
@@ -77,23 +78,27 @@ describe('fieldConverter', () => {
7778
expect(deriveComplexType(fields.subDoc)).toBe(ComplexTypes.EMBEDDED);
7879
});
7980

80-
it('schould derive ARRAY', () => {
81+
it('should derive DECIMAL', () => {
82+
expect(deriveComplexType(fields.salary)).toBe(ComplexTypes.DECIMAL);
83+
});
84+
85+
it('should derive ARRAY', () => {
8186
expect(deriveComplexType(fields.users)).toBe(ComplexTypes.ARRAY);
8287
expect(deriveComplexType(fields.skills)).toBe(ComplexTypes.ARRAY);
8388
expect(deriveComplexType(fields.employment)).toBe(ComplexTypes.ARRAY);
8489
});
8590

86-
it('schould derive ENUM', () => {
91+
it('should derive ENUM', () => {
8792
expect(deriveComplexType(fields.gender)).toBe(ComplexTypes.ENUM);
8893
expect(deriveComplexType(fields.languages.schema.paths.ln)).toBe(ComplexTypes.ENUM);
8994
expect(deriveComplexType(fields.languages.schema.paths.sk)).toBe(ComplexTypes.ENUM);
9095
});
9196

92-
it('schould derive REFERENCE', () => {
97+
it('should derive REFERENCE', () => {
9398
expect(deriveComplexType(fields.user)).toBe(ComplexTypes.REFERENCE);
9499
});
95100

96-
it('schould derive SCALAR', () => {
101+
it('should derive SCALAR', () => {
97102
expect(deriveComplexType(fields.name)).toBe(ComplexTypes.SCALAR);
98103
expect(deriveComplexType(fields.relocation)).toBe(ComplexTypes.SCALAR);
99104
expect(deriveComplexType(fields.age)).toBe(ComplexTypes.SCALAR);
@@ -103,7 +108,7 @@ describe('fieldConverter', () => {
103108
expect(deriveComplexType(fields.subDoc)).not.toBe(ComplexTypes.SCALAR);
104109
});
105110

106-
it('schould derive MIXED mongoose type', () => {
111+
it('should derive MIXED mongoose type', () => {
107112
expect(deriveComplexType(fields.someDynamic)).toBe(ComplexTypes.MIXED);
108113
});
109114
});
@@ -141,6 +146,18 @@ describe('fieldConverter', () => {
141146
expect(schemaComposer.get('MongoID')).toBe(customType);
142147
schemaComposer.delete('MongoID');
143148
});
149+
150+
it('should convert any mongoose field to graphQL type', () => {
151+
schemaComposer.clear();
152+
expect(schemaComposer.has('BSONDecimal')).toBeFalsy();
153+
const mongooseField = {
154+
path: 'salary',
155+
instance: 'Decimal128',
156+
};
157+
expect(convertFieldToGraphQL(mongooseField, '', schemaComposer)).toBe('BSONDecimal');
158+
expect(schemaComposer.has('BSONDecimal')).toBeTruthy();
159+
expect(schemaComposer.get('BSONDecimal').getType()).toBe(GraphQLBSONDecimal);
160+
});
144161
});
145162

146163
describe('scalarToGraphQL()', () => {

src/fieldsConverter.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const ComplexTypes: {
2525
REFERENCE: 'REFERENCE';
2626
SCALAR: 'SCALAR';
2727
MIXED: 'MIXED';
28+
DECIMAL: 'DECIMAL';
2829
};
2930

3031
export function dotPathsToEmbedded(

src/fieldsConverter.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
import { upperFirst } from 'graphql-compose';
1414
import type { GraphQLScalarType } from 'graphql-compose/lib/graphql';
1515
import GraphQLMongoID from './types/mongoid';
16+
import GraphQLBSONDecimal from './types/bsonDecimal';
1617

1718
type MongooseFieldT = MongooseSchemaField<any>;
1819
type MongooseFieldMapT = { [fieldName: string]: MongooseFieldT };
@@ -30,6 +31,7 @@ export const ComplexTypes = {
3031
REFERENCE: 'REFERENCE',
3132
SCALAR: 'SCALAR',
3233
MIXED: 'MIXED',
34+
DECIMAL: 'DECIMAL',
3335
};
3436

3537
function _getFieldName(field: MongooseFieldT): string {
@@ -211,6 +213,11 @@ export function convertFieldToGraphQL(
211213
return (documentArrayToGraphQL(field, prefix, schemaComposer): any);
212214
case ComplexTypes.MIXED:
213215
return 'JSON';
216+
case ComplexTypes.DECIMAL:
217+
if (!schemaComposer.has('BSONDecimal')) {
218+
schemaComposer.add(GraphQLBSONDecimal);
219+
}
220+
return 'BSONDecimal';
214221
default:
215222
return scalarToGraphQL(field);
216223
}
@@ -241,6 +248,8 @@ export function deriveComplexType(field: MongooseFieldT): $Keys<typeof ComplexTy
241248
return ComplexTypes.MIXED;
242249
} else if (fieldType === 'ObjectID') {
243250
return ComplexTypes.REFERENCE;
251+
} else if (fieldType === 'Decimal128') {
252+
return ComplexTypes.DECIMAL;
244253
}
245254

246255
const enums = _getFieldEnums(field);

src/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { composeWithMongoose } from './composeWithMongoose';
22
import GraphQLMongoID from './types/mongoid';
3+
import GraphQLBSONDecimal from './types/bsonDecimal';
34

45
export default composeWithMongoose;
56

67
export * from './composeWithMongoose';
78
export * from './composeWithMongooseDiscriminators';
89
export * from './fieldsConverter';
910
export * from './resolvers';
10-
export { GraphQLMongoID };
11+
export { GraphQLMongoID, GraphQLBSONDecimal };

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import { composeWithMongoose } from './composeWithMongoose';
44
import GraphQLMongoID from './types/mongoid';
5+
import GraphQLBSONDecimal from './types/bsonDecimal';
56

67
export default composeWithMongoose;
78

89
export * from './composeWithMongoose';
910
export * from './composeWithMongooseDiscriminators';
1011
export * from './fieldsConverter';
1112
export * from './resolvers';
12-
export { GraphQLMongoID };
13+
export { GraphQLMongoID, GraphQLBSONDecimal };
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* @flow */
2+
3+
import mongoose from 'mongoose';
4+
import { Kind } from 'graphql-compose/lib/graphql';
5+
import GraphQLBSONDecimal from '../bsonDecimal';
6+
7+
const Decimal128 = mongoose.Types.Decimal128;
8+
9+
describe('GraphQLBSONDecimal', () => {
10+
describe('serialize', () => {
11+
it('pass Decimal128', () => {
12+
const amount = Decimal128.fromString('90000000000000000000000000000000.09');
13+
expect(GraphQLBSONDecimal.serialize(amount)).toBe('90000000000000000000000000000000.09');
14+
});
15+
16+
it('pass String', () => {
17+
const amount = '90000000000000000000000000000000.09';
18+
expect(GraphQLBSONDecimal.serialize(amount)).toBe('90000000000000000000000000000000.09');
19+
});
20+
});
21+
22+
describe('parseValue', () => {
23+
it('pass Decimal128', () => {
24+
const amount = Decimal128.fromString('90000000000000000000000000000000.09');
25+
expect(GraphQLBSONDecimal.parseValue(amount)).toBeInstanceOf(Decimal128);
26+
});
27+
28+
it('pass String', () => {
29+
const amount = '90000000000000000000000000000000.09';
30+
expect(GraphQLBSONDecimal.parseValue(amount)).toBeInstanceOf(Decimal128);
31+
});
32+
33+
it('pass Integer', () => {
34+
const amount = 123;
35+
expect(GraphQLBSONDecimal.parseValue(amount)).toBeInstanceOf(Decimal128);
36+
});
37+
38+
it('pass any custom string value', () => {
39+
const id = 'custom_id';
40+
expect(() => GraphQLBSONDecimal.parseValue(id)).toThrow('not a valid Decimal128 string');
41+
});
42+
});
43+
44+
describe('parseLiteral', () => {
45+
it('parse a ast STRING literal', async () => {
46+
const ast = {
47+
kind: Kind.STRING,
48+
value: '90000000000000000000000000000000.09',
49+
};
50+
const amount: any = GraphQLBSONDecimal.parseLiteral(ast);
51+
expect(amount).toBeInstanceOf(Decimal128);
52+
expect(amount.toString()).toEqual('90000000000000000000000000000000.09');
53+
});
54+
55+
it('parse a ast INT literal', async () => {
56+
const ast: any = {
57+
kind: Kind.INT,
58+
value: '123',
59+
};
60+
const amount: any = GraphQLBSONDecimal.parseLiteral(ast);
61+
expect(amount).toBeInstanceOf(Decimal128);
62+
expect(amount.toString()).toEqual('123');
63+
});
64+
});
65+
});

src/types/bsonDecimal.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { GraphQLScalarType } from 'graphql-compose/lib/graphql';
2+
3+
declare const GraphQLBSONDecimal: GraphQLScalarType;
4+
5+
export default GraphQLBSONDecimal;

src/types/bsonDecimal.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* @flow */
2+
3+
import mongoose from 'mongoose';
4+
import { GraphQLScalarType, Kind } from 'graphql-compose/lib/graphql';
5+
6+
const Decimal128 = mongoose.Types.Decimal128;
7+
8+
const GraphQLBSONDecimal = new GraphQLScalarType({
9+
name: 'BSONDecimal',
10+
description:
11+
'The `Decimal` scalar type uses the IEEE 754 decimal128 ' +
12+
'decimal-based floating-point numbering format. ' +
13+
'Supports 34 decimal digits of precision, a max value of ' +
14+
'approximately 10^6145, and min value of approximately -10^6145',
15+
serialize: String,
16+
parseValue(value: any) {
17+
if (typeof value === 'string') {
18+
return Decimal128.fromString(value);
19+
}
20+
if (typeof value === 'number') {
21+
return Decimal128.fromString(value.toString());
22+
}
23+
if (value instanceof Decimal128) {
24+
return value;
25+
}
26+
throw new TypeError('Field error: value is an invalid Decimal');
27+
},
28+
parseLiteral(ast) {
29+
if (ast.kind === Kind.STRING || ast.kind === Kind.INT) {
30+
return Decimal128.fromString(ast.value);
31+
}
32+
return null;
33+
},
34+
});
35+
36+
export default GraphQLBSONDecimal;

src/types/mongoid.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { GraphQLScalarType } from 'graphql-compose/lib/graphql';
22

3-
export type MongoId = string | any;
4-
53
declare const GraphQLMongoID: GraphQLScalarType;
64

75
export default GraphQLMongoID;

0 commit comments

Comments
 (0)