Socket
Socket
Sign inDemoInstall

@asymmetrik/fhir-qb-mongo

Package Overview
Dependencies
4
Maintainers
8
Versions
10
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.10.2 to 0.10.3

124

index.js

@@ -10,3 +10,3 @@ let supportedSearchTransformations = {

*/
let buildAndQuery = function({ queries }) {
let buildAndQuery = function(queries) {
return { $and: queries };

@@ -18,3 +18,3 @@ };

*/
let buildOrQuery = function({ queries, invert }) {
let buildOrQuery = function({ queries, invert = false }) {
return { [invert ? '$nor' : '$or']: queries };

@@ -119,7 +119,83 @@ };

/**
* Takes in 2 lists, joinsToPerform and matchesToPerform. Constructs a mongo aggregation query that first performs
* any necessary joins as dictated by joinsToPerform, and then filters the results them down using matchesToPerform.
*
* Returns a mongo aggregate query.
* TODO - WORK IN PROGRESS
* Apply search result transformations
* @param query
* @param searchResultTransformations
*/
let applySearchResultTransformations = function({
query,
searchResultTransformations,
}) {
Object.keys(searchResultTransformations).forEach(transformation => {
query.push(
supportedSearchTransformations[transformation](
searchResultTransformations[transformation],
),
);
});
return query;
};
/**
* If we should not include archived, add a filter to remove them from the results
* @param query
* @param archivedParamPath
* @param includeArchived
* @returns {*}
*/
let applyArchivedFilter = function({
query,
archivedParamPath,
includeArchived,
}) {
if (!includeArchived) {
query.push({ $match: { [archivedParamPath]: false } });
}
return query;
};
/**
* Apply paging
* @param query
* @param pageNumber
* @param resultsPerPage
* @returns {*}
*/
let applyPaging = function({ query, pageNumber, resultsPerPage }) {
// If resultsPerPage is defined, skip to the appropriate page and limit the number of results that appear per page.
// Otherwise just insert a filler (to keep mongo happy) that skips no entries.
let pageSelection = resultsPerPage
? [{ $skip: (pageNumber - 1) * resultsPerPage }, { $limit: resultsPerPage }]
: [{ $skip: 0 }];
// If resultsPerPage is defined, calculate the total number of pages as the total number of records
// divided by the results per page rounded up to the nearest integer.
// Otherwise if resultsPerPage is not defined, all of the results will be on one page.
let numberOfPages = resultsPerPage
? { $ceil: { $divide: ['$total', resultsPerPage] } }
: 1;
query.push({
$facet: {
metadata: [
{ $count: 'total' },
{ $addFields: { numberOfPages: numberOfPages } },
{ $addFields: { page: pageNumber } }, // TODO may need some additional validation on this.
],
data: pageSelection,
},
});
return query;
};
/**
* Assembles a mongo aggregation pipeline
* @param joinsToPerform - List of joins to perform first through lookups
* @param matchesToPerform - List of matches to perform
* @param searchResultTransformations
* @param implementationParameters
* @param includeArchived
* @param pageNumber
* @param resultsPerPage
* @returns {Array}
*/
let assembleSearchQuery = function({

@@ -129,6 +205,16 @@ joinsToPerform,

searchResultTransformations,
implementationParameters,
includeArchived,
pageNumber,
resultsPerPage,
}) {
let aggregatePipeline = [];
let query = [];
let toSuppress = {};
// Check that the necessary implementation parameters were passed through
let {archivedParamPath} = implementationParameters;
if (!archivedParamPath) {
throw new Error('Missing required implementation parameter \'archivedParamPath\'');
}
// Construct the necessary joins and add them to the aggregate pipeline. Also follow each $lookup with an $unwind

@@ -139,3 +225,3 @@ // for ease of use.

let { from, localKey, foreignKey } = join;
aggregatePipeline.push({
query.push({
$lookup: {

@@ -148,3 +234,3 @@ from: from,

});
aggregatePipeline.push({ $unwind: `$${from}` });
query.push({ $unwind: `$${from}` });
toSuppress[from] = 0;

@@ -163,3 +249,3 @@ }

}
aggregatePipeline.push({ $match: buildAndQuery({ queries: listOfOrs }) });
query.push({ $match: buildAndQuery(listOfOrs) });
}

@@ -169,15 +255,13 @@

if (Object.keys(toSuppress).length > 0) {
aggregatePipeline.push({ $project: toSuppress });
query.push({ $project: toSuppress });
}
// TODO - WORK IN PROGRESS - handling search result transformations
// Handle search result parameters
Object.keys(searchResultTransformations).forEach(transformation => {
aggregatePipeline.push(
supportedSearchTransformations[transformation](
searchResultTransformations[transformation],
),
);
query = applyArchivedFilter({ query, archivedParamPath, includeArchived });
query = applySearchResultTransformations({
query,
searchResultTransformations,
});
return aggregatePipeline;
query = applyPaging({ query, pageNumber, resultsPerPage });
return query;
};

@@ -184,0 +268,0 @@

@@ -196,4 +196,20 @@ const mongoQB = require('./index');

describe('assembleSearchQuery Tests', () => {
test('Should return empty pipeline if no matches or joins to perform', () => {
const expectedResult = [];
test('Should return empty pipeline (except for archival and paging) if no matches or joins to perform', () => {
const expectedResult = [
{ $match: { 'meta._isArchived': false } },
{
$facet: {
data: [{ $skip: 0 }, { $limit: 10 }],
metadata: [
{ $count: 'total' },
{
$addFields: {
numberOfPages: { $ceil: { $divide: ['$total', 10] } },
},
},
{ $addFields: { page: 1 } },
],
},
},
];
let observedResult = mongoQB.assembleSearchQuery({

@@ -203,2 +219,6 @@ joinsToPerform: [],

searchResultTransformations: {},
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: false,
pageNumber: 1,
resultsPerPage: 10,
});

@@ -219,2 +239,17 @@ expect(observedResult).toEqual(expectedResult);

{ $project: { foo: 0 } },
{ $match: { 'meta._isArchived': false } },
{
$facet: {
data: [{ $skip: 0 }, { $limit: 10 }],
metadata: [
{ $count: 'total' },
{
$addFields: {
numberOfPages: { $ceil: { $divide: ['$total', 10] } },
},
},
{ $addFields: { page: 1 } },
],
},
},
];

@@ -225,2 +260,6 @@ let observedResult = mongoQB.assembleSearchQuery({

searchResultTransformations: {},
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: false,
pageNumber: 1,
resultsPerPage: 10,
});

@@ -230,3 +269,20 @@ expect(observedResult).toEqual(expectedResult);

test('Should fill in empty matches with empty objects to keep queries valid', () => {
const expectedResult = [{ $match: { $and: [{ $or: [{}] }] } }];
const expectedResult = [
{ $match: { $and: [{ $or: [{}] }] } },
{ $match: { 'meta._isArchived': false } },
{
$facet: {
data: [{ $skip: 0 }, { $limit: 10 }],
metadata: [
{ $count: 'total' },
{
$addFields: {
numberOfPages: { $ceil: { $divide: ['$total', 10] } },
},
},
{ $addFields: { page: 1 } },
],
},
},
];
let observedResult = mongoQB.assembleSearchQuery({

@@ -236,2 +292,6 @@ joinsToPerform: [],

searchResultTransformations: {},
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: false,
pageNumber: 1,
resultsPerPage: 10,
});

@@ -243,2 +303,17 @@ expect(observedResult).toEqual(expectedResult);

{ $match: { $and: [{ $or: [{ foo: { $gte: 1, $lte: 10 } }] }] } },
{ $match: { 'meta._isArchived': false } },
{
$facet: {
data: [{ $skip: 0 }, { $limit: 10 }],
metadata: [
{ $count: 'total' },
{
$addFields: {
numberOfPages: { $ceil: { $divide: ['$total', 10] } },
},
},
{ $addFields: { page: 1 } },
],
},
},
];

@@ -249,2 +324,6 @@ let observedResult = mongoQB.assembleSearchQuery({

searchResultTransformations: {},
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: false,
pageNumber: 1,
resultsPerPage: 10,
});

@@ -256,3 +335,20 @@ expect(observedResult).toEqual(expectedResult);

test('Should add $limit to the end of the pipeline when given _count parameter', () => {
const expectedResult = [{ $limit: 3 }];
const expectedResult = [
{ $match: { 'meta._isArchived': false } },
{ $limit: 3 },
{
$facet: {
data: [{ $skip: 0 }, { $limit: 10 }],
metadata: [
{ $count: 'total' },
{
$addFields: {
numberOfPages: { $ceil: { $divide: ['$total', 10] } },
},
},
{ $addFields: { page: 1 } },
],
},
},
];
let observedResult = mongoQB.assembleSearchQuery({

@@ -262,2 +358,6 @@ joinsToPerform: [],

searchResultTransformations: { _count: 3 },
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: false,
pageNumber: 1,
resultsPerPage: 10,
});

@@ -267,2 +367,94 @@ expect(observedResult).toEqual(expectedResult);

});
describe('Paging Tests', () => {
test('Should default to page 1 with no limits if resultsPerPage is undefined', () => {
const expectedResult = [
{
$match: {
'meta._isArchived': false,
},
},
{
$facet: {
data: [{ $skip: 0 }],
metadata: [
{
$count: 'total',
},
{
$addFields: {
numberOfPages: 1,
},
},
{
$addFields: {
page: 1,
},
},
],
},
},
];
let observedResult = mongoQB.assembleSearchQuery({
joinsToPerform: [],
matchesToPerform: [],
searchResultTransformations: {},
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: false,
pageNumber: 1,
});
expect(observedResult).toEqual(expectedResult);
});
});
describe('Apply Archived Filter Tests', () => {
test('Should throw an error if missing the required archivedParamPath from the implementation parameters', () => {
let error;
try {
mongoQB.assembleSearchQuery({
joinsToPerform: [],
matchesToPerform: [],
searchResultTransformations: {},
implementationParameters: {},
includeArchived: false,
pageNumber: 1,
});
} catch (err) {
error = err;
}
expect(error.message).toContain('Missing required implementation parameter \'archivedParamPath\'');
});
test('Should return input query as is if we are not filtering out archived results', () => {
const expectedResult = [
{
$facet: {
data: [{ $skip: 0 }, {$limit: 10}],
metadata: [
{
$count: 'total',
},
{
$addFields: {
numberOfPages: {$ceil: {$divide:['$total',10]}},
},
},
{
$addFields: {
page: 1,
},
},
],
},
},
];
let observedResult = mongoQB.assembleSearchQuery({
joinsToPerform: [],
matchesToPerform: [],
searchResultTransformations: {},
implementationParameters: {archivedParamPath: 'meta._isArchived'},
includeArchived: true,
pageNumber: 1,
resultsPerPage: 10
});
expect(observedResult).toEqual(expectedResult);
});
});
});

4

package.json
{
"name": "@asymmetrik/fhir-qb-mongo",
"version": "0.10.2",
"version": "0.10.3",
"description": "FHIR query builder for Mongo DB",

@@ -35,3 +35,3 @@ "main": "index.js",

},
"gitHead": "d5334845f7d5f5d2e00c3826016757bbfef9c4f4"
"gitHead": "0f6aeb28427f9a5574a5a6bbe0ead0cdf0ad8af8"
}

@@ -23,3 +23,2 @@ # FHIR-Query-Builder-Mongo

```
These are used by the fhir-qb to build a query that will work in the mongo
aggregation pipeline.
These are used by the fhir-qb to build a query that will work in the mongo aggregation pipeline.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc