Open brunostuani opened 5 years ago
This seems a bug in the angular service generation. The fragment definition is missing in the generated API queries. It works if I manually edit the generated service and add the missing fragment after the query.
@brunostuani Currently Amplify Codegen does not support fragments. Marking this as an enhancement.
As my GraphQL schema is getting bigger, I was looking for a similar solution and just discovered fragments. This would be a very nice addition to the CLI!
Is there a way to make it work by bypassing the codegen part, or are fragments simply not usable on Amplify ? Here is the alternative I was considering (a client query builder): https://github.com/atulmy/gql-query-builder But having this handled server-side would be much better.
I have been using a hacky implementation for some time now and finally managed to assemble it as a plugin https://github.com/artemkloko/amplify-graphql-fragments-generator
It supports only TypeScript and is not for production. Still would be nice if someone could test it on a playground project or comment on the strategy itself.
@artemkloko I'm really liking the look of that library! I have also created your first issue! (sorry!) - https://github.com/artemkloko/amplify-graphql-fragments-generator/issues/1
let me know if you need any help with the issue 👍
I found a different way of doing this myself. I'm leaving it here for anyone that wants to use it as well. So far, it's been working for us, and it preserves the types we expect to see, with minimal effort and minimal verbosity.
It takes care of the the SERVER-SIDE problem of only asking for what you need, rather than having to ask for everything all the time ...which matters. A lot, in fact. 😎
We're doing this in a Nuxt3 SSR project, so there's a heck of a lot of bells and whistles.
codegen
-generated Query// Import the necessary modules from 'graphql' and '@aws-amplify/api-graphql'
import { parse, print } from "graphql";
import type { GraphQLResult } from "@aws-amplify/api-graphql";
// Complex/Contrived Usage Examples:
// import auto-generated query from Amplify
import { getRoom } from "~/graphql/queries";
// Define a type for generated queries. This type is a string (the query itself)
// and an object with __generatedQueryOutput and __generatedQueryInput properties.
// OutputType is the type of the data returned by the query, and InputType is the
// type of the variables passed to the query.
type CodeGenQuery<OutputType, InputType> = string & {
__generatedQueryOutput: OutputType;
__generatedQueryInput: InputType;
};
/**
* Use a generic GraphQL query to dynamically generate lean queries with partial return fields and explicit sub-fields.
*
* This function takes a typed "full" GQL query and its typed query variables.
* It dynamically generates a new GQL query to return only the requested fields, then
* executes that query using the lean version of the query string. The lean query
* string only includes and returns the fields specified in the `fields` parameter, putting
* that concern and work rightfully on the server, rather than asking for everything and dealing with that
* on the client. Most importantly, it utilizes the existing types generated in the GQL introspection
* schema and requires no Type Assertions on the "lean query response" object. It also cuts down on DB usage
* and load by ORDERS of magnitude, while still allowing for DEEP and extensible query depths (maxDepth) without
* cost or performance impact.
*
* We preserve/maintain the original types on graphQL's auto-generated queries, saving you
* many hours of pointless work, and many hundreds of lines of repetitive, useless query permutations.
*
* @param args - An object with the following properties:
* - query: The original, typed query auto-generated/CodeGen-ed by GQL.
* - variables: The typed variables to pass to the query.
* - fields: The fields or nested fields to include in the lean query string.
*
* @returns A promise that resolves to the correctly-typed result of the GraphQL query.
*/
export default async function useLeanQuery<T, V>(args: {
query: CodeGenQuery<T, V>;
variables: V;
fields: string[];
}): Promise<GraphQLResult<T>> {
// Parse the query string into a GraphQL Abstract Syntax Tree (AST)
const document = parse(args.query);
// Find the first field in the query
const firstDefinition = document.definitions[0];
// Check if the first definition has a selection set
if ("selectionSet" in firstDefinition) {
const firstField = firstDefinition.selectionSet.selections[0];
// Check if the first field has a selection set
if (firstField && "selectionSet" in firstField && firstField.selectionSet) {
// Replace the selections of the first field with the specified fields
firstField.selectionSet.selections = args.fields.map((field) => ({
kind: "Field",
name: { kind: "Name", value: field },
}));
}
}
// Convert the modified GraphQL AST back into a string
const leanQuery = print(document);
// Perform the GraphQL query using the lean query and the provided variables
// useAPI() is a composable that returns an instance of the Amplify GraphQL API client class
return (await useAPI().graphql({
query: leanQuery,
variables: args.variables,
})) as GraphQLResult<T>;
}
// Contrived/Complex Usage Examples:
/**
* A chat room (Room) model has many associated Users and Messages.
* Message and User models associated to a Room model record also carry many more associations and nested Models of
* their own.
*
* We just want the following fields and data from the chat room, without also having to return LOADS of User model and
* Message model instances, each with their own sub-nested sets of data.
*
* When you use the auto-gen queries, you don't get to declare the return fields...so you get the whole jungle,
* not just the banana.
*
* That's expensive on the client, expensive on the API, and expensive on Dynamo.
* So here, we query on return fields, AND nested fields on those
*/
import { getRoom } from "~/graphql/queries";
async function getRoomData() {
const roomAgain = await useLeanQuery({
query: getRoom,
variables: { id: "ef6338c3-7f7f-4b44-bc6c-c7feeba8dc45" },
fields: [
"name",
"id",
"users { items { id, user { firstName, lastName } } }",
"messages { items { id, userId, content } }",
],
});
/**
* Instances of the User model carry massive amounts of nested data via Associations (Messages, Rooms, Uploads, etc.).
* When you use the auto-gen queries, you don't get to declare the return fields...so you get the whole jungle,
* not just the banana.
*
* ...we don't want all of them OR need ANY of them for this query, so why do we *have* to receive all of it?
*/
import { getUser } from "~/graphql/queries";
const getUserInfo = await useLeanQuery({
query: getUser,
variables: { id: "61cccc43-6b4d-4c46-bbc8-d15cc7ebeb9e" },
fields: [
"id",
"emailAddress",
"phoneNumber",
"firstName",
"lastName",
"birthdate",
"profilePicture",
"createdAt",
"updatedAt",
],
});
}
... That's it!
import { getRoom } from '@/graphql/queries'
// if you're not using Nuxt, just import the useLeanQuery function from wherever you put it
const getRoomName = await useLeanQuery({
query: getRoom,
variables: {
id: 'ef6338c3-7f7f-4b44-bc6c-c7feeba8dc45',
},
fields: ['name', 'id'],
})
// Notice how there's no issue with IntelliSense or with Type inference!
console.log('getRoomName', getRoomName.data.getRoom)
getRoomName { name: 'TEST-ROOM', id: 'ef6338c3-7f7f-4b44-bc6c-c7feeba8dc45' }
type Room @model @auth(rules: [{ allow: public }]) {
id: ID!
name: String
users: [RoomUser] @hasMany(indexName: "byRoom", fields: ["id"])
messages: [Message] @hasMany(indexName: "byRoom", fields: ["id"])
group: Group @belongsTo(fields: ["groupId"])
groupId: ID @index(name: "byGroup")
project: Project @belongsTo(fields: ["projectId"])
projectId: ID @index(name: "byProject")
}
🪄 MAGIC.
Leaving this here for anyone else who has been stuck like so many other people asking for this same thing, for so long.
You can even do things like this (for nested fields on models via relationships):
async function getRoomData() {
const roomAgain = await useLeanQuery({
query: getRoom,
variables: {
id: 'ef6338c3-7f7f-4b44-bc6c-c7feeba8dc45',
},
fields: [
'name',
'id',
'users { items { id, user { firstName, lastName } } }',
'messages { items { id, userId, content } }',
],
})
Hey @brunostuani 👋, Apologies for the significant delay in responding. The issue has been resolved in the latest Amplify CLI version. Please test it with the latest CLI and let us know if there are any other issues.
Which Category is your question related to? GraphQL transformer codegen
Provide additional details e.g. code snippets I'm adding a fragment in a custom GraphQL query file. For example:
Codegen gracefully generates SheetPartsFragment into my API, but when requesting the qery through the generated API, I get this error:
core.js:15724 ERROR Error: Uncaught (in promise): Object: {"data":null,"errors":[{"path":null,"locations":[{"line":9,"column":9,"sourceName":null}],"message":"Validation error of type UndefinedFragment: Undefined fragment SheetParts @ 'getList/sheets/items'"}]}
Am I missing something?
Thanks