From c816e93a465c06e10039afd62ca580e4555d9d06 Mon Sep 17 00:00:00 2001 From: tada5hi Date: Tue, 3 Feb 2026 14:44:20 +0100 Subject: [PATCH 1/3] feat: enhance typing with interfaces and lesser coupling --- .../codec-url-simple/src/decoder/module.ts | 35 +++++------- .../src/parameter/fields/collection/module.ts | 23 +++----- .../src/parameter/fields/collection/types.ts | 10 +++- .../src/parameter/fields/record/module.ts | 6 +-- .../core/src/parameter/fields/record/types.ts | 8 +++ .../parameter/filters/collection/module.ts | 6 +-- .../src/parameter/filters/collection/types.ts | 20 ++++++- .../src/parameter/filters/helpers/index.ts | 8 --- .../src/parameter/filters/helpers/module.ts | 44 --------------- packages/core/src/parameter/filters/index.ts | 1 - .../src/parameter/filters/record/module.ts | 6 +-- .../src/parameter/filters/record/types.ts | 54 +++++++++++-------- packages/core/src/parameter/module.ts | 34 +++++++----- .../src/parameter/pagination/pagination.ts | 4 +- .../core/src/parameter/pagination/types.ts | 12 +++-- .../parameter/relations/collection/module.ts | 11 ++-- .../parameter/relations/collection/types.ts | 12 ++++- .../src/parameter/relations/record/module.ts | 4 +- .../src/parameter/relations/record/types.ts | 10 ++-- .../src/parameter/sorts/collection/module.ts | 10 ++-- .../src/parameter/sorts/collection/types.ts | 10 +++- .../core/src/parameter/sorts/record/module.ts | 4 +- .../core/src/parameter/sorts/record/types.ts | 12 ++++- packages/core/src/parameter/types.ts | 41 +++++++++----- .../core/src/parser/parameter/fields/base.ts | 4 +- .../core/src/parser/parameter/filters/base.ts | 4 +- .../src/parser/parameter/pagination/base.ts | 4 +- .../src/parser/parameter/relations/base.ts | 4 +- .../core/src/parser/parameter/sort/base.ts | 4 +- packages/parser-expression/src/module.ts | 39 +++++++------- .../src/parameter/filters/module.ts | 22 +++++++- .../test/unit/parser/filters.spec.ts | 32 +++++------ packages/parser-simple/src/module.ts | 39 +++++++------- .../src/parameter/fields/module.ts | 16 +++--- .../src/parameter/filters/module.ts | 20 +++---- packages/sql/package.json | 6 +-- packages/sql/src/visitor/fields.ts | 6 +-- 37 files changed, 312 insertions(+), 273 deletions(-) delete mode 100644 packages/core/src/parameter/filters/helpers/index.ts delete mode 100644 packages/core/src/parameter/filters/helpers/module.ts diff --git a/packages/codec-url-simple/src/decoder/module.ts b/packages/codec-url-simple/src/decoder/module.ts index ee3840657..f06950046 100644 --- a/packages/codec-url-simple/src/decoder/module.ts +++ b/packages/codec-url-simple/src/decoder/module.ts @@ -13,11 +13,12 @@ import { } from '@rapiq/parser-simple'; import { parse } from 'qs'; import type { - Condition, - Fields, - Pagination, - Relations, - Sorts, + IFields, + IFilters, + IPagination, + IQuery, + IRelations, + ISorts, } from '@rapiq/core'; import { Query, @@ -44,7 +45,7 @@ export class URLDecoder { this.sort = new SimpleSortParser(); } - decode(input: string) : Query | null { + decode(input: string) : IQuery | null { const parsed = parse(input); if (!isObject(parsed)) { return null; @@ -75,7 +76,7 @@ export class URLDecoder { return output; } - decodeFields(input: string) : Fields | null { + decodeFields(input: string) : IFields | null { const output = parse(input); if (!isObject(output)) { return null; @@ -88,41 +89,33 @@ export class URLDecoder { return this.fields.parse(output); } - decodeFilters(input: string) : Condition | null { + decodeFilters(input: string) : IFilters | null { const output = parse(input); if (!isObject(output)) { return null; } if (output[URLParameter.FILTERS]) { - if (isObject(output[URLParameter.FILTERS])) { - return this.filters.parse(output[URLParameter.FILTERS]); - } - - return null; + return this.filters.parse(output[URLParameter.FILTERS]); } return this.filters.parse(output); } - decodePagination(input: string) : Pagination | null { + decodePagination(input: string) : IPagination | null { const output = parse(input); if (!isObject(output)) { return null; } if (output[URLParameter.PAGINATION]) { - if (isObject(output[URLParameter.PAGINATION])) { - return this.pagination.parse(output[URLParameter.PAGINATION]); - } - - return null; + return this.pagination.parse(output[URLParameter.PAGINATION]); } return this.pagination.parse(output); } - decodeRelations(input: string) : Relations | null { + decodeRelations(input: string) : IRelations | null { const output = parse(input); if (!isObject(output)) { return null; @@ -135,7 +128,7 @@ export class URLDecoder { return this.relations.parse(output); } - decodeSort(input: string) : Sorts | null { + decodeSort(input: string) : ISorts | null { const output = parse(input); if (!isObject(output)) { return null; diff --git a/packages/core/src/parameter/fields/collection/module.ts b/packages/core/src/parameter/fields/collection/module.ts index 5fb2cd917..381116684 100644 --- a/packages/core/src/parameter/fields/collection/module.ts +++ b/packages/core/src/parameter/fields/collection/module.ts @@ -5,19 +5,20 @@ * view the LICENSE file that was distributed with this source code. */ +import type { IField } from '../record'; import { Field } from '../record'; import { FieldOperator } from '../../../schema'; -import type { IFieldsVisitor } from './types'; +import type { IFields, IFieldsVisitor } from './types'; type FieldsExecuteOptions = { default: string[], allowed: string[], }; -export class Fields { - readonly value : Field[]; +export class Fields implements IFields { + readonly value : IField[]; - constructor(value : Field[] = []) { + constructor(value : IField[] = []) { this.value = value; } @@ -25,24 +26,12 @@ export class Fields { return visitor.visitFields(this); } - mergeWith(input: Fields | Field) { - if (input instanceof Field) { - this.value.push(input); - } - - if (input instanceof Fields) { - for (let i = 0; i < input.value.length; i++) { - this.value.push(input.value[i]); - } - } - } - /** * Extract field set, with includes and excludes. * * @param options */ - execute(options: FieldsExecuteOptions) : Fields { + execute(options: FieldsExecuteOptions) : IFields { const includes : string[] = []; const excludes : string[] = []; const explicates : string[] = []; diff --git a/packages/core/src/parameter/fields/collection/types.ts b/packages/core/src/parameter/fields/collection/types.ts index 4ce093a1a..f4b26362a 100644 --- a/packages/core/src/parameter/fields/collection/types.ts +++ b/packages/core/src/parameter/fields/collection/types.ts @@ -5,8 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Fields } from './module'; +import type { IField } from '../record'; export interface IFieldsVisitor { - visitFields(expr: Fields): R; + visitFields(expr: IFields): R; +} + +export interface IFields { + readonly value: IField[] + + accept(visitor: IFieldsVisitor): R; } diff --git a/packages/core/src/parameter/fields/record/module.ts b/packages/core/src/parameter/fields/record/module.ts index bba4635af..fe4733a02 100644 --- a/packages/core/src/parameter/fields/record/module.ts +++ b/packages/core/src/parameter/fields/record/module.ts @@ -5,10 +5,10 @@ * view the LICENSE file that was distributed with this source code. */ -import type { IFieldVisitor } from './types'; +import type { IField, IFieldVisitor } from './types'; -export class Field { - name: string; +export class Field implements IField { + readonly name: string; readonly operator: string | undefined; diff --git a/packages/core/src/parameter/fields/record/types.ts b/packages/core/src/parameter/fields/record/types.ts index 7ad103a28..53e2ef535 100644 --- a/packages/core/src/parameter/fields/record/types.ts +++ b/packages/core/src/parameter/fields/record/types.ts @@ -10,3 +10,11 @@ import type { Field } from './module'; export interface IFieldVisitor { visitField(expr: Field): R; } + +export interface IField { + readonly name: string; + + readonly operator: string | undefined; + + accept(visitor: IFieldVisitor): R; +} diff --git a/packages/core/src/parameter/filters/collection/module.ts b/packages/core/src/parameter/filters/collection/module.ts index 5d00a8dcc..5c5bd6f7d 100644 --- a/packages/core/src/parameter/filters/collection/module.ts +++ b/packages/core/src/parameter/filters/collection/module.ts @@ -8,11 +8,11 @@ import { CompoundCondition as BaseCompoundCondition } from '@ucast/core'; import type { Condition } from '../condition'; -import type { IFiltersVisitor } from './types'; +import type { IFilters, IFiltersVisitor } from './types'; export class Filters< T extends Condition = Condition, -> extends BaseCompoundCondition { +> extends BaseCompoundCondition implements IFilters { accept(visitor: IFiltersVisitor) : R { return visitor.visitFilters(this); } @@ -27,7 +27,7 @@ export class Filters< } } - flatten(aggregatedResult?: T[]) : Filters { + flatten(aggregatedResult?: T[]) : IFilters { // this.value.splice(0, this.value.length, ...next); return new Filters( diff --git a/packages/core/src/parameter/filters/collection/types.ts b/packages/core/src/parameter/filters/collection/types.ts index 9cbfaa0e7..d01407a21 100644 --- a/packages/core/src/parameter/filters/collection/types.ts +++ b/packages/core/src/parameter/filters/collection/types.ts @@ -5,8 +5,24 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Filters } from './module'; +import type { Condition } from '../condition'; export interface IFiltersVisitor { - visitFilters(expr: Filters): R; + visitFilters(expr: IFilters): R; +} + +export interface IFilters< + T extends Condition = Condition, +> { + readonly operator: string; + + readonly value : T[]; + + accept(visitor: IFiltersVisitor) : R; + + add(child: T) : void; + + clear() : void; + + flatten(items?: T[]) : IFilters; } diff --git a/packages/core/src/parameter/filters/helpers/index.ts b/packages/core/src/parameter/filters/helpers/index.ts deleted file mode 100644 index bafb296b1..000000000 --- a/packages/core/src/parameter/filters/helpers/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) 2025. - * Author Peter Placzek (tada5hi) - * For the full copyright and license information, - * view the LICENSE file that was distributed with this source code. - */ - -export * from './module'; diff --git a/packages/core/src/parameter/filters/helpers/module.ts b/packages/core/src/parameter/filters/helpers/module.ts deleted file mode 100644 index 23db3c9c1..000000000 --- a/packages/core/src/parameter/filters/helpers/module.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2025-2025. - * Author Peter Placzek (tada5hi) - * For the full copyright and license information, - * view the LICENSE file that was distributed with this source code. - */ - -import { Filters } from '../collection'; -import type { Condition } from '../condition'; - -export function flattenConditions( - conditions: T[], - operator: string, - aggregatedResult?: T[], -) { - const flatConditions: T[] = aggregatedResult || []; - - for (let i = 0, { length } = conditions; i < length; i++) { - const currentNode = conditions[i]; - - if (Filters.check(currentNode, operator)) { - flattenConditions(currentNode.value as T[], operator, flatConditions); - } else { - flatConditions.push(currentNode); - } - } - - return flatConditions; -} - -export function optimizedCompoundCondition( - operator: string, - conditions: T[], -): Filters { - if (conditions.length === 1) { - const [first] = conditions; - if (Filters.check(first)) { - return first.flatten() as Filters; - } - } - - const filters = new Filters(operator, conditions); - return filters.flatten(); -} diff --git a/packages/core/src/parameter/filters/index.ts b/packages/core/src/parameter/filters/index.ts index 14d98d9de..3b7884352 100644 --- a/packages/core/src/parameter/filters/index.ts +++ b/packages/core/src/parameter/filters/index.ts @@ -8,5 +8,4 @@ export * from './collection'; export * from './condition'; export * from './record'; -export * from './helpers'; export * from './regex'; diff --git a/packages/core/src/parameter/filters/record/module.ts b/packages/core/src/parameter/filters/record/module.ts index 921f1cbe3..7be4a1f24 100644 --- a/packages/core/src/parameter/filters/record/module.ts +++ b/packages/core/src/parameter/filters/record/module.ts @@ -7,14 +7,12 @@ import { FieldCondition as BaseFieldCondition } from '@ucast/core'; import { FilterFieldOperator } from '../../../schema'; -import type { IFilterVisitor } from './types'; +import type { IFilter, IFilterVisitor } from './types'; export class Filter< OPERATOR extends string = `${FilterFieldOperator}`, VALUE = unknown, -> extends BaseFieldCondition { - public readonly raw: unknown; - +> extends BaseFieldCondition implements IFilter { // eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor constructor( operator: OPERATOR, diff --git a/packages/core/src/parameter/filters/record/types.ts b/packages/core/src/parameter/filters/record/types.ts index 7efad2223..a4c775adc 100644 --- a/packages/core/src/parameter/filters/record/types.ts +++ b/packages/core/src/parameter/filters/record/types.ts @@ -6,40 +6,52 @@ */ import type { FilterFieldOperator } from '../../../schema'; -import type { Filters } from '../collection'; -import type { Filter } from './module'; +import type { IFilters } from '../collection'; export interface IFilterVisitor { - visitFilter(expr: Filter): R; + visitFilter(expr: IFilter): R; - visitFilterEqual?(expr: Filter) : R; - visitFilterNotEqual?(expr: Filter) : R; + visitFilterEqual?(expr: IFilter) : R; + visitFilterNotEqual?(expr: IFilter) : R; - visitFilterLessThan?(expr: Filter) : R; + visitFilterLessThan?(expr: IFilter) : R; - visitFilterLessThanEqual?(expr: Filter) : R; + visitFilterLessThanEqual?(expr: IFilter) : R; - visitFilterGreaterThan?(expr: Filter) : R; + visitFilterGreaterThan?(expr: IFilter) : R; - visitFilterGreaterThanEqual?(expr: Filter) : R; + visitFilterGreaterThanEqual?(expr: IFilter) : R; - visitFilterExists?(expr: Filter) : R; + visitFilterExists?(expr: IFilter) : R; - visitFilterIn?(expr: Filter) : R; - visitFilterNotIn?(expr: Filter) : R; + visitFilterIn?(expr: IFilter) : R; + visitFilterNotIn?(expr: IFilter) : R; - visitFilterMod?(expr: Filter) : R; + visitFilterMod?(expr: IFilter) : R; - visitFilterElemMatch?(expr: Filter) : R; + visitFilterElemMatch?(expr: IFilter) : R; - visitFilterContains?(expr: Filter) : R; - visitFilterNotContains?(expr: Filter) : R; + visitFilterContains?(expr: IFilter) : R; + visitFilterNotContains?(expr: IFilter) : R; - visitFilterStartsWith?(expr: Filter) : R; - visitFilterNotStartsWith?(expr: Filter) : R; + visitFilterStartsWith?(expr: IFilter) : R; + visitFilterNotStartsWith?(expr: IFilter) : R; - visitFilterEndsWith?(expr: Filter) : R; - visitFilterNotEndsWith?(expr: Filter) : R; + visitFilterEndsWith?(expr: IFilter) : R; + visitFilterNotEndsWith?(expr: IFilter) : R; - visitFilterRegex?(expr: Filter) : R; + visitFilterRegex?(expr: IFilter) : R; +} + +export interface IFilter< + OPERATOR extends string = `${FilterFieldOperator}`, + VALUE = unknown, +> { + readonly field : string; + + readonly operator : string | OPERATOR; + + readonly value: VALUE; + + accept(visitor: IFilterVisitor) : R; } diff --git a/packages/core/src/parameter/module.ts b/packages/core/src/parameter/module.ts index 9c8aedafd..684da2b29 100644 --- a/packages/core/src/parameter/module.ts +++ b/packages/core/src/parameter/module.ts @@ -5,25 +5,33 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Field, Fields } from './fields'; -import type { Filter, Filters } from './filters'; -import type { Pagination } from './pagination'; -import type { Relation, Relations } from './relations'; -import type { Sort, Sorts } from './sorts'; -import type { IQueryVisitor, QueryOptions } from './types'; +import type { + IField, IFields, +} from './fields'; +import type { + IFilter, IFilters, +} from './filters'; +import type { IPagination } from './pagination'; +import type { + IRelation, IRelations, +} from './relations'; +import type { + ISort, ISorts, +} from './sorts'; +import type { IQuery, IQueryVisitor, QueryContext } from './types'; -export class Query { - public fields : Fields | Field | undefined; +export class Query implements IQuery { + public fields : IFields | IField | undefined; - public filters : Filters | Filter | undefined; + public filters : IFilters | IFilter | undefined; - public relations : Relations | Relation | undefined; + public relations : IRelations | IRelation | undefined; - public pagination : Pagination | undefined; + public pagination : IPagination | undefined; - public sorts : Sorts | Sort | undefined; + public sorts : ISorts | ISort | undefined; - constructor(options: QueryOptions = {}) { + constructor(options: QueryContext = {}) { this.fields = options.fields; this.filters = options.filters; this.relations = options.relations; diff --git a/packages/core/src/parameter/pagination/pagination.ts b/packages/core/src/parameter/pagination/pagination.ts index 81e44d6ab..0012a3cb6 100644 --- a/packages/core/src/parameter/pagination/pagination.ts +++ b/packages/core/src/parameter/pagination/pagination.ts @@ -5,9 +5,9 @@ * view the LICENSE file that was distributed with this source code. */ -import type { IPaginationVisitor } from './types'; +import type { IPagination, IPaginationVisitor } from './types'; -export class Pagination { +export class Pagination implements IPagination { limit : number | undefined; offset : number | undefined; diff --git a/packages/core/src/parameter/pagination/types.ts b/packages/core/src/parameter/pagination/types.ts index 28b160318..dc3b08b70 100644 --- a/packages/core/src/parameter/pagination/types.ts +++ b/packages/core/src/parameter/pagination/types.ts @@ -5,8 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Pagination } from './pagination'; - export interface IPaginationVisitor { - visitPagination(expr: Pagination): R; + visitPagination(expr: IPagination): R; +} + +export interface IPagination { + limit?: number, + + offset?: number + + accept(visitor: IPaginationVisitor) : R; } diff --git a/packages/core/src/parameter/relations/collection/module.ts b/packages/core/src/parameter/relations/collection/module.ts index 854fe51b1..91d800bd3 100644 --- a/packages/core/src/parameter/relations/collection/module.ts +++ b/packages/core/src/parameter/relations/collection/module.ts @@ -5,13 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ +import type { IRelation } from '../record'; import { Relation } from '../record'; -import type { IRelationsVisitor } from './types'; +import type { IRelations, IRelationsVisitor } from './types'; -export class Relations { - readonly value: Relation[]; +export class Relations implements IRelations { + readonly value: IRelation[]; - constructor(value: Relation[] = []) { + constructor(value: IRelation[] = []) { this.value = value; } @@ -19,7 +20,7 @@ export class Relations { return visitor.visitRelations(this); } - extract(root: string): Relations { + extract(root: string): IRelations { const removed: Relations = new Relations(); for (let i = this.value.length - 1; i >= 0; i--) { diff --git a/packages/core/src/parameter/relations/collection/types.ts b/packages/core/src/parameter/relations/collection/types.ts index 6b319ee58..842f1e285 100644 --- a/packages/core/src/parameter/relations/collection/types.ts +++ b/packages/core/src/parameter/relations/collection/types.ts @@ -5,8 +5,16 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Relations } from './module'; +import type { IRelation } from '../record'; export interface IRelationsVisitor { - visitRelations(expr: Relations): R; + visitRelations(expr: IRelations): R; +} + +export interface IRelations { + readonly value: IRelation[]; + + accept(visitor: IRelationsVisitor) : R; + + extract(root: string): IRelations; } diff --git a/packages/core/src/parameter/relations/record/module.ts b/packages/core/src/parameter/relations/record/module.ts index a72083587..9192193b9 100644 --- a/packages/core/src/parameter/relations/record/module.ts +++ b/packages/core/src/parameter/relations/record/module.ts @@ -5,9 +5,9 @@ * view the LICENSE file that was distributed with this source code. */ -import type { IRelationVisitor } from './types'; +import type { IRelation, IRelationVisitor } from './types'; -export class Relation { +export class Relation implements IRelation { readonly name: string; constructor(name: string) { diff --git a/packages/core/src/parameter/relations/record/types.ts b/packages/core/src/parameter/relations/record/types.ts index 7262212e4..dc099a142 100644 --- a/packages/core/src/parameter/relations/record/types.ts +++ b/packages/core/src/parameter/relations/record/types.ts @@ -5,8 +5,12 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Relation } from './module'; - export interface IRelationVisitor { - visitRelation(expr: Relation): R; + visitRelation(expr: IRelation): R; +} + +export interface IRelation { + readonly name: string; + + accept(visitor: IRelationVisitor) : R; } diff --git a/packages/core/src/parameter/sorts/collection/module.ts b/packages/core/src/parameter/sorts/collection/module.ts index 98576e84b..c81a690c7 100644 --- a/packages/core/src/parameter/sorts/collection/module.ts +++ b/packages/core/src/parameter/sorts/collection/module.ts @@ -5,13 +5,13 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Sort } from '../record'; -import type { ISortsVisitor } from './types'; +import type { ISort } from '../record'; +import type { ISorts, ISortsVisitor } from './types'; -export class Sorts { - readonly value: Sort[]; +export class Sorts implements ISorts { + readonly value: ISort[]; - constructor(value: Sort[] = []) { + constructor(value: ISort[] = []) { this.value = value; } diff --git a/packages/core/src/parameter/sorts/collection/types.ts b/packages/core/src/parameter/sorts/collection/types.ts index 39b2920fc..2d289d536 100644 --- a/packages/core/src/parameter/sorts/collection/types.ts +++ b/packages/core/src/parameter/sorts/collection/types.ts @@ -5,8 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Sorts } from './module'; +import type { ISort } from '../record'; export interface ISortsVisitor { - visitSorts(expr: Sorts): R; + visitSorts(expr: ISorts): R; +} + +export interface ISorts { + readonly value: ISort[]; + + accept(visitor: ISortsVisitor) : R; } diff --git a/packages/core/src/parameter/sorts/record/module.ts b/packages/core/src/parameter/sorts/record/module.ts index 5d7592462..25a3b9332 100644 --- a/packages/core/src/parameter/sorts/record/module.ts +++ b/packages/core/src/parameter/sorts/record/module.ts @@ -6,9 +6,9 @@ */ import { SortDirection } from '../../../schema'; -import type { ISortVisitor } from './types'; +import type { ISort, ISortVisitor } from './types'; -export class Sort { +export class Sort implements ISort { readonly name: string; readonly operator: `${SortDirection}`; diff --git a/packages/core/src/parameter/sorts/record/types.ts b/packages/core/src/parameter/sorts/record/types.ts index a268b15b5..ae475eb3f 100644 --- a/packages/core/src/parameter/sorts/record/types.ts +++ b/packages/core/src/parameter/sorts/record/types.ts @@ -5,8 +5,16 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Sort } from './module'; +import type { SortDirection } from '../../../schema'; export interface ISortVisitor { - visitSort(expr: Sort): R; + visitSort(expr: ISort): R; +} + +export interface ISort { + readonly name: string; + + readonly operator: `${SortDirection}`; + + accept(visitor: ISortVisitor) : R; } diff --git a/packages/core/src/parameter/types.ts b/packages/core/src/parameter/types.ts index 42552d975..9704b5a88 100644 --- a/packages/core/src/parameter/types.ts +++ b/packages/core/src/parameter/types.ts @@ -6,30 +6,45 @@ */ import type { - Field, Fields, + IField, IFields, } from './fields'; import type { - Filter, - Filters, + IFilter, + IFilters, } from './filters'; import type { Query } from './module'; import type { - Relation, - Relations, + IRelation, + IRelations, } from './relations'; -import type { Pagination } from './pagination'; +import type { IPagination } from './pagination'; import type { - Sort, Sorts, + ISort, + ISorts, } from './sorts'; -export type QueryOptions = { - fields?: Fields | Field, - filters?: Filters | Filter, - relations?: Relations | Relation, - pagination?: Pagination, - sorts?: Sorts | Sort +export type QueryContext = { + fields?: IFields | IField, + filters?: IFilters | IFilter, + relations?: IRelations | IRelation, + pagination?: IPagination, + sorts?: ISorts | ISort }; export interface IQueryVisitor { visitQuery(expr: Query) : R; } + +export interface IQuery { + fields?: IFields | IField, + + filters?: IFilters | IFilter, + + relations?: IRelations | IRelation, + + pagination?: IPagination, + + sorts?: ISorts | ISort + + accept(visitor: IQueryVisitor) : R; +} diff --git a/packages/core/src/parser/parameter/fields/base.ts b/packages/core/src/parser/parameter/fields/base.ts index 0e0fe3e4c..207a68aad 100644 --- a/packages/core/src/parser/parameter/fields/base.ts +++ b/packages/core/src/parser/parameter/fields/base.ts @@ -5,14 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Fields } from '../../../parameter'; +import type { IFields } from '../../../parameter'; import { FieldsSchema, Schema, defineFieldsSchema } from '../../../schema'; import type { ObjectLiteral } from '../../../types'; import { BaseParser } from '../../base'; export abstract class BaseFieldsParser< OPTIONS extends ObjectLiteral = ObjectLiteral, -> extends BaseParser { +> extends BaseParser { protected resolveSchema< RECORD extends ObjectLiteral = ObjectLiteral, >(input?: string | Schema | FieldsSchema) : FieldsSchema { diff --git a/packages/core/src/parser/parameter/filters/base.ts b/packages/core/src/parser/parameter/filters/base.ts index b4a407138..4ff678c8a 100644 --- a/packages/core/src/parser/parameter/filters/base.ts +++ b/packages/core/src/parser/parameter/filters/base.ts @@ -5,14 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Condition } from '../../../parameter'; +import type { IFilters } from '../../../parameter'; import { FiltersSchema, Schema, defineFiltersSchema } from '../../../schema'; import type { ObjectLiteral } from '../../../types'; import { BaseParser } from '../../base'; export abstract class BaseFiltersParser< OPTIONS extends ObjectLiteral = ObjectLiteral, -> extends BaseParser { +> extends BaseParser { protected resolveSchema< RECORD extends ObjectLiteral = ObjectLiteral, >(input?: string | Schema | FiltersSchema) : FiltersSchema { diff --git a/packages/core/src/parser/parameter/pagination/base.ts b/packages/core/src/parser/parameter/pagination/base.ts index 49fb3a9ad..5e11eaadd 100644 --- a/packages/core/src/parser/parameter/pagination/base.ts +++ b/packages/core/src/parser/parameter/pagination/base.ts @@ -5,14 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Pagination } from '../../../parameter'; +import type { IPagination } from '../../../parameter'; import { PaginationSchema, Schema, definePaginationSchema } from '../../../schema'; import type { ObjectLiteral } from '../../../types'; import { BaseParser } from '../../base'; export abstract class BasePaginationParser< OPTIONS extends ObjectLiteral = ObjectLiteral, -> extends BaseParser { +> extends BaseParser { protected resolveSchema< RECORD extends ObjectLiteral = ObjectLiteral, >(input?: string | Schema | PaginationSchema) : PaginationSchema { diff --git a/packages/core/src/parser/parameter/relations/base.ts b/packages/core/src/parser/parameter/relations/base.ts index 670e0ca65..621d3130e 100644 --- a/packages/core/src/parser/parameter/relations/base.ts +++ b/packages/core/src/parser/parameter/relations/base.ts @@ -5,14 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Relations } from '../../../parameter'; +import type { IRelations } from '../../../parameter'; import { RelationsSchema, Schema } from '../../../schema'; import type { ObjectLiteral } from '../../../types'; import { BaseParser } from '../../base'; export abstract class BaseRelationsParser< OPTIONS extends ObjectLiteral = ObjectLiteral, -> extends BaseParser { +> extends BaseParser { protected resolveSchema< RECORD extends ObjectLiteral = ObjectLiteral, >(input?: string | Schema | RelationsSchema) : RelationsSchema | undefined { diff --git a/packages/core/src/parser/parameter/sort/base.ts b/packages/core/src/parser/parameter/sort/base.ts index 12a2f955e..cfb05a0d6 100644 --- a/packages/core/src/parser/parameter/sort/base.ts +++ b/packages/core/src/parser/parameter/sort/base.ts @@ -5,14 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Sorts } from '../../../parameter'; +import type { ISorts } from '../../../parameter'; import { Schema, SortSchema, defineSortSchema } from '../../../schema'; import type { ObjectLiteral } from '../../../types'; import { BaseParser } from '../../base'; export abstract class BaseSortParser< OPTIONS extends ObjectLiteral = ObjectLiteral, -> extends BaseParser { +> extends BaseParser { protected resolveSchema< RECORD extends ObjectLiteral = ObjectLiteral, >(input?: string | Schema | SortSchema) : SortSchema { diff --git a/packages/parser-expression/src/module.ts b/packages/parser-expression/src/module.ts index 0863b584d..b34057d9e 100644 --- a/packages/parser-expression/src/module.ts +++ b/packages/parser-expression/src/module.ts @@ -6,15 +6,16 @@ */ import type { - Fields, - Filter, - Filters, + IFields, + IFilter, + IFilters, + IPagination, + IQuery, + IRelations, + ISorts, ObjectLiteral, - Pagination, ParseParameterOptions, - Relations, SchemaRegistry, - Sorts, } from '@rapiq/core'; import { BaseParser, @@ -34,7 +35,7 @@ import type { ParseOptions } from './types'; export class ExpressionParser extends BaseParser< ParseOptions, -Query +IQuery > { protected fieldsParser : ExpressionFieldsParser; @@ -65,7 +66,7 @@ Query >( input: unknown, options: ParseOptions = {}, - ): Query { + ): IQuery { const schema = this.getBaseSchema(options.schema); const output : Query = new Query(); @@ -79,7 +80,7 @@ Query }; if (!this.skipParameter(options.relations)) { - let relations: Relations | undefined; + let relations: IRelations | undefined; if (isPropertySet(input, Parameter.RELATIONS)) { // todo: parse parameter & url-parameter @@ -95,7 +96,7 @@ Query } if (!this.skipParameter(options.fields)) { - let fields : Fields | undefined; + let fields : IFields | undefined; if (isPropertySet(input, Parameter.FIELDS)) { // todo: parse parameter & url-parameter @@ -117,7 +118,7 @@ Query } if (!this.skipParameter(options.filters)) { - let filters : Filters | Filter | undefined; + let filters : IFilters | IFilter | undefined; if (isPropertySet(input, Parameter.FILTERS)) { // todo: parse parameter & url-parameter filters = this.parseFilters( @@ -138,7 +139,7 @@ Query } if (!this.skipParameter(options.pagination)) { - let pagination : Pagination | undefined; + let pagination : IPagination | undefined; if (isPropertySet(input, Parameter.PAGINATION)) { // todo: parse parameter & url-parameter @@ -153,7 +154,7 @@ Query } if (!this.skipParameter(options.sort)) { - let sort : Sorts | undefined; + let sort : ISorts | undefined; if (isPropertySet(input, Parameter.SORT)) { // todo: parse parameter & url-parameter @@ -187,7 +188,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ): Relations { + ): IRelations { return this.relationsParser.parse(input, options); } @@ -202,7 +203,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Fields { + ) : IFields { return this.fieldsParser.parse(input, options); } @@ -217,8 +218,8 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Filters | Filter { - return this.filtersParser.parse(input, options); + ) : IFilters | IFilter { + return this.filtersParser.parseExact(input, options); } /** @@ -232,7 +233,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Pagination { + ) : IPagination { return this.paginationParser.parse(input, options); } @@ -247,7 +248,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Sorts { + ) : ISorts { return this.sortParser.parse(input, options); } diff --git a/packages/parser-expression/src/parameter/filters/module.ts b/packages/parser-expression/src/parameter/filters/module.ts index a7ad737c3..61295f996 100644 --- a/packages/parser-expression/src/parameter/filters/module.ts +++ b/packages/parser-expression/src/parameter/filters/module.ts @@ -6,7 +6,13 @@ */ import type { - FiltersParseOptions, FiltersSchema, ObjectLiteral, Scalar, + Condition, + FiltersParseOptions, + FiltersSchema, + IFilter, + IFilters, + ObjectLiteral, + Scalar, } from '@rapiq/core'; import { BaseFiltersParser, @@ -37,7 +43,19 @@ FiltersParseOptions parse( input: unknown, options: FiltersParseOptions = {}, - ) : Filters | Filter { + ) : IFilters { + const expr = this.parseExact(input, options); + if (expr instanceof Filters) { + return expr; + } + + return new Filters(FilterCompoundOperator.AND, [expr as unknown as Condition]); + } + + parseExact( + input: unknown, + options: FiltersParseOptions = {}, + ) : IFilters | IFilter { if (typeof input !== 'string') { throw FiltersParseError.inputInvalid(); } diff --git a/packages/parser-expression/test/unit/parser/filters.spec.ts b/packages/parser-expression/test/unit/parser/filters.spec.ts index 807eb9723..6bf6bb0ad 100644 --- a/packages/parser-expression/test/unit/parser/filters.spec.ts +++ b/packages/parser-expression/test/unit/parser/filters.spec.ts @@ -20,49 +20,49 @@ describe('filters/expr-parser', () => { }); it('should parse eq expression', () => { - const output = parser.parse('eq(name, \'admin\')'); + const output = parser.parseExact('eq(name, \'admin\')'); expect(output).toEqual(new Filter(FilterFieldOperator.EQUAL, 'name', 'admin')); }); it('should parse not eq expression', () => { - const output = parser.parse('not(eq(name, \'admin\'))'); + const output = parser.parseExact('not(eq(name, \'admin\'))'); expect(output).toEqual(new Filter(FilterFieldOperator.NOT_EQUAL, 'name', 'admin')); }); it('should parse not not eq expression', () => { - const output = parser.parse('not(not(eq(name, \'admin\')))'); + const output = parser.parseExact('not(not(eq(name, \'admin\')))'); expect(output).toEqual(new Filter(FilterFieldOperator.EQUAL, 'name', 'admin')); }); it('should parse lt expression', () => { - const output = parser.parse('lt(age, \'18\')'); + const output = parser.parseExact('lt(age, \'18\')'); expect(output).toEqual(new Filter(FilterFieldOperator.LESS_THAN, 'age', 18)); }); it('should parse lte expression', () => { - const output = parser.parse('lte(age, \'18\')'); + const output = parser.parseExact('lte(age, \'18\')'); expect(output).toEqual(new Filter(FilterFieldOperator.LESS_THAN_EQUAL, 'age', 18)); }); it('should parse gt expression', () => { - const output = parser.parse('gt(age, \'18\')'); + const output = parser.parseExact('gt(age, \'18\')'); expect(output).toEqual(new Filter(FilterFieldOperator.GREATER_THAN, 'age', 18)); }); it('should parse gte expression', () => { - const output = parser.parse('gte(age, \'18\')'); + const output = parser.parseExact('gte(age, \'18\')'); expect(output).toEqual(new Filter(FilterFieldOperator.GREATER_THAN_EQUAL, 'age', 18)); }); it('should parse contains expression', () => { - const output = parser.parse('contains(name, \'Peter\')'); + const output = parser.parseExact('contains(name, \'Peter\')'); expect(output).toEqual(new Filter( FilterFieldOperator.CONTAINS, @@ -72,7 +72,7 @@ describe('filters/expr-parser', () => { }); it('should parse startsWith expression', () => { - const output = parser.parse('startsWith(name, \'Peter\')'); + const output = parser.parseExact('startsWith(name, \'Peter\')'); expect(output).toEqual(new Filter( FilterFieldOperator.STARTS_WITH, @@ -82,7 +82,7 @@ describe('filters/expr-parser', () => { }); it('should parse endsWith expression', () => { - const output = parser.parse('endsWith(name, \'Peter\')'); + const output = parser.parseExact('endsWith(name, \'Peter\')'); expect(output).toEqual(new Filter( FilterFieldOperator.ENDS_WITH, @@ -92,7 +92,7 @@ describe('filters/expr-parser', () => { }); it('should parse in expression', () => { - const output = parser.parse('in(name, \'Peter\', \'Hans\')'); + const output = parser.parseExact('in(name, \'Peter\', \'Hans\')'); expect(output).toEqual(new Filter( FilterFieldOperator.IN, @@ -105,7 +105,7 @@ describe('filters/expr-parser', () => { }); it('should parse negated in expression', () => { - const output = parser.parse('not(in(name, \'Peter\', \'Hans\'))'); + const output = parser.parseExact('not(in(name, \'Peter\', \'Hans\'))'); expect(output).toEqual(new Filter( FilterFieldOperator.NOT_IN, @@ -118,7 +118,7 @@ describe('filters/expr-parser', () => { }); it('should parse nin expression', () => { - const output = parser.parse('nin(name, \'Peter\', \'Hans\')'); + const output = parser.parseExact('nin(name, \'Peter\', \'Hans\')'); expect(output).toEqual(new Filter( FilterFieldOperator.NOT_IN, @@ -131,7 +131,7 @@ describe('filters/expr-parser', () => { }); it('should parse negated nin expression', () => { - const output = parser.parse('not(nin(name, \'Peter\', \'Hans\'))'); + const output = parser.parseExact('not(nin(name, \'Peter\', \'Hans\'))'); expect(output).toEqual(new Filter( FilterFieldOperator.IN, @@ -144,7 +144,7 @@ describe('filters/expr-parser', () => { }); it('should parse nested expression', () => { - const output = parser.parse('and(eq(user.friends, \'5\'), contains(user.name, \'Bob\'))'); + const output = parser.parseExact('and(eq(user.friends, \'5\'), contains(user.name, \'Bob\'))'); expect(output).toEqual(new Filters( FilterCompoundOperator.AND, @@ -164,7 +164,7 @@ describe('filters/expr-parser', () => { }); it('should negated nested expression', () => { - const output = parser.parse('not(and(eq(name, \'foo\'), lt(age, \'15\')))'); + const output = parser.parseExact('not(and(eq(name, \'foo\'), lt(age, \'15\')))'); expect(output).toEqual(new Filters( FilterCompoundOperator.OR, diff --git a/packages/parser-simple/src/module.ts b/packages/parser-simple/src/module.ts index 9012476a0..4c9cd53b3 100644 --- a/packages/parser-simple/src/module.ts +++ b/packages/parser-simple/src/module.ts @@ -6,15 +6,14 @@ */ import type { - - Fields, - Filters, + IFields, + IFilters, + IPagination, + IRelations, + ISorts, ObjectLiteral, - Pagination, ParseParameterOptions, - Relations, SchemaRegistry, - Sorts, } from '@rapiq/core'; import { BaseParser, Parameter, Query, isObject, @@ -77,7 +76,7 @@ Query }; if (!this.skipParameter(options.relations)) { - let relations: Relations | undefined; + let relations: IRelations | undefined; if (isPropertySet(input, Parameter.RELATIONS)) { // todo: parse parameter & url-parameter @@ -93,7 +92,7 @@ Query } if (!this.skipParameter(options.fields)) { - let fields : Fields | undefined; + let fields : IFields | undefined; if (isPropertySet(input, Parameter.FIELDS)) { // todo: parse parameter & url-parameter @@ -115,7 +114,7 @@ Query } if (!this.skipParameter(options.filters)) { - let filters : Filters | undefined; + let filters : IFilters | undefined; if (isPropertySet(input, Parameter.FILTERS)) { // todo: parse parameter & url-parameter filters = this.parseFilters( @@ -136,7 +135,7 @@ Query } if (!this.skipParameter(options.pagination)) { - let pagination : Pagination | undefined; + let pagination : IPagination | undefined; if (isPropertySet(input, Parameter.PAGINATION)) { // todo: parse parameter & url-parameter @@ -151,21 +150,21 @@ Query } if (!this.skipParameter(options.sort)) { - let sort : Sorts | undefined; + let sorts : ISorts | undefined; if (isPropertySet(input, Parameter.SORT)) { // todo: parse parameter & url-parameter - sort = this.parseSort( + sorts = this.parseSort( input[Parameter.SORT], parameterOptions, ); } else if (schema.sort.defaultKeys.length > 0) { // todo: this should be simplified - sort = this.parseSort(undefined, parameterOptions); + sorts = this.parseSort(undefined, parameterOptions); } - if (typeof sort !== 'undefined') { - output.sorts = sort; + if (typeof sorts !== 'undefined') { + output.sorts = sorts; } } @@ -185,7 +184,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ): Relations { + ): IRelations { return this.relationsParser.parse(input, options); } @@ -200,7 +199,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Fields { + ) : IFields { return this.fieldsParser.parse(input, options); } @@ -215,7 +214,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Filters { + ) : IFilters { return this.filtersParser.parse(input, options); } @@ -230,7 +229,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Pagination { + ) : IPagination { return this.paginationParser.parse(input, options); } @@ -245,7 +244,7 @@ Query >( input: unknown, options: ParseParameterOptions = {}, - ) : Sorts { + ) : ISorts { return this.sortParser.parse(input, options); } diff --git a/packages/parser-simple/src/parameter/fields/module.ts b/packages/parser-simple/src/parameter/fields/module.ts index 97392340d..fadd644c3 100644 --- a/packages/parser-simple/src/parameter/fields/module.ts +++ b/packages/parser-simple/src/parameter/fields/module.ts @@ -13,11 +13,12 @@ import { Fields, FieldsParseError, applyMapping, - groupArrayByKeyPath, - isObject, - isPathAllowed, + groupArrayByKeyPath, isObject, isPathAllowed, +} from '@rapiq/core'; +import type { + IFields, ObjectLiteral, + Relations, } from '@rapiq/core'; -import type { ObjectLiteral, Relations } from '@rapiq/core'; import type { SimpleFieldsParseOptions } from './types'; export class SimpleFieldsParser extends BaseFieldsParser { @@ -26,7 +27,7 @@ export class SimpleFieldsParser extends BaseFieldsParser( input: unknown, options: SimpleFieldsParseOptions = {}, - ) : Fields { + ) : IFields { const schema = this.resolveSchema(options.schema); // If it is an empty array, nothing is allowed @@ -152,10 +153,7 @@ export class SimpleFieldsParser extends BaseFieldsParser { - element.name = `${key}.${element.name}`; - return element; - }, + (element) => new Field(`${key}.${element.name}`, element.operator), )); } diff --git a/packages/parser-simple/src/parameter/filters/module.ts b/packages/parser-simple/src/parameter/filters/module.ts index 1935e6e08..d5bdc4806 100644 --- a/packages/parser-simple/src/parameter/filters/module.ts +++ b/packages/parser-simple/src/parameter/filters/module.ts @@ -16,14 +16,14 @@ import { applyMapping, isObject, isPathAllowed, - isPropertyNameValid, - parseKey, - stringifyKey, + isPropertyNameValid, parseKey, stringifyKey, } from '@rapiq/core'; import type { Condition, FiltersParseOptions, + IFilter, + IFilters, ObjectLiteral, Relations, Scalar, @@ -39,7 +39,7 @@ FiltersParseOptions protected run( input: unknown, options: FiltersParseOptions = {}, - ) : Condition[] { + ) : IFilter[] { const schema = this.resolveSchema(options.schema); const throwOnFailure = options.throwOnFailure ?? schema.throwOnFailure; @@ -94,7 +94,7 @@ FiltersParseOptions currentKey: string, data: TempType, options: FiltersParseOptions = {}, - ) : Filter[] { + ) : IFilter[] { const schema = this.resolveSchema(options.schema); const throwOnFailure = options.throwOnFailure ?? schema.throwOnFailure; @@ -102,7 +102,7 @@ FiltersParseOptions // todo: currentKey.value === DEFAULT_ID && empty data =>build defaults otherwise - const output : Filter[] = []; + const output : IFilter[] = []; let keys = Object.keys(data.attributes); for (let i = 0; i < keys.length; i++) { @@ -215,23 +215,23 @@ FiltersParseOptions parse( input: unknown, options: FiltersParseOptions = {}, - ) : Filters { + ) : IFilters { let items = this.run(input, { ...options, async: false, - }); + }) as unknown as Condition[]; if (items.length === 0) { items = this.buildDefaults(options); } - return new Filters(FilterCompoundOperator.AND, items); + return new Filters(FilterCompoundOperator.AND, items as unknown as Condition[]); } parseTyped( input: SimpleFiltersParserInput, options: FiltersParseOptions = {}, - ) : Filters { + ) : IFilters { return this.parse(input, options); } diff --git a/packages/sql/package.json b/packages/sql/package.json index 0f35f30de..58bc4ee4d 100644 --- a/packages/sql/package.json +++ b/packages/sql/package.json @@ -17,12 +17,10 @@ "dist/" ], "devDependencies": { - "@rapiq/core": "^1.0.0", - "@ucast/core": "^1.10.2" + "@rapiq/core": "^1.0.0" }, "peerDependencies": { - "@rapiq/core": "^1.0.0", - "@ucast/core": "^1.10.2" + "@rapiq/core": "^1.0.0" }, "scripts": { "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json", diff --git a/packages/sql/src/visitor/fields.ts b/packages/sql/src/visitor/fields.ts index 775d4db1b..0f52dbe57 100644 --- a/packages/sql/src/visitor/fields.ts +++ b/packages/sql/src/visitor/fields.ts @@ -6,7 +6,7 @@ */ import type { - Field, FieldOperator, Fields, IFieldVisitor, IFieldsVisitor, + FieldOperator, IField, IFieldVisitor, IFields, IFieldsVisitor, } from '@rapiq/core'; import type { IFieldsAdapter } from '../adapter'; import type { VisitorOptions } from './types'; @@ -27,13 +27,13 @@ IFieldVisitor { this.options = options; } - visitField(expr: Field): IFieldsAdapter { + visitField(expr: IField): IFieldsAdapter { this.adapter.add(expr.name, expr.operator as FieldOperator); return this.adapter; } - visitFields(expr: Fields): IFieldsAdapter { + visitFields(expr: IFields): IFieldsAdapter { for (let i = 0; i < expr.value.length; i++) { expr.value[i].accept(this); } From fd505f8a2b542e61934eeb47c3591222a296a497 Mon Sep 17 00:00:00 2001 From: tada5hi Date: Tue, 3 Feb 2026 15:02:19 +0100 Subject: [PATCH 2/3] feat: simplify filter(s) typing --- package-lock.json | 7 ++-- packages/core/package.json | 1 - .../src/parameter/filters/collection/check.ts | 22 ++++++++++++ .../src/parameter/filters/collection/index.ts | 2 ++ .../parameter/filters/collection/module.ts | 35 ++++++++----------- .../src/parameter/filters/collection/types.ts | 4 +-- .../core/src/parameter/filters/condition.ts | 18 ++++++++-- .../src/parameter/filters/record/module.ts | 20 ++++++----- packages/core/src/parameter/types.ts | 3 +- .../src/schema/parameter/filters/schema.ts | 8 ++--- .../src/schema/parameter/filters/types.ts | 6 ++-- .../src/parameter/filters/module.ts | 9 +++-- .../src/parameter/filters/module.ts | 14 ++++---- 13 files changed, 90 insertions(+), 59 deletions(-) create mode 100644 packages/core/src/parameter/filters/collection/check.ts diff --git a/package-lock.json b/package-lock.json index c9e68d411..4427e1ab8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18542,7 +18542,6 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@ucast/core": "^1.10.2", "pathtrace": "^1.1.0", "smob": "^1.5.0" }, @@ -18599,12 +18598,10 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@rapiq/core": "^1.0.0", - "@ucast/core": "^1.10.2" + "@rapiq/core": "^1.0.0" }, "peerDependencies": { - "@rapiq/core": "^1.0.0", - "@ucast/core": "^1.10.2" + "@rapiq/core": "^1.0.0" } }, "packages/typeorm": { diff --git a/packages/core/package.json b/packages/core/package.json index 0b62923c5..9ade09806 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,7 +58,6 @@ }, "homepage": "https://github.com/Tada5hi/rapiq#readme", "dependencies": { - "@ucast/core": "^1.10.2", "pathtrace": "^1.1.0", "smob": "^1.5.0" }, diff --git a/packages/core/src/parameter/filters/collection/check.ts b/packages/core/src/parameter/filters/collection/check.ts new file mode 100644 index 000000000..096824cfe --- /dev/null +++ b/packages/core/src/parameter/filters/collection/check.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026. + * Author Peter Placzek (tada5hi) + * For the full copyright and license information, + * view the LICENSE file that was distributed with this source code. + */ + +import type { ICondition } from '../condition'; +import type { IFilters } from './types'; + +export function isFilters( + input: ICondition, + operator?: string, +) : input is IFilters { + if (!Array.isArray(input)) return false; + + if (operator) { + return operator === input.operator; + } + + return true; +} diff --git a/packages/core/src/parameter/filters/collection/index.ts b/packages/core/src/parameter/filters/collection/index.ts index e2d407456..44231ff48 100644 --- a/packages/core/src/parameter/filters/collection/index.ts +++ b/packages/core/src/parameter/filters/collection/index.ts @@ -4,5 +4,7 @@ * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ + +export * from './check'; export * from './module'; export * from './types'; diff --git a/packages/core/src/parameter/filters/collection/module.ts b/packages/core/src/parameter/filters/collection/module.ts index 5c5bd6f7d..ef04c4ff8 100644 --- a/packages/core/src/parameter/filters/collection/module.ts +++ b/packages/core/src/parameter/filters/collection/module.ts @@ -5,14 +5,22 @@ * view the LICENSE file that was distributed with this source code. */ -import { CompoundCondition as BaseCompoundCondition } from '@ucast/core'; - -import type { Condition } from '../condition'; +import type { Condition, ICondition } from '../condition'; import type { IFilters, IFiltersVisitor } from './types'; +import { isFilters } from './check'; export class Filters< T extends Condition = Condition, -> extends BaseCompoundCondition implements IFilters { +> implements IFilters { + readonly value: T[]; + + readonly operator: string; + + constructor(operator: string, conditions: T[]) { + this.operator = operator; + this.value = conditions; + } + accept(visitor: IFiltersVisitor) : R { return visitor.visitFilters(this); } @@ -36,7 +44,7 @@ export class Filters< ); } - protected flattenInternal( + protected flattenInternal( conditions: F[], operator: string, aggregatedResult?: F[], @@ -46,7 +54,7 @@ export class Filters< for (let i = 0, { length } = conditions; i < length; i++) { const currentNode = conditions[i]; - if (Filters.check(currentNode, operator)) { + if (isFilters(currentNode, operator)) { currentNode.flatten(flatConditions); } else { flatConditions.push(currentNode); @@ -55,19 +63,4 @@ export class Filters< return flatConditions; } - - static check( - condition: Condition, - operator?: string, - ) : condition is Filters { - if (!(condition instanceof Filters)) { - return false; - } - - if (operator) { - return operator === condition.operator; - } - - return true; - } } diff --git a/packages/core/src/parameter/filters/collection/types.ts b/packages/core/src/parameter/filters/collection/types.ts index d01407a21..86ed4c11c 100644 --- a/packages/core/src/parameter/filters/collection/types.ts +++ b/packages/core/src/parameter/filters/collection/types.ts @@ -5,14 +5,14 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Condition } from '../condition'; +import type { ICondition } from '../condition'; export interface IFiltersVisitor { visitFilters(expr: IFilters): R; } export interface IFilters< - T extends Condition = Condition, + T extends ICondition = ICondition, > { readonly operator: string; diff --git a/packages/core/src/parameter/filters/condition.ts b/packages/core/src/parameter/filters/condition.ts index cf9c20904..5af6d3bac 100644 --- a/packages/core/src/parameter/filters/condition.ts +++ b/packages/core/src/parameter/filters/condition.ts @@ -5,9 +5,23 @@ * view the LICENSE file that was distributed with this source code. */ -import { Condition as BaseCondition } from '@ucast/core'; +export interface ICondition< + T = unknown, +> { + readonly operator: string; + readonly value: T; +} export class Condition< T = unknown, -> extends BaseCondition { +> { + readonly operator: string; + + readonly value: T; + + constructor(operator: string, value: T) { + this.operator = operator; + + this.value = value; + } } diff --git a/packages/core/src/parameter/filters/record/module.ts b/packages/core/src/parameter/filters/record/module.ts index 7be4a1f24..d93f4b808 100644 --- a/packages/core/src/parameter/filters/record/module.ts +++ b/packages/core/src/parameter/filters/record/module.ts @@ -5,21 +5,23 @@ * view the LICENSE file that was distributed with this source code. */ -import { FieldCondition as BaseFieldCondition } from '@ucast/core'; import { FilterFieldOperator } from '../../../schema'; import type { IFilter, IFilterVisitor } from './types'; export class Filter< OPERATOR extends string = `${FilterFieldOperator}`, VALUE = unknown, -> extends BaseFieldCondition implements IFilter { - // eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor - constructor( - operator: OPERATOR, - key: string, - value: VALUE, - ) { - super(operator, key, value); +> implements IFilter { + readonly operator: string; + + readonly value: VALUE; + + readonly field: string; + + constructor(operator: string, field: string, value: VALUE) { + this.operator = operator; + this.field = field; + this.value = value; } accept(visitor: IFilterVisitor) : R { diff --git a/packages/core/src/parameter/types.ts b/packages/core/src/parameter/types.ts index 9704b5a88..29a74ca6a 100644 --- a/packages/core/src/parameter/types.ts +++ b/packages/core/src/parameter/types.ts @@ -12,7 +12,6 @@ import type { IFilter, IFilters, } from './filters'; -import type { Query } from './module'; import type { IRelation, IRelations, @@ -32,7 +31,7 @@ export type QueryContext = { }; export interface IQueryVisitor { - visitQuery(expr: Query) : R; + visitQuery(expr: IQuery) : R; } export interface IQuery { diff --git a/packages/core/src/schema/parameter/filters/schema.ts b/packages/core/src/schema/parameter/filters/schema.ts index bb69468cc..c0ec74f61 100644 --- a/packages/core/src/schema/parameter/filters/schema.ts +++ b/packages/core/src/schema/parameter/filters/schema.ts @@ -5,7 +5,7 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Condition, Filter } from '../../../parameter'; +import type { ICondition, IFilter } from '../../../parameter'; import type { MaybeAsync, ObjectLiteral, SimpleKeys } from '../../../types'; import type { FiltersOptions, @@ -15,7 +15,7 @@ import { BaseSchema } from '../../base'; export class FiltersSchema< T extends ObjectLiteral = ObjectLiteral, > extends BaseSchema> { - public default : Condition | undefined; + public default : ICondition | undefined; public defaultIsUndefined : boolean; @@ -52,7 +52,7 @@ export class FiltersSchema< // --------------------------------------------------------- - validate(input: Filter) : MaybeAsync { + validate(input: IFilter) : MaybeAsync { if (typeof this.options.validate === 'undefined') { return input; } @@ -62,7 +62,7 @@ export class FiltersSchema< // --------------------------------------------------------- - setDefault(input?: Condition) { + setDefault(input?: ICondition) { this.default = input; this.defaultIsUndefined = !input; } diff --git a/packages/core/src/schema/parameter/filters/types.ts b/packages/core/src/schema/parameter/filters/types.ts index 0430aea31..0844f8e09 100644 --- a/packages/core/src/schema/parameter/filters/types.ts +++ b/packages/core/src/schema/parameter/filters/types.ts @@ -5,20 +5,20 @@ * view the LICENSE file that was distributed with this source code. */ -import type { Condition, Filter } from '../../../parameter'; +import type { ICondition, IFilter } from '../../../parameter'; import type { MaybeAsync, ObjectLiteral, SimpleKeys, } from '../../../types'; import type { BaseSchemaOptions } from '../../types'; -export type Validator = (input: Filter) => MaybeAsync; +export type Validator = (input: IFilter) => MaybeAsync; export type FiltersOptions< T extends ObjectLiteral = ObjectLiteral, > = BaseSchemaOptions & { mapping?: Record, allowed?: SimpleKeys[], - default?: Condition, + default?: ICondition, validate?: Validator }; diff --git a/packages/parser-expression/src/parameter/filters/module.ts b/packages/parser-expression/src/parameter/filters/module.ts index 61295f996..86ecdf5f5 100644 --- a/packages/parser-expression/src/parameter/filters/module.ts +++ b/packages/parser-expression/src/parameter/filters/module.ts @@ -6,7 +6,6 @@ */ import type { - Condition, FiltersParseOptions, FiltersSchema, IFilter, @@ -22,6 +21,7 @@ import { FilterFieldOperator, Filters, FiltersParseError, + isFilters, isPathAllowed, isPropertyNameValid, } from '@rapiq/core'; @@ -45,11 +45,14 @@ FiltersParseOptions options: FiltersParseOptions = {}, ) : IFilters { const expr = this.parseExact(input, options); - if (expr instanceof Filters) { + if ( + isFilters(expr, FilterCompoundOperator.AND) || + isFilters(expr, FilterCompoundOperator.OR) + ) { return expr; } - return new Filters(FilterCompoundOperator.AND, [expr as unknown as Condition]); + return new Filters(FilterCompoundOperator.AND, [expr]); } parseExact( diff --git a/packages/parser-simple/src/parameter/filters/module.ts b/packages/parser-simple/src/parameter/filters/module.ts index d5bdc4806..a6de2c17c 100644 --- a/packages/parser-simple/src/parameter/filters/module.ts +++ b/packages/parser-simple/src/parameter/filters/module.ts @@ -15,18 +15,18 @@ import { FiltersParseError, applyMapping, isObject, - isPathAllowed, - isPropertyNameValid, parseKey, stringifyKey, + isPathAllowed, isPropertyNameValid, parseKey, stringifyKey, } from '@rapiq/core'; import type { - Condition, FiltersParseOptions, + ICondition, IFilter, IFilters, ObjectLiteral, Relations, Scalar, + TempType, } from '@rapiq/core'; @@ -216,16 +216,16 @@ FiltersParseOptions input: unknown, options: FiltersParseOptions = {}, ) : IFilters { - let items = this.run(input, { + let items: ICondition[] = this.run(input, { ...options, async: false, - }) as unknown as Condition[]; + }); if (items.length === 0) { items = this.buildDefaults(options); } - return new Filters(FilterCompoundOperator.AND, items as unknown as Condition[]); + return new Filters(FilterCompoundOperator.AND, items); } parseTyped( @@ -239,7 +239,7 @@ FiltersParseOptions RECORD extends ObjectLiteral = ObjectLiteral, >( options: FiltersParseOptions = {}, - ) : Condition[] { + ) : ICondition[] { const schema = this.resolveSchema(options.schema); if (!schema.default) { return []; From f6258507ad235cd4057cb03146338edbbef34db6 Mon Sep 17 00:00:00 2001 From: tada5hi Date: Tue, 3 Feb 2026 15:08:23 +0100 Subject: [PATCH 3/3] feat: use class instead of concrete clazz impls --- .../codec-url-simple/src/encoder/module.ts | 29 +++++++------- .../src/encoder/visitors/module.ts | 39 ++++++++++--------- packages/codec-url-simple/src/types.ts | 4 +- packages/parser-expression/src/module.ts | 2 +- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/packages/codec-url-simple/src/encoder/module.ts b/packages/codec-url-simple/src/encoder/module.ts index f2fe980d2..bf3d6ec60 100644 --- a/packages/codec-url-simple/src/encoder/module.ts +++ b/packages/codec-url-simple/src/encoder/module.ts @@ -6,11 +6,14 @@ */ import type { - Field, - Fields, - Filter, - Filters, Pagination, - Query, Relations, Sorts, + IField, + IFields, + IFilter, + IFilters, + IPagination, + IQuery, + IRelations, + ISorts, } from '@rapiq/core'; import type { IEncoder } from '../types'; import type { ISerializer } from './serializer'; @@ -23,35 +26,35 @@ export class URLEncoder implements IEncoder { this.visitor = new QueryVisitor(); } - encode(input: Query): string | null { + encode(input: IQuery): string | null { return this.runSerializer(this.visitor.visitQuery(input)); } - encodeFields(input: Fields) { + encodeFields(input: IFields) { return this.runSerializer(this.visitor.visitFields(input)); } - encodeField(input: Field) { + encodeField(input: IField) { return this.runSerializer(this.visitor.visitField(input)); } - encodeFilters(input: Filters) { + encodeFilters(input: IFilters) { return this.runSerializer(this.visitor.visitFilters(input)); } - encodeFilter(input: Filter) { + encodeFilter(input: IFilter) { return this.runSerializer(this.visitor.visitFilter(input)); } - encodePagination(input: Pagination) { + encodePagination(input: IPagination) { return this.runSerializer(this.visitor.visitPagination(input)); } - encodeRelations(input: Relations) { + encodeRelations(input: IRelations) { return this.runSerializer(this.visitor.visitRelations(input)); } - encodeSort(input: Sorts) { + encodeSort(input: ISorts) { return this.runSerializer(this.visitor.visitSorts(input)); } diff --git a/packages/codec-url-simple/src/encoder/visitors/module.ts b/packages/codec-url-simple/src/encoder/visitors/module.ts index 69f4e0084..d0194d205 100644 --- a/packages/codec-url-simple/src/encoder/visitors/module.ts +++ b/packages/codec-url-simple/src/encoder/visitors/module.ts @@ -6,25 +6,26 @@ */ import type { - Field, - Fields, - Filter, - Filters, + IField, IFieldVisitor, + IFields, IFieldsVisitor, + IFilter, IFilterVisitor, + IFilters, IFiltersVisitor, + IPagination, IPaginationVisitor, + IQuery, IQueryVisitor, + IRelation, IRelationVisitor, + IRelations, IRelationsVisitor, + ISort, ISortVisitor, + ISorts, ISortsVisitor, - Pagination, - Query, - Relation, - Relations, - Sort, Sorts, } from '@rapiq/core'; import type { ArraySerializer, RecordArraySerializer, RecordSerializer } from '../serializer'; import { QuerySerializer } from '../serializer'; @@ -68,7 +69,7 @@ export class QueryVisitor implements IQueryVisitor, this.sort = new SortsVisitor(serializer.sort); } - visitQuery(expr: Query): QuerySerializer { + visitQuery(expr: IQuery): QuerySerializer { if (expr.fields) { expr.fields.accept(this.fields); } @@ -92,39 +93,39 @@ export class QueryVisitor implements IQueryVisitor, return this.serializer; } - visitFields(expr: Fields): RecordArraySerializer { + visitFields(expr: IFields): RecordArraySerializer { return expr.accept(this.fields); } - visitField(expr: Field): RecordArraySerializer { + visitField(expr: IField): RecordArraySerializer { return expr.accept(this.fields); } - visitFilter(expr: Filter): RecordSerializer { + visitFilter(expr: IFilter): RecordSerializer { return expr.accept(this.filters); } - visitFilters(expr: Filters): RecordSerializer { + visitFilters(expr: IFilters): RecordSerializer { return expr.accept(this.filters); } - visitPagination(expr: Pagination): RecordSerializer { + visitPagination(expr: IPagination): RecordSerializer { return expr.accept(this.pagination); } - visitRelation(expr: Relation): ArraySerializer { + visitRelation(expr: IRelation): ArraySerializer { return expr.accept(this.relations); } - visitRelations(expr: Relations): ArraySerializer { + visitRelations(expr: IRelations): ArraySerializer { return expr.accept(this.relations); } - visitSort(expr: Sort): ArraySerializer { + visitSort(expr: ISort): ArraySerializer { return expr.accept(this.sort); } - visitSorts(expr: Sorts): ArraySerializer { + visitSorts(expr: ISorts): ArraySerializer { return expr.accept(this.sort); } } diff --git a/packages/codec-url-simple/src/types.ts b/packages/codec-url-simple/src/types.ts index 4b7146a77..f13d7865b 100644 --- a/packages/codec-url-simple/src/types.ts +++ b/packages/codec-url-simple/src/types.ts @@ -5,11 +5,11 @@ * view the LICENSE file that was distributed with this source code. */ -import type { ObjectLiteral, Query } from '@rapiq/core'; +import type { IQuery, ObjectLiteral } from '@rapiq/core'; export interface IEncoder< OUTPUT = any, OPTIONS extends ObjectLiteral = ObjectLiteral, > { - encode(input: Query, options?: Partial) : OUTPUT + encode(input: IQuery, options?: Partial) : OUTPUT } diff --git a/packages/parser-expression/src/module.ts b/packages/parser-expression/src/module.ts index b34057d9e..c485fd2a4 100644 --- a/packages/parser-expression/src/module.ts +++ b/packages/parser-expression/src/module.ts @@ -69,7 +69,7 @@ IQuery ): IQuery { const schema = this.getBaseSchema(options.schema); - const output : Query = new Query(); + const output = new Query(); if (!isObject(input)) { return output;