@trapi/query
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -1,3 +0,3 @@ | ||
import { QueryBuildOptions, QueryRecord } from "./type"; | ||
export declare function buildQuery<T extends Record<string, any>>(record?: QueryRecord<T>, options?: QueryBuildOptions): string; | ||
import { QueryRecord } from "./type"; | ||
export declare function buildQuery<T extends Record<string, any>>(record?: QueryRecord<T>): string; | ||
//# sourceMappingURL=build.d.ts.map |
@@ -11,2 +11,3 @@ "use strict"; | ||
const fields_1 = require("./fields"); | ||
const filters_1 = require("./filters"); | ||
const includes_1 = require("./includes"); | ||
@@ -16,22 +17,20 @@ const sort_1 = require("./sort"); | ||
const utils_1 = require("./utils"); | ||
function buildQuery(record, options) { | ||
function buildQuery(record) { | ||
if (typeof record === 'undefined' || record === null) | ||
return ''; | ||
options ??= {}; | ||
options.key ??= {}; | ||
let query = {}; | ||
if (typeof record[type_1.QueryRecordKey.FIELDS] !== 'undefined') { | ||
query[options.key[type_1.QueryRecordKey.FIELDS] ?? type_1.QueryRecordKey.FIELDS] = (0, fields_1.buildQueryFields)(record[type_1.QueryRecordKey.FIELDS]); | ||
if (typeof record[type_1.QueryKey.FIELDS] !== 'undefined') { | ||
query[type_1.QueryKey.FIELDS] = (0, fields_1.buildQueryFields)(record[type_1.QueryKey.FIELDS]); | ||
} | ||
if (typeof record[type_1.QueryRecordKey.FILTER] !== 'undefined') { | ||
query[options.key[type_1.QueryRecordKey.FILTER] ?? type_1.QueryRecordKey.FILTER] = (0, utils_1.flattenNestedProperties)(record[type_1.QueryRecordKey.FILTER]); | ||
if (typeof record[type_1.QueryKey.FILTER] !== 'undefined') { | ||
query[type_1.QueryKey.FILTER] = (0, filters_1.buildQueryFilters)(record[type_1.QueryKey.FILTER]); | ||
} | ||
if (typeof record[type_1.QueryRecordKey.INCLUDE] !== 'undefined') { | ||
query[options.key[type_1.QueryRecordKey.INCLUDE] ?? type_1.QueryRecordKey.INCLUDE] = (0, includes_1.buildQueryIncludes)(record[type_1.QueryRecordKey.INCLUDE]); | ||
if (typeof record[type_1.QueryKey.INCLUDE] !== 'undefined') { | ||
query[type_1.QueryKey.INCLUDE] = (0, includes_1.buildQueryIncludes)(record[type_1.QueryKey.INCLUDE]); | ||
} | ||
if (typeof record[type_1.QueryRecordKey.PAGE] !== 'undefined') { | ||
query[options.key[type_1.QueryRecordKey.PAGE] ?? type_1.QueryRecordKey.PAGE] = record[type_1.QueryRecordKey.PAGE]; | ||
if (typeof record[type_1.QueryKey.PAGE] !== 'undefined') { | ||
query[type_1.QueryKey.PAGE] = record[type_1.QueryKey.PAGE]; | ||
} | ||
if (typeof record[type_1.QueryRecordKey.SORT] !== 'undefined') { | ||
query[options.key[type_1.QueryRecordKey.SORT] ?? type_1.QueryRecordKey.SORT] = (0, sort_1.buildQuerySort)(record[type_1.QueryRecordKey.SORT]); | ||
if (typeof record[type_1.QueryKey.SORT] !== 'undefined') { | ||
query[type_1.QueryKey.SORT] = (0, sort_1.buildQuerySort)(record[type_1.QueryKey.SORT]); | ||
} | ||
@@ -38,0 +37,0 @@ return (0, utils_1.buildURLQueryString)(query); |
@@ -1,4 +0,4 @@ | ||
import { FieldsOptions, FieldsTransformed } from "./type"; | ||
import { FieldsOptions, FieldsParsed } from "./type"; | ||
export declare function buildDomainFields(data: Record<string, string[]> | string[], options?: FieldsOptions): Record<string, string[]>; | ||
export declare function parseFields(data: unknown, options: FieldsOptions): FieldsTransformed; | ||
export declare function parseFields(data: unknown, options: FieldsOptions): FieldsParsed; | ||
//# sourceMappingURL=parse.d.ts.map |
@@ -15,6 +15,6 @@ "use strict"; | ||
function buildDomainFields(data, options) { | ||
options = options ?? { queryAlias: type_1.DEFAULT_ALIAS_ID }; | ||
options = options ?? { defaultAlias: type_1.DEFAULT_ALIAS_ID }; | ||
let domainFields = {}; | ||
if (Array.isArray(data)) { | ||
domainFields[options.queryAlias] = data; | ||
domainFields[options.defaultAlias] = data; | ||
} | ||
@@ -36,3 +36,3 @@ else { | ||
options.includes ??= []; | ||
options.queryAlias ??= type_1.DEFAULT_ALIAS_ID; | ||
options.defaultAlias ??= type_1.DEFAULT_ALIAS_ID; | ||
let allowedDomainFields; | ||
@@ -49,44 +49,40 @@ if (options.allowed) { | ||
if (prototype === '[object String]') { | ||
data = { [options.queryAlias]: data }; | ||
data = { [options.defaultAlias]: data }; | ||
} | ||
if (prototype === '[object Array]') { | ||
data = { [options.queryAlias]: data }; | ||
data = { [options.defaultAlias]: data }; | ||
} | ||
const transformed = []; | ||
for (const key in data) { | ||
if (!data.hasOwnProperty(key) || typeof key !== 'string') { | ||
let transformed = []; | ||
for (const alias in data) { | ||
if (!data.hasOwnProperty(alias) || typeof alias !== 'string') { | ||
continue; | ||
} | ||
const value = data[key]; | ||
const valuePrototype = Object.prototype.toString.call(value); | ||
if (valuePrototype !== '[object Array]' && | ||
valuePrototype !== '[object String]') { | ||
const fieldsArr = buildArrayFieldsRepresentation(data[alias]); | ||
if (fieldsArr.length === 0) | ||
continue; | ||
} | ||
let fields = []; | ||
/* istanbul ignore next */ | ||
if (valuePrototype === '[object String]') { | ||
fields = value.split(','); | ||
} | ||
/* istanbul ignore next */ | ||
if (valuePrototype === '[object Array]') { | ||
fields = value | ||
.filter(val => typeof val === 'string'); | ||
} | ||
let fieldsAppend; | ||
for (let i = 0; i < fields.length; i++) { | ||
if (fields[i].substr(0, 1) === '+') { | ||
fieldsAppend = true; | ||
fields[i] = fields[i].substr(1); | ||
for (let i = 0; i < fieldsArr.length; i++) { | ||
let operator; | ||
switch (true) { | ||
case fieldsArr[i].substr(0, 1) === type_1.FieldOperator.INCLUDE: | ||
operator = type_1.FieldOperator.INCLUDE; | ||
break; | ||
case fieldsArr[i].substr(0, 1) === type_1.FieldOperator.EXCLUDE: | ||
operator = type_1.FieldOperator.EXCLUDE; | ||
break; | ||
} | ||
if (operator) | ||
fieldsArr[i] = fieldsArr[i].substr(1); | ||
fields.push({ | ||
key: fieldsArr[i], | ||
...(operator ? { operator } : {}) | ||
}); | ||
} | ||
if (fields.length === 0) | ||
continue; | ||
const allowedDomains = typeof allowedDomainFields !== 'undefined' ? Object.keys(allowedDomainFields) : []; | ||
const targetKey = allowedDomains.length === 1 ? allowedDomains[0] : key; | ||
const targetKey = allowedDomains.length === 1 ? allowedDomains[0] : alias; | ||
// is not default domain && includes are defined? | ||
if (key !== type_1.DEFAULT_ALIAS_ID && | ||
key !== options.queryAlias && | ||
if (alias !== type_1.DEFAULT_ALIAS_ID && | ||
alias !== options.defaultAlias && | ||
typeof options.includes !== 'undefined') { | ||
const includesMatched = options.includes.filter(include => include.alias === key); | ||
const includesMatched = options.includes.filter(include => include.alias === alias); | ||
if (includesMatched.length === 0) { | ||
@@ -97,7 +93,11 @@ continue; | ||
fields = fields | ||
.map(part => { | ||
const fullKey = key + '.' + part; | ||
return options.aliasMapping.hasOwnProperty(fullKey) ? options.aliasMapping[fullKey].split('.').pop() : part; | ||
.map(field => { | ||
const fullKey = alias + '.' + field.key; | ||
return { | ||
...(targetKey && targetKey !== type_1.DEFAULT_ALIAS_ID ? { alias: targetKey } : {}), | ||
...field, | ||
key: options.aliasMapping.hasOwnProperty(fullKey) ? options.aliasMapping[fullKey].split('.').pop() : field.key | ||
}; | ||
}) | ||
.filter(part => { | ||
.filter(field => { | ||
if (typeof allowedDomainFields === 'undefined') { | ||
@@ -107,20 +107,35 @@ return true; | ||
return (0, utils_1.hasOwnProperty)(allowedDomainFields, targetKey) && | ||
allowedDomainFields[targetKey].indexOf(part) !== -1; | ||
allowedDomainFields[targetKey].indexOf(field.key) !== -1; | ||
}); | ||
if (fields.length > 0) { | ||
const item = { | ||
fields: fields | ||
}; | ||
if (targetKey !== type_1.DEFAULT_ALIAS_ID) { | ||
item.alias = targetKey; | ||
} | ||
if (typeof fieldsAppend !== 'undefined') { | ||
item.addFields = fieldsAppend; | ||
} | ||
transformed.push(item); | ||
transformed = [...transformed, ...fields]; | ||
} | ||
} | ||
const keys = Object.keys(transformed); | ||
if (keys.length === 1) { | ||
if (keys[0] === type_1.DEFAULT_ALIAS_ID) { | ||
return transformed[keys[0]]; | ||
} | ||
} | ||
return transformed; | ||
} | ||
exports.parseFields = parseFields; | ||
function buildArrayFieldsRepresentation(data) { | ||
const valuePrototype = Object.prototype.toString.call(data); | ||
if (valuePrototype !== '[object Array]' && | ||
valuePrototype !== '[object String]') { | ||
return []; | ||
} | ||
let fieldsArr = []; | ||
/* istanbul ignore next */ | ||
if (valuePrototype === '[object String]') { | ||
fieldsArr = data.split(','); | ||
} | ||
/* istanbul ignore next */ | ||
if (valuePrototype === '[object Array]') { | ||
fieldsArr = data | ||
.filter(val => typeof val === 'string'); | ||
} | ||
return fieldsArr; | ||
} | ||
//# sourceMappingURL=parse.js.map |
@@ -1,2 +0,2 @@ | ||
import { IncludesTransformed } from "../includes"; | ||
import { IncludesParsed } from "../includes"; | ||
import { Flatten, KeyWithOptionalPrefix, OnlyObject, ToOneAndMany } from "../utils"; | ||
@@ -7,19 +7,22 @@ export declare const DEFAULT_ALIAS_ID: string; | ||
allowed?: Record<string, string[]> | string[]; | ||
includes?: IncludesTransformed; | ||
queryAlias?: string; | ||
includes?: IncludesParsed; | ||
defaultAlias?: string; | ||
}; | ||
export declare type AliasFields = { | ||
addFields?: boolean; | ||
export declare type FieldParsed = { | ||
key: string; | ||
alias?: string; | ||
fields: string[]; | ||
operator?: FieldOperator; | ||
}; | ||
export declare type FieldsTransformed = AliasFields[]; | ||
declare type FieldOperatorAdd = '+'; | ||
declare type FieldWithOperator<T extends Record<string, any>> = KeyWithOptionalPrefix<keyof T, FieldOperatorAdd> | KeyWithOptionalPrefix<keyof T, FieldOperatorAdd>[]; | ||
export declare type FieldsParsed = FieldParsed[]; | ||
export declare enum FieldOperator { | ||
INCLUDE = "+", | ||
EXCLUDE = "-" | ||
} | ||
declare type FieldWithOperator<T extends Record<string, any>> = KeyWithOptionalPrefix<keyof T, FieldOperator> | KeyWithOptionalPrefix<keyof T, FieldOperator>[]; | ||
export declare type FieldRecord<T> = { | ||
[K in keyof T]?: T[K] extends OnlyObject<T[K]> ? (FieldRecord<Flatten<T[K]>> | FieldWithOperator<Flatten<T[K]>>) : never; | ||
} | { | ||
[key: string]: ToOneAndMany<KeyWithOptionalPrefix<keyof T, FieldOperatorAdd>[]>; | ||
[key: string]: ToOneAndMany<KeyWithOptionalPrefix<keyof T, FieldOperator>[]>; | ||
} | FieldWithOperator<T>; | ||
export {}; | ||
//# sourceMappingURL=type.d.ts.map |
@@ -9,4 +9,10 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DEFAULT_ALIAS_ID = void 0; | ||
exports.FieldOperator = exports.DEFAULT_ALIAS_ID = void 0; | ||
exports.DEFAULT_ALIAS_ID = '__DEFAULT__'; | ||
// ----------------------------------------------------------- | ||
var FieldOperator; | ||
(function (FieldOperator) { | ||
FieldOperator["INCLUDE"] = "+"; | ||
FieldOperator["EXCLUDE"] = "-"; | ||
})(FieldOperator = exports.FieldOperator || (exports.FieldOperator = {})); | ||
//# sourceMappingURL=type.js.map |
@@ -0,3 +1,4 @@ | ||
export * from './build'; | ||
export * from './parse'; | ||
export * from './type'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -19,4 +19,5 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./build"), exports); | ||
__exportStar(require("./parse"), exports); | ||
__exportStar(require("./type"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -1,3 +0,3 @@ | ||
import { FiltersOptions, FiltersTransformed } from "./type"; | ||
export declare function parseFilters(data: unknown, options?: FiltersOptions): FiltersTransformed; | ||
import { FiltersOptions, FiltersParsed } from "./type"; | ||
export declare function parseFilters(data: unknown, options?: FiltersOptions): FiltersParsed; | ||
//# sourceMappingURL=parse.d.ts.map |
@@ -11,2 +11,3 @@ "use strict"; | ||
const utils_1 = require("../utils"); | ||
const type_1 = require("./type"); | ||
// -------------------------------------------------- | ||
@@ -66,12 +67,23 @@ // -------------------------------------------------- | ||
const fieldDetails = (0, utils_1.getFieldDetails)(key); | ||
if (!(0, utils_1.isFieldAllowedByIncludes)(fieldDetails, options.includes, { queryAlias: options.queryAlias })) { | ||
if (!(0, utils_1.isFieldAllowedByIncludes)(fieldDetails, options.includes, { defaultAlias: options.defaultAlias })) { | ||
continue; | ||
} | ||
const keyWithQueryAlias = (0, utils_1.buildFieldWithQueryAlias)(fieldDetails, options.queryAlias); | ||
const keyWithAlias = (0, utils_1.buildFieldWithAlias)(fieldDetails, options.defaultAlias); | ||
if (typeof options.allowed !== 'undefined' && | ||
options.allowed.indexOf(key) === -1 && | ||
options.allowed.indexOf(keyWithQueryAlias) === -1) { | ||
options.allowed.indexOf(keyWithAlias) === -1) { | ||
continue; | ||
} | ||
temp[keyWithQueryAlias] = value; | ||
const alias = typeof fieldDetails.path === 'undefined' && | ||
typeof fieldDetails.alias === 'undefined' ? | ||
(options.defaultAlias ? | ||
options.defaultAlias : | ||
undefined) | ||
: | ||
fieldDetails.alias; | ||
temp[keyWithAlias] = { | ||
key: fieldDetails.name, | ||
...(alias ? { alias } : {}), | ||
value: value | ||
}; | ||
} | ||
@@ -85,57 +97,30 @@ const items = []; | ||
} | ||
let value = temp[key]; | ||
/* istanbul ignore next */ | ||
const paramKey = typeof options.queryBindingKeyFn === 'function' ? | ||
options.queryBindingKeyFn(key) : | ||
'filter_' + key.replace(/\W/g, '_'); | ||
const queryString = [ | ||
key | ||
]; | ||
let isInOperator = false; | ||
if (typeof value === 'string') { | ||
const isNegationPrefix = value.charAt(0) === '!'; | ||
if (isNegationPrefix) | ||
value = value.slice(1); | ||
const isLikeOperator = value.charAt(0) === '~'; | ||
if (isLikeOperator) | ||
value = value.slice(1); | ||
isInOperator = value.includes(','); | ||
if (isInOperator || isLikeOperator) { | ||
if (isNegationPrefix) { | ||
queryString.push('NOT'); | ||
} | ||
if (isLikeOperator) { | ||
queryString.push('LIKE'); | ||
} | ||
else { | ||
queryString.push('IN'); | ||
} | ||
const filter = { | ||
...(temp[key].alias ? { alias: temp[key].alias } : {}), | ||
key: temp[key].key, | ||
value: temp[key].value | ||
}; | ||
if (typeof filter.value === 'string') { | ||
const negationOperator = filter.value.charAt(0) === type_1.FilterOperator.NEGATION; | ||
if (negationOperator) { | ||
filter.operator ??= {}; | ||
filter.operator[type_1.FilterOperatorLabel.NEGATION] = negationOperator; | ||
filter.value = filter.value.slice(1); | ||
} | ||
else { | ||
if (isNegationPrefix) { | ||
queryString.push("!="); | ||
} | ||
else { | ||
queryString.push("="); | ||
} | ||
const likeOperator = filter.value.charAt(0) === type_1.FilterOperator.LIKE; | ||
if (likeOperator) { | ||
filter.operator ??= {}; | ||
filter.operator[type_1.FilterOperatorLabel.LIKE] = likeOperator; | ||
filter.value = filter.value.slice(1); | ||
} | ||
if (isLikeOperator) { | ||
value += '%'; | ||
const inOperator = filter.value.includes(type_1.FilterOperator.IN); | ||
if (inOperator) { | ||
filter.operator ??= {}; | ||
filter.operator[type_1.FilterOperatorLabel.IN] = true; | ||
} | ||
if (isInOperator) { | ||
queryString.push('(:...' + paramKey + ')'); | ||
if (typeof filter.operator !== 'undefined') { | ||
filter.value = filter.operator[type_1.FilterOperatorLabel.IN] ? filter.value.split(',') : filter.value; | ||
} | ||
else { | ||
queryString.push(':' + paramKey); | ||
} | ||
} | ||
else { | ||
isInOperator = false; | ||
queryString.push("="); | ||
queryString.push(':' + paramKey); | ||
} | ||
items.push({ | ||
statement: queryString.join(" "), | ||
binding: { [paramKey]: isInOperator ? value.split(',') : value } | ||
}); | ||
items.push(filter); | ||
} | ||
@@ -142,0 +127,0 @@ return items; |
@@ -1,2 +0,2 @@ | ||
import { IncludesTransformed } from "../includes"; | ||
import { IncludesParsed } from "../includes"; | ||
import { Flatten, OnlyObject, OnlyScalar } from "../utils"; | ||
@@ -6,24 +6,36 @@ export declare type FiltersOptions = { | ||
allowed?: string[]; | ||
includes?: IncludesTransformed; | ||
queryAlias?: string; | ||
includes?: IncludesParsed; | ||
defaultAlias?: string; | ||
queryBindingKeyFn?: (key: string) => string; | ||
}; | ||
export declare type FilterTransformed = { | ||
statement: string; | ||
binding: Record<string, any>; | ||
export declare type FilterParsed = { | ||
key: string; | ||
alias?: string; | ||
operator?: { | ||
[K in FilterOperatorLabel]?: boolean; | ||
}; | ||
value: FilterValue<string | number | boolean | null>; | ||
}; | ||
export declare type FiltersTransformed = FilterTransformed[]; | ||
declare type OperatorConfig<V, O> = { | ||
operator: O; | ||
export declare type FiltersParsed = FilterParsed[]; | ||
export declare type OperatorConfig<V, O> = { | ||
operator: O | O[]; | ||
value: V | V[]; | ||
}; | ||
declare type FilterNegationOperator = '!'; | ||
declare type FilterLikeOperator = '~'; | ||
declare type FilterOperator = FilterNegationOperator | FilterLikeOperator | `${FilterNegationOperator}${FilterLikeOperator}`; | ||
declare type FilterValue<V> = V extends string | number | boolean ? (V | V[] | FilterValueOperator<V> | Array<FilterValueOperator<V>>) : never; | ||
export declare enum FilterOperatorLabel { | ||
NEGATION = "negation", | ||
LIKE = "like", | ||
IN = "in" | ||
} | ||
export declare enum FilterOperator { | ||
NEGATION = "!", | ||
LIKE = "~", | ||
IN = "," | ||
} | ||
declare type FilterValue<V> = V extends string | number | boolean ? (V | V[]) : never; | ||
declare type FilterValueWithOperator<V> = V extends string | number | boolean ? (FilterValue<V> | FilterValueOperator<V> | Array<FilterValueOperator<V>>) : never; | ||
declare type FilterValueOperator<V extends string | number | boolean> = `!${V}` | `!~${V}` | `~${V}`; | ||
export declare type FilterRecord<T> = { | ||
[K in keyof T]?: T[K] extends OnlyScalar<T[K]> ? T[K] | FilterValue<T[K]> | OperatorConfig<T[K], FilterOperator> : T[K] extends OnlyObject<T[K]> ? FilterRecord<Flatten<T[K]>> : never; | ||
[K in keyof T]?: T[K] extends OnlyScalar<T[K]> ? T[K] | FilterValueWithOperator<T[K]> | OperatorConfig<T[K], FilterOperator> : T[K] extends OnlyObject<T[K]> ? FilterRecord<Flatten<T[K]>> : never; | ||
}; | ||
export {}; | ||
//# sourceMappingURL=type.d.ts.map |
@@ -9,2 +9,15 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FilterOperator = exports.FilterOperatorLabel = void 0; | ||
var FilterOperatorLabel; | ||
(function (FilterOperatorLabel) { | ||
FilterOperatorLabel["NEGATION"] = "negation"; | ||
FilterOperatorLabel["LIKE"] = "like"; | ||
FilterOperatorLabel["IN"] = "in"; | ||
})(FilterOperatorLabel = exports.FilterOperatorLabel || (exports.FilterOperatorLabel = {})); | ||
var FilterOperator; | ||
(function (FilterOperator) { | ||
FilterOperator["NEGATION"] = "!"; | ||
FilterOperator["LIKE"] = "~"; | ||
FilterOperator["IN"] = ","; | ||
})(FilterOperator = exports.FilterOperator || (exports.FilterOperator = {})); | ||
//# sourceMappingURL=type.js.map |
@@ -1,3 +0,3 @@ | ||
import { IncludesOptions, IncludesTransformed } from "./type"; | ||
export declare function parseIncludes(data: unknown, options?: IncludesOptions): IncludesTransformed; | ||
import { IncludesOptions, IncludesParsed } from "./type"; | ||
export declare function parseIncludes(data: unknown, options?: IncludesOptions): IncludesParsed; | ||
//# sourceMappingURL=parse.d.ts.map |
@@ -105,3 +105,3 @@ "use strict"; | ||
return { | ||
property: relation.includes('.') ? relation.split('.').slice(-2).join('.') : (options.queryAlias ? options.queryAlias + '.' + relation : relation), | ||
property: relation.includes('.') ? relation.split('.').slice(-2).join('.') : (options.defaultAlias ? options.defaultAlias + '.' + relation : relation), | ||
alias: relation.split('.').pop() | ||
@@ -108,0 +108,0 @@ }; |
import { Flatten, OnlyObject } from "../utils"; | ||
export declare type IncludeTransformed = { | ||
export declare type IncludeParsed = { | ||
property: string; | ||
alias: string; | ||
}; | ||
export declare type IncludesTransformed = IncludeTransformed[]; | ||
export declare type IncludesParsed = IncludeParsed[]; | ||
export declare type IncludesOptions = { | ||
@@ -11,3 +11,3 @@ aliasMapping?: Record<string, string>; | ||
includeParents?: boolean | string[] | string; | ||
queryAlias?: string; | ||
defaultAlias?: string; | ||
}; | ||
@@ -14,0 +14,0 @@ export declare type IncludeRecord<T extends Record<string, any>> = { |
@@ -6,4 +6,5 @@ export * from './build'; | ||
export * from './pagination'; | ||
export * from './parse'; | ||
export * from './sort'; | ||
export * from './type'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -24,4 +24,5 @@ "use strict"; | ||
__exportStar(require("./pagination"), exports); | ||
__exportStar(require("./parse"), exports); | ||
__exportStar(require("./sort"), exports); | ||
__exportStar(require("./type"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -1,2 +0,2 @@ | ||
import { PaginationOptions, PaginationTransformed } from "./type"; | ||
import { PaginationOptions, PaginationParsed } from "./type"; | ||
/** | ||
@@ -8,3 +8,3 @@ * Transform pagination data to an appreciate data format. | ||
*/ | ||
export declare function parsePagination(data: unknown, options?: PaginationOptions): PaginationTransformed; | ||
export declare function parsePagination(data: unknown, options?: PaginationOptions): PaginationParsed; | ||
//# sourceMappingURL=parse.d.ts.map |
export declare type PaginationOptions = { | ||
maxLimit?: number; | ||
}; | ||
export declare type PaginationTransformed = { | ||
export declare type PaginationParsed = { | ||
limit?: number; | ||
@@ -6,0 +6,0 @@ offset?: number; |
@@ -1,2 +0,2 @@ | ||
import { SortOptions, SortTransformed } from "./type"; | ||
import { SortOptions, SortParsed } from "./type"; | ||
/** | ||
@@ -7,3 +7,3 @@ * Transform sort data to appreciate data format. | ||
*/ | ||
export declare function parseSort(data: unknown, options?: SortOptions): SortTransformed; | ||
export declare function parseSort(data: unknown, options?: SortOptions): SortParsed; | ||
//# sourceMappingURL=parse.d.ts.map |
@@ -11,2 +11,3 @@ "use strict"; | ||
const utils_1 = require("../utils"); | ||
const type_1 = require("./type"); | ||
// -------------------------------------------------- | ||
@@ -30,6 +31,5 @@ // -------------------------------------------------- | ||
options.allowed.length === 0) { | ||
return {}; | ||
return []; | ||
} | ||
options.aliasMapping = options.aliasMapping ? (0, utils_1.buildObjectFromStringArray)(options.aliasMapping) : {}; | ||
const items = {}; | ||
const prototype = Object.prototype.toString.call(data); | ||
@@ -40,3 +40,3 @@ /* istanbul ignore next */ | ||
prototype !== '[object Object]') { | ||
return items; | ||
return []; | ||
} | ||
@@ -62,6 +62,7 @@ let parts = []; | ||
} | ||
const items = {}; | ||
for (let i = 0; i < parts.length; i++) { | ||
let direction = 'ASC'; | ||
let direction = type_1.SortDirection.ASC; | ||
if (parts[i].substr(0, 1) === '-') { | ||
direction = 'DESC'; | ||
direction = type_1.SortDirection.DESC; | ||
parts[i] = parts[i].substr(1); | ||
@@ -74,17 +75,28 @@ } | ||
const fieldDetails = (0, utils_1.getFieldDetails)(key); | ||
if (!(0, utils_1.isFieldAllowedByIncludes)(fieldDetails, options.includes, { queryAlias: options.queryAlias })) { | ||
if (!(0, utils_1.isFieldAllowedByIncludes)(fieldDetails, options.includes, { defaultAlias: options.defaultAlias })) { | ||
continue; | ||
} | ||
const keyWithQueryAlias = (0, utils_1.buildFieldWithQueryAlias)(fieldDetails, options.queryAlias); | ||
const keyWithAlias = (0, utils_1.buildFieldWithAlias)(fieldDetails, options.defaultAlias); | ||
if (typeof options.allowed !== 'undefined' && | ||
!isMultiDimensionalArray(options.allowed) && | ||
options.allowed.indexOf(key) === -1 && | ||
options.allowed.indexOf(keyWithQueryAlias) === -1) { | ||
options.allowed.indexOf(keyWithAlias) === -1) { | ||
continue; | ||
} | ||
items[keyWithQueryAlias] = direction; | ||
const alias = typeof fieldDetails.path === 'undefined' && | ||
typeof fieldDetails.alias === 'undefined' ? | ||
(options.defaultAlias ? | ||
options.defaultAlias : | ||
undefined) | ||
: | ||
fieldDetails.alias; | ||
items[keyWithAlias] = { | ||
key: fieldDetails.name, | ||
...(alias ? { alias } : {}), | ||
value: direction | ||
}; | ||
} | ||
if (isMultiDimensionalArray(options.allowed)) { | ||
outerLoop: for (let i = 0; i < options.allowed.length; i++) { | ||
const temp = {}; | ||
const temp = []; | ||
for (let j = 0; j < options.allowed[i].length; j++) { | ||
@@ -94,3 +106,4 @@ const keyWithAlias = options.allowed[i][j]; | ||
if (items.hasOwnProperty(key) || items.hasOwnProperty(keyWithAlias)) { | ||
temp[keyWithAlias] = items.hasOwnProperty(key) ? items[key] : items[keyWithAlias]; | ||
const item = items.hasOwnProperty(key) ? items[key] : items[keyWithAlias]; | ||
temp.push(item); | ||
} | ||
@@ -104,7 +117,7 @@ else { | ||
// if we get no match, the sort data is invalid. | ||
return {}; | ||
return []; | ||
} | ||
return items; | ||
return Object.values(items); | ||
} | ||
exports.parseSort = parseSort; | ||
//# sourceMappingURL=parse.js.map |
@@ -1,2 +0,2 @@ | ||
import { IncludesTransformed } from "../includes"; | ||
import { IncludesParsed } from "../includes"; | ||
import { Flatten, KeyWithOptionalPrefix, OnlyObject, OnlyScalar } from "../utils"; | ||
@@ -6,7 +6,15 @@ export declare type SortOptions = { | ||
allowed?: string[] | string[][]; | ||
includes?: IncludesTransformed; | ||
queryAlias?: string; | ||
includes?: IncludesParsed; | ||
defaultAlias?: string; | ||
}; | ||
export declare type SortDirection = 'ASC' | 'DESC'; | ||
export declare type SortTransformed = Record<string, SortDirection>; | ||
export declare enum SortDirection { | ||
ASC = "ASC", | ||
DESC = "DESC" | ||
} | ||
export declare type SortElementParsed = { | ||
alias?: string; | ||
key: string; | ||
value: SortDirection; | ||
}; | ||
export declare type SortParsed = SortElementParsed[]; | ||
declare type SortOperatorDesc = '-'; | ||
@@ -13,0 +21,0 @@ declare type SortWithOperator<T extends Record<string, any>> = KeyWithOptionalPrefix<keyof T, SortOperatorDesc> | KeyWithOptionalPrefix<keyof T, SortOperatorDesc>[]; |
@@ -9,2 +9,8 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.SortDirection = void 0; | ||
var SortDirection; | ||
(function (SortDirection) { | ||
SortDirection["ASC"] = "ASC"; | ||
SortDirection["DESC"] = "DESC"; | ||
})(SortDirection = exports.SortDirection || (exports.SortDirection = {})); | ||
//# sourceMappingURL=type.js.map |
@@ -1,13 +0,7 @@ | ||
import { FieldRecord } from "./fields"; | ||
import { FilterRecord } from "./filters"; | ||
import { IncludeRecord } from "./includes"; | ||
import { PaginationRecord } from "./pagination"; | ||
import { SortRecord } from "./sort"; | ||
export declare type BuildQueryKeyOption = { | ||
[Key in QueryRecordKey]?: string; | ||
}; | ||
export declare type QueryBuildOptions = { | ||
key?: BuildQueryKeyOption; | ||
}; | ||
export declare enum QueryRecordKey { | ||
import { FieldRecord, FieldsOptions, FieldsParsed } from "./fields"; | ||
import { FilterRecord, FiltersOptions, FiltersParsed } from "./filters"; | ||
import { IncludeRecord, IncludesOptions, IncludesParsed } from "./includes"; | ||
import { PaginationOptions, PaginationRecord, PaginationParsed } from "./pagination"; | ||
import { SortOptions, SortRecord, SortParsed } from "./sort"; | ||
export declare enum QueryKey { | ||
FILTER = "filter", | ||
@@ -19,9 +13,17 @@ FIELDS = "fields", | ||
} | ||
export declare type QueryRecord<A extends Record<string, any>> = { | ||
filter?: FilterRecord<A>; | ||
fields?: FieldRecord<A>; | ||
sort?: SortRecord<A>; | ||
include?: IncludeRecord<A>; | ||
page?: PaginationRecord<A>; | ||
export declare type QueryRecord<R extends Record<string, any>> = { | ||
[K in QueryKey]?: QueryRecordType<K, R>; | ||
}; | ||
export declare type QueryRecordType<T extends QueryKey, R extends Record<string, any>> = T extends QueryKey.FIELDS ? FieldRecord<R> : T extends QueryKey.FILTER ? FilterRecord<R> : T extends QueryKey.INCLUDE ? IncludeRecord<R> : T extends QueryKey.PAGE ? PaginationRecord<R> : T extends QueryKey.SORT ? SortRecord<R> : never; | ||
export declare type QueryParseInput = { | ||
[K in QueryKey]?: any; | ||
}; | ||
export declare type QueryParseOptions = { | ||
[K in QueryKey]?: QueryKeyOption<K> | boolean; | ||
}; | ||
export declare type QueryParseOutput = { | ||
[K in QueryKey]?: QueryRecordTransformed<K>; | ||
}; | ||
export declare type QueryKeyOption<T extends QueryKey> = T extends QueryKey.FIELDS ? FieldsOptions : T extends QueryKey.FILTER ? FiltersOptions : T extends QueryKey.INCLUDE ? IncludesOptions : T extends QueryKey.PAGE ? PaginationOptions : T extends QueryKey.SORT ? SortOptions : never; | ||
export declare type QueryRecordTransformed<T extends QueryKey> = T extends QueryKey.FIELDS ? FieldsParsed : T extends QueryKey.FILTER ? FiltersParsed : T extends QueryKey.INCLUDE ? IncludesParsed : T extends QueryKey.PAGE ? PaginationParsed : T extends QueryKey.SORT ? SortParsed : never; | ||
//# sourceMappingURL=type.d.ts.map |
@@ -9,12 +9,12 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.QueryRecordKey = void 0; | ||
exports.QueryKey = void 0; | ||
// ----------------------------------------------------------- | ||
var QueryRecordKey; | ||
(function (QueryRecordKey) { | ||
QueryRecordKey["FILTER"] = "filter"; | ||
QueryRecordKey["FIELDS"] = "fields"; | ||
QueryRecordKey["SORT"] = "sort"; | ||
QueryRecordKey["INCLUDE"] = "include"; | ||
QueryRecordKey["PAGE"] = "page"; | ||
})(QueryRecordKey = exports.QueryRecordKey || (exports.QueryRecordKey = {})); | ||
var QueryKey; | ||
(function (QueryKey) { | ||
QueryKey["FILTER"] = "filter"; | ||
QueryKey["FIELDS"] = "fields"; | ||
QueryKey["SORT"] = "sort"; | ||
QueryKey["INCLUDE"] = "include"; | ||
QueryKey["PAGE"] = "page"; | ||
})(QueryKey = exports.QueryKey || (exports.QueryKey = {})); | ||
//# sourceMappingURL=type.js.map |
@@ -1,7 +0,7 @@ | ||
import { IncludesTransformed } from "../includes"; | ||
import { IncludesParsed } from "../includes"; | ||
import { FieldDetails } from "./field"; | ||
export declare function isFieldAllowedByIncludes(field: string | FieldDetails, includes?: IncludesTransformed, options?: { | ||
queryAlias?: string; | ||
export declare function isFieldAllowedByIncludes(field: string | FieldDetails, includes?: IncludesParsed, options?: { | ||
defaultAlias?: string; | ||
}): boolean; | ||
export declare function buildFieldWithQueryAlias(field: string | FieldDetails, queryAlias?: string): string; | ||
export declare function buildFieldWithAlias(field: string | FieldDetails, defaultAlias?: string): string; | ||
//# sourceMappingURL=include.d.ts.map |
@@ -9,3 +9,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.buildFieldWithQueryAlias = exports.isFieldAllowedByIncludes = void 0; | ||
exports.buildFieldWithAlias = exports.isFieldAllowedByIncludes = void 0; | ||
const field_1 = require("./field"); | ||
@@ -24,4 +24,4 @@ function isFieldAllowedByIncludes(field, includes, options) { | ||
// check if field is associated to the default domain. | ||
if (details.path === options.queryAlias || | ||
details.alias === options.queryAlias) { | ||
if (details.path === options.defaultAlias || | ||
details.alias === options.defaultAlias) { | ||
return true; | ||
@@ -32,3 +32,3 @@ } | ||
exports.isFieldAllowedByIncludes = isFieldAllowedByIncludes; | ||
function buildFieldWithQueryAlias(field, queryAlias) { | ||
function buildFieldWithAlias(field, defaultAlias) { | ||
const details = typeof field === 'string' ? (0, field_1.getFieldDetails)(field) : field; | ||
@@ -38,7 +38,7 @@ if (typeof details.path === 'undefined' && | ||
// try to use query alias | ||
return queryAlias ? queryAlias + '.' + details.name : details.name; | ||
return defaultAlias ? defaultAlias + '.' + details.name : details.name; | ||
} | ||
return details.alias + '.' + details.name; | ||
} | ||
exports.buildFieldWithQueryAlias = buildFieldWithQueryAlias; | ||
exports.buildFieldWithAlias = buildFieldWithAlias; | ||
//# sourceMappingURL=include.js.map |
{ | ||
"name": "@trapi/query", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "An tiny library which provides utility types/functions for request and response query handling.", | ||
@@ -53,3 +53,3 @@ "main": "./dist/index.js", | ||
}, | ||
"gitHead": "fbabb84192fbfbf8744f0e17d109ecb23992ef9c" | ||
"gitHead": "91fda58914c6ec3337fada8afa4b3f0cbe331a1e" | ||
} |
538
README.MD
@@ -11,7 +11,15 @@ # @trapi/query 🌈 | ||
It extends the specification format between request- & response-handling for querying and fetching data according the following query parameters: | ||
- `fields` Return only specific fields or extend the default selection. | ||
- `filter`: Filter the data set, according specific criteria. | ||
- `include` Include related resources of the primary data. | ||
- `page` Limit the number of resources returned of the whole set. | ||
- `fields` Return only specific fields or extend the default selection. | ||
- `sort` Sort the resources according one or more keys in asc/desc direction. | ||
--- | ||
**Important NOTE** | ||
The examples in the [Parsing](#parsing-) section, are not available with current release of the `typeorm-extension@0.3.0` library. | ||
--- | ||
**Table of Contents** | ||
@@ -23,4 +31,21 @@ | ||
- [Parsing](#parsing-) | ||
- Types | ||
- Options | ||
- [Functions](#functions) | ||
- [buildQuery](#buildquery) | ||
- [parseQuery](#parsequery) | ||
- [Types](#types) | ||
- [Key](#key) | ||
- [QueryKey](#querykey) | ||
- [QueryKeyOption](#querykeyoption) | ||
- [Record](#record) | ||
- [QueryRecord](#queryrecord) | ||
- [Parse](#parse) | ||
- [QueryParseOption](#queryparseoptions) | ||
- [QueryParseOutput](#queryparseoutput) | ||
- [Parsed](#parsed) | ||
- [FieldsParsed](#fieldsparsed) | ||
- [FiltersParsed](#filtersparsed) | ||
- [IncludeParsed](#includesparsed) | ||
- [PaginationParsed](#paginationparsed) | ||
- [SortParsed](#sortparsed) | ||
- [Options](#options) | ||
- [FieldsOptions](#fieldsoptions) | ||
@@ -31,8 +56,3 @@ - [FiltersOptions](#filtersoptions) | ||
- [SortOptions](#sortoptions) | ||
- Transformed | ||
- [FieldsTransformed](#fieldstransformed) | ||
- [FiltersTransformed](#filterstransformed) | ||
- [IncludeTransformed](#includestransformed) | ||
- [PaginationTransformed](#paginationtransformed) | ||
- [SortTransformed](#sorttransformed) | ||
## Installation | ||
@@ -169,9 +189,10 @@ | ||
``` | ||
In this example `typeorm` is used as the object-relational mapping (ORM) and `typeorm-extension` is used | ||
to apply the transformed request query parameters on the db query. | ||
In this example [typeorm](https://www.npmjs.com/package/typeorm) is used as the object-relational mapping (ORM) and [typeorm-extension](https://www.npmjs.com/package/typeorm-extension) is used | ||
to apply the parsed request query parameters on the db query. | ||
When you use express or another library, you can use the API utils accordingly to the | ||
When you use [express](https://www.npmjs.com/package/express) or another library, you can use the API utils accordingly to the | ||
following code snippet: | ||
#### Parsing - Extended | ||
#### Parse - In Detail | ||
```typescript | ||
@@ -186,11 +207,8 @@ import {getRepository} from "typeorm"; | ||
parsePagination, | ||
parseSort | ||
} from "typeorm-extension"; | ||
parseSort, | ||
QueryKey | ||
} from "@trapi/query"; | ||
import { | ||
applyFieldsTransformed, | ||
applyFiltersTransformed, | ||
applyIncludesTransformed, | ||
applyPaginationTransformed, | ||
applySortTransformed | ||
applyParsed | ||
} from "typeorm-extension"; | ||
@@ -226,29 +244,39 @@ | ||
const includes = applyIncludesTransformed(query, parseIncludes(include)); | ||
const includesParsed = parseIncludes(include, { | ||
allowed: 'profile' | ||
}); | ||
applySortTransformed(query, parseSort(sort, { | ||
queryAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'], | ||
const fieldsParsed = parseFields(fields, { | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id', 'profile.avatar'], | ||
// profile.id can only be used as sorting key, if the relation 'profile' is included. | ||
includes: includes | ||
})); | ||
includes: includesParsed | ||
}); | ||
applyFieldsTransformed(query, parseFields(fields, { | ||
queryAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id', 'profile.avatar'], | ||
// porfile fields can only be included, if the relation 'profile' is included. | ||
includes: includes | ||
})); | ||
const filterParsed = parseFilters(filter, { | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'], // profile.id can only be used as sorting key, if the relation 'profile' is included. | ||
includes: includesParsed | ||
}); | ||
// only allow filtering users by id & name | ||
applyFiltersTransformed(query, parseFilters(filter, { | ||
queryAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'], | ||
// porfile.id can only be used as a filter, if the relation 'profile' is included. | ||
includes: includes | ||
})); | ||
const pageParsed = parsePagination(page, { | ||
maxLimit: 20 | ||
}); | ||
// only allow to select 20 items at maximum. | ||
const pagination = applyPaginationTransformed(query, parsePagination(page, {maxLimit: 20})); | ||
const sortParsed = parseSort(sort, { | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'], // profile.id can only be used as sorting key, if the relation 'profile' is included. | ||
includes: includesParsed | ||
}); | ||
const parsed = applyParsed(query, { | ||
[QueryKey.FIELDS]: fieldsParsed, | ||
// only allow filtering users by id & name | ||
[QueryKey.FILTER]: filterParsed, | ||
[QueryKey.INCLUDE]: includesParsed, | ||
// only allow to select 20 items at maximum. | ||
[QueryKey.PAGE]: pageParsed, | ||
[QueryKey.SORT]: sortParsed | ||
}); | ||
// ----------------------------------------------------- | ||
@@ -263,3 +291,3 @@ | ||
total, | ||
...pagination | ||
...parsed[QueryKey.PAGE] | ||
} | ||
@@ -271,6 +299,86 @@ } | ||
This can even be much easier, because `typeorm-extension` uses `@trapi/query` under the hood ⚡. | ||
```typescript | ||
import {getRepository} from "typeorm"; | ||
import {Request, Response} from 'express'; | ||
#### Transform - Simple | ||
import { | ||
parseQuery, | ||
QueryKey, | ||
QueryParseOutput | ||
} from "@trapi/query"; | ||
import { | ||
applyParsed | ||
} from "typeorm-extension"; | ||
/** | ||
* Get many users. | ||
* | ||
* Request example | ||
* - url: /users?page[limit]=10&page[offset]=0&include=profile&filter[id]=1&fields[user]=id,name | ||
* | ||
* Return Example: | ||
* { | ||
* data: [ | ||
* {id: 1, name: 'tada5hi', profile: {avatar: 'avatar.jpg', cover: 'cover.jpg'}} | ||
* ], | ||
* meta: { | ||
* total: 1, | ||
* limit: 20, | ||
* offset: 0 | ||
* } | ||
* } | ||
* @param req | ||
* @param res | ||
*/ | ||
export async function getUsers(req: Request, res: Response) { | ||
const {fields, filter, include, page, sort} = req.query; | ||
const output: QueryParseOutput = parseQuery({ | ||
[QueryKey.FIELDS]: { | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id', 'profile.avatar'] | ||
}, | ||
[QueryKey.FILTER]: { | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'] | ||
}, | ||
[QueryKey.INCLUDE]: { | ||
allowed: ['profile'] | ||
}, | ||
[QueryKey.PAGE]: { | ||
maxLimit: 20 | ||
}, | ||
[QueryKey.SORT]: { | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'] | ||
} | ||
}); | ||
const repository = getRepository(User); | ||
const query = repository.createQueryBuilder('user'); | ||
// ----------------------------------------------------- | ||
const parsed = applyParsed(query, output); | ||
// ----------------------------------------------------- | ||
const [entities, total] = await query.getManyAndCount(); | ||
return res.json({ | ||
data: { | ||
data: entities, | ||
meta: { | ||
total, | ||
...parsed[QueryKey.PAGE] | ||
} | ||
} | ||
}); | ||
} | ||
``` | ||
#### Parse - Third Party Library | ||
It can even be much easier to parse the query key values, because `typeorm-extension` uses `@trapi/query` under the hood ⚡. | ||
This is much shorter than the previous example and has less direct dependencies 😁. | ||
@@ -318,3 +426,3 @@ | ||
const includes = applyIncludes(query, include, { | ||
queryAlias: 'user', | ||
defaultAlias: 'user', | ||
allowed: ['profile'] | ||
@@ -324,3 +432,3 @@ }); | ||
applySort(query, sort, { | ||
queryAlias: 'user', | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'], | ||
@@ -332,3 +440,3 @@ // profile.id can only be used as sorting key, if the relation 'profile' is included. | ||
applyFields(query, fields, { | ||
queryAlias: 'user', | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id', 'profile.avatar'], | ||
@@ -341,3 +449,3 @@ // porfile fields can only be included, if the relation 'profile' is included. | ||
applyFilters(query, filter, { | ||
queryAlias: 'user', | ||
defaultAlias: 'user', | ||
allowed: ['id', 'name', 'profile.id'], | ||
@@ -367,90 +475,259 @@ // porfile.id can only be used as a filter, if the relation 'profile' is included. | ||
### Options | ||
## Functions | ||
#### FieldsOptions | ||
## buildQuery | ||
▸ `function` **buildQuery**<`T`>(`record: QueryRecord<T>`, `options?: QueryBuildOptions`): `string` | ||
Build a query string from a provided [QueryRecord](#queryrecord). | ||
#### Example | ||
**`Simple`** | ||
```typescript | ||
type FieldsOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: Record<string, string[]> | string[], | ||
includes?: IncludesTransformed, | ||
queryAlias?: string | ||
}; | ||
// Only drop database if it does exists. | ||
import {buildQuery, QueryKey} from "@trapi/query"; | ||
type User = { | ||
id: number, | ||
name: string, | ||
age?: number | ||
} | ||
const query: string = buildQuery<T>({ | ||
[QueryKey.FIELDS]: ['+age'], | ||
[QueryKey.FILTER]: { | ||
name: '~pe' | ||
} | ||
}); | ||
console.log(query); | ||
// ?fields=+age&filter[name]=~pe | ||
``` | ||
#### FiltersOptions | ||
#### Type parameters | ||
| Name | Description | | ||
| :------ | :------ | | ||
| `T` | A type, interface, or class which represent the data structure. | ||
#### Parameters | ||
| Name | Type | Description | | ||
| :------ | :------ | :------ | | ||
| `record` | `QueryRecord`<`T`> | Query specification [more](#record). | | ||
| `options` | `QueryBuildOptions` | Options for building fields, filter, include, ... query [more](#fieldsoptions) | ||
#### Returns | ||
`string` | ||
The function return a string, which can be parsed with the [parseQuery](#parsequery) function. | ||
I.e. `/users?page[limit]=10&page[offset]=0&include=profile&filter[id]=1&fields[user]=id,name` | ||
## parseQuery | ||
▸ `function` **parseQuery**<`T`>(`input: QueryParseInput`, `options?: QueryParseOptions`): `QueryParseOutput` | ||
Parse a query string to an efficient data structure ⚡. The output will | ||
be an object with each possible value of the [QueryKey](#querykey) enum as property key and the | ||
parsed data as value. | ||
#### Example | ||
**`Simple`** | ||
```typescript | ||
export type FiltersOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: string[], | ||
includes?: IncludesTransformed, | ||
queryAlias?: string, | ||
queryBindingKeyFn?: (key: string) => string | ||
}; | ||
// Only drop database if it does exists. | ||
import { | ||
FieldOperator, | ||
FilterOperator, | ||
parseQuery, | ||
QueryParseOutput | ||
} from "@trapi/query"; | ||
const output: QueryParseOutput = parseQuery({ | ||
[QueryKey.FIELDS]: ['+age'], | ||
[QueryKey.FILTER]: { | ||
name: '~pe' | ||
} | ||
}); | ||
console.log(output); | ||
//{ | ||
// fields: [ | ||
// {key: 'age', operator: FieldOperator.INCLUDE} | ||
// ], | ||
// filter: [ | ||
// {key: 'name', value: 'pe', operator: FilterOperator.LIKE} | ||
// ] | ||
//} | ||
``` | ||
#### IncludesOptions | ||
#### Type parameters | ||
| Name | Description | | ||
| :------ | :------ | | ||
#### Parameters | ||
| Name | Type | Description | | ||
| :------ | :------ | :------ | | ||
| `input` | `QueryParseInput` | Query input data passed i.e. via URL [more](#queryparseinput). | | ||
| `options` | `QueryParseOptions` | Options for parsing fields, filter, include, ... [more](#queryparseoptions) | ||
#### Returns | ||
[QueryParseOutput](#queryparseoutput) | ||
The function return an object. | ||
## Types | ||
### Key | ||
#### QueryKey | ||
```typescript | ||
type IncludesOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: string[], | ||
includeParents?: boolean | string[] | string | ||
queryAlias?: string, | ||
}; | ||
export enum QueryKey { | ||
FILTER = 'filter', | ||
FIELDS = 'fields', | ||
SORT = 'sort', | ||
INCLUDE = 'include', | ||
PAGE = 'page' | ||
} | ||
``` | ||
#### PaginationOptions | ||
#### QueryKeyOption | ||
```typescript | ||
type PaginationOptions = { | ||
maxLimit?: number | ||
}; | ||
export type QueryKeyOption<T extends QueryKey> = T extends QueryKey.FIELDS ? | ||
FieldsOptions : | ||
T extends QueryKey.FILTER ? | ||
FiltersOptions : | ||
T extends QueryKey.INCLUDE ? | ||
IncludesOptions : | ||
T extends QueryKey.PAGE ? | ||
PaginationOptions : | ||
T extends QueryKey.SORT ? | ||
SortOptions : | ||
never; | ||
``` | ||
#### SortOptions | ||
### Record | ||
#### QueryRecord | ||
```typescript | ||
type SortOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: string[] | string[][], | ||
includes?: IncludesTransformed, | ||
queryAlias?: string | ||
}; | ||
export type QueryRecord<R extends Record<string, any>> = { | ||
[K in QueryKey]?: QueryRecordType<K,R> | ||
} | ||
export type QueryRecordType< | ||
T extends QueryKey, | ||
R extends Record<string, any> | ||
> = T extends QueryKey.FIELDS ? | ||
FilterRecord<R> : | ||
T extends QueryKey.FILTER ? | ||
FieldRecord<R> : | ||
T extends QueryKey.INCLUDE ? | ||
IncludeRecord<R> : | ||
T extends QueryKey.PAGE ? | ||
PaginationRecord<R> : | ||
T extends QueryKey.SORT ? | ||
SortRecord<R> : | ||
never; | ||
``` | ||
### Transformed | ||
#### QueryRecordParsed | ||
```typescript | ||
export type QueryRecordParsed<T extends QueryKey> = | ||
T extends QueryKey.FIELDS ? | ||
FieldsParsed : | ||
T extends QueryKey.FILTER ? | ||
FiltersParsed : | ||
T extends QueryKey.INCLUDE ? | ||
IncludesParsed : | ||
T extends QueryKey.PAGE ? | ||
PaginationParsed : | ||
T extends QueryKey.SORT ? | ||
SortParsed : | ||
never; | ||
``` | ||
#### FieldsTransformed | ||
### Parse | ||
#### QueryParseOptions | ||
```typescript | ||
export type QueryParseOptions = { | ||
/** | ||
* On default all query keys are enabled. | ||
*/ | ||
[K in QueryKey]?: QueryKeyOption<K> | boolean | ||
} | ||
``` | ||
#### QueryParseInput | ||
```typescript | ||
export type AliasFields = { | ||
addFields?: boolean, | ||
export type QueryParseOutput = { | ||
[K in QueryKey]?: any | ||
} | ||
``` | ||
#### QueryParseOutput | ||
```typescript | ||
export type QueryParseOutput = { | ||
[K in QueryKey]?: QueryRecordParsed<K> | ||
} | ||
``` | ||
### Parsed | ||
#### FieldsParsed | ||
```typescript | ||
export enum FieldOperator { | ||
INCLUDE = '+', | ||
EXCLUDE = '-' | ||
} | ||
export type FieldParsed = { | ||
key: string, | ||
alias?: string, | ||
fields: string[] | ||
operator?: FieldOperator | ||
}; | ||
export type FieldsTransformed = AliasFields[]; | ||
export type FieldsParsed = FieldParsed[]; | ||
``` | ||
#### FiltersTransformed | ||
#### FiltersParsed | ||
```typescript | ||
export type FilterTransformed = { | ||
statement: string, | ||
binding: Record<string, any> | ||
export enum FilterOperatorLabel { | ||
NEGATION = 'negation', | ||
LIKE = 'like', | ||
IN = 'in' | ||
} | ||
export type FilterParsed = { | ||
key: string, | ||
alias?: string, | ||
operator?: { | ||
[K in FilterOperatorLabel]?: boolean | ||
}, | ||
value: FilterValue<string | number | boolean | null> | ||
}; | ||
export type FiltersTransformed = FilterTransformed[]; | ||
export type FiltersParsed = FilterParsed[]; | ||
``` | ||
#### IncludesTransformed | ||
#### IncludesParsed | ||
```typescript | ||
export type IncludeTransformed = { | ||
export type IncludeParsed = { | ||
property: string, | ||
alias: string | ||
}; | ||
export type IncludesTransformed = IncludeTransformed[]; | ||
export type IncludesParsed = IncludeParsed[]; | ||
``` | ||
#### PaginationTransformed | ||
#### PaginationParsed | ||
```typescript | ||
export type PaginationTransformed = { | ||
export type PaginationParsed = { | ||
limit?: number, | ||
@@ -461,7 +738,66 @@ offset?: number | ||
#### SortTransformed | ||
#### SortParsed | ||
```typescript | ||
export type SortDirection = 'ASC' | 'DESC'; | ||
export type SortTransformed = Record<string, SortDirection>; | ||
export enum SortDirection { | ||
ASC = 'ASC', | ||
DESC = 'DESC' | ||
} | ||
export type SortElementParsed = { | ||
alias?: string, | ||
key: string, | ||
value: SortDirection | ||
} | ||
export type SortParsed = SortElementParsed[]; | ||
``` | ||
### Options | ||
#### FieldsOptions | ||
```typescript | ||
export type FieldsOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: Record<string, string[]> | string[], | ||
includes?: IncludesParsed, | ||
defaultAlias?: string | ||
}; | ||
``` | ||
#### FiltersOptions | ||
```typescript | ||
export type FiltersOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: string[], | ||
includes?: IncludesParsed, | ||
defaultAlias?: string, | ||
queryBindingKeyFn?: (key: string) => string | ||
}; | ||
``` | ||
#### IncludesOptions | ||
```typescript | ||
export type IncludesOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: string[], | ||
includeParents?: boolean | string[] | string | ||
defaultAlias?: string, | ||
}; | ||
``` | ||
#### PaginationOptions | ||
```typescript | ||
export type PaginationOptions = { | ||
maxLimit?: number | ||
}; | ||
``` | ||
#### SortOptions | ||
```typescript | ||
export type SortOptions = { | ||
aliasMapping?: Record<string, string>, | ||
allowed?: string[] | string[][], | ||
includes?: IncludesParsed, | ||
defaultAlias?: string | ||
}; | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
123441
126
1381
790