UNPKG

rapiq

Version:

A tiny library which provides utility types/functions for request and response query handling.

1 lines 143 kB
{"version":3,"file":"index.mjs","sources":["../src/constants.ts","../src/utils/object.ts","../src/utils/array.ts","../src/utils/mapping.ts","../src/utils/key.ts","../src/utils/merge.ts","../src/utils/relation.ts","../src/utils/url.ts","../src/parameter/utils/parse/allowed-option.ts","../src/parameter/fields/container.ts","../src/parameter/fields/constants.ts","../src/errors/code.ts","../src/errors/base.ts","../src/errors/build.ts","../src/errors/parse.ts","../src/parameter/fields/errors/build.ts","../src/parameter/fields/errors/parse.ts","../src/parameter/fields/utils/domain.ts","../src/parameter/fields/utils/input.ts","../src/parameter/fields/utils/name.ts","../src/parameter/fields/parse.ts","../src/parameter/filters/constants.ts","../src/parameter/filters/utils/value.ts","../src/parameter/filters/utils/operator.ts","../src/parameter/filters/container.ts","../src/parameter/filters/build.ts","../src/parameter/filters/errors/build.ts","../src/parameter/filters/errors/parse.ts","../src/parameter/filters/parse.ts","../src/parameter/pagination/errors/build.ts","../src/parameter/pagination/errors/parse.ts","../src/parameter/pagination/parse.ts","../src/parameter/relations/errors/build.ts","../src/parameter/relations/errors/parse.ts","../src/parameter/relations/utils/parents.ts","../src/parameter/relations/utils/path.ts","../src/parameter/relations/parse.ts","../src/parameter/sort/type.ts","../src/parameter/sort/build.ts","../src/parameter/sort/container.ts","../src/parameter/sort/errors/build.ts","../src/parameter/sort/errors/parse.ts","../src/parameter/sort/utils.ts","../src/parameter/sort/parse.ts","../src/build/builder.ts","../src/build/module.ts","../src/parse/utils.ts","../src/parse/parser.ts","../src/parse/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n// -----------------------------------------------------------\n\nexport enum Parameter {\n FILTERS = 'filters',\n FIELDS = 'fields',\n PAGINATION = 'pagination',\n RELATIONS = 'relations',\n SORT = 'sort',\n}\n\n// -----------------------------------------------------------\n\nexport enum URLParameter {\n FILTERS = 'filter',\n FIELDS = 'fields',\n PAGINATION = 'page',\n RELATIONS = 'include',\n SORT = 'sort',\n}\n\n// -----------------------------------------------------------\n\nexport const DEFAULT_ID = '__DEFAULT__';\n","/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nexport function isObject(item: unknown) : item is Record<string, any> {\n return (\n !!item &&\n typeof item === 'object' &&\n !Array.isArray(item)\n );\n}\n\nexport function extendObject(target: Record<string, any>, source: Record<string, any>) {\n const keys = Object.keys(source);\n for (let i = 0; i < keys.length; i++) {\n target[keys[i]] = source[keys[i]];\n }\n\n return target;\n}\n\nexport function hasOwnProperty<\n X extends Record<string, any>,\n Y extends PropertyKey>(obj: X, prop: Y): obj is X & Record<Y, unknown> {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n}\n\ntype Options = {\n transformer?: (\n input: unknown,\n output: Record<string, any>,\n key: string\n ) => boolean | undefined,\n validator?: (\n input: unknown,\n key: string\n ) => boolean | undefined,\n prefixParts?: string[]\n};\n\nexport function toFlatObject(\n data: Record<string, any>,\n options: Options = {},\n): Record<string, any> {\n const prefixParts = options.prefixParts || [];\n let output: Record<string, string> = {};\n\n const keys = Object.keys(data);\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n\n if (isObject(data[key])) {\n output = {\n ...output,\n ...toFlatObject(data[key], {\n ...options,\n prefixParts: [...prefixParts, key],\n }),\n };\n\n continue;\n }\n\n const destinationKey = [...prefixParts, key].join('.');\n\n if (options.transformer) {\n const result = options.transformer(data[key], output, destinationKey);\n if (result) {\n continue;\n }\n }\n\n if (options.validator) {\n const result = options.validator(data[key], destinationKey);\n if (!result) {\n continue;\n }\n }\n\n output[destinationKey] = data[key];\n }\n\n return output;\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { DEFAULT_ID } from '../constants';\nimport { isObject } from './object';\n\nexport function buildKeyPath(key: string, prefix?: string) {\n if (typeof prefix === 'string') {\n return `${prefix}.${key}`;\n }\n\n return key;\n}\n\ntype Options = {\n transformer?: (\n input: unknown,\n output: string[],\n prefix?: string\n ) => boolean | undefined\n};\n\nexport function toKeyPathArray(\n input: unknown,\n options?: Options,\n prefix?: string,\n): string[] {\n options = options || {};\n\n const output: string[] = [];\n\n if (options.transformer) {\n const result = options.transformer(input, output, prefix);\n if (result) {\n return output;\n }\n }\n\n if (Array.isArray(input)) {\n for (let i = 0; i < input.length; i++) {\n if (options.transformer) {\n const result = options.transformer(input[i], output, prefix);\n if (result) {\n return output;\n }\n }\n\n if (Array.isArray(input[i])) {\n for (let j = 0; j < input[i].length; j++) {\n const key = buildKeyPath(input[i][j], prefix);\n output.push(key);\n }\n\n continue;\n }\n\n if (typeof input[i] === 'string') {\n output.push(buildKeyPath(input[i], prefix));\n\n continue;\n }\n\n if (isObject(input[i])) {\n const keys = Object.keys(input[i]);\n for (let j = 0; j < keys.length; j++) {\n const value = buildKeyPath(keys[j] as string, prefix);\n const data = toKeyPathArray(input[i][keys[j]], options, value);\n if (data.length === 0) {\n output.push(value);\n } else {\n output.push(...data);\n }\n }\n }\n }\n\n return output;\n }\n\n if (isObject(input)) {\n const keys = Object.keys(input);\n for (let i = 0; i < keys.length; i++) {\n const value = buildKeyPath(keys[i], prefix);\n const data = toKeyPathArray(input[keys[i]], options, value);\n if (data.length === 0) {\n output.push(value);\n } else {\n output.push(...data);\n }\n }\n\n return output;\n }\n\n if (typeof input === 'string') {\n const value = buildKeyPath(input, prefix);\n output.push(value);\n\n return output;\n }\n\n return output;\n}\n\nexport function groupArrayByKeyPath(input: string[]): Record<string, string[]> {\n const pathItems: Record<string, string[]> = {};\n\n for (let i = 0; i < input.length; i++) {\n const parts = input[i].split('.');\n\n let key: string;\n let name: string;\n if (parts.length === 1) {\n key = DEFAULT_ID;\n name = input[i];\n } else {\n name = parts.pop() as string;\n key = parts.join('.');\n }\n\n if (!Object.prototype.hasOwnProperty.call(pathItems, key)) {\n pathItems[key] = [];\n }\n\n pathItems[key].push(name);\n }\n\n return pathItems;\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { hasOwnProperty } from './object';\n\nexport function applyMapping(\n name: string,\n map?: Record<string, string>,\n onlyKey?: boolean,\n) {\n if (typeof map === 'undefined') {\n return name;\n }\n\n const keys = Object.keys(map);\n if (keys.length === 0) {\n return name;\n }\n\n let parts = name.split('.');\n\n const output = [];\n let run = true;\n while (run) {\n const value = parts.shift();\n if (typeof value === 'undefined') {\n run = false;\n break;\n }\n\n if (hasOwnProperty(map, value)) {\n output.push(map[value]);\n } else {\n let found = false;\n\n const rest : string[] = [];\n const copy = [...parts];\n while (copy.length > 0) {\n const key = [value, ...copy].join('.');\n if (hasOwnProperty(map, key)) {\n output.push(map[key]);\n found = true;\n break;\n } else {\n const last = copy.pop();\n if (last) {\n rest.unshift(last);\n }\n }\n }\n\n if (found) {\n parts = rest;\n } else {\n output.push(value);\n }\n }\n }\n\n if (onlyKey) {\n return output.pop() || name;\n }\n\n if (output.length === 0) {\n return name;\n }\n\n return output.join('.');\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { KeyDetails } from './type';\n\nexport function parseKey(\n field: string,\n) : KeyDetails {\n const parts : string[] = field.split('.');\n\n const name = parts.pop() as string;\n\n return {\n name,\n path: parts.length > 0 ? parts.join('.') : undefined,\n };\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { createMerger } from 'smob';\n\nexport const merge = createMerger({\n clone: true,\n inPlace: false,\n array: true,\n arrayDistinct: true,\n});\n","/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isObject } from 'smob';\nimport type { RelationsParseOutput } from '../parameter';\nimport type { KeyDetails } from './type';\n\nexport function isPathAllowedByRelations(\n path?: string,\n includes?: RelationsParseOutput,\n) : boolean {\n if (typeof path === 'undefined' || typeof includes === 'undefined') {\n return true;\n }\n\n return includes.some(\n (include) => include.key === path,\n );\n}\n\nexport function buildKeyWithPath(input: KeyDetails) : string;\nexport function buildKeyWithPath(key: string, path: string): string;\nexport function buildKeyWithPath(\n name: string | KeyDetails,\n path?: string,\n) : string {\n let details : KeyDetails;\n if (isObject(name)) {\n details = name;\n } else {\n details = {\n name,\n path,\n };\n }\n\n return details.path || path ?\n `${details.path || path}.${details.name}` :\n details.name;\n}\n","/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isObject } from 'smob';\n\ntype Options = {\n withoutQuestionMark?: boolean,\n prefixParts?: string[]\n};\n\nexport function serializeAsURI(data: Record<string, any>, options: Options = {}) : string {\n // Loop through the data object\n const keys = Object.keys(data);\n if (keys.length === 0) {\n return '';\n }\n\n const prefixParts = options.prefixParts || [];\n\n // Create a query array to hold the key/value pairs\n const query: string[] = [];\n\n for (let i = 0; i < keys.length; i++) {\n let value = data[keys[i]];\n\n if (isObject(value)) {\n query.push(...serializeAsURI(value, {\n ...options,\n prefixParts: [...prefixParts, keys[i]],\n }));\n\n continue;\n }\n\n if (Array.isArray(value)) {\n value = value\n .map((el) => `${el}`)\n .filter(Boolean)\n .join(',');\n }\n\n if (value) {\n const destinationKey = [...prefixParts, keys[i]]\n .reduce((acc, curr) => `${acc}[${curr}]`, '');\n\n // Encode each key and value, concatenate them into a string, and push them to the array\n query.push(`${encodeURIComponent(destinationKey)}=${encodeURIComponent(value)}`);\n }\n }\n\n return query.join('&');\n}\n\nexport function buildURLQueryString(data?: string | Record<string, any>, options: Options = {}) {\n if (typeof data === 'undefined' || data === null) return '';\n\n // If the data is already a string, return it as-is\n if (typeof data === 'string') return data;\n\n const output = serializeAsURI(data);\n if (output.length === 0) {\n return '';\n }\n\n // Join each item in the array with a `&` and return the resulting string\n return (options.withoutQuestionMark ? '' : '?') + output;\n}\n","/*\n * Copyright (c) 2022-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { NestedKeys, NestedResourceKeys, ObjectLiteral } from '../../../types';\nimport { toKeyPathArray } from '../../../utils';\nimport type { ParseAllowedOption } from '../../type';\n\nexport function flattenParseAllowedOption<T>(\n input?: ParseAllowedOption<T>,\n) : string[] {\n if (typeof input === 'undefined') {\n return [];\n }\n\n return toKeyPathArray(input);\n}\n\nexport function isPathCoveredByParseAllowedOption<T extends ObjectLiteral>(\n input: ParseAllowedOption<T> |\n NestedKeys<T>[] |\n NestedResourceKeys<T>[],\n path: string | string[],\n) : boolean {\n const paths = Array.isArray(path) ? path : [path];\n\n const items = toKeyPathArray(input);\n for (let i = 0; i < items.length; i++) {\n if (paths.indexOf(items[i]) !== -1) {\n return true;\n }\n }\n\n return false;\n}\n","/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { ObjectLiteral } from '../../types';\nimport { groupArrayByKeyPath, merge, toFlatObject } from '../../utils';\nimport { flattenParseAllowedOption } from '../utils';\nimport type { FieldsParseOptions } from './type';\n\nexport class FieldsOptionsContainer<T extends ObjectLiteral = ObjectLiteral> {\n public options : FieldsParseOptions<T>;\n\n public default : Record<string, string[]>;\n\n public defaultIsUndefined : boolean;\n\n public allowed : Record<string, string[]>;\n\n public allowedIsUndefined : boolean;\n\n public items : Record<string, string[]>;\n\n public keys : string[];\n\n public reverseMapping : Record<string, string>;\n\n constructor(input: FieldsParseOptions<T> = {}) {\n this.options = input;\n\n this.allowed = {};\n this.allowedIsUndefined = true;\n\n this.default = {};\n this.defaultIsUndefined = true;\n\n this.items = {};\n this.keys = [];\n\n this.reverseMapping = {};\n\n this.initDefault();\n this.initAllowed();\n this.initItems();\n\n this.initReverseMapping();\n }\n\n protected initDefault() {\n if (typeof this.options.default === 'undefined') {\n this.default = {};\n this.defaultIsUndefined = true;\n return;\n }\n\n this.default = groupArrayByKeyPath(\n flattenParseAllowedOption(this.options.default),\n );\n this.defaultIsUndefined = false;\n }\n\n protected initAllowed() {\n if (typeof this.options.allowed === 'undefined') {\n if (typeof this.options.default !== 'undefined') {\n const items = toFlatObject(this.options.default, {\n validator(input) {\n if (!Array.isArray(input)) {\n return false;\n }\n\n return !input.some((el) => typeof el !== 'string');\n },\n });\n\n if (items.length > 0) {\n this.allowed = items;\n this.allowedIsUndefined = false;\n return;\n }\n }\n\n this.allowed = {};\n this.allowedIsUndefined = true;\n return;\n }\n\n this.allowed = groupArrayByKeyPath(\n flattenParseAllowedOption(this.options.allowed),\n );\n this.allowedIsUndefined = false;\n }\n\n protected initItems() {\n this.items = merge(this.default || {}, this.allowed || {});\n this.keys = Object.keys(this.items);\n }\n\n protected initReverseMapping() {\n if (typeof this.options.mapping === 'undefined') {\n return;\n }\n\n this.reverseMapping = this.buildReverseRecord(this.options.mapping);\n }\n\n private buildReverseRecord(\n record: Record<string, string>,\n ) : Record<string, string> {\n const keys = Object.keys(record);\n const output : Record<string, string> = {};\n\n for (let i = 0; i < keys.length; i++) {\n output[record[keys[i]]] = keys[i];\n }\n\n return output;\n }\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nexport enum FieldOperator {\n INCLUDE = '+',\n EXCLUDE = '-',\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nexport enum ErrorCode {\n NONE = 'none',\n\n INPUT_INVALID = 'inputInvalid',\n\n KEY_INVALID = 'keyInvalid',\n\n KEY_PATH_INVALID = 'keyPathInvalid',\n\n KEY_NOT_ALLOWED = 'keyNotAllowed',\n\n KEY_VALUE_INVALID = 'keyValueInvalid',\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { ErrorCode } from './code';\nimport type { BaseErrorOptions } from './types';\n\nexport class BaseError extends Error {\n public readonly code : `${ErrorCode}`;\n\n constructor(input: BaseErrorOptions | string) {\n if (typeof input === 'string') {\n super(input);\n\n this.code = ErrorCode.NONE;\n } else {\n super(input.message);\n\n this.code = input.code || ErrorCode.NONE;\n }\n }\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isObject } from '../utils';\nimport { BaseError } from './base';\nimport type { BaseErrorOptions } from './types';\n\nexport class BuildError extends BaseError {\n constructor(message?: string | BaseErrorOptions) {\n if (isObject(message)) {\n message.message = 'A building error has occurred.';\n }\n\n super(message || 'A building error has occurred.');\n }\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isObject } from '../utils';\nimport { BaseError } from './base';\nimport { ErrorCode } from './code';\nimport type { BaseErrorOptions } from './types';\n\nexport class ParseError extends BaseError {\n constructor(message?: string | BaseErrorOptions) {\n if (isObject(message)) {\n message.message = message.message || 'A parsing error has occurred.';\n }\n\n super(message || 'A parsing error has occurred.');\n }\n\n static inputInvalid() {\n return new this({\n message: 'The shape of the input is not valid.',\n code: ErrorCode.INPUT_INVALID,\n });\n }\n\n static keyNotAllowed(name: string) {\n return new this({\n message: `The key ${name} is not covered by allowed/default options.`,\n code: ErrorCode.KEY_NOT_ALLOWED,\n });\n }\n\n static keyInvalid(key: string) {\n return new this({\n message: `The key ${key} is invalid.`,\n code: ErrorCode.KEY_INVALID,\n });\n }\n\n static keyPathInvalid(key: string) {\n return new this({\n message: `The key path ${key} is invalid.`,\n code: ErrorCode.KEY_PATH_INVALID,\n });\n }\n\n static keyValueInvalid(key: string) {\n return new this({\n message: `The value of the key ${key} is invalid.`,\n code: ErrorCode.KEY_VALUE_INVALID,\n });\n }\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuildError } from '../../../errors';\n\nexport class FieldsBuildError extends BuildError {\n\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { ParseError } from '../../../errors';\n\nexport class FieldsParseError extends ParseError {\n\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { DEFAULT_ID } from '../../../constants';\n\nexport function buildFieldDomainRecords(\n data?: Record<string, string[]> | string[],\n): Record<string, string[]> {\n if (typeof data === 'undefined') {\n return {};\n }\n\n let domainFields: Record<string, string[]> = {};\n\n if (Array.isArray(data)) {\n domainFields[DEFAULT_ID] = data;\n } else {\n domainFields = data;\n }\n\n return domainFields;\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nexport function parseFieldsInput(input: unknown): string[] {\n let output: string[] = [];\n\n if (typeof input === 'string') {\n output = input.split(',');\n } else if (Array.isArray(input)) {\n for (let i = 0; i < input.length; i++) {\n if (typeof input[i] === 'string') {\n output.push(input[i]);\n }\n }\n }\n\n return output;\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nexport function isValidFieldName(input: string) : boolean {\n return /^[a-zA-Z_][a-zA-Z0-9_]*$/gu.test(input);\n}\n","/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { distinctArray, isObject } from 'smob';\nimport { DEFAULT_ID } from '../../constants';\nimport type { ObjectLiteral } from '../../types';\nimport {\n applyMapping, hasOwnProperty, isPathAllowedByRelations,\n} from '../../utils';\nimport { FieldOperator } from './constants';\nimport { FieldsOptionsContainer } from './container';\nimport { FieldsParseError } from './errors';\nimport type { FieldsInputTransformed, FieldsParseOptions, FieldsParseOutput } from './type';\nimport { isValidFieldName, parseFieldsInput } from './utils';\n\nexport function parseQueryFields<T extends ObjectLiteral = ObjectLiteral>(\n input: unknown,\n options?: FieldsParseOptions<T> | FieldsOptionsContainer<T>,\n) : FieldsParseOutput {\n let container : FieldsOptionsContainer<T>;\n if (options instanceof FieldsOptionsContainer) {\n container = options;\n } else {\n container = new FieldsOptionsContainer<T>(options);\n }\n\n // If it is an empty array nothing is allowed\n if (\n (!container.allowedIsUndefined || !container.defaultIsUndefined) &&\n container.keys.length === 0\n ) {\n return [];\n }\n\n let data : Record<string, any> = {\n [DEFAULT_ID]: [],\n };\n\n if (isObject(input)) {\n data = input;\n } else if (typeof input === 'string' || Array.isArray(input)) {\n data = { [DEFAULT_ID]: input };\n } else if (container.options.throwOnFailure) {\n throw FieldsParseError.inputInvalid();\n }\n\n let { keys } = container;\n\n if (\n keys.length > 0 &&\n hasOwnProperty(data, DEFAULT_ID)\n ) {\n data = {\n [keys[0]]: data[DEFAULT_ID],\n };\n } else {\n keys = distinctArray([...keys, ...Object.keys(data)]);\n }\n\n const output : FieldsParseOutput = [];\n\n for (let i = 0; i < keys.length; i++) {\n const path = keys[i];\n\n if (\n path !== DEFAULT_ID &&\n !isPathAllowedByRelations(path, container.options.relations)\n ) {\n if (container.options.throwOnFailure) {\n throw FieldsParseError.keyPathInvalid(path);\n }\n\n continue;\n }\n\n let fields : string[] = [];\n\n if (hasOwnProperty(data, path)) {\n fields = parseFieldsInput(data[path]);\n } else if (\n hasOwnProperty(container.reverseMapping, path) &&\n hasOwnProperty(data, container.reverseMapping[path])\n ) {\n fields = parseFieldsInput(data[container.reverseMapping[path]]);\n }\n\n const transformed : FieldsInputTransformed = {\n default: [],\n included: [],\n excluded: [],\n };\n\n if (fields.length > 0) {\n for (let j = 0; j < fields.length; j++) {\n let operator: FieldOperator | undefined;\n\n const character = fields[j].substring(0, 1);\n\n if (character === FieldOperator.INCLUDE) {\n operator = FieldOperator.INCLUDE;\n } else if (character === FieldOperator.EXCLUDE) {\n operator = FieldOperator.EXCLUDE;\n }\n\n if (operator) {\n fields[j] = fields[j].substring(1);\n }\n\n fields[j] = applyMapping(fields[j], container.options.mapping, true);\n\n let isValid : boolean;\n if (hasOwnProperty(container.items, path)) {\n isValid = container.items[path].indexOf(fields[j]) !== -1;\n } else {\n isValid = isValidFieldName(fields[j]);\n }\n\n if (!isValid) {\n if (container.options.throwOnFailure) {\n throw FieldsParseError.keyNotAllowed(fields[j]);\n }\n\n continue;\n }\n\n if (operator === FieldOperator.INCLUDE) {\n transformed.included.push(fields[j]);\n } else if (operator === FieldOperator.EXCLUDE) {\n transformed.excluded.push(fields[j]);\n } else {\n transformed.default.push(fields[j]);\n }\n }\n }\n\n if (\n transformed.default.length === 0 &&\n hasOwnProperty(container.default, path)\n ) {\n transformed.default = container.default[path];\n }\n\n if (\n transformed.included.length === 0 &&\n transformed.default.length === 0 &&\n hasOwnProperty(container.allowed, path)\n ) {\n transformed.default = container.allowed[path];\n }\n\n transformed.default = Array.from(new Set([\n ...transformed.default,\n ...transformed.included,\n ]));\n\n for (let j = 0; j < transformed.excluded.length; j++) {\n const index = transformed.default.indexOf(transformed.excluded[j]);\n if (index !== -1) {\n transformed.default.splice(index, 1);\n }\n }\n\n if (transformed.default.length > 0) {\n for (let j = 0; j < transformed.default.length; j++) {\n let destPath : string | undefined;\n if (path !== DEFAULT_ID) {\n destPath = path;\n } else if (container.options.defaultPath) {\n destPath = container.options.defaultPath;\n }\n\n output.push({\n key: transformed.default[j],\n ...(destPath ? { path: destPath } : {}),\n });\n }\n }\n }\n\n return output;\n}\n","/*\n * Copyright (c) 2022-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nexport enum FilterComparisonOperator {\n EQUAL = '$eq',\n NOT_EQUAL = '$ne',\n LIKE = '$l',\n NOT_LIKE = '$nl',\n LESS_THAN_EQUAL = '$lte',\n LESS_THAN = '$lt',\n GREATER_THAN_EQUAL = '$gte',\n GREATER_THAN = '$gt',\n IN = '$in',\n NOT_IN = '$nin',\n}\n\nexport enum FilterInputOperatorValue {\n NEGATION = '!',\n LIKE = '~',\n LESS_THAN_EQUAL = '<=',\n LESS_THAN = '<',\n MORE_THAN_EQUAL = '>=',\n MORE_THAN = '>',\n IN = ',',\n}\n","/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { FilterValueSimple } from '../type';\n\nexport function transformFilterValue(input: FilterValueSimple) : FilterValueSimple {\n if (typeof input === 'string') {\n input = input.trim();\n const lower = input.toLowerCase();\n\n if (lower === 'true') {\n return true;\n }\n\n if (lower === 'false') {\n return false;\n }\n\n if (lower === 'null') {\n return null;\n }\n\n if (input.length === 0) {\n return input;\n }\n\n const num = Number(input);\n if (!Number.isNaN(num)) {\n return num;\n }\n\n const parts = input.split(',');\n if (parts.length > 1) {\n return transformFilterValue(parts);\n }\n }\n\n if (Array.isArray(input)) {\n for (let i = 0; i < input.length; i++) {\n input[i] = transformFilterValue(input[i]) as string | number;\n }\n\n return (input as unknown[])\n .filter((n) => n === 0 || n === null || !!n) as FilterValueSimple;\n }\n\n if (typeof input === 'undefined' || input === null) {\n return null;\n }\n\n return input;\n}\n","/*\n * Copyright (c) 2022-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { FilterComparisonOperator, FilterInputOperatorValue } from '../constants';\nimport type { FilterValueSimple } from '../type';\n\nfunction matchOperator(key: string, value: FilterValueSimple, position: 'start' | 'end' | 'global') : FilterValueSimple | undefined {\n if (typeof value === 'string') {\n switch (position) {\n case 'start': {\n if (value.substring(0, key.length) === key) {\n return value.substring(key.length);\n }\n break;\n }\n case 'end': {\n if (value.substring(0 - key.length) === key) {\n return value.substring(0, value.length - key.length - 1);\n }\n break;\n }\n }\n\n return undefined;\n }\n\n if (Array.isArray(value)) {\n let match = false;\n for (let i = 0; i < value.length; i++) {\n const output = matchOperator(key, value[i], position);\n if (typeof output !== 'undefined') {\n match = true;\n value[i] = output as string | number;\n }\n }\n\n if (match) {\n return value;\n }\n }\n\n return undefined;\n}\n\nexport function parseFilterValue(input: FilterValueSimple) : {\n operator: `${FilterComparisonOperator}`,\n value: FilterValueSimple\n} {\n if (\n typeof input === 'string' &&\n input.includes(FilterInputOperatorValue.IN)\n ) {\n input = input.split(FilterInputOperatorValue.IN);\n }\n\n let negation = false;\n\n let value = matchOperator(FilterInputOperatorValue.NEGATION, input, 'start');\n if (typeof value !== 'undefined') {\n negation = true;\n input = value;\n }\n\n if (Array.isArray(input)) {\n return {\n value: input,\n operator: negation ?\n FilterComparisonOperator.NOT_IN :\n FilterComparisonOperator.IN,\n };\n }\n\n value = matchOperator(FilterInputOperatorValue.LIKE, input, 'start');\n if (typeof value !== 'undefined') {\n return {\n value,\n operator: negation ?\n FilterComparisonOperator.NOT_LIKE :\n FilterComparisonOperator.LIKE,\n };\n }\n\n value = matchOperator(FilterInputOperatorValue.LESS_THAN_EQUAL, input, 'start');\n if (typeof value !== 'undefined') {\n return {\n value,\n operator: FilterComparisonOperator.LESS_THAN_EQUAL,\n };\n }\n\n value = matchOperator(FilterInputOperatorValue.LESS_THAN, input, 'start');\n if (typeof value !== 'undefined') {\n return {\n value,\n operator: FilterComparisonOperator.LESS_THAN,\n };\n }\n\n value = matchOperator(FilterInputOperatorValue.MORE_THAN_EQUAL, input, 'start');\n if (typeof value !== 'undefined') {\n return {\n value,\n operator: FilterComparisonOperator.GREATER_THAN_EQUAL,\n };\n }\n\n value = matchOperator(FilterInputOperatorValue.MORE_THAN, input, 'start');\n if (typeof value !== 'undefined') {\n return {\n value,\n operator: FilterComparisonOperator.GREATER_THAN,\n };\n }\n\n return {\n value: input,\n operator: negation ?\n FilterComparisonOperator.NOT_EQUAL :\n FilterComparisonOperator.EQUAL,\n };\n}\n","/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { ObjectLiteral } from '../../types';\nimport {\n buildKeyWithPath, hasOwnProperty, parseKey, toFlatObject,\n} from '../../utils';\nimport { flattenParseAllowedOption } from '../utils';\nimport { FilterComparisonOperator } from './constants';\nimport type {\n FiltersParseOptions, FiltersParseOutput, FiltersParseOutputElement,\n} from './type';\nimport { parseFilterValue, transformFilterValue } from './utils';\n\nexport class FiltersOptionsContainer<T extends ObjectLiteral = ObjectLiteral> {\n public options : FiltersParseOptions<T>;\n\n public default : Record<string, any>;\n\n public defaultKeys : string[];\n\n public defaultOutput : FiltersParseOutput;\n\n public allowed : string[];\n\n public allowedIsUndefined : boolean;\n\n constructor(input: FiltersParseOptions<T> = {}) {\n this.options = input;\n\n this.allowed = [];\n this.allowedIsUndefined = true;\n\n this.default = {};\n this.defaultKeys = [];\n this.defaultOutput = [];\n\n this.initDefault();\n this.initAllowed();\n }\n\n protected initDefault() {\n if (!this.options.default) {\n this.default = {};\n this.defaultKeys = [];\n this.defaultOutput = this.buildParseOutput();\n return;\n }\n\n this.default = toFlatObject(this.options.default);\n this.defaultKeys = Object.keys(this.default);\n this.defaultOutput = this.buildParseOutput();\n }\n\n protected initAllowed() {\n if (typeof this.options.allowed === 'undefined') {\n if (typeof this.options.default !== 'undefined') {\n const flatten = toFlatObject(this.options.default);\n const allowed = Object.keys(flatten);\n if (allowed.length > 0) {\n this.allowed = allowed;\n this.allowedIsUndefined = false;\n return;\n }\n }\n\n this.allowed = [];\n this.allowedIsUndefined = true;\n return;\n }\n\n this.allowed = flattenParseAllowedOption(this.options.allowed);\n this.allowedIsUndefined = false;\n }\n\n public buildParseOutput(\n input: Record<string, FiltersParseOutputElement> = {},\n ) : FiltersParseOutput {\n const inputKeys = Object.keys(input || {});\n\n if (\n !this.options.defaultByElement &&\n inputKeys.length > 0\n ) {\n return Object.values(input);\n }\n\n if (this.defaultKeys.length > 0) {\n const output : FiltersParseOutput = [];\n\n for (let i = 0; i < this.defaultKeys.length; i++) {\n const keyDetails = parseKey(this.defaultKeys[i]);\n\n if (\n this.options.defaultByElement &&\n inputKeys.length > 0\n ) {\n const keyWithPath = buildKeyWithPath(keyDetails);\n if (hasOwnProperty(input, keyWithPath)) {\n continue;\n }\n }\n\n if (this.options.defaultByElement || inputKeys.length === 0) {\n let path : string | undefined;\n if (keyDetails.path) {\n path = keyDetails.path;\n } else if (this.options.defaultPath) {\n path = this.options.defaultPath;\n }\n\n output.push(this.transformParseOutputElement({\n ...(path ? { path } : {}),\n key: keyDetails.name,\n value: this.default[this.defaultKeys[i]],\n }));\n }\n }\n\n return input ? [...Object.values(input), ...output] : output;\n }\n\n return input ? Object.values(input) : [];\n }\n\n // ^([0-9]+(?:\\.[0-9]+)*){0,1}([a-zA-Z0-9-_]+(?:\\.[a-zA-Z0-9-_]+)*){0,1}$\n public transformParseOutputElement(element: FiltersParseOutputElement) : FiltersParseOutputElement {\n if (\n hasOwnProperty(element, 'path') &&\n (typeof element.path === 'undefined' || element.path === null)\n ) {\n delete element.path;\n }\n\n if (element.operator) {\n return element;\n }\n\n if (typeof element.value === 'string') {\n element = {\n ...element,\n ...parseFilterValue(element.value),\n };\n } else {\n element.operator = FilterComparisonOperator.EQUAL;\n }\n\n element.value = transformFilterValue(element.value);\n\n return element;\n }\n}\n","/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n extendObject, isObject, toFlatObject,\n} from '../../utils';\nimport type { FiltersBuildInput } from './type';\n\nexport function transformFiltersBuildInput(input: FiltersBuildInput<any>) : Record<string, any> {\n return toFlatObject(input, {\n transformer: (input, output, key) => {\n if (typeof input === 'undefined') {\n output[key] = null;\n\n return true;\n }\n\n if (Array.isArray(input)) {\n // preserve null values\n const data : string[] = [];\n for (let i = 0; i < input.length; i++) {\n if (input[i] === null) {\n input[i] = 'null';\n }\n\n if (typeof input[i] === 'number') {\n input[i] = `${input[i]}`;\n }\n\n if (typeof input[i] === 'string') {\n data.push(input[i]);\n }\n }\n\n output[key] = data.join(',');\n\n return true;\n }\n\n if (isObject(input)) {\n const tmp = transformFiltersBuildInput(input as FiltersBuildInput<any>);\n extendObject(output, tmp);\n }\n\n return undefined;\n },\n });\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuildError } from '../../../errors';\n\nexport class FiltersBuildError extends BuildError {\n\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { ParseError } from '../../../errors';\n\nexport class FiltersParseError extends ParseError {\n\n}\n","/*\n * Copyright (c) 2021-2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { NestedKeys, ObjectLiteral } from '../../types';\nimport type { KeyDetails } from '../../utils';\nimport {\n applyMapping,\n buildKeyWithPath,\n isObject,\n isPathAllowedByRelations,\n parseKey,\n} from '../../utils';\nimport { isValidFieldName } from '../fields';\nimport { isPathCoveredByParseAllowedOption } from '../utils';\nimport { FiltersOptionsContainer } from './container';\nimport { FiltersParseError } from './errors';\nimport type { FiltersParseOptions, FiltersParseOutput, FiltersParseOutputElement } from './type';\n\nexport function parseQueryFilters<T extends ObjectLiteral = ObjectLiteral>(\n data: unknown,\n options?: FiltersParseOptions<T> | FiltersOptionsContainer<T>,\n) : FiltersParseOutput {\n let container : FiltersOptionsContainer<T>;\n if (options instanceof FiltersOptionsContainer) {\n container = options;\n } else {\n container = new FiltersOptionsContainer<T>(options);\n }\n\n // If it is an empty array nothing is allowed\n if (\n !container.allowedIsUndefined &&\n container.allowed.length === 0\n ) {\n return container.defaultOutput;\n }\n\n /* istanbul ignore next */\n if (!isObject(data)) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.inputInvalid();\n }\n\n return container.defaultOutput;\n }\n\n const { length } = Object.keys(data);\n if (length === 0) {\n return container.defaultOutput;\n }\n\n const items : Record<string, FiltersParseOutputElement> = {};\n\n // transform to appreciate data format & validate input\n const keys = Object.keys(data);\n for (let i = 0; i < keys.length; i++) {\n const value : unknown = data[keys[i]];\n\n if (\n typeof value !== 'string' &&\n typeof value !== 'number' &&\n typeof value !== 'boolean' &&\n typeof value !== 'undefined' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyValueInvalid(keys[i]);\n }\n continue;\n }\n\n keys[i] = applyMapping(keys[i], container.options.mapping);\n\n const fieldDetails : KeyDetails = parseKey(keys[i]);\n\n if (\n container.allowedIsUndefined &&\n !isValidFieldName(fieldDetails.name)\n ) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyInvalid(fieldDetails.name);\n }\n continue;\n }\n\n if (\n typeof fieldDetails.path !== 'undefined' &&\n !isPathAllowedByRelations(fieldDetails.path, container.options.relations)\n ) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyPathInvalid(fieldDetails.path);\n }\n continue;\n }\n\n const fullKey : string = buildKeyWithPath(fieldDetails);\n if (\n !container.allowedIsUndefined &&\n container.allowed &&\n !isPathCoveredByParseAllowedOption(container.allowed, [keys[i], fullKey])\n ) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyInvalid(fieldDetails.name);\n }\n\n continue;\n }\n\n const filter = container.transformParseOutputElement({\n key: fieldDetails.name,\n value: value as string | boolean | number,\n });\n\n if (container.options.validate) {\n if (Array.isArray(filter.value)) {\n const output : (string | number)[] = [];\n for (let j = 0; j < filter.value.length; j++) {\n if (container.options.validate(filter.key as NestedKeys<T>, filter.value[j])) {\n output.push(filter.value[j]);\n } else if (container.options.throwOnFailure) {\n throw FiltersParseError.keyValueInvalid(fieldDetails.name);\n }\n }\n\n filter.value = output as string[] | number[];\n if (filter.value.length === 0) {\n continue;\n }\n } else if (!container.options.validate(filter.key as NestedKeys<T>, filter.value)) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyValueInvalid(fieldDetails.name);\n }\n\n continue;\n }\n }\n\n if (\n typeof filter.value === 'string' &&\n filter.value.length === 0\n ) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyValueInvalid(fieldDetails.name);\n }\n\n continue;\n }\n\n if (\n Array.isArray(filter.value) &&\n filter.value.length === 0\n ) {\n if (container.options.throwOnFailure) {\n throw FiltersParseError.keyValueInvalid(fieldDetails.name);\n }\n\n continue;\n }\n\n if (fieldDetails.path || container.options.defaultPath) {\n filter.path = fieldDetails.path || container.options.defaultPath;\n }\n\n items[fullKey] = filter;\n }\n\n return container.buildParseOutput(items);\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuildError } from '../../../errors';\n\nexport class PaginationBuildError extends BuildError {\n\n}\n","/*\n * Copyright (c) 2023.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { ParseError } from '../../../errors';\n\nexport class PaginationParseError extends ParseError {\n static limitExceeded(limit: number) {\n return new this({\n message: `The pagination limit must not exceed the value of ${limit}.`,\n });\n