Skip to content

Commit

Permalink
Add performance hints
Browse files Browse the repository at this point in the history
  • Loading branch information
ecooper committed Dec 10, 2024
1 parent 4eed51c commit 053d765
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 46 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -89,4 +89,4 @@
"pre-commit": "npm run pretest"
}
}
}
}
4 changes: 2 additions & 2 deletions src/commands/database/create.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/commands/database/list.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -39,7 +39,7 @@ async function listDatabasesWithAccountAPI(argv) {

container.resolve("logger").stdout(
colorize(output, {
format: JSON_FORMAT,
format: Format.JSON,
color: color,
}),
);
Expand All @@ -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);
Expand Down
22 changes: 20 additions & 2 deletions src/commands/query.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../lib/command-helpers.mjs";
import {
formatError,
formatPerformanceHint,
formatQueryResponse,
getSecret,
} from "../lib/fauna-client.mjs";
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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;
Expand Down
41 changes: 33 additions & 8 deletions src/commands/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand All @@ -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 {
Expand All @@ -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);
Expand Down
17 changes: 12 additions & 5 deletions src/lib/command-helpers.mjs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -155,15 +155,15 @@ 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) {
logger.debug(
"--raw has taken precedence over other formatting options, using JSON output",
"argv",
);
return JSON_FORMAT;
return Format.JSON;
}

return argv.format;
Expand Down Expand Up @@ -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: {
Expand All @@ -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) {
Expand Down
42 changes: 37 additions & 5 deletions src/lib/fauna-client.mjs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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";
}

Expand All @@ -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,
},
}),
);
}
Expand Down Expand Up @@ -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;
}
};

10 changes: 5 additions & 5 deletions src/lib/fauna.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 },
);
}
};
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 053d765

Please sign in to comment.