Skip to content

Commit 0f5e435

Browse files
committed
feat: add {sort: { multi: true }} option to resolvers which have sort arg. This option makes sort arg as List where the client can provide several sorting keys.
closes #210
1 parent 1cedd33 commit 0f5e435

File tree

9 files changed

+80
-11
lines changed

9 files changed

+80
-11
lines changed

src/resolvers/__tests__/findMany-test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Resolver, schemaComposer, ObjectTypeComposer } from 'graphql-compose';
1+
import { Resolver, schemaComposer, ObjectTypeComposer, EnumTypeComposer } from 'graphql-compose';
22
import { UserModel, IUser } from '../../__mocks__/userModel';
33
import { findMany } from '../findMany';
44
import { convertModelToGraphQL } from '../../fieldsConverter';
55
import { ExtendedResolveParams } from '..';
6+
import { testFieldConfig } from '../../utils/testHelpers';
67

78
beforeAll(() => UserModel.base.createConnection());
89
afterAll(() => UserModel.base.disconnect());
@@ -155,4 +156,37 @@ describe('findMany() ->', () => {
155156
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
156157
});
157158
});
159+
160+
it('should support multi sort', async () => {
161+
let spyQuery: any;
162+
const resolver = findMany(UserModel, UserTC, { sort: { multi: true } }).wrapResolve(
163+
(next) => (rp) => {
164+
const res = next(rp);
165+
spyQuery = rp.query;
166+
return res;
167+
}
168+
);
169+
170+
expect((resolver.getArgTC('sort') as EnumTypeComposer).getFieldNames()).toEqual(
171+
expect.arrayContaining([
172+
'_ID_ASC',
173+
'_ID_DESC',
174+
'NAME_ASC',
175+
'NAME_DESC',
176+
'NAME__AGE_ASC',
177+
'NAME__AGE_DESC',
178+
])
179+
);
180+
const res = await testFieldConfig({
181+
field: resolver,
182+
args: {
183+
sort: ['_ID_ASC', 'NAME_ASC', '_ID_DESC'],
184+
},
185+
selection: `{
186+
name
187+
}`,
188+
});
189+
expect(res).toEqual([{ name: 'userName1' }, { name: 'userName2' }]);
190+
expect(spyQuery?.options).toEqual({ limit: 1000, sort: { _id: 1, name: 1 } });
191+
});
158192
});

src/resolvers/findByIds.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface FindByIdsResolverOpts {
3636
type TArgs = {
3737
_ids: any;
3838
limit?: number;
39-
sort?: Record<string, any>;
39+
sort?: string | string[] | Record<string, any>;
4040
};
4141

4242
export function findByIds<TSource = any, TContext = any, TDoc extends Document = any>(

src/resolvers/findMany.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type TArgs = {
4646
filter?: any;
4747
limit?: number;
4848
skip?: number;
49-
sort?: Record<string, any>;
49+
sort?: string | string[] | Record<string, any>;
5050
};
5151

5252
export function findMany<TSource = any, TContext = any, TDoc extends Document = any>(

src/resolvers/findOne.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface FindOneResolverOpts {
4040

4141
type TArgs = {
4242
filter?: any;
43-
sort?: Record<string, any>;
43+
sort?: string | string[] | Record<string, any>;
4444
skip?: number;
4545
};
4646

src/resolvers/helpers/__tests__/sort-test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,17 @@ describe('Resolver helper `sort` ->', () => {
7676
const args: any = sortHelperArgs(UserTC, UserModel, {
7777
sortTypeName: 'SortInput',
7878
});
79+
expect(args.sort.type.getTypeName()).toBe('SortInput');
7980
expect(args.sort.type).toBeInstanceOf(EnumTypeComposer);
8081
});
82+
83+
it('should return sort field as List', () => {
84+
const args: any = sortHelperArgs(UserTC, UserModel, {
85+
sortTypeName: 'SortInput',
86+
multi: true,
87+
});
88+
expect(args.sort.type.getTypeName()).toBe('[SortInput!]');
89+
});
8190
});
8291

8392
describe('sortHelper()', () => {

src/resolvers/helpers/sort.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
/* eslint-disable no-use-before-define */
22

33
import type { Model, Document } from 'mongoose';
4-
import type {
4+
import {
55
ObjectTypeComposerArgumentConfigMapDefinition,
66
ObjectTypeComposer,
77
SchemaComposer,
88
EnumTypeComposer,
9+
isObject,
910
} from 'graphql-compose';
1011
import { getIndexesFromModel, extendByReversedIndexes } from '../../utils/getIndexesFromModel';
1112
import type { ExtendedResolveParams } from '../index';
1213

1314
export type SortHelperArgsOpts = {
1415
sortTypeName?: string;
16+
/**
17+
* Allow sort by several fields.
18+
* This makes arg as array of sort values.
19+
*/
20+
multi?: boolean;
1521
};
1622

1723
export function sortHelperArgs<TDoc extends Document = any>(
@@ -35,14 +41,34 @@ export function sortHelperArgs<TDoc extends Document = any>(
3541

3642
return {
3743
sort: {
38-
type: gqSortType,
44+
type: opts?.multi ? gqSortType.NonNull.List : gqSortType,
3945
},
4046
};
4147
}
4248

4349
export function sortHelper(resolveParams: ExtendedResolveParams): void {
44-
const sort = resolveParams && resolveParams.args && resolveParams.args.sort;
45-
if (sort && typeof sort === 'object' && Object.keys(sort).length > 0) {
50+
const _sort = resolveParams?.args?.sort;
51+
if (!_sort) return;
52+
53+
let sort: Record<string, any>;
54+
if (Array.isArray(_sort)) {
55+
sort = {};
56+
// combine array in one object,
57+
// keep only first key occurrence (rest skip)
58+
_sort.forEach((o) => {
59+
if (isObject(o)) {
60+
Object.keys(o).forEach((key) => {
61+
if (!sort.hasOwnProperty(key)) {
62+
sort[key] = (o as any)[key];
63+
}
64+
});
65+
}
66+
});
67+
} else {
68+
sort = _sort;
69+
}
70+
71+
if (typeof sort === 'object' && Object.keys(sort).length > 0) {
4672
resolveParams.query = resolveParams.query.sort(sort);
4773
}
4874
}

src/resolvers/removeOne.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface RemoveOneResolverOpts {
2323

2424
type TArgs = {
2525
filter?: any;
26-
sort?: Record<string, any>;
26+
sort?: string | string[] | Record<string, any>;
2727
};
2828

2929
export function removeOne<TSource = any, TContext = any, TDoc extends Document = any>(

src/resolvers/updateMany.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type TArgs = {
3838
filter?: any;
3939
limit?: number;
4040
skip?: number;
41-
sort?: string;
41+
sort?: string | string[] | Record<string, any>;
4242
};
4343

4444
export function updateMany<TSource = any, TContext = any, TDoc extends Document = any>(

src/resolvers/updateOne.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type TArgs = {
3232
record: any;
3333
filter?: any;
3434
skip?: number;
35-
sort?: Record<string, any>;
35+
sort?: string | string[] | Record<string, any>;
3636
};
3737

3838
export function updateOne<TSource = any, TContext = any, TDoc extends Document = any>(

0 commit comments

Comments
 (0)