Skip to content

Commit 6bff49a

Browse files
author
yoavkarako
authored
1.6.5 (#51)
1 parent 4553356 commit 6bff49a

10 files changed

+130
-66
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Change Log
2+
##### 1.6.5
3+
- Allow override of the is-resolved-field logic in projection and update validation
24
##### 1.6.4
35
- `$all`
46
##### 1.6.3

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# graphql-to-mongodb
22
[![Build Status](https://travis-ci.org/Soluto/graphql-to-mongodb.svg?branch=master)](https://travis-ci.org/Soluto/graphql-to-mongodb)
33

4-
If you want to give your Nodejs GraphQL service a whole lot of the power of the MongoDb database you have standing behind it with very little hassle, you've come to the right place!
4+
If you want to grant your Nodejs GraphQL service a whole lot of the power of the MongoDb database standing behind it with very little hassle, you've come to the right place!
55

66
### [Examples](./examples)
77
### [Change Log](./CHANGELOG.md)
@@ -31,11 +31,11 @@ new GraphQLObjectType({
3131
```
3232
#### An example GraphQL query supported by the package:
3333
34-
Queries the first 50 persons, oldest first, over the age of 18, and whose first name is John
34+
Queries the first 50 people, oldest first, over the age of 18, and whose first name is John.
3535
3636
```
3737
{
38-
person (
38+
people (
3939
filter: {
4040
age: { GT: 18 },
4141
name: {
@@ -51,7 +51,7 @@ Queries the first 50 persons, oldest first, over the age of 18, and whose first
5151
}
5252
```
5353
54-
**To implement, we'll define the peron query field in our GraphQL scheme like so:**
54+
**To implement, we'll define the people query field in our GraphQL scheme like so:**
5555
5656
5757
```js
@@ -84,7 +84,7 @@ You'll notice that integrating the package takes little more than adding some fa
8484
8585
**The following field is added to the schema (copied from graphiQl):**
8686
```
87-
person(
87+
people(
8888
filter: PersonFilterType
8989
sort: PersonSortType
9090
pagination: GraphQLPaginationType

index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ export { getGraphQLFilterType } from './src/graphQLFilterType';
22
export { getMongoDbFilter, MongoDbFilter } from './src/mongoDbFilter';
33
export { getGraphQLUpdateType } from './src/graphQLUpdateType';
44
export { getGraphQLInsertType } from './src/graphQLInsertType';
5-
export { getMongoDbUpdate } from './src/mongoDbUpdate';
5+
export { getMongoDbUpdate, UpdateObj } from './src/mongoDbUpdate';
66
export { validateUpdateArgs } from "./src/mongoDbUpdateValidation";
77
export { default as GraphQLPaginationType } from './src/graphQLPaginationType';
88
export { getGraphQLSortType, GraphQLSortType } from './src/graphQLSortType';
9-
export { default as getMongoDbSort } from './src/mongoDbSort';
10-
export { getMongoDbProjection, MongoDbProjection } from './src/mongoDbProjection';
11-
export { getMongoDbQueryResolver, getGraphQLQueryArgs, QueryOptions } from './src/queryResolver';
9+
export { default as getMongoDbSort, MongoDbSort } from './src/mongoDbSort';
10+
export { getMongoDbProjection, MongoDbProjection, GetMongoDbProjectionOptions } from './src/mongoDbProjection';
11+
export { getMongoDbQueryResolver, getGraphQLQueryArgs, QueryOptions, MongoDbOptions } from './src/queryResolver';
1212
export { getMongoDbUpdateResolver, getGraphQLUpdateArgs, UpdateOptions } from './src/updateResolver';
1313
export { setLogger, getLogger } from './src/logger';
1414
export { getTypesCache, clearTypesCache } from './src/common';

package.json

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "graphql-to-mongodb",
3-
"version": "1.6.4",
4-
"description": "Generic run-time generation of input filter types for existing graphql types and parsing client requests to mongodb queries",
3+
"version": "1.6.5",
4+
"description": "Generic run-time generation of input filter types for existing graphql types, and parsing of said input types into MongoDB queries",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",
77
"scripts": {
@@ -31,5 +31,12 @@
3131
},
3232
"peerDependencies": {
3333
"graphql": "^14.2.1"
34-
}
34+
},
35+
"keywords": [
36+
"graphql",
37+
"mongodb",
38+
"generate",
39+
"backend",
40+
"binding"
41+
]
3542
}

src/mongoDbProjection.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,49 @@
11
import { getInnerType, flatten, addPrefixToProperties, GraphQLFieldsType } from './common';
2-
import { isType, GraphQLResolveInfo, SelectionNode, FragmentSpreadNode } from 'graphql';
2+
import { isType, GraphQLResolveInfo, SelectionNode, FragmentSpreadNode, GraphQLField } from 'graphql';
33
import { logOnError } from './logger';
44

55
export interface MongoDbProjection {
66
[key: string]: 1
77
};
88

9-
export const getMongoDbProjection = logOnError((info: GraphQLResolveInfo, graphQLFieldsType: GraphQLFieldsType, ...excludedFields: string[]): MongoDbProjection => {
9+
export interface GetMongoDbProjectionOptions {
10+
isResolvedField: (field: GraphQLField<any, any>) => boolean;
11+
excludedFields: string[];
12+
}
13+
14+
const defaultOptions: GetMongoDbProjectionOptions = {
15+
isResolvedField: ((field: GraphQLField<any, any>) => !!field.resolve),
16+
excludedFields: [],
17+
}
18+
19+
export const getMongoDbProjection = logOnError((info: GraphQLResolveInfo, graphQLFieldsType: GraphQLFieldsType, options: GetMongoDbProjectionOptions = defaultOptions): MongoDbProjection => {
1020
if (!Object.keys(info).includes('fieldNodes')) throw 'First argument of "getMongoDbProjection" must be a GraphQLResolveInfo';
1121
if (!isType(graphQLFieldsType)) throw 'Second argument of "getMongoDbProjection" must be a GraphQLType';
1222

1323
const nodes = flatten(info.fieldNodes.map(_ => [..._.selectionSet.selections]));
1424

15-
const projection = getSelectedProjection(nodes, graphQLFieldsType, { info, fragments: {} });
25+
const projection = getSelectedProjection(nodes, graphQLFieldsType, { info, fragments: {} }, {
26+
...options,
27+
isResolvedField: options.isResolvedField || ((field: GraphQLField<any, any>) => !!field.resolve)
28+
});
1629

1730
return omitRedundantProjection(projection);
1831
});
1932

2033
function getSelectedProjection(
21-
selectionNodes: SelectionNode[],
22-
graphQLFieldsType: GraphQLFieldsType,
23-
extra: { info: GraphQLResolveInfo, fragments: { [key: string]: MongoDbProjection } },
24-
...excludedFields: string[]): MongoDbProjection {
34+
selectionNodes: SelectionNode[],
35+
graphQLFieldsType: GraphQLFieldsType,
36+
extra: { info: GraphQLResolveInfo, fragments: { [key: string]: MongoDbProjection } },
37+
options: GetMongoDbProjectionOptions = defaultOptions): MongoDbProjection {
2538
const fields = graphQLFieldsType.getFields()
2639

2740
return selectionNodes.reduce((projection, node) => {
2841
if (node.kind === 'Field') {
29-
if (node.name.value === '__typename' || excludedFields.includes(node.name.value)) return projection;
42+
if (node.name.value === '__typename' || options.excludedFields.includes(node.name.value)) return projection;
3043

3144
const field = fields[node.name.value];
3245

33-
if (!!field.resolve) {
46+
if (options.isResolvedField(field)) {
3447
const dependencies: string[] = field["dependencies"] || [];
3548
const dependenciesProjection = dependencies.reduce((agg, dependency) => ({ ...agg, [dependency]: 1 }), {});
3649
return {
@@ -44,7 +57,7 @@ function getSelectedProjection(
4457
[node.name.value]: 1
4558
};
4659

47-
const nested = getSelectedProjection([...node.selectionSet.selections], getInnerType(field.type) as GraphQLFieldsType, extra);
60+
const nested = getSelectedProjection([...node.selectionSet.selections], getInnerType(field.type) as GraphQLFieldsType, extra, options);
4861

4962
return {
5063
...projection,
@@ -54,31 +67,32 @@ function getSelectedProjection(
5467
const type = extra.info.schema.getType(node.typeCondition.name.value);
5568
return {
5669
...projection,
57-
...getSelectedProjection([...node.selectionSet.selections], type as GraphQLFieldsType, extra, ...excludedFields)
70+
...getSelectedProjection([...node.selectionSet.selections], type as GraphQLFieldsType, extra, options)
5871
};
5972
} else if (node.kind === 'FragmentSpread') {
6073
return {
6174
...projection,
62-
...getFragmentProjection(node, graphQLFieldsType, extra)
75+
...getFragmentProjection(node, graphQLFieldsType, extra, options)
6376
};
6477
}
6578
}, {});
6679
}
6780

6881
function getFragmentProjection(
69-
fragmentSpreadNode: FragmentSpreadNode,
70-
graphQLFieldsType: GraphQLFieldsType,
71-
extra: { info: GraphQLResolveInfo, fragments: { [key: string]: MongoDbProjection } }): MongoDbProjection {
82+
fragmentSpreadNode: FragmentSpreadNode,
83+
graphQLFieldsType: GraphQLFieldsType,
84+
extra: { info: GraphQLResolveInfo, fragments: { [key: string]: MongoDbProjection } },
85+
options: GetMongoDbProjectionOptions = defaultOptions): MongoDbProjection {
7286
const fragmentName = fragmentSpreadNode.name.value;
7387
if (extra.fragments[fragmentName]) return extra.fragments[fragmentName];
7488
const fragmentNode = extra.info.fragments[fragmentName];
75-
extra.fragments[fragmentName] = getSelectedProjection([...fragmentNode.selectionSet.selections], graphQLFieldsType, extra);
89+
extra.fragments[fragmentName] = getSelectedProjection([...fragmentNode.selectionSet.selections], graphQLFieldsType, extra, options);
7690
return extra.fragments[fragmentName];
7791
}
7892

7993
function omitRedundantProjection(projection: MongoDbProjection) {
8094
return Object.keys(projection).reduce((proj, key) => {
81-
if (Object.keys(projection).some(otherKey =>
95+
if (Object.keys(projection).some(otherKey =>
8296
otherKey !== key && new RegExp(`^${otherKey}[.]`).test(key))) {
8397
return proj;
8498
}

src/mongoDbUpdateValidation.ts

+33-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { UpdateArgs } from "./mongoDbUpdate";
2-
import { GraphQLObjectType, GraphQLType, GraphQLNonNull, GraphQLList, GraphQLFieldMap, GraphQLError } from "graphql";
2+
import { GraphQLObjectType, GraphQLType, GraphQLNonNull, GraphQLList, GraphQLFieldMap, GraphQLField, GraphQLError } from "graphql";
33
import { isNonNullField, getInnerType, flatten, isListField } from "./common";
44
import { OVERWRITE } from "./graphQLUpdateType";
55

@@ -13,35 +13,52 @@ export enum ShouldAssert {
1313
False
1414
}
1515

16-
export function validateUpdateArgs(updateArgs: UpdateArgs, graphQLType: GraphQLObjectType, overwrite: boolean): void {
16+
export interface ValidateUpdateArgsOptions {
17+
overwrite: boolean;
18+
isResolvedField?: (field: GraphQLField<any, any>) => boolean;
19+
}
20+
21+
const defaultOptions: ValidateUpdateArgsOptions = {
22+
overwrite: false,
23+
};
24+
25+
export function validateUpdateArgs(updateArgs: UpdateArgs, graphQLType: GraphQLObjectType, options: ValidateUpdateArgsOptions = defaultOptions): void {
1726
let errors: string[] = [];
1827

19-
errors = errors.concat(validateNonNullableFieldsOuter(updateArgs, overwrite, graphQLType));
28+
errors = errors.concat(validateNonNullableFieldsOuter(updateArgs, graphQLType, options));
2029

2130
if (errors.length > 0) {
2231
throw new GraphQLError(errors.join("\n"));
2332
}
2433
}
2534

26-
function validateNonNullableFieldsOuter(updateArgs: UpdateArgs, overwrite: boolean, graphQLType: GraphQLObjectType): string[] {
35+
function validateNonNullableFieldsOuter(
36+
updateArgs: UpdateArgs,
37+
graphQLType: GraphQLObjectType,
38+
{ overwrite, isResolvedField }: ValidateUpdateArgsOptions): string[] {
2739
const shouldAssert: ShouldAssert = !!updateArgs.setOnInsert
2840
? ShouldAssert.True
2941
: overwrite
3042
? ShouldAssert.DefaultTrueRoot
3143
: ShouldAssert.False;
3244

33-
return validateNonNullableFields(Object.keys(updateArgs).map(_ => updateArgs[_]), graphQLType, shouldAssert);
45+
return validateNonNullableFields(Object.keys(updateArgs).map(_ => updateArgs[_]), graphQLType, shouldAssert, isResolvedField);
3446
}
3547

36-
export function validateNonNullableFields(objects: object[], graphQLType: GraphQLObjectType, shouldAssert: ShouldAssert, path: string[] = []): string[] {
48+
export function validateNonNullableFields(
49+
objects: object[],
50+
graphQLType: GraphQLObjectType,
51+
shouldAssert: ShouldAssert,
52+
isResolvedField: ((field: GraphQLField<any, any>) => boolean) = (field: GraphQLField<any, any>) => !!field.resolve,
53+
path: string[] = []): string[] {
3754
const typeFields = graphQLType.getFields();
3855

3956
const errors: string[] = shouldAssert === ShouldAssert.True ? validateNonNullableFieldsAssert(objects, typeFields, path) : [];
4057

4158
const overwrite = objects.map(_ => _[OVERWRITE]).filter(_ => _)[0];
4259
shouldAssert = getShouldAssert(shouldAssert, overwrite);
4360

44-
return [...errors, ...validateNonNullableFieldsTraverse(objects, typeFields, shouldAssert, path)];
61+
return [...errors, ...validateNonNullableFieldsTraverse(objects, typeFields, shouldAssert, isResolvedField, path)];
4562
}
4663

4764
export function validateNonNullableFieldsAssert(objects: object[], typeFields: GraphQLFieldMap<any, any>, path: string[] = []): string[] {
@@ -98,25 +115,30 @@ export function getShouldAssert(current: ShouldAssert, input?: boolean): ShouldA
98115
return current;
99116
}
100117

101-
export function validateNonNullableFieldsTraverse(objects: object[], typeFields: GraphQLFieldMap<any, any>, shouldAssert: ShouldAssert, path: string[] = []): string[] {
118+
export function validateNonNullableFieldsTraverse(
119+
objects: object[],
120+
typeFields: GraphQLFieldMap<any, any>,
121+
shouldAssert: ShouldAssert,
122+
isResolvedField: (field: GraphQLField<any, any>) => boolean = (field: GraphQLField<any, any>) => !!field.resolve,
123+
path: string[] = []): string[] {
102124
let keys: string[] = Array.from(new Set(flatten(objects.map(_ => Object.keys(_)))));
103125

104126
return keys.reduce((agg, key) => {
105127
const field = typeFields[key];
106128
const type = field.type;
107129
const innerType = getInnerType(type);
108130

109-
if (!(innerType instanceof GraphQLObjectType) || field.resolve) {
131+
if (!(innerType instanceof GraphQLObjectType) || isResolvedField(field)) {
110132
return agg;
111133
}
112134

113135
const newPath = [...path, key];
114136
const values = objects.map(_ => _[key]).filter(_ => _);
115137

116138
if (isListField(type)) {
117-
return [...agg, ...flatten(flattenListField(values, type).map(_ => validateNonNullableFields([_], innerType, ShouldAssert.True, newPath)))];
139+
return [...agg, ...flatten(flattenListField(values, type).map(_ => validateNonNullableFields([_], innerType, ShouldAssert.True, isResolvedField, newPath)))];
118140
} else {
119-
return [...agg, ...validateNonNullableFields(values, innerType, shouldAssert, newPath)];
141+
return [...agg, ...validateNonNullableFields(values, innerType, shouldAssert, isResolvedField, newPath)];
120142
}
121143
}, []);
122144
}

src/queryResolver.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { getMongoDbFilter, MongoDbFilter } from './mongoDbFilter';
2-
import { getMongoDbProjection, MongoDbProjection } from './mongoDbProjection';
2+
import { getMongoDbProjection, MongoDbProjection, GetMongoDbProjectionOptions } from './mongoDbProjection';
33
import { getGraphQLFilterType } from './graphQLFilterType';
44
import { getGraphQLSortType } from './graphQLSortType';
55
import GraphQLPaginationType from './graphQLPaginationType';
66
import getMongoDbSort, { MongoDbSort } from "./mongoDbSort";
7-
import { isType, GraphQLResolveInfo, GraphQLFieldResolver, GraphQLObjectType, GraphQLInputObjectType } from 'graphql';
7+
import { isType, GraphQLResolveInfo, GraphQLFieldResolver, GraphQLInputObjectType, GraphQLField } from 'graphql';
88
import { GraphQLFieldsType } from './common';
99

1010
export interface QueryCallback<TSource, TContext> {
@@ -26,12 +26,14 @@ export interface MongoDbOptions {
2626
projection?: MongoDbProjection;
2727
}
2828

29-
export interface QueryOptions {
29+
export type QueryOptions = {
3030
differentOutputType?: boolean;
31-
};
31+
} & Partial<GetMongoDbProjectionOptions>;
3232

3333
const defaultOptions: Required<QueryOptions> = {
34-
differentOutputType: false
34+
differentOutputType: false,
35+
excludedFields: [],
36+
isResolvedField: (field: GraphQLField<any, any>) => !!field.resolve
3537
};
3638

3739
export function getMongoDbQueryResolver<TSource, TContext>(
@@ -40,24 +42,24 @@ export function getMongoDbQueryResolver<TSource, TContext>(
4042
queryOptions?: QueryOptions): GraphQLFieldResolver<TSource, TContext> {
4143
if (!isType(graphQLType)) throw 'getMongoDbQueryResolver must recieve a graphql type';
4244
if (typeof queryCallback !== 'function') throw 'getMongoDbQueryResolver must recieve a queryCallback function';
43-
const requiredQueryOptions: Required<QueryOptions> = { ...defaultOptions, ...queryOptions };
45+
const requiredQueryOptions = { ...defaultOptions, ...queryOptions };
4446

4547
return async (source: TSource, args: { [argName: string]: any }, context: TContext, info: GraphQLResolveInfo): Promise<any> => {
4648
const filter = getMongoDbFilter(graphQLType, args.filter);
47-
const projection = requiredQueryOptions.differentOutputType ? undefined : getMongoDbProjection(info, graphQLType);
49+
const projection = requiredQueryOptions.differentOutputType ? undefined : getMongoDbProjection(info, graphQLType, requiredQueryOptions);
4850
const options: MongoDbOptions = { projection };
4951
if (args.sort) options.sort = getMongoDbSort(args.sort);
5052
if (args.pagination && args.pagination.limit) options.limit = args.pagination.limit;
5153
if (args.pagination && args.pagination.skip) options.skip = args.pagination.skip;
52-
54+
5355
return await queryCallback(filter, projection, options, source, args, context, info);
5456
}
5557
}
5658

57-
export function getGraphQLQueryArgs(graphQLType: GraphQLFieldsType): { [key: string]: { type: GraphQLInputObjectType } } & {
59+
export function getGraphQLQueryArgs(graphQLType: GraphQLFieldsType): { [key: string]: { type: GraphQLInputObjectType } } & {
5860
filter: { type: GraphQLInputObjectType },
59-
sort: { type: GraphQLInputObjectType } ,
60-
pagination: { type: GraphQLInputObjectType }
61+
sort: { type: GraphQLInputObjectType },
62+
pagination: { type: GraphQLInputObjectType }
6163
} {
6264
return {
6365
filter: { type: getGraphQLFilterType(graphQLType) },

0 commit comments

Comments
 (0)