Skip to content

Commit 7f161f0

Browse files
committed
feat:add sort parameter support to documents API
1 parent c708d5c commit 7f161f0

File tree

4 files changed

+94
-3
lines changed

4 files changed

+94
-3
lines changed

.code-samples.meilisearch.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ get_documents_post_1: |-
5555
fields: ['title', 'genres', 'rating', 'language'],
5656
limit: 3
5757
})
58+
get_documents_sort_1: |-
59+
client.index('movies').getDocuments({
60+
sort: ['release_date:desc']
61+
})
62+
get_documents_sort_multiple_1: |-
63+
client.index('movies').getDocuments({
64+
sort: ['rating:desc', 'release_date:asc']
65+
})
5866
add_or_replace_documents_1: |-
5967
client.index('movies').addDocuments([{
6068
id: 287947,

src/indexes.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,27 @@ export class Index<T extends RecordAny = RecordAny> {
305305
params?: DocumentsQuery<D>,
306306
): Promise<ResourceResults<D[]>> {
307307
const relativeBaseURL = `indexes/${this.uid}/documents`;
308+
// Create a shallow copy so we can safely normalize parameters
309+
const normalizedParams = params ? { ...params } : undefined;
310+
// Omit empty sort arrays to avoid server-side validation errors
311+
if (
312+
normalizedParams &&
313+
Array.isArray(normalizedParams.sort) &&
314+
normalizedParams.sort.length === 0
315+
) {
316+
delete (normalizedParams as { sort?: string[] }).sort;
317+
}
308318

309-
return params?.filter !== undefined
319+
return normalizedParams?.filter !== undefined
310320
? // In case `filter` is provided, use `POST /documents/fetch`
311321
await this.httpRequest.post<ResourceResults<D[]>>({
312322
path: `${relativeBaseURL}/fetch`,
313-
body: params,
323+
body: normalizedParams,
314324
})
315325
: // Else use `GET /documents` method
316326
await this.httpRequest.get<ResourceResults<D[]>>({
317327
path: relativeBaseURL,
318-
params,
328+
params: normalizedParams,
319329
});
320330
}
321331

src/types/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ export type DocumentsQuery<T = RecordAny> = ResourceQuery & {
510510
limit?: number;
511511
offset?: number;
512512
retrieveVectors?: boolean;
513+
sort?: string[];
513514
};
514515

515516
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)