Skip to content

Commit

Permalink
Better handle errors from the client and detect multiple operations (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller authored May 9, 2024
1 parent 2d561d5 commit 9c3d761
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/khaki-birds-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/generate-persisted-query-manifest": patch
---

Better report errors that originate from Apollo Client during manifest generation.
5 changes: 5 additions & 0 deletions .changeset/perfect-lies-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/generate-persisted-query-manifest": patch
---

Detect multiple operations during manifest generation and report them as errors.
32 changes: 26 additions & 6 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/generate-persisted-query-manifest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"cosmiconfig-typescript-loader": "^5.0.0",
"globby": "^11.1.0",
"lodash": "^4.17.21",
"semver": "^7.6.0",
"vfile": "^4.2.1",
"vfile-reporter": "^6.0.2"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,84 @@ const query;
await cleanup();
});

test("does not allow multiple operations in a single document", async () => {
const { cleanup, runCommand, writeFile } = await setup();

await writeFile(
"./src/queries.graphql",
`
query GreetingQuery {
greeting
}
query GoodbyeQuery {
goodbye
}
`,
);

await writeFile(
"./src/mutations.graphql",
`
mutation SayHello {
sayHello
}
mutation SayGoodbye {
goodbye
}
`,
);

await writeFile(
"./src/subscriptions.graphql",
`
subscription HelloSubscription {
hello
}
subscription GoodbyeSubscription {
goodbye
}
`,
);
await writeFile(
"./src/mixed.graphql",
`
mutation TestMutation {
test
}
query TestQuery {
test
}
subscription TestSubscription {
test
}
`,
);

const { code, stderr } = await runCommand();

expect(code).toBe(1);
expect(stderr).toMatchInlineSnapshot(`
[
"src/mixed.graphql",
"1:1 error Cannot declare multiple operations in a single document.",
"src/mutations.graphql",
"1:1 error Cannot declare multiple operations in a single document.",
"src/queries.graphql",
"1:1 error Cannot declare multiple operations in a single document.",
"src/subscriptions.graphql",
"1:1 error Cannot declare multiple operations in a single document.",
"✖ 4 errors",
]
`);

await cleanup();
});

test("gathers and reports all errors together", async () => {
const { cleanup, runCommand, writeFile } = await setup();
const anonymousQuery = gql`
Expand Down
45 changes: 42 additions & 3 deletions packages/generate-persisted-query-manifest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFragmentRegistry } from "@apollo/client/cache";
import type { FragmentRegistryAPI } from "@apollo/client/cache";
import semver from "semver";
import {
ApolloClient,
ApolloLink,
Expand All @@ -21,6 +22,7 @@ import {
type DocumentNode,
visit,
GraphQLError,
BREAK,
} from "graphql";
import { first, sortBy } from "lodash";
import { createHash } from "node:crypto";
Expand Down Expand Up @@ -259,10 +261,22 @@ const ERROR_MESSAGES = {
)}".`;
},
parseError(error: Error) {
return `${error.name}: ${error.message}`;
return formatErrorMessage(error);
},
multipleOperations() {
return "Cannot declare multiple operations in a single document.";
},
};

async function enableDevMessages() {
const { loadDevMessages, loadErrorMessages } = await import(
"@apollo/client/dev"
);

loadDevMessages();
loadErrorMessages();
}

function addError(
source: Pick<DocumentSource, "file"> & Partial<DocumentSource>,
message: string,
Expand Down Expand Up @@ -372,6 +386,10 @@ function uniq<T>(arr: T[]) {
return [...new Set(arr)];
}

function formatErrorMessage(error: Error) {
return `${error.name}: ${error.message}`;
}

async function fromFilepathList(
documents: string | string[],
): Promise<DocumentSourceConfig> {
Expand All @@ -385,6 +403,8 @@ async function fromFilepathList(
continue;
}

let documentCount = 0;

visit(source.node, {
FragmentDefinition(node) {
const name = node.name.value;
Expand All @@ -404,6 +424,11 @@ async function fromFilepathList(
OperationDefinition(node) {
const name = node.name?.value;

if (++documentCount > 1) {
addError(source, ERROR_MESSAGES.multipleOperations());
return BREAK;
}

if (!name) {
addError(source, ERROR_MESSAGES.anonymousOperation(node));

Expand Down Expand Up @@ -560,15 +585,29 @@ export async function generatePersistedQueryManifest(
}),
});

if (semver.gte(client.version, "3.8.0")) {
await enableDevMessages();
}

for (const [_, sources] of sortBy([...operationsByName.entries()], first)) {
for (const source of sources) {
if (source.node) {
await client.query({ query: source.node, fetchPolicy: "no-cache" });
try {
await client.query({ query: source.node, fetchPolicy: "no-cache" });
} catch (error) {
if (error instanceof Error) {
addError(source, formatErrorMessage(error));
} else {
addError(source, "Unknown error occured. Please file a bug.");
}
}
}
}
}

maybeReportErrorsAndExit(configFile);
maybeReportErrorsAndExit(
uniq(sources.map((source) => source.file).concat(configFile)),
);

return {
format: "apollo-persisted-query-manifest",
Expand Down

0 comments on commit 9c3d761

Please sign in to comment.