Skip to content

Commit

Permalink
Refactor Account API support and container management (#556)
Browse files Browse the repository at this point in the history
* Support v2 account API error shapes
* Update account client to support v2 api calls
* Move container to a singleton in config rather than cli
* Wire clientId/clientSecret argv through to OAuthClient
  • Loading branch information
ecooper authored Jan 9, 2025
1 parent 57641e1 commit 334ed9f
Show file tree
Hide file tree
Showing 49 changed files with 1,071 additions and 763 deletions.
18 changes: 10 additions & 8 deletions src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,19 @@ import loginCommand from "./commands/login.mjs";
import queryCommand from "./commands/query.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
import shellCommand from "./commands/shell.mjs";
import { container, setContainer } from "./config/container.mjs";
import { buildCredentials } from "./lib/auth/credentials.mjs";
import { getDbCompletions, getProfileCompletions } from "./lib/completions.mjs";
import { configParser } from "./lib/config/config.mjs";
import { handleParseYargsError } from "./lib/errors.mjs";
import {
applyAccountUrl,
applyLocalArg,
checkForUpdates,
fixPaths,
logArgv,
} from "./lib/middleware.mjs";

/** @typedef {import('awilix').AwilixContainer<import('./config/setup-container.mjs').modifiedInjectables> } cliContainer */

/** @type {cliContainer} */
export let container;
/** @type {import('yargs').Argv} */
export let builtYargs;

Expand All @@ -36,10 +34,11 @@ const __dirname = path.dirname(__filename);

/**
* @param {string|string[]} _argvInput - The command string provided by the user or test. Parsed by yargs into an argv object.
* @param {cliContainer} _container - A built and ready for use awilix container with registered injectables.
* @param {import('./config/container.mjs').container} container - A built and ready for use awilix container with registered injectables.
*/
export async function run(_argvInput, _container) {
container = _container;
export async function run(_argvInput, container) {
setContainer(container);

const argvInput = _argvInput;
const logger = container.resolve("logger");
const parseYargs = container.resolve("parseYargs");
Expand Down Expand Up @@ -111,7 +110,10 @@ function buildYargs(argvInput) {
.env("FAUNA")
.config("config", configParser.bind(null, argvInput))
.middleware([checkForUpdates, logArgv], true)
.middleware([applyLocalArg, fixPaths, buildCredentials], false)
.middleware(
[applyLocalArg, fixPaths, applyAccountUrl, buildCredentials],
false,
)
.command(queryCommand)
.command(shellCommand)
.command(loginCommand)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/database/create.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//@ts-check
import { ServiceError } from "fauna";

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs";
Expand Down
2 changes: 1 addition & 1 deletion src/commands/database/delete.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { ServiceError } from "fauna";

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs";
Expand Down
7 changes: 3 additions & 4 deletions src/commands/database/list.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
//@ts-check
import chalk from "chalk";

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { FaunaAccountClient } from "../../lib/fauna-account-client.mjs";
import { colorize, Format } from "../../lib/formatting/colorize.mjs";

async function listDatabasesWithAccountAPI(argv) {
const { pageSize, database } = argv;
const accountClient = new FaunaAccountClient();
const response = await accountClient.listDatabases({
const { listDatabases } = container.resolve("accountAPI");
const response = await listDatabases({
pageSize,
path: database,
});
Expand Down
2 changes: 1 addition & 1 deletion src/commands/local.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import chalk from "chalk";
import { AbortError } from "fauna";

import { container } from "../cli.mjs";
import { pushSchema } from "../commands/schema/push.mjs";
import { container } from "../config/container.mjs";
import { ensureContainerRunning } from "../lib/docker-containers.mjs";
import { CommandError, ValidationError } from "../lib/errors.mjs";
import { colorize, Format } from "../lib/formatting/colorize.mjs";
Expand Down
27 changes: 20 additions & 7 deletions src/commands/login.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//@ts-check

import { container } from "../cli.mjs";
import { FaunaAccountClient } from "../lib/fauna-account-client.mjs";
import { container } from "../config/container.mjs";
import { getToken, startOAuthRequest } from "../lib/account-api.mjs";

async function doLogin(argv) {
const logger = container.resolve("logger");
Expand All @@ -13,16 +13,29 @@ async function doLogin(argv) {
const credentials = container.resolve("credentials");
const oAuth = container.resolve("oauthClient");
oAuth.server.on("ready", async () => {
const authCodeParams = oAuth.getOAuthParams();
const dashboardOAuthURL =
await FaunaAccountClient.startOAuthRequest(authCodeParams);
const authCodeParams = oAuth.getOAuthParams({ clientId: argv.clientId });
const dashboardOAuthURL = await startOAuthRequest(authCodeParams);
open(dashboardOAuthURL);
logger.stdout(`To login, open your browser to:\n${dashboardOAuthURL}`);
});
oAuth.server.on("auth_code_received", async () => {
try {
const tokenParams = oAuth.getTokenParams();
const accessToken = await FaunaAccountClient.getToken(tokenParams);
const { clientId, clientSecret, authCode, redirectURI, codeVerifier } =
oAuth.getTokenParams({
clientId: argv.clientId,
clientSecret: argv.clientSecret,
});

/* eslint-disable camelcase */
const accessToken = await getToken({
client_id: clientId,
client_secret: clientSecret,
code: authCode,
redirect_uri: redirectURI,
code_verifier: codeVerifier,
});
/* eslint-enable camelcase */

await credentials.login(accessToken);
} catch (err) {
logger.stderr(err);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/query.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ts-check

import { container } from "../cli.mjs";
import { container } from "../config/container.mjs";
import {
CommandError,
isUnknownError,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/schema/abandon.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ts-check

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { getSecret } from "../../lib/fauna-client.mjs";

Expand Down
2 changes: 1 addition & 1 deletion src/commands/schema/commit.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ts-check

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { getSecret } from "../../lib/fauna-client.mjs";

Expand Down
2 changes: 1 addition & 1 deletion src/commands/schema/diff.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import chalk from "chalk";

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { ValidationError } from "../../lib/errors.mjs";
import { getSecret } from "../../lib/fauna-client.mjs";
import { reformatFSL } from "../../lib/schema.mjs";
Expand Down
2 changes: 1 addition & 1 deletion src/commands/schema/pull.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ts-check

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { getSecret } from "../../lib/fauna-client.mjs";
import { LOCAL_SCHEMA_OPTIONS } from "./schema.mjs";

Expand Down
2 changes: 1 addition & 1 deletion src/commands/schema/push.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import path from "path";

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { ValidationError } from "../../lib/errors.mjs";
import { getSecret } from "../../lib/fauna-client.mjs";
import { reformatFSL } from "../../lib/schema.mjs";
Expand Down
2 changes: 1 addition & 1 deletion src/commands/schema/status.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import chalk from "chalk";
import path from "path";

import { container } from "../../cli.mjs";
import { container } from "../../config/container.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { getSecret } from "../../lib/fauna-client.mjs";
import { reformatFSL } from "../../lib/schema.mjs";
Expand Down
2 changes: 1 addition & 1 deletion src/commands/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import repl from "node:repl";

import * as esprima from "esprima";

import { container } from "../cli.mjs";
import { container } from "../config/container.mjs";
import { formatQueryResponse, getSecret } from "../lib/fauna-client.mjs";
import { clearHistoryStorage, initHistoryStorage } from "../lib/file-util.mjs";
import { validateDatabaseOrSecret } from "../lib/middleware.mjs";
Expand Down
10 changes: 10 additions & 0 deletions src/config/container.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @typedef {import('awilix').AwilixContainer<import('./config/setup-container.mjs').modifiedInjectables> } container */
/** @type {container} */
export let container;

/**
* @param {container} newContainer - The new container to set.
*/
export const setContainer = (newContainer) => {
container = newContainer;
};
9 changes: 5 additions & 4 deletions src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import open from "open";
import updateNotifier from "update-notifier";

import { parseYargs } from "../cli.mjs";
import { makeAccountRequest } from "../lib/account.mjs";
import accountAPI from "../lib/account-api.mjs";
import { Credentials } from "../lib/auth/credentials.mjs";
import OAuthClient from "../lib/auth/oauth-client.mjs";
import { makeRetryableFaunaRequest } from "../lib/db.mjs";
import { makeRetryableFaunaRequest } from "../lib/core-api.mjs";
import * as faunaV10 from "../lib/fauna.mjs";
import {
formatError,
Expand Down Expand Up @@ -73,7 +73,6 @@ export const injectables = {
updateNotifier: awilix.asValue(updateNotifier),
fauna: awilix.asValue(fauna),
faunadb: awilix.asValue(faunadb),
codeToAnsi: awilix.asValue(codeToAnsi),

// generic lib (homemade utilities)
parseYargs: awilix.asValue(parseYargs),
Expand All @@ -86,8 +85,9 @@ export const injectables = {
},
{ lifetime: Lifetime.SINGLETON },
),
codeToAnsi: awilix.asValue(codeToAnsi),
oauthClient: awilix.asClass(OAuthClient, { lifetime: Lifetime.SCOPED }),
makeAccountRequest: awilix.asValue(makeAccountRequest),
accountAPI: awilix.asValue(accountAPI),
makeFaunaRequest: awilix.asValue(makeRetryableFaunaRequest),
errorHandler: awilix.asValue((_error, exitCode) => exit(exitCode)),

Expand All @@ -96,6 +96,7 @@ export const injectables = {
credentials: awilix.asClass(Credentials, {
lifetime: Lifetime.SINGLETON,
}),

// utilities for interacting with Fauna
runQueryFromString: awilix.asValue(runQueryFromString),
formatError: awilix.asValue(formatError),
Expand Down
14 changes: 7 additions & 7 deletions src/config/setup-test-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { spy, stub } from "sinon";

import { f, InMemoryWritableStream } from "../../test/helpers.mjs";
import { parseYargs } from "../cli.mjs";
import { makeRetryableFaunaRequest } from "../lib/db.mjs";
import { makeRetryableFaunaRequest } from "../lib/core-api.mjs";
import * as faunaClientV10 from "../lib/fauna.mjs";
import { formatQueryInfo } from "../lib/fauna-client.mjs";
import * as faunaClientV4 from "../lib/faunadb.mjs";
Expand Down Expand Up @@ -79,11 +79,6 @@ export function setupTestContainer() {
}),
codeToAnsi: awilix.asValue(stub().returnsArg(0)),
logger: awilix.asFunction((cradle) => spy(buildLogger(cradle))).singleton(),
AccountClient: awilix.asValue(() => ({
startOAuthRequest: stub(),
getToken: stub(),
getSession: stub(),
})),
oauthClient: awilix.asFunction(stub()),
docker: awilix.asValue({
createContainer: stub(),
Expand All @@ -96,6 +91,12 @@ export function setupTestContainer() {
pull: stub(),
}),
credentials: awilix.asClass(stub()).singleton(),
accountAPI: awilix.asValue({
listDatabases: stub(),
createKey: stub(),
refreshSession: stub(),
getSession: stub(),
}),
errorHandler: awilix.asValue((error, exitCode) => {
error.code = exitCode;
throw error;
Expand All @@ -104,7 +105,6 @@ export function setupTestContainer() {
fetch: awilix.asValue(stub().resolves(f({}))),
gatherFSL: awilix.asValue(stub().resolves([])),
makeFaunaRequest: awilix.asValue(spy(makeRetryableFaunaRequest)),
makeAccountRequest: awilix.asValue(stub()),
runQueryFromString: awilix.asValue(stub().resolves({})),
isQueryable: awilix.asValue(stub().resolves()),
formatError: awilix.asValue(stub()),
Expand Down
Loading

0 comments on commit 334ed9f

Please sign in to comment.