diff --git a/packages/adapter-commons/src/sort.ts b/packages/adapter-commons/src/sort.ts index 5803a000c3..d83dcd6880 100644 --- a/packages/adapter-commons/src/sort.ts +++ b/packages/adapter-commons/src/sort.ts @@ -69,17 +69,36 @@ export function compare (a: any, b: any, compareStrings: any = exports.compareNS // An in-memory sorting function according to the // $sort special query parameter export function sorter ($sort: any) { - const criteria = Object.keys($sort).map(key => { - const direction = $sort[key]; + let sortLevels = false; // True if $sort has tags with '.' i.e. '{a: 1, b: -1, "c.x.z": 1}' + + const getVal = (a: any, sortKeys: any[]) => { + let keys = sortKeys.map(key => key); + let val = a; + do { + let key = keys.shift(); + val = val[key]; + } while (keys.length); + + return val; + }; + + const criteria = Object.keys($sort).map(key => { + const direction = $sort[key]; + const keys = key.split('.'); + sortLevels = keys.length > 1; - return { key, direction }; - }); + return { keys, direction }; + }); return function (a: any, b: any) { let compare; for (const criterion of criteria) { - compare = criterion.direction * exports.compare(a[criterion.key], b[criterion.key]); + if (sortLevels) { + compare = criterion.direction * exports.compare(getVal(a, criterion.keys), getVal(b, criterion.keys)); + } else { + compare = criterion.direction * exports.compare(a[criterion.keys[0]], b[criterion.keys[0]]); + } if (compare !== 0) { return compare; diff --git a/packages/adapter-commons/test/sort.test.ts b/packages/adapter-commons/test/sort.test.ts index 88e144b56c..1dde313091 100644 --- a/packages/adapter-commons/test/sort.test.ts +++ b/packages/adapter-commons/test/sort.test.ts @@ -176,4 +176,69 @@ describe('@feathersjs/adapter-commons', () => { ]); }); }); + + describe('sorter mongoDB-like sorting on embedded objects', () => { + let data: any[] = []; + + beforeEach(() => { + data = [ + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 } + ]; + + }); + + it('straight test', () => { + const sort = sorter({ + amount: -1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 } + ]); + }); + + it('embedded sort 1', () => { + const sort = sorter({ + "item.category": 1, + "item.type": 1, + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 } + ]); + }); + + it('embedded sort 2', () => { + const sort = sorter({ + "item.category": 1, + "item.type": 1, + amount: 1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 } + ]); + }); + }); + });