Giter Club home page Giter Club logo

prisma-appsync's Introduction

Hi 🖖

I'm Sylvain (:seel-vahn), hands-on CTO and Product Engineer with 15+ years of experience building things for the web. Creator of the open-source ◭ Prisma-AppSync library. Founder at Kuizto (launching soon). Previously CTO at Travis; Tech. Director at Clemenger BBDO.

  • 🍉 Founder at Kuizto, kinda like Spotify or Netflix, but for your daily cooking!
  • ⚡️ Worked on a wide variety of projects with Airbnb, MARS, Amazon and many other clients.
  • 🧳 Previously CTO at Travis an early-stage startup building a collaborative travel planning app.
  • 👨🏽‍💻 Interested in Full-stack web dev .html, .js, .css, .ts, .vue, .svelte, .gql, .prisma, .cdk.
  • 👾 Creator of the Open-Source Prisma-AppSync generator for ◭ Prisma (210+ stars).
  • 💬 Co-organizer and main speaker at the Melbourne Vue.js Meetup from 2017 to 2021.
  • 🎬 Early adopter of technologies like GraphQL, Serverless, Prisma, AWS CDK, Cloudflare Workers, Vue, or Svelte.
  • 🏆 Awarded by Cannes Lions Grand Prix, Global Amazon Alexa Cup, The FWA, Webby Awards.

Wanna Chat?

@ me on Twitter, message me on LinkedIn, or send me an email.

prisma-appsync's People

Contributors

allcontributors[bot] avatar cipriancaba avatar cjjenkinson avatar maoosi avatar max-konin avatar michachan avatar nhu-mai-101 avatar ryands17 avatar tenrys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

prisma-appsync's Issues

Support Prisma 3.3.0 with relation fields for MongoDB and MongoDB 5 support

Prisma 3.3.0 has been released with two major features we have been waiting for.

  1. Support for MongoDB 5, which means we can use a serverless db, perfect fit for lambda!
  2. Support for ordering by relation fields in MongoDB

I am guessing we can just add a resolutions field in package.json to take advantage of the MongoDB 5 support but the relation fields would need to be added to the generator?

It feels like the prisma team are soon going to support embedded types with MongoDB, which is the biggest feature missing at the moment IMHO. Something to think about maybe?

Thanks!

Some queries are returning errors

I'm using this prisma-appsync to deploy GraphQL API to lambda and AppSync using my existing PostgreSQL database. Some queries are returning errors while executing. I'm using the command introspect initially to get prisma.schema based on the database.

ERROR	Invoke Error 	{
    "errorType": "TypeError",
    "errorMessage": "Cannot read property 'findUnique' of undefined",
    "stack": [
        "TypeError: Cannot read property 'findUnique' of undefined",
        "    at ut.get (/var/task/prisma/generated/prisma-appsync/client/index.js:2:72228)",
        "    at async $s.resolve (/var/task/prisma/generated/prisma-appsync/client/index.js:2:76727)",
        "    at async Runtime.exports.handler (/var/task/index.js:18:18)"
    ]
}

Feature: Support for runtime-only fields (data injected at runtime, not in DB)

query getPost {
  twitterShareUrl
}

query getUser {
  posts { twitterShareUrl }
}
return await prismaAppSync.resolve({
  event,
  runtime: {
    '**/{getPost,posts}/twitterShareUrl': {
      omit: ['twitterShareUrl'],
      select: { slug: true },
      inject: (post) => {
        const shareUrl = `https://myblog.com/${post.slug}`
        const twitterShareUrl = `https://twitter.com/intent/tweet?text=${shareUrl}`
        return { ...post, twitterShareUrl }
      }
    }
  }
})

Glob tester for runtime.

Feature: Simplify custom resolvers usage

  • Automate generator discovery for custom-resolver.yaml and custom-schema.gql
  • Automate CDK boilerplate discovery for custom-resolver.yaml and custom-schema.gql
  • Add more documentation around custom resolvers (usage with @aws_api_key, usage with shield, ...)

override field level resolver

Hey

I am looking to have a custom resolver on single field but that lives outside of the resolvers function. I'l try to explain.

Let's say the Prisma schema is this.

model User {
  id                 String        @id @default(dbgenerated()) @map("_id") @db.ObjectId
  name               String

Then else where in our CDK we have a service that wants to add in a avatar or something on the User

extend type User {
	avatar: String
}

So far so good.

Now what I want (I think) is for the prisma-appsync to not try to resolve user.avatar at all, because it is setup else where in the cdk.

Simply excluding the resolver is not enough because when a query like

query {
	getUser {
		id
		name
		avatar  <--- this should not touch the prisma resolver and instead use the custom resolver setup outside.
	}
}

it will obviously try to find avatar from the prisma client, which doesn't exist in the database.

Maybe something in the custom resolvers like:

- typeName: User
  fieldName: avatar
  dataSource: ignore

Runtime.HandlerNotFound: index.main is undefined or not exported

I followed the installation guide including steps 5 & 6 outlined here:
https://prisma-appsync.vercel.app/guides/installation.html

I'm using Prisma 3.7 and followed their quickstart guide for TypeScript, combined with MySQL for the RDS.

I successfully generated everything and deployed the application with CDK AppSync but receive this error when running queries:
Runtime.HandlerNotFound: index.main is undefined or not exported

Any idea what may be lacking?

Feature request: Adding a new GraphQL mutation createMany

Problem

Currently no way to perform batch create operations.

Suggested solution

Adding a new GraphQL mutation createMany, so that we can do the below:

mutation {
    createManyPosts(
        data: [
            { title: "Foo" },
            { title: "Bar" }
        ]
    ) {
        count
    }
}

Additional context

  • Should leverage the existing Prisma createMany.
  • We should probably leave skipDuplicates out for now, as it would require much more work. We can create a new feature request for this at a later stage.
  • Affected Prisma-AppSync parts: Generator + Generator templates + Client + Tests + Docs (Tip: Have a look at deleteMany operation as an example).
  • Might require some change in one of Prisma-AppSync dependency used to merge AppSync schemas: appsync-schema-converter.

Reduce Lambda Package size

After running prisma generate inside afterBundling(), we won't need the prisma package anymore. we could run yarn remove after that, also we won't need the prisma-appsync package if its being compiled. Removing them reduces the size significantly.

Zipped file size reduced from 75MB to 19MB

              // added some other clean up also.
              "yarn prisma generate",
              "rm -rf node_modules/.prisma/client/query-engine-debian-openssl-1.1.x",
              "rm -rf node_modules/@prisma/engines-version/*",
              "rm -rf node_modules/@prisma/engines/*",
              "yarn remove prisma",
              "yarn remove prisma-appsync",

Is there any reason to keep the packages?

Embedded Document support for mongodb + prisma 3.10.0

Prisma has just released 3.10.0 which has support for mongodb embedded docs.

model Product {
  id     String  @id @default(auto()) @map("_id") @db.ObjectId
  name   String
  photos Photo[]
}

type Photo {
  height Int
  width  Int
  url    String
}

However at the moment prisma-appsync throws an error(s)

Error: Unknown type "Photo".

Unknown type "Photo".

Unknown type "Photo".

Unknown type "Photo".
    at assertValidSDL (.../node_modules/prisma-appsync/dist/generator.js:239891:15)

Know this has only just come out, but wanted to raise it so its on your radar! 👍

Relation type value's that are not PascalCase causes error `Unknown type ... Did you mean ...`

The error seems to be throwing when prettier is attempting to parse the generated graphql schema.

It looks like the prettier error is caused by the GraphQL schema's input field values not being PascalCase.

Here's an example error:

Unknown type "github_issue_labels_v3CreateInput". Did you mean "GithubIssueLabelsV3CreateInput", "GithubIssueLabelsV3UpdateInput", "GithubIssueLabelsV3CreateManyInput", "GithubIssueLabelsV3OrderByInput", or "GithubIssueLabelsV3UpsertInput"?

Here's where the parser failed in the graphql schema

input GithubIssuesV3GithubIssueLabelsV3CreateRelationsInput {
    create: [github_issue_labels_v3CreateInput]
    connect: [github_issue_labels_v3WhereUniqueInput]
    connectOrCreate: [github_issue_labels_v3ConnectOrCreateInput]
}

However there is no github_issue_labels_v3CreateInput in the schema but there is a GithubIssueLabelsV3CreateInput which I believe is the correct value it should be using.

input GithubIssueLabelsV3CreateInput {
    issue_id: Int!
    repo_id: Int!
    label: String!
    github_issues_v3: GithubIssueLabelsV3GithubIssuesV3CreateRelationsInput!
    github_repo_v3: GithubIssueLabelsV3GithubRepoV3CreateRelationsInput!
}

Here in the template is where the problem seems to be generated from.

Interestingly enough I noticed that the GithubIssueLabelsV3CreateInput is being used properly when referenced in the template as {{ model.name }} but the snake_case version occurs when using { field.relation.type }}.

Working when template uses {{ model.name }} as used here:

input {{ model.name }}UpsertInput {
        create: {{ model.name }}CreateInput!
        update: {{ model.name }}UpdateInput!
    }

Which generates appropriate PascalCase output:

input GithubIssueLabelsV3UpsertInput {
    create: GithubIssueLabelsV3CreateInput!
    update: GithubIssueLabelsV3UpdateInput!
}

Breaking when template uses {{ field.relation.type }} as used here:

input {{ field.relation.name }}CreateRelationsInput {
                {% if field.relation.kind === "one" -%}
                create: {{ field.relation.type }}CreateInput
                connect: {{ field.relation.type }}WhereUniqueInput
                connectOrCreate: {{ field.relation.type }}ConnectOrCreateInput
                {% else  -%}
                create: [{{ field.relation.type }}CreateInput]
                connect: [{{ field.relation.type }}WhereUniqueInput]
                connectOrCreate: [{{ field.relation.type }}ConnectOrCreateInput]
                {% endif -%}
            }

Which generates the unexpected snake_case output:

input GithubIssuesV3GithubIssueLabelsV3CreateRelationsInput {
    create: [github_issue_labels_v3CreateInput]
    connect: [github_issue_labels_v3WhereUniqueInput]
    connectOrCreate: [github_issue_labels_v3ConnectOrCreateInput]
}

Where clause inconsistent in v2

I have the following model in schema.prisma

model WarehouseStockBatch {
  id                Int                @id @default(autoincrement())
  quantity      Int               @default(0)
  sku               String
  stockOrderNumber  String

  @@unique([sku, stockOrderNumber])
}

I am trying to use an atomic operation on the batch, but the mutation fails even with the regular updates (data). The reason is Argument where of type WarehouseStockBatchWhereUniqueInput needs exactly one argument, but you provided sku and stockOrderNumber.\\nUnknown arg skuin where.sku for type WarehouseStockBatchWhereUniqueInput. Did you meanid?\\nUnknown arg stockOrderNumberin where.stockOrderNumber for type WarehouseStockBatchWhereUniqueInput. Did you meansku_stockOrderNumber?\\n\",\"type\":\"INTERNAL_SERVER_ERROR\",\"code\":500}

Basically, I am trying to target the index created for the update. This works very well with the prisma client, but for some reason the graphql generated where clause is not the same as in Prisma. The one from graphql:

input WarehouseStockBatchWhereUniqueInput {
    id: Int
    sku: String
    stockOrderNumber: String
}

and the one from prisma:

export type WarehouseStockBatchWhereUniqueInput = {
    id?: number
    sku_stockOrderNumber?: WarehouseStockBatchSkuStockOrderNumberCompoundUniqueInput
  }

You can notice the missing sku and stockOrderNumber params in prisma and also the unique index being present.

Prisma-AppSync Client API rewrite

TLDR

After using Prisma-AppSync in various projects for about a year, I’ve decided it was time to rewrite Prisma-AppSync Client API from the ground (this will not affect the generated GraphQL schema or CRUD operations, only change the way the Client API is being used as part of the Lambda Resolver).

Why?

Here’s what I’m hoping to achieve from this re-write:

  • Simplify usage by streamlining API and adopting a more opinionated approach. Provide a better TypeScript DX with cleaner naming conventions and closer to Prisma Client.
  • Make fine-grained access control and custom resolvers easier to use and more flexible across all scopes, from small to larger size projects (see shield in the preview below).
  • Adopt a TDD approach with full CI/CD integration. This will help to cover more edge cases and bring Prisma-AppSync to a stable version quicker.
  • Refactor internal code structure, reduce external dependencies and improve execution performances. This should also make contributions to the project much easier for others.

Preview

Work in progress, very likely to evolve:

Minimal example

/**
 * Instantiate Prisma-AppSync Client
 */
const prismaAppSync = new PrismaAppSync()

/**
 * Lambda handler (AppSync Direct Lambda Resolver)
 */
export const resolver = async (event: any, context: any) => {
    return await prismaAppSync.resolve({ event })
}

Advanced example

/**
 * Instantiate Prisma-AppSync Client
 */
const prismaAppSync = new PrismaAppSync()

/**
 * Lambda handler (AppSync Direct Lambda Resolver)
 */
export const main = async (event: any, context: any) => {

    return await prismaAppSync.resolve<'listPosts' | 'notify'>({
        event,
        resolvers: {
            // Extend the generated CRUD API with a custom Query
            notify: async ({ args }: QueryParams) => {
                return {
                    message: `${args.message} from notify`,
                }
            },

            // Disable, or override, a generated CRUD operation
            listPosts: false,
        },
        shield: ({ authorization, identity }: QueryParams) => {
            const isAdmin = identity?.groups?.includes('admin')
            const isOwner = { owner: { cognitoSub: identity?.sub } }
            const isCognitoAuth = authorization === Authorizations.AMAZON_COGNITO_USER_POOLS

            return {
                // By default, access is limited to logged-in Cognito users
                '**': isCognitoAuth,

                // Posts and Comments can only be modified by their owner
                'modify/{post,comment}{,/**}': {
                    rule: isOwner,
                    reason: ({ model }) => `${model} can only be modified by their owner.`,
                },

                // Password field is protected
                '**/*password{,/**}': {
                    rule: false,
                    reason: () => 'Field password is not accessible.',
                },

                // Custom resolver `notify` is restricted to admins
                'notify{,/**}': {
                    rule: isAdmin,
                },
            }
        },
        hooks: () => {
            return {
                // Triggered after a Post is modified
                'after:modify/post': async ({ prismaClient, prismaArgs, result }: AfterHookParams) => {
                    await prismaClient.hiddenModel.create({
                        data: prismaArgs.data,
                    })
                    return result
                },
            }
        },
    })

}

Timeline

The work on this has already started, though I'm not able to commit at this stage. I’ll update this issue as soon as I have a clearer view of the timeline.

Feedback

Please feel free to respond to this issue with any suggestions you might have around the Client API itself, and the preview code from above.

Improve Error Handling

Right now, displaying and handling errors can be a bit confusing. I'm not sure if there is a best practice for this case but even with a unique constraint failure it can throw a lot of text.

recording

Upcoming rename of `@prisma/sdk` to `@prisma/internals` with Prisma 4

Hey,

Jan from Prisma Engineering here.

Quick heads up that we will soon rename our @prisma/sdk package to @prisma/internals with Prisma 4, which we plan to release on June 28th (soon!).

The @prisma/sdk package was meant as an Prisma internal package only, but somehow it leaked out over time and is now used across all kinds of tools - including yours of course 😄

With the rename of the Npm package we want to make it clearer that we can not give any API guarantees for @prisma/internals, might need to introduce breaking changes to the API from time to time and will not follow semantic versioning (breaking changes only in major upgrade) with it. We think that it is better communicated with internals than with sdk.
With Prisma 4, besides the package name nothing should change though.

Additionally it would be super helpful if you could help us gain an understanding where, how and why you are using @prisma/sdk (soon @prisma/internals, remember 😀) to achieve, what exactly. We want to cleanup that package and your feedback will be valuable to us to define a better API.

Looking forward to your feedback.

Best
Jan & your friends at Prisma

PS: Are you using Prisma.dmmf from import { Prisma } from '@prisma/client' in your code somewhere by chance? That will also change soon and not include the Prisma.dmmf.schema sub property. Instead you can use getDmmf from @prisma/internals moving forward.

Case Insensitive Search

Hi,

We are in the process of building search functionality on our app and we have noticed ( at least when using Mongo as the DB) that the queries are not case insensitive.

To get this to work we have looked at enabling collation table wide on MongoDB which had no affect when querying via AppSync. This is probably cause Prisma uses Regex to query MongoDB and that the collation is more for the full string compare.

After looking further into it, it seems that Prisma needs mode to be passed through as part of the where filters. The relevant docs can be found here.

Has anyone else been able to get a case insensitive search working with MongoDB using prisma-appsync? If not is this something we can integrate in this package?

Feature: Ability to subscribe to log events using the Client API

prismaAppSync.on('log', (log) => {
    console.log({
        // 'INFO' | 'WARN' | 'ERROR'
        level: log.level,
        // 'UNDEFINED' | 'FORBIDDEN' | 'BAD_USER_INPUT' | 'INTERNAL_SERVER_ERROR' | 'TOO_MANY_REQUESTS'
        type: log.type,
        // 200 | 401 | 400 | 500 | 429
        code: log.code,
        // string
        message: log.message,
        // string
        trace: log.trace,
    })
})

AWS_LAMBDA Authorization

First of all, thanks for sharing this library.

Is that possible to enable AWS_LAMBDA Authorization?

Generator for v2

Great job on the library, your approach makes the most sense from all the research I've done

I saw the progress on the next version which looks really great, was wondering if there's a generator we can use with the preview version eg yarn create prisma-appsync-app@preview

Only thing I'm missing is the cdk boilerplate, but I assume I can just use the one from the dev branch?

Atomic Operations

Tested out the new release so far things are working great with the createMany options and such.
I'm currently working with Atomic Operations for a Inventory project and noticed it could be implemented in this generator.

https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#atomic-number-operations

I roughly tried it and it works, but opens up an issue when doing update operations where everything need to use "set:" since GraphQL does not support Union Types.

What are your thoughts on this?

`<Model>CreateManyInput` can be empty if the table contains only relations and an ID field, creating GraphQL syntax errors

Prisma Schema:

model PostAuthor {
  uuid String @id @default(uuid())

  userUuid String?
  user     User?   @relation(fields: [userUuid], references: [uuid])

  agentUuid String?
  agent     Agent?  @relation(fields: [agentUuid], references: [uuid])
  Post      Post[]
}

Generated GraphQL:

input PostAuthorCreateManyInput {
            
}

Workaround is to make a nullable field, I guess? I'm doing this so I can have some sort of way to link different types of models on a same relationship, since Prisma does not currently support that feature. I imagine GraphQL unions get no use because of that, right?

Issue with Queries returning a single parameter (such as `count` queries)

Great work! We used the beta version and that worked smoothly. After updating to the RC it seems that the graphql queries that return only scalar types don't work anymore (also the generated count* queries do not work). Error message is: "Error reading required parameters from appsyncEvent". After looking at the code, the reason is that an error is thrown because the selectionSetList is empty, see: https://github.com/maoosi/prisma-appsync/blob/main/packages/client/src/adapter.ts#L34-L40

To check (or call one of the count* functions) :

  1. Just add a custom resolver to the lambda fn:
            const healthCheck = async () => {
            const result = await app.prismaClient.$queryRaw<{ healthcheck: boolean }[]>`select 1 as healthcheck`;
            return result[0].healthcheck;
        };
  1. extend your schema:
extend type Query {
...
healthCheck: Boolean
 }

Additional Info: Event object handed over to lambda:

{
  arguments: {},
  identity: null,
  info: {
    fieldName: 'healthCheck',
    selectionSetList: [],
    parentTypeName: 'Query',
    variables: {}
  }
}

Thanks for your support.

Feature request: Providing easier access to Prisma Client

Problem

Right now, accessing the Prisma client instance is only possible via the hooks functions. This makes it unhandy to use utility methods such as Prisma middleware or events.

Suggested solution

Expose Prisma client instance via [instanceof PrismaAppSync].prisma, such as:

// initialise client
const app = new PrismaAppSync({
    connectionUrl: process.env.CONNECTION_URL
})

// access Prisma client
app.prisma.$use(async (params, next) => {
  console.log('This is middleware!')
  return next(params)
})

Additional context

  • Most of the work will probably happen within src/client/index.ts (main class) and src/client/_resolver.ts (where Prisma Client is currently initiated).
  • Open discussion: Should we remove app.$disconnect entirely and use app.prisma.$disconnect instead? Or do we think it is more accessible this way, and should only push app.prisma.xxx usage for more "advanced scenarios"? I'm thinking option 2, but keen to get extra opinions on this.
  • Open discussion: Similarly, should we remove the prisma parameter from the before and after hooks functions? I'm thinking yes since using Prisma client directly would probably fit into "advanced scenario". But again, keen to get extra opinions on this.

Impossible to perform integration tests because of JEST_WORKER_ID conditions

Hello,

Thank you for this library, it saves a lot of time. I'm currently experimenting it with a custom prisma middleware and prisma casl. The middleware adds additional conditions in where clauses on hierarchical queries, and also performs some input validation for connect, create and createOrConnect. I also developed a graphql to appsync event converter to ease the process.

I was trying to run some integration tests with Jest, but the prisma-appsync code has conditions like this before prisma calls:
if (process.env.JEST_WORKER_ID) return args

This is great for unit tests, but it makes it impossible to perform integration tests that need to query the database.

Maybe using a custom environment variable instead of JEST_WORKER_ID would help here.

Current workaround is to reset the JEST_WORKER_ID in beforeEach, but it makes me feel uncomfortable.

beforeEach(() => {
  if (process.env.JEST_WORKER_ID) {
    process.env.JEST_WORKER_ID = ''
  }
})

Regards

Issue with relation If field is not null the input is required in mutation

Hey!

I have a model Client that has a many to one relation with Tenant, here there is the model and the generated input for the createClient mutation:
As tenantId and Tenant cannot be null the generated input ClientCreateInput makes both fields required

model Client {
  id       String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  tenantId String @default(dbgenerated("uuid_generate_v4()")) @db.Uuid <--- both fields cannot be null
  Tenant   Tenant @relation(fields: [tenantId], references: [id])      <--- 
  User     User[]
}
input ClientCreateInput {
    tenantId: String!                         <--- both fields are required
    Tenant: ClientTenantCreateRelationsInput! <--- passing both in a mutation to create a
    User: ClientUserCreateRelationsInput           Client leads to an exception
}

So if I only pass one I get this error
Screen Shot 2021-09-01 at 09 02 38

And if I try passing both I get a timeout exertion and a hidden error message in Cloudwatch
Screen Shot 2021-09-01 at 09 05 43

My "solution" was to make the fields nullable with the ? mark

model Client {
  id       String @id  @default(dbgenerated("uuid_generate_v4()")) @db.Uuid 
  tenantId String? @default(dbgenerated("uuid_generate_v4()")) @db.Uuid <--- both fields are nullable
  Tenant   Tenant? @relation(fields: [tenantId], references: [id])      <--- 
  User     User[]
}

that way the input for those fields are optional

input ClientCreateInput {
    tenantId: String                         <--- both fields are optional to pass in the mutation
    Tenant: ClientTenantCreateRelationsInput <--- just providing one is enough
    User: ClientUserCreateRelationsInput
}

And if I provide only the Tenant I am able to create the Client and connect with a Tenant
Screen Shot 2021-09-01 at 10 45 08

same if I provide the tenantId
Screen Shot 2021-09-01 at 10 50 59

making the field nullable in the model to make the input optional is not what I want, I would prefer a directive that let me set the field as not null and in the same time the input for the create mutation to be optional

Feature request: Adding pagination for list queries

Problem

Currently, fetching all data from a list operation could result into a massive response.

Suggested solution

Adding support for pagination on all list queries:

query {
    listPosts(skip: 3, take: 4) {
        title
    }
}

Additional context

Getting syntax error when deploying my own schema.prisma

This error only happens when I do it with my own database schema. The sample one works fine.
`yarn deploy
yarn run v1.22.17
warning package.json: No license field
$ prisma generate && cd cdk && cdk deploy
Prisma schema loaded from prisma/schema.prisma
Error:
✔ Generated Prisma Client (3.9.0 | library) to ./node_modules/@prisma/client in 833ms

SyntaxError: Syntax Error: Expected Name, found "}". (914:5)
912 |
913 |
914 | }
| ^
915 |
916 | input DbTest3OrderByInput {
917 | id: OrderByArg
at e (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:175469:20)
at Object.parse (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:176938:19)
at Object.parse2 [as parse] (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:198238:24)
at coreFormat (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:199163:18)
at format2 (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:199348:18)
at /Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:228996:16
at Object.format (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:229010:16)
at PrismaAppSyncCompiler.makeFile (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:322770:42)
at async PrismaAppSyncCompiler.makeSchema (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:322567:33)
at async Object.onGenerate (/Users/ameer/Documents/DDLPreDev/Ameer_DDLDevs/Appsyncprisma-prod1/node_modules/prisma-appsync/dist/generator.js:322973:9)

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.`

Feature request: Adding a new GraphQL mutation updateMany

Problem

Currently no way to perform batch update operations.

Suggested solution

Adding a new GraphQL mutation updateMany, so that we can do the below:

mutation {
    updateManyPosts(
        where: { title: "FOO" }
        data: { title: "Foo" }
    ) {
        count
    }
}

Additional context

Issue with generated GraphQL input `CreateInput` when using `@default(uuid())` inside Prisma Schema

Great work! We used the beta version and that worked smoothly. After updating to the RC it seems that ID fields that are auto-populated by the database are mandatory when executing the create* mutations. That means: When creating a record I have to handover the id although the id is autogenerated in the database:
Here is the example:

schema.prisma:

model Phase {
    id            String          @id @default(uuid())
    name          String          @unique
}
...?

generated schema.gql (using beta):

input PhaseCreateInput {
	name: String!
}

generated schema.gql (after upgrade to RC):

input PhaseCreateInput {
    id: String!
    name: String!
}

Could you fix that? Thanks!

Not Authorized error when attempting to connect relational data

I am generating an AppSync api for a schema that contains contacts, tags, and a many-to-many relationship table. A contact can have multiple tags. Tags can be on multiple contacts. I largely followed this Prisma guide to set up an explicit many-to-many relation between tags and contacts.

model Contact {
  id                    Int                             @id @default(autoincrement())
  name                  String
  tags                  TagsOnContacts[]
}

model Tag {
  id                Int                 @id @default(autoincrement())
  name              String              @unique @db.VarChar(45)
  color             String              @db.VarChar(7)
  contacts          TagsOnContacts[]
}

model TagsOnContacts {
  contactId  Int
  contact    Contact         @relation(fields: [contactId], references: [id])
  tagId      Int
  tag        Tag             @relation(fields: [tagId], references: [id])
  assignedAt DateTime        @default(now())
  assignedBy String?

  @@id([contactId, tagId])
}

However, when I attempt to connect the data using a query like this, I receive a permission error. Do you know what I might be doing wrong?

mutation MyMutation {
  updateContact(
		data: { tags: {connect: {contactId: 10, tagId: 4}} },
		where: {id: 10}
	) {
    id
  }
}
{
	"data": {
		"updateContact": null
	},
	"errors": [
		{
			"path": [
				"updateContact"
			],
			"data": null,
			"errorType": "Unauthorized",
			"errorInfo": null,
			"locations": [
				{
					"line": 2,
					"column": 3,
					"sourceName": null
				}
			],
			"message": "Not Authorized to access updateContact on type Mutation"
		}
	]
}

I'm using an API key in AppSync for authorisation. I can create and update contacts and tags, but I am unable to connect one to the other without the authorisation error.

Subscriptions

I have a problem creating custom subscription.

I've created custom resolver (to create a resource) and I would like to add custom subscription for that.
It's not clear for me if I should do anything beside defining that in graphql schema?
Do I need to create custom resolver to handle that? Is that magically handled? Is there a chance to get some documentation or example for it?

pnpm test fails. Missing Prisma

import { PrismaClient, Prisma } from "@prisma/client";

Prisma is not exported when installed fresh. Only after the generator is run does it expose Prisma. But can't run generator without first building. Maybe this is more a prisma issue?

Prisma CDK lambda layer construct

Hi, love this project! I created a reusable CDK construct to generate a prisma layer, including support for prisma-appsync and bundling the prisma directory automatically. Love to get your feedback and maybe a note in the docs if it looks good.

PR is open here: jetbridge/jetkit-cdk#30

Where Filtering

Where filtering for custom resolvers doesn't handle the args.where, would need to split/convert (parseWhere) to {name: { contains: "rai" } }

{
    "operation": "custom",
    "model": "paginateUsers",
    "args": {
        "select": {
            "name": true,
            "id": true
        },
        "where": {
            "name_contains": "rai"
        },
        "skip": 0,
        "take": 10
    }
}

Alternatively, if the where Inputs types that are generated were converted to follow a similar structure it would become more closer to how prisma and nexus handles filtering.

extend type Query {
  paginateUsers(
    where: PaginateUserWhereInput
    orderBy: UserOrderByInput
    skip: Int
    take: Int
  ): [User]
}

input PaginateUserWhereInput {
  AND: [UserWhereInput!]
  OR: [UserWhereInput!]
  NOT: [UserWhereInput!]
  id: Int
  name: StringNullableFilter
}

input StringNullableFilter {
  equals: String
  in: [String!]
  notIn: [String!]
  lt: String
  lte: String
  gt: String
  gte: String
  contains: String
  startsWith: String
  endsWith: String
  not: NestedStringNullableFilter
}

input NestedStringNullableFilter {
  equals: String
  in: [String!]
  notIn: [String!]
  lt: String
  lte: String
  gt: String
  gte: String
  contains: String
  startsWith: String
  endsWith: String
  not: NestedStringNullableFilter
}

image

Issue: Adding policy statements to the CDK boilerplate creates a resolution error: `statement.freeze is not a function`

Hey there, just wanted to drop a note with a problem I ran into and how I fixed it, considering Google has zero hits for this particular error.

Given I am hosting a DB in AWS RDS, I wanted to deploy the lambda function to a VPC with connectivity to it, whitelisting inbound traffic from the VPC it's attached to. I got the VPC attachment figured out by adding the following config to the lambdaFunction const in appsync.ts:

            vpc: ec2.Vpc.fromLookup(this, "default_vpc", {vpcId: "<VPC_ID>"}),
            allowPublicSubnet: true,
            allowAllOutbound: true,

However, when CDK tried to deploy it, it runs into an issue where it cannot create network interfaces.

In order to fix this, the correct policy has to be added to the lambda execution role. Conveniently, the creators of this package added a policies field in the AppSyncStackProps.function to facilitate this.

HOWEVER, this interface doesn't work properly, as it is passing raw objects to new iamPolicyDocument here. The statement prop this is being passed to expects PolicyStatement[]; -- I believe the fact the AppSyncStackProps.function.policies field lacking a type results in this finding the following weird runtime error: resolution error: statement.freeze is not a function..

What I discovered is that the CDK boilerplate needs to be creating new iam.PolicyStatement and pass that to statements like so:

                 inlinePolicies: {
                    customApiFunctionPolicy: new iam.PolicyDocument({
                        statements:  this.props.function.policies.map((statement) => {
                            return new iam.PolicyStatement(statement)
                        })
                    })
                },

My parameters to AppSyncStackProps in index.ts now looks like this:

function: {
        code: join(process.cwd(), 'handler.ts'),
        policies: [
            {
                actions: [
                    "ec2:DescribeNetworkInterfaces",
                    "ec2:CreateNetworkInterface",
                    "ec2:DeleteNetworkInterface",
                    "ec2:DescribeInstances",
                    "ec2:AttachNetworkInterface"
                ],
                resources: ["*"]
            }
        ],

After implementing this cdk synth now outputs a lambda execution role with the proper permissions and deployment succeeds.

I don't have time at the moment to make a PR for this, but like I said, figured this might be helpful to someone else down the line.

Keywords:
resolution error: statement.freeze is not a function.

cdk v2

Do you have plans converting this repo to Cdk v2 ?

Feature request: Protecting nested fields with fine-grained access control

Problem

  • The current implementation of Fine-grained access control doesn't protect nested fields.
  • Working with large schemas is quite cumbersome, as rules become hard to visualise.

Suggested solution

Refactor the access-control entirely, so that it live into its own file (currently part of src/client/_resolver.ts). Then adapt the current implementation, so that all nested fields are also protected.

getPosts {
    # author refers to the User model
    author {
        secret # should deny access
    }
}

To avoid any security pitfall and keep a good DX, I suggest that applying allow OR deny on a given subject should automatically deny all nested fields operations. From there, allowing access to nested fields would require to be explicitly defined:

// allow access to author > secret
app.allow({ action: AuthActions.access, subject: 'Post', fields: ['author.secret'] })

// allow access to all author fields
app.allow({ action: AuthActions.access, subject: 'Post', fields: ['author.*'] })

// allow access to all author fields and nested fields
app.allow({ action: AuthActions.access, subject: 'Post', fields: ['author.**'] })

Additional context

  • Current fine-grained access control implementation is built upon stalniy/casl, which already offers ways to protect nested fields. The new solution should leverage this.
  • To deny all nested fields operations by default, it would probably require enforcing app.deny({ action: AuthActions.access, subject: 'XXX', fields: ['*.**'] }) (assuming this is working) - as soon as any rule gets applied on a given subject.
  • There is a new CASL Prisma library in preview.

Issue parsing prismaClientModels from auto-injected environment variable `PRISMA_APPSYNC_GENERATED_CONFIG`.

I'm running prisma-appsync on AWS lambda. It was running fine until I upgraded to using ES modules to enable tree shaking to reduce my bundle size.

After upgrading, I was able to get my lambda functions that use prisma working, using a lambda layer. When I use prisma-appsync from my lambda layer I get a strange error telling me

    "errorType": "Error",
    "errorMessage": "@prisma/client did not initialize yet. Please run \"prisma generate\" and try to import it again.\nIn case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues",
    "stack": [
        "Error: @prisma/client did not initialize yet. Please run \"prisma generate\" and try to import it again.",
        "In case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues",
        "    at new PrismaClient (/opt/nodejs/node_modules/.prisma/client/index.js:3:11)",
        "    at null.CustomPrismaClient (/node_modules/prisma-appsync/dist/prisma-appsync/index.js:2:70146)",
        "    at Object.PrismaAppSync (/node_modules/prisma-appsync/dist/prisma-appsync/index.js:2:70683)",
        "    at getApp (/be/ats/src/api/graphql/appSyncHandler.ts:20:15)",
        "    at Runtime.appSyncHandler (/be/ats/src/api/graphql/appSyncHandler.ts:39:15)",
        "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
    ]
}

Which is odd because none of my other prisma-using lambda functions had this error, only my appsync handler function.

I tried bundling prisma-appsync with my lambda function and not getting it off the lambda layer. This results in a different error:

2021-10-03T21:01:53.329Z prisma:tryLoadEnv Environment variables not found at null
--
2021-10-03T21:01:53.331Z prisma:tryLoadEnv Environment variables not found at /packages/repo/prisma/.env
2021-10-03T21:01:53.331Z prisma:tryLoadEnv No Environment variables loaded
START RequestId: 45d0bddf-942d-4913-8484-3df79a763bb8 Version: $LATEST
2021-10-03T21:01:53.588Z prisma:tryLoadEnv Environment variables not found at null
2021-10-03T21:01:53.588Z prisma:tryLoadEnv Environment variables not found at /packages/repo/prisma/.env
2021-10-03T21:01:53.588Z prisma:tryLoadEnv No Environment variables loaded
2021-10-03T21:01:53.625Z prisma:client clientVersion: 3.1.1
2021-10-03T21:01:53.626Z prisma:client:libraryEngine internalSetup
2021-10-03T21:01:53.661Z	45d0bddf-942d-4913-8484-3df79a763bb8	TRACE	Starting appSyncHandler
2021-10-03T21:01:53.661Z	45d0bddf-942d-4913-8484-3df79a763bb8	ERROR	API [500]:  Issue parsing prismaClientModels from auto-injected environment variable `PRISMA_APPSYNC_GENERATED_CONFIG`.
2021-10-03T21:01:53.810Z	45d0bddf-942d-4913-8484-3df79a763bb8	ERROR	Invoke Error 	{     "errorType": "500",     "errorMessage": "Internal error.",     "errorInfo": "Internal Server Error",     "privateMessage": "Issue parsing prismaClientModels from auto-injected environment variable `PRISMA_APPSYNC_GENERATED_CONFIG`.",     "data": null,     "stack": [         "InternalError: Internal error.",         "    at Object.parseRequest (/node_modules/prisma-appsync/dist/prisma-appsync/index.js:2:43315)",         "    at Object.PrismaAppSyncAdapter (/node_modules/prisma-appsync/dist/prisma-appsync/index.js:2:41397)",         "    at Object.parseEvent (/node_modules/prisma-appsync/dist/prisma-appsync/index.js:2:70959)",         "    at Runtime.appSyncHandler (/be/ats/src/api/graphql/appSyncHandler.ts:44:9)",         "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"     ] }

Any suggestions?

Customise which mutations / queries are generated

We are looking for a way to customise which CRUD operations are created on a per model basis. For example, We might not want to offer a delete mutations at all on a certain model.

I know I could restrict the API to only super admins or something, by using the

/// @PrismaAppSync.mutation: '@@adminsOnly'

However, we would love to be able to have the generated docs etc correctly show that the delete mutation should not be used.

Feature request: Refactor orderBy to support multiple fields and relations.

Was trying multiple orderBy and came into an issue that would throw an error "e[r].toLowerCase is not a function" when I looked into the code, I can see 'input' is being passed but it wouldn't be the object key but the parent array key.

query MyQuery {
  listUsers(skip: 2, take: 2, orderBy: {id: DESC, name: DESC}) {
    name
    id
  }
}

Prisma supports multiple OrderBy so I made some changes and built it with below:

    private parseOrderBy(orderByInput: any) {
        const orderByOutput: any = []
        for (const input in orderByInput) {
            const orderByKey = Object.keys(orderByInput[input])[0]
            const orderByArg = orderByInput[input][orderByKey].toLowerCase()

            if (PrismaOrderByArgs.includes(orderByArg)) {
                orderByOutput.push({
                    [orderByKey]: orderByArg,
                })
            }
        }

        return orderByOutput
    }

Does this approach make sense or is there a different plan for orderBy ?

Authorization rules conditions cannot be applied to list

Hey

first off, thank you so much for making this project. It has been awesome to be integrate it with our CDK setup and although I am just getting started with it, its been awesome so far.

I am hoping this is more user error than a bug, but wanted to report it anyway.

In the handler.ts I have one rule, which is

	app.deny({
		action: AuthActions.all,
		subject: 'Task',
		condition: { isAdmin: true },
		reason: 'Only admins can access admin tasks',
	});

however when running the listTasks query, I now get the error

Authorization rules conditions cannot be applied to list (no where clause available)

I have tried different conditions, like

condition: { isAdmin: {$eq: true} }

But still no luck

Anything obvious I am missing here?

Again thanks for the great work!

EDIT:
Sorry should have put in that we are using the mongodb preview from prisma, so it could be that?

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["mongodb"]
  binaryTargets   = ["native", "rhel-openssl-1.0.x"]

}

generator appsync {
  provider                  = "prisma-appsync"
  directiveAlias_adminsOnly = "@aws_iam @aws_cognito_user_pools(cognito_groups: [\"Admin\"])"
  debug                     = true
  directiveAlias_default    = "@aws_iam @aws_cognito_user_pools(cognito_groups: [\"Admin\", \"User\"])"
  customSchema              = "./custom-schema.gql"
}
``

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.