Showing preview only (1,055K chars total). Download the full file or copy to clipboard to get everything.
Repository: maoosi/prisma-appsync
Branch: main
Commit: edc151046c56
Files: 136
Total size: 1004.8 KB
Directory structure:
gitextract_frruakbg/
├── .all-contributorsrc
├── .eslintrc
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── unit-tests.yml
├── .gitignore
├── .markdownlint.json
├── .npmignore
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── bin/
│ ├── build.mjs
│ ├── cleans.mjs
│ ├── dev.mjs
│ ├── env.mjs
│ ├── postinstall.mjs
│ ├── publish/
│ │ ├── _pkg.core.cleanse.js
│ │ ├── _pkg.core.restore.js
│ │ └── _pkg.installer.cleanse.js
│ ├── publish.mjs
│ └── test.mjs
├── docs/
│ ├── .vitepress/
│ │ ├── config.ts
│ │ └── theme/
│ │ ├── Layout.vue
│ │ ├── index.ts
│ │ └── styles/
│ │ └── vars.css
│ ├── changelog/
│ │ ├── 1.0.0-rc.1.md
│ │ ├── 1.0.0-rc.2.md
│ │ ├── 1.0.0-rc.3.md
│ │ ├── 1.0.0-rc.4.md
│ │ ├── 1.0.0-rc.5.md
│ │ ├── 1.0.0-rc.6.md
│ │ ├── 1.0.0-rc.7.md
│ │ ├── 1.0.0.md
│ │ └── index.md
│ ├── contributing.md
│ ├── features/
│ │ ├── gql-schema.md
│ │ ├── hooks.md
│ │ └── resolvers.md
│ ├── index.md
│ ├── quick-start/
│ │ ├── deploy.md
│ │ ├── getting-started.md
│ │ ├── installation.md
│ │ └── usage.md
│ ├── security/
│ │ ├── appsync-authz.md
│ │ ├── query-depth.md
│ │ ├── rate-limiter.md
│ │ ├── shield-acl.md
│ │ └── xss-sanitizer.md
│ ├── support.md
│ └── tools/
│ └── appsync-gql-schema-diff.md
├── package.json
├── packages/
│ ├── boilerplate/
│ │ ├── cdk/
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── appsync.ts
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ ├── cdk.json
│ │ ├── handler.ts
│ │ ├── prisma/
│ │ │ └── sqlite.prisma
│ │ ├── server/
│ │ │ └── server.ts
│ │ └── tsconfig.json
│ ├── client/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── adapter.ts
│ │ │ ├── consts.ts
│ │ │ ├── core.ts
│ │ │ ├── guard.ts
│ │ │ ├── index.ts
│ │ │ ├── inspector.ts
│ │ │ ├── resolver.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tsconfig.json
│ ├── generator/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── client.ts
│ │ │ ├── directives.ts
│ │ │ ├── generator.ts
│ │ │ ├── handler.ts
│ │ │ ├── index.ts
│ │ │ ├── resolvers.ts
│ │ │ └── schema.ts
│ │ └── tsconfig.json
│ ├── installer/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── installer.ts
│ │ └── tsconfig.json
│ └── server/
│ ├── package.json
│ ├── src/
│ │ ├── appsync-simulator.ts
│ │ ├── index.d.ts
│ │ ├── index.ts
│ │ ├── lambdaRequest.vtl
│ │ └── lambdaResponse.vtl
│ └── tsconfig.json
├── pnpm-workspace.yaml
├── tests/
│ ├── client/
│ │ ├── adapter.test.ts
│ │ ├── core.test.ts
│ │ ├── guard.test.ts
│ │ ├── mocks/
│ │ │ ├── graphql-json.ts
│ │ │ ├── lambda-event.ts
│ │ │ └── lambda-identity.ts
│ │ ├── resolver.test.ts
│ │ ├── utils/
│ │ │ └── index.ts
│ │ └── utils.test.ts
│ └── generator/
│ ├── @gql.test.ts
│ ├── crud.test.ts
│ ├── mock/
│ │ ├── appsync-directives.gql
│ │ └── appsync-scalars.gql
│ └── schemas/
│ ├── @gql.prisma
│ ├── crud.gql
│ ├── crud.prisma
│ └── generated/
│ ├── @gql/
│ │ ├── client/
│ │ │ ├── adapter.d.ts
│ │ │ ├── consts.d.ts
│ │ │ ├── core.d.ts
│ │ │ ├── guard.d.ts
│ │ │ ├── index.d.ts
│ │ │ ├── index.js
│ │ │ ├── inspector.d.ts
│ │ │ ├── resolver.d.ts
│ │ │ ├── types.d.ts
│ │ │ └── utils.d.ts
│ │ ├── resolvers.yaml
│ │ └── schema.gql
│ └── crud/
│ ├── client/
│ │ ├── adapter.d.ts
│ │ ├── consts.d.ts
│ │ ├── core.d.ts
│ │ ├── guard.d.ts
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── inspector.d.ts
│ │ ├── resolver.d.ts
│ │ ├── types.d.ts
│ │ └── utils.d.ts
│ ├── resolvers.yaml
│ └── schema.gql
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"projectName": "prisma-appsync",
"projectOwner": "maoosi",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": true,
"types": {
"creator": {
"symbol": "🐙",
"description": "Creator & maintainer"
}
},
"contributors": [
{
"login": "maoosi",
"name": "Sylvain",
"avatar_url": "https://avatars.githubusercontent.com/u/4679377?v=4",
"profile": "https://sylvainsimao.fr",
"contributions": [
"creator",
"code",
"ideas",
"doc"
]
},
{
"login": "Tenrys",
"name": "Bell",
"avatar_url": "https://avatars.githubusercontent.com/u/3979239?v=4",
"profile": "http://bell.moe",
"contributions": [
"code",
"ideas"
]
},
{
"login": "cipriancaba",
"name": "Ciprian Caba",
"avatar_url": "https://avatars.githubusercontent.com/u/695515?v=4",
"profile": "http://www.cipriancaba.com",
"contributions": [
"code",
"ideas"
]
},
{
"login": "tomschut",
"name": "Tom",
"avatar_url": "https://avatars.githubusercontent.com/u/4933446?v=4",
"profile": "https://github.com/tomschut",
"contributions": [
"code",
"ideas"
]
},
{
"login": "ryparker",
"name": "Ryan Parker",
"avatar_url": "https://avatars.githubusercontent.com/u/17558268?v=4",
"profile": "http://ryanparker.dev",
"contributions": [
"code"
]
},
{
"login": "cjjenkinson",
"name": "Cameron Jenkinson",
"avatar_url": "https://avatars.githubusercontent.com/u/5429478?v=4",
"profile": "https://www.cameronjjenkinson.com",
"contributions": [
"code"
]
},
{
"login": "jeremy-white",
"name": "jeremy-white",
"avatar_url": "https://avatars.githubusercontent.com/u/42325631?v=4",
"profile": "https://github.com/jeremy-white",
"contributions": [
"code"
]
},
{
"login": "max-konin",
"name": "Max Konin",
"avatar_url": "https://avatars.githubusercontent.com/u/1570356?v=4",
"profile": "https://github.com/max-konin",
"contributions": [
"code"
]
},
{
"login": "michachan",
"name": "Michael Chan",
"avatar_url": "https://avatars.githubusercontent.com/u/27760344?v=4",
"profile": "https://github.com/michachan",
"contributions": [
"code"
]
},
{
"login": "nhu-mai-101",
"name": "Nhu Mai",
"avatar_url": "https://avatars.githubusercontent.com/u/84061316?v=4",
"profile": "https://www.linkedin.com/in/nhu-mai/",
"contributions": [
"code"
]
}
],
"commitConvention": "angular",
"contributorsPerLine": 7,
"commitType": "docs"
}
================================================
FILE: .eslintrc
================================================
{
"root": true,
"extends": "@antfu",
"rules": {
"jsonc/indent": ["error", 4, {}],
"@typescript-eslint/indent": [
"error",
4,
{
"offsetTernaryExpressions": true,
"ignoredNodes": ["TemplateLiteral *", "TSTypeParameterInstantiation"],
"SwitchCase": 1
}
],
"@typescript-eslint/consistent-type-definitions": ["error", "type"]
},
"globals": {
"$": true,
"chalk": true,
"cd": true,
"argv": true,
"fs": true,
"nothrow": true
}
}
================================================
FILE: .github/FUNDING.yml
================================================
github: [maoosi]
================================================
FILE: .github/workflows/unit-tests.yml
================================================
name: Unit Tests
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 7
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
cache: "pnpm"
- name: Install global dependencies
run: "pnpm add -g zx"
- name: Install project dependencies
run: "pnpm install"
- name: Run tests
run: "pnpm run test"
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
*/**/node_modules/
# Testing
tests/prisma/generated/
playground/
debug/
# Dist folder
dist/
# Docs
docs/.vitepress/dist/
docs/.vitepress/cache/
tmp.md
# Boilerplate files
packages/boilerplate/cdk/*.lock
packages/boilerplate/cdk/cdk.out
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
================================================
FILE: .markdownlint.json
================================================
{
"default": true,
"no-inline-html": false,
"line-length": false,
"no-trailing-punctuation": false
}
================================================
FILE: .npmignore
================================================
.DS_Store
node_modules/
*/**/node_modules/
# Source files
packages/
bin/
tests/
playground/
docs/
.editorconfig
.eslintrc
.markdownlint.json
.prettierignore
.prettierrc.cjs
tsconfig.json
# Package cache files
package-*.json
pnpm-*.yaml
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Workspace
pnpm-workspace.yaml
vite.config.ts
# Installer
dist/installer
# Editor directories and files
.idea
.vscode
.github
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": [
"dbaeumer.vscode-eslint",
"johnpapa.vscode-peacock"
]
}
================================================
FILE: .vscode/settings.json
================================================
{
// Visuals
"peacock.color": "#1f08f6",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#4b38f9",
"activityBar.activeBorder": "#f95745",
"activityBar.background": "#4b38f9",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#f95745",
"activityBarBadge.foreground": "#15202b",
"sash.hoverBorder": "#4b38f9",
"statusBar.background": "#1f08f6",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#4b38f9",
"statusBarItem.remoteBackground": "#1f08f6",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#1f08f6",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#1f08f699",
"titleBar.inactiveForeground": "#e7e7e799",
"commandCenter.border": "#e7e7e799"
},
// ESLint config
"eslint.codeAction.showDocumentation": {
"enable": true
},
"eslint.probe": [
"javascript",
"typescript",
"javascriptreact",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"json5"
],
"prettier.enable": false,
// Editor
"editor.formatOnSave": false,
"editor.accessibilitySupport": "off",
"editor.cursorSmoothCaretAnimation": "on",
"editor.find.addExtraSpaceOnTop": false,
"editor.guides.bracketPairs": "active",
"editor.inlineSuggest.enabled": true,
"editor.lineNumbers": "interval",
"editor.multiCursorModifier": "ctrlCmd",
"editor.renderWhitespace": "boundary",
"editor.suggestSelection": "first",
"editor.tabSize": 4,
"editor.unicodeHighlight.invisibleCharacters": false,
"editor.codeActionsOnSave": {
"source.fixAll": "never",
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"[markdown]": {
"editor.formatOnSave": false
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma",
"editor.formatOnSave": true
},
"files.eol": "\n",
"typescript.tsdk": "node_modules/typescript/lib",
// Grammarly
"grammarly.selectors": [
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/changelog/1.0.0-rc.1.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/contributing.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/essentials/concept.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/essentials/getting-started.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/advanced/securing-api.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/advanced/extending-api.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "README.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/advanced/hooks.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/changelog/1.0.0-rc.4.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/changelog/1.0.0-rc.5.md"
},
{
"language": "markdown",
"scheme": "file",
"pattern": "docs/changelog/1.0.0-rc.6.md"
}
]
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
[prisma-appsync.vercel.app/changelog/](https://prisma-appsync.vercel.app/changelog/)
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
[prisma-appsync.vercel.app/contributing.html](https://prisma-appsync.vercel.app/contributing.html)
================================================
FILE: LICENSE.txt
================================================
BSD 2-Clause License
Copyright (c) 2024, Sylvain Simao <hello@sylvainsimao.fr>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
<p align="center">
<img width="250" height="250" src="https://prisma-appsync.vercel.app/logo.png" alt="Prisma-AppSync" />
</p>
# Prisma-AppSync · [](/packages/client/src/) [](https://aws.amazon.com/appsync/) [](https://www.prisma.io)
**Prisma-AppSync** turns your [Prisma Schema](https://www.prisma.io) into a fully-featured GraphQL API, tailored for [AWS AppSync](https://aws.amazon.com/appsync/).
## ✔️ Features
💎 **Use your ◭ Prisma Schema**<br/>Quickly define your data model and deploy a GraphQL API tailored for AWS AppSync.
⚡️ **Auto-generated CRUD operations**<br/>Using Prisma syntax, with a robust TS Client designed for AWS Lambda Resolvers.
⛑ **Pre-configured security**<br/>Built-in XSS protection, query depth limitation, and in-memory rate limiting.
🔐 **Fine-grained ACL and authorization**<br/>Flexible security options such as API keys, IAM, Cognito, and more.
🔌 **Fully extendable features**<br/>Customize your GraphQL schema, API resolvers, and data flow as needed.
## 🚀 Getting started
Run the following command and follow the prompts 🙂
```shell
npx create-prisma-appsync-app@latest
```
## 📓 Documentation
[Read the documentation](https://prisma-appsync.vercel.app) to learn how to use Prisma-AppSync.
## 🙏 Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100px;" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#creator-maoosi" title="Creator & maintainer">🐙</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://bell.moe"><img src="https://avatars.githubusercontent.com/u/3979239?v=4?s=100" width="100px;" alt="Bell"/><br /><sub><b>Bell</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=Tenrys" title="Code">💻</a> <a href="#ideas-Tenrys" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.cipriancaba.com"><img src="https://avatars.githubusercontent.com/u/695515?v=4?s=100" width="100px;" alt="Ciprian Caba"/><br /><sub><b>Ciprian Caba</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=cipriancaba" title="Code">💻</a> <a href="#ideas-cipriancaba" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomschut"><img src="https://avatars.githubusercontent.com/u/4933446?v=4?s=100" width="100px;" alt="Tom"/><br /><sub><b>Tom</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=tomschut" title="Code">💻</a> <a href="#ideas-tomschut" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ryanparker.dev"><img src="https://avatars.githubusercontent.com/u/17558268?v=4?s=100" width="100px;" alt="Ryan Parker"/><br /><sub><b>Ryan Parker</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=ryparker" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.cameronjjenkinson.com"><img src="https://avatars.githubusercontent.com/u/5429478?v=4?s=100" width="100px;" alt="Cameron Jenkinson"/><br /><sub><b>Cameron Jenkinson</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=cjjenkinson" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeremy-white"><img src="https://avatars.githubusercontent.com/u/42325631?v=4?s=100" width="100px;" alt="jeremy-white"/><br /><sub><b>jeremy-white</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=jeremy-white" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/max-konin"><img src="https://avatars.githubusercontent.com/u/1570356?v=4?s=100" width="100px;" alt="Max Konin"/><br /><sub><b>Max Konin</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=max-konin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/michachan"><img src="https://avatars.githubusercontent.com/u/27760344?v=4?s=100" width="100px;" alt="Michael Chan"/><br /><sub><b>Michael Chan</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=michachan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/nhu-mai/"><img src="https://avatars.githubusercontent.com/u/84061316?v=4?s=100" width="100px;" alt="Nhu Mai"/><br /><sub><b>Nhu Mai</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=nhu-mai-101" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
Wanting to help? Get started with our [contribution guide](https://prisma-appsync.vercel.app/contributing.html) or consider [💛 Github sponsors](https://github.com/sponsors/maoosi).
## 🌟 Sponsors
**Thanks go to these wonderful sponsors!**
[](https://kuizto.co/?utm_source=prisma_appsync&utm_medium=github)
[Kuizto.co](https://kuizto.co/?utm_source=prisma_appsync&utm_medium=github) is a cooking app that adds a unique twist to everyday cooking. Netflix-like feed to explore tailored recipes. Get inspired by others, save to cooklists, plan instantly!
================================================
FILE: bin/build.mjs
================================================
#!/usr/bin/env zx
/* eslint-disable no-console */
/* eslint-disable n/prefer-global/process */
import './env.mjs'
try {
// cleanup previous generated files
console.log(chalk.blue('\n🧹 [chore] cleanup\n'))
await $`rm -rf dist`.quiet()
if (!argv?.ignoreGenerator) {
console.log(chalk.blue('🛠️ [build] packages/generator'))
// build Prisma-AppSync Generator
await $`esbuild packages/generator/src/index.ts --bundle --format=cjs --keep-names --platform=node --target=node18 --external:fsevents --external:_http_common --outfile=dist/generator.js --define:import.meta.url='_importMetaUrl' --banner:js="const _importMetaUrl=require('url').pathToFileURL(__filename)"`.quiet()
}
if (!argv?.ignoreClient) {
console.log(chalk.blue('🛠️ [build] packages/client'))
// build Prisma-AppSync Client
await $`esbuild packages/client/src/index.ts --bundle '--define:process.env.NODE_ENV="production"' --format=cjs --minify --keep-names --platform=node --target=node18 --external:fsevents --external:@prisma/client --outfile=dist/client/index.js --legal-comments=inline`.quiet()
console.log(chalk.blue('🛠️ [build] packages/client types'))
// build Prisma-AppSync Client TS Declarations
await $`tsc packages/client/src/*.ts --outDir dist/client/ --declaration --emitDeclarationOnly --esModuleInterop --downlevelIteration`.nothrow().quiet()
}
if (!argv?.ignoreInstaller) {
if (process.env.COMPILE_MODE === 'preview') {
console.log(chalk.blue('🛠️ [build] packages/installer (preview mode)'))
// build installer (preview mode)
await $`esbuild packages/installer/src/index.ts --bundle '--define:process.env.NODE_ENV="production"' '--define:process.env.COMPILE_MODE="preview"' --format=cjs --minify --keep-names --platform=node --target=node18 --external:fsevents --external:_http_common --outfile=dist/installer/bin/index.js`.quiet()
}
else {
console.log(chalk.blue('🛠️ [build] packages/installer'))
// build installer (default)
await $`esbuild packages/installer/src/index.ts --bundle '--define:process.env.NODE_ENV="production"' --format=cjs --minify --keep-names --platform=node --target=node18 --external:fsevents --external:_http_common --outfile=dist/installer/bin/index.js`.quiet()
}
}
if (!argv?.ignoreServer) {
console.log(chalk.blue('🛠️ [build] packages/server'))
// build server
await $`esbuild packages/server/src/index.ts --bundle --format=cjs --minify --keep-names --platform=node --target=node18 --external:fsevents --external:@prisma/client --external:amplify-appsync-simulator --external:_http_common --outfile=dist/server/index.js`.quiet()
// build server TS Declarations
await $`cp packages/server/src/index.d.ts dist/server/index.d.ts && chmod -R 755 dist`.quiet()
// copy server .vtl files into build folder
await $`cp -R packages/server/src/*.vtl dist/server && chmod -R 755 dist`.quiet()
}
}
catch (error) {
console.log(chalk.red(`🚨 [build] error\n\n${error}`))
}
================================================
FILE: bin/cleans.mjs
================================================
#!/usr/bin/env zx
import './env.mjs'
console.log(chalk.blue('\n🧹 [chore] deleting all `node_modules` folders\n'))
await $`find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +`
console.log(chalk.blue('🧹 [chore] deleting all `dist` folders\n'))
await $`find . -name 'dist' -type d -prune -exec rm -rf '{}' +`
console.log(chalk.blue('🧹 [chore] deleting all `cdk.out` folders\n'))
await $`find . -name 'cdk.out' -type d -prune -exec rm -rf '{}' +`
console.log(chalk.blue('🧹 [chore] deleting all `generated` folders\n'))
await $`find . -name 'generated' -type d -prune -exec rm -rf '{}' +`
console.log(chalk.blue('🧹 [chore] deleting all `yarn.lock` files\n'))
await $`find . -name 'yarn.lock' -type f -prune -exec rm -rf '{}' +`
console.log(chalk.blue('🧹 [chore] deleting all `pnpm-lock.yaml` files\n'))
await $`find . -name 'pnpm-lock.yaml' -type f -prune -exec rm -rf '{}' +`
console.log(chalk.blue('\n📦 [install] re-installing all dependencies\n'))
await $`pnpm install`
================================================
FILE: bin/dev.mjs
================================================
#!/usr/bin/env zx
import './env.mjs'
// path
const playgroundPath = 'playground'
// reset
if (argv?.reset) {
console.log(chalk.blue('\n💻 [dev] reset `playground` dir'))
await fs.remove(playgroundPath)
}
// build project
await $`zx bin/build.mjs`
// install
const playgroundExists = await fs.pathExists(playgroundPath)
console.log('')
if (!playgroundExists) {
console.log(chalk.blue('💻 [dev] create `playground` dir'))
await fs.ensureDir(playgroundPath)
console.log(chalk.blue('💻 [dev] run installer'))
cd(playgroundPath)
process.env.INSTALL_MODE = 'contributor'
await $`node ../dist/installer/bin/index.js`
}
else {
console.log(chalk.blue('💻 [dev] run prisma generate\n'))
cd(playgroundPath)
await $`npx prisma generate`
}
// start dev server
console.log(chalk.blue('💻 [dev] start dev server\n'))
await $`yarn dev`
================================================
FILE: bin/env.mjs
================================================
#!/usr/bin/env zx
process.env.FORCE_COLOR = 3
================================================
FILE: bin/postinstall.mjs
================================================
#!/usr/bin/env zx
import './env.mjs'
// set DATABASE_URL env variable to docker instance
process.env.DATABASE_URL = 'postgresql://prisma:prisma@localhost:5433/tests'
// install boilerplate dependencies using Yarn
console.log(chalk.blue('\n📦 [post-install] install cdk boilerplate dependencies\n'))
await $`cd packages/boilerplate/cdk && yarn install`
================================================
FILE: bin/publish/_pkg.core.cleanse.js
================================================
const fs = require('fs')
const path = require('path')
// Define absolute paths for original pkg and temporary pkg.
const ORIG_PKG_PATH = path.resolve(__dirname, '../../package.json')
const BACKUP_PKG_PATH = path.resolve(__dirname, '../../package-beforePublish.json')
const RESTORE_PKG_PATH = path.resolve(__dirname, '../../package-afterPublish.json')
// Obtain original `package.json` contents.
const pkgData = require(ORIG_PKG_PATH)
// Write/cache the original `package.json` data to `package-beforePublish.json` file.
fs.writeFile(BACKUP_PKG_PATH, JSON.stringify(pkgData, null, 4), (err) => {
if (err)
throw err
})
// Write/cache the original `package.json` data to `package-afterPublish.json` file.
fs.writeFile(RESTORE_PKG_PATH, JSON.stringify(pkgData, null, 4), (err) => {
if (err)
throw err
})
// Remove all scripts from the scripts section.
delete pkgData.scripts
// Remove all pkgs from the devDependencies section.
delete pkgData.devDependencies
// Remove pnpm engine
delete pkgData.engines.pnpm
// Overwrite original `package.json` with new data (i.e. minus the specific data).
fs.writeFile(ORIG_PKG_PATH, JSON.stringify(pkgData, null, 4), (err) => {
if (err)
throw err
})
================================================
FILE: bin/publish/_pkg.core.restore.js
================================================
const fs = require('fs')
const path = require('path')
// Define absolute paths for original pkg and temporary pkg.
const ORIG_PKG_PATH = path.resolve(__dirname, '../../package.json')
const BACKUP_PKG_PATH = path.resolve(__dirname, '../../package-beforePublish.json')
const RESTORE_PKG_PATH = path.resolve(__dirname, '../../package-afterPublish.json')
// Obtain original/cached contents (with new version) from `package-afterPublish`.
const pkgData = `${JSON.stringify(require(RESTORE_PKG_PATH), null, 4)}\n`
// Write data from `package-afterPublish` back to original `package.json`.
fs.writeFile(ORIG_PKG_PATH, pkgData, (err) => {
if (err)
throw err
})
// Delete the temporary `package-beforePublish` file.
fs.unlink(BACKUP_PKG_PATH, (err) => {
if (err)
throw err
})
// Delete the temporary `package-afterPublish` file.
fs.unlink(RESTORE_PKG_PATH, (err) => {
if (err)
throw err
})
================================================
FILE: bin/publish/_pkg.installer.cleanse.js
================================================
const fs = require('fs')
const path = require('path')
// Define absolute paths for original pkg and temporary pkg.
const SRC_PKG_PATH = path.resolve(__dirname, '../../packages/installer/package.json')
const DEST_PKG_PATH = path.resolve(__dirname, '../../dist/installer/package.json')
// Obtain original `package.json` contents.
const pkgData = require(SRC_PKG_PATH)
// Remove all scripts from the scripts section.
delete pkgData.scripts
// Remove all pkgs from the dependencies section.
delete pkgData.dependencies
// Remove all pkgs from the devDependencies section.
delete pkgData.devDependencies
// Remove private tag
delete pkgData.private
// Create new `package.json` with new data (i.e. minus the specific data).
fs.writeFile(DEST_PKG_PATH, JSON.stringify(pkgData, null, 4), (err) => {
if (err)
throw err
})
================================================
FILE: bin/publish.mjs
================================================
#!/usr/bin/env zx
/* eslint-disable @typescript-eslint/no-unused-vars */
import Listr from 'listr'
import prompts from 'prompts'
await $`zx bin/env.mjs`
$.verbose = false
async function getPublishConfig() {
const { tag } = await prompts({
type: 'select',
name: 'tag',
message: 'Select publish tag',
choices: [
{ title: 'preview', value: 'preview' },
{ title: 'latest', value: 'latest' },
],
initial: 0,
})
if (!tag)
process.exit()
let latestPublished = '0.0.9'
try {
latestPublished = String(await $`npm show prisma-appsync@${tag} version`)?.trim()
}
catch (err) {
try { latestPublished = String(await $`npm show prisma-appsync version`)?.trim() }
catch (err) {}
}
const minorPos = latestPublished.lastIndexOf('.')
const possibleFutureVersion = `${latestPublished.slice(0, minorPos)}.${
parseInt(latestPublished.slice(minorPos + 1)) + 1
}`
const { publishVersion } = await prompts({
type: 'text',
name: 'publishVersion',
message: `Enter new version for @${tag}? (latest = "${latestPublished}")`,
initial: possibleFutureVersion,
})
if (!publishVersion || publishVersion === latestPublished)
process.exit()
const { versionOk } = await prompts({
type: 'confirm',
name: 'versionOk',
message: `Run "pnpm publish --tag ${tag} --no-git-checks" with pkg version "${publishVersion}"?`,
initial: false,
})
return {
versionOk,
publishVersion,
tag,
}
}
async function publishCore({ tag }) {
console.log('Publishing Core...')
await new Listr([
{
title: 'Cleansing package.json',
task: async () => {
await $`node bin/publish/_pkg.core.cleanse`
},
},
{
title: `Publishing on NPM with tag ${tag}`,
task: async () => await $`pnpm publish --tag ${tag} --no-git-checks`,
},
{
title: 'Restoring package.json',
task: async () => await $`node bin/publish/_pkg.core.restore`,
},
]).run().catch((err) => {
console.error(err)
})
}
async function publishInstaller({ tag }) {
console.log('Publishing Installer...')
await new Listr([
{
title: 'Copy + Cleanse package.json',
task: async () => await $`node bin/publish/_pkg.installer.cleanse`,
},
{
title: 'Publishing on NPM',
task: async () => await $`cd ./dist/installer/ && pnpm publish --tag ${tag} --no-git-checks`,
},
]).run().catch((err) => {
console.error(err)
})
}
const publishConfig = await getPublishConfig()
if (publishConfig.versionOk) {
const corePkgFile = './package.json'
const installerPkgFile = './packages/installer/package.json'
// change package.json versions
console.log(`\nSetting publish version to ${publishConfig.publishVersion}...`)
const corePkg = await fs.readJson(corePkgFile)
const installerPkg = await fs.readJson(installerPkgFile)
corePkg.version = publishConfig.publishVersion
installerPkg.version = publishConfig.publishVersion
await fs.writeJson(corePkgFile, corePkg, { spaces: 4 })
await fs.writeJson(installerPkgFile, installerPkg, { spaces: 4 })
// preview?
if (publishConfig.tag === 'preview')
process.env.COMPILE_MODE = "preview"
// build + test
console.log('Building + Testing...')
await $`zx bin/test.mjs`
// publish packages
await publishCore(publishConfig)
await publishInstaller(publishConfig)
console.log('Done!')
}
================================================
FILE: bin/test.mjs
================================================
#!/usr/bin/env zx
/* eslint-disable no-console */
import './env.mjs'
// build
await $`zx bin/build.mjs`
// prisma client for tests
console.log(chalk.blue('\n🧪 [test] run prisma generate'))
await $`npx prisma generate --schema tests/generator/schemas/crud.prisma`
await $`npx prisma generate --schema tests/generator/schemas/@gql.prisma`
// unit tests
console.log(chalk.blue('🧪 [test] run unit tests\n'))
await $`VITE_CJS_IGNORE_WARNING=true vitest run tests`
================================================
FILE: docs/.vitepress/config.ts
================================================
export default {
title: 'Prisma-AppSync',
description: 'GraphQL API Generator for AWS and ◭ Prisma',
head: [['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }]],
vue: {
reactivityTransform: true,
},
lastUpdated: true,
themeConfig: {
logo: '/logo.svg',
editLink: {
text: 'Suggest changes to this page',
pattern: 'https://github.com/maoosi/prisma-appsync/edit/main/docs/:path',
},
socialLinks: [{ icon: 'github', link: 'https://github.com/maoosi/prisma-appsync' }],
lastUpdatedText: 'Updated Date',
footer: {
message: 'Released under the BSD 2-Clause License.',
copyright: 'Copyright © 2021-present Sylvain Simao',
},
nav: [
{ text: 'Documentation', link: '/quick-start/getting-started' },
{ text: 'Changelog', link: '/changelog/1.0.0' },
{ text: 'Support', link: '/support' },
{
text: 'Tools',
items: [
{
text: 'AppSync GraphQL Schema Diff',
link: '/tools/appsync-gql-schema-diff',
},
],
},
{
text: 'Links',
items: [
{
text: 'Report a bug',
link: 'https://github.com/maoosi/prisma-appsync/issues',
},
{
text: 'Sponsor',
link: 'https://github.com/sponsors/maoosi',
},
{
text: 'Roadmap',
link: 'https://github.com/users/maoosi/projects/1',
},
],
},
],
sidebar: [
{
text: 'Quick start',
items: [
{ text: 'Getting started', link: '/quick-start/getting-started' },
{ text: 'Installation', link: '/quick-start/installation' },
{ text: 'Usage', link: '/quick-start/usage' },
{ text: 'Deploy', link: '/quick-start/deploy' },
],
},
{
text: 'Features',
collapsible: true,
items: [
{ text: 'Lifecycle hooks', link: '/features/hooks' },
{ text: 'Custom resolvers', link: '/features/resolvers' },
{ text: 'Tweaking GQL schema', link: '/features/gql-schema' },
],
},
{
text: 'Security',
collapsible: true,
items: [
{ text: 'Authorization', link: '/security/appsync-authz' },
{ text: 'Shield (ACL rules)', link: '/security/shield-acl' },
{ text: 'XSS sanitizer', link: '/security/xss-sanitizer' },
{ text: 'Query depth', link: '/security/query-depth' },
{ text: 'Rate limiter (DOS)', link: '/security/rate-limiter' },
],
},
{
text: 'Contributing',
collapsible: true,
items: [
{ text: 'Contributions guide', link: '/contributing' },
],
},
{
text: 'Changelog',
collapsible: true,
collapsed: true,
items: [
{ text: '(latest) v1.0.0', link: '/changelog/1.0.0' },
{ text: 'Previous', link: '/changelog/' },
],
},
],
},
}
================================================
FILE: docs/.vitepress/theme/Layout.vue
================================================
<script setup>
import DefaultTheme from 'vitepress/theme'
const { Layout } = DefaultTheme
</script>
<template>
<Layout>
<template #aside-outline-after>
<div class="content">
<div class="title">Our sponsors</div>
<div>
<a href="https://kuizto.co/?utm_source=prisma_appsync&utm_medium=github" target="_blank">
<img src="/sponsors/kuizto-square.png" alt="Kuizto — The Everyday Cooking App" />
</a>
</div>
</div>
</template>
</Layout>
</template>
<style scoped>
.content {
position: relative;
border-left: 1px solid var(--vp-c-divider);
padding-left: 16px;
font-size: 13px;
font-weight: 500;
margin-top: 64px;
}
.title {
letter-spacing: 0.4px;
line-height: 28px;
font-size: 13px;
font-weight: 600;
}
</style>
================================================
FILE: docs/.vitepress/theme/index.ts
================================================
import Theme from 'vitepress/theme'
import Layout from './Layout.vue'
import './styles/vars.css'
export default {
extends: Theme,
Layout: Layout
}
================================================
FILE: docs/.vitepress/theme/styles/vars.css
================================================
/**
* Colors
* -------------------------------------------------------------------------- */
:root {
--vp-c-brand: #5379f4;
--vp-c-brand-light: #747bff;
--vp-c-brand-lighter: #9499ff;
--vp-c-brand-dark: #535bf2;
--vp-c-brand-darker: #454ce1;
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: var(--vp-c-brand-light);
--vp-button-brand-text: var(--vp-c-text-dark-1);
--vp-button-brand-bg: var(--vp-c-brand);
--vp-button-brand-hover-border: var(--vp-c-brand-light);
--vp-button-brand-hover-text: var(--vp-c-text-dark-1);
--vp-button-brand-hover-bg: var(--vp-c-brand-light);
--vp-button-brand-active-border: var(--vp-c-brand-light);
--vp-button-brand-active-text: var(--vp-c-text-dark-1);
--vp-button-brand-active-bg: var(--vp-button-brand-bg);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #5379f4 30%, #f59533);
}
/**
* Component: Algolia
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand) !important;
}
================================================
FILE: docs/changelog/1.0.0-rc.1.md
================================================
---
editLink: false
---
# 1.0.0-rc.1
::: warning 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](https://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
::: details Prisma-AppSync Client usage
`BREAKING`Usage 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 })
}
```
:::
::: details 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
})
```
:::
::: details 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`,
}
},
}
})
```
:::
::: details 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
},
},
})
```
:::
::: details 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.`,
},
}
},
})
```
:::
::: details 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 }])"
}
```
:::
::: details 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"])`
:::
::: details 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.
:::
::: details 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](https://github.com/sponsors/maoosi) and [opening a new issue](https://github.com/maoosi/prisma-appsync/issues).
:::
## 🎉 New features
::: details New documentation website
`NEW FEATURE`
Accessible from [prisma-appsync.vercel.app](https://prisma-appsync.vercel.app).
:::
::: details 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
```
:::
::: details 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.
:::
::: details Support added for Prisma 4.5.x
`NEW FEATURE`
Codebase adapted and fully tested for Prisma 4.5.x support.
:::
::: details 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
:::
::: details Support for Atomic Operations
`NEW FEATURE`
```graphql
mutation {
updatePost(
where: { id: 1 },
operation: {
views: { increment: 1 }
}
) {
views
}
}
```
:::
::: details Support for order by relational fields
`NEW FEATURE`
```graphql
query {
listPosts(
orderBy: {
author: {
name: ASC
}
}
) {
title
author {
name
}
}
}
```
:::
::: details Support for Case Sensitivity (PostgreSQL and MongoDB connectors only)
`NEW FEATURE`
```graphql
query {
listPosts(
where: {
title: {
contains: "prisma",
mode: "insensitive"
}
}
) {
title
}
}
```
:::
::: details 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.
:::
::: details 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.
:::
::: details 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.
:::
::: details 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
::: details Added support for uniqueIndexes (🙏 contribution from [@cipriancaba](https://github.com/cipriancaba))
🐙 [Issue #46](https://github.com/maoosi/prisma-appsync/issues/46) /[PR #47](https://github.com/maoosi/prisma-appsync/pull/47): Prisma is no longer providing idFields
:::
::: details Fixed casing issue on GraphQL relation fields (🙏 contribution from [@ryparker](https://github.com/ryparker))
🐙 [Issue #37](https://github.com/maoosi/prisma-appsync/issues/37) / [PR #38](https://github.com/maoosi/prisma-appsync/pull/38): Generated GraphQL schema relation definitions using the incorrect case for type values
:::
::: details Fixed issue on nullable relation fields in mutations
🐙 [Issue #26](https://github.com/maoosi/prisma-appsync/issues/26): Issue using nullable relation fields with mutations
:::
::: details Replaced env var JEST_WORKER_ID with PRISMA_APPSYNC_TESTING
🐙 [Issue #32](https://github.com/maoosi/prisma-appsync/issues/32): Issue performing tests because of JEST_WORKER_ID
:::
## 💛 Github Sponsors
Enjoy using Prisma-AppSync? Please consider sponsoring me at 🐙 [maoosi ↗](https://github.com/sponsors/maoosi) so that I can spend more time working on the project.
================================================
FILE: docs/changelog/1.0.0-rc.2.md
================================================
---
editLink: false
---
# 1.0.0-rc.2
**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**
## Major improvements
### Support for Prisma Fluent API syntax on Relation Filters
> 🚨 Breaking change affecting syntax for Relation Filters.
In this release, we are changing how to write relation filters. We are replacing the [original Prisma Client syntax](https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#filter-on--to-one-relations) (using `is` and `isNot` filters) with the newest [Fluent API syntax](https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#fluent-api) which feels more natural to write, but also allows using more complex Relation Filters such as `contains`, `endsWith`, `equals`, `gt`, `gte`, `lt`, `lte`, `in`, `not`, `notIn` and `startsWith`.
**Before**
```graphql
query {
listPosts(
where: {
author: { is: { username: "xxx" } }
}
) {
title
author { username }
}
}
```
**After**
```graphql
query {
listPosts(
where: {
author: { username: { equals: "xxx" } }
}
) {
title
author { username }
}
}
```
### Improved readability for underlying Prisma client errors
In this release, we have improved readability for all [known errors](https://www.prisma.io/docs/reference/api-reference/error-reference) thrown by the underlying Prisma Client. For example, using an incorrect connection URL (`DATABASE_URL`) will now return the below message as part of the API response:
> Error with Prisma client initialization. https://www.prisma.io/docs/reference/api-reference/error-reference#prismaclientinitializationerror
The full error trace will still appear inside the terminal and/or CloudWatch logs.
### New documentation guide: "Adding Hooks"
In this release, we have added a new guide on "Adding Hooks". Particularly useful to trigger actions and/or manipulate data `before` or `after` queries.
https://prisma-appsync.vercel.app/advanced/hooks.html
**Example snippet:**
```tsx
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
},
},
})
```
## Fixes
- [Issue using the `@aws_auth` directive along with additional authorization modes.](https://github.com/maoosi/prisma-appsync/pull/52)
- [Issue with `before` and `after` hook responses.](https://github.com/maoosi/prisma-appsync/pull/54)
## Credits
<table>
<tbody>
<tr>
<td align="center"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100px;" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#maintainer-maoosi" title="Maintainer">🧙♂️</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
<td align="center"><a href="http://www.cipriancaba.com"><img src="https://avatars.githubusercontent.com/u/695515?v=4?s=100" width="100px;" alt="Ciprian Caba"/><br /><sub><b>Ciprian Caba</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=cipriancaba" title="Code">💻</a> <a href="#ideas-cipriancaba" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
</tbody>
</table>
## Github sponsors
Enjoy using Prisma-AppSync? Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).
================================================
FILE: docs/changelog/1.0.0-rc.3.md
================================================
---
editLink: false
---
# 1.0.0-rc.3
**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**
## Fixes
- [Issue with Queries returning a single parameter (such as `count` queries)](https://github.com/maoosi/prisma-appsync/issues/61)
- [Issue with generated GraphQL input `CreateInput` when using `@default(uuid())` inside Prisma Schema](https://github.com/maoosi/prisma-appsync/issues/62)
## Credits
<table>
<tbody>
<tr>
<td align="center"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100px;" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#maintainer-maoosi" title="Maintainer">🧙♂️</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>
## Github sponsors
Enjoy using Prisma-AppSync? Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).
================================================
FILE: docs/changelog/1.0.0-rc.4.md
================================================
---
editLink: false
---
# 1.0.0-rc.4
**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**
## Highlights
### ⚡️ Local dev server is now using `vite-node` instead of `ts-node-dev`
Due to some incompatibilities between `ts-node-dev` and some of the newest changes, Vite is now used as the underlying Node runtime for the Prisma-AppSync local dev server.
To migrate an existing project using the local dev server, you'll need to edit the `dev` script inside your `package.json` and replace the following part:
```shell
npx ts-node-dev --rs --transpile-only --watch './*.ts' -- ./server.ts
```
with:
```shell
npx vite-node ./server.ts --watch --
```
### ⚡️ Local dev server upgraded to GraphQL Yoga v3, with the ability to use custom options
When using Prisma-AppSync local dev server, it is now possible to pass custom options from the `server.ts` file.
```ts
createServer({
yogaServerOptions: {
cors: {
origin: 'http://localhost:4000',
credentials: true,
allowedHeaders: ['X-Custom-Header'],
methods: ['POST']
}
/* ...other args */
}
})
```
For the full list of supported options, please refer to https://the-guild.dev/graphql/yoga-server/docs and the `createYoga` method.
## Fixes and improvements
- [Auto-populated fields (autoincrement, uuid, updatedAt, …) are now visible and directly editable from the GraphQL schema (Issue #70)](https://github.com/maoosi/prisma-appsync/issues/70)
- [Fixed issue with lists (arrays) in Prisma Schema not being properly cast into the GraphQL Schema (PR #78)](https://github.com/maoosi/prisma-appsync/pull/78)
- [Added `cuid` as part of the auto-populated fields (PR #72)](https://github.com/maoosi/prisma-appsync/pull/72)
- [Initialize `prismaArgs` with empty select (PR #69)](https://github.com/maoosi/prisma-appsync/pull/69)
- [Added an optional generic type for QueryParams (PR #74)](https://github.com/maoosi/prisma-appsync/pull/74)
- [Fixed issue with CDK boilerplate policy statements (Issue #64)](https://github.com/maoosi/prisma-appsync/issues/64)
- [Fixed docs using the wrong syntax for fine-grained access control examples (Issue #79)](https://github.com/maoosi/prisma-appsync/issues/79)
- CDK boilerplate Lambda function upgraded to `NODEJS_16_X`
- CDK boilerplate `warmUp(boolean)` parameter becomes `useWarmUp(number)`, allowing to specify the number of warm-up functions to use (default `0`)
## Credits
<table>
<tbody>
<tr>
<td align="center"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100px;" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#maintainer-maoosi" title="Maintainer">🧙♂️</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
<td align="center"><a href="http://www.cipriancaba.com"><img src="https://avatars.githubusercontent.com/u/695515?v=4?s=100" width="100px;" alt="Ciprian Caba"/><br /><sub><b>Ciprian Caba</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=cipriancaba" title="Code">💻</a> <a href="#ideas-cipriancaba" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://www.cameronjjenkinson.com"><img src="https://avatars.githubusercontent.com/u/5429478?v=4?s=100" width="100px;" alt="Cameron Jenkinson"/><br /><sub><b>Cameron Jenkinson</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=cjjenkinson" title="Code">💻</a></td>
<td align="center"><a href="http://bell.moe"><img src="https://avatars.githubusercontent.com/u/3979239?v=4?s=100" width="100px;" alt="Bell"/><br /><sub><b>Bell</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=Tenrys" title="Code">💻</a></td>
</tr>
</tbody>
</table>
## Github sponsors
Enjoy using Prisma-AppSync? Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).
================================================
FILE: docs/changelog/1.0.0-rc.5.md
================================================
---
editLink: false
---
# 1.0.0-rc.5
**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**
## Highlights
### ⚡️ Async shield rules
Async Shield rules are now supported in Prisma-AppSync, opening up to 3 different ways to define fine-grained access control rules:
```ts
return await prismaAppSync.resolve({
event,
shield: () => {
return {
// Boolean
'listPosts{,/**}': { rule: true },
// Function
'listPosts{,/**}': { rule: () => true },
// (NEW) Async Function
'listPosts{,/**}': {
rule: async () => {
await sleep(1000)
return true
},
},
}
},
})
```
### ⚡️ Support for deeply nested relation filters
Deeply nested relation filters are now supported in Prisma-AppSync, allowing to perform the following queries:
```graphql
query {
listComments(
where: {
author: {
# deeply nested relation filter
posts: {
every: {
published: { equals: true }
}
}
}
}
)
}
```
```graphql
query {
listUsers(
where: {
posts: {
every: {
# deeply nested relation filter
comments: {
every: {
message: { startsWith: 'hello' }
}
}
}
}
}
)
}
```
### ⚡️ Support for `extendedWhereUnique` preview feature
Using the `extendedWhereUnique` preview feature flag will enable filtering on non-unique fields in Prisma-AppSync, allowing to do the following:
```prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["extendedWhereUnique"]
}
```
```graphql
mutation($id: Int!, $version: Int) {
updatePost(
# version is a non-unique field
where: { id: $id, version: { equals: $version } },
operation: { version: { increment: 1 } }
) {
id
version
}
}
```
See [Prisma Docs](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#filter-on-non-unique-fields-with-userwhereuniqueinput) for more details.
## Fixes and improvements
- [`maxDepth` parameter not working properly with Json fields (Issue #71).](https://github.com/maoosi/prisma-appsync/issues/71)
- [Local dev server reads `undefined` when using nested arrays in query (Issue #83).](https://github.com/maoosi/prisma-appsync/issues/81)
- [GraphQL input `<Model>WhereUniqueInput` shouldn’t include Relation fields (Issue #83).](https://github.com/maoosi/prisma-appsync/issues/83)
- [Unit tests for Prisma to GraphQL schema conversion (Issue #84).](https://github.com/maoosi/prisma-appsync/issues/84)
- [Local dev server returning `null` for `0` values (PR #82).](https://github.com/maoosi/prisma-appsync/pull/82)
- [Issue: fields with `@default` should appear as required `!` in generated GraphQL schema base type (Issue #91).](https://github.com/maoosi/prisma-appsync/issues/91)
- Improved, more readable, Prisma Client errors logs.
## Credits
<table>
<tbody>
<tr>
<td align="center"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100px;" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#maintainer-maoosi" title="Maintainer">🧙♂️</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
<td align="center"><a href="http://www.cipriancaba.com"><img src="https://avatars.githubusercontent.com/u/695515?v=4?s=100" width="100px;" alt="Ciprian Caba"/><br /><sub><b>Ciprian Caba</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=cipriancaba" title="Code">💻</a> <a href="#ideas-cipriancaba" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://bell.moe"><img src="https://avatars.githubusercontent.com/u/3979239?v=4?s=100" width="100px;" alt="Bell"/><br /><sub><b>Bell</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=Tenrys" title="Code">💻</a></td>
</tr>
</tbody>
</table>
## Github sponsors
Enjoy using Prisma-AppSync? Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).
================================================
FILE: docs/changelog/1.0.0-rc.6.md
================================================
---
editLink: false
---
# 1.0.0-rc.6
**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**
> 🚨 This release include breaking changes, so please make sure to read the below thoroughly before upgrading.
## Breaking
### 💔 Updated `upsert<Model>` mutation params to be similar to Prisma Client API
This change is considered breaking if you are using `upsert` mutations.
```graphql
# before
mutation {
upsertPost(
where: { id: 1 }
data: { title: "Hello world" }
) {
title
}
}
# after
mutation {
upsertPost(
where: { id: 1 }
create: { title: "Hello world" }
update: { title: "Hello world" }
) {
title
}
}
```
### 💔 Updated `QueryParams.paths` format to fix various reported issues on Shield ACL rules
This change fixes various reported issues on Shield ACL rules. [See full details here.](https://github.com/maoosi/prisma-appsync/issues/125) It also allows creating more granular rules such as [`createPost/**/connect{,/**}`](https://globster.xyz/?q=createPost%2F**%2Fconnect%7B%2C%2F**%7D&f=createPost%2CcreatePost%2Ftitle%2CcreatePost%2Fauthor%2CcreatePost%2Fauthor%2Fconnect%2CcreatePost%2Fauthor%2Fconnect%2Fid%2CgetPost%2CgetPost%2Fid%2CgetPost%2Ftitle).
Only considered breaking if you have implemented advanced fine-grained access control rules, or if you are using `QueryParams.paths` for some custom business logic (most likely inside Hooks).
**Example:**
```graphql
mutation createPost {
createPost(
data: {
title: "Hello people"
author: { connect: { id: 1 } }
}
) {
id
title
}
}
```
**Before:**
```json
{
"paths": [
"/create/post/title",
"/create/post/author/id",
"/get/post/id",
"/get/post/title"
]
}
```
**After:**
```json
{
"paths": [
"createPost",
"createPost/title",
"createPost/author",
"createPost/author/connect",
"createPost/author/connect/id",
"getPost",
"getPost/id",
"getPost/title"
]
}
```
## Highlights
### ⚡️ Support for custom GraphQL scalars on fields
**Prisma schema:**
```prisma
/// @gql(scalars: { website: "AWSURL" })
model Company {
id Int @id @default(autoincrement())
name String
website String?
}
```
**GraphQL output:**
```graphql
type Company {
id: Int!
name: String!
website: AWSURL
}
```
### ⚡️ Support for nullable in Query filters
**Example #1:**
```graphql
query {
listUsers (
where: {
fullname: { isNull: true }
}
) {
id
}
}
```
**Example #2:**
```graphql
query {
listPosts (
where: {
author: { is: NULL }
}
) {
id
}
}
```
**Example #3:**
```graphql
query {
listPosts (
where: {
author: { isNot: NULL }
}
) {
id
}
}
```
### ⚡️ Refreshed documentation
[Prisma-AppSync documentation](https://prisma-appsync.vercel.app) has been refreshed with new navigation, revised content, and a new guide on [Tweaking the GraphQL Schema](https://prisma-appsync.vercel.app/features/gql-schema.html).
## Fixes and improvements
- [The local dev server now supports concurrent queries.](https://github.com/maoosi/prisma-appsync/issues/103)
- [The local dev server now returns __typename (similar to AppSync)](https://github.com/maoosi/prisma-appsync/issues/115)
- [All fields with `@default()` are now optional in GraphQL output](https://github.com/maoosi/prisma-appsync/issues/96)
- [Improved performances on ACL Shield Functions (checks now runs in parallel).](https://github.com/maoosi/prisma-appsync/issues/92)
- [Fixed issue with ACL Shield rules and WhereUniqueInput.](https://github.com/maoosi/prisma-appsync/issues/123)
- [Fixed issue with using `is` and `isNot` inside `some` or `every`.](https://github.com/maoosi/prisma-appsync/issues/102)
- [Fixed issue using arguments with no selectionSet on the local dev server.](https://github.com/maoosi/prisma-appsync/pull/104)
- [Fixed issue with `UpdateRelationsInput`, `delete` and `deleteMany` input types.](https://github.com/maoosi/prisma-appsync/pull/99)
## Sponsors
<table>
<tr>
<td align="center" style="width:300px;">
<a href="https://kuizto.co" rel="noopener" target="_blank">
<img src="https://prisma-appsync.vercel.app/sponsors/kuizto-logo.jpg" width="120px;" alt="kuizto.co"/>
<br /><sub><b>Solve and sparkle up your daily food life</b></sub>
</a>
</td>
<td align="center" style="width:300px;">
<a href="https://travistravis.co" rel="noopener" target="_blank">
<img src="https://prisma-appsync.vercel.app/sponsors/travistravis-logo.jpg" width="120px;" alt="travistravis.co"/>
<br /><sub><b>Collaborative travel planning</b></sub>
</a>
</td>
</tr>
</table>
## Credits
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100px;" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#creator-maoosi" title="Creator & maintainer">🐙</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://bell.moe"><img src="https://avatars.githubusercontent.com/u/3979239?v=4?s=100" width="100px;" alt="Bell"/><br /><sub><b>Bell</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=Tenrys" title="Code">💻</a> <a href="#ideas-Tenrys" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomschut"><img src="https://avatars.githubusercontent.com/u/4933446?v=4?s=100" width="100px;" alt="Tom"/><br /><sub><b>Tom</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=tomschut" title="Code">💻</a> <a href="#ideas-tomschut" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeremy-white"><img src="https://avatars.githubusercontent.com/u/42325631?v=4?s=100" width="100px;" alt="jeremy-white"/><br /><sub><b>jeremy-white</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=jeremy-white" title="Code">💻</a></td>
</tr>
</tbody>
</table>
## Annoucements
<img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="18px;" alt="Sylvain"/> **I am starting my Founder journey with [kuizto.co](https://kuizto.co).** Kuizto is a bit like Netflix for your daily food! Lots of visual cooking inspiration, auto-generated grocery lists, and a small social layer to share and discover deliciously simple meals. [Please register for early access](https://kuizto.co), launching later this year!
<img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="18px;" alt="Sylvain"/> **[Prisma-EdgeQL](https://github.com/kuizto/prisma-edgeql) is an edge-compatible Prisma Client (using PlanetScale driver).** The project was initially built as part of my work at [kuizto.co](https://kuizto.co) and will be released open-source soon. Please go [Star the repo](https://github.com/kuizto/prisma-edgeql) if you are interested!
## Github sponsors
Enjoy using Prisma-AppSync? **Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).**
================================================
FILE: docs/changelog/1.0.0-rc.7.md
================================================
---
editLink: false
---
# 1.0.0-rc.7
**🌟 Support Prisma-AppSync by Starring Our Repo!**
## Highlights
### Local Dev Server Transitioned to Amplify AppSync Simulator
> 🚨 Breaking Change: Please Read the Following Carefully Before Upgrading.
The previous version of our Prisma-AppSync local development server relied on GraphQL Yoga, complemented by several custom functions to mimic AWS Lambda and AppSync's internal behaviors. This was an effective approach initially but began to cause issues as Prisma-AppSync was used for more complex use cases.
To resolve these issues and simplify maintenance, we've opted to replace our bespoke implementation with Amplify AppSync Simulator. Amplify AppSync Simulator is an integral package within the [AWS Amplify CLI](https://github.com/aws-amplify/amplify-cli) and aims to accurately simulate the experience of using AppSync locally.
**This migration brings numerous advantages to using the local development server:**
- Enables the use of Codegen [Issue #137](https://github.com/maoosi/prisma-appsync/issues/137)
- Supports using GraphQL Fragments [Issue #112](https://github.com/maoosi/prisma-appsync/issues/112)
- Accommodates Authentication and Authorization modes provided by AWS AppSync, including Cognito User Pools.
- Enables Subscription support, using a local WebSocket server.
**For users already using the Prisma-AppSync local development server who wish to migrate, follow these steps:**
1. Substitute your local `server.ts` file with the newer version found at `packages/boilerplate/server.ts`.
2. Install the new required dependency using `yarn add js-yaml -D`.
3. Modify the CLI command to initiate the local development server within your `package.json` file (by default, this is the `dev` script).
4. Here are the corresponding before and after scripts:
```shell
# before
npx vite-node ./server.ts --watch --
--handler handler.ts
--schema prisma/generated/prisma-appsync/schema.gql
--port 4000
--watchers '[{"watch":["**/*.prisma","*.prisma"],"exec":"npx prisma generate && touch ./server.ts"}]'
--headers '{"x-fingerprint":"123456"}' # removed
# after
npx vite-node ./server.ts --watch --
--handler handler.ts
--schema prisma/generated/prisma-appsync/schema.gql
--resolvers prisma/generated/prisma-appsync/resolvers.yaml # added
--port 4000
--wsPort 4001 # added
--watchers '[{"watch":["**/*.prisma","*.prisma"],"exec":"npx prisma generate && touch ./server.ts"}]'
```
### Upgraded to Prisma 5.1.1+
> 🚨 Breaking Change: Please Read the Following Carefully Before Upgrading.
Prisma-AppSync internals were updated to support Prisma 5.1.1. One potentially breaking change is that the [`extendedWhereUnique`](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#filter-on-non-unique-fields-with-userwhereuniqueinput) preview feature was promoted to general availability. So newly generated `WhereUniqueInput` schema types exposes all fields on the model, not just unique fields.
### Updated Minimum NodeJS Version Requirement
> 🚨 Breaking Change: Please Read the Following Carefully Before Upgrading.
The compilation target of Prisma-AppSync was updated **from Node.js 14 to Node.js 16**. Please ensure you have the minimum required Node.js version (Node.js 16) enabled on your local environment and deployed Lambda function.
### Updated CDK Boilerplate
The provided CDK Boilerplate has been updated to use the latest depdencies and recommended CDK packages. In addition, the default Lambda function version has been updated to use **Node 18.X**.
## Fixes and improvements
- [GraphQL Schema adjusted to make some array elements and responses non-nullable.](https://github.com/maoosi/prisma-appsync/pull/133)
- [Schema generation issue when using Prisma @@id attributes](https://github.com/maoosi/prisma-appsync/issues/149)
## Sponsors
<table>
<tr>
<td align="center" style="width:300px;">
<a href="https://kuizto.co" rel="noopener" target="_blank">
<img src="https://prisma-appsync.vercel.app/sponsors/kuizto-logo.jpg" width="120px;" alt="kuizto.co"/>
<br /><sub><b>Reconnect with home cooking</b></sub>
</a>
</td>
</tr>
</table>
## Credits
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#creator-maoosi" title="Creator & maintainer">🐙</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomschut"><img src="https://avatars.githubusercontent.com/u/4933446?v=4?s=100" width="100" alt="Tom"/><br /><sub><b>Tom</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=tomschut" title="Code">💻</a> <a href="#ideas-tomschut" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://bell.moe"><img src="https://avatars.githubusercontent.com/u/3979239?v=4?s=100" width="100" alt="Bell"/><br /><sub><b>Bell</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=Tenrys" title="Code">💻</a> <a href="#ideas-Tenrys" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/max-konin"><img src="https://avatars.githubusercontent.com/u/1570356?v=4?s=100" width="100" alt="Max Konin"/><br /><sub><b>Max Konin</b></sub></a><br /><a href="https://github.com/maoosi/prisma-appsync/commits?author=max-konin" title="Code">💻</a></td>
</tr>
</tbody>
</table>
## Github sponsors
Enjoy using Prisma-AppSync? **Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).**
================================================
FILE: docs/changelog/1.0.0.md
================================================
---
editLink: false
---
# v1.0.0
**Support Prisma-AppSync by Starring Our Repo!**
## 🌟 Sponsor
[](https://kuizto.co/?utm_source=prisma_appsync&utm_medium=github)
[Kuizto.co](https://kuizto.co/?utm_source=prisma_appsync&utm_medium=github) is a cooking app that adds a unique twist to everyday cooking. Netflix-like feed to explore tailored recipes. Get inspired by others, save to cooklists, plan instantly!
## 🚀 Release Summary
- Prisma-AppSync officially stable! 🎉
- <u>Breaking</u> change to context alias values
- <u>Breaking</u> change to maximum query depth defaults
- Enhanced `@gql` and `@auth` directives for finer control
- Generator Revamp & New Diff Tool for improved GraphQL Schema output
- Streamlined Model Relations: `Create[Model]Without[Relation]Input`
- Default input values are now visible in your GraphQL IDE
- Added support for `AWS_LAMBDA` authorization mode
## 👀 Full Changelog
### 👉 Prisma-AppSync officially stable! 🎉
Exciting news! Prisma-AppSync has achieved stability and is already in use in multiple production projects. Time to celebrate the release of v1.0.0!
### 👉 <u>Breaking</u> change to context alias values
To streamline values in `Context.alias` (accessible from hooks and custom resolvers params), the `modify` alias has been renammed to `mutate`, and `batchModify` is now referred to as `batchMutate`.
### 👉 <u>Breaking</u> change to maximum query depth defaults
To align maximum query depth with the latest changes from v1.0.0, the `maxDepth` default value was changed from `3` to `4`.
To limit side effects, you have the option to manually set it to its previous value via:
```ts
const prismaAppSync = new PrismaAppSync({ maxDepth: 3 })
```
### 👉 Enhanced `@gql` and `@auth` directives for finer control
The `@gql` directive has been updated to provide more detailed control over CRUD operations:
```prisma
// before: only top-level rules were supported
@gql(queries: null, mutations: null)
// after: define specific rules for each CRUD operation
@gql(queries: { list: null, count: null }, mutations: { update: null, delete: null })
```
Same goes with the `@auth` directive, allowing granular access rules per operation:
```prisma
// before: only top-level rules were supported
@auth(queries: [{ allow: iam }])
// after: individual rules for specific query operations
@auth(queries: { list: [{ allow: iam }] })
```
Field-level authorization is now possible with the `@auth` directive:
```prisma
// newly supported field-level authorization rules
@auth(fields: { password: [{ allow: apiKey }] })
```
The `defaultDirective` in the prisma-appsync generator config is now optional, providing flexibility in configurations:
```prisma
generator appsync {
provider = "prisma-appsync"
// `defaultDirective` can be specified or omitted
defaultDirective = "@auth(model: [{ allow: iam }])"
}
```
When provided, `defaultDirective` seamlessly integrates with model-specific directives:
```prisma
// specified 'defaultDirective' for all models:
@auth(model: [{ allow: iam }])
// additional 'model directive' for enhanced control:
@auth(model: [{ allow: apiKey }])
// resulting merged directive for the model:
@auth(model: [{ allow: iam }, { allow: apiKey }])
```
### 👉 Generator Revamp for improved GraphQL Schema output
The Generator package has been totally rewritten to address reported issues and unlock a slew of new features. This not only makes the GraphQL Schema output more concise and well-optimized but also ensures Prisma-AppSync is ready for what's next.
With the largest production schemas, this revamp has led to a reduction of up to 500 lines in the GraphQL Schema output.
::: info Free online tool: AppSync GraphQL Schema Diff
To see the before/after with your own schema or simply compare two different AppSync Schemas, we've published a free online tool: [AppSync GraphQL Schema Diff](https://prisma-appsync.vercel.app/tools/appsync-gql-schema-diff.html).
:::
### 👉 Streamlined Model Relations: Create[Model]Without[Relation]Input
With the generator revamp, you can now create, update, or upsert any Model Relation (like Author) tied to a particular Model (such as Post) in just one GraphQL query. This eliminates the previous, more cumbersome process of inserting each Model separately and then manually associating them. The improvement is in sync with the Prisma Client API, offering a more streamlined and developer-friendly approach.
```gql
mutation {
createPost(
data: {
title: "Example post"
author: {
connectOrCreate: {
where: { name: "John Doe" }
create: { name: "John Doe" }
}
}
}
) {
title
author {
name
}
}
}
```
### 👉 Default input values are now visible in your GraphQL IDE
Considering the Prisma model example below:
```prisma
model Post {
content String
views Int @default(0)
isPublished Boolean @default(false)
}
```
This model will result in the following GraphQL schema:
```gql
input PostCreateInput {
content: String!
views: Int = 0
isPublished: Boolean = false
}
```
This update automatically fills the default values for `views` (0) and `isPublished` (false) in your GraphQL IDE, making it easier to see and work with your schema defaults.
### 👉 Added support for `AWS_LAMBDA` authorization mode
You can now utilize `AWS_LAMBDA` as an authorization mode with the `@auth` directive:
```prisma
// AWS_LAMBDA
@auth(model: [{ allow: lambda }])
```
## 🙏 Credits
<a href="https://sylvainsimao.fr"><img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="100" alt="Sylvain"/><br /><sub><b>Sylvain</b></sub></a><br /><a href="#creator-maoosi" title="Creator & maintainer">🐙</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Code">💻</a> <a href="#ideas-maoosi" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/maoosi/prisma-appsync/commits?author=maoosi" title="Documentation">📖</a>
## 💛 Github Sponsors
Enjoy using Prisma-AppSync? **Please consider [💛 Github sponsors](https://github.com/sponsors/maoosi).**
================================================
FILE: docs/changelog/index.md
================================================
# Changelog
- [(latest) v1.0.0](/changelog/1.0.0.html)
- [1.0.0-rc.7](/changelog/1.0.0-rc.7.html)
- [1.0.0-rc.6](/changelog/1.0.0-rc.6.html)
- [1.0.0-rc.5](/changelog/1.0.0-rc.5.html)
- [1.0.0-rc.4](/changelog/1.0.0-rc.4.html)
- [1.0.0-rc.3](/changelog/1.0.0-rc.3.html)
- [1.0.0-rc.2](/changelog/1.0.0-rc.2.html)
- [1.0.0-rc.1](/changelog/1.0.0-rc.1.html)
================================================
FILE: docs/contributing.md
================================================
# Contributions guide
Thanks for your interest in contributing!
## 👉 Discuss first
Before starting to work on a pull request, it's always better to open an issue first to confirm its desirability and discuss the approach with the maintainers.
## 👉 Project packages
<table>
<tr>
<td width="800px">
**`packages/generator`**
Generator for [Prisma ORM](https://www.prisma.io/), whose role is to parse your Prisma Schema and generate all the necessary components to run and deploy a GraphQL API tailored for AWS AppSync.
</td>
</tr>
<tr>
<td>
**`packages/client`**
Think of it as [Prisma Client](https://www.prisma.io/client) for GraphQL. Fully typed and designed for AWS Lambda AppSync Resolvers. It can handle CRUD operations with just a single line of code, or be fully extended.
</td>
</tr>
<tr>
<td>
**`packages/installer`**
Interactive CLI tool that streamlines the setup of new Prisma-AppSync projects, making it as simple as running `npx create-prisma-appsync-app@latest`.
</td>
</tr>
<tr>
<td>
**`packages/server`**
Local dev environment that mimics running Prisma-AppSync in production. It includes an AppSync simulator, local Lambda resolvers execution, a GraphQL IDE, hot-reloading, and authorizations.
</td>
</tr>
</table>
## 👉 Repository setup
We use `pnpm` as the core package manager, `yarn` + `docker` for creating the AWS CDK bundle before deployment, `zx` for running scripts, `aws` + `cdk` CLIs for deployment.
**Start with cloning the repo on your local machine:**
```bash
git clone https://github.com/maoosi/prisma-appsync.git
```
**Checkout the `dev` branch (working branch):**
```bash
git checkout dev
```
**Install pre-requirements:**
| Step |
|:-------------|
| 1. Install NodeJS, [latest LTS is recommended ↗](https://nodejs.org/en/about/releases/) |
| 2. Install [pnpm ↗](https://pnpm.js.org/) |
| 3. Install [yarn@1 ↗](https://classic.yarnpkg.com/en/docs/install/) |
| 4. Install [zx ↗](https://github.com/google/zx) |
| 5. Install [docker ↗](https://www.docker.com/products/docker-desktop) |
| 6. Install the [AWS CLI ↗](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) |
| 7. Install the [AWS CDK ↗](https://github.com/aws/aws-cdk) |
**Verify installation:**
```bash
node -v && pnpm --version && yarn --version && zx --version && docker --version && aws --version && cdk --version
```
**Install dependencies:**
```bash
pnpm install
```
**Run local dev playground:**
```bash
pnpm dev
```
> See list of commands below for more details about `pnpm dev`.
## 👉 Commands
| Command | Description |
| ------------- |:-------------|
| `pnpm install` | Install project dependencies. |
| `pnpm test` | Run all unit tests and e2e tests. |
| `pnpm build` | Build the entire prisma-appsync library. |
| `pnpm dev` | Creates local dev setup, useful for contributing [1]. |
> [1] Auto-generates a 'playground' folder (if not there already) and launches a local GraphQL + AWS AppSync server. This simulates the Prisma-AppSync AWS environment for local development, with 'playground' contents pointing to local source packages.
## 👉 Commit convention
We use [Conventional Commits ↗](https://www.conventionalcommits.org/) for commit messages such as:
```ts
<type>[optional scope]: <description>
```
> - Possible types: `feat` / `fix` / `chore` / `docs`
> - Possible scopes: `client` / `generator` / `cli` / `boilerplate` / `server`
> - Description: Short description, with issue number when relevant.
Here are some examples:
| Type | Commit message |
|:------------- |:------------- |
| Bug fix | `fix(client): issue #234 - JEST_WORKER_ID replaced` |
| New feature | `feat(generator): new defaultDirective parameter` |
| Routine task | `chore: deps updated to latest` |
| Docs update | `docs: fix typo inside home` |
## 👉 Coding guidelines
### ESLint
We use [ESLint ↗](https://eslint.org/) for both linting and formatting.
<table><tr><td width="500px" valign="top">
#### IDE Setup
We recommend using [VS Code ↗](https://code.visualstudio.com/) along with the [ESLint extension ↗](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint).
With the settings on the right, you can have auto-fix and formatting when you save the code you are editing.
</td><td width="500px"><br>
VS Code's `settings.json`
```json
{
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true
}
}
```
</td></tr></table>
### No Prettier
Since ESLint is already configured to format the code, there is no need to duplicate the functionality with Prettier. If you have Prettier installed in your editor, we recommend you disable it when working on the project to avoid conflict.
## 👉 License
When you contribute code to the Prisma-AppSync project, you grant the maintainers permission to use and share your code under the project's BSD 2-Clause License. You also affirm that you are the original author of the code and have the authority to license it.
================================================
FILE: docs/features/gql-schema.md
================================================
# Tweaking GraphQL Schema
Prisma-AppSync provides ways to tweak and customise the GraphQL Schema output.
## 👉 Models directives
Tweaking the GraphQL schema for a given model require to write directives via AST comments (triple-slash `///`).
```prisma
/// @gql(mutations: null, subscriptions: null)
/// @gql(fields: { password: null })
/// @gql(scalars: { email: "AWSEmail" })
model User {
id Int @id @default(autoincrement())
email String
password String
}
```
## 👉 Usage with @gql syntax
### Disabling an entire model
```prisma
// Disable all queries, mutations and subscriptions
@gql(model: null)
```
### Disabling queries
```prisma
// Disable all queries (get, list, count, ...)
@gql(queries: null)
// Disable granular queries
@gql(queries: { list: null, count: null })
```
### Disabling mutations
```prisma
// Disable all mutations (create, update, upsert, delete, ...)
@gql(mutations: null)
// Disable granular mutations
@gql(mutations: { update: null, delete: null })
```
> **Cascading Rules:**
>
> - Disabling `update` **will also disable** `upsert`
> - Disabling `create` **will also disable** `upsert`
> - Disabling `mutations` **will also disable** `subscriptions`
### Disabling subscriptions
```prisma
// Disable all subscriptions (onCreated, onUpdated, ...)
@gql(subscriptions: null)
// Disable granular subscriptions
@gql(mutations: { onCreated: null, onUpdated: null })
```
### Hiding fields
```prisma
// If applied to a model with a `password` field:
// hide `password` field from the generated Type
@gql(fields: { password: null })
```
> **Note:** To maintain Prisma Client integrity, hidden fields remain writable in mutation operations.
### Custom scalars on fields
```prisma
// If applied to a model with a `website` (string) field:
// use scalar `AWSURL` instead of default `String`
@gql(scalars: { website: "AWSURL" })
```
================================================
FILE: docs/features/hooks.md
================================================
# Lifecycle hooks
Hooks let you “hook into” Prisma-AppSync lifecycle to either trigger custom business logic or manipulate data at runtime.
## 👉 Example code
Basic example:
```ts
return await prismaAppSync.resolve({
event,
hooks: {
// Mutate Post title before creation on database
'before:createPost': async (params: BeforeHookParams) => {
params.prismaArgs.data.title = 'New post title'
return params
},
// Override query result using always the same Post title
'after:listPosts': async (params: AfterHookParams) => {
params.result = params.result.map(r => r.title = 'Always the same title')
return params
},
},
})
```
Advanced example:
```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
},
},
})
```
## 👉 Types
```ts
export interface QueryParams {
type: GraphQLType
operation: string
context: Context
fields: string[]
paths: string[]
args: any
prismaArgs: PrismaArgs
authorization: Authorization
identity: Identity
headers: any
prismaClient: PrismaClient
}
type BeforeHookParams = QueryParams
type AfterHookParams = QueryParams & {
result: any | any[]
}
```
## 👉 Usage rules
- Hooks are made of a **Path** (e.g. `after:updatePost`) and an async function.
- **Path** syntax always starts with `before:` or `after:`.
> `before` or `after` querying data from the database.
- **Path** syntax after `:` uses [Micromatch syntax](https://github.com/micromatch/micromatch).
- Hooks are fully typed, so VSCode IntelliSense will give you the full list of Hooks Paths you can use while typing. Example:

- Hooks functions all receive a single object as a parameter. Here is an example object received inside `after:getPost`:
```json
{
"type": "Query",
"operation": "getPost",
"context": { "action": "get", "alias": "access", "model": "Post" },
"fields": ["title", "status"],
"paths": ["get/post/title", "get/post/status"],
"args": { "where": { "id": 5 } },
"prismaArgs": {
"where": { "id": 5 },
"select": { "title": true, "status": true }
},
"authorization": "API_KEY",
"identity": {},
"result": { "title": "My first post", "status": "PUBLISHED" }
}
```
- Key `result` is only available inside `after` hooks.
- Hooks async functions MUST return the object received as a parameter (either mutated or untouched).
- Using hooks on custom resolvers requires explicitly listing resolvers using a TypeScript Generic `prismaAppSync.resolve<T>`:
```ts
// Using custom resolver `likePost`
return await prismaAppSync.resolve<'likePost'>({ event, hooks })
// Using multiple custom resolvers
return await prismaAppSync.resolve<'likePost' | 'unlikePost'>({ event, hooks })
```
================================================
FILE: docs/features/resolvers.md
================================================
# Custom resolvers
Let's assume we want to extend our GraphQL CRUD API and add a custom mutation `incrementPostsViews` based on our Prisma Schema:
```prisma
model Post {
id Int @id @default(autoincrement())
views Int
}
```
## 👉 1. Extending our GraphQL Schema
To extend our auto-generated `schema.gql`, we will create a new `custom-schema.gql` file next to our `schema.prisma` file:
```graphql
extend type Mutation {
"""
Increment post views by +1
"""
incrementPostsViews(postId: Int!): Post
}
```
For Prisma-AppSync to merge our `custom-schema.gql` with the auto-generated schema, we edit the `schema.prisma` generator config:
```json{3}
generator appsync {
provider = "prisma-appsync"
extendSchema = "./custom-schema.gql"
}
```
## 👉 2. Extending our Resolvers Config
For AWS AppSync to be able to use our new `incrementPostsViews` mutation, we also create a new `custom-resolvers.yaml` next to our `schema.prisma` file:
```yaml
- typeName: Mutation
fieldName: incrementPostsViews
dataSource: prisma-appsync
```
For Prisma-AppSync to merge our `custom-resolvers.yaml` with the auto-generated resolvers config, we edit the `schema.prisma` generator config:
```json{4}
generator appsync {
provider = "prisma-appsync"
extendSchema = "./custom-schema.gql"
extendResolvers = "./custom-resolvers.yaml"
}
```
## 👉 3. Coding our new Resolver Function
Querying the `incrementPostsViews` mutation will automatically run a Resolver Function inside our Lambda `handler.ts` file. This is where we will code our custom business logic.
```ts
return await prismaAppSync.resolve<'incrementPostsViews'>({
event,
resolvers: {
// code for our new resolver function
incrementPostsViews: async ({ args, prismaClient }: QueryParamsCustom) => {
return await prismaClient.post.update({
data: { views: { increment: 1 } },
where: { id: args.postId }
})
},
}
})
```
## 👉 4. Updating our CDK file for bundling
To make sure our `custom-schema.gql` and `custom-resolvers.yaml` are properly bundled and deployed on AWS, we update the `beforeBundling` function inside `cdk/index.ts`:
```ts
function: {
bundling: {
commandHooks: {
beforeBundling(inputDir: string, outputDir: string): string[] {
const schema = path.join(inputDir, 'prisma/schema.prisma')
const gql = path.join(inputDir, 'prisma/custom-schema.gql')
const yaml = path.join(inputDir, 'prisma/custom-resolvers.yaml')
return [
`cp ${schema} ${outputDir}`,
`cp ${gql} ${outputDir}`,
`cp ${yaml} ${outputDir}`,
]
},
},
}
}
```
🚀 **Done! Next time we deploy on AWS, we will be able to use our new `incrementPostsViews` mutation.**
================================================
FILE: docs/index.md
================================================
---
layout: home
hero:
name: Prisma-AppSync
text: GQL API Generator for Prisma ORM
tagline: Turns your Prisma Schema into a fully-featured GraphQL API, tailored for AWS AppSync.
image:
src: /logo.svg
alt: Prisma-AppSync
actions:
- theme: brand
text: Try Prisma-AppSync
link: /quick-start/getting-started
- theme: alt
text: View on GitHub ↗
link: https://github.com/maoosi/prisma-appsync
features:
- icon: ◭
title: Prisma Schema to CRUD API
details: Deploy a GraphQL API from your Prisma Schema with auto-generated CRUD.
- icon: ⚡️
title: GraphQL on AWS AppSync
details: Serverless GraphQL with real-time updates and built-in security on AppSync.
- icon: 🧑💻
title: Fast and Flexible DX
details: Build and deploy a working API in minutes, easily customise to your needs.
---
================================================
FILE: docs/quick-start/deploy.md
================================================
# Deploy
## 👉 1. Prepare your local machine
Make sure to install the below on your local machine:
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [AWS CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install)
- [Docker](https://docs.docker.com/get-docker/)
Then [configure your local environment](https://docs.aws.amazon.com/cdk/v2/guide/cli.html#cli-environment) with the AWS Account of your choice.
## 👉 2. Setup a Database
Setup the database of your choice. It doesn't have to be hosted on Amazon AWS, you can use any database supported by Prisma. If you are not sure what to use, we recommend using [PlanetScale](https://planetscale.com) and read the following [integration guide](https://planetscale.com/docs/tutorials/prisma-quickstart).
## 👉 3. Deploy on AWS
Run the below CDK CLI command:
> Where `DATABASE_URL` is your own [database connection url](https://www.prisma.io/docs/reference/database-reference/connection-urls).
```bash
DATABASE_URL=mysql://xxx yarn deploy
```
**🚀 Done! Your GraphQL API is now ready to use.**
================================================
FILE: docs/quick-start/getting-started.md
================================================
# Getting started
**Prisma-AppSync** seamlessly transforms your [Prisma Schema](https://www.prisma.io) into a comprehensive GraphQL API, tailored for [AWS AppSync](https://aws.amazon.com/appsync/).
<table><tr><td width="500px" valign="top">
**From `schema.prisma`:**
```prisma
model Post {
id Int
title String
}
```
</td><td width="500px">
**To full-blown GraphQL API:**
```graphql
query list {
listPosts {
id
title
}
}
```
</td></tr></table>
## 👉 Features
💎 **Use your ◭ Prisma Schema**<br/>Quickly define your data model and deploy a GraphQL API tailored for AWS AppSync.
⚡️ **Auto-generated CRUD operations**<br/>Using Prisma syntax, with a robust TS Client designed for AWS Lambda Resolvers.
⛑ **Pre-configured security**<br/>Built-in XSS protection, query depth limitation, and in-memory rate limiting.
🔐 **Fine-grained ACL and authorization**<br/>Flexible security options such as API keys, IAM, Cognito, and more.
🔌 **Fully extendable features**<br/>Customize your GraphQL schema, API resolvers, and data flow as needed.
## 👉 Built around 4 packages
<table>
<tr>
<td width="800px">
**`packages/generator`**
Generator for [Prisma ORM](https://www.prisma.io/), whose role is to parse your Prisma Schema and generate all the necessary components to run and deploy a GraphQL API tailored for AWS AppSync.
</td>
</tr>
<tr>
<td>
**`packages/client`**
Think of it as [Prisma Client](https://www.prisma.io/client) for GraphQL. Fully typed and designed for AWS Lambda AppSync Resolvers. It can handle CRUD operations with just a single line of code, or be fully extended.
</td>
</tr>
<tr>
<td>
**`packages/installer`**
Interactive CLI tool that streamlines the setup of new Prisma-AppSync projects, making it as simple as running `npx create-prisma-appsync-app@latest`.
</td>
</tr>
<tr>
<td>
**`packages/server`**
Local dev environment that mimics running Prisma-AppSync in production. It includes an AppSync simulator, local Lambda resolvers execution, a GraphQL IDE, hot-reloading, and authorizations.
</td>
</tr>
</table>
================================================
FILE: docs/quick-start/installation.md
================================================
# Installation
## 👉 Option 1: Using the CLI Installer (recommended)
Run the following command and follow the prompts 🙂
```bash
npx create-prisma-appsync-app@latest
```
🚀 Done!
## 👉 Option 2: Manual Install
Add `prisma-appsync` to your project dependencies.
```bash
# using yarn
yarn add prisma-appsync
# using npm
npm i prisma-appsync
```
Edit your `schema.prisma` file and add:
```json
generator appsync {
provider = "prisma-appsync"
}
```
Also make sure to use the right binary targets:
```json{3}
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-1.0.x"]
}
```
Generate your Prisma Client (this will also generate your Prisma-AppSync client):
```bash
npx prisma generate
```
Create your `handler.ts` Lambda handler (AppSync Direct Lambda Resolver):
```ts
// Import generated Prisma-AppSync client (adjust path as necessary)
import { PrismaAppSync } from './prisma/generated/prisma-appsync/client'
// Instantiate Prisma-AppSync Client
const prismaAppSync = new PrismaAppSync()
// Lambda handler (AppSync Direct Lambda Resolver)
export const main = async (event: any) => {
return await prismaAppSync.resolve({ event })
}
```
Either copy the AWS CDK boilerplate provided with Prisma-AppSync into your project, OR just use it as a reference for your own CDK config:
```bash
# path to cdk boilerplate
./node_modules/prisma-appsync/dist/boilerplate/cdk/
```
Refer to [AWS CDK Toolkit docs ↗](https://docs.aws.amazon.com/cdk/v2/guide/cli.html) for more info.
================================================
FILE: docs/quick-start/usage.md
================================================
# Usage
## 👉 Folder structure
Using the CLI Installer (recommended):
```bash
project/
|__ handler.ts # lambda function handler (API resolver)
|__ server.ts # local server (for dev)
|__ cdk/ # AWS CDK deploy boilerplate
|__ prisma/
|__ schema.prisma # prisma schema (data source)
|__ generated/ # auto-generated after each `npx prisma generate`
```
## 👉 Generating the API
Run the below command from the project root directory:
```bash
npx prisma generate
```
After each `prisma generate`, files inside `prisma/generated` will be auto-generated.
## 👉 Local dev server
Run the local server and try Prisma-AppSync locally (only if using the CLI Installer):
```bash
yarn run dev
```
This will automatically push your Prisma Schema changes to a SQLite database, as well as launch a local GraphQL IDE server (with auto-reload and TS support).
================================================
FILE: docs/security/appsync-authz.md
================================================
# AppSync Authorization modes
AWS AppSync provides [authz directives ↗](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html) for configuring security and data protection.
::: warning SECURITY MUST NEVER BE TAKEN FOR GRANTED
Prisma-AppSync implements a basic mechanism to help mitigate some common issues. However, accuracy is not guaranteed and you should always test your own API security implementation.
:::
## 👉 Models directives
Applying AppSync authorization modes for a given model require to write directives using AST comments (triple-slash `///`).
```prisma
/// @auth(model: [{ allow: iam }, { allow: apiKey }])
model Post {
id Int @id @default(autoincrement())
title String
}
```
## 👉 Usage with @auth syntax
> **Note:** For now, `@auth` only works supports the `allow` key.
### Entire model
```prisma
// Apply to all queries, mutations and subscriptions
@auth(model: [{ allow: iam }])
```
### Queries
```prisma
// Apply to all queries (get, list, count, ...)
@auth(queries: [{ allow: iam }])
// Apply to granular queries
@auth(queries: { list: [{ allow: iam }] })
```
### Mutations
```prisma
// Apply to all mutations (create, update, upsert, delete, ...)
@auth(mutations: [{ allow: iam }])
// Apply to granular mutations
@auth(mutations: { create: [{ allow: iam }] })
```
### Subscriptions
```prisma
// Apply to all subscriptions (onCreated, onUpdated, ...)
@auth(subscriptions: [{ allow: iam }])
// Apply to granular subscriptions
@auth(subscriptions: { onCreated: [{ allow: iam }] })
```
### Fields
```prisma
// Apply to specific Type fields
@auth(fields: { password: [{ allow: apiKey }] })
```
## 👉 Supported Authorization modes
<https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html>
```prisma
// API_KEY Authorization
@auth(model: [{ allow: apiKey }])
// AWS_IAM
@auth(model: [{ allow: iam }])
// OPENID_CONNECT
@auth(model: [{ allow: oidc }])
// AWS_LAMBDA
@auth(model: [{ allow: lambda }])
// AMAZON_COGNITO_USER_POOLS
@auth(model: [{ allow: userPools }])
// AMAZON_COGNITO_USER_POOLS with groups
@auth(model: [{ allow: userPools, groups: ["users", "admins"] }])
// Allow multiples
@auth(model: [{ allow: apiKey }, { allow: userPools, groups: ["admins"] }])
```
## 👉 Default directive
It is also possible to set a `defaultDirective`, that will apply to all generated Types:
```prisma{3}
generator appsync {
provider = "prisma-appsync"
defaultDirective = "@auth(model: [{ allow: iam }])"
}
```
When provided, `defaultDirective` seamlessly integrates with model-specific directives:
```prisma
// specified 'defaultDirective' for all models:
@auth(model: [{ allow: iam }])
// additional 'model directive' for enhanced control:
@auth(model: [{ allow: apiKey }])
// resulting merged directive for the model:
@auth(model: [{ allow: iam }, { allow: apiKey }])
```
================================================
FILE: docs/security/query-depth.md
================================================
# Query depth
## 👉 Usage
Prisma-AppSync automatically prevents from abusing query depth, by limiting query complexity.
**For example, it will prevent from doing this:**
```graphql
query IAmEvil {
author(id: "abc") {
posts {
author {
posts {
author {
posts {
author {
# that could go on as deep as the client wants!
}
}
}
}
}
}
}
}
```
Default value for the maximum query depth is set to `4`. It is possible to change the default max depth value via the `maxDepth` option:
```ts
const prismaAppSync = new PrismaAppSync({ maxDepth: 3 })
```
================================================
FILE: docs/security/rate-limiter.md
================================================
# Rate limiter (DOS)
::: warning WARNING NOTICE
Limits are kept in memory and are not shared between function instantiations. This means limits can reset arbitrarily when new instances get spawned or different instances are used to serve requests.
:::
## 👉 Usage
Prisma-AppSync uses in-memory rate-limiting to try protect your Database from most common DOS attacks.
To change the default value (default to 200 requests per user, per minute), you can adjust the `maxReqPerUserMinute` option when instantiating the Client:
```ts
const prismaAppSync = new PrismaAppSync({ maxReqPerUserMinute: 500 })
```
## 👉 Disable rate limiter
If you prefer to disable the in-memory rate limiter, set the option to false:
```ts
const prismaAppSync = new PrismaAppSync({ maxReqPerUserMinute: false })
```
================================================
FILE: docs/security/shield-acl.md
================================================
# Shield (Access Control Rules)
Fine-grained access control rules can be used via the `shield` property of Prisma-AppSync client, directly inside the Lambda Handler function.
::: warning SECURITY MUST NEVER BE TAKEN FOR GRANTED
Prisma-AppSync implements a basic mechanism to help mitigate some common issues. However, accuracy is not guaranteed and you should always test your own API security implementation.
:::
## 👉 Basic example
For example, we might want to only allow access to `PUBLISHED` posts:
```ts
return await prismaAppSync.resolve({
event,
shield: () => {
// Prisma filtering syntax
// https://www.prisma.io/docs/concepts/components/prisma-client/filtering-and-sorting
const isPublished = { status: { equals: 'PUBLISHED' } }
return {
// Micromatch syntax
// https://github.com/micromatch/micromatch
'getPost{,/**}': {
rule: isPublished,
reason: () => 'Unpublished Posts cannot be accessed.',
},
}
},
})
```
Useful links to create shield rules:
- [Micromatch syntax](https://github.com/micromatch/micromatch)
- [Micromatch tester](https://globster.xyz/?q=getPost%7B%2C%2F**%7D&f=getPost%2Ftitle%2CgetPost%2Fstatus)
## 👉 Usage with AppSync Authorization modes
Combining fine-grained access control with [AppSync Authorization modes](/security/appsync-authz) allows to implement powerful controls around data.
Let's assume we want to restrict API access to users logged in via `AMAZON_COGNITO_USER_POOLS` and only allow the owner of a given Post to modify it:
```ts
return await prismaAppSync.resolve({
event,
shield: ({ authorization, identity }: QueryParams) => {
const isCognitoAuth = authorization === Authorizations.AMAZON_COGNITO_USER_POOLS
const isOwner = { owner: { cognitoSub: identity?.sub } }
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.`,
},
}
},
})
```
> The above example implies using Cognito User Pools Authorization. Plus having set up an `Owner` relation on the `Post` model, and a `cognitoSub` field on the `User` model (containing all users `sub`).
## 🚨 Order matters
The latest matching rule ALWAYS overrides previous ones.
```ts
// Bad - Second rule overrides first one
return {
'listUsers/password': false,
'listUsers{,/**}': true,
}
// Good - Always write the more specific rules last
return {
'listUsers{,/**}': true,
'listUsers/password': false
}
```
================================================
FILE: docs/security/xss-sanitizer.md
================================================
# XSS sanitizer
## 👉 Usage
Prisma-AppSync automatically perform XSS sanitization and encode all data coming through the GraphQL API.
**Take a look at this example:**
<table><tr><td width="800px">
1/ Assuming the following GraphQL Input:
```graphql
mutation maliciousPost($title: String!) {
createPost(data: { title: $title }) {
title
}
}
```
```json
{
"title": "<IMG SRC=\"javascript:alert('XSS');\">"
}
```
</td></tr><tr><td>
2/ Prisma-AppSync will automatically remove the malicious code and encode Html, before storing anything in the database:
| Column name | Value |
| ------------- |:-------------|
| title | `<img src>` |
</td></tr><tr><td>
3/ Finally, the GraphQL API will also automatically clarify (decode) all data before sending the response:
```ts
console.log(post.title) // output: "<img src>"
```
</td></tr></table>
## 👉 Disable xss sanitization
If you prefer to disable data sanitization, set the `sanitize` option to false when instantiating the Client:
```ts
const prismaAppSync = new PrismaAppSync({ sanitize: false })
```
================================================
FILE: docs/support.md
================================================
# Support
## <img src="https://avatars.githubusercontent.com/u/4679377?v=4?s=100" width="50" alt="Sylvain"/>
**👋 Hi, I’m Sylvain! [On-demand CTO](https://sylvainsimao.com/freelance/) and creator of ◭ Prisma-AppSync.**
Using Prisma-AppSync in production and looking for hourly paid support? You can contact me on [Twitter](https://twitter.com/Sylvain_Simao), message me on [LinkedIn](https://www.linkedin.com/in/sylvainsimao/), or [send me an email](https://sylvainsimao.com/contact).
================================================
FILE: docs/tools/appsync-gql-schema-diff.md
================================================
---
aside: false
---
<script lang="ts" setup>
import { buildSchema } from 'graphql';
import { diff as diffSchema } from '@graphql-inspector/core'
import { ref, watch } from 'vue'
const old_schema = ref('')
const new_schema = ref('')
const output = ref<{ message: string; level: "BREAKING" | "NON_BREAKING" | "DANGEROUS"}[]>([])
const error = ref(false)
watch([old_schema, new_schema], async () => {
error.value = false
try {
const aws = `
scalar AWSDate
scalar AWSTime
scalar AWSDateTime
scalar AWSTimestamp
scalar AWSEmail
scalar AWSJSON
scalar AWSURL
scalar AWSPhone
scalar AWSIPAddress
scalar BigInt
scalar Double
directive @aws_subscribe(mutations: [String!]!) on FIELD_DEFINITION
directive @deprecated(reason: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ENUM | ENUM_VALUE
directive @aws_auth(cognito_groups: [String!]!) on FIELD_DEFINITION
directive @aws_api_key on FIELD_DEFINITION | OBJECT
directive @aws_iam on FIELD_DEFINITION | OBJECT
directive @aws_oidc on FIELD_DEFINITION | OBJECT
directive @aws_cognito_user_pools(cognito_groups: [String!]) on FIELD_DEFINITION | OBJECT
directive @aws_lambda on FIELD_DEFINITION | OBJECT`
const logs = await diffSchema(
buildSchema(aws + '\n' + old_schema.value),
buildSchema(aws + '\n' + new_schema.value)
)
output.value = format(logs)
} catch(err) {
output.value = [{ message: err.message, level: 'BREAKING' }]
error.value = true
}
})
function format(logs) {
return logs.map(log => ({ message: log.message, level: log.criticality.level }))
}
</script>
# AppSync GraphQL Schema Diff
Compare changes between AppSync GraphQL Schemas.
<div class="tool">
<div class="diff">
<label>
Old Schema
<textarea v-model="old_schema" placeholder="Paste old gql schema"></textarea>
</label>
<label>
New Schema
<textarea v-model="new_schema" placeholder="Paste new gql schema"></textarea>
</label>
</div>
<div class="output" v-if="old_schema && new_schema">
<label>Diff:</label>
<ol>
<li v-for="log in output" :class="{ breaking: log.level === 'BREAKING', dangerous: log.level === 'DANGEROUS' }">
{{ log.level }}: {{ log.message }}
</li>
</ol>
<span v-if="output.length === 0">No differences.</span>
</div>
</div>
<style lang="scss" scoped>
.tool {
display: flex;
flex-direction: column;
gap: 4rem;
margin-top: 2rem;
}
.diff {
display: grid;
grid-template-columns: 1fr 1fr;
width: 100%;
height: 500px;
gap: 2rem;
font-size: 0.8rem;
label {
font-weight: bold;
}
textarea {
border: 1px solid #1d1d1d;
display: block;
width: 100%;
height: 100%;
border-radius: 15px;
padding: 0.5rem;
}
}
.output {
display: block;
font-size: 0.8rem;
label {
font-weight: bold;
}
span {
color: green;
}
li {
color: #02a676;
&.breaking { color: #d6231e; }
&.dangerous { color: #f8b500; }
}
}
</style>
================================================
FILE: package.json
================================================
{
"name": "prisma-appsync",
"version": "1.0.2",
"description": "⚡ AppSync GraphQL API Generator for ◭ Prisma ORM.",
"author": "maoosi <hello@sylvainsimao.fr>",
"license": "BSD-2-Clause",
"repository": "git@github.com:maoosi/prisma-appsync.git",
"keywords": [
"api",
"appsync",
"aws",
"crud",
"generator",
"graphql",
"prisma",
"prisma-appsync",
"appsync-crud-api"
],
"bin": "./dist/generator.js",
"engines": {
"node": ">=14",
"pnpm": ">=6"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
"postinstall": "zx bin/postinstall.mjs",
"build": "zx bin/build.mjs",
"test": "zx bin/test.mjs",
"dev": "zx bin/dev.mjs",
"cleans": "zx bin/cleans.mjs",
"publish": "zx bin/publish.mjs",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:serve": "vitepress serve docs"
},
"devDependencies": {
"@antfu/eslint-config": "^2.4.6",
"@graphql-inspector/core": "^5.0.2",
"@graphql-tools/schema": "^10.0.2",
"@prisma/client": "^5.7.1",
"@types/lodash": "^4.14.202",
"@types/node": "^20.10.5",
"@zerollup/ts-transform-paths": "^1.7.18",
"all-contributors-cli": "^6.26.1",
"easygraphql-tester": "^6.0.1",
"esbuild": "^0.19.10",
"eslint": "^8.56.0",
"graphql": "16.8.1",
"listr": "^0.14.3",
"lodash": "^4.17.21",
"pluralize": "^8.0.0",
"prisma": "^5.7.1",
"prompts": "^2.4.2",
"sass": "^1.69.5",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vite-tsconfig-paths": "^4.2.2",
"vitepress": "1.0.0-rc.32",
"vitest": "^1.1.0"
}
}
================================================
FILE: packages/boilerplate/cdk/package.json
================================================
{
"name": "prisma-appsync-cdk",
"version": "1.0.0",
"description": "Sample AWS CDK template for Prisma-AppSync",
"author": "maoosi <hello@sylvainsimao.fr>",
"private": true,
"license": "BSD-2-Clause",
"devDependencies": {
"@types/js-yaml": "^4.0.5",
"@types/node": "^20.4.8",
"aws-cdk-lib": "^2.90.0",
"constructs": "^10.2.69",
"js-yaml": "^4.1.0",
"scule": "^1.0.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
}
}
================================================
FILE: packages/boilerplate/cdk/src/appsync.ts
================================================
/* eslint-disable no-new */
import { readFileSync } from 'fs'
import type { Construct } from 'constructs'
import { camelCase, kebabCase, pascalCase } from 'scule'
import { load } from 'js-yaml'
import {
Duration,
RemovalPolicy,
Stack,
aws_appsync as appSync,
aws_iam as iam,
aws_lambda as lambda,
aws_lambda_nodejs as lambdaNodejs,
type StackProps
} from 'aws-cdk-lib'
export interface AppSyncStackProps {
resourcesPrefix: string
cognitoUserPoolId?: string
schema: string
resolvers: string
function: {
code: string
memorySize: number
useWarmUp: number
policies?: iam.PolicyStatementProps[]
bundling?: lambdaNodejs.BundlingOptions
environment?: {}
}
additionalApiKeys?: string[]
authorizationConfig: appSync.AuthorizationConfig
}
export class AppSyncStack extends Stack {
private props: AppSyncStackProps
private resourcesPrefix: string
private resourcesPrefixCamel: string
private graphqlApi: appSync.GraphqlApi
private directResolverFn: lambda.Alias
private apiRole: iam.Role
private dataSources: {
lambda?: appSync.LambdaDataSource
none?: appSync.NoneDataSource
}
constructor(scope: Construct, id: string, tplProps: AppSyncStackProps, props?: StackProps) {
super(scope, id, props)
// stack naming convention
this.props = tplProps
this.resourcesPrefix = kebabCase(this.props.resourcesPrefix)
this.resourcesPrefixCamel = camelCase(this.resourcesPrefix)
this.createGraphQLApi()
this.createLambdaResolver()
this.createDataSources()
this.createPrismaAppSyncResolvers()
}
createGraphQLApi() {
// create appsync instance
this.graphqlApi = new appSync.GraphqlApi(this, `${this.resourcesPrefixCamel}Api`, {
name: this.resourcesPrefix,
schema: appSync.SchemaFile.fromAsset(this.props.schema),
authorizationConfig: this.props.authorizationConfig,
logConfig: {
fieldLogLevel: appSync.FieldLogLevel.ERROR,
},
xrayEnabled: true,
})
// create default API key
new appSync.CfnApiKey(this, `${this.resourcesPrefixCamel}ApiKey`, {
apiId: this.graphqlApi.apiId,
description: `${this.resourcesPrefix}_api-key`,
expires: Math.floor(new Date().setDate(new Date().getDate() + 365) / 1000.0),
})
// create additional API keys
if (this.props.additionalApiKeys) {
this.props.additionalApiKeys.forEach((apiKey: string) => {
new appSync.CfnApiKey(this, `${this.resourcesPrefixCamel}ApiKey${pascalCase(apiKey)}`, {
apiId: this.graphqlApi.apiId,
description: `${this.resourcesPrefix}_api-key_${kebabCase(apiKey)}`,
expires: Math.floor(new Date().setDate(new Date().getDate() + 365) / 1000.0),
})
})
}
}
createLambdaResolver() {
// create function execution role
const lambdaExecutionRole = new iam.Role(this, `${this.resourcesPrefixCamel}FnExecRole`, {
roleName: `${this.resourcesPrefix}_fn-exec-role`,
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
...(this.props.function?.policies
&& this.props.function.policies.length > 0 && {
inlinePolicies: {
customApiFunctionPolicy: new iam.PolicyDocument({
statements: this.props.function.policies.map((statement) => {
return new iam.PolicyStatement(statement)
}),
}),
},
}),
})
// create lambda function datasource
const lambdaFunction = new lambdaNodejs.NodejsFunction(this, `${this.resourcesPrefixCamel}Fn`, {
functionName: `${this.resourcesPrefix}_fn`,
role: lambdaExecutionRole,
environment: this.props.function.environment || {},
runtime: lambda.Runtime.NODEJS_18_X,
timeout: Duration.seconds(10),
handler: 'main',
entry: this.props.function.code,
memorySize: this.props.function.memorySize,
tracing: lambda.Tracing.ACTIVE,
currentVersionOptions: {
removalPolicy: RemovalPolicy.RETAIN,
retryAttempts: 2,
},
...(this.props.function.bundling && {
bundling: this.props.function.bundling,
}),
})
// create alias (from latest version)
this.directResolverFn = new lambda.Alias(this, `${this.resourcesPrefixCamel}_FnAliasLive`, {
aliasName: 'live',
version: lambdaFunction.currentVersion,
...(this.props.function.useWarmUp > 0 && {
provisionedConcurrentExecutions: this.props.function.useWarmUp,
}),
})
// create IAM role
this.apiRole = new iam.Role(this, `${this.resourcesPrefixCamel}ApiRole`, {
roleName: `${this.resourcesPrefix}_api-role`,
assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'),
inlinePolicies: {
allowEc2DescribeNetworkInterfaces: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ['lambda:InvokeAsync', 'lambda:InvokeFunction'],
resources: [this.directResolverFn.functionArn],
}),
],
}),
},
})
}
createPrismaAppSyncResolvers() {
// read resolvers from yaml
const resolvers = load(readFileSync(this.props.resolvers, 'utf8'))
// create resolvers
if (Array.isArray(resolvers)) {
resolvers.forEach((resolver: any) => {
const resolvername = `${resolver.fieldName}${resolver.typeName}_resolver`
if (['lambda', 'prisma-appsync'].includes(resolver.dataSource) && this.dataSources.lambda) {
new appSync.Resolver(this, resolvername, {
api: this.graphqlApi,
typeName: resolver.typeName,
fieldName: resolver.fieldName,
dataSource: this.dataSources.lambda,
})
}
else if (resolver.dataSource === 'none' && this.dataSources.none) {
new appSync.Resolver(this, resolvername, {
api: this.graphqlApi,
typeName: resolver.typeName,
fieldName: resolver.fieldName,
dataSource: this.dataSources.none,
requestMappingTemplate: appSync.MappingTemplate.fromString(
resolver.requestMappingTemplate,
),
responseMappingTemplate: appSync.MappingTemplate.fromString(
resolver.responseMappingTemplate,
),
})
}
})
}
}
createDataSources() {
this.dataSources = {}
// create datasource of type "lambda"
this.dataSources.lambda = new appSync.LambdaDataSource(
this,
`${this.resourcesPrefixCamel}LambdaDatasource`,
{
api: this.graphqlApi,
name: `${this.resourcesPrefixCamel}LambdaDataSource`,
lambdaFunction: this.directResolverFn,
serviceRole: this.apiRole,
},
)
// create datasource of type "none"
this.dataSources.none = new appSync.NoneDataSource(this, `${this.resourcesPrefixCamel}NoneDatasource`, {
api: this.graphqlApi,
name: `${this.resourcesPrefixCamel}NoneDataSource`,
})
}
}
================================================
FILE: packages/boilerplate/cdk/src/index.ts
================================================
/* eslint-disable no-new */
import { join } from 'path'
import { App } from 'aws-cdk-lib'
import { AuthorizationType } from 'aws-cdk-lib/aws-appsync'
import { kebabCase } from 'scule'
import { AppSyncStack } from './appsync'
const app = new App()
new AppSyncStack(app, kebabCase('{{ projectName }}'), {
resourcesPrefix: '{{ projectName }}',
schema: join(process.cwd(), '{{ relativeGqlSchemaPath }}'),
resolvers: join(process.cwd(), '{{ relativeYmlResolversPath }}'),
function: {
code: join(process.cwd(), '{{ relativeHandlerPath }}'),
memorySize: 1536,
useWarmUp: 0, // useWarmUp > 0 will incur extra costs
environment: {
NODE_ENV: 'production',
DATABASE_URL: process.env.DATABASE_URL,
},
bundling: {
minify: true,
sourceMap: true,
forceDockerBundling: true,
commandHooks: {
beforeBundling(inputDir: string, outputDir: string): string[] {
return [`cp ${inputDir}/{{ relativePrismaSchemaPath }} ${outputDir}`]
},
beforeInstall() {
return []
},
afterBundling() {
return [
'npx prisma generate',
'rm -rf generated',
// npm + yarn 1.x
'rm -rf node_modules/@prisma/engines',
'rm -rf node_modules/@prisma/client/node_modules',
'rm -rf node_modules/.bin',
'rm -rf node_modules/prisma',
'rm -rf node_modules/prisma-appsync',
]
},
},
nodeModules: ['prisma', '@prisma/client'],
environment: {
NODE_ENV: 'production',
},
},
},
authorizationConfig: {
defaultAuthorization: {
authorizationType: AuthorizationType.API_KEY,
},
},
})
app.synth()
================================================
FILE: packages/boilerplate/cdk/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": ["es2016", "es2017.object", "es2017.string"],
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false
}
}
================================================
FILE: packages/boilerplate/cdk.json
================================================
{
"app": "npx ts-node --prefer-ts-exts cdk/src/index.ts"
}
================================================
FILE: packages/boilerplate/handler.ts
================================================
import type { AppSyncResolverEvent } from './prisma/generated/prisma-appsync/client'
import { PrismaAppSync } from './prisma/generated/prisma-appsync/client'
// Instantiate Prisma-AppSync Client
const prismaAppSync = new PrismaAppSync()
// Lambda handler (AppSync Direct Lambda Resolver)
export const main = async (event: AppSyncResolverEvent<any>) => {
return await prismaAppSync.resolve({ event })
}
================================================
FILE: packages/boilerplate/prisma/sqlite.prisma
================================================
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-1.0.x"]
}
generator appsync {
provider = "prisma-appsync"
}
/// @gql(fields: { passwordHash: null })
model User {
id Int @id @default(autoincrement())
email String @unique
passwordHash String
posts Post[]
createdAt DateTime @default(now())
}
/// @gql(scalars: { source: "AWSURL" })
model Post {
id Int @id @default(autoincrement())
title String
source String?
author User? @relation(fields: [authorId], references: [id])
authorId Int?
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
}
================================================
FILE: packages/boilerplate/server/server.ts
================================================
import { join } from 'path'
import { readFileSync } from 'fs'
import { load } from 'js-yaml'
import { argv, createServer } from 'prisma-appsync/dist/server'
(async () => {
const schema = readFileSync(join(process.cwd(), argv.flags.schema), { encoding: 'utf-8' })
const lambdaHandler = await import(join(process.cwd(), argv.flags.handler))
const resolvers = load(readFileSync(join(process.cwd(), argv.flags.resolvers), { encoding: 'utf-8' }))
const port = argv.flags.port
const wsPort = argv.flags.wsPort
const watchers = argv.flags.watchers ? JSON.parse(argv.flags.watchers) : []
createServer({
schema,
lambdaHandler,
resolvers,
port,
wsPort,
watchers,
})
})()
================================================
FILE: packages/boilerplate/tsconfig.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": ["es2018"],
"declaration": false,
"strict": true,
"noImplicitAny": false,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"baseUrl": "."
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
================================================
FILE: packages/client/package.json
================================================
{
"name": "prisma-appsync-client",
"private": true,
"version": "1.0.0",
"author": "maoosi <hello@sylvainsimao.fr>",
"license": "BSD-2-Clause",
"devDependencies": {
"@types/aws-lambda": "^8.10.126",
"@types/micromatch": "^4.0.5",
"deepmerge": "^4.3.1",
"html-entities": "^2.4.0",
"lambda-rate-limiter": "^4.0.0",
"micromatch": "^4.0.5",
"wild-wild-path": "^4.0.0",
"wild-wild-utils": "^5.0.0",
"xss": "^1.0.14"
}
}
================================================
FILE: packages/client/src/adapter.ts
================================================
import { CustomError } from './inspector'
import { sanitize } from './guard'
import {
clone,
isEmpty,
isObject,
isUndefined,
lowerFirst,
merge,
objectToPaths,
uniq,
walk,
} from './utils'
import type {
Action,
ActionsAlias,
AppSyncEvent,
Authorization,
Context,
GraphQLType,
Identity,
Model,
Options,
PrismaArgs,
QueryParams,
} from './types'
import {
Actions,
ActionsAliasesList,
Authorizations,
BatchActionsList,
Prisma_ReservedKeysForPaths,
} from './consts'
/**
* #### Parse AppSync direct resolver `event` and returns Query Params.
*
* @param {AppSyncEvent} appsyncEvent - AppSync event received in Lambda.
* @param {Required<PrismaAppSyncOptionsType>} options - PrismaAppSync Client options.
* @param {any|null} customResolvers? - Custom Resolvers.
* @returns `{ type, operation, context, fields, paths, args, prismaArgs, authorization, identity }` - QueryParams
*/
export async function parseEvent(appsyncEvent: AppSyncEvent, options: Options, customResolvers?: any | null): Promise<QueryParams> {
if (
isEmpty(appsyncEvent?.info?.fieldName)
|| isUndefined(appsyncEvent?.info?.selectionSetList)
|| isEmpty(appsyncEvent?.info?.parentTypeName)
|| isUndefined(appsyncEvent?.arguments)
)
throw new CustomError('Error reading required parameters from appsyncEvent.', { type: 'INTERNAL_SERVER_ERROR' })
const operation = getOperation({ fieldName: appsyncEvent.info.fieldName })
const context = getContext({ customResolvers, options, operation })
const { identity, authorization } = getAuthIdentity({
appsyncEvent,
})
const fields = getFields({
_selectionSetList: appsyncEvent.info.selectionSetList,
})
const sanitizedArgs = options.sanitize
? await sanitize(await addNullables(appsyncEvent.arguments))
: await addNullables(appsyncEvent.arguments)
const args = clone(sanitizedArgs)
const prismaArgs = getPrismaArgs({
action: context.action,
defaultPagination: options.defaultPagination,
_arguments: clone(sanitizedArgs),
_selectionSetList: appsyncEvent.info.selectionSetList,
})
const type = getType({
_parentTypeName: appsyncEvent.info.parentTypeName,
})
const paths = getPaths({
operation,
context,
prismaArgs,
})
const headers = appsyncEvent?.request?.headers || {}
return {
operation,
context,
fields,
args,
prismaArgs,
type,
authorization,
identity,
paths,
headers,
}
}
/**
* #### Convert `is: <enum>NULL` and `isNot: <enum>NULL` to `is: null` and `isNot: null`
*
* @param {any} data
* @returns any
*/
export async function addNullables(data: any): Promise<any> {
return await walk(data, async ({ key, value }, node) => {
if (key === 'is' || key === 'isNot') {
value = value === 'NULL' ? null : undefined
node.ignoreChilds()
}
else if (value && isObject(value) && Object.keys(value).includes('isNull')) {
const { isNull, ...val } = value as any
if (isNull === true)
value = { ...val, equals: null }
else
value = { ...val, not: null }
node.ignoreChilds()
}
return { key, value }
})
}
/**
* #### Returns authorization and identity.
*
* @param {any} options
* @param {AppSyncEvent} options.appsyncEvent - AppSync event received in Lambda.
* @returns `{ authorization, identity }`
*
* https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html#aws-appsync-resolver-context-reference-identity
*/
export function getAuthIdentity({ appsyncEvent }: { appsyncEvent: AppSyncEvent }): {
identity: Identity
authorization: Authorization
} {
let authorization: Authorization = null
let identity: Identity = null
// API_KEY authorization
if (isEmpty(appsyncEvent?.identity)) {
authorization = Authorizations.API_KEY
identity = {
...(appsyncEvent?.request?.headers
&& typeof appsyncEvent.request.headers['x-api-key'] !== 'undefined' && {
requestApiKey: appsyncEvent.request.headers['x-api-key'],
}),
...(appsyncEvent?.request?.headers
&& typeof appsyncEvent.request.headers['user-agent'] !== 'undefined' && {
requestUserAgent: appsyncEvent.request.headers['user-agent'],
}),
}
}
// AWS_LAMBDA authorization
else if (appsyncEvent?.identity && typeof (appsyncEvent.identity as any).resolverContext !== 'undefined') {
authorization = Authorizations.AWS_LAMBDA
identity = appsyncEvent.identity
}
// AWS_IAM authorization
else if (
appsyncEvent?.identity
&& typeof (appsyncEvent.identity as any).cognitoIdentityAuthType !== 'undefined'
&& typeof (appsyncEvent.identity as any).cognitoIdentityAuthProvider !== 'undefined'
&& typeof (appsyncEvent.identity as any).cognitoIdentityPoolId !== 'undefined'
&& typeof (appsyncEvent.identity as any).cognitoIdentityId !== 'undefined'
) {
authorization = Authorizations.AWS_IAM
identity = appsyncEvent.identity
}
// AMAZON_COGNITO_USER_POOLS authorization
else if (
appsyncEvent?.identity
&& typeof (appsyncEvent.identity as any).sub !== 'undefined'
&& typeof (appsyncEvent.identity as any).issuer !== 'undefined'
&& typeof (appsyncEvent.identity as any).username !== 'undefined'
&& typeof (appsyncEvent.identity as any).claims !== 'undefined'
&& typeof (appsyncEvent.identity as any).sourceIp !== 'undefined'
) {
authorization = Authorizations.AMAZON_COGNITO_USER_POOLS
identity = appsyncEvent.identity
}
// OPENID_CONNECT authorization
else if (
appsyncEvent?.identity
&& typeof (appsyncEvent.identity as any).sub !== 'undefined'
&& typeof (appsyncEvent.identity as any).issuer !== 'undefined'
&& typeof (appsyncEvent.identity as any).claims !== 'undefined'
) {
authorization = Authorizations.OPENID_CONNECT
identity = appsyncEvent.identity
}
// ERROR
else {
throw new CustomError('Couldn\'t detect caller identity.', {
type: 'INTERNAL_SERVER_ERROR',
})
}
return { authorization, identity }
}
/**
* #### Returns context (`action`, `alias` and `model`).
*
* @param {any} options
* @param {any|null} options.customResolvers
* @param {string} options.operation
* @param {Options} options.options
* @returns Context
*/
export function getContext({
customResolvers,
operation,
options,
}: {
customResolvers?: any | null
operation: string
options: Options
}): Context {
const context: Context = {
action: String(),
alias: null,
model: null,
}
if (customResolvers && typeof customResolvers[operation] !== 'undefined') {
context.action = operation
context.alias = 'custom'
context.model = null
}
else {
context.action = getAction({ operation })
context.model = getModel({ operation, action: context.action, options })
context.alias = getActionAlias({ action: context.action })
}
return context
}
/**
* #### Returns operation (`getPost`, `listUsers`, ..).
*
* @param {any} options
* @param {string} options.fieldName
* @returns Operation
*/
export function getOperation({ fieldName }: { fieldName: string }): string {
const operation = fieldName
if (!(operation.length > 0))
throw new CustomError('Error parsing \'operation\' from input event.', { type: 'INTERNAL_SERVER_ERROR' })
return operation
}
/**
* #### Returns action (`get`, `list`, `create`, ...).
*
* @param {any} options
* @param {string} options.operation
* @returns Action
*/
export function getAction({ operation }: { operation: string }): Action {
const actionsList = Object.keys(Actions).sort().reverse()
const action = actionsList.find((action: Action) => {
return operation.toLowerCase().startsWith(String(action).toLowerCase())
}) as Action
if (!(typeof action !== 'undefined' && String(action).length > 0)) {
throw new CustomError(
'Error parsing \'action\' from input event. If you are trying to query a custom resolver, make sure it is properly declared inside \'prismaAppSync.resolve({ event, resolvers: { /* HERE */ } })\'.',
{ type: 'INTERNAL_SERVER_ERROR' },
)
}
return action
}
/**
* #### Returns action alias (`access`, `create`, `modify`, `subscribe`).
*
* @param {any} options
* @param {Action} options.action
* @returns ActionsAlias
*/
export function getActionAlias({ action }: { action: Action }): ActionsAlias {
let actionAlias: ActionsAlias = null
for (const alias in ActionsAliasesList) {
const actionsList = ActionsAliasesList[alias]
if (actionsList.includes(action)) {
actionAlias = alias as ActionsAlias
break
}
}
if (!(typeof action !== 'undefined' && String(action).length > 0))
throw new CustomError('Error parsing \'actionAlias\' from input event.', { type: 'INTERNAL_SERVER_ERROR' })
return actionAlias
}
/**
* #### Returns model (`Post`, `User`, ...).
*
* @param {any} options
* @param {string} options.operation
* @param {Action} options.action
* @param {Options} options.options
* @returns Model
*/
export function getModel(
{ operation, action, options }:
{ operation: string; action: Action; options: Options },
): Model {
const actionModel = operation.replace(String(action), '')
if (!(actionModel.length > 0))
throw new CustomError('Error parsing \'model\' from input event.', { type: 'INTERNAL_SERVER_ERROR' })
const model = options?.modelsMapping?.[actionModel]
if (!model) {
throw new CustomError(`Resolver "${actionModel}" not found. If it's a custom resolver, please ensure it's available within your Lambda function.`, {
type: 'INTERNAL_SERVER_ERROR',
})
}
return model
}
/**
* #### Returns fields (`title`, `author`, ...).
*
* @param {any} options
* @param {string[]} options._selectionSetList
* @returns string[]
*/
export function getFields({ _selectionSetList }: { _selectionSetList: string[] }): string[] {
const fields: string[] = []
_selectionSetList.forEach((item: string) => {
const field = item.split('/')[0]
if (!fields.includes(field) && !field.startsWith('__'))
fields.push(item)
})
return fields
}
/**
* #### Returns GraphQL type (`Query`, `Mutation` or `Subscription`).
*
* @param {any} options
* @param {string} options._parentTypeName
* @returns GraphQLType
*/
export function getType({ _parentTypeName }: { _parentTypeName: string }): GraphQLType {
const type = _parentTypeName
if (!['Query', 'Mutation', 'Subscription'].includes(type))
throw new CustomError('Error parsing \'type\' from input event.', { type: 'INTERNAL_SERVER_ERROR' })
return type as GraphQLType
}
/**
* #### Returns Prisma args (`where`, `data`, `orderBy`, ...).
*
* @param {any} options
* @param {Action} options.action
* @param {Options['defaultPagination']} options.defaultPagination
* @param {any} options._arguments
* @param {any} options._selectionSetList
* @returns PrismaArgs
*/
export function getPrismaArgs({
action,
defaultPagination,
_arguments,
_selectionSetList,
}: {
action: Action
defaultPagination: Options['defaultPagination']
_arguments: any
_selectionSetList: any
}): PrismaArgs {
const prismaArgs: PrismaArgs = {}
if (typeof _arguments.data !== 'undefined' && typeof _arguments.operation !== 'undefined') {
throw new CustomError('Using \'data\' and \'operation\' together is not possible.', {
type: 'BAD_USER_INPUT',
})
}
if (typeof _arguments.data !== 'undefined')
prismaArgs.data = _arguments.data
else if (typeof _arguments.operation !== 'undefined')
prismaArgs.data = _arguments.operation
if (typeof _arguments.create !== 'undefined')
prismaArgs.create = _arguments.create
if (typeof _arguments.update !== 'undefined')
prismaArgs.update = _arguments.update
if (typeof _arguments.where !== 'undefined')
prismaArgs.where = _arguments.where
if (typeof _arguments.orderBy !== 'undefined')
prismaArgs.orderBy = parseOrderBy(_arguments.orderBy)
if (typeof _arguments.skipDuplicates !== 'undefined')
prismaArgs.skipDuplicates = _arguments.skipDuplicates
if (typeof _selectionSetList !== 'undefined')
prismaArgs.select = parseSelectionList(_selectionSetList)
if (isEmpty(prismaArgs.select))
delete prismaArgs.select
if (typeof _arguments.skip !== 'undefined')
prismaArgs.skip = Number.parseInt(_arguments.skip)
else if (defaultPagination !== false && action === Actions.list)
prismaArgs.skip = 0
if (typeof _arguments.take !== 'undefined')
prismaArgs.take = Number.parseInt(_arguments.take)
else if (defaultPagination !== false && action === Actions.list)
prismaArgs.take = defaultPagination
return prismaArgs
}
/**
* #### Returns individual `orderBy` record formatted for Prisma.
*
* @param {any} sortObj
* @returns any
*/
function getOrderBy(sortObj: any): any {
if (Object.keys(sortObj).length > 1)
throw new CustomError('Wrong \'orderBy\' input format.', { type: 'BAD_USER_INPUT' })
const key: any = Object.keys(sortObj)[0]
const value = typeof sortObj[key] === 'object' ? getOrderBy(sortObj[key]) : sortObj[key].toLowerCase()
return { [key]: value }
}
/**
* #### Returns Prisma `orderBy` from parsed `event.arguments.orderBy`.
*
* @param {any} orderByInputs
* @returns any[]
*/
function parseOrderBy(orderByInputs: any): any[] {
const orderByOutput: any = []
const orderByInputsArray = Array.isArray(orderByInputs) ? orderByInputs : [orderByInputs]
orderByInputsArray.forEach((orderByInput: any) => {
orderByOutput.push(getOrderBy(orderByInput))
})
return orderByOutput
}
/**
* #### Returns individual `include` field formatted for Prisma.
*
* @param {any} parts
* @returns any
*/
function getInclude(parts: any): any {
const field = parts[0]
const value = parts.length > 1 ? getSelect(parts.splice(1)) : true
return {
include: {
[field]: value,
},
}
}
/**
* #### Returns individual `select` field formatted for Prisma.
*
* @param {any} parts
* @returns any
*/
function getSelect(parts: any): any {
const field = parts[0]
const value = parts.length > 1 ? getSelect(parts.splice(1)) : true
return {
select: {
[field]: value,
},
}
}
/**
* #### Return Prisma `select` from parsed `event.arguments.info.selectionSetList`.
*
* @param {any} selectionSetList
* @returns any
*/
function parseSelectionList(selectionSetList: any): any {
let prismaArgs: any = { select: {} }
for (let i = 0; i < selectionSetList.length; i++) {
const path = selectionSetList[i]
const parts = path.split('/')
if (!parts.includes('__typename')) {
if (parts.length > 1)
prismaArgs = merge(prismaArgs, getInclude(parts))
else
prismaArgs = merge(prismaArgs, getSelect(parts))
}
}
if (prismaArgs.include) {
for (const include in prismaArgs.include) {
if (typeof prismaArgs.select[include] !== 'undefined')
delete prismaArgs.select[include]
}
prismaArgs.select = merge(prismaArgs.select, prismaArgs.include)
delete prismaArgs.include
}
return typeof prismaArgs.select !== 'undefined' ? prismaArgs.select : {}
}
/**
* #### Returns req and res paths (`updatePost/title`, `getPost/date`, ..).
*
* @param {any} options
* @param {string} options.operation
* @param {Context} options.context
* @param {PrismaArgs} options.prismaArgs
* @returns string[]
*/
export function getPaths({
operation,
context,
prismaArgs,
}: {
operation: string
context: Context
prismaArgs: PrismaArgs
}): string[] {
const paths: string[] = [
operation,
...objectToPaths({
...(prismaArgs?.data && {
data: prismaArgs.data,
}),
...(prismaArgs?.select && {
select: prismaArgs.select,
}),
}),
]
paths.forEach((path: string, index: number) => {
if (path.startsWith('data')) {
paths[index] = path.replace('data', operation)
}
else if (path.startsWith('select')) {
const action = BatchActionsList.includes(context.action) ? Actions.list : Actions.get
if (context.model !== null) {
const model = action === Actions.list ? context.model.plural : context.model.singular
paths[index] = path.replace('select', `${lowerFirst(action)}${model}`)
}
else {
paths[index] = path.replace('select', operation)
}
}
})
return uniq(
paths.map(
(path: string) => path
.split('/')
.filter(k => !Prisma_ReservedKeysForPaths.includes(k))
.join('/'),
).filter(Boolean),
)
}
================================================
FILE: packages/client/src/consts.ts
================================================
import type { Action } from './types'
import { uniq } from './utils'
// Enums
export enum Actions {
// queries
get = 'get',
list = 'list',
count = 'count',
// mutations (multiple)
createMany = 'createMany',
updateMany = 'updateMany',
deleteMany = 'deleteMany',
// mutations (single)
create = 'create',
update = 'update',
upsert = 'upsert',
delete = 'delete',
// subscriptions (multiple)
onCreatedMany = 'onCreatedMany',
onUpdatedMany = 'onUpdatedMany',
onDeletedMany = 'onDeletedMany',
onMutatedMany = 'onMutatedMany',
// subscriptions (single)
onCreated = 'onCreated',
onUpdated = 'onUpdated',
onUpserted = 'onUpserted',
onDeleted = 'onDeleted',
onMutated = 'onMutated',
}
export enum ActionsAliases {
access = 'access',
batchAccess = 'batchAccess',
create = 'create',
batchCreate = 'batchCreate',
delete = 'delete',
batchDelete = 'batchDelete',
modify = 'modify',
batchModify = 'batchModify',
subscribe = 'subscribe',
batchSubscribe = 'batchSubscribe',
}
/**
* ### Authorizations
*
* - `API_KEY`: Via hard-coded API key passed into `x-api-key` header.
* - `AWS_IAM`: Via IAM identity and associated IAM policy rules.
* - `AMAZON_COGNITO_USER_POOLS`: Via Amazon Cognito user token.
* - `AWS_LAMBDA`: Via an AWS Lambda function.
* - `OPENID_CONNECT`: Via Open ID connect such as Auth0.
*
* https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html
*/
export enum Authorizations {
API_KEY = 'API_KEY',
AWS_IAM = 'AWS_IAM',
AMAZON_COGNITO_USER_POOLS = 'AMAZON_COGNITO_USER_POOLS',
AWS_LAMBDA = 'AWS_LAMBDA',
OPENID_CONNECT = 'OPENID_CONNECT',
}
// Consts
export const Prisma_QueryOptions = [
'where', 'data', 'select', 'orderBy', 'include', 'distinct',
]
export const Prisma_NestedQueries = [
'create', 'createMany', 'set', 'connect', 'connectOrCreate', 'disconnect', 'update', 'upsert', 'delete', 'updateMany', 'deleteMany',
]
export const Prisma_FilterConditionsAndOperatos = [
'equals', 'not', 'in', 'notIn', 'lt', 'lte', 'gt', 'gte', 'contains', 'search', 'mode', 'startsWith', 'endsWith', 'AND', 'OR', 'NOT',
]
export const Prisma_FilterRelationFilters = [
'some', 'every', 'none', 'is', 'isNot',
]
export const Prisma_ScalarListMethods = [
'set', 'push', 'unset',
]
export const Prisma_ScalarListFilters = [
'has', 'hasEvery', 'hasSome', 'isEmpty', 'isSet', 'equals',
]
export const Prisma_CompositeTypeMethods = [
'set', 'unset', 'update', 'upsert', 'push',
]
export const Prisma_CompositeTypeFilters = [
'equals', 'is', 'isNot', 'isEmpty', 'every', 'some', 'none',
]
export const Prisma_AtomicNumberOperations = [
'increment', 'decrement', 'multiply', 'divide', 'set',
]
export const Prisma_JSONFilters = [
'path', 'string_contains', 'string_starts_with', 'string_ends_with', 'array_contains', 'array_starts_with', 'array_ends_with',
]
export const Prisma_ReservedKeysForPaths = uniq([
...Prisma_QueryOptions,
...Prisma_FilterConditionsAndOperatos,
...Prisma_FilterRelationFilters,
...Prisma_ScalarListFilters,
...Prisma_CompositeTypeFilters,
...Prisma_JSONFilters,
])
export const Prisma_ReservedKeys = uniq([
...Prisma_QueryOptions,
...Prisma_NestedQueries,
...Prisma_FilterConditionsAndOperatos,
...Prisma_FilterRelationFilters,
...Prisma_ScalarListMethods,
...Prisma_ScalarListFilters,
...Prisma_CompositeTypeMethods,
...Prisma_CompositeTypeFilters,
...Prisma_AtomicNumberOperations,
...Prisma_JSONFilters,
])
export const ActionsAliasesList = {
access: [Actions.get, Actions.list, Actions.count],
batchAccess: [Actions.list, Actions.count],
create: [Actions.create, Actions.createMany],
batchCreate: [Actions.createMany],
modify: [Actions.upsert, Actions.update, Actions.updateMany, Actions.delete, Actions.deleteMany],
batchModify: [Actions.updateMany, Actions.deleteMany],
delete: [Actions.delete, Actions.deleteMany],
batchDelete: [Actions.deleteMany],
subscribe: [
Actions.onCreatedMany,
Actions.onUpdatedMany,
Actions.onDeletedMany,
Actions.onMutatedMany,
Actions.onCreated,
Actions.onUpdated,
Actions.onUpserted,
Actions.onDeleted,
Actions.onMutated,
],
batchSubscribe: [Actions.onCreatedMany, Actions.onUpdatedMany, Actions.onDeletedMany, Actions.onMutatedMany],
} as const
let actionsListMultiple: Action[] = []
let actionsListSingle: Action[] = []
for (const actionAlias in ActionsAliasesList) {
if (actionAlias.startsWith('batch'))
actionsListMultiple = actionsListMultiple.concat(ActionsAliasesList[actionAlias])
else
actionsListSingle = actionsListSingle.concat(ActionsAliasesList[actionAlias])
}
export const ActionsList = actionsListSingle.filter((item, pos) => actionsListSingle.indexOf(item) === pos)
export const BatchActionsList = actionsListMultiple.filter((item, pos) => actionsListMultiple.indexOf(item) === pos)
export const DebugTestingKey = '__prismaAppsync'
================================================
FILE: packages/client/src/core.ts
================================================
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable no-restricted-globals */
/* eslint-disable n/prefer-global/process */
import type {
AfterHookParams,
InjectedConfig,
Options,
PrismaAppSyncOptionsType,
ResolveParams,
Shield,
ShieldAuthorization,
} from './types'
import {
Prisma,
PrismaClient,
} from './types'
import {
BatchActionsList,
DebugTestingKey,
} from './consts'
import { CustomError, log, parseError } from './inspector'
import {
clarify,
getDepth,
getShieldAuthorization,
preventDOS,
runHooks,
} from './guard'
import { parseEvent } from './adapter'
import { isEmpty, omit } from './utils'
import { prismaQueryJoin } from './resolver'
import * as queries from './resolver'
/**
* ## Auto-injected at generation time
*/
// eslint-disable-next-line spaced-comment
const injectedConfig: InjectedConfig = {} //! inject:config
/**
* ## Prisma-AppSync Client ʲˢ
*
* Type-safe Prisma AppSync client for TypeScript & Node.js
* @example
* ```
* const prismaAppSync = new PrismaAppSync()
*
* // lambda handler (AppSync Direct Lambda Resolver)
* export const resolver = async (event: any, context: any) => {
* return await prismaAppSync.resolve({ event })
* }
* ```
*
*
* Read more in our [docs](https://prisma-appsync.vercel.app).
*/
export class PrismaAppSync {
public options: Options
public prismaClient: PrismaClient<Prisma.PrismaClientOptions, 'query' | 'info' | 'warn' | 'error'>
/**
* ### Client Constructor
*
* Instantiate Prisma-AppSync Client.
* @example
* ```
* const prismaAppSync = new PrismaAppSync()
* ```
*
* @param {PrismaAppSyncOptionsType} options
* @param {string} options.connectionString? - Prisma connection string (database connection URL).
* @param {boolean} options.sanitize? - Enable sanitize inputs (parse xss + encode html).
* @param {'INFO' | 'WARN' | 'ERROR'} options.logLevel? - Server logs level (visible in CloudWatch).
* @param {number|false} options.defaultPagination? - Default pagination for list Query (items per page).
* @param {number} options.maxDepth? - Maximum allowed GraphQL query depth.
* @param {number} options.maxReqPerUserMinute? - Maximum allowed requests per user, per minute.
*
* @default
* ```
* {
* connectionString: process.env.DATABASE_URL,
* sanitize: true,
* logLevel: 'INFO',
* defaultPagination: 50,
* maxDepth: 4,
* maxReqPerUserMinute: 200
* }
* ```
*
*
* Read more in our [docs](https://prisma-appsync.vercel.app).
*/
constructor(options?: PrismaAppSyncOptionsType) {
// Set ENV variable DATABASE_URL if connectionString option is set
if (typeof options?.connectionString !== 'undefined')
process.env.DATABASE_URL = options.connectionString
// Set client options using constructor options
this.options = {
modelsMapping: {},
fieldsMapping: {},
connectionString: String(process.env.DATABASE_URL),
sanitize:
typeof options?.sanitize !== 'undefined'
? options.sanitize
: true,
logLevel:
typeof options?.logLevel !== 'undefined'
? options.logLevel
: 'INFO',
defaultPagination:
typeof options?.defaultPagination !== 'undefined'
? options.defaultPagination
: 50,
maxDepth:
typeof options?.maxDepth !== 'undefined'
? options.maxDepth
: 4,
maxReqPerUserMinute:
typeof options?.maxReqPerUserMinute !== 'undefined'
? options.maxReqPerUserMinute
: 200,
}
this.options.modelsMapping = {}
// Read injected config
if (injectedConfig?.modelsMapping) {
this.options.modelsMapping = injectedConfig.modelsMapping
}
else if (process?.env?.PRISMA_APPSYNC_INJECTED_CONFIG) {
try {
this.options.modelsMapping = JSON.parse(
process.env.PRISMA_APPSYNC_INJECTED_CONFIG,
).modelsMapping
}
catch {}
}
if (injectedConfig?.fieldsMapping) {
this.options.fieldsMapping = injectedConfig.fieldsMapping
}
else if (process?.env?.PRISMA_APPSYNC_INJECTED_CONFIG) {
try {
this.options.fieldsMapping = JSON.parse(
process.env.PRISMA_APPSYNC_INJECTED_CONFIG,
).fieldsMapping
}
catch {}
}
// Make sure injected config isn't empty
if (Object.keys(this.options.modelsMapping).length === 0) {
throw new CustomError('Issue with auto-injected models mapping config.', {
type: 'INTERNAL_SERVER_ERROR',
})
}
// Set ENV variable for log level
process.env.PRISMA_APPSYNC_LOG_LEVEL = this.options.logLevel
// Debug logs
// eslint-disable-next-line unused-imports/no-unused-vars
const { fieldsMapping, ...newInstanceLogs } = this.options
log('New Prisma-AppSync instance created:', newInstanceLogs)
// Prisma client options
const prismaLogDef: Prisma.LogDefinition[] = [
{ emit: 'event', level: 'query' },
{ emit: 'event', level: 'error' },
{ emit: 'event', level: 'info' },
{ emit: 'event', level: 'warn' },
]
// Create new Prisma Client
if (process?.env?.PRISMA_APPSYNC_TESTING === 'true') {
if (!global.prisma)
global.prisma = new PrismaClient({ log: prismaLogDef })
this.prismaClient = global.prisma
}
else {
this.prismaClient = new PrismaClient({ log: prismaLogDef })
}
// Prisma logs
if (!(process?.env?.PRISMA_APPSYNC_TESTING === 'true')) {
this.prismaClient.$on('query', (e: any) => log('Prisma Client query:', e, 'INFO'))
this.prismaClient.$on('info', (e: any) => log('Prisma Client info:', e, 'INFO'))
this.prismaClient.$on('warn', (e: any) => log('Prisma Client warn:', e, 'WARN'))
this.prismaClient.$on('error', (e: any) => log('Prisma Client error:', e, 'ERROR'))
}
}
/**
* ### Resolver
*
* Resolve the API request, based on the AppSync `event` received by the Direct Lambda Resolver.
* @example
* ```
* await prismaAppSync.resolve({ event })
*
* // custom resolvers
* await prismaAppSync.resolve<'notify'|'listPosts'>(
* event,
* resolvers: {
* // extend CRUD API with a custom `notify` query
* notify: async ({ args }) => { return { message: args.message } },
*
* // disable one of the generated CRUD API query
* listPosts: false,
* }
* })
* ```
*
* @param {ResolveParams} resolveParams
* @param {any} resolveParams.event - AppSync event received by the Direct Lambda Resolver.
* @param {any} resolveParams.resolvers? - Custom resolvers (to extend the CRUD API).
* @param {function} resolveParams.shield? - Shield configuration (to protect your API).
* @param {function} resolveParams.hooks? - Hooks (to trigger functions based on events).
* @returns Promise<result>
*
*
* Read more in our [docs](https://prisma-appsync.vercel.app).
*/
public async resolve<CustomResolvers = void>(
resolveParams: ResolveParams<'//! inject:type:operations', Extract<CustomResolvers, string>>,
): Promise<any> {
let result: any = null
try {
log('Resolving API request w/ event (truncated):', {
arguments: resolveParams.event.arguments,
identity: resolveParams.event.identity,
info: omit(resolveParams.event.info, 'selectionSetGraphQL'),
})
// Adapter :: parse appsync event
let QueryParams = await parseEvent(
resolveParams.event,
this.options,
resolveParams.resolvers,
)
log('Parsed event:', QueryParams)
// Guard :: rate limiting
const callerUuid
= (QueryParams.identity as any)?.sourceIp?.[0]
|| (QueryParams.identity as any)?.sourceIp
|| (QueryParams.identity as any)?.sub
|| JSON.stringify(QueryParams.identity)
if (this.options.maxReqPerUserMinute && callerUuid) {
const { limitExceeded, count } = await preventDOS({
callerUuid,
maxReqPerMinute: this.options.maxReqPerUserMinute,
})
if (limitExceeded) {
throw new CustomError(
`Rate limit (maxReqPerUserMinute=${this.options.maxReqPerUserMinute}) exceeded for caller "${callerUuid}".`,
{
type: 'TOO_MANY_REQUESTS',
},
)
}
else {
log(`Rate limit check for caller "${callerUuid}" returned ${count}/${this.options.maxReqPerUserMinute} (last minute).`)
}
}
// Guard :: block queries with a depth > maxDepth
const depth = getDepth({
paths: QueryParams.paths,
context: QueryParams.context,
fieldsMapping: this.options.fieldsMapping,
})
if (depth > this.options.maxDepth) {
throw new CustomError(
`Query has depth of ${depth}, which exceeds max depth of ${this.options.maxDepth}.`,
{
type: 'FORBIDDEN',
},
)
}
else {
log(`Query has depth of ${depth} (max allowed is ${this.options.maxDepth}).`)
}
// Guard :: create shield from config
const shield: Shield = resolveParams?.shield
? await resolveParams.shield(QueryParams)
: {}
// Guard :: get shield authorization config
const shieldAuth: ShieldAuthorization = await getShieldAuthorization({
shield,
paths: QueryParams.paths,
context: QueryParams.context,
})
if (Object.keys(shield).length === 0)
log('Query shield authorization: No Shield setup detected.', null, 'WARN')
else
log('Query shield authorization:', shieldAuth)
// Guard :: if `canAccess` if equal to `false`, we reject the API call
if (!shieldAuth.canAccess) {
const reason = typeof shieldAuth.reason === 'string'
? shieldAuth.reason
: shieldAuth.reason({
action: QueryParams.context.action,
model: QueryParams.context.model?.singular || QueryParams.context.action,
})
throw new CustomError(reason, { type: 'FORBIDDEN' })
}
// Guard :: if `prismaFilter` is set, combine with current Prisma query
if (!isEmpty(shieldAuth.prismaFilter)) {
log('QueryParams before adding Shield filters:', QueryParams)
QueryParams.prismaArgs = prismaQueryJoin(
[QueryParams.prismaArgs, { where: shieldAuth.prismaFilter }],
[
'where',
'data',
'orderBy',
'skip',
'take',
'skipDuplicates',
'select',
],
)
log('QueryParams after adding Shield filters:', QueryParams)
}
// Guard: get and run all before hooks functions matching query
if (!isEmpty(resolveParams?.hooks)) {
QueryParams = await runHooks({
when: 'before',
hooks: resolveParams.hooks,
prismaClient: this.prismaClient,
QueryParams,
})
}
// Resolver :: resolve query for UNIT TESTS
if (process?.env?.PRISMA_APPSYNC_TESTING === 'true') {
log('Resolving query for UNIT TESTS.')
const isBatchAction = BatchActionsList.includes(
QueryParams?.context?.action,
)
const getTestResult = () => {
return {
...QueryParams.fields.reduce((a, v) => {
const value = !isEmpty(QueryParams?.prismaArgs?.data?.[v])
? QueryParams.prismaArgs.data[v]
: (Math.random() + 1).toString(36).substring(7)
return { ...a, [v]: String(value) }
}, {}),
[DebugTestingKey]: {
QueryParams,
},
}
}
if (isBatchAction)
result = [getTestResult(), getTestResult()]
else
result = getTestResult()
}
// Resolver :: query is disabled
else if (
resolveParams?.resolvers
&& typeof resolveParams.resolvers[QueryParams.operation] === 'boolean'
&& resolveParams.resolvers[QueryParams.operation] === false
) {
throw new CustomError(
`Query resolver for ${QueryParams.operation} is disabled.`,
{ type: 'FORBIDDEN' },
)
}
// Resolver :: resolve query with Custom Resolver
else if (
typeof resolveParams?.resolvers?.[QueryParams.operation] === 'function'
) {
log(`Resolving query for Custom Resolver "${QueryParams.operation}".`)
const customResolverFn = resolveParams.resolvers[
QueryParams.operation
] as Function
result = await customResolverFn({
...QueryParams,
prismaClient: this.prismaClient,
})
}
// Resolver :: resolve query with built-in CRUD
else if (!isEmpty(QueryParams?.context?.model)) {
log(`Resolving query for built-in CRUD operation "${QueryParams.operation}".`)
try {
result = await queries[`${QueryParams.context.action}Query`](
this.prismaClient,
QueryParams,
)
}
catch (err: any) {
if (err instanceof Prisma.PrismaClientKnownRequestError) {
throw new CustomError(
`Prisma Client known request error${err?.code ? ` (code ${err.code})` : ''}. https://www.prisma.io/docs/reference/api-reference/error-reference#prismaclientknownrequesterror`,
{ type: 'INTERNAL_SERVER_ERROR', cause: err },
)
}
else if (err instanceof Prisma.PrismaClientUnknownRequestError) {
throw new CustomError(
'Prisma Client unknown request error. https://www.prisma.io/docs/reference/api-reference/error-reference#prismaclientunknownrequesterror',
{ type: 'INTERNAL_SERVER_ERROR', cause: err },
)
}
else if (err instanceof Prisma.PrismaClientRustPanicError) {
throw new CustomError(
'Prisma Client Rust panic error. https://www.prisma.io/docs/reference/api-reference/error-reference#prismaclientrustpanicerror',
{ type: 'INTERNAL_SERVER_ERROR', cause: err },
)
}
else if (err instanceof Prisma.PrismaClientInitializationError) {
throw new CustomError(
`Prisma Client initialization error${err?.errorCode ? ` (errorCode ${err.errorCode})` : ''}. https://www.prisma.io/docs/reference/api-reference/error-reference#prismaclientinitializationerror`,
{ type: 'INTERNAL_SERVER_ERROR', cause: err },
)
}
else if (err instanceof Prisma.PrismaClientValidationError) {
throw new CustomError(
'Prisma Client validation error. https://www.prisma.io/docs/reference/api-reference/error-reference#prismaclientvalidationerror',
{ type: 'INTERNAL_SERVER_ERROR', cause: err },
)
}
else {
throw new CustomError(
err?.message?.split('\n')?.pop() || 'Unknown error during query.',
{ type: 'INTERNAL_SERVER_ERROR', cause: err },
)
}
}
}
// Resolver :: query resolver not found
else {
throw new CustomError(
`Query resolver for ${QueryParams.operation} could not be found.`,
{
type: 'INTERNAL_SERVER_ERROR',
},
)
}
// Guard: get and run all after hooks functions matching query
if (!isEmpty(resolveParams?.hooks)) {
const q: AfterHookParams = await runHooks({
when: 'after',
hooks: resolveParams.hooks,
prismaClient: this.prismaClient,
QueryParams,
result,
})
result = q.result
}
}
catch (error) {
// Return error
return Promise.reject(parseError(error as Error))
}
// Guard :: clarify result (decode html)
const resultClarified = this.options.sanitize ? await clarify(result) : result
log('Returning response to API request w/ result:', resultClarified)
return resultClarified
}
}
================================================
FILE: packages/client/src/guard.ts
================================================
import lambdaRateLimiter from 'lambda-rate-limiter'
import type {
Context,
PrismaClient,
QueryParams,
Shield,
ShieldAuthorization,
ShieldRule,
} from './types'
import { DebugTestingKey } from './consts'
import { decode, encode, filterXSS, isEmpty, isMatchingGlob, merge, walk } from './utils'
import { CustomError } from './inspector'
// https:// github.com/blackflux/lambda-rate-limiter
const limiter = lambdaRateLimiter({
interval: 60 * 1000, // 60 seconds = 1 minute
uniqueTokenPerInterval: 1000,
})
/**
* #### Sanitize data (parse xss + encode html).
*
* @param {any} data
* @returns any
*/
export async function sanitize(data: any): Promise<any> {
return await walk(data, async ({ key, value }, node) => {
if (typeof key === 'string' && key === DebugTestingKey)
node.ignoreChilds()
if (typeof value === 'string')
value = encode(filterXSS(value))
return { key, value }
})
}
/**
* #### Clarify data (decode html).
*
* @param {any} data
* @returns any
*/
export async function clarify(data: any): Promise<any> {
return await walk(data, async ({ key, value }, node) => {
if (typeof key === 'string' && key === DebugTestingKey)
node.ignoreChilds()
if (typeof value === 'string')
value = decode(value)
return { key, value }
})
}
/**
* #### Returns an Shield authorization object for a given field.
*
* @param {any} options
* @param {Shield} options.shield
* @param {ShieldRule} options.shieldRule
* @param {string} options.globPattern
* @param {string} options.matcher
* @param {Context} options.context
* @returns Promise<ShieldAuthorization>
*/
async function getFieldAuthorization(
{ shield, shieldRule, globPattern, matcher, context }:
{
shield: Shield
shieldRule: ShieldRule
globPattern: string
matcher: string
context: Context
},
): Promise<ShieldAuthorization> {
const authorization: ShieldAuthorization = {
canAccess: true,
reason: String(),
prismaFilter: {},
matcher: String(),
globPattern: String(),
}
if (typeof shieldRule === 'boolean') {
authorization.canAccess = shield[matcher] as boolean
}
else {
if (typeof shieldRule.rule === 'undefined')
throw new Error('Badly formed shield rule.')
if (typeof shieldRule.rule === 'boolean') {
authorization.canAccess = shieldRule.rule
}
else if (typeof shieldRule.rule === 'function') {
const ruleResult = shieldRule.rule(context)
if (ruleResult instanceof Promise)
authorization.canAccess = await ruleResult
else if (typeof ruleResult === 'boolean')
authorization.canAccess = ruleResult
else
throw new Error('Shield rule must return a boolean.')
}
else {
authorization.canAccess = true
if (!authorization.prismaFilter)
authorization.prismaFilter = {}
authorization.prismaFilter = merge(authorization.prismaFilter, shieldRule.rule)
}
}
authorization.matcher = matcher
authorization.globPattern = globPattern
const isReasonDefined = typeof shieldRule !== 'boolean' && typeof shieldRule.reason !== 'undefined'
let reason = `Matcher: ${authorization.matcher}`
if (isReasonDefined && typeof shieldRule.reason === 'function')
reason = shieldRule.reason({ action: context.action, model: context.model?.singular || context.action })
else if (isReasonDefined && typeof shieldRule.reason === 'string')
reason = shieldRule.reason
authorization.reason = reason
return authorization
}
/**
* #### Returns an authorization object from a Shield configuration passed as input.
*
* @param {Shield} options.shield
* @param {string[]} options.paths
* @param {Context} options.context
* @returns ShieldAuthorization
*/
export async function getShieldAuthorization({
shield,
paths,
context,
}: {
shield: Shield
paths: string[]
context: Context
}): Promise<ShieldAuthorization> {
let authorization: ShieldAuthorization = {
canAccess: true,
reason: String(),
prismaFilter: {},
matcher: String(),
globPattern: String(),
}
for (const matcher in shield) {
const concurrentFieldsAuthCheck: Promise<any>[] = []
const globPattern = matcher
for (let i = paths.length - 1; i >= 0; i--) {
const reqPath: string = paths[i]
if (isMatchingGlob(reqPath, globPattern)) {
const shieldRule = shield[matcher]
concurrentFieldsAuthCheck.push(
getFieldAuthorization({ shield, shieldRule, globPattern, matcher, context }),
)
}
}
const fieldsAuthCheckResults = await Promise.allSettled(concurrentFieldsAuthCheck)
for (let fieldIndex = 0; fieldIndex < fieldsAuthCheckResults.length; fieldIndex++) {
const fieldAuthCheckResult = fieldsAuthCheckResults[fieldIndex]
if (fieldAuthCheckResult.status === 'rejected') {
throw new CustomError(fieldAuthCheckResult.reason, { type: 'INTERNAL_SERVER_ERROR' })
}
else {
authorization = fieldAuthCheckResult.value
if (!fieldAuthCheckResult.value.canAccess)
break
}
}
}
return authorization
}
/**
* #### Returns GraphQL query depth for any given Query.
*
* @param {any} options
* @param {string[]} options.paths
* @param {Context} options.context
* @param {any} options.fieldsMapping
* @returns number
*/
export function getDepth(
{ paths, context, fieldsMapping }:
{ paths: string[]; context: Context; fieldsMapping: any },
): number {
let depth = 0
const stopPaths: string[] = []
if (!isEmpty(fieldsMapping)) {
for (const fieldMap in fieldsMapping) {
if (fieldsMapping[fieldMap].type.toLowerCase() === 'json')
stopPaths.push(String(fieldMap))
}
}
paths.forEach((path: string) => {
const stopPath = stopPaths.find((p: string) => path.includes(p))
const stopIndex = stopPath ? stopPath.split('/').length - 1 : undefined
const parts = path.split('/').filter(Boolean).slice(1, stopIndex ? stopIndex + 1 : undefined)
const pathDepth = parts.length
if (pathDepth > depth)
depth = pathDepth
})
if (context.model === null)
depth += 1
return depth
}
/**
* #### Execute hooks that apply to a given Query.
*
* @param {any} options
* @param {'before' | 'after'} options.when
* @param {any} options.hooks
* @param {PrismaClient} options.prismaClient
* @param {QueryParams} options.QueryParams
* @param {any | any[]} options.result
* @returns Promise<void | any>
*/
export async function runHooks({
when,
hooks,
prismaClient,
QueryParams,
result,
}: {
when: 'before' | 'after'
hooks: any
prismaClient: PrismaClient
QueryParams: QueryParams
result?: any | any[]
}): Promise<void | any> {
const matchingHooks = Object.keys(hooks).filter((hookPath: string) => {
const hookParts = hookPath.split(':')
const hookWhen = hookParts[0]
const hookGlob = hookParts[1]
const currentPath = QueryParams.operation
return hookWhen === when && isMatchingGlob(currentPath, hookGlob)
})
let hookResponse = when === 'after'
? { ...QueryParams, result }
: QueryParams
if (matchingHooks.length > 0) {
for (let index = 0; index < matchingHooks.length; index++) {
const hookPath = matchingHooks[index]
if (Object.prototype.hasOwnProperty.call(hooks, hookPath)) {
hookResponse = await hooks[hookPath]({
...QueryParams,
...(typeof result !== 'undefined' && when === 'after' && { result }),
prismaClient,
})
}
}
}
return hookResponse
}
export async function preventDOS({
callerUuid,
maxReqPerMinute,
}: {
callerUuid: string
maxReqPerMinute: number
}): Promise<{
limitExceeded: boolean
count: number
}> {
let limitExceeded = false
let count = -1
try {
count = await limiter.check(maxReqPerMinute, callerUuid)
}
catch (error) {
limitExceeded = true
count = maxReqPerMinute
}
return {
limitExceeded,
count,
}
}
================================================
FILE: packages/client/src/index.ts
================================================
import {
clone,
decode,
dotate,
encode,
filterXSS,
isEmpty,
isMatchingGlob,
isObject,
isUndefined,
lowerFirst,
merge,
replaceAll,
walk,
} from './utils'
export { PrismaAppSync } from './core'
export { CustomError, log } from './inspector'
export { queryBuilder } from './resolver'
export {
QueryParams,
QueryParamsCustom,
BeforeHookParams,
AfterHookParams,
Authorization,
AppSyncEvent,
Identity,
API_KEY,
AWS_IAM,
AMAZON_COGNITO_USER_POOLS,
AWS_LAMBDA,
OPENID_CONNECT,
AppSyncResolverHandler,
AppSyncResolverEvent,
AppSyncIdentity,
} from './types'
export { Authorizations } from './consts'
const _ = {
merge,
clone,
decode,
encode,
dotate,
isMatchingGlob,
filterXSS,
isEmpty,
isUndefined,
lowerFirst,
isObject,
walk,
replaceAll,
}
export { _ }
================================================
FILE: packages/client/src/inspector.ts
================================================
/* eslint-disable n/prefer-global/process */
/* eslint-disable no-console */
import { inspect as nodeInspect } from 'node:util'
import type { logLevel } from './types'
const errorCodes = {
FORBIDDEN: 401,
BAD_USER_INPUT: 400,
INTERNAL_SERVER_ERROR: 500,
TOO_MANY_REQUESTS: 429,
}
export type ErrorExtensions = {
type: keyof typeof errorCodes
cause?: any
}
export type ErrorDetails = {
error: string
type: ErrorExtensions['type']
code: number
cause?: ErrorExtensions['cause']
}
export class CustomError extends Error {
public error: ErrorDetails['error']
public type: ErrorDetails['type']
public code: ErrorDetails['code']
public cause: ErrorDetails['cause']
public details: ErrorDetails
constructor(message: string, extensions: ErrorExtensions) {
super(message)
this.error = message
this.type = extensions.type
this.cause = extensions?.cause?.meta?.cause || extensions?.cause
this.code = typeof errorCodes[this.type] !== 'undefined' ? errorCodes[this.type] : errorCodes.INTERNAL_SERVER_ERROR
this.message = JSON.stringify({
error: this.error,
type: this.type,
code: this.code,
})
const maxCauseMessageLength = 500
if (this.cause?.message?.length > maxCauseMessageLength)
this.cause.message = `... ${this.cause.message.slice(this.cause.message.length - maxCauseMessageLength)}`
this.details = {
error: this.error,
type: this.type,
code: this.code,
...(this.cause && { cause: this.cause }),
}
if (!(process?.env?.PRISMA_APPSYNC_TESTING === 'true'))
log(message, this.details, 'ERROR')
}
}
export function parseError(error: Error): CustomError {
if (error instanceof CustomError) {
return error
}
else {
return new CustomError(error.message, {
type: 'INTERNAL_SERVER_ERROR',
cause: error,
})
}
}
export function log(message: string, obj?: any, level?: logLevel): void {
if (canPrintLog(level || 'INFO')) {
printLog(message, level || 'INFO')
if (obj) {
console.log(
nodeInspect(obj, {
compact: false,
depth: 5,
breakLength: 80,
maxStringLength: 800,
...(!process.env.LAMBDA_TASK_ROOT && {
colors: true,
}),
}),
)
}
}
}
export function printLog(message: any, level: logLevel): void {
const timestamp = new Date().toLocaleString(undefined, {
day: 'numeric',
month: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
const prefix = `◭ ${timestamp} <<${level}>>`
const log = [prefix, message].join(' ')
if (level === 'ERROR' && canPrintLog(level))
console.error(`\x1B[31m${log}`)
else if (level === 'WARN' && canPrintLog(level))
console.warn(`\x1B[33m${log}`)
else if (level === 'INFO' && canPrintLog(level))
console.info(`\x1B[36m${log}`)
}
function canPrintLog(level: logLevel): boolean {
if (process?.env?.PRISMA_APPSYNC_TESTING === 'true')
return false
const logLevel = String(process.env.PRISMA_APPSYNC_LOG_LEVEL) as logLevel
return (logLevel === 'ERROR' && level === 'ERROR')
|| (logLevel === 'WARN' && ['WARN', 'ERROR'].includes(level))
|| (logLevel === 'INFO')
}
================================================
FILE: packages/client/src/resolver.ts
================================================
import type {
PrismaArgs,
PrismaClient,
PrismaCount,
PrismaCreate,
PrismaCreateMany,
PrismaDelete,
PrismaDeleteMany,
PrismaGet,
PrismaList,
PrismaOperator,
PrismaUpdate,
PrismaUpdateMany,
PrismaUpsert,
QueryBuilder,
QueryParams,
} from './types'
import { merge } from './utils'
/**
* #### Query Builder
*/
export function prismaQueryJoin<T>(queries: PrismaArgs[], operators: PrismaOperator[]): T {
const prismaArgs: PrismaArgs = {}
// 'where', 'orderBy', 'select', 'skip', 'take', ...
operators.forEach((operator: PrismaOperator) => {
queries.forEach((query: PrismaArgs) => {
if (query?.[operator]) {
if (operator === 'where') {
if (prismaArgs.where?.AND) {
prismaArgs.where.AND.push(query.where)
}
else if (prismaArgs.where) {
prismaArgs.where = {
...prismaArgs.where,
AND: [query.where],
}
}
else {
prismaArgs.where = query.where
}
}
else if (prismaArgs?.[operator]) {
prismaArgs[operator] = merge(prismaArgs[operator], query[operator]) as never
}
else {
prismaArgs[operator] = query[operator] as never
}
}
})
})
return prismaArgs as T
}
export const queryBuilder: QueryBuilder = {
prismaGet: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaGet>(prismaQueries, ['where', 'select'])
},
prismaList: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaList>(prismaQueries, ['where', 'orderBy', 'select', 'skip', 'take'])
},
prismaCount: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaCount>(prismaQueries, ['where', 'orderBy', 'select', 'skip', 'take'])
},
prismaCreate: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaCreate>(prismaQueries, ['data', 'select'])
},
prismaCreateMany: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaCreateMany>(prismaQueries, ['data', 'skipDuplicates'])
},
prismaUpdate: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaUpdate>(prismaQueries, ['data', 'where', 'select'])
},
prismaUpdateMany: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaUpdateMany>(prismaQueries, ['data', 'where'])
},
prismaUpsert: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaUpsert>(prismaQueries, ['where', 'create', 'update', 'select'])
},
prismaDelete: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaDelete>(prismaQueries, ['where', 'select'])
},
prismaDeleteMany: (...prismaQueries: PrismaArgs[]) => {
return prismaQueryJoin<PrismaDeleteMany>(prismaQueries, ['where'])
},
}
/**
* #### Query :: Find Unique
*
* https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findunique
* @param {PrismaClient} prismaClient
* @param {QueryParams} query
*/
export async function getQuery(prismaClient: PrismaClient, query: QueryParams) {
if (query.context.model === null)
return
const results = await prismaClient[query.context.model.prismaRef].findUnique(queryBuilder.prismaGet(query.prismaArgs))
return results
}
/**
* #### Query :: Find Many
*
* https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findmany
* @param {PrismaClient} prismaClient
* @param {QueryParams} query
*/
export async function listQuery(prismaClient: PrismaClient, query: QueryParams) {
if (query.context.model === null)
return
const result
gitextract_frruakbg/ ├── .all-contributorsrc ├── .eslintrc ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── unit-tests.yml ├── .gitignore ├── .markdownlint.json ├── .npmignore ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── bin/ │ ├── build.mjs │ ├── cleans.mjs │ ├── dev.mjs │ ├── env.mjs │ ├── postinstall.mjs │ ├── publish/ │ │ ├── _pkg.core.cleanse.js │ │ ├── _pkg.core.restore.js │ │ └── _pkg.installer.cleanse.js │ ├── publish.mjs │ └── test.mjs ├── docs/ │ ├── .vitepress/ │ │ ├── config.ts │ │ └── theme/ │ │ ├── Layout.vue │ │ ├── index.ts │ │ └── styles/ │ │ └── vars.css │ ├── changelog/ │ │ ├── 1.0.0-rc.1.md │ │ ├── 1.0.0-rc.2.md │ │ ├── 1.0.0-rc.3.md │ │ ├── 1.0.0-rc.4.md │ │ ├── 1.0.0-rc.5.md │ │ ├── 1.0.0-rc.6.md │ │ ├── 1.0.0-rc.7.md │ │ ├── 1.0.0.md │ │ └── index.md │ ├── contributing.md │ ├── features/ │ │ ├── gql-schema.md │ │ ├── hooks.md │ │ └── resolvers.md │ ├── index.md │ ├── quick-start/ │ │ ├── deploy.md │ │ ├── getting-started.md │ │ ├── installation.md │ │ └── usage.md │ ├── security/ │ │ ├── appsync-authz.md │ │ ├── query-depth.md │ │ ├── rate-limiter.md │ │ ├── shield-acl.md │ │ └── xss-sanitizer.md │ ├── support.md │ └── tools/ │ └── appsync-gql-schema-diff.md ├── package.json ├── packages/ │ ├── boilerplate/ │ │ ├── cdk/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── appsync.ts │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── cdk.json │ │ ├── handler.ts │ │ ├── prisma/ │ │ │ └── sqlite.prisma │ │ ├── server/ │ │ │ └── server.ts │ │ └── tsconfig.json │ ├── client/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── adapter.ts │ │ │ ├── consts.ts │ │ │ ├── core.ts │ │ │ ├── guard.ts │ │ │ ├── index.ts │ │ │ ├── inspector.ts │ │ │ ├── resolver.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ └── tsconfig.json │ ├── generator/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── client.ts │ │ │ ├── directives.ts │ │ │ ├── generator.ts │ │ │ ├── handler.ts │ │ │ ├── index.ts │ │ │ ├── resolvers.ts │ │ │ └── schema.ts │ │ └── tsconfig.json │ ├── installer/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── installer.ts │ │ └── tsconfig.json │ └── server/ │ ├── package.json │ ├── src/ │ │ ├── appsync-simulator.ts │ │ ├── index.d.ts │ │ ├── index.ts │ │ ├── lambdaRequest.vtl │ │ └── lambdaResponse.vtl │ └── tsconfig.json ├── pnpm-workspace.yaml ├── tests/ │ ├── client/ │ │ ├── adapter.test.ts │ │ ├── core.test.ts │ │ ├── guard.test.ts │ │ ├── mocks/ │ │ │ ├── graphql-json.ts │ │ │ ├── lambda-event.ts │ │ │ └── lambda-identity.ts │ │ ├── resolver.test.ts │ │ ├── utils/ │ │ │ └── index.ts │ │ └── utils.test.ts │ └── generator/ │ ├── @gql.test.ts │ ├── crud.test.ts │ ├── mock/ │ │ ├── appsync-directives.gql │ │ └── appsync-scalars.gql │ └── schemas/ │ ├── @gql.prisma │ ├── crud.gql │ ├── crud.prisma │ └── generated/ │ ├── @gql/ │ │ ├── client/ │ │ │ ├── adapter.d.ts │ │ │ ├── consts.d.ts │ │ │ ├── core.d.ts │ │ │ ├── guard.d.ts │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── inspector.d.ts │ │ │ ├── resolver.d.ts │ │ │ ├── types.d.ts │ │ │ └── utils.d.ts │ │ ├── resolvers.yaml │ │ └── schema.gql │ └── crud/ │ ├── client/ │ │ ├── adapter.d.ts │ │ ├── consts.d.ts │ │ ├── core.d.ts │ │ ├── guard.d.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── inspector.d.ts │ │ ├── resolver.d.ts │ │ ├── types.d.ts │ │ └── utils.d.ts │ ├── resolvers.yaml │ └── schema.gql ├── tsconfig.json └── vite.config.ts
SYMBOL INDEX (752 symbols across 44 files)
FILE: bin/publish.mjs
function getPublishConfig (line 11) | async function getPublishConfig() {
function publishCore (line 66) | async function publishCore({ tag }) {
function publishInstaller (line 89) | async function publishInstaller({ tag }) {
FILE: bin/publish/_pkg.core.cleanse.js
constant ORIG_PKG_PATH (line 5) | const ORIG_PKG_PATH = path.resolve(__dirname, '../../package.json')
constant BACKUP_PKG_PATH (line 6) | const BACKUP_PKG_PATH = path.resolve(__dirname, '../../package-beforePub...
constant RESTORE_PKG_PATH (line 7) | const RESTORE_PKG_PATH = path.resolve(__dirname, '../../package-afterPub...
FILE: bin/publish/_pkg.core.restore.js
constant ORIG_PKG_PATH (line 5) | const ORIG_PKG_PATH = path.resolve(__dirname, '../../package.json')
constant BACKUP_PKG_PATH (line 6) | const BACKUP_PKG_PATH = path.resolve(__dirname, '../../package-beforePub...
constant RESTORE_PKG_PATH (line 7) | const RESTORE_PKG_PATH = path.resolve(__dirname, '../../package-afterPub...
FILE: bin/publish/_pkg.installer.cleanse.js
constant SRC_PKG_PATH (line 5) | const SRC_PKG_PATH = path.resolve(__dirname, '../../packages/installer/p...
constant DEST_PKG_PATH (line 6) | const DEST_PKG_PATH = path.resolve(__dirname, '../../dist/installer/pack...
FILE: packages/boilerplate/cdk/src/appsync.ts
type AppSyncStackProps (line 17) | interface AppSyncStackProps {
class AppSyncStack (line 34) | class AppSyncStack extends Stack {
method constructor (line 46) | constructor(scope: Construct, id: string, tplProps: AppSyncStackProps,...
method createGraphQLApi (line 60) | createGraphQLApi() {
method createLambdaResolver (line 91) | createLambdaResolver() {
method createPrismaAppSyncResolvers (line 155) | createPrismaAppSyncResolvers() {
method createDataSources (line 190) | createDataSources() {
FILE: packages/boilerplate/cdk/src/index.ts
method beforeBundling (line 27) | beforeBundling(inputDir: string, outputDir: string): string[] {
method beforeInstall (line 30) | beforeInstall() {
method afterBundling (line 33) | afterBundling() {
FILE: packages/client/src/adapter.ts
function parseEvent (line 43) | async function parseEvent(appsyncEvent: AppSyncEvent, options: Options, ...
function addNullables (line 108) | async function addNullables(data: any): Promise<any> {
function getAuthIdentity (line 138) | function getAuthIdentity({ appsyncEvent }: { appsyncEvent: AppSyncEvent ...
function getContext (line 216) | function getContext({
function getOperation (line 252) | function getOperation({ fieldName }: { fieldName: string }): string {
function getAction (line 268) | function getAction({ operation }: { operation: string }): Action {
function getActionAlias (line 292) | function getActionAlias({ action }: { action: Action }): ActionsAlias {
function getModel (line 319) | function getModel(
function getFields (line 346) | function getFields({ _selectionSetList }: { _selectionSetList: string[] ...
function getType (line 365) | function getType({ _parentTypeName }: { _parentTypeName: string }): Grap...
function getPrismaArgs (line 384) | function getPrismaArgs({
function getOrderBy (line 444) | function getOrderBy(sortObj: any): any {
function parseOrderBy (line 460) | function parseOrderBy(orderByInputs: any): any[] {
function getInclude (line 477) | function getInclude(parts: any): any {
function getSelect (line 494) | function getSelect(parts: any): any {
function parseSelectionList (line 511) | function parseSelectionList(selectionSetList: any): any {
function getPaths (line 548) | function getPaths({
FILE: packages/client/src/consts.ts
type Actions (line 6) | enum Actions {
type ActionsAliases (line 37) | enum ActionsAliases {
type Authorizations (line 61) | enum Authorizations {
FILE: packages/client/src/core.ts
class PrismaAppSync (line 57) | class PrismaAppSync {
method constructor (line 93) | constructor(options?: PrismaAppSyncOptionsType) {
method resolve (line 226) | public async resolve<CustomResolvers = void>(
FILE: packages/client/src/guard.ts
function sanitize (line 26) | async function sanitize(data: any): Promise<any> {
function clarify (line 44) | async function clarify(data: any): Promise<any> {
function getFieldAuthorization (line 67) | async function getFieldAuthorization(
function getShieldAuthorization (line 139) | async function getShieldAuthorization({
function getDepth (line 202) | function getDepth(
function runHooks (line 244) | async function runHooks({
function preventDOS (line 287) | async function preventDOS({
FILE: packages/client/src/inspector.ts
type ErrorExtensions (line 13) | type ErrorExtensions = {
type ErrorDetails (line 18) | type ErrorDetails = {
class CustomError (line 25) | class CustomError extends Error {
method constructor (line 32) | constructor(message: string, extensions: ErrorExtensions) {
function parseError (line 63) | function parseError(error: Error): CustomError {
function log (line 75) | function log(message: string, obj?: any, level?: logLevel): void {
function printLog (line 95) | function printLog(message: any, level: logLevel): void {
function canPrintLog (line 115) | function canPrintLog(level: logLevel): boolean {
FILE: packages/client/src/resolver.ts
function prismaQueryJoin (line 23) | function prismaQueryJoin<T>(queries: PrismaArgs[], operators: PrismaOper...
function getQuery (line 97) | async function getQuery(prismaClient: PrismaClient, query: QueryParams) {
function listQuery (line 113) | async function listQuery(prismaClient: PrismaClient, query: QueryParams) {
function countQuery (line 129) | async function countQuery(prismaClient: PrismaClient, query: QueryParams) {
function createQuery (line 145) | async function createQuery(prismaClient: PrismaClient, query: QueryParam...
function createManyQuery (line 161) | async function createManyQuery(prismaClient: PrismaClient, query: QueryP...
function updateQuery (line 177) | async function updateQuery(prismaClient: PrismaClient, query: QueryParam...
function updateManyQuery (line 193) | async function updateManyQuery(prismaClient: PrismaClient, query: QueryP...
function upsertQuery (line 209) | async function upsertQuery(prismaClient: PrismaClient, query: QueryParam...
function deleteQuery (line 225) | async function deleteQuery(prismaClient: PrismaClient, query: QueryParam...
function deleteManyQuery (line 241) | async function deleteManyQuery(prismaClient: PrismaClient, query: QueryP...
FILE: packages/client/src/types.ts
type logLevel (line 13) | type logLevel = 'INFO' | 'WARN' | 'ERROR'
type PrismaAppSyncOptionsType (line 15) | type PrismaAppSyncOptionsType = {
type Options (line 24) | type Options = Required<PrismaAppSyncOptionsType> & {
type InjectedConfig (line 29) | type InjectedConfig = {
type RuntimeConfig (line 35) | type RuntimeConfig = {
type Action (line 41) | type Action = typeof Actions[keyof typeof Actions] | string
type ActionsAlias (line 43) | type ActionsAlias = typeof ActionsAliases[keyof typeof ActionsAliases] |...
type ActionsAliasStr (line 45) | type ActionsAliasStr = keyof typeof ActionsAliases
type Context (line 47) | type Context = {
type Model (line 53) | type Model = { prismaRef: string; singular: string; plural: string } | null
type QueryParams (line 78) | type QueryParams<T = any> = {
type Authorization (line 91) | type Authorization = typeof Authorizations[keyof typeof Authorizations] ...
type PrismaGet (line 93) | type PrismaGet = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArgs, ...
type PrismaList (line 94) | type PrismaList = Pick<PrismaArgs, 'where' | 'orderBy' | 'select' | 'ski...
type PrismaCount (line 95) | type PrismaCount = Pick<PrismaArgs, 'where' | 'orderBy' | 'select' | 'sk...
type PrismaCreate (line 96) | type PrismaCreate = Pick<Required<PrismaArgs>, 'data'> & Pick<PrismaArgs...
type PrismaCreateMany (line 97) | type PrismaCreateMany = Pick<Required<PrismaArgs>, 'data'> & Pick<Prisma...
type PrismaUpdate (line 98) | type PrismaUpdate = Pick<Required<PrismaArgs>, 'data' | 'where'> & Pick<...
type PrismaUpdateMany (line 99) | type PrismaUpdateMany = Pick<Required<PrismaArgs>, 'data' | 'where'>
type PrismaUpsert (line 100) | type PrismaUpsert = Pick<Required<PrismaArgs>, 'where'> &
type PrismaDelete (line 102) | type PrismaDelete = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArg...
type PrismaDeleteMany (line 103) | type PrismaDeleteMany = Pick<Required<PrismaArgs>, 'where'>
type QueryBuilder (line 105) | type QueryBuilder = {
type QueryParamsCustom (line 118) | type QueryParamsCustom<T = any> = QueryParams<T> & {
type BeforeHookParams (line 122) | type BeforeHookParams = QueryParams & {
type AfterHookParams (line 148) | type AfterHookParams = QueryParams & {
type ShieldContext (line 153) | type ShieldContext = {
type Reason (line 158) | type Reason = string | ((context: ShieldContext) => string)
type ShieldRule (line 160) | type ShieldRule = boolean | ((context: ShieldContext) => boolean | Promi...
type Shield (line 162) | type Shield = {
type HooksProps (line 171) | type HooksProps = {
type HooksReturn (line 176) | type HooksReturn = {
type HookPath (line 181) | type HookPath<Operations extends string, CustomResolvers> = Operations |...
type HooksParameter (line 183) | type HooksParameter<
type HooksParameters (line 189) | type HooksParameters<
type Hooks (line 199) | type Hooks<Operations extends string, CustomResolvers extends string> =
type ShieldAuthorization (line 203) | type ShieldAuthorization = {
type ResolveParams (line 211) | type ResolveParams<Operations extends string, CustomResolvers extends st...
type PrismaArgs (line 224) | type PrismaArgs = {
type PrismaOperator (line 236) | type PrismaOperator = keyof Required<PrismaArgs>
type AppSyncEvent (line 238) | type AppSyncEvent = AppSyncResolverEvent<any>
type GraphQLType (line 240) | type GraphQLType = 'Query' | 'Mutation' | 'Subscription'
type API_KEY (line 242) | type API_KEY = null | {
type AWS_LAMBDA (line 246) | type AWS_LAMBDA = AppSyncIdentityLambda
type AWS_IAM (line 248) | type AWS_IAM = AppSyncIdentityIAM
type AMAZON_COGNITO_USER_POOLS (line 250) | type AMAZON_COGNITO_USER_POOLS = AppSyncIdentityCognito
type OPENID_CONNECT (line 252) | type OPENID_CONNECT = AppSyncIdentityOIDC
type Identity (line 254) | type Identity = API_KEY | AWS_LAMBDA | AWS_IAM | AMAZON_COGNITO_USER_POO...
FILE: packages/client/src/utils.ts
function merge (line 16) | function merge(...sources: any[]): any {
function clone (line 28) | function clone(source: any): any {
function decode (line 41) | function decode(str: string): string {
function encode (line 53) | function encode(str: string): string {
function dotate (line 66) | function dotate(source: any): any {
function objectToPaths (line 79) | function objectToPaths(source: any): string[] {
function omit (line 119) | function omit(obj: any, omitKey: string | string[]): any {
function isMatchingGlob (line 135) | function isMatchingGlob(path: string, globPatterns: string | string[]): ...
function filterXSS (line 147) | function filterXSS(str: string): string {
function isEmpty (line 159) | function isEmpty(element: any): boolean {
function isUndefined (line 178) | function isUndefined(element: any): boolean {
function lowerFirst (line 201) | function lowerFirst(str: string): string {
function upperFirst (line 217) | function upperFirst(str: string): string {
function walk (line 251) | async function walk(
function walkObj (line 264) | async function walkObj(
class WalkNode (line 293) | class WalkNode {
method constructor (line 294) | constructor(public _path: (string | number)[] = [], public _ignoreChil...
method ignoreChilds (line 299) | ignoreChilds() { this._ignoreChildren = true }
method getPath (line 300) | getPath() { return this._path }
function replaceAll (line 313) | function replaceAll(str: string, findArray: string[], replaceArray: stri...
function uniq (line 339) | function uniq<T>(array: readonly T[]): T[] {
function uniqBy (line 348) | function uniqBy<T>(array: readonly T[], iteratee: keyof T | ((a: any) =>...
FILE: packages/generator/src/client.ts
class ClientConfigBuilder (line 8) | class ClientConfigBuilder {
method createRuntimeConfig (line 11) | public async createRuntimeConfig(dmmf: DMMF.Document, options?: { defa...
method mergeResolvers (line 42) | public async mergeResolvers(baseResolvers: string, mergeResolvers: str...
method prettyYaml (line 46) | private async prettyYaml(yaml: string) {
method parseModelDMMF (line 61) | private parseModelDMMF(modelDMMF: DMMF.Model, options?: { defaultDirec...
method createModelConfig (line 79) | private createModelConfig(model: ParsedModel): RuntimeConfig {
type ParsedModel (line 198) | type ParsedModel = {
type Resolver (line 206) | type Resolver = {
FILE: packages/generator/src/directives.ts
function getActionType (line 10) | function getActionType(action) {
function extractUniqueAuthzModes (line 21) | function extractUniqueAuthzModes(
function parseDocumentationMatches (line 34) | function parseDocumentationMatches(regex, doc, find, replace) {
function parseModelDirectives (line 50) | function parseModelDirectives(
function accessNestedProperty (line 91) | function accessNestedProperty(obj, path) {
function isDirectiveDefined (line 95) | function isDirectiveDefined(directive, ...properties) {
function isActionEligibleForGQL (line 99) | function isActionEligibleForGQL(
function isFieldEligibleForGQL (line 131) | function isFieldEligibleForGQL(
function mergeIfArray (line 137) | function mergeIfArray(target, source) {
function getAuthzDirectivesForModel (line 144) | function getAuthzDirectivesForModel(directives) {
function getAuthzDirectivesForAction (line 164) | function getAuthzDirectivesForAction(action, directives) {
function getAuthzDirectivesForField (line 181) | function getAuthzDirectivesForField(field, directives) {
function combineUniqueAuthDirectives (line 187) | function combineUniqueAuthDirectives(authDirectives: Authz[]): Authz[] {
function convertToGQLDirectives (line 206) | function convertToGQLDirectives(prismaAuthDirectives, schemaAuthzModes) {
type Action (line 243) | type Action = 'get' | 'list' | 'count' | 'create' | 'createMany' | 'upda...
type Directives (line 245) | type Directives = {
type ModelDirectives (line 250) | type ModelDirectives = {
type Authz (line 260) | type Authz = {
type DirectiveGql (line 265) | type DirectiveGql = {
type DirectiveAuth (line 274) | type DirectiveAuth = {
FILE: packages/generator/src/generator.ts
class PrismaAppSyncGenerator (line 8) | class PrismaAppSyncGenerator {
method constructor (line 16) | constructor(options: GeneratorOption) {
method makeAppSyncSchema (line 25) | public async makeAppSyncSchema() {
method makeAppSyncResolvers (line 52) | public async makeAppSyncResolvers() {
method makeClientRuntimeConfig (line 75) | public async makeClientRuntimeConfig() {
method replaceInFile (line 102) | private async replaceInFile(file: string, findRegex: RegExp, replace: ...
type GeneratorOption (line 111) | type GeneratorOption = {
FILE: packages/generator/src/handler.ts
method onManifest (line 12) | onManifest() {
method onGenerate (line 20) | async onGenerate(options: any) {
FILE: packages/generator/src/resolvers.ts
class ResolversBuilder (line 6) | class ResolversBuilder {
method createResolvers (line 9) | public async createResolvers(dmmf: DMMF.Document, options?: { defaultD...
method mergeResolvers (line 26) | public async mergeResolvers(baseResolvers: string, mergeResolvers: str...
method prettyYaml (line 30) | private async prettyYaml(yaml: string) {
method parseModelDMMF (line 45) | private parseModelDMMF(modelDMMF: DMMF.Model, options?: { defaultDirec...
method buildAppSyncResolvers (line 59) | private async buildAppSyncResolvers() {
method createQueryResolvers (line 71) | private createQueryResolvers(model: ParsedModel) {
method createMutationResolvers (line 100) | private createMutationResolvers(model: ParsedModel) {
method createSubscriptionResolvers (line 165) | private createSubscriptionResolvers(model: ParsedModel) {
type ParsedModel (line 249) | type ParsedModel = {
type Resolver (line 255) | type Resolver = {
FILE: packages/generator/src/schema.ts
class SchemaBuilder (line 16) | class SchemaBuilder {
method createSchema (line 24) | public async createSchema(dmmf: DMMF.Document, options?: { defaultDire...
method mergeSchemas (line 59) | public async mergeSchemas(baseSchema: string, mergeSchema: string) {
method perttyGraphQL (line 68) | private async perttyGraphQL(schema: string) {
method parseModelDMMF (line 88) | private parseModelDMMF(modelDMMF: DMMF.Model, options?: { defaultDirec...
method createBaseTypes (line 229) | private createBaseTypes() {
method createBaseInputs (line 238) | private createBaseInputs() {
method createBaseEnums (line 604) | private createBaseEnums() {
method createEnumsInputs (line 616) | private createEnumsInputs(datamodel: DMMF.Datamodel) {
method createEnumsTypes (line 641) | private createEnumsTypes(datamodel: DMMF.Datamodel) {
method createModelTypes (line 650) | private createModelTypes(model: ParsedModel) {
method createModelInputs (line 666) | private createModelInputs(model: ParsedModel, allModels: ParsedModel[]) {
method createModelQueries (line 1046) | private createModelQueries(model: ParsedModel) {
method createModelMutations (line 1093) | private createModelMutations(model: ParsedModel) {
method createModelSubscriptions (line 1193) | private createModelSubscriptions(model: ParsedModel) {
method getFieldScalar (line 1294) | private getFieldScalar(field: DMMF.Field, inject?: FieldScalarOptions) {
method buildAppSyncSchema (line 1365) | private async buildAppSyncSchema() {
method buildTypes (line 1378) | private buildTypes() {
method buildEnums (line 1391) | private buildEnums() {
method buildInputs (line 1401) | private buildInputs() {
method buildQueries (line 1421) | private buildQueries() {
method buildMutations (line 1425) | private buildMutations() {
method buildSubscriptions (line 1429) | private buildSubscriptions() {
method buildOperations (line 1433) | private buildOperations(operations, keyword) {
method formatOperation (line 1443) | private formatOperation(operation) {
type ParsedModel (line 1458) | type ParsedModel = {
type GqlField (line 1480) | type GqlField = {
type GqlArg (line 1490) | type GqlArg = {
type GqlType (line 1495) | type GqlType = {
type GqlInput (line 1502) | type GqlInput = {
type GqlEnum (line 1508) | type GqlEnum = {
type GqlQuery (line 1514) | type GqlQuery = {
type GqlMutation (line 1522) | type GqlMutation = {
type GqlSubscription (line 1530) | type GqlSubscription = {
type FieldScalarOptions (line 1538) | type FieldScalarOptions = {
FILE: packages/installer/src/index.ts
function main (line 5) | async function main(): Promise<any> {
FILE: packages/installer/src/installer.ts
class Installer (line 9) | class Installer {
method constructor (line 41) | constructor() {
method start (line 83) | public async start(): Promise<void> {
method printBranding (line 93) | private async printBranding(): Promise<void> {
method printQuickstart (line 119) | private async printQuickstart(): Promise<void> {
method askQuestions (line 134) | private async askQuestions(): Promise<void> {
method prepare (line 222) | private async prepare(): Promise<void> {
method install (line 489) | private async install(): Promise<void> {
method replaceInFile (line 588) | private replaceInFile(file: string, findRegex: RegExp, replace: string...
FILE: packages/server/src/appsync-simulator.ts
function useAppSyncSimulator (line 21) | function useAppSyncSimulator({
type ServerOptions (line 114) | type ServerOptions = {
FILE: packages/server/src/index.ts
function createServer (line 49) | async function createServer(serverOptions: ServerOptions): Promise<void> {
FILE: tests/client/adapter.test.ts
constant TESTING (line 59) | const TESTING: {
function mockAppSyncEvent (line 76) | function mockAppSyncEvent(identity: NonNullable<Authorization>) {
FILE: tests/client/core.test.ts
constant TESTING (line 25) | const TESTING = {
function mockAppSyncEvent (line 33) | function mockAppSyncEvent(operationName: string, query: string) {
FILE: tests/client/guard.test.ts
constant TESTING (line 10) | const TESTING = {
FILE: tests/client/mocks/graphql-json.ts
type variablesObject (line 5) | type variablesObject = {
type Argument (line 9) | type Argument = {
type Selection (line 28) | type Selection = {
type SelectionSet (line 42) | type SelectionSet = {
type VariableDefinition (line 47) | type VariableDefinition = {
type ActualDefinitionNode (line 65) | type ActualDefinitionNode = {
function flatMap (line 76) | function flatMap(arg: any, callback: any) {
function isString (line 80) | function isString(arg: any): boolean {
function isObject (line 84) | function isObject(arg: any): boolean {
function getArgument (line 88) | function getArgument(arg: any) {
function getArguments (line 110) | function getArguments(args: any[]) {
function getSelections (line 120) | function getSelections(selections: Selection[]) {
function checkEachVariableInQueryIsDefined (line 150) | function checkEachVariableInQueryIsDefined(defintion: ActualDefinitionNo...
function replaceVariables (line 184) | function replaceVariables(obj: any, variables: any): any {
function graphQlQueryToJson (line 200) | function graphQlQueryToJson(
FILE: tests/client/mocks/lambda-event.ts
function mockLambdaEvent (line 5) | function mockLambdaEvent({
FILE: tests/client/mocks/lambda-identity.ts
function mockLambdaIdentity (line 14) | function mockLambdaIdentity(identity: Authorization, opts?: mockOptions)...
type mockOptions (line 70) | type mockOptions = {
FILE: tests/client/resolver.test.ts
constant TESTING (line 11) | const TESTING = {
FILE: tests/client/utils/index.ts
function format (line 3) | function format(str, ...args) {
function testEach (line 10) | function testEach(cases: any[][]): (name: string, fn: Function) => void {
FILE: tests/generator/schemas/generated/@gql/client/consts.d.ts
type Actions (line 1) | enum Actions {
type ActionsAliases (line 22) | enum ActionsAliases {
type Authorizations (line 45) | enum Authorizations {
FILE: tests/generator/schemas/generated/@gql/client/core.d.ts
class PrismaAppSync (line 20) | class PrismaAppSync {
FILE: tests/generator/schemas/generated/@gql/client/index.js
function el (line 11) | function el(e,t,r){let i=br(e,t,"-",!1,r)||[],s=br(t,e,"",!1,r)||[],a=br...
function tl (line 11) | function tl(e,t){let r=1,i=1,s=Zi(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a...
function rl (line 11) | function rl(e,t,r){if(e===t)return{pattern:e,count:[],digits:0};let i=il...
function Yi (line 11) | function Yi(e,t,r,i){let s=tl(e,t),a=[],o=e,l;for(let c=0;c<s.length;c++...
function br (line 11) | function br(e,t,r,i,s){let a=[];for(let o of e){let{string:l}=o;!i&&!Xi(...
function il (line 11) | function il(e,t){let r=[];for(let i=0;i<e.length;i++)r.push([e[i],t[i]])...
function sl (line 11) | function sl(e,t){return e>t?1:t>e?-1:0}
function Xi (line 11) | function Xi(e,t,r){return e.some(i=>i[t]===r)}
function Zi (line 11) | function Zi(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}
function Ji (line 11) | function Ji(e,t){return e-e%Math.pow(10,t)}
function es (line 11) | function es(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}
function nl (line 11) | function nl(e,t,r){return`[${e}${t-e===1?"":"-"}${t}]`}
function ts (line 11) | function ts(e){return/^-?(0+)\d/.test(e)}
function al (line 11) | function al(e,t,r){if(!t.isPadded)return e;let i=Math.abs(t.maxLen-Strin...
method extglobChars (line 17) | extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e....
method globChars (line 17) | globChars(e){return e===!0?Vl:Ls}
function gc (line 17) | function gc(e){return!!e&&typeof e=="object"}
function mc (line 17) | function mc(e){var t=Object.prototype.toString.call(e);return t==="[obje...
function Ac (line 17) | function Ac(e){return e.$$typeof===bc}
function Ec (line 17) | function Ec(e){return Array.isArray(e)?[]:{}}
function Ve (line 17) | function Ve(e,t){return t.clone!==!1&&t.isMergeableObject(e)?Ce(Ec(e),e,...
function xc (line 17) | function xc(e,t,r){return e.concat(t).map(function(i){return Ve(i,r)})}
function vc (line 17) | function vc(e,t){if(!t.customMerge)return Ce;var r=t.customMerge(e);retu...
function Rc (line 17) | function Rc(e){return Object.getOwnPropertySymbols?Object.getOwnProperty...
function Js (line 17) | function Js(e){return Object.keys(e).concat(Rc(e))}
function en (line 17) | function en(e,t){try{return t in e}catch{return!1}}
function wc (line 17) | function wc(e,t){return en(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Obje...
function Sc (line 17) | function Sc(e,t,r){var i={};return r.isMergeableObject(e)&&Js(e).forEach...
function Ce (line 17) | function Ce(e,t,r){r=r||{},r.arrayMerge=r.arrayMerge||xc,r.isMergeableOb...
function Oc (line 18) | function Oc(e,t){var p=t===void 0?_c:t,d=p.mode,r=d===void 0?"specialCha...
function qc (line 18) | function qc(e,t){var i=(t===void 0?Nc:t).level,r=i===void 0?"all":i;if(!...
function kc (line 18) | function kc(e,t){var r=t===void 0?Pc:t,i=r.level,s=i===void 0?"all":i,a=...
function pn (line 18) | function pn(){var e={};return e["align-content"]=!1,e["align-items"]=!1,...
function Dc (line 18) | function Dc(e,t,r){}
function Ic (line 18) | function Ic(e,t,r){}
function Mc (line 18) | function Mc(e,t){return Hc.test(t)?"":t}
function Uc (line 18) | function Uc(e,t){e=Qe.trimRight(e),e[e.length-1]!==";"&&(e+=";");var r=e...
function gn (line 19) | function gn(e){return e==null}
function $c (line 19) | function $c(e){var t={};for(var r in e)t[r]=e[r];return t}
function mn (line 19) | function mn(e){e=$c(e||{}),e.whiteList=e.whiteList||xt.whiteList,e.onAtt...
function Bc (line 19) | function Bc(e,t){var r=new En(t);return r.process(e)}
function wn (line 19) | function wn(){return{a:["target","href","title"],abbr:["title"],address:...
function Vc (line 19) | function Vc(e,t,r){}
function jc (line 19) | function jc(e,t,r){}
function Qc (line 19) | function Qc(e,t,r){}
function Kc (line 19) | function Kc(e,t,r){}
function Tn (line 19) | function Tn(e){return e.replace(Yc,"<").replace(Xc,">")}
function Wc (line 19) | function Wc(e,t,r,i){if(r=Nn(r),t==="href"||t==="src"){if(r=Tt.trim(r),r...
function Cn (line 19) | function Cn(e){return e.replace(Zc,""")}
function _n (line 19) | function _n(e){return e.replace(Jc,'"')}
function On (line 19) | function On(e){return e.replace(eu,n(function(r,i){return i[0]==="x"||i[...
function Pn (line 19) | function Pn(e){return e.replace(tu,":").replace(ru," ")}
function Ln (line 19) | function Ln(e){for(var t="",r=0,i=e.length;r<i;r++)t+=e.charCodeAt(r)<32...
function Nn (line 19) | function Nn(e){return e=_n(e),e=On(e),e=Pn(e),e=Ln(e),e}
function qn (line 19) | function qn(e){return e=Cn(e),e=Tn(e),e}
function iu (line 19) | function iu(){return""}
function su (line 19) | function su(e,t){typeof t!="function"&&(t=n(function(){},"next"));var r=...
function nu (line 19) | function nu(e){for(var t="",r=0;r<e.length;){var i=e.indexOf("<!--",r);i...
function au (line 19) | function au(e){var t=e.split("");return t=t.filter(function(r){var i=r.c...
function ou (line 19) | function ou(e){var t=me.spaceIndex(e),r;return t===-1?r=e.slice(1,-1):r=...
function lu (line 19) | function lu(e){return e.slice(0,2)==="</"}
function cu (line 19) | function cu(e,t,r){"use strict";var i="",s=0,a=!1,o=!1,l=0,c=e.length,u=...
function pu (line 19) | function pu(e,t){"use strict";var r=0,i=0,s=[],a=!1,o=e.length;function ...
function fu (line 19) | function fu(e,t){for(;t<e.length;t++){var r=e[t];if(r!==" ")return r==="...
function du (line 19) | function du(e,t){for(;t<e.length;t++){var r=e[t];if(r!==" ")return r==="...
function hu (line 19) | function hu(e,t){for(;t>0;t--){var r=e[t];if(r!==" ")return r==="="?t:-1}}
function gu (line 19) | function gu(e){return e[0]==='"'&&e[e.length-1]==='"'||e[0]==="'"&&e[e.l...
function kn (line 19) | function kn(e){return gu(e)?e.substr(1,e.length-2):e}
function Ct (line 19) | function Ct(e){return e==null}
function Au (line 19) | function Au(e){var t=_t.spaceIndex(e);if(t===-1)return{html:"",closing:e...
function Eu (line 19) | function Eu(e){var t={};for(var r in e)t[r]=e[r];return t}
function xu (line 19) | function xu(e){var t={};for(var r in e)Array.isArray(e[r])?t[r.toLowerCa...
function In (line 19) | function In(e){e=Eu(e||{}),e.stripIgnoreTag&&(e.onIgnoreTag&&console.err...
function Bn (line 19) | function Bn(e,t){var r=new $n(t);return r.process(e)}
function vu (line 19) | function vu(){return typeof self<"u"&&typeof DedicatedWorkerGlobalScope<...
function pr (line 26) | function pr(e){if(typeof e!="object"||e===null)return!1;let t=Object.get...
function pe (line 26) | function pe(...e){return Qr.default.all([{},...e])}
function Re (line 26) | function Re(e){return(0,Qr.default)({},e)}
function Nt (line 26) | function Nt(e){return(0,Lt.decode)(e)}
function qt (line 26) | function qt(e){return(0,Lt.encode)(e)}
function jn (line 26) | function jn(e){return dt(e,{shallowArrays:!0})}
function Qn (line 26) | function Qn(e){let t=dt(e),r=Object.keys(t).map(a=>a.split(".").filter(o...
function Kn (line 26) | function Kn(e,t){let r=Re(e);return(Array.isArray(t)?t:[t]).forEach(s=>d...
function We (line 26) | function We(e,t){return(0,Gn.isMatch)(e,t)}
function kt (line 26) | function kt(e){return(0,Vn.default)(e)}
function W (line 26) | function W(e){return e==null||typeof e>"u"||typeof e=="string"&&e.trim()...
function Ye (line 26) | function Ye(e){return e===void 0||typeof e>"u"}
function Dt (line 26) | function Dt(e){return e?e.charAt(0).toLowerCase()+e.slice(1):""}
function we (line 26) | async function we(e,t){return Array.isArray(e)?await Promise.all(e.map(a...
function Pt (line 26) | async function Pt(e,t,r=new Ke){let i=Re(e),s={},a=Object.keys(i);for(le...
method constructor (line 26) | constructor(t=[],r=!1){this._path=t;this._ignoreChildren=r;this._path=t,...
method ignoreChilds (line 26) | ignoreChilds(){this._ignoreChildren=!0}
method getPath (line 26) | getPath(){return this._path}
function Wn (line 26) | function Wn(e,t,r){let i=[],s={};for(let a=0;a<t.length;a++)i.push(t[a]....
function Xe (line 26) | function Xe(e){return Array.from(new Set(e))}
method constructor (line 26) | constructor(t,r){super(t),this.error=t,this.type=r.type,this.cause=r?.ca...
function sa (line 26) | function sa(e){return e instanceof N?e:new N(e.message,{type:"INTERNAL_S...
function G (line 26) | function G(e,t,r){Ft(r||"INFO")&&(_u(e,r||"INFO"),t&&console.log((0,ia.i...
function _u (line 26) | function _u(e,t){let s=[`\u25ED ${new Date().toLocaleString(void 0,{day:...
function Ft (line 26) | function Ft(e){if(process?.env?.PRISMA_APPSYNC_TESTING==="true")return!1...
method addEventListener (line 26) | addEventListener(i,s){this._onabort.push(s)}
method constructor (line 26) | constructor(){t()}
method abort (line 26) | abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.abort...
method constructor (line 26) | constructor(t){super(t),this.fill(0)}
method create (line 26) | static create(t){let r=la(t);if(!r)return[];e.#l=!0;let i=new e(t,r);ret...
method constructor (line 26) | constructor(t,r){if(!e.#l)throw new TypeError("instantiate Stack using S...
method push (line 26) | push(t){this.heap[this.length++]=t}
method pop (line 26) | pop(){return this.heap[--this.length]}
method unsafeExposeInternals (line 26) | static unsafeExposeInternals(t){return{starts:t.#R,ttls:t.#y,sizes:t.#v,...
method max (line 26) | get max(){return this.#l}
method maxSize (line 26) | get maxSize(){return this.#p}
method calculatedSize (line 26) | get calculatedSize(){return this.#g}
method size (line 26) | get size(){return this.#i}
method fetchMethod (line 26) | get fetchMethod(){return this.#O}
method dispose (line 26) | get dispose(){return this.#b}
method disposeAfter (line 26) | get disposeAfter(){return this.#h}
method constructor (line 26) | constructor(t){let{max:r=0,ttl:i,ttlResolution:s=1,ttlAutopurge:a,update...
method getRemainingTTL (line 26) | getRemainingTTL(t){return this.#s.has(t)?1/0:0}
method #q (line 26) | #q(){let t=new qe(this.#l),r=new qe(this.#l);this.#y=t,this.#R=r,this.#k...
method #M (line 26) | #M(){let t=new qe(this.#l);this.#g=0,this.#v=t,this.#C=r=>{this.#g-=t[r]...
method #E (line 26) | *#E({allowStale:t=this.allowStale}={}){if(this.#i)for(let r=this.#n;!(!t...
method #x (line 26) | *#x({allowStale:t=this.allowStale}={}){if(this.#i)for(let r=this.#o;!(!t...
method #I (line 26) | #I(t){return t!==void 0&&this.#s.get(this.#r[t])===t}
method entries (line 26) | *entries(){for(let t of this.#E())this.#e[t]!==void 0&&this.#r[t]!==void...
method rentries (line 26) | *rentries(){for(let t of this.#x())this.#e[t]!==void 0&&this.#r[t]!==voi...
method keys (line 26) | *keys(){for(let t of this.#E()){let r=this.#r[t];r!==void 0&&!this.#t(th...
method rkeys (line 26) | *rkeys(){for(let t of this.#x()){let r=this.#r[t];r!==void 0&&!this.#t(t...
method values (line 26) | *values(){for(let t of this.#E())this.#e[t]!==void 0&&!this.#t(this.#e[t...
method rvalues (line 26) | *rvalues(){for(let t of this.#x())this.#e[t]!==void 0&&!this.#t(this.#e[...
method [Symbol.iterator] (line 26) | [Symbol.iterator](){return this.entries()}
method find (line 26) | find(t,r={}){for(let i of this.#E()){let s=this.#e[i],a=this.#t(s)?s.__s...
method forEach (line 26) | forEach(t,r=this){for(let i of this.#E()){let s=this.#e[i],a=this.#t(s)?...
method rforEach (line 26) | rforEach(t,r=this){for(let i of this.#x()){let s=this.#e[i],a=this.#t(s)...
method purgeStale (line 26) | purgeStale(){let t=!1;for(let r of this.#x({allowStale:!0}))this.#d(r)&&...
method dump (line 26) | dump(){let t=[];for(let r of this.#E({allowStale:!0})){let i=this.#r[r],...
method load (line 26) | load(t){this.clear();for(let[r,i]of t){if(i.start){let s=Date.now()-i.st...
method set (line 26) | set(t,r,i={}){if(r===void 0)return this.delete(t),this;let{ttl:s=this.tt...
method pop (line 26) | pop(){try{for(;this.#i;){let t=this.#e[this.#o];if(this.#L(!0),this.#t(t...
method #L (line 26) | #L(t){let r=this.#o,i=this.#r[r],s=this.#e[r];return this.#S&&this.#t(s)...
method has (line 26) | has(t,r={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=r,a=this....
method peek (line 26) | peek(t,r={}){let{allowStale:i=this.allowStale}=r,s=this.#s.get(t);if(s!=...
method #N (line 26) | #N(t,r,i,s){let a=r===void 0?void 0:this.#e[r];if(this.#t(a))return a;le...
method #t (line 26) | #t(t){if(!this.#S)return!1;let r=t;return!!r&&r instanceof Promise&&r.ha...
method fetch (line 26) | async fetch(t,r={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=th...
method get (line 26) | get(t,r={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updat...
method #H (line 26) | #H(t,r){this.#f[r]=t,this.#c[t]=r}
method #_ (line 26) | #_(t){t!==this.#n&&(t===this.#o?this.#o=this.#c[t]:this.#H(this.#f[t],th...
method delete (line 26) | delete(t){let r=!1;if(this.#i!==0){let i=this.#s.get(t);if(i!==void 0)if...
method clear (line 26) | clear(){for(let t of this.#x({allowStale:!0})){let r=this.#e[t];if(this....
method constructor (line 26) | constructor({cacheNull:t=!0,...r}){super(r),(0,ke.default)(!("maxAge"in ...
method memoize (line 26) | async memoize(t,r){(0,ke.default)(typeof r=="function"),this.has(t)||thi...
method memoizeSync (line 26) | memoizeSync(t,r){if((0,ke.default)(typeof r=="function"),!this.has(t)){l...
function ua (line 26) | async function ua(e){return await we(e,async({key:t,value:r},i)=>(typeof...
function pa (line 26) | async function pa(e){return await we(e,async({key:t,value:r},i)=>(typeof...
function Lu (line 26) | async function Lu({shield:e,shieldRule:t,globPattern:r,matcher:i,context...
function fa (line 26) | async function fa({shield:e,paths:t,context:r}){let i={canAccess:!0,reas...
function da (line 26) | function da({paths:e,context:t,fieldsMapping:r}){let i=0,s=[];if(!W(r))f...
function Zr (line 26) | async function Zr({when:e,hooks:t,prismaClient:r,QueryParams:i,result:s}...
function ha (line 26) | async function ha({callerUuid:e,maxReqPerMinute:t}){let r=!1,i=-1;try{i=...
function ma (line 26) | async function ma(e,t,r){if(W(e?.info?.fieldName)||Ye(e?.info?.selection...
function ga (line 26) | async function ga(e){return await we(e,async({key:t,value:r},i)=>{if(t==...
function Nu (line 26) | function Nu({appsyncEvent:e}){let t=null,r=null;if(W(e?.identity))t="API...
function qu (line 26) | function qu({customResolvers:e,operation:t,options:r}){let i={action:"",...
function ku (line 26) | function ku({fieldName:e}){let t=e;if(!(t.length>0))throw new N("Error p...
function Du (line 26) | function Du({operation:e}){let r=Object.keys(Kr).sort().reverse().find(i...
function Iu (line 26) | function Iu({action:e}){let t=null;for(let r in Ne)if(Ne[r].includes(e))...
function Hu (line 26) | function Hu({operation:e,action:t,options:r}){let i=e.replace(String(t),...
function Mu (line 26) | function Mu({_selectionSetList:e}){let t=[];return e.forEach(r=>{let i=r...
function Uu (line 26) | function Uu({_parentTypeName:e}){let t=e;if(!["Query","Mutation","Subscr...
function Fu (line 26) | function Fu({action:e,defaultPagination:t,_arguments:r,_selectionSetList...
function ya (line 26) | function ya(e){if(Object.keys(e).length>1)throw new N("Wrong 'orderBy' i...
function $u (line 26) | function $u(e){let t=[];return(Array.isArray(e)?e:[e]).forEach(i=>{t.pus...
function Bu (line 26) | function Bu(e){let t=e[0],r=e.length>1?Jr(e.splice(1)):!0;return{include...
function Jr (line 26) | function Jr(e){let t=e[0],r=e.length>1?Jr(e.splice(1)):!0;return{select:...
function zu (line 26) | function zu(e){let t={select:{}};for(let r=0;r<e.length;r++){let s=e[r]....
function Gu (line 26) | function Gu({operation:e,context:t,prismaArgs:r}){let i=[e,...Qn({...r?....
function ee (line 26) | function ee(e,t){let r={};return t.forEach(i=>{e.forEach(s=>{s?.[i]&&(i=...
function Vu (line 26) | async function Vu(e,t){return t.context.model===null?void 0:await e[t.co...
function ju (line 26) | async function ju(e,t){return t.context.model===null?void 0:await e[t.co...
function Qu (line 26) | async function Qu(e,t){return t.context.model===null?void 0:await e[t.co...
function Ku (line 26) | async function Ku(e,t){return t.context.model===null?void 0:await e[t.co...
function Wu (line 26) | async function Wu(e,t){return t.context.model===null?void 0:await e[t.co...
function Yu (line 26) | async function Yu(e,t){return t.context.model===null?void 0:await e[t.co...
function Xu (line 26) | async function Xu(e,t){return t.context.model===null?void 0:await e[t.co...
function Zu (line 26) | async function Zu(e,t){return t.context.model===null?void 0:await e[t.co...
function Ju (line 26) | async function Ju(e,t){return t.context.model===null?void 0:await e[t.co...
function ep (line 26) | async function ep(e,t){return t.context.model===null?void 0:await e[t.co...
method constructor (line 27) | constructor(t){if(typeof t?.connectionString<"u"&&(process.env.DATABASE_...
method resolve (line 27) | async resolve(t){let r=null;try{G("Resolving API request w/ event (trunc...
FILE: tests/generator/schemas/generated/@gql/client/inspector.d.ts
type ErrorExtensions (line 8) | type ErrorExtensions = {
type ErrorDetails (line 12) | type ErrorDetails = {
class CustomError (line 18) | class CustomError extends Error {
FILE: tests/generator/schemas/generated/@gql/client/types.d.ts
type logLevel (line 4) | type logLevel = 'INFO' | 'WARN' | 'ERROR';
type PrismaAppSyncOptionsType (line 5) | type PrismaAppSyncOptionsType = {
type Options (line 13) | type Options = Required<PrismaAppSyncOptionsType> & {
type InjectedConfig (line 17) | type InjectedConfig = {
type RuntimeConfig (line 33) | type RuntimeConfig = {
type Action (line 49) | type Action = typeof Actions[keyof typeof Actions] | string;
type ActionsAlias (line 50) | type ActionsAlias = typeof ActionsAliases[keyof typeof ActionsAliases] |...
type ActionsAliasStr (line 51) | type ActionsAliasStr = keyof typeof ActionsAliases;
type Context (line 52) | type Context = {
type Model (line 57) | type Model = {
type QueryParams (line 84) | type QueryParams<T = any> = {
type Authorization (line 96) | type Authorization = typeof Authorizations[keyof typeof Authorizations] ...
type PrismaGet (line 97) | type PrismaGet = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArgs, ...
type PrismaList (line 98) | type PrismaList = Pick<PrismaArgs, 'where' | 'orderBy' | 'select' | 'ski...
type PrismaCount (line 99) | type PrismaCount = Pick<PrismaArgs, 'where' | 'orderBy' | 'select' | 'sk...
type PrismaCreate (line 100) | type PrismaCreate = Pick<Required<PrismaArgs>, 'data'> & Pick<PrismaArgs...
type PrismaCreateMany (line 101) | type PrismaCreateMany = Pick<Required<PrismaArgs>, 'data'> & Pick<Prisma...
type PrismaUpdate (line 102) | type PrismaUpdate = Pick<Required<PrismaArgs>, 'data' | 'where'> & Pick<...
type PrismaUpdateMany (line 103) | type PrismaUpdateMany = Pick<Required<PrismaArgs>, 'data' | 'where'>;
type PrismaUpsert (line 104) | type PrismaUpsert = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArg...
type PrismaDelete (line 105) | type PrismaDelete = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArg...
type PrismaDeleteMany (line 106) | type PrismaDeleteMany = Pick<Required<PrismaArgs>, 'where'>;
type QueryBuilder (line 107) | type QueryBuilder = {
type QueryParamsCustom (line 119) | type QueryParamsCustom<T = any> = QueryParams<T> & {
type BeforeHookParams (line 122) | type BeforeHookParams = QueryParams & {
type AfterHookParams (line 147) | type AfterHookParams = QueryParams & {
type ShieldContext (line 151) | type ShieldContext = {
type Reason (line 155) | type Reason = string | ((context: ShieldContext) => string);
type ShieldRule (line 156) | type ShieldRule = boolean | ((context: ShieldContext) => boolean | Promi...
type Shield (line 157) | type Shield = {
type HooksProps (line 163) | type HooksProps = {
type HooksReturn (line 167) | type HooksReturn = {
type HookPath (line 171) | type HookPath<Operations extends string, CustomResolvers> = Operations |...
type HooksParameter (line 172) | type HooksParameter<HookType extends 'before' | 'after', Operations exte...
type HooksParameters (line 173) | type HooksParameters<HookType extends 'before' | 'after', Operations ext...
type Hooks (line 176) | type Hooks<Operations extends string, CustomResolvers extends string> = ...
type ShieldAuthorization (line 177) | type ShieldAuthorization = {
type ResolveParams (line 184) | type ResolveParams<Operations extends string, CustomResolvers extends st...
type PrismaArgs (line 193) | type PrismaArgs = {
type PrismaOperator (line 204) | type PrismaOperator = keyof Required<PrismaArgs>;
type AppSyncEvent (line 205) | type AppSyncEvent = AppSyncResolverEvent<any>;
type GraphQLType (line 206) | type GraphQLType = 'Query' | 'Mutation' | 'Subscription';
type API_KEY (line 207) | type API_KEY = null | {
type AWS_LAMBDA (line 210) | type AWS_LAMBDA = AppSyncIdentityLambda;
type AWS_IAM (line 211) | type AWS_IAM = AppSyncIdentityIAM;
type AMAZON_COGNITO_USER_POOLS (line 212) | type AMAZON_COGNITO_USER_POOLS = AppSyncIdentityCognito;
type OPENID_CONNECT (line 213) | type OPENID_CONNECT = AppSyncIdentityOIDC;
type Identity (line 214) | type Identity = API_KEY | AWS_LAMBDA | AWS_IAM | AMAZON_COGNITO_USER_POO...
FILE: tests/generator/schemas/generated/@gql/client/utils.d.ts
class WalkNode (line 168) | class WalkNode {
FILE: tests/generator/schemas/generated/crud/client/consts.d.ts
type Actions (line 1) | enum Actions {
type ActionsAliases (line 22) | enum ActionsAliases {
type Authorizations (line 45) | enum Authorizations {
FILE: tests/generator/schemas/generated/crud/client/core.d.ts
class PrismaAppSync (line 20) | class PrismaAppSync {
FILE: tests/generator/schemas/generated/crud/client/index.js
function el (line 11) | function el(e,t,r){let i=br(e,t,"-",!1,r)||[],s=br(t,e,"",!1,r)||[],a=br...
function tl (line 11) | function tl(e,t){let r=1,i=1,s=Zi(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a...
function rl (line 11) | function rl(e,t,r){if(e===t)return{pattern:e,count:[],digits:0};let i=il...
function Yi (line 11) | function Yi(e,t,r,i){let s=tl(e,t),a=[],o=e,l;for(let c=0;c<s.length;c++...
function br (line 11) | function br(e,t,r,i,s){let a=[];for(let o of e){let{string:l}=o;!i&&!Xi(...
function il (line 11) | function il(e,t){let r=[];for(let i=0;i<e.length;i++)r.push([e[i],t[i]])...
function sl (line 11) | function sl(e,t){return e>t?1:t>e?-1:0}
function Xi (line 11) | function Xi(e,t,r){return e.some(i=>i[t]===r)}
function Zi (line 11) | function Zi(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}
function Ji (line 11) | function Ji(e,t){return e-e%Math.pow(10,t)}
function es (line 11) | function es(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}
function nl (line 11) | function nl(e,t,r){return`[${e}${t-e===1?"":"-"}${t}]`}
function ts (line 11) | function ts(e){return/^-?(0+)\d/.test(e)}
function al (line 11) | function al(e,t,r){if(!t.isPadded)return e;let i=Math.abs(t.maxLen-Strin...
method extglobChars (line 17) | extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e....
method globChars (line 17) | globChars(e){return e===!0?Vl:Ls}
function gc (line 17) | function gc(e){return!!e&&typeof e=="object"}
function mc (line 17) | function mc(e){var t=Object.prototype.toString.call(e);return t==="[obje...
function Ac (line 17) | function Ac(e){return e.$$typeof===bc}
function Ec (line 17) | function Ec(e){return Array.isArray(e)?[]:{}}
function Ve (line 17) | function Ve(e,t){return t.clone!==!1&&t.isMergeableObject(e)?Ce(Ec(e),e,...
function xc (line 17) | function xc(e,t,r){return e.concat(t).map(function(i){return Ve(i,r)})}
function vc (line 17) | function vc(e,t){if(!t.customMerge)return Ce;var r=t.customMerge(e);retu...
function Rc (line 17) | function Rc(e){return Object.getOwnPropertySymbols?Object.getOwnProperty...
function Js (line 17) | function Js(e){return Object.keys(e).concat(Rc(e))}
function en (line 17) | function en(e,t){try{return t in e}catch{return!1}}
function wc (line 17) | function wc(e,t){return en(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Obje...
function Sc (line 17) | function Sc(e,t,r){var i={};return r.isMergeableObject(e)&&Js(e).forEach...
function Ce (line 17) | function Ce(e,t,r){r=r||{},r.arrayMerge=r.arrayMerge||xc,r.isMergeableOb...
function Oc (line 18) | function Oc(e,t){var p=t===void 0?_c:t,d=p.mode,r=d===void 0?"specialCha...
function qc (line 18) | function qc(e,t){var i=(t===void 0?Nc:t).level,r=i===void 0?"all":i;if(!...
function kc (line 18) | function kc(e,t){var r=t===void 0?Pc:t,i=r.level,s=i===void 0?"all":i,a=...
function pn (line 18) | function pn(){var e={};return e["align-content"]=!1,e["align-items"]=!1,...
function Dc (line 18) | function Dc(e,t,r){}
function Ic (line 18) | function Ic(e,t,r){}
function Mc (line 18) | function Mc(e,t){return Hc.test(t)?"":t}
function Uc (line 18) | function Uc(e,t){e=Qe.trimRight(e),e[e.length-1]!==";"&&(e+=";");var r=e...
function gn (line 19) | function gn(e){return e==null}
function $c (line 19) | function $c(e){var t={};for(var r in e)t[r]=e[r];return t}
function mn (line 19) | function mn(e){e=$c(e||{}),e.whiteList=e.whiteList||xt.whiteList,e.onAtt...
function Bc (line 19) | function Bc(e,t){var r=new En(t);return r.process(e)}
function wn (line 19) | function wn(){return{a:["target","href","title"],abbr:["title"],address:...
function Vc (line 19) | function Vc(e,t,r){}
function jc (line 19) | function jc(e,t,r){}
function Qc (line 19) | function Qc(e,t,r){}
function Kc (line 19) | function Kc(e,t,r){}
function Tn (line 19) | function Tn(e){return e.replace(Yc,"<").replace(Xc,">")}
function Wc (line 19) | function Wc(e,t,r,i){if(r=Nn(r),t==="href"||t==="src"){if(r=Tt.trim(r),r...
function Cn (line 19) | function Cn(e){return e.replace(Zc,""")}
function _n (line 19) | function _n(e){return e.replace(Jc,'"')}
function On (line 19) | function On(e){return e.replace(eu,n(function(r,i){return i[0]==="x"||i[...
function Pn (line 19) | function Pn(e){return e.replace(tu,":").replace(ru," ")}
function Ln (line 19) | function Ln(e){for(var t="",r=0,i=e.length;r<i;r++)t+=e.charCodeAt(r)<32...
function Nn (line 19) | function Nn(e){return e=_n(e),e=On(e),e=Pn(e),e=Ln(e),e}
function qn (line 19) | function qn(e){return e=Cn(e),e=Tn(e),e}
function iu (line 19) | function iu(){return""}
function su (line 19) | function su(e,t){typeof t!="function"&&(t=n(function(){},"next"));var r=...
function nu (line 19) | function nu(e){for(var t="",r=0;r<e.length;){var i=e.indexOf("<!--",r);i...
function au (line 19) | function au(e){var t=e.split("");return t=t.filter(function(r){var i=r.c...
function ou (line 19) | function ou(e){var t=me.spaceIndex(e),r;return t===-1?r=e.slice(1,-1):r=...
function lu (line 19) | function lu(e){return e.slice(0,2)==="</"}
function cu (line 19) | function cu(e,t,r){"use strict";var i="",s=0,a=!1,o=!1,l=0,c=e.length,u=...
function pu (line 19) | function pu(e,t){"use strict";var r=0,i=0,s=[],a=!1,o=e.length;function ...
function fu (line 19) | function fu(e,t){for(;t<e.length;t++){var r=e[t];if(r!==" ")return r==="...
function du (line 19) | function du(e,t){for(;t<e.length;t++){var r=e[t];if(r!==" ")return r==="...
function hu (line 19) | function hu(e,t){for(;t>0;t--){var r=e[t];if(r!==" ")return r==="="?t:-1}}
function gu (line 19) | function gu(e){return e[0]==='"'&&e[e.length-1]==='"'||e[0]==="'"&&e[e.l...
function kn (line 19) | function kn(e){return gu(e)?e.substr(1,e.length-2):e}
function Ct (line 19) | function Ct(e){return e==null}
function Au (line 19) | function Au(e){var t=_t.spaceIndex(e);if(t===-1)return{html:"",closing:e...
function Eu (line 19) | function Eu(e){var t={};for(var r in e)t[r]=e[r];return t}
function xu (line 19) | function xu(e){var t={};for(var r in e)Array.isArray(e[r])?t[r.toLowerCa...
function In (line 19) | function In(e){e=Eu(e||{}),e.stripIgnoreTag&&(e.onIgnoreTag&&console.err...
function Bn (line 19) | function Bn(e,t){var r=new $n(t);return r.process(e)}
function vu (line 19) | function vu(){return typeof self<"u"&&typeof DedicatedWorkerGlobalScope<...
function pr (line 26) | function pr(e){if(typeof e!="object"||e===null)return!1;let t=Object.get...
function pe (line 26) | function pe(...e){return Qr.default.all([{},...e])}
function Re (line 26) | function Re(e){return(0,Qr.default)({},e)}
function Nt (line 26) | function Nt(e){return(0,Lt.decode)(e)}
function qt (line 26) | function qt(e){return(0,Lt.encode)(e)}
function jn (line 26) | function jn(e){return dt(e,{shallowArrays:!0})}
function Qn (line 26) | function Qn(e){let t=dt(e),r=Object.keys(t).map(a=>a.split(".").filter(o...
function Kn (line 26) | function Kn(e,t){let r=Re(e);return(Array.isArray(t)?t:[t]).forEach(s=>d...
function We (line 26) | function We(e,t){return(0,Gn.isMatch)(e,t)}
function kt (line 26) | function kt(e){return(0,Vn.default)(e)}
function W (line 26) | function W(e){return e==null||typeof e>"u"||typeof e=="string"&&e.trim()...
function Ye (line 26) | function Ye(e){return e===void 0||typeof e>"u"}
function Dt (line 26) | function Dt(e){return e?e.charAt(0).toLowerCase()+e.slice(1):""}
function we (line 26) | async function we(e,t){return Array.isArray(e)?await Promise.all(e.map(a...
function Pt (line 26) | async function Pt(e,t,r=new Ke){let i=Re(e),s={},a=Object.keys(i);for(le...
method constructor (line 26) | constructor(t=[],r=!1){this._path=t;this._ignoreChildren=r;this._path=t,...
method ignoreChilds (line 26) | ignoreChilds(){this._ignoreChildren=!0}
method getPath (line 26) | getPath(){return this._path}
function Wn (line 26) | function Wn(e,t,r){let i=[],s={};for(let a=0;a<t.length;a++)i.push(t[a]....
function Xe (line 26) | function Xe(e){return Array.from(new Set(e))}
method constructor (line 26) | constructor(t,r){super(t),this.error=t,this.type=r.type,this.cause=r?.ca...
function sa (line 26) | function sa(e){return e instanceof N?e:new N(e.message,{type:"INTERNAL_S...
function G (line 26) | function G(e,t,r){Ft(r||"INFO")&&(_u(e,r||"INFO"),t&&console.log((0,ia.i...
function _u (line 26) | function _u(e,t){let s=[`\u25ED ${new Date().toLocaleString(void 0,{day:...
function Ft (line 26) | function Ft(e){if(process?.env?.PRISMA_APPSYNC_TESTING==="true")return!1...
method addEventListener (line 26) | addEventListener(i,s){this._onabort.push(s)}
method constructor (line 26) | constructor(){t()}
method abort (line 26) | abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.abort...
method constructor (line 26) | constructor(t){super(t),this.fill(0)}
method create (line 26) | static create(t){let r=la(t);if(!r)return[];e.#l=!0;let i=new e(t,r);ret...
method constructor (line 26) | constructor(t,r){if(!e.#l)throw new TypeError("instantiate Stack using S...
method push (line 26) | push(t){this.heap[this.length++]=t}
method pop (line 26) | pop(){return this.heap[--this.length]}
method unsafeExposeInternals (line 26) | static unsafeExposeInternals(t){return{starts:t.#R,ttls:t.#y,sizes:t.#v,...
method max (line 26) | get max(){return this.#l}
method maxSize (line 26) | get maxSize(){return this.#p}
method calculatedSize (line 26) | get calculatedSize(){return this.#g}
method size (line 26) | get size(){return this.#i}
method fetchMethod (line 26) | get fetchMethod(){return this.#O}
method dispose (line 26) | get dispose(){return this.#b}
method disposeAfter (line 26) | get disposeAfter(){return this.#h}
method constructor (line 26) | constructor(t){let{max:r=0,ttl:i,ttlResolution:s=1,ttlAutopurge:a,update...
method getRemainingTTL (line 26) | getRemainingTTL(t){return this.#s.has(t)?1/0:0}
method #q (line 26) | #q(){let t=new qe(this.#l),r=new qe(this.#l);this.#y=t,this.#R=r,this.#k...
method #M (line 26) | #M(){let t=new qe(this.#l);this.#g=0,this.#v=t,this.#C=r=>{this.#g-=t[r]...
method #E (line 26) | *#E({allowStale:t=this.allowStale}={}){if(this.#i)for(let r=this.#n;!(!t...
method #x (line 26) | *#x({allowStale:t=this.allowStale}={}){if(this.#i)for(let r=this.#o;!(!t...
method #I (line 26) | #I(t){return t!==void 0&&this.#s.get(this.#r[t])===t}
method entries (line 26) | *entries(){for(let t of this.#E())this.#e[t]!==void 0&&this.#r[t]!==void...
method rentries (line 26) | *rentries(){for(let t of this.#x())this.#e[t]!==void 0&&this.#r[t]!==voi...
method keys (line 26) | *keys(){for(let t of this.#E()){let r=this.#r[t];r!==void 0&&!this.#t(th...
method rkeys (line 26) | *rkeys(){for(let t of this.#x()){let r=this.#r[t];r!==void 0&&!this.#t(t...
method values (line 26) | *values(){for(let t of this.#E())this.#e[t]!==void 0&&!this.#t(this.#e[t...
method rvalues (line 26) | *rvalues(){for(let t of this.#x())this.#e[t]!==void 0&&!this.#t(this.#e[...
method [Symbol.iterator] (line 26) | [Symbol.iterator](){return this.entries()}
method find (line 26) | find(t,r={}){for(let i of this.#E()){let s=this.#e[i],a=this.#t(s)?s.__s...
method forEach (line 26) | forEach(t,r=this){for(let i of this.#E()){let s=this.#e[i],a=this.#t(s)?...
method rforEach (line 26) | rforEach(t,r=this){for(let i of this.#x()){let s=this.#e[i],a=this.#t(s)...
method purgeStale (line 26) | purgeStale(){let t=!1;for(let r of this.#x({allowStale:!0}))this.#d(r)&&...
method dump (line 26) | dump(){let t=[];for(let r of this.#E({allowStale:!0})){let i=this.#r[r],...
method load (line 26) | load(t){this.clear();for(let[r,i]of t){if(i.start){let s=Date.now()-i.st...
method set (line 26) | set(t,r,i={}){if(r===void 0)return this.delete(t),this;let{ttl:s=this.tt...
method pop (line 26) | pop(){try{for(;this.#i;){let t=this.#e[this.#o];if(this.#L(!0),this.#t(t...
method #L (line 26) | #L(t){let r=this.#o,i=this.#r[r],s=this.#e[r];return this.#S&&this.#t(s)...
method has (line 26) | has(t,r={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=r,a=this....
method peek (line 26) | peek(t,r={}){let{allowStale:i=this.allowStale}=r,s=this.#s.get(t);if(s!=...
method #N (line 26) | #N(t,r,i,s){let a=r===void 0?void 0:this.#e[r];if(this.#t(a))return a;le...
method #t (line 26) | #t(t){if(!this.#S)return!1;let r=t;return!!r&&r instanceof Promise&&r.ha...
method fetch (line 26) | async fetch(t,r={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=th...
method get (line 26) | get(t,r={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updat...
method #H (line 26) | #H(t,r){this.#f[r]=t,this.#c[t]=r}
method #_ (line 26) | #_(t){t!==this.#n&&(t===this.#o?this.#o=this.#c[t]:this.#H(this.#f[t],th...
method delete (line 26) | delete(t){let r=!1;if(this.#i!==0){let i=this.#s.get(t);if(i!==void 0)if...
method clear (line 26) | clear(){for(let t of this.#x({allowStale:!0})){let r=this.#e[t];if(this....
method constructor (line 26) | constructor({cacheNull:t=!0,...r}){super(r),(0,ke.default)(!("maxAge"in ...
method memoize (line 26) | async memoize(t,r){(0,ke.default)(typeof r=="function"),this.has(t)||thi...
method memoizeSync (line 26) | memoizeSync(t,r){if((0,ke.default)(typeof r=="function"),!this.has(t)){l...
function ua (line 26) | async function ua(e){return await we(e,async({key:t,value:r},i)=>(typeof...
function pa (line 26) | async function pa(e){return await we(e,async({key:t,value:r},i)=>(typeof...
function Lu (line 26) | async function Lu({shield:e,shieldRule:t,globPattern:r,matcher:i,context...
function fa (line 26) | async function fa({shield:e,paths:t,context:r}){let i={canAccess:!0,reas...
function da (line 26) | function da({paths:e,context:t,fieldsMapping:r}){let i=0,s=[];if(!W(r))f...
function Zr (line 26) | async function Zr({when:e,hooks:t,prismaClient:r,QueryParams:i,result:s}...
function ha (line 26) | async function ha({callerUuid:e,maxReqPerMinute:t}){let r=!1,i=-1;try{i=...
function ma (line 26) | async function ma(e,t,r){if(W(e?.info?.fieldName)||Ye(e?.info?.selection...
function ga (line 26) | async function ga(e){return await we(e,async({key:t,value:r},i)=>{if(t==...
function Nu (line 26) | function Nu({appsyncEvent:e}){let t=null,r=null;if(W(e?.identity))t="API...
function qu (line 26) | function qu({customResolvers:e,operation:t,options:r}){let i={action:"",...
function ku (line 26) | function ku({fieldName:e}){let t=e;if(!(t.length>0))throw new N("Error p...
function Du (line 26) | function Du({operation:e}){let r=Object.keys(Kr).sort().reverse().find(i...
function Iu (line 26) | function Iu({action:e}){let t=null;for(let r in Ne)if(Ne[r].includes(e))...
function Hu (line 26) | function Hu({operation:e,action:t,options:r}){let i=e.replace(String(t),...
function Mu (line 26) | function Mu({_selectionSetList:e}){let t=[];return e.forEach(r=>{let i=r...
function Uu (line 26) | function Uu({_parentTypeName:e}){let t=e;if(!["Query","Mutation","Subscr...
function Fu (line 26) | function Fu({action:e,defaultPagination:t,_arguments:r,_selectionSetList...
function ya (line 26) | function ya(e){if(Object.keys(e).length>1)throw new N("Wrong 'orderBy' i...
function $u (line 26) | function $u(e){let t=[];return(Array.isArray(e)?e:[e]).forEach(i=>{t.pus...
function Bu (line 26) | function Bu(e){let t=e[0],r=e.length>1?Jr(e.splice(1)):!0;return{include...
function Jr (line 26) | function Jr(e){let t=e[0],r=e.length>1?Jr(e.splice(1)):!0;return{select:...
function zu (line 26) | function zu(e){let t={select:{}};for(let r=0;r<e.length;r++){let s=e[r]....
function Gu (line 26) | function Gu({operation:e,context:t,prismaArgs:r}){let i=[e,...Qn({...r?....
function ee (line 26) | function ee(e,t){let r={};return t.forEach(i=>{e.forEach(s=>{s?.[i]&&(i=...
function Vu (line 26) | async function Vu(e,t){return t.context.model===null?void 0:await e[t.co...
function ju (line 26) | async function ju(e,t){return t.context.model===null?void 0:await e[t.co...
function Qu (line 26) | async function Qu(e,t){return t.context.model===null?void 0:await e[t.co...
function Ku (line 26) | async function Ku(e,t){return t.context.model===null?void 0:await e[t.co...
function Wu (line 26) | async function Wu(e,t){return t.context.model===null?void 0:await e[t.co...
function Yu (line 26) | async function Yu(e,t){return t.context.model===null?void 0:await e[t.co...
function Xu (line 26) | async function Xu(e,t){return t.context.model===null?void 0:await e[t.co...
function Zu (line 26) | async function Zu(e,t){return t.context.model===null?void 0:await e[t.co...
function Ju (line 26) | async function Ju(e,t){return t.context.model===null?void 0:await e[t.co...
function ep (line 26) | async function ep(e,t){return t.context.model===null?void 0:await e[t.co...
method constructor (line 27) | constructor(t){if(typeof t?.connectionString<"u"&&(process.env.DATABASE_...
method resolve (line 27) | async resolve(t){let r=null;try{G("Resolving API request w/ event (trunc...
FILE: tests/generator/schemas/generated/crud/client/inspector.d.ts
type ErrorExtensions (line 8) | type ErrorExtensions = {
type ErrorDetails (line 12) | type ErrorDetails = {
class CustomError (line 18) | class CustomError extends Error {
FILE: tests/generator/schemas/generated/crud/client/types.d.ts
type logLevel (line 4) | type logLevel = 'INFO' | 'WARN' | 'ERROR';
type PrismaAppSyncOptionsType (line 5) | type PrismaAppSyncOptionsType = {
type Options (line 13) | type Options = Required<PrismaAppSyncOptionsType> & {
type InjectedConfig (line 17) | type InjectedConfig = {
type RuntimeConfig (line 33) | type RuntimeConfig = {
type Action (line 49) | type Action = typeof Actions[keyof typeof Actions] | string;
type ActionsAlias (line 50) | type ActionsAlias = typeof ActionsAliases[keyof typeof ActionsAliases] |...
type ActionsAliasStr (line 51) | type ActionsAliasStr = keyof typeof ActionsAliases;
type Context (line 52) | type Context = {
type Model (line 57) | type Model = {
type QueryParams (line 84) | type QueryParams<T = any> = {
type Authorization (line 96) | type Authorization = typeof Authorizations[keyof typeof Authorizations] ...
type PrismaGet (line 97) | type PrismaGet = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArgs, ...
type PrismaList (line 98) | type PrismaList = Pick<PrismaArgs, 'where' | 'orderBy' | 'select' | 'ski...
type PrismaCount (line 99) | type PrismaCount = Pick<PrismaArgs, 'where' | 'orderBy' | 'select' | 'sk...
type PrismaCreate (line 100) | type PrismaCreate = Pick<Required<PrismaArgs>, 'data'> & Pick<PrismaArgs...
type PrismaCreateMany (line 101) | type PrismaCreateMany = Pick<Required<PrismaArgs>, 'data'> & Pick<Prisma...
type PrismaUpdate (line 102) | type PrismaUpdate = Pick<Required<PrismaArgs>, 'data' | 'where'> & Pick<...
type PrismaUpdateMany (line 103) | type PrismaUpdateMany = Pick<Required<PrismaArgs>, 'data' | 'where'>;
type PrismaUpsert (line 104) | type PrismaUpsert = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArg...
type PrismaDelete (line 105) | type PrismaDelete = Pick<Required<PrismaArgs>, 'where'> & Pick<PrismaArg...
type PrismaDeleteMany (line 106) | type PrismaDeleteMany = Pick<Required<PrismaArgs>, 'where'>;
type QueryBuilder (line 107) | type QueryBuilder = {
type QueryParamsCustom (line 119) | type QueryParamsCustom<T = any> = QueryParams<T> & {
type BeforeHookParams (line 122) | type BeforeHookParams = QueryParams & {
type AfterHookParams (line 147) | type AfterHookParams = QueryParams & {
type ShieldContext (line 151) | type ShieldContext = {
type Reason (line 155) | type Reason = string | ((context: ShieldContext) => string);
type ShieldRule (line 156) | type ShieldRule = boolean | ((context: ShieldContext) => boolean | Promi...
type Shield (line 157) | type Shield = {
type HooksProps (line 163) | type HooksProps = {
type HooksReturn (line 167) | type HooksReturn = {
type HookPath (line 171) | type HookPath<Operations extends string, CustomResolvers> = Operations |...
type HooksParameter (line 172) | type HooksParameter<HookType extends 'before' | 'after', Operations exte...
type HooksParameters (line 173) | type HooksParameters<HookType extends 'before' | 'after', Operations ext...
type Hooks (line 176) | type Hooks<Operations extends string, CustomResolvers extends string> = ...
type ShieldAuthorization (line 177) | type ShieldAuthorization = {
type ResolveParams (line 184) | type ResolveParams<Operations extends string, CustomResolvers extends st...
type PrismaArgs (line 193) | type PrismaArgs = {
type PrismaOperator (line 204) | type PrismaOperator = keyof Required<PrismaArgs>;
type AppSyncEvent (line 205) | type AppSyncEvent = AppSyncResolverEvent<any>;
type GraphQLType (line 206) | type GraphQLType = 'Query' | 'Mutation' | 'Subscription';
type API_KEY (line 207) | type API_KEY = null | {
type AWS_LAMBDA (line 210) | type AWS_LAMBDA = AppSyncIdentityLambda;
type AWS_IAM (line 211) | type AWS_IAM = AppSyncIdentityIAM;
type AMAZON_COGNITO_USER_POOLS (line 212) | type AMAZON_COGNITO_USER_POOLS = AppSyncIdentityCognito;
type OPENID_CONNECT (line 213) | type OPENID_CONNECT = AppSyncIdentityOIDC;
type Identity (line 214) | type Identity = API_KEY | AWS_LAMBDA | AWS_IAM | AMAZON_COGNITO_USER_POO...
FILE: tests/generator/schemas/generated/crud/client/utils.d.ts
class WalkNode (line 168) | class WalkNode {
Condensed preview — 136 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,116K chars).
[
{
"path": ".all-contributorsrc",
"chars": 2911,
"preview": "{\n \"projectName\": \"prisma-appsync\",\n \"projectOwner\": \"maoosi\",\n \"repoType\": \"github\",\n \"repoHost\": \"https://github.c"
},
{
"path": ".eslintrc",
"chars": 621,
"preview": "{\n \"root\": true,\n \"extends\": \"@antfu\",\n \"rules\": {\n \"jsonc/indent\": [\"error\", 4, {}],\n \"@typescri"
},
{
"path": ".github/FUNDING.yml",
"chars": 17,
"preview": "github: [maoosi]\n"
},
{
"path": ".github/workflows/unit-tests.yml",
"chars": 607,
"preview": "name: Unit Tests\n\non:\n pull_request:\n branches: [main]\n\njobs:\n test:\n runs-on: ubuntu-latest\n\n steps:\n -"
},
{
"path": ".gitignore",
"chars": 398,
"preview": ".DS_Store\nnode_modules/\n*/**/node_modules/\n\n# Testing\ntests/prisma/generated/\nplayground/\ndebug/\n\n# Dist folder\ndist/\n\n#"
},
{
"path": ".markdownlint.json",
"chars": 117,
"preview": "{\n \"default\": true,\n \"no-inline-html\": false,\n \"line-length\": false,\n \"no-trailing-punctuation\": false\n}\n"
},
{
"path": ".npmignore",
"chars": 481,
"preview": ".DS_Store\nnode_modules/\n*/**/node_modules/\n\n# Source files\npackages/\nbin/\ntests/\nplayground/\ndocs/\n.editorconfig\n.eslint"
},
{
"path": ".vscode/extensions.json",
"chars": 103,
"preview": "{\n \"recommendations\": [\n \"dbaeumer.vscode-eslint\",\n \"johnpapa.vscode-peacock\"\n ]\n}\n"
},
{
"path": ".vscode/settings.json",
"chars": 3831,
"preview": "{\n // Visuals\n \"peacock.color\": \"#1f08f6\",\n \"workbench.colorCustomizations\": {\n \"activityBar.activeBackg"
},
{
"path": "CHANGELOG.md",
"chars": 97,
"preview": "# Changelog\n\n[prisma-appsync.vercel.app/changelog/](https://prisma-appsync.vercel.app/changelog/)"
},
{
"path": "CONTRIBUTING.md",
"chars": 114,
"preview": "# Contributing\n\n[prisma-appsync.vercel.app/contributing.html](https://prisma-appsync.vercel.app/contributing.html)"
},
{
"path": "LICENSE.txt",
"chars": 1346,
"preview": "BSD 2-Clause License\n\nCopyright (c) 2024, Sylvain Simao <hello@sylvainsimao.fr>\nAll rights reserved.\n\nRedistribution and"
},
{
"path": "README.md",
"chars": 6394,
"preview": "<p align=\"center\">\n <img width=\"250\" height=\"250\" src=\"https://prisma-appsync.vercel.app/logo.png\" alt=\"Prisma-AppSyn"
},
{
"path": "bin/build.mjs",
"chars": 3163,
"preview": "#!/usr/bin/env zx\n/* eslint-disable no-console */\n/* eslint-disable n/prefer-global/process */\nimport './env.mjs'\n\ntry {"
},
{
"path": "bin/cleans.mjs",
"chars": 986,
"preview": "#!/usr/bin/env zx\nimport './env.mjs'\n\nconsole.log(chalk.blue('\\n🧹 [chore] deleting all `node_modules` folders\\n'))\nawait"
},
{
"path": "bin/dev.mjs",
"chars": 866,
"preview": "#!/usr/bin/env zx\nimport './env.mjs'\n\n// path\nconst playgroundPath = 'playground'\n\n// reset\nif (argv?.reset) {\n conso"
},
{
"path": "bin/env.mjs",
"chars": 47,
"preview": "#!/usr/bin/env zx\n\nprocess.env.FORCE_COLOR = 3\n"
},
{
"path": "bin/postinstall.mjs",
"chars": 353,
"preview": "#!/usr/bin/env zx\nimport './env.mjs'\n\n// set DATABASE_URL env variable to docker instance\nprocess.env.DATABASE_URL = 'po"
},
{
"path": "bin/publish/_pkg.core.cleanse.js",
"chars": 1227,
"preview": "const fs = require('fs')\nconst path = require('path')\n\n// Define absolute paths for original pkg and temporary pkg.\ncons"
},
{
"path": "bin/publish/_pkg.core.restore.js",
"chars": 922,
"preview": "const fs = require('fs')\nconst path = require('path')\n\n// Define absolute paths for original pkg and temporary pkg.\ncons"
},
{
"path": "bin/publish/_pkg.installer.cleanse.js",
"chars": 833,
"preview": "const fs = require('fs')\nconst path = require('path')\n\n// Define absolute paths for original pkg and temporary pkg.\ncons"
},
{
"path": "bin/publish.mjs",
"chars": 3747,
"preview": "#!/usr/bin/env zx\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\nimport Listr from 'listr'\nimport prompts from "
},
{
"path": "bin/test.mjs",
"chars": 462,
"preview": "#!/usr/bin/env zx\n/* eslint-disable no-console */\nimport './env.mjs'\n\n// build\nawait $`zx bin/build.mjs`\n\n// prisma clie"
},
{
"path": "docs/.vitepress/config.ts",
"chars": 3773,
"preview": "export default {\n title: 'Prisma-AppSync',\n description: 'GraphQL API Generator for AWS and ◭ Prisma',\n\n head: "
},
{
"path": "docs/.vitepress/theme/Layout.vue",
"chars": 820,
"preview": "<script setup>\nimport DefaultTheme from 'vitepress/theme'\n\nconst { Layout } = DefaultTheme\n</script>\n\n<template>\n <Layo"
},
{
"path": "docs/.vitepress/theme/index.ts",
"chars": 156,
"preview": "import Theme from 'vitepress/theme'\nimport Layout from './Layout.vue'\nimport './styles/vars.css'\n\nexport default {\n e"
},
{
"path": "docs/.vitepress/theme/styles/vars.css",
"chars": 1345,
"preview": "/**\n * Colors\n * -------------------------------------------------------------------------- */\n\n:root {\n --vp-c-brand"
},
{
"path": "docs/changelog/1.0.0-rc.1.md",
"chars": 13488,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.1\n\n::: warning BREAKING\n🚨 This release comes with a **major rewrite of the Prisma-Ap"
},
{
"path": "docs/changelog/1.0.0-rc.2.md",
"chars": 4102,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.2\n\n**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**\n\n## Majo"
},
{
"path": "docs/changelog/1.0.0-rc.3.md",
"chars": 1121,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.3\n\n**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**\n\n## Fixe"
},
{
"path": "docs/changelog/1.0.0-rc.4.md",
"chars": 4167,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.4\n\n**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**\n\n## High"
},
{
"path": "docs/changelog/1.0.0-rc.5.md",
"chars": 4419,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.5\n\n**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**\n\n## High"
},
{
"path": "docs/changelog/1.0.0-rc.6.md",
"chars": 7604,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.6\n\n**🌟 Help us spread the word about Prisma-AppSync by starring the repo.**\n\n> 🚨 Thi"
},
{
"path": "docs/changelog/1.0.0-rc.7.md",
"chars": 6104,
"preview": "---\neditLink: false\n---\n\n# 1.0.0-rc.7\n\n**🌟 Support Prisma-AppSync by Starring Our Repo!**\n\n## Highlights\n\n### Local Dev "
},
{
"path": "docs/changelog/1.0.0.md",
"chars": 6243,
"preview": "---\neditLink: false\n---\n\n# v1.0.0\n\n**Support Prisma-AppSync by Starring Our Repo!**\n\n## 🌟 Sponsor\n\n[\n- [1.0.0-rc.7](/changelog/1.0.0-rc.7.html)\n- [1.0.0-rc.6](/chang"
},
{
"path": "docs/contributing.md",
"chars": 4980,
"preview": "# Contributions guide\n\nThanks for your interest in contributing!\n\n## 👉 Discuss first\n\nBefore starting to work on a pull "
},
{
"path": "docs/features/gql-schema.md",
"chars": 1887,
"preview": "# Tweaking GraphQL Schema\n\nPrisma-AppSync provides ways to tweak and customise the GraphQL Schema output.\n\n## 👉 Models d"
},
{
"path": "docs/features/hooks.md",
"chars": 3563,
"preview": "# Lifecycle hooks\n\nHooks let you “hook into” Prisma-AppSync lifecycle to either trigger custom business logic or manipul"
},
{
"path": "docs/features/resolvers.md",
"chars": 2798,
"preview": "# Custom resolvers\n\nLet's assume we want to extend our GraphQL CRUD API and add a custom mutation `incrementPostsViews` "
},
{
"path": "docs/index.md",
"chars": 856,
"preview": "---\nlayout: home\n\nhero:\n name: Prisma-AppSync\n text: GQL API Generator for Prisma ORM\n tagline: Turns your Prisma Sch"
},
{
"path": "docs/quick-start/deploy.md",
"chars": 1125,
"preview": "# Deploy\n\n## 👉 1. Prepare your local machine\n\nMake sure to install the below on your local machine:\n\n- [AWS CLI](https:/"
},
{
"path": "docs/quick-start/getting-started.md",
"chars": 2089,
"preview": "# Getting started\n\n**Prisma-AppSync** seamlessly transforms your [Prisma Schema](https://www.prisma.io) into a comprehen"
},
{
"path": "docs/quick-start/installation.md",
"chars": 1532,
"preview": "# Installation\n\n## 👉 Option 1: Using the CLI Installer (recommended)\n\nRun the following command and follow the prompts 🙂"
},
{
"path": "docs/quick-start/usage.md",
"chars": 879,
"preview": "# Usage\n\n## 👉 Folder structure\n\nUsing the CLI Installer (recommended):\n\n```bash\nproject/\n |__ handler.ts # lambda func"
},
{
"path": "docs/security/appsync-authz.md",
"chars": 2873,
"preview": "# AppSync Authorization modes\n\nAWS AppSync provides [authz directives ↗](https://docs.aws.amazon.com/appsync/latest/devg"
},
{
"path": "docs/security/query-depth.md",
"chars": 673,
"preview": "# Query depth\n\n## 👉 Usage\n\nPrisma-AppSync automatically prevents from abusing query depth, by limiting query complexity."
},
{
"path": "docs/security/rate-limiter.md",
"chars": 795,
"preview": "# Rate limiter (DOS)\n\n::: warning WARNING NOTICE\nLimits are kept in memory and are not shared between function instantia"
},
{
"path": "docs/security/shield-acl.md",
"chars": 2804,
"preview": "# Shield (Access Control Rules)\n\nFine-grained access control rules can be used via the `shield` property of Prisma-AppSy"
},
{
"path": "docs/security/xss-sanitizer.md",
"chars": 1077,
"preview": "# XSS sanitizer\n\n## 👉 Usage\n\nPrisma-AppSync automatically perform XSS sanitization and encode all data coming through th"
},
{
"path": "docs/support.md",
"chars": 487,
"preview": "# Support\n\n## <img src=\"https://avatars.githubusercontent.com/u/4679377?v=4?s=100\" width=\"50\" alt=\"Sylvain\"/>\n\n**👋 Hi, I"
},
{
"path": "docs/tools/appsync-gql-schema-diff.md",
"chars": 3412,
"preview": "---\naside: false\n---\n\n<script lang=\"ts\" setup>\nimport { buildSchema } from 'graphql';\nimport { diff as diffSchema } from"
},
{
"path": "package.json",
"chars": 1908,
"preview": "{\n \"name\": \"prisma-appsync\",\n \"version\": \"1.0.2\",\n \"description\": \"⚡ AppSync GraphQL API Generator for ◭ Prisma"
},
{
"path": "packages/boilerplate/cdk/package.json",
"chars": 513,
"preview": "{\n \"name\": \"prisma-appsync-cdk\",\n \"version\": \"1.0.0\",\n \"description\": \"Sample AWS CDK template for Prisma-AppSy"
},
{
"path": "packages/boilerplate/cdk/src/appsync.ts",
"chars": 8206,
"preview": "/* eslint-disable no-new */\nimport { readFileSync } from 'fs'\nimport type { Construct } from 'constructs'\nimport { camel"
},
{
"path": "packages/boilerplate/cdk/src/index.ts",
"chars": 2050,
"preview": "/* eslint-disable no-new */\nimport { join } from 'path'\nimport { App } from 'aws-cdk-lib'\nimport { AuthorizationType } f"
},
{
"path": "packages/boilerplate/cdk/tsconfig.json",
"chars": 603,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2018\",\n \"module\": \"commonjs\",\n \"lib\": [\"es2016\", \"es2017.o"
},
{
"path": "packages/boilerplate/cdk.json",
"chars": 62,
"preview": "{\n \"app\": \"npx ts-node --prefer-ts-exts cdk/src/index.ts\"\n}"
},
{
"path": "packages/boilerplate/handler.ts",
"chars": 408,
"preview": "import type { AppSyncResolverEvent } from './prisma/generated/prisma-appsync/client'\nimport { PrismaAppSync } from './pr"
},
{
"path": "packages/boilerplate/prisma/sqlite.prisma",
"chars": 798,
"preview": "datasource db {\n provider = \"sqlite\"\n url = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n provider = \"pr"
},
{
"path": "packages/boilerplate/server/server.ts",
"chars": 743,
"preview": "import { join } from 'path'\nimport { readFileSync } from 'fs'\nimport { load } from 'js-yaml'\nimport { argv, createServer"
},
{
"path": "packages/boilerplate/tsconfig.json",
"chars": 777,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"display\": \"Default\",\n \"compilerOptions\": {\n \"ta"
},
{
"path": "packages/client/package.json",
"chars": 514,
"preview": "{\n \"name\": \"prisma-appsync-client\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"author\": \"maoosi <hello@sylvain"
},
{
"path": "packages/client/src/adapter.ts",
"chars": 17784,
"preview": "import { CustomError } from './inspector'\nimport { sanitize } from './guard'\nimport {\n clone,\n isEmpty,\n isObje"
},
{
"path": "packages/client/src/consts.ts",
"chars": 5146,
"preview": "import type { Action } from './types'\nimport { uniq } from './utils'\n\n// Enums\n\nexport enum Actions {\n // queries\n "
},
{
"path": "packages/client/src/core.ts",
"chars": 18837,
"preview": "/* eslint-disable @typescript-eslint/ban-types */\n/* eslint-disable no-restricted-globals */\n/* eslint-disable n/prefer-"
},
{
"path": "packages/client/src/guard.ts",
"chars": 8721,
"preview": "import lambdaRateLimiter from 'lambda-rate-limiter'\nimport type {\n Context,\n PrismaClient,\n QueryParams,\n Sh"
},
{
"path": "packages/client/src/index.ts",
"chars": 912,
"preview": "import {\n clone,\n decode,\n dotate,\n encode,\n filterXSS,\n isEmpty,\n isMatchingGlob,\n isObject,\n "
},
{
"path": "packages/client/src/inspector.ts",
"chars": 3621,
"preview": "/* eslint-disable n/prefer-global/process */\n/* eslint-disable no-console */\nimport { inspect as nodeInspect } from 'nod"
},
{
"path": "packages/client/src/resolver.ts",
"chars": 7969,
"preview": "import type {\n PrismaArgs,\n PrismaClient,\n PrismaCount,\n PrismaCreate,\n PrismaCreateMany,\n PrismaDelet"
},
{
"path": "packages/client/src/types.ts",
"chars": 7620,
"preview": "import { Prisma, PrismaClient } from '@prisma/client'\nimport type {\n AppSyncIdentity,\n AppSyncIdentityCognito,\n "
},
{
"path": "packages/client/src/utils.ts",
"chars": 9382,
"preview": "/* eslint-disable @typescript-eslint/ban-types */\nimport { flatten } from 'wild-wild-utils'\nimport { isMatch } from 'mic"
},
{
"path": "packages/client/tsconfig.json",
"chars": 114,
"preview": "{\n \"extends\": \"../../tsconfig.json\",\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
},
{
"path": "packages/generator/package.json",
"chars": 556,
"preview": "{\n \"name\": \"prisma-appsync-generator\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"author\": \"maoosi <hello@sylv"
},
{
"path": "packages/generator/src/client.ts",
"chars": 7550,
"preview": "import type { RuntimeConfig } from '@client/types'\nimport type { DMMF } from '@prisma/generator-helper'\nimport { plural "
},
{
"path": "packages/generator/src/directives.ts",
"chars": 10592,
"preview": "import { merge, replaceAll, uniq, uniqBy } from '@client/utils'\nimport type { DMMF } from '@prisma/generator-helper'\n\nco"
},
{
"path": "packages/generator/src/generator.ts",
"chars": 4213,
"preview": "import { dirname, join } from 'node:path'\nimport { copy, outputFile, readFile } from 'fs-extra'\nimport type { DMMF } fro"
},
{
"path": "packages/generator/src/handler.ts",
"chars": 3179,
"preview": "/* eslint-disable no-console */\n// Dependencies\nimport { generatorHandler } from '@prisma/generator-helper'\nimport Prism"
},
{
"path": "packages/generator/src/index.ts",
"chars": 39,
"preview": "#!/usr/bin/env node\nimport './handler'\n"
},
{
"path": "packages/generator/src/resolvers.ts",
"chars": 8278,
"preview": "import type { DMMF } from '@prisma/generator-helper'\nimport { plural } from 'pluralize'\nimport * as prettier from 'prett"
},
{
"path": "packages/generator/src/schema.ts",
"chars": 61869,
"preview": "import type { DMMF } from '@prisma/generator-helper'\nimport { plural } from 'pluralize'\nimport flow from 'lodash/flow'\ni"
},
{
"path": "packages/generator/tsconfig.json",
"chars": 114,
"preview": "{\n \"extends\": \"../../tsconfig.json\",\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
},
{
"path": "packages/installer/package.json",
"chars": 486,
"preview": "{\n \"name\": \"create-prisma-appsync-app\",\n \"private\": true,\n \"version\": \"1.0.2\",\n \"author\": \"maoosi <hello@syl"
},
{
"path": "packages/installer/src/index.ts",
"chars": 285,
"preview": "#!/usr/bin/env node\n/* eslint-disable n/prefer-global/process */\nimport { Installer } from './installer'\n\nasync function"
},
{
"path": "packages/installer/src/installer.ts",
"chars": 24452,
"preview": "import path from 'node:path'\nimport * as jetpack from 'fs-jetpack'\nimport prompts from 'prompts'\nimport degit from 'degi"
},
{
"path": "packages/installer/tsconfig.json",
"chars": 114,
"preview": "{\n \"extends\": \"../../tsconfig.json\",\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
},
{
"path": "packages/server/package.json",
"chars": 299,
"preview": "{\n \"name\": \"prisma-appsync-server\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"author\": \"maoosi <hello@sylvain"
},
{
"path": "packages/server/src/appsync-simulator.ts",
"chars": 4534,
"preview": "/* eslint-disable no-console */\nimport { readFileSync } from 'fs'\nimport { resolve } from 'path'\nimport { exec as nodeEx"
},
{
"path": "packages/server/src/index.d.ts",
"chars": 45,
"preview": "export { createServer, argv } from './index'\n"
},
{
"path": "packages/server/src/index.ts",
"chars": 2068,
"preview": "/* eslint-disable n/prefer-global/process */\n/* eslint-disable no-console */\nimport { cli as cleye } from 'cleye'\nimport"
},
{
"path": "packages/server/src/lambdaRequest.vtl",
"chars": 97,
"preview": "{\n \"version\": \"2018-05-29\",\n \"operation\": \"Invoke\",\n \"payload\": $util.toJson($context)\n}"
},
{
"path": "packages/server/src/lambdaResponse.vtl",
"chars": 115,
"preview": "#if($ctx.error)\n $util.error($ctx.error.message, $ctx.error.type, $ctx.result)\n #end\n $util.toJson($ctx.result)"
},
{
"path": "packages/server/tsconfig.json",
"chars": 114,
"preview": "{\n \"extends\": \"../../tsconfig.json\",\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
},
{
"path": "pnpm-workspace.yaml",
"chars": 112,
"preview": "packages:\n - \"packages/server\"\n - \"packages/client\"\n - \"packages/installer\"\n - \"packages/generator\"\n"
},
{
"path": "tests/client/adapter.test.ts",
"chars": 21236,
"preview": "/* eslint-disable n/prefer-global/process */\nimport { describe, expect, test } from 'vitest'\nimport {\n addNullables,\n"
},
{
"path": "tests/client/core.test.ts",
"chars": 10248,
"preview": "/* eslint-disable n/prefer-global/process */\n/* eslint-disable no-new */\nimport { describe, expect, test } from 'vitest'"
},
{
"path": "tests/client/guard.test.ts",
"chars": 13022,
"preview": "/* eslint-disable n/prefer-global/process */\nimport { describe, expect, test } from 'vitest'\nimport { getDepth, getShiel"
},
{
"path": "tests/client/mocks/graphql-json.ts",
"chars": 6467,
"preview": "// adapted from: https://github.com/trayio/graphql-query-to-json\nimport { parse } from 'graphql'\nimport mapValues from '"
},
{
"path": "tests/client/mocks/lambda-event.ts",
"chars": 1988,
"preview": "import type { AppSyncEvent, AppSyncIdentity, Identity } from '../../../packages/client/src'\nimport { _ } from '../../../"
},
{
"path": "tests/client/mocks/lambda-identity.ts",
"chars": 2360,
"preview": "import type {\n AMAZON_COGNITO_USER_POOLS,\n API_KEY,\n AWS_IAM,\n AWS_LAMBDA,\n Authorization,\n Identity,\n"
},
{
"path": "tests/client/resolver.test.ts",
"chars": 4812,
"preview": "/* eslint-disable n/prefer-global/process */\nimport { describe, expect } from 'vitest'\nimport * as queries from '@client"
},
{
"path": "tests/client/utils/index.ts",
"chars": 482,
"preview": "import { test } from 'vitest'\n\nfunction format(str, ...args) {\n return str.replace(/{(\\d+)}/g, (match, number) => {\n "
},
{
"path": "tests/client/utils.test.ts",
"chars": 11817,
"preview": "import { describe, expect, test } from 'vitest'\nimport {\n clone,\n decode,\n dotate,\n encode,\n filterXSS,\n "
},
{
"path": "tests/generator/@gql.test.ts",
"chars": 3913,
"preview": "/* eslint-disable unicorn/prefer-node-protocol */\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\nimport {"
},
{
"path": "tests/generator/crud.test.ts",
"chars": 11811,
"preview": "/* eslint-disable unicorn/prefer-node-protocol */\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\nimport {"
},
{
"path": "tests/generator/mock/appsync-directives.gql",
"chars": 529,
"preview": "directive @aws_subscribe(mutations: [String!]!) on FIELD_DEFINITION\ndirective @deprecated(reason: String) on FIELD_DEFIN"
},
{
"path": "tests/generator/mock/appsync-scalars.gql",
"chars": 177,
"preview": "scalar AWSDate\nscalar AWSTime\nscalar AWSDateTime\nscalar AWSTimestamp\nscalar AWSEmail\nscalar AWSJSON\nscalar AWSURL\nscalar"
},
{
"path": "tests/generator/schemas/@gql.prisma",
"chars": 1138,
"preview": "datasource db {\n provider = \"postgres\"\n url = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n provider = \""
},
{
"path": "tests/generator/schemas/crud.gql",
"chars": 329,
"preview": "type PublishNotification {\n message: String!\n}\n\nextend type Mutation {\n \"\"\"\n Send a notification.\n \"\"\"\n n"
},
{
"path": "tests/generator/schemas/crud.prisma",
"chars": 1690,
"preview": "datasource db {\n provider = \"postgres\"\n url = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n provider = \"prisma"
},
{
"path": "tests/generator/schemas/generated/@gql/client/adapter.d.ts",
"chars": 4381,
"preview": "import type { Action, ActionsAlias, AppSyncEvent, Authorization, Context, GraphQLType, Identity, Model, Options, PrismaA"
},
{
"path": "tests/generator/schemas/generated/@gql/client/consts.d.ts",
"chars": 3366,
"preview": "export declare enum Actions {\n get = \"get\",\n list = \"list\",\n count = \"count\",\n createMany = \"createMany\",\n "
},
{
"path": "tests/generator/schemas/generated/@gql/client/core.d.ts",
"chars": 3379,
"preview": "import type { Options, PrismaAppSyncOptionsType, ResolveParams } from './types';\nimport { Prisma, PrismaClient } from '."
},
{
"path": "tests/generator/schemas/generated/@gql/client/guard.d.ts",
"chars": 1928,
"preview": "import type { Context, PrismaClient, QueryParams, Shield, ShieldAuthorization } from './types';\n/**\n * #### Sanitize dat"
},
{
"path": "tests/generator/schemas/generated/@gql/client/index.d.ts",
"chars": 1031,
"preview": "import { clone, decode, dotate, encode, filterXSS, isEmpty, isMatchingGlob, isUndefined, lowerFirst, merge, replaceAll, "
},
{
"path": "tests/generator/schemas/generated/@gql/client/index.js",
"chars": 214296,
"preview": "\"use strict\";var Ea=Object.create;var De=Object.defineProperty;var xa=Object.getOwnPropertyDescriptor;var va=Object.getO"
},
{
"path": "tests/generator/schemas/generated/@gql/client/inspector.d.ts",
"chars": 917,
"preview": "import type { logLevel } from './types';\ndeclare const errorCodes: {\n FORBIDDEN: number;\n BAD_USER_INPUT: number;\n"
},
{
"path": "tests/generator/schemas/generated/@gql/client/resolver.d.ts",
"chars": 3236,
"preview": "import type { PrismaArgs, PrismaClient, PrismaOperator, QueryBuilder, QueryParams } from './types';\n/**\n * #### Query B"
},
{
"path": "tests/generator/schemas/generated/@gql/client/types.d.ts",
"chars": 7805,
"preview": "import { Prisma, PrismaClient } from '@prisma/client';\nimport type { AppSyncIdentity, AppSyncIdentityCognito, AppSyncIde"
},
{
"path": "tests/generator/schemas/generated/@gql/client/utils.d.ts",
"chars": 5239,
"preview": "/**\n * #### Deep merge objects (without mutating the target object).\n *\n * @example const newObj = merge(obj1, obj2, obj"
},
{
"path": "tests/generator/schemas/generated/@gql/resolvers.yaml",
"chars": 1331,
"preview": "- typeName: Query\n fieldName: getUser\n dataSource: prisma-appsync\n- typeName: Query\n fieldName: listUsers\n dataSourc"
},
{
"path": "tests/generator/schemas/generated/@gql/schema.gql",
"chars": 16079,
"preview": "type BatchPayload {\n count: Int!\n}\n\ntype User {\n id: Int!\n email: AWSEmail!\n posts: [Post!]!\n createdAt: "
},
{
"path": "tests/generator/schemas/generated/crud/client/adapter.d.ts",
"chars": 4381,
"preview": "import type { Action, ActionsAlias, AppSyncEvent, Authorization, Context, GraphQLType, Identity, Model, Options, PrismaA"
},
{
"path": "tests/generator/schemas/generated/crud/client/consts.d.ts",
"chars": 3366,
"preview": "export declare enum Actions {\n get = \"get\",\n list = \"list\",\n count = \"count\",\n createMany = \"createMany\",\n "
},
{
"path": "tests/generator/schemas/generated/crud/client/core.d.ts",
"chars": 4926,
"preview": "import type { Options, PrismaAppSyncOptionsType, ResolveParams } from './types';\nimport { Prisma, PrismaClient } from '."
},
{
"path": "tests/generator/schemas/generated/crud/client/guard.d.ts",
"chars": 1928,
"preview": "import type { Context, PrismaClient, QueryParams, Shield, ShieldAuthorization } from './types';\n/**\n * #### Sanitize dat"
},
{
"path": "tests/generator/schemas/generated/crud/client/index.d.ts",
"chars": 1031,
"preview": "import { clone, decode, dotate, encode, filterXSS, isEmpty, isMatchingGlob, isUndefined, lowerFirst, merge, replaceAll, "
},
{
"path": "tests/generator/schemas/generated/crud/client/index.js",
"chars": 245245,
"preview": "\"use strict\";var Ea=Object.create;var De=Object.defineProperty;var xa=Object.getOwnPropertyDescriptor;var va=Object.getO"
},
{
"path": "tests/generator/schemas/generated/crud/client/inspector.d.ts",
"chars": 917,
"preview": "import type { logLevel } from './types';\ndeclare const errorCodes: {\n FORBIDDEN: number;\n BAD_USER_INPUT: number;\n"
},
{
"path": "tests/generator/schemas/generated/crud/client/resolver.d.ts",
"chars": 3236,
"preview": "import type { PrismaArgs, PrismaClient, PrismaOperator, QueryBuilder, QueryParams } from './types';\n/**\n * #### Query B"
},
{
"path": "tests/generator/schemas/generated/crud/client/types.d.ts",
"chars": 7805,
"preview": "import { Prisma, PrismaClient } from '@prisma/client';\nimport type { AppSyncIdentity, AppSyncIdentityCognito, AppSyncIde"
},
{
"path": "tests/generator/schemas/generated/crud/client/utils.d.ts",
"chars": 5239,
"preview": "/**\n * #### Deep merge objects (without mutating the target object).\n *\n * @example const newObj = merge(obj1, obj2, obj"
},
{
"path": "tests/generator/schemas/generated/crud/resolvers.yaml",
"chars": 7574,
"preview": "- typeName: Query\n fieldName: getUser\n dataSource: prisma-appsync\n- typeName: Query\n fieldName: listUsers\n dataSourc"
},
{
"path": "tests/generator/schemas/generated/crud/schema.gql",
"chars": 46003,
"preview": "type BatchPayload {\n count: Int!\n}\n\ntype User {\n uuid: String!\n username: String!\n email: AWSEmail!\n webs"
},
{
"path": "tsconfig.json",
"chars": 1155,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"display\": \"Default\",\n \"compilerOptions\": {\n \"ta"
},
{
"path": "vite.config.ts",
"chars": 177,
"preview": "// vite.config.ts\nimport { defineConfig } from 'vitest/config'\nimport tsconfigPaths from 'vite-tsconfig-paths'\n\nexport d"
}
]
About this extraction
This page contains the full source code of the maoosi/prisma-appsync GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 136 files (1004.8 KB), approximately 324.7k tokens, and a symbol index with 752 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.