Skip to content

Commit 1a1f07f

Browse files
committed
feat: add sort parameter support to documents API
1 parent 3ae56b3 commit 1a1f07f

File tree

4 files changed

+102
-7
lines changed

4 files changed

+102
-7
lines changed

.code-samples.meilisearch.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ create_an_index_1: |-
3131
client.createIndex('movies', { primaryKey: 'id' })
3232
update_an_index_1: |-
3333
client.updateIndex('movies', { primaryKey: 'id' })
34-
rename_an_index_1: |-
35-
client.updateIndex('INDEX_A', { indexUid: 'INDEX_B' })
3634
delete_an_index_1: |-
3735
client.deleteIndex('movies')
3836
swap_indexes_1: |-
@@ -55,6 +53,14 @@ get_documents_post_1: |-
5553
fields: ['title', 'genres', 'rating', 'language'],
5654
limit: 3
5755
})
56+
get_documents_sort_1: |-
57+
client.index('movies').getDocuments({
58+
sort: ['release_date:desc']
59+
})
60+
get_documents_sort_multiple_1: |-
61+
client.index('movies').getDocuments({
62+
sort: ['rating:desc', 'release_date:asc']
63+
})
5864
add_or_replace_documents_1: |-
5965
client.index('movies').addDocuments([{
6066
id: 287947,

src/indexes.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,24 +298,35 @@ export class Index<T extends RecordAny = RecordAny> {
298298
* Get documents of an index.
299299
*
300300
* @param params - Parameters to browse the documents. Parameters can contain
301-
* the `filter` field only available in Meilisearch v1.2 and newer
301+
* the `filter` field only available in Meilisearch v1.2 and newer, and the
302+
* `sort` field available in Meilisearch v1.16 and newer
302303
* @returns Promise containing the returned documents
303304
*/
304305
async getDocuments<D extends RecordAny = T>(
305306
params?: DocumentsQuery<D>,
306307
): Promise<ResourceResults<D[]>> {
307308
const relativeBaseURL = `indexes/${this.uid}/documents`;
309+
// Create a shallow copy so we can safely normalize parameters
310+
const normalizedParams = params ? { ...params } : undefined;
311+
// Omit empty sort arrays to avoid server-side validation errors
312+
if (
313+
normalizedParams &&
314+
Array.isArray(normalizedParams.sort) &&
315+
normalizedParams.sort.length === 0
316+
) {
317+
delete (normalizedParams as { sort?: string[] }).sort;
318+
}
308319

309-
return params?.filter !== undefined
320+
return normalizedParams?.filter !== undefined
310321
? // In case `filter` is provided, use `POST /documents/fetch`
311322
await this.httpRequest.post<ResourceResults<D[]>>({
312323
path: `${relativeBaseURL}/fetch`,
313-
body: params,
324+
body: normalizedParams,
314325
})
315326
: // Else use `GET /documents` method
316327
await this.httpRequest.get<ResourceResults<D[]>>({
317328
path: relativeBaseURL,
318-
params,
329+
params: normalizedParams,
319330
});
320331
}
321332

src/types/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export type ResultsWrapper<T> = {
141141

142142
export type IndexOptions = {
143143
primaryKey?: string;
144-
indexUid?: string;
144+
uid?: string;
145145
};
146146

147147
export type IndexObject = {
@@ -510,6 +510,12 @@ export type DocumentsQuery<T = RecordAny> = ResourceQuery & {
510510
limit?: number;
511511
offset?: number;
512512
retrieveVectors?: boolean;
513+
/**
514+
* Array of strings containing the attributes to sort on. Each string should
515+
* be in the format "attribute:direction" where direction is either "asc" or
516+
* "desc". Example: ["price:asc", "rating:desc"]
517+
*/
518+
sort?: string[];
513519
};
514520

515521
export type DocumentQuery<T = RecordAny> = {

tests/documents.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,78 @@ describe("Documents tests", () => {
258258
expect(documentsGet.results[0]).not.toHaveProperty("_vectors");
259259
});
260260

261+
test(`${permission} key: Get documents with sorting by single field`, async () => {
262+
const client = await getClient(permission);
263+
264+
await client
265+
.index(indexPk.uid)
266+
.updateSortableAttributes(["id"])
267+
.waitTask();
268+
269+
await client.index(indexPk.uid).addDocuments(dataset).waitTask();
270+
271+
const documents = await client.index(indexPk.uid).getDocuments<Book>({
272+
sort: ["id:asc"],
273+
});
274+
275+
expect(documents.results.length).toEqual(dataset.length);
276+
// Verify documents are sorted by id in ascending order
277+
const ids = documents.results.map((doc) => doc.id);
278+
const sortedIds = [...ids].sort((a, b) => a - b);
279+
expect(ids).toEqual(sortedIds);
280+
});
281+
282+
test(`${permission} key: Get documents with sorting by multiple fields`, async () => {
283+
const client = await getClient(permission);
284+
285+
await client
286+
.index(indexPk.uid)
287+
.updateSortableAttributes(["id", "title"])
288+
.waitTask();
289+
290+
await client.index(indexPk.uid).addDocuments(dataset).waitTask();
291+
292+
const documents = await client.index(indexPk.uid).getDocuments<Book>({
293+
sort: ["id:desc", "title:asc"],
294+
});
295+
296+
expect(documents.results.length).toEqual(dataset.length);
297+
// Verify documents are sorted by id in descending order, then by title ascending
298+
const ids = documents.results.map((doc) => doc.id);
299+
const sortedIds = [...ids].sort((a, b) => b - a);
300+
expect(ids).toEqual(sortedIds);
301+
});
302+
303+
test(`${permission} key: Get documents with empty sort array`, async () => {
304+
const client = await getClient(permission);
305+
306+
await client
307+
.index(indexPk.uid)
308+
.updateSortableAttributes(["id"])
309+
.waitTask();
310+
311+
await client.index(indexPk.uid).addDocuments(dataset).waitTask();
312+
313+
const documents = await client.index(indexPk.uid).getDocuments<Book>({
314+
sort: [],
315+
});
316+
317+
expect(documents.results.length).toEqual(dataset.length);
318+
// Should return documents in default order (no specific sorting)
319+
});
320+
321+
test(`${permission} key: Get documents with sorting should trigger error for non-sortable attribute`, async () => {
322+
const client = await getClient(permission);
323+
324+
await client.index(indexPk.uid).addDocuments(dataset).waitTask();
325+
326+
await assert.rejects(
327+
client.index(indexPk.uid).getDocuments({ sort: ["title:asc"] }),
328+
Error,
329+
/Attribute `title` is not sortable/,
330+
);
331+
});
332+
261333
test(`${permission} key: Replace documents from index that has NO primary key`, async () => {
262334
const client = await getClient(permission);
263335
await client.index(indexNoPk.uid).addDocuments(dataset).waitTask();

0 commit comments

Comments
 (0)