@apollo/client-ai-apps 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/pr.yaml +35 -0
- package/.github/workflows/prep-release.yml +4 -3
- package/.github/workflows/release.yaml +8 -5
- package/.github/workflows/verify-changeset.yml +4 -4
- package/CHANGELOG.md +16 -0
- package/dist/utilities/getToolNamesFromDocument.d.ts +1 -1
- package/dist/utilities/getToolNamesFromDocument.d.ts.map +1 -1
- package/dist/utilities/getToolNamesFromDocument.js +14 -5
- package/dist/utilities/getToolNamesFromDocument.js.map +1 -1
- package/dist/vite/apolloClientAiApps.d.ts.map +1 -1
- package/dist/vite/apolloClientAiApps.js +2 -1
- package/dist/vite/apolloClientAiApps.js.map +1 -1
- package/integration-tests/docker-compose.yml +30 -0
- package/integration-tests/global-teardown.js +9 -0
- package/integration-tests/graphql-server/Dockerfile +12 -0
- package/integration-tests/graphql-server/package.json +10 -0
- package/integration-tests/graphql-server/server.ts +22 -0
- package/integration-tests/mcp-config.yaml +29 -0
- package/integration-tests/mock-app/index.html +12 -0
- package/integration-tests/mock-app/package.json +24 -0
- package/integration-tests/mock-app/src/App.tsx +23 -0
- package/integration-tests/mock-app/src/main.tsx +22 -0
- package/integration-tests/mock-app/src/tools/Echo.tsx +33 -0
- package/integration-tests/mock-app/src/tools/Hello.tsx +19 -0
- package/integration-tests/mock-app/src/tools/Private.tsx +34 -0
- package/integration-tests/mock-app/src/tools/SemiPrivate.tsx +34 -0
- package/integration-tests/mock-app/tsconfig.json +25 -0
- package/integration-tests/mock-app/vite.config.ts +22 -0
- package/integration-tests/package-lock.json +5749 -0
- package/integration-tests/package.json +20 -0
- package/integration-tests/playwright.config.ts +23 -0
- package/integration-tests/schema.graphql +10 -0
- package/integration-tests/tests/hello.spec.ts +11 -0
- package/integration-tests/tests/privateDirective.spec.ts +73 -0
- package/integration-tests/tests/variables.spec.ts +24 -0
- package/package.json +5 -5
- package/src/react/__tests__/createHydrationUtils.test.tsx +62 -0
- package/src/utilities/getToolNamesFromDocument.ts +19 -7
- package/src/vite/__tests__/apolloClientAiApps.test.ts +161 -62
- package/src/vite/apolloClientAiApps.ts +2 -1
- package/vitest.config.ts +2 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "integration-tests",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"workspaces": [
|
|
6
|
+
"./graphql-server",
|
|
7
|
+
"./mock-app"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build:lib": "npm run build --prefix ..",
|
|
11
|
+
"build:mock-app": "npm run build --workspace mock-app",
|
|
12
|
+
"pretest": "npm-run-all build:*",
|
|
13
|
+
"test": "playwright test"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@apollo/mcp-impostor-host": "^0.3.0",
|
|
17
|
+
"@playwright/test": "^1.59.1",
|
|
18
|
+
"npm-run-all": "^4.1.5"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests",
|
|
5
|
+
globalTeardown: "./global-teardown.js",
|
|
6
|
+
webServer: [
|
|
7
|
+
{
|
|
8
|
+
command: "npx serve-impostor-host --playwright",
|
|
9
|
+
url: "http://localhost:8080",
|
|
10
|
+
reuseExistingServer: !process.env.CI,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
command: "docker compose up --build",
|
|
14
|
+
url: "http://localhost:8000/health",
|
|
15
|
+
reuseExistingServer: !process.env.CI,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
workers: 1,
|
|
19
|
+
use: {
|
|
20
|
+
browserName: "chromium",
|
|
21
|
+
trace: "retain-on-failure",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { test } from "@apollo/mcp-impostor-host/playwright";
|
|
2
|
+
import { expect } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
const URL = "http://localhost:8000/mcp?app=mock-app&appTarget=mcp";
|
|
5
|
+
|
|
6
|
+
test("renders data from a tool with no arguments", async ({ mcpHost }) => {
|
|
7
|
+
const connection = await mcpHost.connect({ url: URL });
|
|
8
|
+
const { appFrame } = await connection.callTool("Hello");
|
|
9
|
+
|
|
10
|
+
await expect(appFrame.getByTestId("greeting")).toHaveText("Hello, world!");
|
|
11
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test } from "@apollo/mcp-impostor-host/playwright";
|
|
2
|
+
import { expect } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
const URL = "http://localhost:8000/mcp?app=mock-app&appTarget=mcp";
|
|
5
|
+
|
|
6
|
+
test("omits private field in structuredContent, but available to the view", async ({
|
|
7
|
+
mcpHost,
|
|
8
|
+
}) => {
|
|
9
|
+
const connection = await mcpHost.connect({ url: URL });
|
|
10
|
+
const { result, appFrame } = await connection.callTool("SemiPrivate");
|
|
11
|
+
|
|
12
|
+
expect(result.structuredContent).toEqual({
|
|
13
|
+
result: {
|
|
14
|
+
data: {
|
|
15
|
+
user: {
|
|
16
|
+
__typename: "User",
|
|
17
|
+
fullName: "MCP User",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
toolName: "SemiPrivate",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(result._meta).toEqual({
|
|
25
|
+
toolName: "SemiPrivate",
|
|
26
|
+
structuredContent: {
|
|
27
|
+
result: {
|
|
28
|
+
data: {
|
|
29
|
+
user: {
|
|
30
|
+
__typename: "User",
|
|
31
|
+
address: "1234 Main St",
|
|
32
|
+
fullName: "MCP User",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
toolName: "SemiPrivate",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await expect(appFrame.getByTestId("fullName")).toHaveText("MCP User");
|
|
41
|
+
await expect(appFrame.getByTestId("address")).toHaveText("1234 Main St");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("omits full selection sets, but available to the view", async ({
|
|
45
|
+
mcpHost,
|
|
46
|
+
}) => {
|
|
47
|
+
const connection = await mcpHost.connect({ url: URL });
|
|
48
|
+
const { result, appFrame } = await connection.callTool("Private");
|
|
49
|
+
|
|
50
|
+
expect(result.structuredContent).toEqual({
|
|
51
|
+
result: { data: {} },
|
|
52
|
+
toolName: "Private",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(result._meta).toEqual({
|
|
56
|
+
toolName: "Private",
|
|
57
|
+
structuredContent: {
|
|
58
|
+
result: {
|
|
59
|
+
data: {
|
|
60
|
+
user: {
|
|
61
|
+
__typename: "User",
|
|
62
|
+
address: "1234 Main St",
|
|
63
|
+
fullName: "MCP User",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
toolName: "Private",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await expect(appFrame.getByTestId("fullName")).toHaveText("MCP User");
|
|
72
|
+
await expect(appFrame.getByTestId("address")).toHaveText("1234 Main St");
|
|
73
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { test } from "@apollo/mcp-impostor-host/playwright";
|
|
2
|
+
import { expect } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
const URL = "http://localhost:8000/mcp?app=mock-app&appTarget=mcp";
|
|
5
|
+
|
|
6
|
+
test("renders data from a tool with arguments", async ({ mcpHost }) => {
|
|
7
|
+
const connection = await mcpHost.connect({ url: URL });
|
|
8
|
+
const { result, appFrame } = await connection.callTool("Echo", {
|
|
9
|
+
message: "Hello!",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(result.structuredContent).toStrictEqual({
|
|
13
|
+
toolName: "Echo",
|
|
14
|
+
result: {
|
|
15
|
+
data: {
|
|
16
|
+
echo: "Hello! (Hello!)",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
expect(result._meta).toStrictEqual({
|
|
21
|
+
toolName: "Echo",
|
|
22
|
+
});
|
|
23
|
+
await expect(appFrame.getByTestId("echo")).toHaveText("Hello! (Hello!)");
|
|
24
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "@apollo/client-ai-apps",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apollo",
|
|
7
7
|
"graphql",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"attw": "npm run build && attw --pack . --ignore-rules cjs-resolves-to-esm --profile node16"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@apollo/client": "^4.
|
|
107
|
+
"@apollo/client": "^4.1.7",
|
|
108
108
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
109
109
|
"@modelcontextprotocol/ext-apps": "^1.0.1",
|
|
110
110
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
"esbuild": "^0.25.12",
|
|
120
120
|
"graphql": "^16.12.0",
|
|
121
121
|
"happy-dom": "^20.0.10",
|
|
122
|
+
"jiti": "^2.6.1",
|
|
122
123
|
"memfs": "^4.56.10",
|
|
123
124
|
"mock-require": "^3.0.3",
|
|
124
125
|
"prettier": "^3.7.4",
|
|
@@ -144,9 +145,8 @@
|
|
|
144
145
|
}
|
|
145
146
|
},
|
|
146
147
|
"dependencies": {
|
|
147
|
-
"@graphql-codegen/cli": "^
|
|
148
|
-
"@graphql-codegen/typescript": "^
|
|
149
|
-
"@graphql-codegen/typescript-operations": "^5.0.9",
|
|
148
|
+
"@graphql-codegen/cli": "^7.1.0",
|
|
149
|
+
"@graphql-codegen/typescript-operations": "^6.0.3",
|
|
150
150
|
"@graphql-tools/graphql-tag-pluck": "^8.3.23",
|
|
151
151
|
"@wry/equality": "^0.5.7",
|
|
152
152
|
"cosmiconfig": "^9.0.0",
|
|
@@ -77,6 +77,68 @@ eachHostEnv((setupHost, ApolloClient) => {
|
|
|
77
77
|
await expect(takeSnapshot).not.toRerender();
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
test("returns tool input value when @tool name is defined by operation name", async () => {
|
|
81
|
+
using _ = spyOnConsole("debug");
|
|
82
|
+
|
|
83
|
+
const query: TypedDocumentNode<
|
|
84
|
+
{ products: Array<{ __typename: "Product"; id: string }> },
|
|
85
|
+
{ category: string; page: number; sortBy: string }
|
|
86
|
+
> = gql`
|
|
87
|
+
"Get products by category"
|
|
88
|
+
query Products($category: String!, $page: Int!, $sortBy: String!) @tool {
|
|
89
|
+
products(category: $category, page: $page, sortBy: $sortBy) {
|
|
90
|
+
id
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const client = new ApolloClient({
|
|
96
|
+
cache: new InMemoryCache(),
|
|
97
|
+
manifest: mockApplicationManifest({
|
|
98
|
+
operations: [parseManifestOperation(query)],
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
using env = await setupHost({
|
|
103
|
+
client,
|
|
104
|
+
toolCall: {
|
|
105
|
+
name: "Products",
|
|
106
|
+
input: { category: "electronics", page: 1, sortBy: "title" },
|
|
107
|
+
result: { structuredContent: { result: { data: { products: [] } } } },
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
const { host, params } = env;
|
|
111
|
+
|
|
112
|
+
host.sendToolInput(params.toolInput);
|
|
113
|
+
host.sendToolResult(params.toolResult);
|
|
114
|
+
|
|
115
|
+
const { useHydratedVariables } = createHydrationUtils(query);
|
|
116
|
+
|
|
117
|
+
using _disabledAct = disableActEnvironment();
|
|
118
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(
|
|
119
|
+
() =>
|
|
120
|
+
useHydratedVariables({
|
|
121
|
+
category: "music",
|
|
122
|
+
page: 1,
|
|
123
|
+
sortBy: "name",
|
|
124
|
+
}),
|
|
125
|
+
{
|
|
126
|
+
wrapper: ({ children }) => (
|
|
127
|
+
<ApolloProvider client={client}>{children}</ApolloProvider>
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const [variables] = await takeSnapshot();
|
|
133
|
+
expect(variables).toStrictEqual({
|
|
134
|
+
category: "electronics",
|
|
135
|
+
page: 1,
|
|
136
|
+
sortBy: "title",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await expect(takeSnapshot).not.toRerender();
|
|
140
|
+
});
|
|
141
|
+
|
|
80
142
|
test("returns user-provided variables when tool name does not match", async () => {
|
|
81
143
|
using _ = spyOnConsole("debug");
|
|
82
144
|
const query = gql`
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { getOperationDefinition } from "@apollo/client/utilities/internal";
|
|
2
|
-
import { Kind, type DocumentNode } from "graphql";
|
|
2
|
+
import { Kind, type DirectiveNode, type DocumentNode } from "graphql";
|
|
3
3
|
|
|
4
4
|
export function getToolNamesFromDocument(document: DocumentNode) {
|
|
5
5
|
const operationDef = getOperationDefinition(document);
|
|
6
|
+
const operationName = operationDef?.name?.value;
|
|
7
|
+
const toolDirectives =
|
|
8
|
+
operationDef?.directives?.filter((d) => d.name.value === "tool") ?? [];
|
|
9
|
+
|
|
10
|
+
if (toolDirectives.length === 1) {
|
|
11
|
+
return new Set([
|
|
12
|
+
getToolNameFromDirective(toolDirectives[0]) ?? operationName,
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
return new Set(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return nameArg?.value.kind === Kind.STRING ? [nameArg.value.value] : [];
|
|
13
|
-
})
|
|
17
|
+
toolDirectives.flatMap((d) => {
|
|
18
|
+
const name = getToolNameFromDirective(d);
|
|
19
|
+
return name ? [name] : [];
|
|
20
|
+
})
|
|
14
21
|
);
|
|
15
22
|
}
|
|
23
|
+
|
|
24
|
+
function getToolNameFromDirective(directive: DirectiveNode) {
|
|
25
|
+
const nameArg = directive.arguments?.find((arg) => arg.name.value === "name");
|
|
26
|
+
return nameArg?.value.kind === Kind.STRING ? nameArg.value.value : undefined;
|
|
27
|
+
}
|
|
@@ -2061,43 +2061,16 @@ describe("tool input types", () => {
|
|
|
2061
2061
|
);
|
|
2062
2062
|
expect(content).toMatchInlineSnapshot(`
|
|
2063
2063
|
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
export type InputMaybe<T> = Maybe<T>;
|
|
2067
|
-
|
|
2068
|
-
export type Exact<T extends {
|
|
2064
|
+
/** Internal type. DO NOT USE DIRECTLY. */
|
|
2065
|
+
type Exact<T extends {
|
|
2069
2066
|
[key: string]: unknown;
|
|
2070
2067
|
}> = {
|
|
2071
2068
|
[K in keyof T]: T[K];
|
|
2072
2069
|
};
|
|
2073
2070
|
|
|
2074
|
-
/** All built-in and custom scalars, mapped to their actual values */
|
|
2075
|
-
export type Scalars = {
|
|
2076
|
-
ID: {
|
|
2077
|
-
input: string;
|
|
2078
|
-
output: string;
|
|
2079
|
-
};
|
|
2080
|
-
String: {
|
|
2081
|
-
input: string;
|
|
2082
|
-
output: string;
|
|
2083
|
-
};
|
|
2084
|
-
Boolean: {
|
|
2085
|
-
input: boolean;
|
|
2086
|
-
output: boolean;
|
|
2087
|
-
};
|
|
2088
|
-
Int: {
|
|
2089
|
-
input: number;
|
|
2090
|
-
output: number;
|
|
2091
|
-
};
|
|
2092
|
-
Float: {
|
|
2093
|
-
input: number;
|
|
2094
|
-
output: number;
|
|
2095
|
-
};
|
|
2096
|
-
};
|
|
2097
|
-
|
|
2098
2071
|
export type CreateTodoMutationVariables = Exact<{
|
|
2099
|
-
title:
|
|
2100
|
-
description?:
|
|
2072
|
+
title: string;
|
|
2073
|
+
description?: string | null | undefined;
|
|
2101
2074
|
}>;"
|
|
2102
2075
|
`);
|
|
2103
2076
|
});
|
|
@@ -2126,6 +2099,26 @@ describe("tool input types", () => {
|
|
|
2126
2099
|
});
|
|
2127
2100
|
await server.listen();
|
|
2128
2101
|
|
|
2102
|
+
expect(
|
|
2103
|
+
fs.readFileSync(
|
|
2104
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
2105
|
+
"utf-8"
|
|
2106
|
+
)
|
|
2107
|
+
).toMatchInlineSnapshot(`
|
|
2108
|
+
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2109
|
+
/** Internal type. DO NOT USE DIRECTLY. */
|
|
2110
|
+
type Exact<T extends {
|
|
2111
|
+
[key: string]: unknown;
|
|
2112
|
+
}> = {
|
|
2113
|
+
[K in keyof T]: T[K];
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
export type CreateTodoMutationVariables = Exact<{
|
|
2117
|
+
title: string;
|
|
2118
|
+
description?: string | null | undefined;
|
|
2119
|
+
}>;"
|
|
2120
|
+
`);
|
|
2121
|
+
|
|
2129
2122
|
const content = fs.readFileSync(
|
|
2130
2123
|
".apollo-client-ai-apps/types/register.d.ts",
|
|
2131
2124
|
"utf-8"
|
|
@@ -2147,6 +2140,71 @@ describe("tool input types", () => {
|
|
|
2147
2140
|
`);
|
|
2148
2141
|
});
|
|
2149
2142
|
|
|
2143
|
+
test("uses operation name and description for tool name/description when @tool is used without arguments", async () => {
|
|
2144
|
+
vol.fromJSON({
|
|
2145
|
+
"package.json": mockPackageJson(),
|
|
2146
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2147
|
+
"""
|
|
2148
|
+
Creates a todo
|
|
2149
|
+
"""
|
|
2150
|
+
mutation CreateTodo($title: String!, $description: String) @tool {
|
|
2151
|
+
createTodo(title: $title, description: $description) {
|
|
2152
|
+
id
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
`),
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
await using server = await setupServer({
|
|
2159
|
+
plugins: [
|
|
2160
|
+
apolloClientAiApps({
|
|
2161
|
+
targets: ["mcp"],
|
|
2162
|
+
appsOutDir: "dist/apps",
|
|
2163
|
+
schema,
|
|
2164
|
+
}),
|
|
2165
|
+
],
|
|
2166
|
+
});
|
|
2167
|
+
await server.listen();
|
|
2168
|
+
|
|
2169
|
+
expect(
|
|
2170
|
+
fs.readFileSync(
|
|
2171
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
2172
|
+
"utf-8"
|
|
2173
|
+
)
|
|
2174
|
+
).toMatchInlineSnapshot(`
|
|
2175
|
+
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2176
|
+
/** Internal type. DO NOT USE DIRECTLY. */
|
|
2177
|
+
type Exact<T extends {
|
|
2178
|
+
[key: string]: unknown;
|
|
2179
|
+
}> = {
|
|
2180
|
+
[K in keyof T]: T[K];
|
|
2181
|
+
};
|
|
2182
|
+
|
|
2183
|
+
export type CreateTodoMutationVariables = Exact<{
|
|
2184
|
+
title: string;
|
|
2185
|
+
description?: string | null | undefined;
|
|
2186
|
+
}>;"
|
|
2187
|
+
`);
|
|
2188
|
+
|
|
2189
|
+
expect(
|
|
2190
|
+
fs.readFileSync(".apollo-client-ai-apps/types/register.d.ts", "utf-8")
|
|
2191
|
+
).toMatchInlineSnapshot(`
|
|
2192
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2193
|
+
import "@apollo/client-ai-apps";
|
|
2194
|
+
|
|
2195
|
+
import type { CreateTodoMutationVariables } from "./operation-types.js";
|
|
2196
|
+
|
|
2197
|
+
declare module "@apollo/client-ai-apps" {
|
|
2198
|
+
interface Register {
|
|
2199
|
+
toolName: "CreateTodo";
|
|
2200
|
+
toolInputs: {
|
|
2201
|
+
CreateTodo: CreateTodoMutationVariables;
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
}"
|
|
2205
|
+
`);
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2150
2208
|
test("generates register.d.ts with operations that contain fragments", async () => {
|
|
2151
2209
|
vol.fromJSON({
|
|
2152
2210
|
"package.json": mockPackageJson(),
|
|
@@ -2201,43 +2259,16 @@ describe("tool input types", () => {
|
|
|
2201
2259
|
)
|
|
2202
2260
|
).toMatchInlineSnapshot(`
|
|
2203
2261
|
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
export type InputMaybe<T> = Maybe<T>;
|
|
2207
|
-
|
|
2208
|
-
export type Exact<T extends {
|
|
2262
|
+
/** Internal type. DO NOT USE DIRECTLY. */
|
|
2263
|
+
type Exact<T extends {
|
|
2209
2264
|
[key: string]: unknown;
|
|
2210
2265
|
}> = {
|
|
2211
2266
|
[K in keyof T]: T[K];
|
|
2212
2267
|
};
|
|
2213
2268
|
|
|
2214
|
-
/** All built-in and custom scalars, mapped to their actual values */
|
|
2215
|
-
export type Scalars = {
|
|
2216
|
-
ID: {
|
|
2217
|
-
input: string;
|
|
2218
|
-
output: string;
|
|
2219
|
-
};
|
|
2220
|
-
String: {
|
|
2221
|
-
input: string;
|
|
2222
|
-
output: string;
|
|
2223
|
-
};
|
|
2224
|
-
Boolean: {
|
|
2225
|
-
input: boolean;
|
|
2226
|
-
output: boolean;
|
|
2227
|
-
};
|
|
2228
|
-
Int: {
|
|
2229
|
-
input: number;
|
|
2230
|
-
output: number;
|
|
2231
|
-
};
|
|
2232
|
-
Float: {
|
|
2233
|
-
input: number;
|
|
2234
|
-
output: number;
|
|
2235
|
-
};
|
|
2236
|
-
};
|
|
2237
|
-
|
|
2238
2269
|
export type CreateTodoMutationVariables = Exact<{
|
|
2239
|
-
title:
|
|
2240
|
-
description?:
|
|
2270
|
+
title: string;
|
|
2271
|
+
description?: string | null | undefined;
|
|
2241
2272
|
}>;"
|
|
2242
2273
|
`);
|
|
2243
2274
|
});
|
|
@@ -2546,6 +2577,74 @@ describe("tool input types", () => {
|
|
|
2546
2577
|
}"
|
|
2547
2578
|
`);
|
|
2548
2579
|
});
|
|
2580
|
+
|
|
2581
|
+
test("generates matching variable type names for operations with lowercase names", async () => {
|
|
2582
|
+
vol.fromJSON({
|
|
2583
|
+
"package.json": mockPackageJson(),
|
|
2584
|
+
"src/my-component.tsx": declareOperation(gql`
|
|
2585
|
+
"Creates a todo"
|
|
2586
|
+
mutation createTodo($title: String!, $description: String) @tool {
|
|
2587
|
+
createTodo(title: $title, description: $description) {
|
|
2588
|
+
id
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
`),
|
|
2592
|
+
});
|
|
2593
|
+
|
|
2594
|
+
await using server = await setupServer({
|
|
2595
|
+
plugins: [
|
|
2596
|
+
apolloClientAiApps({
|
|
2597
|
+
targets: ["mcp"],
|
|
2598
|
+
appsOutDir: "dist/apps",
|
|
2599
|
+
schema,
|
|
2600
|
+
}),
|
|
2601
|
+
],
|
|
2602
|
+
});
|
|
2603
|
+
await server.listen();
|
|
2604
|
+
|
|
2605
|
+
const operationTypes = fs.readFileSync(
|
|
2606
|
+
".apollo-client-ai-apps/types/operation-types.d.ts",
|
|
2607
|
+
"utf-8"
|
|
2608
|
+
);
|
|
2609
|
+
const register = fs.readFileSync(
|
|
2610
|
+
".apollo-client-ai-apps/types/register.d.ts",
|
|
2611
|
+
"utf-8"
|
|
2612
|
+
);
|
|
2613
|
+
|
|
2614
|
+
// operation-types.d.ts should export a variables type for the operation.
|
|
2615
|
+
expect(operationTypes).toMatchInlineSnapshot(`
|
|
2616
|
+
"// Auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2617
|
+
/** Internal type. DO NOT USE DIRECTLY. */
|
|
2618
|
+
type Exact<T extends {
|
|
2619
|
+
[key: string]: unknown;
|
|
2620
|
+
}> = {
|
|
2621
|
+
[K in keyof T]: T[K];
|
|
2622
|
+
};
|
|
2623
|
+
|
|
2624
|
+
export type createTodoMutationVariables = Exact<{
|
|
2625
|
+
title: string;
|
|
2626
|
+
description?: string | null | undefined;
|
|
2627
|
+
}>;"
|
|
2628
|
+
`);
|
|
2629
|
+
|
|
2630
|
+
// register.d.ts must import the same variables type name that
|
|
2631
|
+
// operation-types.d.ts exports.
|
|
2632
|
+
expect(register).toMatchInlineSnapshot(`
|
|
2633
|
+
"// This file is auto-generated by @apollo/client-ai-apps. Do not edit manually.
|
|
2634
|
+
import "@apollo/client-ai-apps";
|
|
2635
|
+
|
|
2636
|
+
import type { createTodoMutationVariables } from "./operation-types.js";
|
|
2637
|
+
|
|
2638
|
+
declare module "@apollo/client-ai-apps" {
|
|
2639
|
+
interface Register {
|
|
2640
|
+
toolName: "createTodo";
|
|
2641
|
+
toolInputs: {
|
|
2642
|
+
createTodo: createTodoMutationVariables;
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
}"
|
|
2646
|
+
`);
|
|
2647
|
+
});
|
|
2549
2648
|
});
|
|
2550
2649
|
|
|
2551
2650
|
describe("html transforms", () => {
|
|
@@ -299,10 +299,11 @@ async function generateOperationTypes(
|
|
|
299
299
|
documents,
|
|
300
300
|
generates: {
|
|
301
301
|
"operation-types.d.ts": {
|
|
302
|
-
plugins: ["typescript
|
|
302
|
+
plugins: ["typescript-operations"],
|
|
303
303
|
config: {
|
|
304
304
|
nonOptionalTypename: true,
|
|
305
305
|
skipTypeNameForRoot: true,
|
|
306
|
+
namingConvention: "keep",
|
|
306
307
|
} satisfies TypeScriptDocumentsPluginConfig,
|
|
307
308
|
},
|
|
308
309
|
},
|
package/vitest.config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineConfig } from "vitest/config";
|
|
1
|
+
import { defineConfig, defaultExclude } from "vitest/config";
|
|
2
2
|
import react from "@vitejs/plugin-react";
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
@@ -8,6 +8,7 @@ export default defineConfig({
|
|
|
8
8
|
setupFiles: ["./vitest-setup.ts"],
|
|
9
9
|
mockReset: true,
|
|
10
10
|
unstubGlobals: true,
|
|
11
|
+
exclude: [...defaultExclude, "integration-tests/**"],
|
|
11
12
|
tags: [
|
|
12
13
|
{
|
|
13
14
|
name: "flaky",
|