diff --git a/README.MD b/README.MD index ac768e11..884beac1 100644 --- a/README.MD +++ b/README.MD @@ -53,7 +53,7 @@ read the [docs](https://rapiq.tada5hi.net). ### Build 🏗 The first step is to construct a [BuildInput](https://rapiq.tada5hi.net/guide/build-api-reference.html#buildinput) object for a generic Record `` and -pass it to the [buildQuery](https://rapiq.tada5hi.net/guide/build-api-reference.html#buildquery) method to convert it to a string. +pass it to the [buildQuery](https://rapiq.tada5hi.net/guide/build-api-reference.html#buildquery) method to convert it to a transportable string. The result string can then be provided as a URL query string to a backend application. The backend application can than process the request, by [parsing](#parse-) the query string. diff --git a/src/parameter/fields/parse.ts b/src/parameter/fields/parse.ts index 587b1b35..ed13513f 100644 --- a/src/parameter/fields/parse.ts +++ b/src/parameter/fields/parse.ts @@ -34,18 +34,6 @@ function buildReverseRecord( return output; } -export function replaceRecordKey(record: Record, key: string, newKey: string) : Record { - if ( - hasOwnProperty(record, key) - ) { - const value = record[key]; - delete record[key]; - record[newKey] = value; - } - - return record; -} - export function parseQueryFields( data: unknown, options?: FieldsParseOptions, diff --git a/src/parameter/fields/type.ts b/src/parameter/fields/type.ts index 0e352f3f..264a6f6c 100644 --- a/src/parameter/fields/type.ts +++ b/src/parameter/fields/type.ts @@ -27,14 +27,15 @@ export type FieldsBuildInput> = never } | - [ - FieldWithOperator>[], + ( + FieldWithOperator>[] + | { [K in keyof T]?: Flatten extends OnlyObject ? FieldsBuildInput> : never - }, - ] + } + )[] | FieldWithOperator>[] | FieldWithOperator>; diff --git a/src/parameter/fields/utils/domain.ts b/src/parameter/fields/utils/domain.ts index 8ce0aecc..e05cb16c 100644 --- a/src/parameter/fields/utils/domain.ts +++ b/src/parameter/fields/utils/domain.ts @@ -5,7 +5,6 @@ * view the LICENSE file that was distributed with this source code. */ -import { hasOwnProperty } from '../../../utils'; import { DEFAULT_ID } from '../../../constants'; export function buildFieldDomainRecords( @@ -25,41 +24,3 @@ export function buildFieldDomainRecords( return domainFields; } - -export function mergeFieldsDomainRecords( - sourceA: Record, - sourceB: Record, -) { - const target: Record = {}; - - let keys = Object.keys(sourceA); - for (let i = 0; i < keys.length; i++) { - if (hasOwnProperty(sourceB, keys[i])) { - target[keys[i]] = [...sourceA[keys[i]], ...sourceB[keys[i]]]; - } else { - target[keys[i]] = [...sourceA[keys[i]]]; - } - } - - keys = Object.keys(sourceB).filter((key) => !keys.includes(key)); - for (let i = 0; i < keys.length; i++) { - if (hasOwnProperty(sourceA, keys[i])) { - target[keys[i]] = [...sourceA[keys[i]], ...sourceB[keys[i]]]; - } else { - target[keys[i]] = [...sourceB[keys[i]]]; - } - } - - keys = Object.keys(target); - - if ( - keys.length >= 2 && - hasOwnProperty(target, DEFAULT_ID) && - Array.isArray(target[DEFAULT_ID]) && - target[DEFAULT_ID].length === 0 - ) { - delete target[DEFAULT_ID]; - } - - return target; -} diff --git a/src/parameter/filters/build.ts b/src/parameter/filters/build.ts index 8119cc7b..6e1332df 100644 --- a/src/parameter/filters/build.ts +++ b/src/parameter/filters/build.ts @@ -36,11 +36,23 @@ export function buildQueryFilters( return true; } + if (Array.isArray(input)) { + // todo: check array elements are string + output[key] = input.join(','); + + return true; + } + if (isFilterOperatorConfig(input)) { if (typeof input.value === 'undefined') { input.value = null; } + if (Array.isArray(input.value)) { + // todo: check array elements are string + input.value = input.value.join(','); + } + if (Array.isArray(input.operator)) { // merge operators input.operator = input.operator diff --git a/src/parameter/filters/type.ts b/src/parameter/filters/type.ts index c3ea530b..76ed0c84 100644 --- a/src/parameter/filters/type.ts +++ b/src/parameter/filters/type.ts @@ -22,8 +22,8 @@ type FilterValueInput = FilterValueInputPrimitive | null | undefined; export type FilterValueSimple = V extends FilterValueInputPrimitive ? (V | V[]) : V; export type FilterValueWithOperator = V extends string | number ? - `!${V}` | `!~${V}` | `~${V}` | `<${V}` | `<=${V}` | `>${V}` | `>=${V}` : - never; + `!${V}` | `!~${V}` | `~${V}` | `<${V}` | `<=${V}` | `>${V}` | `>=${V}` | null | '!null' : + V extends boolean ? null | '!null' : never; export type FilterValue = V extends FilterValueInputPrimitive ? (FilterValueSimple | FilterValueWithOperator | Array>) : diff --git a/src/parse/module.ts b/src/parse/module.ts index 8ec5dde0..5810c6b9 100644 --- a/src/parse/module.ts +++ b/src/parse/module.ts @@ -8,9 +8,9 @@ import { FieldsParseOutput, FiltersParseOutput, - PaginationParseOutput, RelationsParseOutput, + PaginationParseOutput, + RelationsParseOutput, SortParseOutput, - parseQueryRelations, } from '../parameter'; import { Parameter, URLParameter } from '../constants'; import { parseQueryParameter } from './parameter'; @@ -43,7 +43,8 @@ export function parseQuery( case Parameter.RELATIONS: { const value = input[Parameter.RELATIONS] ?? input[URLParameter.RELATIONS]; if (value || options[Parameter.RELATIONS]) { - relations = parseQueryRelations( + relations = parseQueryParameter( + key, value, options[Parameter.RELATIONS], ); @@ -56,7 +57,7 @@ export function parseQuery( const value = input[Parameter.FIELDS] ?? input[URLParameter.FIELDS]; if (value || options[Parameter.FIELDS]) { output[Parameter.FIELDS] = parseQueryParameter( - keys[i], + key, value, options[Parameter.FIELDS], relations, @@ -68,7 +69,7 @@ export function parseQuery( const value = input[Parameter.FILTERS] ?? input[URLParameter.FILTERS]; if (value || options[Parameter.FILTERS]) { output[Parameter.FILTERS] = parseQueryParameter( - keys[i], + key, value, options[Parameter.FILTERS], relations, @@ -80,7 +81,7 @@ export function parseQuery( const value = input[Parameter.PAGINATION] ?? input[URLParameter.PAGINATION]; if (value || options[Parameter.PAGINATION]) { output[Parameter.PAGINATION] = parseQueryParameter( - keys[i], + key, value, options[Parameter.PAGINATION], relations, @@ -92,7 +93,7 @@ export function parseQuery( const value = input[Parameter.SORT] ?? input[URLParameter.SORT]; if (value || options[Parameter.SORT]) { output[Parameter.SORT] = parseQueryParameter( - keys[i], + key, value, options[Parameter.SORT], relations, diff --git a/test/unit/build.spec.ts b/test/unit/build.spec.ts index 1d3fcd52..ba4ea513 100644 --- a/test/unit/build.spec.ts +++ b/test/unit/build.spec.ts @@ -65,6 +65,19 @@ describe('src/build.ts', () => { }); expect(record).toEqual(buildURLQueryString({ [URLParameter.FILTERS]: { 'child.id': 1 } })); + record = buildQuery({ + filter: { + 'child.id': 1, + child: { + 'child.id': 'abc' + }, + }, + }); + expect(record).toEqual(buildURLQueryString({ [URLParameter.FILTERS]: { + 'child.id': 1, + 'child.child.id': 'abc' + } })); + record = buildQuery({ filter: { siblings: { @@ -136,6 +149,14 @@ describe('src/build.ts', () => { expect(record).toEqual(buildURLQueryString({ [URLParameter.FILTERS]: { id: '>=1' } })); // with negation & in operator + record = buildQuery({ + filter: { + id: [1,2,3], + }, + }); + expect(record).toEqual(buildURLQueryString({ [URLParameter.FILTERS]: { id: '1,2,3' } })); + + // with negation & like operator record = buildQuery({ filter: { id: { @@ -200,6 +221,32 @@ describe('src/build.ts', () => { }); expect(record).toEqual(buildURLQueryString({ fields: { [DEFAULT_ID]: ['id'], child: ['id', 'name'] } })); + + record = buildQuery({ + fields: [ + ['id'], + ['name'], + { + child: ['id', 'name'], + } + ] + }); + + expect(record).toEqual(buildURLQueryString({ fields: { [DEFAULT_ID]: ['id', 'name'], child: ['id', 'name'] } })); + record = buildQuery({ + fields: [ + ['id'], + { + child: ['id'], + }, + { + child: ['name'], + } + ] + }); + + expect(record).toEqual(buildURLQueryString({ fields: { [DEFAULT_ID]: ['id'], child: ['id', 'name'] } })); + }); it('should format sort record', () => { diff --git a/test/unit/filters.spec.ts b/test/unit/filters.spec.ts index 5b89ff5d..2d6a25dd 100644 --- a/test/unit/filters.spec.ts +++ b/test/unit/filters.spec.ts @@ -12,6 +12,7 @@ import { parseQueryFilters, parseQueryRelations, } from '../../src'; +import {isFilterOperatorConfig} from "../../src/parameter/filters/utils"; describe('src/filter/index.ts', () => { it('should transform request filters', () => { @@ -349,4 +350,27 @@ describe('src/filter/index.ts', () => { }, ] as FiltersParseOutput); }); + + it('should determine filter operator config', () => { + let data = isFilterOperatorConfig({ + value: 1, + operator: '<' + }); + + expect(data).toBeTruthy(); + + data = isFilterOperatorConfig({ + value: 1, + operator: {} + }) + + expect(data).toBeFalsy(); + + data = isFilterOperatorConfig({ + value: {}, + operator: '<' + }); + + expect(data).toBeFalsy(); + }) });