odata-builder
Version:
odata builder for easier and typesafe usage
252 lines (241 loc) • 12 kB
TypeScript
type PrevDepth$1<T extends number> = [never, 0, 1, 2, 3, 4, 5][T];
type StringFunctionDefinition<T> = {
readonly type: 'concat';
readonly values: ReadonlyArray<string | FieldReference<T, string>>;
} | {
readonly type: 'contains';
readonly value: string | FieldReference<T, string>;
} | {
readonly type: 'endswith';
readonly value: string | FieldReference<T, string>;
} | {
readonly type: 'indexof';
readonly value: string | FieldReference<T, string>;
} | {
readonly type: 'length';
} | {
readonly type: 'startswith';
readonly value: string | FieldReference<T, string>;
} | {
readonly type: 'substring';
readonly start: number | FieldReference<T, number>;
readonly length?: number | FieldReference<T, number>;
} | {
readonly type: 'tolower';
} | {
readonly type: 'toupper';
} | {
readonly type: 'trim';
};
type ArithmeticOperator = 'add' | 'sub' | 'mul' | 'div' | 'mod';
type ArithmeticFunctionDefinition<T> = {
readonly type: ArithmeticOperator;
readonly operand: number | FieldReference<T, number>;
} | {
readonly type: 'round' | 'floor' | 'ceiling';
};
type DateFunctionDefinition<T> = {
readonly type: 'now';
} | {
readonly type: 'date';
readonly field: FieldReference<T, Date>;
} | {
readonly type: 'time';
readonly field: FieldReference<T, Date>;
} | {
readonly type: 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
};
type AnySupportedFunction<T> = StringFunctionDefinition<T> | ArithmeticFunctionDefinition<T> | DateFunctionDefinition<T>;
type ODataDateStringLiteral = string & {
__brand: 'ODataDateStringLiteral';
};
type ODataTimeStringLiteral = string & {
__brand: 'ODataTimeStringLiteral';
};
type FunctionReturnType<F extends AnySupportedFunction<any>> = F extends {
type: 'length' | 'indexof';
} ? number : F extends {
type: 'contains' | 'startswith' | 'endswith';
} ? boolean : F extends {
type: 'tolower' | 'toupper' | 'trim' | 'substring' | 'concat';
} ? string : F extends {
type: 'round' | 'floor' | 'ceiling';
} ? number : F extends {
type: ArithmeticOperator;
} ? number : F extends {
type: 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
} ? number : F extends {
type: 'now';
} ? Date : F extends {
type: 'date';
} ? ODataDateStringLiteral : F extends {
type: 'time';
} ? ODataTimeStringLiteral : never;
type GeneralFilterOperators = 'eq' | 'ne';
type ComparisonOperators = 'gt' | 'ge' | 'lt' | 'le';
type StringAsFunctionOperators = 'contains' | 'startswith' | 'endswith' | 'substringof' | 'indexof';
type StandardStringFilterOperators = GeneralFilterOperators | StringAsFunctionOperators;
type ComparableFilterOperators = GeneralFilterOperators | ComparisonOperators;
type BooleanFilterOperators = GeneralFilterOperators;
type GuidFilterOperators = GeneralFilterOperators;
type FilterOperators<V> = V extends ODataDateStringLiteral | ODataTimeStringLiteral ? ComparableFilterOperators : V extends Date ? ComparableFilterOperators : V extends number ? ComparableFilterOperators : V extends Guid ? GuidFilterOperators : V extends string ? StandardStringFilterOperators : V extends boolean ? BooleanFilterOperators : GeneralFilterOperators;
interface BaseFilterCore<T, VFieldType> {
readonly field: FilterFields<T, VFieldType>;
readonly transform?: ReadonlyArray<PropertyTransform<VFieldType>>;
}
type StandardFilter<T, VFieldType> = BaseFilterCore<T, VFieldType> & ({
readonly function?: undefined;
readonly operator: FilterOperators<VFieldType>;
readonly value: VFieldType | null;
readonly ignoreCase?: VFieldType extends string ? boolean : never;
readonly removeQuotes?: VFieldType extends string | Guid ? boolean : never;
} | (VFieldType extends string ? {
readonly function: StringFunctionDefinition<T>;
readonly operator: FilterOperators<FunctionReturnType<StringFunctionDefinition<T>>>;
readonly value: FunctionReturnType<StringFunctionDefinition<T>> | null;
readonly ignoreCase?: never;
readonly removeQuotes?: FunctionReturnType<StringFunctionDefinition<T>> extends string ? boolean : never;
} : never) | (VFieldType extends number ? {
readonly function: ArithmeticFunctionDefinition<T>;
readonly operator: FilterOperators<FunctionReturnType<ArithmeticFunctionDefinition<T>>>;
readonly value: FunctionReturnType<ArithmeticFunctionDefinition<T>> | null;
readonly ignoreCase?: never;
readonly removeQuotes?: never;
} : never) | (VFieldType extends Date ? {
readonly function: DateFunctionDefinition<T>;
readonly operator: FilterOperators<FunctionReturnType<DateFunctionDefinition<T>>>;
readonly value: FunctionReturnType<DateFunctionDefinition<T>> | null;
readonly ignoreCase?: never;
readonly removeQuotes?: FunctionReturnType<DateFunctionDefinition<T>> extends string ? boolean : never;
} : never));
type DirectBooleanODataFunctionFilter<T, VFieldType extends string> = BaseFilterCore<T, VFieldType> & {
readonly function: Extract<StringFunctionDefinition<T>, {
type: 'contains' | 'startswith' | 'endswith';
}>;
readonly operator?: BooleanFilterOperators;
readonly value?: boolean | null;
readonly ignoreCase?: never;
readonly removeQuotes?: never;
};
type ComparisonFunctionFilter_Internal<T, VFieldType, F extends AnySupportedFunction<T>, FRT> = BaseFilterCore<T, VFieldType> & {
readonly function: F;
readonly operator: FilterOperators<FRT>;
readonly value: (FRT extends ODataDateStringLiteral | ODataTimeStringLiteral ? string : FRT) | null;
readonly ignoreCase?: never;
readonly removeQuotes?: FRT extends ODataDateStringLiteral | ODataTimeStringLiteral | string ? boolean : never;
};
type ComparisonFunctionFilterGenerator<T, VFieldType> = {
[FuncKey in AnySupportedFunction<T>['type']]: FuncKey extends 'contains' | 'startswith' | 'endswith' ? never : ComparisonFunctionFilter_Internal<T, VFieldType, Extract<AnySupportedFunction<T>, {
type: FuncKey;
}>, FunctionReturnType<Extract<AnySupportedFunction<T>, {
type: FuncKey;
}>>>;
}[AnySupportedFunction<T>['type']];
type StringQueryFilter<T> = StandardFilter<T, string> | DirectBooleanODataFunctionFilter<T, string> | Extract<ComparisonFunctionFilterGenerator<T, string>, {
function: StringFunctionDefinition<T>;
}>;
type NumberQueryFilter<T> = StandardFilter<T, number> | Extract<ComparisonFunctionFilterGenerator<T, number>, {
function: ArithmeticFunctionDefinition<T>;
}>;
type DateQueryFilter<T> = StandardFilter<T, Date> | Extract<ComparisonFunctionFilterGenerator<T, Date>, {
function: DateFunctionDefinition<T>;
}>;
type GuidQueryFilter<T> = StandardFilter<T, Guid>;
type BooleanQueryFilter<T> = StandardFilter<T, boolean>;
type QueryFilter<T> = StringQueryFilter<T> | NumberQueryFilter<T> | DateQueryFilter<T> | GuidQueryFilter<T> | BooleanQueryFilter<T> | LambdaFilter<T>;
type PropertyTransform<V> = V extends string ? StringTransform : V extends number ? NumberTransform : V extends Date ? DateTransform : V extends Guid ? GuidTransform : never;
type LambdaFilter<T> = {
readonly [K in ArrayFields<T>]: {
readonly field: K;
readonly lambdaOperator: 'any' | 'all';
readonly expression: QueryFilter<ArrayElement<T, K>> | CombinedFilter<ArrayElement<T, K>>;
};
}[ArrayFields<T>];
type FieldReference<T, V extends string | number | Date | boolean> = {
readonly fieldReference: FilterFields<T, V>;
};
type ArrayFields<T> = {
[K in keyof T]-?: NonNullable<T[K]> extends ReadonlyArray<unknown> ? K : never;
}[keyof T];
type ArrayElement<T, K extends ArrayFields<T>> = NonNullable<T[K]> extends ReadonlyArray<infer U> ? U : never;
type Join<K, P> = K extends string | number ? P extends string | number ? P extends '' ? `${K}` : K extends '' ? `${P}` : `${K}/${P}` : never : never;
type Paths<T, VALUETYPE, D extends number = 5> = D extends 0 ? never : {
[K in keyof T]-?: K extends string ? NonNullable<T[K]> extends VALUETYPE ? K : NonNullable<T[K]> extends ReadonlyArray<VALUETYPE> ? K : NonNullable<T[K]> extends ReadonlyArray<infer U> ? U extends object ? VALUETYPE extends U ? K : never : never : NonNullable<T[K]> extends object ? VALUETYPE extends NonNullable<T[K]> ? K : Join<K, Paths<NonNullable<T[K]>, VALUETYPE, PrevDepth$1<D>>> : never : never;
}[keyof T];
type FilterFields<T, VALUETYPE, Depth extends number = 5> = Paths<T, VALUETYPE, Depth>;
type LambdaFilterFields<T, VALUETYPE> = {
[K in Extract<keyof T, string>]: NonNullable<T[K]> extends ReadonlyArray<infer U_ITEM> ? U_ITEM extends object ? FilterFields<U_ITEM, VALUETYPE> : never : never;
}[Extract<keyof T, string>];
type StringTransform = 'tolower' | 'toupper' | 'trim';
type DateTransform = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
type NumberTransform = 'round' | 'floor' | 'ceiling';
type GuidTransform = 'tolower';
interface CombinedFilter<T> {
logic: 'and' | 'or';
filters: Array<QueryFilter<T> | CombinedFilter<T>>;
}
type ExpandFields<T, Depth extends number = 5> = Depth extends 0 ? never : {
[K in Extract<keyof T, string>]: NonNullable<T[K]> extends object ? HasKeys<NonNullable<T[K]>> extends true ? K | (Depth extends 1 ? never : `${K}/${ExpandFields<NonNullable<T[K]>, PrevDepth<Depth>>}`) : never : never;
}[Extract<keyof T, string>];
type Guid = string & {
_type: Guid;
};
type HasKeys<T> = [keyof T] extends [never] ? false : true;
type PrevDepth<T extends number> = [
never,
0,
1,
2,
3,
4
][T];
interface OrderByDescriptor<T> {
field: OrderByFields<T>;
orderDirection: 'asc' | 'desc';
}
type OrderByFields<T, Depth extends number = 5> = [Depth] extends [never] ? never : {
[K in Extract<keyof T, string>]-?: T[K] extends Record<string, unknown> ? K | (string extends OrderByFields<T[K], PrevDepth<Depth>> ? never : `${K}/${OrderByFields<T[K], PrevDepth<Depth>> & string}`) : K;
}[Extract<keyof T, string>];
type SearchTerm = string & {
readonly __brand: 'SearchTerm';
};
interface SearchPhrase {
readonly phrase: string;
}
type SearchOperator = 'AND' | 'OR' | 'NOT';
interface SearchGroup {
readonly expression: SearchExpression;
}
type SearchExpressionPart = SearchTerm | SearchPhrase | SearchOperator | SearchGroup;
type SearchExpression = readonly SearchExpressionPart[];
declare class SearchExpressionBuilder {
private readonly parts;
constructor(parts?: SearchExpression);
term(value: string): SearchExpressionBuilder;
phrase(value: string): SearchExpressionBuilder;
and(): SearchExpressionBuilder;
or(): SearchExpressionBuilder;
not(expressionBuilder: SearchExpressionBuilder): SearchExpressionBuilder;
group(builder: SearchExpressionBuilder): SearchExpressionBuilder;
build(): SearchExpression;
toString(): string;
equals(other: SearchExpressionBuilder): boolean;
private stringifyPart;
}
declare class OdataQueryBuilder<T> {
private queryComponents;
top(topCount: number): this;
skip(skipCount: number): this;
select(...selectProps: ReadonlyArray<Extract<keyof Required<T>, string>>): this;
filter(...filters: ReadonlyArray<CombinedFilter<Required<T>> | QueryFilter<Required<T>>>): this;
expand(...expandFields: ReadonlyArray<ExpandFields<Required<T>>>): this;
count(countEntities?: boolean): this;
orderBy(...orderByInput: ReadonlyArray<OrderByDescriptor<Required<T>> | null | undefined>): this;
search(searchExpression: string | SearchExpressionBuilder | null | undefined): this;
toQuery(): string;
private addComponent;
}
declare const isCombinedFilter: <T>(filters: unknown) => filters is CombinedFilter<T>;
declare const isQueryFilter: <T>(filter: unknown) => filter is QueryFilter<T>;
export { OdataQueryBuilder, SearchExpressionBuilder, isCombinedFilter, isQueryFilter };
export type { CombinedFilter, ExpandFields, FilterFields, FilterOperators, Guid, LambdaFilterFields, OrderByDescriptor, OrderByFields, QueryFilter, SearchExpression, SearchPhrase, SearchTerm };