Skip to content

Commit 128a844

Browse files
committed
feat: add support for alias mongoose option. It allows to have different names in schema and database. See https://mongoosejs.com/docs/guide.html#aliases
Closes #194
1 parent f7394ad commit 128a844

33 files changed

+264
-66
lines changed

README.md

+19-5
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { composeWithMongoose } from 'graphql-compose-mongoose';
3434
// For using node 8 and above (native async/await)
3535
import { composeWithMongoose } from 'graphql-compose-mongoose/node8';
3636

37-
// Source code without Flowtype declarations
37+
// Source code without FlowType declarations
3838
import { composeWithMongoose } from 'graphql-compose-mongoose/es';
3939
```
4040

@@ -56,7 +56,7 @@ Small explanation for variables naming:
5656

5757
- `UserSchema` - this is a mongoose schema
5858
- `User` - this is a mongoose model
59-
- `UserTC` - this is a `ObjectTypeComposer` instance for User. `ObjectTypeComposer` has `GraphQLObjectType` inside, avaliable via method `UserTC.getType()`.
59+
- `UserTC` - this is a `ObjectTypeComposer` instance for User. `ObjectTypeComposer` has `GraphQLObjectType` inside, available via method `UserTC.getType()`.
6060
- Here and in all other places of code variables suffix `...TC` means that this is `ObjectTypeComposer` instance, `...ITC` - `InputTypeComposer`, `...ETC` - `EnumTypeComposer`.
6161

6262
```js
@@ -79,17 +79,18 @@ const UserSchema = new mongoose.Schema({
7979
type: Number,
8080
index: true,
8181
},
82-
languages: {
82+
ln: {
8383
type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
8484
default: [],
85+
alias: 'languages', // in schema `ln` will be named as `languages`
8586
},
8687
contacts: { // another mongoose way for providing embedded documents
8788
email: String,
8889
phones: [String], // array of strings
8990
},
9091
gender: { // enum field with values
9192
type: String,
92-
enum: ['male', 'female', 'ladyboy'],
93+
enum: ['male', 'female'],
9394
},
9495
someMixed: {
9596
type: mongoose.Schema.Types.Mixed,
@@ -201,7 +202,7 @@ Variable Namings
201202
const CharacterDTC = composeWithMongooseDiscriminators(CharacterModel, baseOptions);
202203

203204
// create Discriminator Types
204-
const droidTypeConverterOptions = { // this options will be merged with baseOptions -> customisationsOptions
205+
const droidTypeConverterOptions = { // this options will be merged with baseOptions -> customizationsOptions
205206
fields: {
206207
remove: ['makeDate'],
207208
}
@@ -528,6 +529,19 @@ const UsersSchema = new Schema({
528529
});
529530
```
530531

532+
### Can field name in schema have different name in database?
533+
534+
Yes, it can. This package understands mongoose [`alias` option](https://mongoosejs.com/docs/guide.html#aliases) for fields. Just provide `alias: 'country'` for field `c` and you get `country` field name in GraphQL schema and Mongoose model but `c` field in database:
535+
536+
```js
537+
const childSchema = new Schema({
538+
c: {
539+
type: String,
540+
alias: 'country'
541+
}
542+
});
543+
```
544+
531545
## Customization options
532546

533547
When we convert model `const UserTC = composeWithMongoose(User, customizationOptions);` you may tune every piece of future derived types and resolvers.

src/__mocks__/userModel.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ const UserSchema: SchemaType<any> = new Schema(
2626
ref: 'UserModel',
2727
},
2828

29-
name: {
29+
n: {
3030
type: String,
3131
required: true,
3232
description: 'Person name',
33+
alias: 'name',
3334
},
3435

3536
age: {

src/__tests__/__snapshots__/integration-test.js.snap

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Array [
88
"employment",
99
"contacts",
1010
"_id",
11-
"name",
11+
"n",
1212
"age",
1313
"gender",
1414
"relocation",
@@ -17,6 +17,7 @@ Array [
1717
"createdAt",
1818
"updatedAt",
1919
"__v",
20+
"name",
2021
"nameVirtual",
2122
"id",
2223
]

src/__tests__/fieldConverter-test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('fieldConverter', () => {
2626

2727
describe('getFieldsFromModel()', () => {
2828
it('should get fieldsMap from mongoose model', () => {
29-
expect(Object.keys(fields)).toEqual(expect.arrayContaining(['name', 'createdAt', '_id']));
29+
expect(Object.keys(fields)).toEqual(expect.arrayContaining(['n', 'createdAt', '_id']));
3030
});
3131

3232
it('should skip double undescored fields', () => {
@@ -101,7 +101,7 @@ describe('fieldConverter', () => {
101101
});
102102

103103
it('should derive SCALAR', () => {
104-
expect(deriveComplexType(fields.name)).toBe(ComplexTypes.SCALAR);
104+
expect(deriveComplexType(fields.n)).toBe(ComplexTypes.SCALAR);
105105
expect(deriveComplexType(fields.relocation)).toBe(ComplexTypes.SCALAR);
106106
expect(deriveComplexType(fields.age)).toBe(ComplexTypes.SCALAR);
107107
expect(deriveComplexType(fields.createdAt)).toBe(ComplexTypes.SCALAR);
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* @flow */
2+
/* eslint-disable no-await-in-loop */
3+
4+
import mongoose from 'mongoose';
5+
import MongodbMemoryServer from 'mongodb-memory-server';
6+
import { schemaComposer, graphql } from 'graphql-compose';
7+
import { composeWithMongoose } from '../../index';
8+
9+
let mongoServer;
10+
beforeAll(async () => {
11+
mongoServer = new MongodbMemoryServer();
12+
const mongoUri = await mongoServer.getConnectionString();
13+
await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true });
14+
// mongoose.set('debug', true);
15+
});
16+
17+
afterAll(() => {
18+
mongoose.disconnect();
19+
mongoServer.stop();
20+
});
21+
22+
// May require additional time for downloading MongoDB binaries
23+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
24+
25+
describe('issue #194 - useAlias', () => {
26+
const UserSchema = new mongoose.Schema({
27+
e: {
28+
type: String,
29+
alias: 'emailAddress',
30+
},
31+
});
32+
33+
const User = mongoose.model('User', UserSchema);
34+
const UserTC = composeWithMongoose(User);
35+
36+
it('check that virtual field works', async () => {
37+
// INIT GRAPHQL SCHEMA
38+
schemaComposer.Query.addFields({ findMany: UserTC.getResolver('findMany') });
39+
schemaComposer.Mutation.addFields({
40+
createOne: UserTC.getResolver('createOne'),
41+
});
42+
const schema = schemaComposer.buildSchema();
43+
44+
const res = await graphql.graphql({
45+
schema,
46+
source:
47+
'mutation { createOne(record: { emailAddress: "[email protected]" }) { record { emailAddress } } }',
48+
});
49+
expect(res).toEqual({ data: { createOne: { record: { emailAddress: '[email protected]' } } } });
50+
51+
const res2 = await graphql.graphql({
52+
schema,
53+
source: 'query { findMany { emailAddress } }',
54+
});
55+
expect(res2).toEqual({ data: { findMany: [{ emailAddress: '[email protected]' }] } });
56+
});
57+
});

src/fieldsConverter.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,13 @@ export function convertModelToGraphQL<TSource, TContext>(
142142
const graphqlFields = {};
143143
const requiredFields = [];
144144

145-
Object.keys(mongooseFields).forEach((fieldName) => {
146-
const mongooseField: MongooseFieldT = mongooseFields[fieldName];
145+
Object.keys(mongooseFields).forEach((key) => {
146+
const mongooseField: MongooseFieldT = mongooseFields[key];
147+
148+
let fieldName = key;
149+
if (typeof (mongooseField: any)?.options?.alias === 'string') {
150+
fieldName = (mongooseField: any)?.options?.alias;
151+
}
147152

148153
if (
149154
(mongooseField: any).isRequired &&

src/resolvers/__tests__/createMany-test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ describe('createMany() ->', () => {
111111

112112
const docs = await UserModel.collection.find({ _id: { $in: res.recordIds } }).toArray();
113113
expect(docs.length).toBe(2);
114-
expect(docs[0].name).toBe(checkedName);
115-
expect(docs[1].name).toBe(checkedName);
114+
expect(docs[0].n).toBe(checkedName);
115+
expect(docs[1].n).toBe(checkedName);
116116
});
117117

118118
it('should return payload.records', async () => {

src/resolvers/__tests__/createOne-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('createOne() ->', () => {
7777
});
7878

7979
const doc = await UserModel.collection.findOne({ _id: res.record._id });
80-
expect(doc.name).toBe(checkedName);
80+
expect(doc.n).toBe(checkedName);
8181
});
8282

8383
it('should return payload.record', async () => {

src/resolvers/__tests__/removeById-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe('removeById() ->', () => {
145145
});
146146
await expect(result).rejects.toMatchSnapshot();
147147
const exist = await UserModel.collection.findOne({ _id: user._id });
148-
expect(exist.name).toBe(user.name);
148+
expect(exist.n).toBe(user.name);
149149
});
150150

151151
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {

src/resolvers/__tests__/removeOne-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ describe('removeOne() ->', () => {
187187
});
188188
await expect(result).rejects.toMatchSnapshot();
189189
const exist = await UserModel.collection.findOne({ _id: user1._id });
190-
expect(exist.name).toBe(user1.name);
190+
expect(exist.n).toBe(user1.name);
191191
});
192192

193193
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {

src/resolvers/__tests__/updateById-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ describe('updateById() ->', () => {
185185
});
186186
await expect(result).rejects.toMatchSnapshot();
187187
const exist = await UserModel.collection.findOne({ _id: user1._id });
188-
expect(exist.name).toBe(user1.name);
188+
expect(exist.n).toBe(user1.name);
189189
});
190190

191191
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {

src/resolvers/__tests__/updateMany-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('updateMany() ->', () => {
9898
});
9999

100100
await expect(UserModel.findOne({ _id: user1._id })).resolves.toEqual(
101-
expect.objectContaining({ name: checkedName })
101+
expect.objectContaining({ n: checkedName })
102102
);
103103
});
104104

src/resolvers/__tests__/updateOne-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('updateOne() ->', () => {
209209
});
210210
await expect(result).rejects.toMatchSnapshot();
211211
const exist = await UserModel.collection.findOne({ _id: user1._id });
212-
expect(exist.name).toBe(user1.name);
212+
expect(exist.n).toBe(user1.name);
213213
});
214214

215215
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {

src/resolvers/count.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import type { Resolver, ObjectTypeComposer } from 'graphql-compose';
55
import type { MongooseDocument } from 'mongoose';
6-
import { filterHelper, filterHelperArgs } from './helpers';
6+
import { filterHelper, filterHelperArgs, prepareAliases } from './helpers';
77
import type { ExtendedResolveParams, GenResolverOpts } from './index';
88
import { beforeQueryHelper } from './helpers/beforeQueryHelper';
99

@@ -20,6 +20,8 @@ export default function count<TSource: MongooseDocument, TContext>(
2020
throw new Error('Second arg for Resolver count() should be instance of ObjectTypeComposer.');
2121
}
2222

23+
const aliases = prepareAliases(model);
24+
2325
return tc.schemaComposer.createResolver({
2426
type: 'Int',
2527
name: 'count',
@@ -34,7 +36,7 @@ export default function count<TSource: MongooseDocument, TContext>(
3436
resolve: (resolveParams: ExtendedResolveParams) => {
3537
resolveParams.query = model.find();
3638
resolveParams.model = model;
37-
filterHelper(resolveParams);
39+
filterHelper(resolveParams, aliases);
3840
if (resolveParams.query.countDocuments) {
3941
// mongoose 5.2.0 and above
4042
resolveParams.query.countDocuments();

src/resolvers/findById.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import type { Resolver, ObjectTypeComposer } from 'graphql-compose';
44
import type { MongooseDocument } from 'mongoose';
5-
import { projectionHelper } from './helpers';
5+
import { projectionHelper, prepareAliases } from './helpers';
66
import type { ExtendedResolveParams, GenResolverOpts } from './index';
77
import { beforeQueryHelper } from './helpers/beforeQueryHelper';
88

@@ -19,6 +19,8 @@ export default function findById<TSource: MongooseDocument, TContext>(
1919
throw new Error('Second arg for Resolver findById() should be instance of ObjectTypeComposer.');
2020
}
2121

22+
const aliases = prepareAliases(model);
23+
2224
return tc.schemaComposer.createResolver({
2325
type: tc,
2426
name: 'findById',
@@ -32,7 +34,7 @@ export default function findById<TSource: MongooseDocument, TContext>(
3234
if (args._id) {
3335
resolveParams.query = model.findById(args._id); // eslint-disable-line
3436
resolveParams.model = model; // eslint-disable-line
35-
projectionHelper(resolveParams);
37+
projectionHelper(resolveParams, aliases);
3638
return beforeQueryHelper(resolveParams);
3739
}
3840
return Promise.resolve(null);

src/resolvers/findByIds.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
sortHelper,
99
sortHelperArgs,
1010
projectionHelper,
11+
prepareAliases,
1112
} from './helpers';
1213
import type { ExtendedResolveParams, GenResolverOpts } from './index';
1314
import { beforeQueryHelper } from './helpers/beforeQueryHelper';
@@ -27,6 +28,8 @@ export default function findByIds<TSource: MongooseDocument, TContext>(
2728
);
2829
}
2930

31+
const aliases = prepareAliases(model);
32+
3033
return tc.schemaComposer.createResolver({
3134
type: tc.getTypeNonNull().getTypePlural(),
3235
name: 'findByIds',
@@ -54,7 +57,7 @@ export default function findByIds<TSource: MongooseDocument, TContext>(
5457

5558
resolveParams.query = model.find(selector); // eslint-disable-line
5659
resolveParams.model = model; // eslint-disable-line
57-
projectionHelper(resolveParams);
60+
projectionHelper(resolveParams, aliases);
5861
limitHelper(resolveParams);
5962
sortHelper(resolveParams);
6063
return beforeQueryHelper(resolveParams);

src/resolvers/findMany.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
sortHelper,
1414
sortHelperArgs,
1515
projectionHelper,
16+
prepareAliases,
1617
} from './helpers';
1718
import type { ExtendedResolveParams, GenResolverOpts } from './index';
1819
import { beforeQueryHelper } from './helpers/beforeQueryHelper';
@@ -30,6 +31,8 @@ export default function findMany<TSource: MongooseDocument, TContext>(
3031
throw new Error('Second arg for Resolver findMany() should be instance of ObjectTypeComposer.');
3132
}
3233

34+
const aliases = prepareAliases(model);
35+
3336
return tc.schemaComposer.createResolver({
3437
type: tc.getTypeNonNull().getTypePlural(),
3538
name: 'findMany',
@@ -52,11 +55,11 @@ export default function findMany<TSource: MongooseDocument, TContext>(
5255
resolve: (resolveParams: ExtendedResolveParams) => {
5356
resolveParams.query = model.find();
5457
resolveParams.model = model;
55-
filterHelper(resolveParams);
58+
filterHelper(resolveParams, aliases);
5659
skipHelper(resolveParams);
5760
limitHelper(resolveParams);
5861
sortHelper(resolveParams);
59-
projectionHelper(resolveParams);
62+
projectionHelper(resolveParams, aliases);
6063
return beforeQueryHelper(resolveParams);
6164
},
6265
});

src/resolvers/findOne.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
sortHelper,
1212
sortHelperArgs,
1313
projectionHelper,
14+
prepareAliases,
1415
} from './helpers';
1516
import type { ExtendedResolveParams, GenResolverOpts } from './index';
1617
import { beforeQueryHelper } from './helpers/beforeQueryHelper';
@@ -28,6 +29,8 @@ export default function findOne<TSource: MongooseDocument, TContext>(
2829
throw new Error('Second arg for Resolver findOne() should be instance of ObjectTypeComposer.');
2930
}
3031

32+
const aliases = prepareAliases(model);
33+
3134
return tc.schemaComposer.createResolver({
3235
type: tc,
3336
name: 'findOne',
@@ -47,10 +50,10 @@ export default function findOne<TSource: MongooseDocument, TContext>(
4750
resolve: (resolveParams: ExtendedResolveParams) => {
4851
resolveParams.query = model.findOne({}); // eslint-disable-line
4952
resolveParams.model = model;
50-
filterHelper(resolveParams);
53+
filterHelper(resolveParams, aliases);
5154
skipHelper(resolveParams);
5255
sortHelper(resolveParams);
53-
projectionHelper(resolveParams);
56+
projectionHelper(resolveParams, aliases);
5457
return beforeQueryHelper(resolveParams);
5558
},
5659
});

0 commit comments

Comments
 (0)