1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-11-10 01:19:53 +01:00
unleash.unleash/src/lib/features/feature-search/search-utils.ts
Gastón Fournier abe160eb7d
feat: Unleash v7 ESM migration (#9877)
We're migrating to ESM, which will allow us to import the latest
versions of our dependencies.

Co-Authored-By: Christopher Kolstad <chriswk@getunleash.io>
2025-05-14 09:47:12 +02:00

125 lines
3.6 KiB
TypeScript

import type { Knex } from 'knex';
import type {
IQueryOperator,
IQueryParam,
} from '../feature-toggle/types/feature-toggle-strategies-store-type.js';
export interface NormalizeParamsDefaults {
limitDefault: number;
maxLimit?: number; // Optional because you might not always want to enforce a max limit
typeDefault?: string; // Optional field for type, not required for every call
}
export type SearchParams = {
query?: string;
offset?: string | number;
limit?: string | number;
sortOrder?: 'asc' | 'desc';
};
export type NormalizedSearchParams = {
normalizedQuery?: string[];
normalizedLimit: number;
normalizedOffset: number;
normalizedSortOrder: 'asc' | 'desc';
};
export const applySearchFilters = (
qb: Knex.QueryBuilder,
searchParams: string[] | undefined,
columns: string[],
): void => {
const hasSearchParams = searchParams?.length;
if (hasSearchParams) {
const sqlParameters = searchParams.map((item) => `%${item}%`);
const sqlQueryParameters = sqlParameters.map(() => '?').join(',');
qb.where((builder) => {
columns.forEach((column) => {
builder.orWhereRaw(
`(${column}) ILIKE ANY (ARRAY[${sqlQueryParameters}])`,
sqlParameters,
);
});
});
}
};
export const applyGenericQueryParams = (
query: Knex.QueryBuilder,
queryParams: IQueryParam[],
): void => {
queryParams.forEach((param) => {
const isSingleParam = param.values.length === 1;
switch (param.operator) {
case 'IS':
case 'IS_ANY_OF':
query.whereIn(param.field, param.values);
break;
case 'IS_NOT':
case 'IS_NONE_OF':
if (isSingleParam) {
query.whereNot(param.field, param.values[0]);
} else {
query.whereNotIn(param.field, param.values);
}
break;
case 'IS_BEFORE':
query.where(param.field, '<', param.values[0]);
break;
case 'IS_ON_OR_AFTER':
query.where(param.field, '>=', param.values[0]);
break;
}
});
};
export const normalizeQueryParams = (
params: SearchParams,
defaults: NormalizeParamsDefaults,
): NormalizedSearchParams => {
const { query, offset, limit = defaults.limitDefault, sortOrder } = params;
const normalizedQuery = query
?.split(',')
.map((query) => query.trim())
.filter((query) => query);
const maxLimit = defaults.maxLimit || 1000;
const normalizedLimit =
Number(limit) > 0 && Number(limit) <= maxLimit
? Number(limit)
: defaults.limitDefault;
const normalizedOffset = Number(offset) > 0 ? Number(offset) : 0;
const normalizedSortOrder =
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
return {
normalizedQuery,
normalizedLimit,
normalizedOffset,
normalizedSortOrder,
};
};
export const parseSearchOperatorValue = (
field: string,
value: string,
): IQueryParam | null => {
const pattern =
/^(IS|IS_NOT|IS_ANY_OF|IS_NONE_OF|INCLUDE|DO_NOT_INCLUDE|INCLUDE_ALL_OF|INCLUDE_ANY_OF|EXCLUDE_IF_ANY_OF|EXCLUDE_ALL|IS_BEFORE|IS_ON_OR_AFTER):(.+)$/;
const match = value.match(pattern);
if (match) {
return {
field,
operator: match[1] as IQueryOperator,
values: match[2].split(',').map((value) => value.trim()),
};
}
return null;
};