Skip to content
Our sponsors
Kuizto — The Everyday Cooking App

1.0.0-rc.1

BREAKING

🚨 This release comes with a major rewrite of the Prisma-AppSync Client API and breaking changes. Please make sure to take a moment and read through the details below before upgrading.

Highlights

  • Codebase rewrite: Prisma-AppSync was rewritten from the ground to offer a simplified API with a more opinionated approach, end-to-end type safety for a better DX, advanced customisation options, as well as improved security and fine-grained access control.
  • Installer CLI: New interactive scaffolding CLI to quickly start new Prisma-AppSync projects, accessible from a single npx create-prisma-appsync-app@latest command. It can also plug into existing projects already using Prisma.
  • Local AppSync Server: New local development environment built for Prisma-AppSync (local database, auto-reload, TS support, GraphQL IDE). Iterate faster by simulating a GraphQL API running on AWS AppSync from your local machine.
  • Documentation website: New documentation website and contribution guide, built upon VitePress, and accessible from prisma-appsync.vercel.app.
  • Lots of improvements: Implemented dozens of improvements, bug fixes and new Prisma features (atomic operations, order by relations, case sensitivity, etc).

🪓 Breaking changes

Prisma-AppSync Client usage

BREAKINGUsage is simplified by adopting a more opinionated approach. Also provides a better TypeScript DX closer to Prisma Client.

  • Fine-gained access control and hooks have been re-engineered entirely to make them easier to use and more flexible for all project sizes.
  • Adopted a full TDD approach with both unit and integration tests. This is to help bring Prisma-AppSync to a stable version quicker.

Before:

ts
// init prisma-appsync client
const app = new PrismaAppSync({
    connectionUrl: process.env.CONNECTION_URL
})

// direct lambda resolver for appsync
export const main = async (event) => {
    // parse the `event` from your Lambda function
    app.parseEvent(event)

    // handle CRUD operations / resolve query
    const result = await app.resolve()

    // close database connection
    await app.prisma.$disconnect()

    // return query result
    return Promise.resolve(result)
}

After:

ts
const prismaAppSync = new PrismaAppSync()

// direct lambda resolver for appsync
export const resolver = async (event) => {
    return await prismaAppSync.resolve({ event })
}
Prisma-AppSync Client options

BREAKING

  • connectionUrl parameter renamed into connectionString. This parameter is optional and leverages Prisma Client naming convention and defaults.
  • debug parameter renamed into logLevel. This parameter is optional and allows specify server logging levels.
  • New maxDepth parameter that improves security and prevents clients from abusing query depth. Defaults to 3.
  • New maxReqPerUserMinute parameter that provides in-memory rate-limiting and prevents common DDoS attacks. Defaults to 200.
ts
const prismaAppSync = new PrismaAppSync({
    // optional, DB connection string, default to env var `DATABASE_URL`
    connectionString,
    // optional, enable data sanitizer for DB storage (incl. XSS parser), default to true
    sanitize,
    // optional, specify server logging level (`INFO`, `WARN`, `ERROR`), default to `INFO`
    logLevel,
    // optional, pagination for listQueries, default to 50
    defaultPagination,
    // optional, allowed graphql query depth, default to 3
    maxDepth,
    // optional, per user, in-memory rate limiting, default to 200
    maxReqPerUserMinute
})
Custom Resolvers API

BREAKING

Before:

ts
app.registerCustomResolvers({
    notify: async ({ args }: CustomResolverProps) => {
        return {
            message: `${args.message} from notify`
        }
    }
})

After:

ts
return await prismaAppSync.resolve<'notify'>({
    event,
    resolvers: {
        notify: async ({ args }: QueryParams) => {
            return {
                message: `${args.message} from notify`,
            }
        },
    }
})
Hooks before/after API

BREAKING

Before:

ts
// execute before resolve
app.beforeResolve(async (props) => {})

// execute after resolve
app.afterResolve(async (props) => {})

After:

ts
return await prismaAppSync.resolve<'likePost'>({
    event,
    hooks: {
        // execute before any query
        'before:**': async (params: BeforeHookParams) => params,

        // execute after any query
        'after:**': async (params: AfterHookParams) => params,

        // execute after custom resolver query `likePost`
        // (e.g. `query { likePost(postId: 3) }`)
        'after:likePost': async (params: AfterHookParams) => {
            await params.prismaClient.notification.create({
                data: {
                    event: 'POST_LIKED',
                    targetId: params.args.postId,
                    userId: params.authIdentity.sub,
                },
            })
            return params
        },
    },
})
Fine-Grained Access Control API

BREAKING

Before:

ts
// before resolving any query
app.beforeResolve(async ({ authIdentity }: BeforeResolveProps) => {
    // rules only apply to Cognito authorization type
    if (authIdentity.authorization !== AuthModes.AMAZON_COGNITO_USER_POOLS)
        return false

    // get current user from database, using Prisma
    // we only need to know the user ID
    const currentUser = await prisma.user.findUnique({
        select: { id: true },
        where: { cognitoSub: authIdentity.sub }
    }) || { id: null }

    // everyone can access (get + list) or create Posts
    app.allow({ action: AuthActions.access, subject: 'Post' })
    app.allow({ action: AuthActions.create, subject: 'Post' })

    // only the author is allowed to modify a given Post
    app.deny({
        action: AuthActions.modify,
        subject: 'Post',
        // IF `Post.ownerId` NOT_EQUAL_TO `currentUser.id` THEN DENY_QUERY
        condition: { ownerId: { $ne: currentUser.id } }
    })
})

After:

ts
return await prismaAppSync.resolve({
    event,
    shield: ({ authorization, identity }: QueryParams) => {
        const isCognitoAuth = authorization === Authorizations.AMAZON_COGNITO_USER_POOLS
        const isOwner = { owner: { cognitoSub: identity?.sub } } // Prisma syntax

        return {
            '**': {
                rule: isCognitoAuth,
                reason: ({ model }) => `${model} access is restricted to logged-in users.`,
            },
            '{update,upsert,delete}Post{,/**}': {
                rule: isOwner,
                reason: ({ model }) => `${model} can only be modified by their owner.`,
            },
        }
    },
})
Prisma-AppSync Generator options

BREAKING

  • customSchema parameter renamed into extendSchema.
  • customResolvers parameter renamed into extendResolvers.
  • new parameter defaultDirective, to globally apply default directives.
ts
generator appsync {
  provider = "prisma-appsync"

  // optional params
  output          = "./generated/prisma-appsync"
  extendSchema    = "./custom-schema.gql"
  extendResolvers = "./custom-resolvers.yaml"
  defaultDirective = "@auth(model: [{ allow: apiKey }])"
}
AppSync Authorizations modes

BREAKING

Before:

json
/// @PrismaAppSync.type: '@aws_api_key @aws_cognito_user_pools(cognito_groups: [\"admins\"])'
model Post {
  id       Int       @id @default(autoincrement())
  title    String
}

After:

json
/// @auth(model: [{ allow: apiKey }, { allow: userPools, groups: ["admins"] }])
model Post {
  id       Int       @id @default(autoincrement())
  title    String
}

👆 Output: @aws_api_key @aws_cognito_user_pools(cognito_groups: ["admins"])

Data sanitizer behaviour

BREAKING

Enabling the data sanitizer will now automatically "clarify/decode" the data before sending it back to the client. Meaning client-side decoding is not anymore necessary, while your data will still be parsed for XSS before storage.

Auto-generated documentation

BREAKING

To reduce the maintenance burden, auto-generated API docs have been removed from Prisma-AppSync in favour of a better description of the data (accessible via the native GraphQL documentation from your favourite GraphQL IDE).

In case this is problematic for you and you’d like auto-generated docs to make a comes back, please consider supporting the project and opening a new issue.

🎉 New features

New documentation website

NEW FEATURE

Accessible from prisma-appsync.vercel.app.

New installer (scaffolding tool)

NEW FEATURE

New installer that can be run via npx create-prisma-appsync-app@latest. Nicely plug with existing Prisma projects (non-destroying), while also allowing scaffolding of new projects from scratch.

bash
    ___      _                             _               __
   / _ \_ __()___ _ __ ___   __ _        /_\  _ __  _ __ / _\_   _ _ __   ___
  / /◭)/ '__| / __| '_ ` _ \ / _` |_____ //◭\\| '_ \| '_ \\ \| | | | '_ \ / __|
 / ___/| |  | \__ \ | | | | | (◭| |_____/  _  \ |◭) | |◭) |\ \ |_| | | | | (__
 \/    |_|  |_|___/_| |_| |_|\__,_|     \_/ \_/ .__/| .__/\__/\__, |_| |_|\___|
                                              |_|   |_|       |___/
  ◭ Prisma-AppSync Installer v1.0.0
Local development environment built for Prisma-AppSync

NEW FEATURE

New local development environment built for Prisma-AppSync (local database, auto-reload, TS support, GraphQL IDE). Simulate a GraphQL API running on AWS AppSync + AWS Lambda Resolver + Prisma ORM + Database.

Support added for Prisma 4.5.x

NEW FEATURE

Codebase adapted and fully tested for Prisma 4.5.x support.

Customise GraphQL Schema output

NEW FEATURE

json
/// @gql(queries: { list: 'posts' }, subscriptions: null)
model Post {
  id       Int       @id @default(autoincrement())
  title    String
}

👆 Output: Default listPosts query renamed to posts / No subscriptions for the Post model

Support for Atomic Operations

NEW FEATURE

graphql
mutation {
  updatePost(
    where: { id: 1 }, 
    operation: { 
      views: { increment: 1 }
    }
  ) {
    views
  }
}
Support for order by relational fields

NEW FEATURE

graphql
query {
  listPosts(
    orderBy: {
      author: {
        name: ASC
      }
    }
  ) {
    title
    author {
      name
    }
  }
}
Support for Case Sensitivity (PostgreSQL and MongoDB connectors only)

NEW FEATURE

graphql
query {
  listPosts(
    where: {
      title: {
        contains: "prisma",
        mode: "insensitive"
      }
    }
  ) {
    title
  }
}
CDK boilerplate upgraded to v2+

IMPROVEMENT

CDK boilerplate was entirely rewritten to offer a more easy-to-use, simpler syntax making it more approachable for people who are new to AWS CDK.

TypeScript types improved for better DX

IMPROVEMENT

Hovering on Prisma-AppSync types and Client API methods using VSCode is now displaying docs and examples across the entire codebase.

Bundle size and performances

IMPROVEMENT

Noticeable gains in bundle size and runtime performances. Lots of dependencies were removed from the previous version, in favour of custom utils functions.

Errors handling and server logs improved

IMPROVEMENT

Both error handling and server logs (accessible from CloudWatch) have been improved to include more details and be easily readable.

ts
// Example object returned from the API
{
	"error": "Query has depth of 4, which exceeds max depth of 3.",
	"type": "FORBIDDEN",
	"code": 401
}

// Error codes
const errorCodes = {
  FORBIDDEN: 401,
  BAD_USER_INPUT: 400,
  INTERNAL_SERVER_ERROR: 500,
  TOO_MANY_REQUESTS: 429,
}

🐞 Bug fixes

Added support for uniqueIndexes (🙏 contribution from @cipriancaba)

🐙 Issue #46 /PR #47: Prisma is no longer providing idFields

Fixed casing issue on GraphQL relation fields (🙏 contribution from @ryparker)

🐙 Issue #37 / PR #38: Generated GraphQL schema relation definitions using the incorrect case for type values

Fixed issue on nullable relation fields in mutations

🐙 Issue #26: Issue using nullable relation fields with mutations

Replaced env var JEST_WORKER_ID with PRISMA_APPSYNC_TESTING

🐙 Issue #32: Issue performing tests because of JEST_WORKER_ID

💛 Github Sponsors

Enjoy using Prisma-AppSync? Please consider sponsoring me at 🐙 maoosi ↗ so that I can spend more time working on the project.

Released under the BSD 2-Clause License.