Skip to content

Commit 56e94c5

Browse files
committed
feat(query): rebase shared query base on main
1 parent 293a087 commit 56e94c5

19 files changed

Lines changed: 792 additions & 250 deletions

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,19 @@ export { AggregationQuery, Reducers } from './query/aggregation.js';
4848
export type { AggregateCommand, SortSpec, LoadField, Reducer } from './query/aggregation.js';
4949
export { Tag, Num, Text, Geo, GeoRadius, Timestamp, FilterExpression } from './query/filter.js';
5050
export type { Inclusive, GeoUnit } from './query/filter.js';
51+
export { BaseQuery, BaseVectorQuery } from './query/base.js';
5152
export type {
52-
BaseQuery,
53+
BaseQueryConfig,
54+
BaseVectorQueryConfig,
55+
ReturnFieldsOptions,
5356
SearchResult,
5457
SearchDocument,
5558
QueryOptions,
5659
FilterInput,
5760
HybridSearchResult,
61+
SortByOptions,
62+
SortDirection,
63+
SortField,
5864
} from './query/base.js';
5965

6066
// Error exports

src/indexes/search-index.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,9 @@ export class SearchIndex {
532532
* Execute a search query against the index
533533
*
534534
* @param query - Query object (VectorQuery, FilterQuery, etc.)
535-
* @param options - Optional query execution options
535+
* @param options - Optional query execution options. If both query-level
536+
* `query.sortBy(...)` and `options.sortBy` are supplied, `options.sortBy`
537+
* takes precedence to preserve the existing `search()` API.
536538
* @returns Search results with documents and scores
537539
*
538540
* @example
@@ -600,13 +602,26 @@ export class SearchIndex {
600602
}
601603

602604
// Add pagination
603-
if (query.limit !== undefined || query.offset !== undefined) {
604-
const offset = query.offset ?? 0;
605-
const limit = query.limit ?? 10;
605+
const queryLimit = query.getLimit();
606+
const queryOffset = query.getOffset();
607+
if (queryLimit !== undefined || queryOffset !== undefined) {
608+
const offset = queryOffset ?? 0;
609+
const limit = queryLimit ?? 10;
606610
searchOptions.LIMIT = { from: offset, size: limit };
607611
}
608612

609-
// Add sorting if specified
613+
// Add sorting if specified on the query. FT.SEARCH accepts one
614+
// SORTBY clause, so use the first collected sort field.
615+
if (query.sortFields.length > 0) {
616+
const [sortField] = query.sortFields;
617+
searchOptions.SORTBY = {
618+
BY: sortField.field,
619+
DIRECTION: sortField.direction,
620+
};
621+
}
622+
623+
// Add sorting if specified in execution options. These options
624+
// preserve the historical API and override query-level sorting.
610625
if (options?.sortBy) {
611626
searchOptions.SORTBY = options.sortBy;
612627
if (options.sortOrder) {

src/query/aggregation.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import type { FtAggregateOptions } from '@redis/search/dist/lib/commands/AGGREGATE.js';
1313
import { QueryValidationError } from '../errors.js';
14-
import { renderFilter, type FilterInput } from './base.js';
14+
import { BaseQuery, renderFilter, type FilterInput, type SortByOptions } from './base.js';
1515

1616
/** Output of {@link AggregationQuery.toCommand}. */
1717
export interface AggregateCommand {
@@ -224,8 +224,7 @@ function assertPositiveInteger(value: number | undefined, label: string): void {
224224
* const { total, results } = await index.aggregate(q);
225225
* ```
226226
*/
227-
export class AggregationQuery {
228-
private readonly _query: string;
227+
export class AggregationQuery extends BaseQuery {
229228
private readonly steps: Step[] = [];
230229
private _load?: LoadField[];
231230
private _params?: Record<string, string | number>;
@@ -240,7 +239,7 @@ export class AggregationQuery {
240239
* the rest of the query DSL uses.
241240
*/
242241
constructor(query?: FilterInput) {
243-
this._query = renderFilter(query);
242+
super({ filter: query });
244243
}
245244

246245
/**
@@ -279,8 +278,16 @@ export class AggregationQuery {
279278
}
280279

281280
/** SORTBY one or more fields. Bare strings sort ASC. */
282-
sortBy(by: SortSpec | SortSpec[], max?: number): this {
283-
const list = Array.isArray(by) ? by : [by];
281+
sortBy(field: string, options?: SortByOptions): this;
282+
sortBy(by: SortSpec | SortSpec[], max?: number): this;
283+
sortBy(by: SortSpec | SortSpec[], maxOrOptions?: number | SortByOptions): this {
284+
const list =
285+
typeof maxOrOptions === 'object' && !Array.isArray(by) && typeof by === 'string'
286+
? [{ field: by, direction: maxOrOptions.direction }]
287+
: Array.isArray(by)
288+
? by
289+
: [by];
290+
const max = typeof maxOrOptions === 'number' ? maxOrOptions : undefined;
284291
if (list.length === 0) {
285292
throw new QueryValidationError('sortBy requires at least one field');
286293
}
@@ -378,7 +385,12 @@ export class AggregationQuery {
378385

379386
/** The rendered query string this aggregation will use. */
380387
get query(): string {
381-
return this._query;
388+
return this.buildQuery();
389+
}
390+
391+
/** Build the FT.AGGREGATE query string. */
392+
buildQuery(): string {
393+
return renderFilter(this.queryFilter);
382394
}
383395

384396
/** Build the structured options for `client.ft.aggregate(indexName, query, options)`. */
@@ -395,12 +407,13 @@ export class AggregationQuery {
395407
options.PARAMS = this._params;
396408
}
397409

398-
if (this._load && this._load.length > 0) {
410+
const loadFields = [...(this.returnFields ?? []), ...(this._load ?? [])];
411+
if (loadFields.length > 0) {
399412
// The Redis client types LOAD entries with template-literal types
400413
// (`@${string}` / `$.${string}`). We've already enforced that
401414
// contract via prefixFieldRef, but TS can't see through the
402415
// string return — cast at the boundary.
403-
options.LOAD = this._load.map((f) =>
416+
options.LOAD = loadFields.map((f) =>
404417
typeof f === 'string'
405418
? prefixFieldRef(f)
406419
: f.as !== undefined
@@ -415,7 +428,7 @@ export class AggregationQuery {
415428
) as FtAggregateOptions['STEPS'];
416429
}
417430

418-
return { query: this._query, options };
431+
return { query: this.buildQuery(), options };
419432
}
420433

421434
private renderStep(step: Step): unknown {

0 commit comments

Comments
 (0)