@comodinx/query-parser
Version:
@comodinx/query-parser is a module for parser query parameters on ORM object (sequelize).
233 lines (206 loc) • 5.97 kB
JavaScript
const { isObject, isString, reduce } = require("lodash");
//
// constants
//
const defaultSeparator = ",";
const defaultConcatenator = ".";
const defaultRemotePrefix = "r-";
const defaultExtraPrefix = "e-";
const mapTypes = {
fields: "attributes"
};
//
// helpers
//
const getDefaultSeparator = (type, options) => {
return (isObject(options) ? options[`${type}Separator`] : defaultSeparator) || defaultSeparator;
};
const getDefaultConcatenator = (type, options) => {
return (
(isObject(options) ? options[`${type}Concatenator`] : defaultConcatenator) ||
defaultConcatenator
);
};
/**
* Indicate if property is remote property
*
* r-person
* \/ -> This is the remote prefix. Then, return true
*
* person
* \____/ -> This is the normal property. Then, return false
*/
const isRemoteProperty = (property) => property.startsWith(defaultRemotePrefix);
/**
* Normalize remote property if necesary
*
* r-person
* \______/ -> This is the remote prefix. Then, return "person"
*
* person
* \____/ -> This is the normal property. Then, return "person"
*/
const normalizeRemoteProperty = (property) =>
property.replace(defaultRemotePrefix, "");
/**
* Indicate if property is extra property
*
* e-person
* \/ -> This is the extra prefix. Then, return true
*
* person
* \____/ -> This is the normal property. Then, return false
*/
const isExtraProperty = (property) => property.startsWith(defaultExtraPrefix);
/**
* Normalize extra property if necesary
*
* e-person
* \______/ -> This is the extra prefix. Then, return "person"
*
* person
* \____/ -> This is the normal property. Then, return "person"
*/
const normalizeExtraProperty = (property) =>
property.replace(defaultExtraPrefix, "");
/**
* Normalize remote or extra property if necesary
*
* r-person
* \______/ -> This is the remote prefix. Then, return "person"
* e-person
* \______/ -> This is the extra prefix. Then, return "person"
*
* person
* \____/ -> This is the normal property. Then, return "person"
*/
const normalizeRemoteExtraProperty = (property) =>
normalizeExtraProperty(normalizeRemoteProperty(property));
/**
* Save related property in correct location
*
* r-person
* \______/ -> This is the remote prefix. Then, save in opts.remotes
*
* person
* \____/ -> This is the normal property. Then, save in carry
*/
const resolveProperty = (opts, property, carry) => {
// Check if property is remote property
//
// r-person
// \/ -> This is the remote prefix
//
if (isRemoteProperty(property)) {
const rawProperty = normalizeRemoteProperty(property);
opts.remotes = opts.remotes || [];
if (!opts.remotes.includes(rawProperty)) {
opts.remotes.push(rawProperty);
}
}
// Check if property is extra property
//
// e-person
// \/ -> This is the extra prefix
//
else if (isExtraProperty(property)) {
const rawProperty = normalizeExtraProperty(property);
opts.extras = opts.extras || [];
if (!opts.extras.includes(rawProperty)) {
opts.extras.push(rawProperty);
}
}
// Handle simple property
//
// user
// \__/ -> This is the simple property
//
else if (!carry.includes(property)) {
carry.push(property);
}
};
const resolveRelationship = (context, type, property) => {
type = mapTypes[type] || type;
if (!isRemoteProperty(property) && !isExtraProperty(property)) {
context[type] = context[type] || [];
}
resolveProperty(context, property, context[type]);
};
//
// source code
//
module.exports = (query, type, options = {}) => {
if (!query || !query[type]) {
return;
}
const concatenator = getDefaultConcatenator(type, options);
const properties = isString(query[type])
? query[type].split(getDefaultSeparator(type, options))
: query[type];
const opts = {
[type]: []
};
opts[type] = reduce(
properties,
(carry, property) => {
let isFirstLevelProperty = true;
let parentPropertyName;
let parentPropertyOpts;
// Check if property is complex.
//
// user.status
// | -> This is the concatenator
//
if (property.includes(concatenator)) {
property.split(concatenator).forEach((partOfProperty) => {
const rawProperty = normalizeRemoteExtraProperty(partOfProperty);
// Check if is initial part of property.
//
// user.status
// \__/ -> This part
//
if (isFirstLevelProperty) {
isFirstLevelProperty = false;
if (!options.skipFirstLevelProperty) {
resolveProperty(opts, partOfProperty, carry);
}
} else {
// prepare relations options
parentPropertyOpts.relations = parentPropertyOpts.relations || {};
parentPropertyOpts.relations[parentPropertyName] =
parentPropertyOpts.relations[parentPropertyName] || {};
// Resolve no first parts of property.
//
// user.status...
// \_____________ -> This parts
//
resolveRelationship(
parentPropertyOpts.relations[parentPropertyName],
type,
partOfProperty
);
}
// save previous property options
parentPropertyOpts =
(parentPropertyOpts &&
parentPropertyOpts.relations &&
parentPropertyOpts.relations[parentPropertyName || rawProperty]) ||
opts;
// save previous property name
parentPropertyName = normalizeRemoteExtraProperty(partOfProperty);
});
}
// Handle single property
//
// status
// | -> Property without concatenator
//
else {
resolveProperty(opts, property, carry);
}
return carry;
},
opts[type]
);
return opts;
};