diff --git a/README.md b/README.md index 3251488..5e60bd2 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,32 @@ projection = { */ ``` +Projections also accepts keepParentField option, which should return the parents included in the object not only the leaves. + +```javascript +const projection = fieldsProjection(info, { + path: 'users', + keepParentField: true, +}); +/* +RESULT: +projection = { + 'edges': 1, // parent node + 'edges.node': 1, // parent node + 'pageInfo': 1, // parent node + 'pageInfo.startCursor': 1, + 'pageInfo.endCursor': 1, + 'pageInfo.hasNextPage': 1, + 'edges.node.id': 1, + 'edges.node.firstName': 1, + 'edges.node.lastName': 1, + 'edges.node.phoneNumber': 1, + 'edges.node.email': 1, + 'edges.node.address': 1, +} +*/ +``` + Projections also accepts transform option, which should be a mapping object between projections paths: diff --git a/index.ts b/index.ts index 8085377..bb69064 100644 --- a/index.ts +++ b/index.ts @@ -74,6 +74,12 @@ export interface FieldsListOptions { */ withDirectives?: boolean; + /** + * Flag which turns on/off whether to return the parent fields or not + * @type {boolean} + */ + keepParentField?: boolean; + /** * Fields skip rule patterns. Usually used to ignore part of request field * subtree. For example if query looks like: @@ -547,11 +553,14 @@ export function fieldsProjection( while (stack.length) { for (const j of Object.keys(stack[0].tree)) { if (stack[0].tree[j]) { + const nodeDottedName = toDotNotation(stack[0].node, j); stack.push({ - node: toDotNotation(stack[0].node, j), + node: nodeDottedName, tree: stack[0].tree[j], }); + if (options?.keepParentField) map[nodeDottedName] = 1; + continue; } diff --git a/test/index.ts b/test/index.ts index 4dde02d..ca9d338 100644 --- a/test/index.ts +++ b/test/index.ts @@ -138,6 +138,90 @@ describe('module "graphql-fields-list"', () => { }); }); + + it('should extract proper fields if keepParentField is specified', () => { + expect( + fieldsProjection(info, { keepParentField: true }) + ).deep.equals({ + users: 1, + "users.edges": 1, + "users.edges.node": 1, + "users.pageInfo": 1, + "users.pageInfo.startCursor": 1, + "users.pageInfo.endCursor": 1, + "users.pageInfo.hasNextPage": 1, + "users.edges.node.id": 1, + "users.edges.node.firstName": 1, + "users.edges.node.lastName": 1, + "users.edges.node.phoneNumber": 1, + "users.edges.node.email": 1, + "users.edges.node.address": 1, + }); + expect( + fieldsProjection(info, { + path: "users", + keepParentField: true, + }) + ).deep.equals({ + edges: 1, + "edges.node": 1, + pageInfo: 1, + "pageInfo.startCursor": 1, + "pageInfo.endCursor": 1, + "pageInfo.hasNextPage": 1, + "edges.node.id": 1, + "edges.node.firstName": 1, + "edges.node.lastName": 1, + "edges.node.phoneNumber": 1, + "edges.node.email": 1, + "edges.node.address": 1, + }); + expect( + fieldsProjection(info, { + skip: ["users.pageInfo"], + path: "users", + keepParentField: true, + }) + ).deep.equals({ + edges: 1, + "edges.node": 1, + "edges.node.id": 1, + "edges.node.firstName": 1, + "edges.node.lastName": 1, + "edges.node.phoneNumber": 1, + "edges.node.email": 1, + "edges.node.address": 1, + }); + expect(fieldsProjection(info, { + path: 'users.edges.node', + keepParentField: true, + })).deep.equals({ + 'id': 1, + 'firstName': 1, + 'lastName': 1, + 'phoneNumber': 1, + 'email': 1, + 'address': 1, + }); + expect(fieldsProjection(info, { + path: 'users.edges', + keepParentField: true, + transform: { + 'node.id': 'node._id', + 'node.firstName': 'node.given_name', + 'node.lastName': 'node.family_name', + }, + })).deep.equals({ + "node": 1, + 'node._id': 1, + 'node.given_name': 1, + 'node.family_name': 1, + 'node.phoneNumber': 1, + 'node.email': 1, + 'node.address': 1, + }); + }); + it('should properly transform field names', () => { expect(fieldsProjection(info, { path: 'users.edges.node',