Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/first-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ First version of `meili`, the Meilisearch CLI built for coding agents.
- Documents: `meili get documents --index <index>`, narrowed by `--ids` or `--filter`; `meili add documents --index <index> --file <path>` and `meili update documents --index <index> --file <path>`, which read the documents from a file, from stdin with `--file -` or from `--data`; `meili delete documents --index <index> (--ids <ids> | --filter <filter> | --all) --yes`.
- Indexes: `meili get indexes [--id <index>]`; `meili add indexes --id <index> [--primary-key <field>]`, `meili update indexes --id <index> [--primary-key <field>] [--new-id <index>]`, which renames the index, and `meili delete indexes --id <index> --yes`.
- Tasks: `meili get tasks`, narrowed by `--ids`, `--statuses`, `--types`, `--index-ids`, `--batch-ids`, `--canceled-by` and the date filters of the route, paging with `--limit` and `--from` and following the `next` cursor with `--all`; `meili cancel tasks` and `meili delete tasks --yes`, which take the same filters and answer a task.
- Search: `meili search --index <index> --q <query>`, on `POST /indexes/{index_uid}/search`, with every non-experimental search parameter of the route as a flag, from `--filter` and `--sort` to `--hybrid` and `--vector`; `--data` or `--file` carries a whole search body, which a flag of the same field overrides.
- Search: `meili search --index <index> --q <query>`, on `POST /indexes/{index_uid}/search`, with every non-experimental search parameter of the route as a flag, from `--filter` and `--sort` to `--hybrid` and `--vector`; `--data` or `--file` carries a whole search body, which a flag of the same field overrides. `meili multi-search --queries '<json>'`, on `POST /multi-search`, runs several queries in one request, each naming its index, answered side by side or merged into one ranked list with `--federation`.
- Settings: `meili get settings --index <index>`, which returns every setting with its current value or its default; `meili update settings --index <index>`, with one flag per setting, from `--searchable-attributes` and `--stop-words` to `--typo-tolerance` and `--embedders`, where a value of `null` resets that one setting and `--data` or `--file` carries a whole settings body; `meili delete settings --index <index> --yes`, which resets them all.
- `meili schema` prints the index of the command tree as JSON, one entry per command with its routes, and `meili schema --command "get indexes"` prints one command in full, with its key actions and whether it returns a task.
- Connection through `--project-url` and `--project-api-key`, or `MEILI_PROJECT_URL` and `MEILI_PROJECT_API_KEY`; `MEILI_MANAGEMENT_TOKEN` and `MEILI_MANAGEMENT_URL` for the Management API.
Expand Down
1 change: 1 addition & 0 deletions docs/contract/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Every API command declares its routes, `static routes = ["GET /indexes", "GET /i
| `PUT /indexes/{index_uid}/documents` | `meili update documents --index <index> --file <path>` |
| `POST …/documents/delete-batch`, `POST …/documents/delete`, `DELETE …/documents` | `meili delete documents --index <index> (--ids <ids> \| --filter <filter> \| --all) --yes` |
| `POST /indexes/{index_uid}/search` | `meili search --index <index> --q <query>` |
| `POST /multi-search` | `meili multi-search --queries <json> [--federation <json>]` |
| `GET /indexes/{index_uid}/settings` | `meili get settings --index <index>` |
| `PATCH /indexes/{index_uid}/settings` | `meili update settings --index <index> --<setting> <value>` |
| `DELETE /indexes/{index_uid}/settings` | `meili delete settings --index <index> --yes` |
Expand Down
221 changes: 221 additions & 0 deletions oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4217,6 +4217,227 @@
"tasks.js"
]
},
"multi-search": {
"aliases": [],
"args": {},
"description": "Run several search queries in one request, each naming its index in --queries, returned side by side or merged into one ranked list with --federation.",
"examples": [
"<%= config.bin %> <%= command.id %> --queries '[{\"indexUid\":\"movies\",\"q\":\"nosferatu\"},{\"indexUid\":\"books\",\"q\":\"nosferatu\"}]'",
"<%= config.bin %> <%= command.id %> --queries '[{\"indexUid\":\"movies\",\"q\":\"nosferatu\"}]' --output json",
"<%= config.bin %> <%= command.id %> --queries '[{\"indexUid\":\"movies\",\"q\":\"horror\"},{\"indexUid\":\"books\",\"q\":\"horror\"}]' --federation '{\"limit\":5}'",
"cat queries.json | <%= config.bin %> <%= command.id %> --file -"
],
"flags": {
"output": {
"char": "o",
"description": "json (default): one document, the API body as it is, indented in a terminal, compact when piped. ndjson: one item per line, list commands only, streams with --all.",
"name": "output",
"hasDynamicHelp": false,
"helpValue": "<mode>",
"multiple": false,
"options": [
"json",
"ndjson"
],
"type": "option"
},
"debug": {
"description": "Print every HTTP request and response on stderr, secrets redacted.",
"name": "debug",
"allowNo": false,
"type": "boolean"
},
"project-url": {
"description": "Project URL, the Meilisearch instance to talk to.",
"name": "project-url",
"hasDynamicHelp": false,
"helpValue": "<url>",
"multiple": false,
"type": "option"
},
"project-api-key": {
"description": "Project API key. `-` reads it from stdin.",
"name": "project-api-key",
"hasDynamicHelp": false,
"helpValue": "<key>",
"multiple": false,
"type": "option"
},
"project": {
"description": "A project of the config file. Cannot be combined with --project-url. Required when the file has several projects.",
"name": "project",
"hasDynamicHelp": false,
"helpValue": "<name>",
"multiple": false,
"type": "option"
},
"queries": {
"description": "The search queries as a JSON array, each one an index to search and its search parameters: `[{\"indexUid\":\"movies\",\"q\":\"nosferatu\"}]`.",
"name": "queries",
"hasDynamicHelp": false,
"helpValue": "<json>",
"multiple": false,
"type": "option"
},
"federation": {
"description": "Merge the hits of every query into one ranked list, as JSON: `{}` for the defaults, `{\"limit\":5}` to narrow it. Without it each query answers its own envelope.",
"name": "federation",
"hasDynamicHelp": false,
"helpValue": "<json>",
"multiple": false,
"type": "option"
},
"file": {
"description": "Read the body from a file. `-` reads from stdin.",
"exclusive": [
"data"
],
"name": "file",
"hasDynamicHelp": false,
"helpValue": "<path>",
"multiple": false,
"type": "option"
},
"data": {
"description": "Inline JSON body.",
"name": "data",
"hasDynamicHelp": false,
"helpValue": "<json>",
"multiple": false,
"type": "option"
}
},
"hasDynamicHelp": false,
"hiddenAliases": [],
"id": "multi-search",
"pluginAlias": "@meilisearch/cli",
"pluginName": "@meilisearch/cli",
"pluginType": "core",
"strict": true,
"enableJsonFlag": false,
"api": "meilisearch",
"routes": [
"POST /multi-search"
],
"keyActions": [
"search"
],
"connectionFlags": {
"project-url": {
"description": "Project URL, the Meilisearch instance to talk to.",
"helpValue": "<url>",
"input": [],
"multiple": false,
"type": "option"
},
"project-api-key": {
"description": "Project API key. `-` reads it from stdin.",
"helpValue": "<key>",
"input": [],
"multiple": false,
"type": "option"
},
"project": {
"description": "A project of the config file. Cannot be combined with --project-url. Required when the file has several projects.",
"helpValue": "<name>",
"input": [],
"multiple": false,
"type": "option"
}
},
"waitFlags": {
"wait": {
"description": "Poll the task until it reaches a final status, then print the final task.",
"allowNo": false,
"type": "boolean"
},
"timeout": {
"description": "Maximum wait time, for example 30s, 2m. Default 60s. Only with --wait.",
"helpValue": "<duration>",
"dependsOn": [
"wait"
],
"input": [],
"multiple": false,
"type": "option"
}
},
"paginationFlags": {
"limit": {
"description": "Passed to the API.",
"helpValue": "<n>",
"input": [],
"multiple": false,
"type": "option"
},
"offset": {
"description": "Passed to the API.",
"helpValue": "<n>",
"input": [],
"multiple": false,
"type": "option"
},
"all": {
"description": "Fetch every page. Streams with ndjson, buffers into one array with json.",
"allowNo": false,
"type": "boolean"
}
},
"fromPaginationFlags": {
"limit": {
"description": "Passed to the API.",
"helpValue": "<n>",
"input": [],
"multiple": false,
"type": "option"
},
"from": {
"description": "Passed to the API: the uid the page starts at, going down.",
"helpValue": "<uid>",
"input": [],
"multiple": false,
"type": "option"
},
"all": {
"description": "Fetch every page. Streams with ndjson, buffers into one array with json.",
"allowNo": false,
"type": "boolean"
}
},
"bodyInputFlags": {
"file": {
"description": "Read the body from a file. `-` reads from stdin.",
"helpValue": "<path>",
"exclusive": [
"data"
],
"input": [],
"multiple": false,
"type": "option"
},
"data": {
"description": "Inline JSON body.",
"helpValue": "<json>",
"input": [],
"multiple": false,
"type": "option"
}
},
"confirmFlags": {
"yes": {
"required": true,
"description": "Confirm the deletion. Nothing is sent to the API without it.",
"allowNo": false,
"type": "boolean"
}
},
"isESM": true,
"relativePath": [
"dist",
"commands",
"multi-search.js"
]
},
"schema": {
"aliases": [],
"args": {},
Expand Down
48 changes: 48 additions & 0 deletions src/commands/multi-search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Flags } from "@oclif/core";
import type { MultiSearchParams } from "meilisearch";
import { type Api, BaseCommand } from "../lib/base-command.js";
import { readJsonObjectBody } from "../lib/input.js";
import { asJson } from "../lib/values.js";

export default class MultiSearch extends BaseCommand {
static api: Api = "meilisearch";

static routes = ["POST /multi-search"];

static keyActions = ["search"];

static override description =
"Run several search queries in one request, each naming its index in --queries, returned side by side or merged into one ranked list with --federation.";

static override examples = [
'<%= config.bin %> <%= command.id %> --queries \'[{"indexUid":"movies","q":"nosferatu"},{"indexUid":"books","q":"nosferatu"}]\'',
'<%= config.bin %> <%= command.id %> --queries \'[{"indexUid":"movies","q":"nosferatu"}]\' --output json',
'<%= config.bin %> <%= command.id %> --queries \'[{"indexUid":"movies","q":"horror"},{"indexUid":"books","q":"horror"}]\' --federation \'{"limit":5}\'',
"cat queries.json | <%= config.bin %> <%= command.id %> --file -",
];

static override flags = {
...BaseCommand.connectionFlags,
queries: Flags.string({
description:
'The search queries as a JSON array, each one an index to search and its search parameters: `[{"indexUid":"movies","q":"nosferatu"}]`.',
helpValue: "<json>",
}),
federation: Flags.string({
description:
'Merge the hits of every query into one ranked list, as JSON: `{}` for the defaults, `{"limit":5}` to narrow it. Without it each query answers its own envelope.',
helpValue: "<json>",
}),
...BaseCommand.bodyInputFlags,
};

async run(): Promise<void> {
const { flags } = await this.parse(MultiSearch);
// The body of --file or --data first, then the flags, which override a field of it (rule 8).
const params = { ...readJsonObjectBody(flags) };
if (flags.queries !== undefined) params.queries = asJson(flags.queries);
if (flags.federation !== undefined) params.federation = asJson(flags.federation);

await this.output(await this.meilisearch.multiSearch(params as MultiSearchParams));
}
}
65 changes: 65 additions & 0 deletions test/commands/__snapshots__/multi-search.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`meili multi-search > passes the API error of a missing index through, which the route answers 400 1`] = `
{
"exitCode": 1,
"stderr": "{"error":{"code":"index_not_found","type":"invalid_request","message":"Inside \`.queries[0]\`: Index \`missing\` not found.","link":"https://docs.meilisearch.com/errors#index_not_found"}}
",
"stdout": "",
}
`;

exports[`meili multi-search > prints its help without any network call 1`] = `
"Run several search queries in one request, each naming its index in --queries, returned side by side or merged into one ranked list with --federation.

USAGE
$ meili multi-search [-o <mode>] [--debug] [--project-url <url>]
[--project-api-key <key>] [--project <name>] [--queries <json>]
[--federation <json>] [--file <path> | --data <json>]

FLAGS
-o, --output=<mode> json (default): one document, the API body as it
is, indented in a terminal, compact when piped.
ndjson: one item per line, list commands only,
streams with --all.
--data=<json> Inline JSON body.
--debug Print every HTTP request and response on stderr,
secrets redacted.
--federation=<json> Merge the hits of every query into one ranked
list, as JSON: \`{}\` for the defaults,
\`{"limit":5}\` to narrow it. Without it each query
answers its own envelope.
--file=<path> Read the body from a file. \`-\` reads from stdin.
--project=<name> A project of the config file. Cannot be combined
with --project-url. Required when the file has
several projects.
--project-api-key=<key> Project API key. \`-\` reads it from stdin.
--project-url=<url> Project URL, the Meilisearch instance to talk to.
--queries=<json> The search queries as a JSON array, each one an
index to search and its search parameters:
\`[{"indexUid":"movies","q":"nosferatu"}]\`.

DESCRIPTION
Run several search queries in one request, each naming its index in --queries,
returned side by side or merged into one ranked list with --federation.

EXAMPLES
$ meili multi-search --queries '[{"indexUid":"movies","q":"nosferatu"},{"indexUid":"books","q":"nosferatu"}]'

$ meili multi-search --queries '[{"indexUid":"movies","q":"nosferatu"}]' --output json

$ meili multi-search --queries '[{"indexUid":"movies","q":"horror"},{"indexUid":"books","q":"horror"}]' --federation '{"limit":5}'

cat queries.json | meili multi-search --file -

"
`;

exports[`meili multi-search > prints the API envelope as it is in JSON 1`] = `
{
"exitCode": 0,
"stderr": "",
"stdout": "{"results":[{"indexUid":"movies","hits":[{"id":1,"title":"Nosferatu"}],"query":"nosferatu","processingTimeMs":1,"limit":20,"offset":0,"estimatedTotalHits":1},{"indexUid":"books","hits":[{"id":"dracula","title":"Dracula"}],"query":"nosferatu","processingTimeMs":1,"limit":20,"offset":0,"estimatedTotalHits":1}]}
",
}
`;
Loading
Loading