Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom queries and mutations #13029

Merged
merged 27 commits into from
Feb 26, 2024

Conversation

svidgen
Copy link
Contributor

@svidgen svidgen commented Feb 19, 2024

Description of changes

Adds basic custom queries and mutations support.

E.g. schema:

const schema = a.schema({
  EchoResult: a.customType({
    resultContent: a.string().required(),
  }),
  echo: a
    .query()
    .arguments({
      argumentContent: a.string().required(),
    })
    .returns(a.ref('EchoResult'))
    .function('echoFunction')
    .authorization([a.allow.public()]),
  echoString: a.query()
    .arguments({
      inputString: a.string().required()
    })
    .returns(a.string())
    .function('echoStringFunction')
    .authorization([a.allow.public()]),
  PostLikeResult: a.customType({
    likes: a.integer().required(),
  }),
  likePost: a
    .mutation()
    .arguments({
      postId: a.id().required(),
    })
    .returns(a.ref('PostLikeResult'))
    .function('likePost')
    .authorization([a.allow.public()]),
  Post: a
    .model({
      id: a.id().required(),
      content: a.string(),
      comments: a.hasMany('Comment')
    })
    .authorization([a.allow.public('apiKey'), a.allow.owner()]),
  Comment: a.model({
    id: a.id().required(),
    content: a.string().required(),
    post: a.belongsTo('Post')
  }).authorization([a.allow.public('apiKey'), a.allow.owner()]),
  listPostReturnPost: a
    .mutation()
    .arguments({
      postId: a.id().required().array().required(),
    })
    .returns(a.ref('Post'))
    .function('likePostReturnPost')
    .authorization([a.allow.public()]),
});

E.g, client code (implemented by this PR):

    const client = generateClient<Schema>();

    const echoStringResult = await client.queries.echoString({
      inputString: "echoString inputString input",
    });
    const echoStringResultString = echoStringResult.data;

    const echoResult = await client.queries.echo({
      argumentContent: "echo argumentContent input",
    });
    const echoResultString = echoResult.data?.resultContent;

    const likePostResult = await client.mutations.likePost({
      postId: "likePost postId input",
    });
    const likePostResultLikes = likePostResult.data?.likes;

    const listPostReturnPostResult = await client.mutations.listPostReturnPost(
      {
        postId: ["like post return post input"],
      },
      {
        headers: {
          MyCustomHeader: "Custom header value",
        },
      }
    );
    const listPostReturnPostResultPost = listPostReturnPostResult.data;
    const listPostReturnPostResultPostComments =
    await listPostReturnPostResultPost?.comments();

Issue #, if available

Description of how you validated changes

  1. Tests
  2. Sample app

Checklist

  • PR description included
  • yarn test passes
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@svidgen svidgen changed the title Custom queries mutations feat: custom queries and mutations Feb 22, 2024
@svidgen svidgen marked this pull request as ready for review February 22, 2024 21:31
@svidgen svidgen requested review from a team as code owners February 22, 2024 21:31
@svidgen svidgen requested a review from a team as a code owner February 22, 2024 21:42
Copy link
Contributor

@stocaaro stocaaro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a pass. Have a few nits and a couple more substantive comments.

Comment on lines 56 to 58
const ops = {} as CustomOpsProperty<T, OpType>;
for (const operation of Object.values(operations)) {
(ops as any)[operation.name] = customOpFactory(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need the as either way, but why not compose the objects and then as it once its complete on the final assignment instead of starting empty with a proper type and composing it through attribute assignment?

I can't convince myself that there are any real differences, but it seems better to assign the type to something we're confident is right rather than assigning it to something we know doesn't meet the type and then build it up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's visually better to start with {} as any, which ends up being the same lie either way. The only difference in my mind that pushes me towards {} as SomeIntendedType with more casts later on is that it communicates intent of the variable and forces me to check the "yes, this is on purpose" box every time by casting.

packages/api-graphql/src/internals/operations/custom.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/internals/operations/custom.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/internals/operations/custom.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/types/index.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/types/index.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/types/index.ts Outdated Show resolved Hide resolved
Copy link
Member

@david-mcafee david-mcafee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the exception of what Aaron has already commented on, this PR looks good to me. Most of my comments are non-blocking nits. This is also non-blocking, but it looks like something is up with your Prettier configuration. The only truly blocking comment is for removal of the console.log.

packages/api-graphql/src/internals/generateClient.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/internals/generateClient.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/internals/generateClient.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/types/index.ts Outdated Show resolved Hide resolved
packages/api-graphql/src/types/index.ts Outdated Show resolved Hide resolved
* @returns Boolean: `true` if the `o[field]` is a `string`
*/
function hasStringField<Field extends string>(
o: any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any possibility not using any? JS codebase is in the progress of migration to stricter linter rules where any is forbidden.

For example, at this particular spot, the type of o could be Record<unknown, unknown>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The amount of casting we're doing in general, including all the any's is starting to feel somewhat egregious and a little risky. So, I agree with your general point. But, I think we should make an effort of it... I'm actually planning on spending a little time composing a sampling of the scary type/runtime mismatches that concern me, and we can plan something from there for the general case.

For this particular spot, I've taken a couple passes at changing the any to something less broad, and it's more of a riddle than it might be worth right now. It doesn't like Record<unknown, unknown>, and similar changes looks like they end up punting the any elsewhere and causing a rippling effect. It looks like it'll be a similar story with some of the other any's laying around.

And for what it's worth, I don't actually think this particular instance is high risk, since the runtime logic in the guard aligns very narrowly with the type assurance it offer. It's largely intended to take "anything" and just tell you whether "the thing" you give it has a particular [T]: string field on it. Not a high risk any, IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I am taking a pass at clearing up the low hanging fruit on code I touched though. Stay tuned!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not as much wiggle room as I'd have liked on some of those any's. But, I squashed a few of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait. Push failed ..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Pushed. Sorry for the noise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in alignment with this. I would be a fan of adding as much strong typing as we can and where we can't having a dev mode that turns on runtime type validation so that we can run some amount of minimal interface testing that raises when types don't match expectations.

The two cases that are hard:

  • Dynamically typed interfaces, like offering custom methods for retrieving certain graphql queries.
  • JSON input parsing.

In both cases, we could document the type interface in a tool like Zod (or using the same strategies) and use the same interface for enforcing runtime type guarantees when it doesn't meet the type expectations.

david-mcafee
david-mcafee previously approved these changes Feb 23, 2024
Copy link
Member

@david-mcafee david-mcafee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

david-mcafee
david-mcafee previously approved these changes Feb 23, 2024
iartemiev
iartemiev previously approved these changes Feb 23, 2024
stocaaro
stocaaro previously approved these changes Feb 26, 2024
Copy link
Contributor

@stocaaro stocaaro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@svidgen svidgen merged commit 7e4eff3 into aws-amplify:main Feb 26, 2024
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants