From 053d76515b763541a11d813c92a58a8712794194 Mon Sep 17 00:00:00 2001 From: "E. Cooper" Date: Tue, 10 Dec 2024 14:23:16 -0800 Subject: [PATCH 1/4] Add performance hints --- package-lock.json | 8 +++--- package.json | 4 +-- src/commands/database/create.mjs | 4 +-- src/commands/database/list.mjs | 6 ++--- src/commands/query.mjs | 22 +++++++++++++++-- src/commands/shell.mjs | 41 +++++++++++++++++++++++++------ src/lib/command-helpers.mjs | 17 +++++++++---- src/lib/fauna-client.mjs | 42 ++++++++++++++++++++++++++++---- src/lib/fauna.mjs | 10 ++++---- src/lib/faunadb.mjs | 6 ++--- src/lib/formatting/colorize.mjs | 16 ++++++------ test/query.mjs | 34 ++++++++++++++++++++++++++ test/shell.mjs | 24 ++++++++++++++++++ 13 files changed, 188 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a4ffb1e..315cb0fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "chalk": "^5.3.0", "eslint": "^9.12.0", "esprima": "^4.0.1", - "fauna": "^2.3.0", + "fauna": "^2.4.0", "faunadb": "^4.5.4", "inquirer": "^12.0.0", "luxon": "^3.5.0", @@ -1788,9 +1788,9 @@ } }, "node_modules/fauna": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/fauna/-/fauna-2.3.0.tgz", - "integrity": "sha512-7mavTqMGvXGtmCOo5Ng+q2OdLWQrSfGX8iJIz8ABcJVC6IvI/sWwvOLRl59fZ4w1coyWX6zin0TC8DfroTiS3g==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fauna/-/fauna-2.4.0.tgz", + "integrity": "sha512-Wfg/dfBxJtlMgLiqJhnAMICEnYjvAZXkbdaNRczOtKO4N6iSFm+J2LFpOMDD/pJ6Q4Y7Oe5CrXzbZHVjobzx3w==", "license": "MPL-2.0", "dependencies": { "base64-js": "^1.5.1" diff --git a/package.json b/package.json index e2618f07..0737d3a6 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "chalk": "^5.3.0", "eslint": "^9.12.0", "esprima": "^4.0.1", - "fauna": "^2.3.0", + "fauna": "^2.4.0", "faunadb": "^4.5.4", "inquirer": "^12.0.0", "luxon": "^3.5.0", @@ -89,4 +89,4 @@ "pre-commit": "npm run pretest" } } -} +} \ No newline at end of file diff --git a/src/commands/database/create.mjs b/src/commands/database/create.mjs index 2e0502f5..e90a72e5 100644 --- a/src/commands/database/create.mjs +++ b/src/commands/database/create.mjs @@ -6,7 +6,7 @@ import { container } from "../../cli.mjs"; import { validateDatabaseOrSecret } from "../../lib/command-helpers.mjs"; import { throwForError } from "../../lib/fauna.mjs"; import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs"; -import { colorize, JSON_FORMAT } from "../../lib/formatting/colorize.mjs"; +import { colorize, Format } from "../../lib/formatting/colorize.mjs"; async function runCreateQuery(secret, argv) { const { fql } = container.resolve("fauna"); @@ -38,7 +38,7 @@ async function createDatabase(argv) { const { color, json } = argv; if (json) { logger.stdout( - colorize({ name: argv.name }, { color, format: JSON_FORMAT }), + colorize({ name: argv.name }, { color, format: Format.JSON }), ); } else { logger.stdout(argv.name); diff --git a/src/commands/database/list.mjs b/src/commands/database/list.mjs index d8747f57..6a0ec135 100644 --- a/src/commands/database/list.mjs +++ b/src/commands/database/list.mjs @@ -6,7 +6,7 @@ import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; import { throwForError } from "../../lib/fauna.mjs"; import { FaunaAccountClient } from "../../lib/fauna-account-client.mjs"; -import { colorize, JSON_FORMAT } from "../../lib/formatting/colorize.mjs"; +import { colorize, Format } from "../../lib/formatting/colorize.mjs"; // Narrow the output fields based on the provided flags. const getOutputFields = (argv) => { @@ -39,7 +39,7 @@ async function listDatabasesWithAccountAPI(argv) { container.resolve("logger").stdout( colorize(output, { - format: JSON_FORMAT, + format: Format.JSON, color: color, }), ); @@ -60,7 +60,7 @@ async function listDatabasesWithSecret(argv) { }); container .resolve("logger") - .stdout(formatQueryResponse(result, { format: JSON_FORMAT, color })); + .stdout(formatQueryResponse(result, { format: Format.JSON, color })); } catch (e) { if (e instanceof FaunaError) { throwForError(e); diff --git a/src/commands/query.mjs b/src/commands/query.mjs index ef148d98..cfe95bb6 100644 --- a/src/commands/query.mjs +++ b/src/commands/query.mjs @@ -11,6 +11,7 @@ import { } from "../lib/command-helpers.mjs"; import { formatError, + formatPerformanceHint, formatQueryResponse, getSecret, } from "../lib/fauna-client.mjs"; @@ -65,6 +66,8 @@ const resolveInput = (argv) => { }; async function queryCommand(argv) { + const logger = container.resolve("logger"); + // Run validation here instead of via check for more control over output validateDatabaseOrSecret(argv); validate(argv); @@ -75,7 +78,15 @@ async function queryCommand(argv) { // get the query handler and run the query try { const secret = await getSecret(); - const { url, timeout, typecheck, apiVersion, color, raw } = argv; + const { + url, + timeout, + typecheck, + apiVersion, + color, + raw, + performanceHints, + } = argv; // If we're writing to a file, don't colorize the output regardless of the user's preference const useColor = argv.output || !isTTY() ? false : color; @@ -89,11 +100,18 @@ async function queryCommand(argv) { url, timeout, typecheck, + performanceHints, format: outputFormat, raw, color: useColor, }); + // If performance hints are enabled, print the summary to stderr. + // This is only supported in v10. + if (performanceHints && apiVersion === "10") { + logger.stderr(formatPerformanceHint(results.summary)); + } + const output = formatQueryResponse(results, { apiVersion, format: outputFormat, @@ -104,7 +122,7 @@ async function queryCommand(argv) { if (argv.output) { container.resolve("fs").writeFileSync(argv.output, output); } else { - container.resolve("logger").stdout(output); + logger.stdout(output); } return results; diff --git a/src/commands/shell.mjs b/src/commands/shell.mjs index 694dd3ad..3783d4dc 100644 --- a/src/commands/shell.mjs +++ b/src/commands/shell.mjs @@ -11,7 +11,11 @@ import { validateDatabaseOrSecret, yargsWithCommonConfigurableQueryOptions, } from "../lib/command-helpers.mjs"; -import { formatQueryResponse, getSecret } from "../lib/fauna-client.mjs"; +import { + formatPerformanceHint, + formatQueryResponse, + getSecret, +} from "../lib/fauna-client.mjs"; import { clearHistoryStorage, initHistoryStorage } from "../lib/file-util.mjs"; async function shellCommand(argv) { @@ -105,11 +109,30 @@ async function shellCommand(argv) { shell.prompt(); }, }, + { + cmd: "togglePerformanceHints", + help: "Enable or disable performance hints. Disabled by default. If enabled, outputs performance hints for the most recent query.", + action: () => { + shell.context.performanceHints = !shell.context.performanceHints; + logger.stderr( + `Performance hints in shell: ${shell.context.performanceHints ? "on" : "off"}`, + ); + shell.prompt(); + }, + }, ].forEach(({ cmd, ...cmdOptions }) => shell.defineCommand(cmd, cmdOptions)); return completionPromise; } +const getArgvOrCtx = (key, argv, ctx) => { + const value = ctx[key] === undefined ? argv[key] : ctx[key]; + if (ctx[key] === undefined) { + ctx[key] = value; + } + return value; +}; + // caches the logger, client, and performQuery for subsequent shell calls async function buildCustomEval(argv) { const runQueryFromString = container.resolve("runQueryFromString"); @@ -122,15 +145,12 @@ async function buildCustomEval(argv) { if (cmd.trim() === "") return cb(); // These are options used for querying and formatting the response - const { apiVersion, color, raw: argvRaw } = argv; - const raw = ctx.raw === undefined ? argvRaw : ctx.raw; - - if (ctx.raw === undefined) { - ctx.raw = argvRaw; - } + const { apiVersion, color } = argv; + const raw = getArgvOrCtx("raw", argv, ctx); + const performanceHints = getArgvOrCtx("performanceHints", argv, ctx); // Using --raw or --json output takes precedence over --format - const outputFormat = resolveFormat(argv); + const outputFormat = resolveFormat({ ...argv, raw }); if (apiVersion === "4") { try { @@ -151,8 +171,13 @@ async function buildCustomEval(argv) { url, timeout, typecheck, + performanceHints, format: outputFormat, }); + + if (performanceHints && apiVersion === "10") { + logger.stdout(formatPerformanceHint(res.summary)); + } } catch (err) { logger.stderr(formatError(err, { apiVersion, raw, color })); return cb(null); diff --git a/src/lib/command-helpers.mjs b/src/lib/command-helpers.mjs index edf000c7..c3abe39f 100644 --- a/src/lib/command-helpers.mjs +++ b/src/lib/command-helpers.mjs @@ -1,7 +1,7 @@ //@ts-check import { container } from "../cli.mjs"; -import { FQL_FORMAT, JSON_FORMAT } from "./formatting/colorize.mjs"; +import { Format } from "./formatting/colorize.mjs"; const COMMON_OPTIONS = { // hidden @@ -155,7 +155,7 @@ export const resolveFormat = (argv) => { "--json has taken precedence over other formatting options, using JSON output", "argv", ); - return JSON_FORMAT; + return Format.JSON; } if (argv.raw) { @@ -163,7 +163,7 @@ export const resolveFormat = (argv) => { "--raw has taken precedence over other formatting options, using JSON output", "argv", ); - return JSON_FORMAT; + return Format.JSON; } return argv.format; @@ -205,8 +205,8 @@ const COMMON_CONFIGURABLE_QUERY_OPTIONS = { alias: "f", description: "Output format for the query. When present, --json takes precedence over --format. Only applies to v10 queries.", - choices: [FQL_FORMAT, JSON_FORMAT], - default: FQL_FORMAT, + choices: [Format.FQL, Format.JSON], + default: Format.FQL, group: "API:", }, typecheck: { @@ -223,6 +223,13 @@ const COMMON_CONFIGURABLE_QUERY_OPTIONS = { default: 5000, group: "API:", }, + performanceHints: { + type: "boolean", + description: + "Enable performance hints for the current query. Only applies to v10 queries.", + default: false, + group: "API:", + }, }; export function yargsWithCommonQueryOptions(yargs) { diff --git a/src/lib/fauna-client.mjs b/src/lib/fauna-client.mjs index 76903085..428e28d6 100644 --- a/src/lib/fauna-client.mjs +++ b/src/lib/fauna-client.mjs @@ -1,7 +1,7 @@ //@ts-check import { container } from "../cli.mjs"; -import { JSON_FORMAT } from "./formatting/colorize.mjs"; +import { colorize, Format } from "./formatting/colorize.mjs"; /** * Gets a secret for the current credentials. @@ -60,9 +60,9 @@ export const runQueryFromString = (expression, argv) => { }), ); } else { - const { secret, url, timeout, format, ...rest } = argv; + const { secret, url, timeout, format, performanceHints, ...rest } = argv; let apiFormat = "decorated"; - if (format === JSON_FORMAT) { + if (format === Format.JSON) { apiFormat = "simple"; } @@ -72,8 +72,14 @@ export const runQueryFromString = (expression, argv) => { secret, url, client: undefined, - // eslint-disable-next-line camelcase - options: { query_timeout_ms: timeout, format: apiFormat, ...rest }, + options: { + /* eslint-disable camelcase */ + query_timeout_ms: timeout, + performance_hints: performanceHints, + /* eslint-enable camelcase */ + format: apiFormat, + ...rest, + }, }), ); } @@ -122,3 +128,29 @@ export const formatQueryResponse = ( return faunaV10.formatQueryResponse(res, { raw, format, color }); } }; + +/** + * Formats a performance hint. If no hint is available, returns a default message. If + * the hint is malformed, returns the hint as is. + * @param {string} performanceHint - The performance hint + * @returns {string} + */ +export const formatPerformanceHint = (performanceHint) => { + if ( + !performanceHint || + typeof performanceHint !== "string" || + !performanceHint.startsWith("performance_hint") + ) { + return "performance_hint: No performance hint available."; + } + + try { + const [message, ...hints] = performanceHint.split("\n"); + return `${message}\n${colorize(hints.join("\n"), { format: Format.FQL })}`; + } catch (err) { + const logger = container.resolve("logger"); + logger.debug(`Unable to parse performance hint: ${err}`); + return performanceHint; + } +}; + diff --git a/src/lib/fauna.mjs b/src/lib/fauna.mjs index ce749349..ad0bfbf6 100644 --- a/src/lib/fauna.mjs +++ b/src/lib/fauna.mjs @@ -13,7 +13,7 @@ import { import { container } from "../cli.mjs"; import { ValidationError } from "./command-helpers.mjs"; -import { colorize, JSON_FORMAT, TEXT_FORMAT } from "./formatting/colorize.mjs"; +import { colorize, Format } from "./formatting/colorize.mjs"; /** * Interprets a string as a FQL expression and returns a query. @@ -139,18 +139,18 @@ export const formatError = (err, opts = {}) => { ) { // If you want full response, use util.inspect to get the full error object. if (raw) { - return colorize(err, { color, format: JSON_FORMAT }); + return colorize(err, { color, format: Format.JSON }); } // Otherwise, return the summary and fall back to the message. return colorize( `The query failed with the following error:\n\n${err.queryInfo?.summary ?? err.message}`, - { color, format: TEXT_FORMAT }, + { color, format: Format.TEXT }, ); } else { return colorize( `The query failed unexpectedly with the following error:\n\n${err.message}`, - { color, format: TEXT_FORMAT }, + { color, format: Format.TEXT }, ); } }; @@ -165,7 +165,7 @@ export const formatError = (err, opts = {}) => { * @returns {string} The formatted response */ export const formatQueryResponse = (res, opts = {}) => { - const { raw, format = JSON_FORMAT, color } = opts; + const { raw, format = Format.JSON, color } = opts; // If raw is set, return the full response object. const data = raw ? res : res.data; diff --git a/src/lib/faunadb.mjs b/src/lib/faunadb.mjs index d555d336..6043d3fe 100644 --- a/src/lib/faunadb.mjs +++ b/src/lib/faunadb.mjs @@ -2,7 +2,7 @@ import { createContext, runInContext } from "node:vm"; import { container } from "../cli.mjs"; -import { colorize, JSON_FORMAT } from "./formatting/colorize.mjs"; +import { colorize, Format } from "./formatting/colorize.mjs"; /** * Creates a V4 Fauna client. @@ -100,7 +100,7 @@ export const formatError = (err, opts = {}) => { ) { // If raw is on, return the full error. if (raw) { - return colorize(err, { color, format: JSON_FORMAT }); + return colorize(err, { color, format: Format.JSON }); } const { errors } = err.requestResult.responseContent; @@ -134,7 +134,7 @@ export const formatError = (err, opts = {}) => { export const formatQueryResponse = (res, opts = {}) => { const { raw, color, format } = opts; const data = raw ? res : res.value; - const resolvedFormat = raw ? JSON_FORMAT : (format ?? JSON_FORMAT); + const resolvedFormat = raw ? Format.JSON : (format ?? Format.JSON); return colorize(data, { format: resolvedFormat, color }); }; diff --git a/src/lib/formatting/colorize.mjs b/src/lib/formatting/colorize.mjs index 34804d08..30fb1809 100644 --- a/src/lib/formatting/colorize.mjs +++ b/src/lib/formatting/colorize.mjs @@ -2,9 +2,11 @@ import stripAnsi from "strip-ansi"; import { container } from "../../cli.mjs"; -export const FQL_FORMAT = "fql"; -export const JSON_FORMAT = "json"; -export const TEXT_FORMAT = "text"; +export const Format = { + FQL: "fql", + JSON: "json", + TEXT: "text", +}; const objToString = (obj) => JSON.stringify(obj, null, 2); @@ -47,11 +49,11 @@ const jsonToAnsi = (obj) => { * @param {string} [opts.format] - The format to use * @returns {string} The formatted object */ -export const toAnsi = (obj, { format = TEXT_FORMAT } = {}) => { +export const toAnsi = (obj, { format = Format.TEXT } = {}) => { switch (format) { - case FQL_FORMAT: + case Format.FQL: return fqlToAnsi(obj); - case JSON_FORMAT: + case Format.JSON: return jsonToAnsi(obj); default: return textToAnsi(obj); @@ -66,7 +68,7 @@ export const toAnsi = (obj, { format = TEXT_FORMAT } = {}) => { * @param {boolean} [opts.color] - Whether to colorize the object * @returns {string} The formatted object */ -export const colorize = (obj, { color = true, format = TEXT_FORMAT } = {}) => { +export const colorize = (obj, { color = true, format = Format.TEXT } = {}) => { const ansiString = toAnsi(obj, { format }); if (color) { diff --git a/test/query.mjs b/test/query.mjs index dd6db298..d59249e9 100644 --- a/test/query.mjs +++ b/test/query.mjs @@ -340,6 +340,40 @@ describe("query", function () { }), ); }); + + it("can set the performanceHints option to true", async function () { + await run( + `query "Database.all()" --performanceHints --secret=foo`, + container, + ); + expect(runQueryFromString).to.have.been.calledWith( + '"Database.all()"', + sinon.match({ + performanceHints: true, + }), + ); + }); + + it("can display performance hints", async function () { + runQueryFromString.resolves({ + summary: "performance_hint: use a more efficient query\n", + data: "fql", + }); + + await run( + `query "Database.all()" --performanceHints --secret=foo`, + container, + ); + + expect(logger.stderr).to.have.been.calledWith( + sinon.match(/use a more efficient query/), + ); + expect(container.resolve("codeToAnsi")).to.have.been.calledWith( + sinon.match(//), + "fql", + ); + expect(logger.stdout).to.have.been.calledWith(sinon.match(/fql/)); + }); }); describe("v4", function () { diff --git a/test/shell.mjs b/test/shell.mjs index 6698593e..b8a82f5e 100644 --- a/test/shell.mjs +++ b/test/shell.mjs @@ -321,6 +321,30 @@ describe("shell", function () { ); }); + it("can display performance hints", async function () { + runQueryFromString.resolves({ + summary: "performance_hint: use a more efficient query\n", + data: "fql", + }); + + const runPromise = run( + `shell --secret "secret" --performanceHints --no-color --format json`, + container, + ); + + await sleep(50); + await stdout.waitForWritten(); + + stdin.push(`Database.all().take(1)\n`); + stdin.push(null); + await sleep(50); + await stdout.waitForWritten(); + + expect(stdout.getWritten()).to.match(/use a more efficient query/); + + return runPromise; + }); + describe("error handling", function () { it.skip("can handle a client-side query syntax error", async function () {}); it.skip("can handle a server-side query syntax error", async function () {}); From cf8795775a40124b12ebe15983032ce500d46f6c Mon Sep 17 00:00:00 2001 From: "E. Cooper" Date: Tue, 10 Dec 2024 14:45:02 -0800 Subject: [PATCH 2/4] Fix linting --- package.json | 2 +- src/lib/fauna-client.mjs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 0737d3a6..a622de6b 100644 --- a/package.json +++ b/package.json @@ -89,4 +89,4 @@ "pre-commit": "npm run pretest" } } -} \ No newline at end of file +} diff --git a/src/lib/fauna-client.mjs b/src/lib/fauna-client.mjs index 428e28d6..542b7e10 100644 --- a/src/lib/fauna-client.mjs +++ b/src/lib/fauna-client.mjs @@ -153,4 +153,3 @@ export const formatPerformanceHint = (performanceHint) => { return performanceHint; } }; - From bcccef51af2233d5e766bd57378651bc5dd7902e Mon Sep 17 00:00:00 2001 From: "E. Cooper" Date: Tue, 10 Dec 2024 15:17:30 -0800 Subject: [PATCH 3/4] Support formatting multiple performance hints in a single summary --- src/lib/fauna-client.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/fauna-client.mjs b/src/lib/fauna-client.mjs index 542b7e10..0977bde7 100644 --- a/src/lib/fauna-client.mjs +++ b/src/lib/fauna-client.mjs @@ -145,8 +145,13 @@ export const formatPerformanceHint = (performanceHint) => { } try { - const [message, ...hints] = performanceHint.split("\n"); - return `${message}\n${colorize(hints.join("\n"), { format: Format.FQL })}`; + const lines = performanceHint.split("\n").map((line) => { + if (line.startsWith("performance_hint")) { + return line; + } + return colorize(line, { format: Format.FQL }); + }); + return lines.join("\n"); } catch (err) { const logger = container.resolve("logger"); logger.debug(`Unable to parse performance hint: ${err}`); From 05d86a25a6c2745529659731a6043f83fd7b781e Mon Sep 17 00:00:00 2001 From: "E. Cooper" Date: Tue, 10 Dec 2024 15:53:18 -0800 Subject: [PATCH 4/4] Add support for rendering multiple performance hints and the summary in general --- src/commands/query.mjs | 7 ++++--- src/commands/shell.mjs | 18 +++++++++++++++--- src/lib/command-helpers.mjs | 9 ++++++++- src/lib/fauna-client.mjs | 23 ++++++++++------------- test/query.mjs | 5 +++-- 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/commands/query.mjs b/src/commands/query.mjs index cfe95bb6..2be73975 100644 --- a/src/commands/query.mjs +++ b/src/commands/query.mjs @@ -11,8 +11,8 @@ import { } from "../lib/command-helpers.mjs"; import { formatError, - formatPerformanceHint, formatQueryResponse, + formatQuerySummary, getSecret, } from "../lib/fauna-client.mjs"; import { isTTY } from "../lib/misc.mjs"; @@ -86,6 +86,7 @@ async function queryCommand(argv) { color, raw, performanceHints, + summary, } = argv; // If we're writing to a file, don't colorize the output regardless of the user's preference @@ -108,8 +109,8 @@ async function queryCommand(argv) { // If performance hints are enabled, print the summary to stderr. // This is only supported in v10. - if (performanceHints && apiVersion === "10") { - logger.stderr(formatPerformanceHint(results.summary)); + if ((summary || performanceHints) && apiVersion === "10") { + logger.stderr(formatQuerySummary(results.summary)); } const output = formatQueryResponse(results, { diff --git a/src/commands/shell.mjs b/src/commands/shell.mjs index 3783d4dc..e225e539 100644 --- a/src/commands/shell.mjs +++ b/src/commands/shell.mjs @@ -12,8 +12,8 @@ import { yargsWithCommonConfigurableQueryOptions, } from "../lib/command-helpers.mjs"; import { - formatPerformanceHint, formatQueryResponse, + formatQuerySummary, getSecret, } from "../lib/fauna-client.mjs"; import { clearHistoryStorage, initHistoryStorage } from "../lib/file-util.mjs"; @@ -120,6 +120,17 @@ async function shellCommand(argv) { shell.prompt(); }, }, + { + cmd: "toggleSummary", + help: "Enable or disable the summary field of the API response. Disabled by default. If enabled, outputs the summary field of the API response.", + action: () => { + shell.context.summary = !shell.context.summary; + logger.stderr( + `Summary in shell: ${shell.context.summary ? "on" : "off"}`, + ); + shell.prompt(); + }, + }, ].forEach(({ cmd, ...cmdOptions }) => shell.defineCommand(cmd, cmdOptions)); return completionPromise; @@ -148,6 +159,7 @@ async function buildCustomEval(argv) { const { apiVersion, color } = argv; const raw = getArgvOrCtx("raw", argv, ctx); const performanceHints = getArgvOrCtx("performanceHints", argv, ctx); + const summary = getArgvOrCtx("summary", argv, ctx); // Using --raw or --json output takes precedence over --format const outputFormat = resolveFormat({ ...argv, raw }); @@ -175,8 +187,8 @@ async function buildCustomEval(argv) { format: outputFormat, }); - if (performanceHints && apiVersion === "10") { - logger.stdout(formatPerformanceHint(res.summary)); + if ((summary || performanceHints) && apiVersion === "10") { + logger.stdout(formatQuerySummary(res.summary)); } } catch (err) { logger.stderr(formatError(err, { apiVersion, raw, color })); diff --git a/src/lib/command-helpers.mjs b/src/lib/command-helpers.mjs index fd84d06b..82797df6 100644 --- a/src/lib/command-helpers.mjs +++ b/src/lib/command-helpers.mjs @@ -225,10 +225,17 @@ const COMMON_CONFIGURABLE_QUERY_OPTIONS = { default: 5000, group: "API:", }, + summary: { + type: "boolean", + description: + "Output the summary field of the API response. Only applies to v10 queries.", + default: false, + group: "API:", + }, performanceHints: { type: "boolean", description: - "Enable performance hints for the current query. Only applies to v10 queries.", + "Output the performance hints for the current query. Only applies to v10 queries.", default: false, group: "API:", }, diff --git a/src/lib/fauna-client.mjs b/src/lib/fauna-client.mjs index 0977bde7..1d26dfd0 100644 --- a/src/lib/fauna-client.mjs +++ b/src/lib/fauna-client.mjs @@ -3,6 +3,8 @@ import { container } from "../cli.mjs"; import { colorize, Format } from "./formatting/colorize.mjs"; +const SUMMARY_FQL_REGEX = /^(\s\s\|)|(\d\s\|)/; + /** * Gets a secret for the current credentials. * @return {Promise} the secret @@ -130,23 +132,18 @@ export const formatQueryResponse = ( }; /** - * Formats a performance hint. If no hint is available, returns a default message. If - * the hint is malformed, returns the hint as is. - * @param {string} performanceHint - The performance hint + * Formats a summary of a query from a fauna + * @param {string} summary - The summary of the query * @returns {string} */ -export const formatPerformanceHint = (performanceHint) => { - if ( - !performanceHint || - typeof performanceHint !== "string" || - !performanceHint.startsWith("performance_hint") - ) { - return "performance_hint: No performance hint available."; +export const formatQuerySummary = (summary) => { + if (!summary || typeof summary !== "string") { + return "No summary returned."; } try { - const lines = performanceHint.split("\n").map((line) => { - if (line.startsWith("performance_hint")) { + const lines = summary.split("\n").map((line) => { + if (!line.match(SUMMARY_FQL_REGEX)) { return line; } return colorize(line, { format: Format.FQL }); @@ -155,6 +152,6 @@ export const formatPerformanceHint = (performanceHint) => { } catch (err) { const logger = container.resolve("logger"); logger.debug(`Unable to parse performance hint: ${err}`); - return performanceHint; + return summary; } }; diff --git a/test/query.mjs b/test/query.mjs index d59249e9..28b39d81 100644 --- a/test/query.mjs +++ b/test/query.mjs @@ -356,7 +356,8 @@ describe("query", function () { it("can display performance hints", async function () { runQueryFromString.resolves({ - summary: "performance_hint: use a more efficient query\n", + summary: + "performance_hint: use a more efficient query\n1 | use a more efficient query", data: "fql", }); @@ -369,7 +370,7 @@ describe("query", function () { sinon.match(/use a more efficient query/), ); expect(container.resolve("codeToAnsi")).to.have.been.calledWith( - sinon.match(//), + sinon.match(/use a more efficient query/), "fql", ); expect(logger.stdout).to.have.been.calledWith(sinon.match(/fql/));