[{"data":1,"prerenderedAt":2532},["ShallowReactive",2],{"search-data":3,"navigation":303,"/appsync-lambda-functions":333,"page-links-/appsync-lambda-functions":2529},[4,10,16,22,27,32,37,42,47,52,57,62,67,73,78,83,88,93,98,103,108,113,118,123,128,133,138,143,148,153,158,163,168,173,178,183,188,193,198,203,208,213,218,223,228,233,238,243,248,253,258,263,268,273,278,283,288,293,298],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/","Nuxt Precognition",[],"A validation module for Nuxt that implements the [Precognition protocol](https://laravel.com/docs/10.x/precognition) in a backend-agnostic way. It supports any backend or validation library, and is not tied to Laravel.",1,{"id":11,"title":12,"titles":13,"content":14,"level":15},"/#why-nuxt-precognition","Why Nuxt Precognition?",[6],"Backend agnostic: Works with any backend that supports the Precognition protocol.Validation library agnostic: Use Zod, Yup, or any other validation library.Client & server side validation: Seamless validation on both ends.Optimal TypeScript support: Typesafe forms and error handling.Highly customizable: Plug in your own error parsers and status handlers.",2,{"id":17,"title":18,"titles":19,"content":20,"level":21},"/#quick-example","Quick Example",[6,12],"interface User {\n  email: string\n  password: string\n}\n\nconst form = useForm(\n  (): User => ({ email: '', password: '' }),\n  (body, headers) => $fetch(\n    '/api/login',\n    { method: 'POST', headers, body }\n  )\n)",3,{"id":23,"title":24,"titles":25,"content":26,"level":15},"/#features","Features",[6],"Laravel compliantValidation library agnosticClient and server side validationTypeScript supportCustomizable error parsing and status handling html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":28,"title":29,"titles":30,"content":31,"level":9},"/installation","Installation",[],"Installation instructions on Nuxt v3/4 project Install the module in your Nuxt app with nuxi: npx nuxi module add nuxt-precognition",{"id":33,"title":34,"titles":35,"content":36,"level":15},"/installation#manual-installation","Manual installation",[29],"Install the with your package manager pnpm add add nuxt-precognition Add module to your project nuxt.config.ts export default defineNuxtConfig({\n  modules: ['nuxt-precognition'],\n  precognition: {\n    // ..\n  }\n}) html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"id":38,"title":39,"titles":40,"content":41,"level":9},"/how-it-works","Core",[],"How the module validates form input data on client and server side, using same validation rules, before form submission. The main advantage on using this module is defining validation rules once (backend), but leveraging them also on client side, before final form submission. Classic example consists on email validation when user registers new profile.\nUser should type an unique email, that client side is hard to validate (specific api should be available to verify uniqueness). With this module the full set of validation rules for email (and only for it) can be verified, after the user types the new email: \u003Cinput v-model=\"form.email\" @change=\"form.validate('email')\" />\n\u003Cdiv v-if=\"form.valid('email')\">OK!!\u003C/div>",{"id":43,"title":44,"titles":45,"content":46,"level":15},"/how-it-works#how-it-works","How it works?",[39],"This module will: differentiate between precognitive validation requests vs full form subsmissions.specify which form keys need validation in case of precognitive request. Needs instruction on how: assign errors to each form key. You will need to 'instruct' it by defining Error Parsers.",{"id":48,"title":49,"titles":50,"content":51,"level":21},"/how-it-works#error-parsers","Error Parsers",[39,44],"Error parsers are functions that extract ValidationErrors from thrown errors. type ValidationErrors = Record\u003Cstring, string | string[]>\n\ninterface ValidationErrorsData {\n  message: string\n  errors: ValidationErrors\n}\n\ntype ValidationErrorParser = (error: Error) => ValidationErrorsData | undefined | null Example using zod // app/utils/precognition.ts or shared/utils/precognition.ts\nimport { ZodError } from 'zod'\n\nexport const zodPrecognitionErrorParser: ValidationErrorParser = (error) => {\n  if (error instanceof ZodError) {\n    const errors: Record\u003Cstring, string[]> = {}\n    for (const issue of error.issues) {\n      const key = issue.path.join('.')\n      if (key in errors) {\n        errors[key].push(issue.message)\n        continue\n      }\n      errors[key] = [issue.message]\n    }\n    return { errors, message: error.message }\n  }\n  return null\n} Note:\nFor Server side validation, place this file in shared/utils folder. Defining a parser is mandatory because each validation library can have different errors shapes.\nValidationErrors is the common interface this module will be able to use. You can define as many error parses as you like. In this way the module will be able to parse also backend http errors. html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"id":53,"title":54,"titles":55,"content":56,"level":9},"/base-example","Base usage",[],"Here the base setup to have form validation up and running. First step is to define you errors parsers. Usually you can do in utils or shared/utils directory. import type { ValidationErrorParser } from 'nuxt-precognition'\n\n// app/utils/precognition.ts or shared/utils/precognition.ts\nexport const customErrorParser: ValidationErrorParser = (error) => {\n  if (error instanceof CustomError) {\n    const errors: Record\u003Cstring, string[]> = {}\n    for (const issue of error.issues) {\n      const key = issue.path.join('.')\n      if (key in errors) {\n        errors[key].push(issue.message)\n        continue\n      }\n      errors[key] = [issue.message]\n    }\n    return { errors, message: error.message }\n  }\n  return null\n} Add the parsers globally // app/plugins/precognition.ts\nexport default defineNuxtPlugin(() => {\n  const { $precognition } = useNuxtApp()\n\n  $precognition.errorParsers.push(customErrorParser)\n\n  // ..\n}) Thats it! From now on, the module knows how to parse ValidationErrors. Note:\nRemember that global parsers will be used all times when Error is intercepted.",{"id":58,"title":59,"titles":60,"content":61,"level":21},"/base-example#example-usage","Example usage",[54],"Use the composable in setup method. const UserSchema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n})\n\nconst form = useForm(\n  (): z.infer\u003Ctypeof UserSchema> => ({\n    email: '',\n    password: '',\n  }),\n  (body, headers) => $fetch('/api/login', {\n    method: 'POST',\n    headers,\n    body,\n  }),\n  {\n    clientValidation(data) {\n      UserSchema.parse(data)\n    },\n  },\n)\n\nfunction login() {\n  form.submit()\n}\n\nfunction reset() {\n  form.reset()\n  document.getElementById('email')?.focus()\n} \u003Cform @submit.prevent=\"login\" @reset.prevent=\"reset\">\n  \u003Cdiv>\n    \u003Clabel for=\"email\">Email address\u003C/label>\n    \u003Cinput id=\"email\" v-model=\"form.email\" name=\"email\" type=\"email\" @change=\"form.validate('email')\" />\n    \u003Cspan v-if=\"form.valid('email')\">OK!!\u003C/span>\n    \u003Cspan v-if=\"form.invalid('email')\">{{ form.errors.email }}\u003C/span>\n  \u003C/div>\n\n  \u003Cdiv>\n    \u003Clabel for=\"password\">Password\u003C/label>\n    \u003Cinput\n      id=\"password\"\n      v-model=\"form.password\"\n      name=\"password\"\n      type=\"password\"\n      autocomplete=\"current-password\"\n      required\n      @change=\"form.validate('password')\"\n    />\n    \u003Cspan v-if=\"form.valid('password')\">OK!!\u003C/span>\n    \u003Cspan v-if=\"form.invalid('password')\">{{ form.errors.password }}\u003C/span>\n  \u003C/div>\n\n  \u003Cdiv>\n    \u003Cbutton type=\"submit\">Sign in\u003C/button>\n    \u003Cbutton type=\"reset\">Reset\u003C/button>\n  \u003C/div>\n\u003C/form>",{"id":63,"title":64,"titles":65,"content":66,"level":21},"/base-example#server-side-validation","Server side validation",[54],"Wait what about http errors? And how can we validate data but skipping next steps? update the default configuration. // nuxt.config.ts\n\nexport default defineNuxtConfig({\n  modules: [\n    'nuxt-precognition'\n  ],\n  precognition: {\n    backendValidation: true,\n    enableNuxtClientErrorParser: true,\n  },\n}) Here we are instructing the module to: add backend validation (precognitive-requests) when we request single key validation.add global parser to translate NuxtErrors to ValidationErrors. Create a Nitro plugin to parse server errors: // server/plugins/precognition.ts\nimport { ZodError } from 'zod'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('request', (event) => {\n    event.context.$precognition.errorParsers = [\n      zodErrorParser\n    ]\n  })\n}) Assuming we are using same validation library on backend (in this example zod), we need to translate zod errors to NuxtErrors the module will understand. Use definePrecognitiveEventHandler in the object way and add validation in the onRequest hook. import { definePrecognitiveEventHandler, readBody } from '#imports'\n// server/api/login.post.ts\nimport { z } from 'zod'\n\nconst loginSchema = z.object({\n  email: z.string().email(),\n  password: z.string()\n}).refine((_data) => {\n  // Check for email and password match\n  // ...\n  return true\n}, { message: 'invalid credentials', path: ['email'] },)\n\nexport default definePrecognitiveEventHandler({\n  async onRequest(event) {\n    const body = await readBody(event)\n    loginSchema.parse(body)\n  },\n  handler: () => {\n    return {\n      status: 200,\n      body: {\n        message: 'Success',\n      },\n    }\n  },\n}) Splitting the handler in different hooks, we can isolate validation from the main functionality. When the precognitive-request is detected, only the validation function will run.",{"id":68,"title":69,"titles":70,"content":71,"level":72},"/base-example#custom-parsers-per-request","Custom Parsers per request",[54,64],"It happens you have a specific apis, where you can have errors in different shapes. For example using common authentication services with specific sdks. No problem, you can define the parser on the useForm composable. const form = useForm(\n  (): z.infer\u003Ctypeof UserSchema> => ({\n    email: '',\n    password: '',\n  }),\n  (body, headers) => $fetch('/api/login', {\n    method: 'POST',\n    headers,\n    body,\n  }),\n  {\n    clientValidation(data) {\n      UserSchema.parse(data)\n    },\n    clientErrorParsers: [\n      (error) => {\n        if (error instanceof CustomError) {\n          // ....\n          return { errors, message: error.message }\n        }\n        //\n        return null\n      }\n    ]\n  },\n) This parser will be used only for this form submission. Same thing on backend. export default definePrecognitiveEventHandler({\n  async onRequest(event) {\n    const body = await readBody(event)\n    loginSchema.parse(body)\n  },\n  handler: () => {\n    return {\n      status: 200,\n      body: {\n        message: 'Success',\n      },\n    }\n  },\n}, {\n  errorParsers: [\n    (error) => {\n      if (error instanceof z.ZodError) {\n        return {\n          message: 'Invalid data',\n          errors: error.issues.map(issue => ({\n            path: issue.path.join('.'),\n            message: issue.message,\n          }))\n        }\n      }\n    }\n  ]\n})",4,{"id":74,"title":75,"titles":76,"content":77,"level":21},"/base-example#asynchronous-form-submission-asyncsubmit","Asynchronous Form Submission (asyncSubmit)",[54,64],"Starting from v0.2.0, useForm provides asyncSubmit() as an alternative to submit(). Unlike submit(), asyncSubmit(): Returns the promise result (Promise\u003CTResp>) directly upon success.Accepts an optional { headers?: HeadersInit } parameter (supporting Headers, Record\u003Cstring, string>, or [string, string][]).Executes client-side validation (if configured).Guarantees that validation errors are always assigned to form keys (form.errors, form.error) before re-throwing the error, allowing callers to handle errors using standard try / catch or pass them to external state managers while the form state remains fully reactive and updated.",{"id":79,"title":80,"titles":81,"content":82,"level":72},"/base-example#standard-try-catch-example","Standard try / catch Example",[54,64,75],"async function handleSubmit() {\n  try {\n    const response = await form.asyncSubmit()\n    console.log('Submission successful:', response)\n  }\n  catch (error) {\n    // Validation errors are already assigned to form keys before this catch block runs\n    console.error('Submission failed:', error)\n  }\n}",{"id":84,"title":85,"titles":86,"content":87,"level":72},"/base-example#integration-with-pinia-colada-usemutation","Integration with Pinia Colada (useMutation)",[54,64,75],"asyncSubmit is particularly useful when combined with data fetching & mutation libraries like Pinia Colada (@pinia/colada). Because asyncSubmit guarantees that validation errors are assigned to the form keys before re-throwing, you can pass form.asyncSubmit directly as the mutation function inside useMutation: import { useMutation } from '@pinia/colada'\nimport { useForm } from 'nuxt-precognition'\n\nconst form = useForm(\n  () => ({\n    email: '',\n    password: '',\n  }),\n  (body, headers) => $fetch('/api/login', {\n    method: 'POST',\n    headers,\n    body,\n  }),\n  {\n    clientValidation(data) {\n      UserSchema.parse(data)\n    },\n  },\n)\n\n// Pass form.asyncSubmit directly as the mutation parameter\nconst { mutate, isLoading } = useMutation({\n  mutation: () => form.asyncSubmit(),\n  onSuccess(data) {\n    // Handle success (e.g. navigate, show toast)\n  },\n  onError(error) {\n    // form.errors and form.invalid() are already populated here\n  },\n}) html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}",{"id":89,"title":90,"titles":91,"content":92,"level":9},"/file-upload","File Upload",[],"By default files are not sent during precognitive validation because of performance implication (saving transfer data).. If you want to append data also during precognitive validation request you could either: set the corresponding option (validateFiles) in the UseFormOptionscall the form function form.validateFiles()",{"id":94,"title":95,"titles":96,"content":97,"level":15},"/file-upload#example","Example",[90],"here a snapshot of code that can: perfom clientSide validation.skip sending file during precognitive validationUse base nuxt $fetch to make http request to backend /**\n * Post schema. Defined using Zod.\n * Add specific file validations because it is\n * sent during precognitive validation request. *\n */\nconst postSchema = z.object({\n  title: z.string().min(5).max(100),\n  content: z.string().min(10).max(1000),\n  friends: z.array(z.string()),\n  image: z.instanceof(File)\n    .refine(file => file.size \u003C= 10 * 1024 * 1024, {\n      message: 'File size must be less than 10MB',\n    })\n    .refine(file => ['image/jpeg', 'image/png', 'image/gif'].includes(file.type), {\n      message: 'File must be JPEG, PNG, or GIF',\n    })\n    .refine(file => file.name.length > 0, {\n      message: 'File must have a name',\n    })\n    .refine(file => !!file.name.match(/^[a-z0-9]+\\.[a-z0-9]+$/i), {\n      message: 'File name must be alphanumeric with an extension. No spaces or special characters.',\n    })\n    .nullable(),\n})\ntype Post = z.infer\u003Ctypeof postSchema>\n\n/**\n * Use the global $api to make backend request (to Laravel for example) defined in Nuxt Plugin.\n * It includes already Zod parser for validation errors.\n */\nconst { $api } = useNuxtApp()\n\n/**\n * Define the form using useForm composable.\n * The second argument is a function to submit the form data to the backend. Being complete agnostic,\n * the headers to submit form data must be specified. Precognnitive header are already present.\n * Here we use FormData to handle file upload.\n * Client-side validation is specified in the UseFormOptions.\n */\nconst postForm = useForm(\n  (): Post => ({ title: '', content: '', friends: [], image: null }),\n  (data, headers) => {\n    const formData = new FormData()\n    formData.append('title', data.title)\n    formData.append('content', data.content)\n    data.friends.forEach((friend, index) => {\n      formData.append(`friends[${index}]`, friend)\n    })\n    if (data.image) {\n      formData.append('image', data.image)\n    }\n    headers['Content-Type'] = 'multipart/form-data'\n    return $api(\n      '/api/posts',\n      {\n        method: 'POST',\n        headers,\n        body: formData,\n      }\n    )\n  },\n  {\n    clientValidation: postSchema.parse,\n    validateFiles: true,\n  },\n)\n\nfunction addImage(e: Event) {\n  postForm.image = null\n  postForm.forgetErrors('image')\n  const input = e.target as HTMLInputElement\n  if (input.files && input.files[0]) {\n    postForm.image = input.files[0]\n    postForm.validate('image')\n  }\n}\n\nfunction handleSubmit() {\n  postForm.submit({\n    onBefore() {\n      postForm.forgetErrors()\n      return true\n    },\n    onStart: (data) => {\n      postSchema.parse(data)\n    }\n  })\n} html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s-Lb2, html code.shiki .s-Lb2{--shiki-default:#85E89D;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":99,"title":100,"titles":101,"content":102,"level":9},"/config","Configuration",[],"How customize the module behaviour by nuxt.config.ts Add to your nuxt.config.ts: export default defineNuxtConfig({\n  modules: ['nuxt-precognition'],\n  precognition: {\n    backendValidation: true,\n    enableNuxtClientErrorParser: true,\n    // ...other options\n  }\n})",{"id":104,"title":105,"titles":106,"content":107,"level":21},"/config#options","Options",[100],"nametypedefaultdescriptionvalidationTimeoutnumber1500Debounce time, in milliseconds, between two precognitive validation requests.backendValidationbooleanfalseFlag to enable the precognitive validation.validateFilesbooleanfalseFlag to enable files validation on precognitive requests.enableNuxtClientErrorParserbooleanfalseFlag to enable nuxtErrorParsers on client side (in form.validate and form.submit).enableLaravelClientErrorParserbooleanfalseFlag to enable laravelErrorParsers on client side (in form.validate and form.submit).enableLaravelServerErrorParserbooleanfalseFlag to enable laravelErrorParsers on server side (in definePrecognitiveEventHandler).",{"id":109,"title":110,"titles":111,"content":112,"level":21},"/config#status-handlers","Status Handlers",[100],"Like in official package, you can define globally, or @instance level, custom handlers for specific error codes: // plugins/precognition.ts\n\nexport default defineNuxtPlugin(() => {\n  const { $precognition } = useNuxtApp()\n\n  $precognition.statusHandlers = {\n    401: async (error, form) => {\n      console.error(error)\n      form.error = createError('Unauthorized')\n      await navigateTo('/login')\n    },\n    403: async (error, form) => {\n      console.error(error)\n      form.error = createError('Forbidden')\n    },\n  }\n}) html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"id":114,"title":115,"titles":116,"content":117,"level":9},"/laravel-integration","Laravel Integration",[],"Setup module for seemless integration with our favorite PHP backend framework. If you are using Laravel, you won't need nuxt nitro integration.",{"id":119,"title":120,"titles":121,"content":122,"level":15},"/laravel-integration#integration-steps","Integration steps",[115],"Enable Backend Validation and Error Parsers: // nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['nuxt-precognition'],\n  precognition: {\n    backendValidation: true,\n    enableLaravelClientErrorParser: true,\n  }\n}) Plugin Example: Add Sanctum token prefetch and ensure proper handling of all precognitive requests. // plugins/laravel.ts\nexport default defineNuxtPlugin((app) => {\n  const { $precognition } = useNuxtApp()\n  const token = useCookie('XSRF-TOKEN')\n\n  const api = $fetch.create({\n    baseURL: 'http://localhost',\n    credentials: 'include',\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json',\n    },\n    onRequest: ({ options }) => {\n      if (token.value) {\n        const headers = new Headers(options.headers)\n        headers.set('X-XSRF-TOKEN', token.value)\n        options.headers = headers\n      }\n    },\n    onResponse: (context) => {\n      // ensure non false positive validations\n      $precognition.assertSuccessfulPrecognitiveResponses(context)\n    },\n  })\n\n  async function fetchSanctumToken() {\n    try {\n      await api('/sanctum/csrf-cookie')\n      token.value = useCookie('XSRF-TOKEN').value\n      if (!token.value)\n        throw new Error('Failed to get CSRF token')\n    }\n    catch (e) {\n      console.error(e)\n    }\n  }\n\n  app.hook('app:mounted', fetchSanctumToken)\n\n  return {\n    provide: {\n      api,\n      sanctum: {\n        fetchToken: fetchSanctumToken,\n        token,\n      },\n    },\n  }\n}) Laravel CORS Configuration: Ensure Precognitive headers will be shared with Nuxt application. // config/cors.php\nreturn [\n  'paths' => ['*'],\n  'allowed_methods' => ['*'],\n  'allowed_origins' => ['*'],\n  'allowed_origins_patterns' => [env('FRONTEND_URL', 'http://localhost:3000')],\n  'allowed_headers' => ['*'],\n  'exposed_headers' => ['Precognition', 'Precognition-Success'],\n  'max_age' => 0,\n  'supports_credentials' => true,\n]; Enable Precognition Middleware: Apply precognitive middleware where needed. // routes/api.php\nRoute::middleware('precognitive')->group(function () {\n    Route::apiResource('posts', \\App\\Http\\Controllers\\PostController::class);\n}); That's it. Nuxt validation will be in sync with Laravel!!. html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"id":124,"title":125,"titles":126,"content":127,"level":9},"/protocol","Backend protocol",[],"What about implementing precognitive validation on different backends? If you need to define your own backend logic outside Nitro, follow these requirements. Precognitive Requests must have Precognitive Header { 'Precognitive': 'true' }To validate specific variables, each keys must be specified inside the ValidateOnly Header, comma separated and leveraging dot notation: { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }To validate the full Form the ValidateOnly Header should be omitted or define as an empty string.Successful validation response must have: Precognitive Header { 'Precognitive': 'true' }Precognitive Successful Header { 'Precognition-Success': 'true' }Precognitive Successful status code: 204 Error validation response must have: Precognitive Header { 'Precognitive': 'true' }Precognition-Validate-Only header if needed { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }Validation Error status code: 422Validation Errors and Message will be parsed as per your define logic, or using standard errorParsers if enabled by config:\nNuxtErrorParsers: Response & { _data: { data: ValidationErrorsData }}LaravelErrorParsers: Response & { _data: ValidationErrorsData }",{"id":129,"title":130,"titles":131,"content":132,"level":9},"/appsync","AWS AppSync Integration",[],"Learn how to integrate Nuxt Precognition with AWS AppSync to validate form inputs. AWS AppSync is a managed serverless GraphQL service that simplifies application development by letting you create a flexible API to securely access, manipulate, and combine data from one or more data sources.",{"id":134,"title":135,"titles":136,"content":137,"level":15},"/appsync#what-is-aws-appsync","What is AWS AppSync?",[130],"AWS AppSync allows you to create serverless GraphQL APIs. Applications interact with AppSync by sending GraphQL queries, mutations, or subscriptions. AppSync then executes these requests against your configured data sources (such as DynamoDB, Aurora Serverless, OpenSearch, AWS Lambda, or HTTP endpoints).",{"id":139,"title":140,"titles":141,"content":142,"level":15},"/appsync#resolvers-in-aws-appsync","Resolvers in AWS AppSync",[130],"Resolvers are the connectors that link GraphQL fields to data sources. When a client performs a GraphQL operation, AppSync uses resolvers to fetch the requested data or execute business logic. AppSync supports two types of resolvers: Unit Resolvers: Map a single GraphQL field to a single data source.Pipeline Resolvers: Execute multiple operations (functions) sequentially against one or more data sources. Resolvers can be authored in: JavaScript (APPSYNC_JS runtime): Modern, recommended approach that allows writing resolver functions in JavaScript.VTL (Velocity Template Language): Legacy templates for request and response mapping.",{"id":144,"title":145,"titles":146,"content":147,"level":15},"/appsync#nuxt-precognition-for-appsync","Nuxt Precognition for AppSync",[130],"Since GraphQL has a single HTTP POST endpoint (/graphql) and returns standard 200 OK status codes even for validation or execution errors, implementing the standard HTTP Precognition protocol (which relies on 204 and 422 status codes) requires a bridge. To make Nuxt Precognition work seamlessly with AWS AppSync, we can either: Pass Precognition metadata via GraphQL request headers.Pass Precognition arguments directly inside the GraphQL mutation inputs. In this section, we'll explore how Nuxt Precognition was designed to support AppSync by adapting the headers, handling response payloads, and using dedicated strategies for: AppSync JS Resolvers: Executing lightweight validation directly within the AppSync runtime using the @sot1986/appsync-precognition helper library.AppSync Lambda Functions: Delegating complex or database-backed validation to AWS Lambda.",{"id":149,"title":150,"titles":151,"content":152,"level":9},"/appsync-resolver-js","AppSync JS Resolvers",[],"Validate your form inputs directly within AWS AppSync using the @sot1986/appsync-precognition package. AWS AppSync allows you to write resolver functions in JavaScript using the APPSYNC_JS runtime. To implement precognitive validation seamlessly, you can use the @sot1986/appsync-precognition library. !IMPORTANT\nThis validation strategy is designed and works only for custom mutations where you write custom resolver handlers. It is not compatible with the default mutations automatically generated by Amplify models.",{"id":154,"title":155,"titles":156,"content":157,"level":15},"/appsync-resolver-js#bundling-tsdown","Bundling & tsdown",[150],"Because AWS AppSync executes resolver files in a sandboxed runtime environment, external Node.js dependencies cannot be loaded dynamically at runtime. You must bundle @sot1986/appsync-precognition directly into your resolver source files during the build/bundling step, while marking native utilities (like @aws-appsync/utils) as external/excluded. For this bundling task, we use tsdown, a fast TypeScript bundler developed by members of the Vite team. Additionally, AWS AppSync enforces a 32KB code size limit for JS Resolvers. Using a bundler like tsdown with dead-code elimination ensures your bundled resolver fits within this limit.",{"id":159,"title":160,"titles":161,"content":162,"level":15},"/appsync-resolver-js#installation-setup","Installation & Setup",[150],"Install the precognition library and the bundling devDependencies inside your Amplify backend project: # Install the validation library\npnpm add @sot1986/appsync-precognition\n\n# Install tsdown and TypeScript for bundling\npnpm add -D tsdown typescript",{"id":164,"title":165,"titles":166,"content":167,"level":21},"/appsync-resolver-js#_1-tsconfig-configuration","1. TSConfig Configuration",[150,160],"Create or update the tsconfig.json file inside your amplify/data folder to compile resolver files properly: {\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"strict\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\n    \"resolvers/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}",{"id":169,"title":170,"titles":171,"content":172,"level":21},"/appsync-resolver-js#_2-tsdown-configuration","2. tsdown Configuration",[150,160],"Create a tsdown.resolvers.config.ts configuration file under your amplify/data directory. This configuration assumes that all resolver files are located under the amplify/data/resolvers/ directory, targeting files matching the *handler.ts pattern: import { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  target: 'esnext',\n  platform: 'node',\n  format: 'esm',\n  deps: {\n    // Only bundle the precognition package\n    onlyBundle: ['@sot1986/appsync-precognition'],\n    // Never bundle the native AppSync utils\n    neverBundle: ['@aws-appsync/utils'],\n  },\n  tsconfig: 'tsconfig.json',\n  logLevel: 'info',\n  clean: false,\n  outExtensions: () => ({\n    js: '.js',\n  }),\n  minify: 'dce-only', // Perform dead-code elimination to reduce bundle size\n  entry: {\n    'resolvers/*': [\n      './resolvers/**/*handler.ts',\n    ],\n  },\n})",{"id":174,"title":175,"titles":176,"content":177,"level":15},"/appsync-resolver-js#middleware-behavior-resolver-order","Middleware Behavior & Resolver Order",[150],"Precognitive validation acts as a middleware (similar to Laravel's validation middleware). It must execute before any write operations or business logic are performed on your database. This ensures that: If the request is precognitive and validation succeeds, it will immediately exit early via runtime.earlyReturn(null), preventing any side effects.If validation fails, the resolver throws a ValidationError early, blocking downstream operations. For single/unit resolvers, always call precognitiveValidation at the very start of the request handler before returning the write operation (e.g., a DynamoDB PutItem or UpdateItem). For multi-step pipeline resolvers, place the validation check in the first function of the pipeline (often alongside authorization) to ensure it executes before later pipeline functions perform database writes.",{"id":179,"title":180,"titles":181,"content":182,"level":15},"/appsync-resolver-js#js-resolver-example","JS Resolver Example",[150],"Here is an example of an APPSYNC_JS pipeline resolver function that validates a signup form. import { precognitiveValidation } from '@sot1986/appsync-precognition'\n\nexport function request(ctx) {\n  // Validate request inputs and handle precognitive flows\n  const validatedInput = precognitiveValidation(ctx, {\n    username: ['required', ['min', 3]],\n    email: ['required', 'email'],\n    password: ['required', ['min', 8]]\n  })\n\n  // Normal execution (if it wasn't a precognitive run or validation succeeded)\n  return {\n    operation: 'PutItem',\n    key: util.dynamodb.toMapValues({ id: util.autoId() }),\n    attributeValues: util.dynamodb.toMapValues(validatedInput)\n  }\n}\n\nexport function response(ctx) {\n  return ctx.result\n}",{"id":184,"title":185,"titles":186,"content":187,"level":15},"/appsync-resolver-js#pipeline-resolvers-assertvalidated","Pipeline Resolvers & assertValidated",[150],"In pipeline resolvers, you typically validate the arguments in the first handler. Subsequent handlers in the pipeline can assert that validation occurred before executing database requests or business logic. The library exports assertValidated(ctx), which verifies that ctx.stash.__validated is present. If it is not present (e.g., validation was bypassed), it will immediately raise an error.",{"id":189,"title":190,"titles":191,"content":192,"level":21},"/appsync-resolver-js#_1-authorization-validation-first-handler","1. Authorization & Validation (First Handler)",[150,185],"import { precognitiveValidation } from '@sot1986/appsync-precognition'\n\nexport function request(ctx) {\n  // Perform authorization middleware checks...\n  \n  return {\n    // optional query to fetch parent resource\n  }\n}\n\nexport function response(ctx) {\n  // Perform validation on inputs\n  precognitiveValidation(ctx, {\n    title: ['required', ['max', 100]],\n    description: ['nullable', 'string', ['max', 1000]]\n  })\n}",{"id":194,"title":195,"titles":196,"content":197,"level":21},"/appsync-resolver-js#_2-database-action-subsequent-handler","2. Database Action (Subsequent Handler)",[150,185],"import { assertValidated } from '@sot1986/appsync-precognition'\n\nexport function request(ctx) {\n  // Assert validation has run successfully in a previous handler\n  assertValidated(ctx)\n\n  // Use the validated arguments safely\n  const validated = ctx.stash.__validated\n\n  return {\n    operation: 'PutItem',\n    key: util.dynamodb.toMapValues({ id: validated.id }),\n    attributeValues: util.dynamodb.toMapValues(validated)\n  }\n} html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"id":199,"title":200,"titles":201,"content":202,"level":9},"/appsync-lambda-functions","AppSync Lambda Functions",[],"Run advanced and database-backed form validations inside AWS Lambda for AppSync using Middy. While APPSYNC_JS resolvers provide high performance for direct data-source mapping, writing all business and validation logic exclusively within JS resolvers is not always practical. Many real-world applications require more flexibility—such as querying external databases via ORMs, interacting with third-party APIs, or leveraging complex validation libraries—which can only be achieved inside AWS Lambda functions.",{"id":204,"title":205,"titles":206,"content":207,"level":15},"/appsync-lambda-functions#how-appsync-invokes-lambda","How AppSync Invokes Lambda",[200],"In AWS AppSync, Lambda functions are never invoked directly by the client; they are always executed through an AppSync Resolver (either a Unit Resolver or a Pipeline Resolver step) configured with a Lambda Data Source: Request Phase: The resolver prepares the invoke payload and calls the Lambda Data Source.Lambda Execution: The Lambda function executes your validation and business logic.Response Phase: The resolver receives the Lambda output (ctx.result) or execution error (ctx.error) and formats the GraphQL response.",{"id":209,"title":210,"titles":211,"content":212,"level":15},"/appsync-lambda-functions#the-appsync-lambda-error-challenge","The AppSync Lambda Error Challenge",[200],"When a Lambda function throws an uncaught exception (e.g. throw new Error(...)), AppSync intercepts the failure at the resolver level with significant limitations: Generic Error Types: AppSync automatically marks the error type as Lambda:Unhandled (or LambdaUnhandled).No Structured Metadata: AppSync only extracts the raw string message (ctx.error.message). No structured errorInfo (such as field paths, rejected values, or granular validation error lists) is shared with the client.Strict GraphQL Schema Constraints: Because GraphQL enforces strict return types for fields, the Lambda function cannot simply return an arbitrary error object as normal data without violating the schema.",{"id":214,"title":215,"titles":216,"content":217,"level":21},"/appsync-lambda-functions#the-solution-the-appsyncerror-bridge","The Solution: The AppSyncError Bridge",[200,210],"To communicate rich validation error details (including field paths, error codes, and messages) back to the GraphQL client without triggering unhandled Lambda failures: Lambda returns a structured error payload: Instead of throwing an uncaught exception, the Lambda function returns an AppSyncError structure inside the invocation result (ctx.result.error).The Resolver transforms the payload: The AppSync resolver's response handler inspects ctx.result. When it identifies an AppSyncError object, it intercepts the result and converts each entry into native GraphQL errors using $util.appendError() and $util.error(). The @sot1986/appsync-precognition/middy package provides the tools to implement this workflow effortlessly across your Amplify backend and Lambda functions.",{"id":219,"title":220,"titles":221,"content":222,"level":15},"/appsync-lambda-functions#overriding-the-lambda-resolver-response-template-in-amplify","Overriding the Lambda Resolver Response Template in Amplify",[200],"In AWS Amplify (Gen 2), resolvers connected to Lambda data sources are provisioned using default VTL templates. These standard templates only check for runtime crashes ($ctx.error) and do not know how to unpack structured AppSyncError payloads. To enable rich error mapping, use the resolveLambdaResponseTemplate() helper in your amplify/backend.ts file to override the response mapping template across all Lambda function resolvers in the AppSync GraphQL API stack: import { defineBackend } from '@aws-amplify/backend'\nimport { resolveLambdaResponseTemplate } from '@sot1986/appsync-precognition/middy'\nimport { CfnFunctionConfiguration } from 'aws-cdk-lib/aws-appsync'\nimport { auth } from './auth/resource'\nimport { data } from './data/resource'\nimport { registerUserHandler } from './functions/register-user/resource'\n\nconst backend = defineBackend({\n  auth,\n  data,\n  registerUserHandler,\n})\n\n// Override response mapping templates for all VTL Lambda function resolvers\nbackend.data.resources.graphqlApi.stack.node.findAll().forEach((child) => {\n  if (child instanceof CfnFunctionConfiguration && !child.runtime) {\n    child.responseMappingTemplate = resolveLambdaResponseTemplate()\n    child.responseMappingTemplateS3Location = undefined\n    child.addPropertyDeletionOverride('ResponseMappingTemplateS3Location')\n  }\n})",{"id":224,"title":225,"titles":226,"content":227,"level":21},"/appsync-lambda-functions#what-resolvelambdaresponsetemplate-generates","What resolveLambdaResponseTemplate() Generates",[200,220],"Under the hood, resolveLambdaResponseTemplate() outputs the following Velocity Template Language (VTL) logic: ## [Start] Handle error or return result for request response invocation type. **\n#if( $ctx.error )\n  $util.error($ctx.error.message, $ctx.error.type)\n#end\n## [Custom Error Handling]\n#if( $ctx.result.error && $ctx.result.error.type == 'AppSyncError' && $ctx.result.error.errorsCount > 0 )\n  #set( $errIndex = 0 )\n  #foreach( $err in $ctx.result.error.errors )\n    #set( $errIndex = $errIndex + 1 )\n    #if( $errIndex \u003C $ctx.result.error.errorsCount )\n      $util.appendError($err.message, $err.errorType, null, $err.errorInfo)\n    #else\n      $util.error($err.message, $err.errorType, null, $err.errorInfo)\n    #end\n  #end\n#end\n$util.toJson($ctx.result)\n## [End] Handle error or return result for request response invocation type. ** Runtime Crashes: If the Lambda unhandled crash occurs, $ctx.error triggers a standard AppSync error.Structured Error Bridge: If $ctx.result.error.type == 'AppSyncError', the template iterates through all errors, appending each error with its specific errorType and errorInfo (e.g. { path: \"email\", value: \"invalid\" }).Success: If there are no errors, $util.toJson($ctx.result) returns the Lambda result directly to the GraphQL execution engine.",{"id":229,"title":230,"titles":231,"content":232,"level":15},"/appsync-lambda-functions#the-error-interface-appsyncerror","The Error Interface & AppSyncError",[200],"Sharing error details between AWS Lambda and AppSync resolvers is only possible if the Lambda invocation result matches the exact interface expected by the resolver response template: interface AppSyncErrorPayload {\n  error: {\n    type: 'AppSyncError'\n    errors: Array\u003C{\n      message: string\n      errorType: string\n      errorInfo?: Record\u003Cstring, any> | null\n    }>\n    errorsCount: number\n  }\n}",{"id":234,"title":235,"titles":236,"content":237,"level":21},"/appsync-lambda-functions#the-appsyncerror-base-class","The AppSyncError Base Class",[200,230],"To satisfy this contract, the package exports the AppSyncError class (which implements AppSyncMappedError). If you want a common error interface across both AppSync JS Resolvers and Lambda functions, all custom business errors thrown inside your Lambda function should extend AppSyncError (or implement the errorItems() method): import { AppSyncError } from '@sot1986/appsync-precognition/middy'\n\nexport class NotFoundError extends AppSyncError {\n  constructor(message: string = 'Resource not found', resourceId?: string) {\n    super(message, 'NotFoundError', resourceId ? { id: resourceId } : undefined)\n  }\n}\n\nexport class UnauthorizedError extends AppSyncError {\n  constructor(message: string = 'Unauthorized access') {\n    super(message, 'UnauthorizedError')\n  }\n}",{"id":239,"title":240,"titles":241,"content":242,"level":21},"/appsync-lambda-functions#the-appsyncerrorhandler-middy-middleware","The appsyncErrorHandler Middy Middleware",[200,230],"To manage cross-cutting concerns (such as error handling, validation, and precognitive checks) cleanly without cluttering your core business logic, we use Middy (@middy/core), the de facto standard middleware engine for AWS Lambda in Node.js environments. The module exports the appsyncErrorHandler() middleware for Middy. It automatically intercepts any error thrown during Lambda execution that subclasses AppSyncError (or implements AppSyncMappedError), converts it into the expected AppSyncError payload, and sets request.error = null so the Lambda execution completes cleanly. The AppSync resolver can then parse the payload and proxy the structured errors to the GraphQL client: import middy from '@middy/core'\nimport { AppSyncError, appsyncErrorHandler } from '@sot1986/appsync-precognition/middy'\n\nasync function baseHandler(event: any) {\n  const item = await findItem(event.arguments.id)\n\n  if (!item) {\n    // Thrown AppSyncError is captured by appsyncErrorHandler\n    throw new AppSyncError('Item not found', 'NotFoundError', { id: event.arguments.id })\n  }\n\n  return item\n}\n\nexport const handler = middy(baseHandler)\n  .use(appsyncErrorHandler())",{"id":244,"title":245,"titles":246,"content":247,"level":15},"/appsync-lambda-functions#full-example-validating-with-zod","Full Example: Validating with Zod",[200],"By combining precognition() and appsyncErrorHandler(), you can create a reusable Middy validation middleware that converts Zod schema errors directly into AppSyncError validation responses.",{"id":249,"title":250,"titles":251,"content":252,"level":21},"/appsync-lambda-functions#_1-create-the-reusable-zod-middleware","1. Create the Reusable Zod Middleware",[200,245],"Create a parser helper that wraps precognition() and maps z.ZodError issues into standard validation paths and messages: import type middy from '@middy/core'\nimport { precognition } from '@sot1986/appsync-precognition/middy'\nimport * as z from 'zod'\n\nexport function parser\u003CTSchema extends z.ZodType, TResult>(options: {\n  schema: TSchema\n}): middy.MiddlewareObj\u003Cz.infer\u003CTSchema>, TResult> {\n  return precognition({\n    validator: event => options.schema.parse(event),\n    toValidationErrors: (error) => {\n      if (error instanceof z.ZodError === false)\n        return null\n\n      return error.issues.map((issue) => {\n        // Strip top-level 'arguments' prefix so path matches form keys (e.g. 'email' instead of 'arguments.email')\n        const path = issue.path.at(0) === 'arguments' ? issue.path.slice(1) : issue.path\n\n        return {\n          path: path.map(String),\n          message: issue.message,\n          value: issue.input,\n        }\n      })\n    },\n  })\n}",{"id":254,"title":255,"titles":256,"content":257,"level":21},"/appsync-lambda-functions#_2-implement-the-lambda-handler","2. Implement the Lambda Handler",[200,245],"Use the parser middleware alongside appsyncErrorHandler() in your Lambda function: import middy from '@middy/core'\nimport { appsyncErrorHandler } from '@sot1986/appsync-precognition/middy'\nimport * as z from 'zod'\nimport { parser } from '../_middlewares/validate'\n\n// 1. Define the input validation schema\nconst CreateUserEventSchema = z.object({\n  arguments: z.object({\n    email: z.string().email('Please enter a valid email address'),\n    name: z.string().min(3, 'Name must be at least 3 characters'),\n    age: z.number().min(18, 'Must be at least 18 years old'),\n  }),\n})\n\n// 2. Base handler (only runs if validation passes and request is NOT precognitive)\nasync function baseHandler(event: z.infer\u003Ctypeof CreateUserEventSchema>) {\n  const { email, name, age } = event.arguments\n\n  // Perform database write / business logic\n  return {\n    id: 'user_123',\n    email,\n    name,\n    age,\n    createdAt: new Date().toISOString(),\n  }\n}\n\n// 3. Compose with Middy\nexport const handler = middy(baseHandler)\n  .use(appsyncErrorHandler())\n  .use(parser({ schema: CreateUserEventSchema }))",{"id":259,"title":260,"titles":261,"content":262,"level":21},"/appsync-lambda-functions#what-happens-behind-the-scenes","What Happens Behind the Scenes:",[200,245],"Precognitive Request: If headers contain Precognition: true, the parser middleware validates the schema. On success, it immediately returns { data: null } with Precognition-Success: true, skipping baseHandler.Validate-Only: If Precognition-Validate-Only: email is sent, only errors on the email field will be reported back.Validation Errors: When Zod throws a ZodError, parser converts it to PrecognitionValidationError (a subclass of AppSyncError), which appsyncErrorHandler transforms into { error: { type: 'AppSyncError', errors, errorsCount } }.GraphQL Error Response: The AppSync resolver response template unpacks this error structure with $util.appendError(), delivering granular field errors to your Nuxt frontend form.",{"id":264,"title":265,"titles":266,"content":267,"level":15},"/appsync-lambda-functions#summary-flow-chart","Summary Flow Chart",[200],"The following diagram illustrates the complete end-to-end lifecycle of a request across the AppSync resolver, Lambda function, Middy middleware pipeline, and the client: Client dispatches a GraphQL mutation with Precognition headers (Precognition, Precognition-Validate-Only).Resolver receives the GraphQL request and invokes the AWS Lambda Data Source passing the context payload ($ctx.args and request headers).The precognition() middleware validates input. If validation fails or business checks fail, an AppSyncError (or PrecognitionValidationError) is thrown.The appsyncErrorHandler() middleware catches the error, formats it into the structured result payload { error: { type: 'AppSyncError', errors, errorsCount } }, and clears request.error so Lambda succeeds without crashing.The VTL response template inspects $ctx.result.error, recognizes AppSyncError, and iterates calling $util.appendError() and $util.error() with errorType and errorInfo.The Nuxt client receives a standard GraphQL error response with full validation metadata in errorInfo, seamlessly binding error messages to each corresponding form input.",{"id":269,"title":270,"titles":271,"content":272,"level":21},"/appsync-lambda-functions#flow-steps-breakdown","Flow Steps Breakdown",[200,265],"Client Request: The client dispatches a GraphQL mutation along with Precognition HTTP headers (Precognition, Precognition-Validate-Only).Resolver Invocation: The AppSync resolver request mapping forwards the request arguments and headers to the Lambda Data Source.Middy Validation: The precognition() middleware validates the input before the handler runs. If validation fails or business logic encounters an issue, an AppSyncError (or PrecognitionValidationError) is thrown.Error Transformation: The appsyncErrorHandler middleware catches the error and formats it into the expected { error: { type: 'AppSyncError', errors, errorsCount } } structure, completing the Lambda invocation cleanly without crashing.Lambda Response: The Lambda function returns this structured object as its invocation result ($ctx.result).Resolver Error Mapping: The AppSync resolver response template (resolveLambdaResponseTemplate()) detects type == 'AppSyncError', loops over errors, and invokes $util.appendError() / $util.error().Client Error Handling: AppSync delivers a standard GraphQL errors response with complete errorType and errorInfo metadata, which the Nuxt Precognition client parses and maps directly to the corresponding form fields. html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"id":274,"title":275,"titles":276,"content":277,"level":9},"/appsync-client-integration","Client-Side Integration",[],"Learn how to configure your GraphQL client and Nuxt Precognition to handle AppSync validation. For Nuxt Precognition's useForm to detect validation failures and populate the form's error state, the submission callback must throw an exception when validation errors are present. GraphQL endpoints typically return a standard HTTP 200 OK status code even when execution or validation encounters an error, returning the error details inside the errors array in the response payload. Because both AppSync JS Resolvers and AWS Lambda Resolvers (via @sot1986/appsync-precognition/middy) output the same unified AppSync error structure (errorType: 'ValidationError' and errorInfo: { path, value }), the client manages both resolver types in the exact same, consistent way.",{"id":279,"title":280,"titles":281,"content":282,"level":15},"/appsync-client-integration#architecture-overview","Architecture Overview",[275],"The client-side integration consists of four main parts: GraphQL Error Utility & ValidationError Class: A parser and error class that extract field paths and messages from AppSync GraphQL errors.Amplify Client Plugin (01.amplify.client.ts): A Nuxt plugin wrapping the AWS Amplify GraphQL client in a Proxy to intercept errors and throw ValidationError.Precognition Parser Plugin (02.precognitionParser.ts): Registers the GraphQL and Zod error parsers with Nuxt Precognition.Form Composable Usage: Using useForm to forward precognitive headers to your GraphQL mutations.",{"id":284,"title":285,"titles":286,"content":287,"level":15},"/appsync-client-integration#_1-error-parser-utility-validationerror-class","1. Error Parser Utility & ValidationError Class",[275],"Create a utility file that defines the ValidationError class and the graphQlValidation parser to extract validation errors from AppSync responses: import * as z from 'zod'\n\nexport interface GraphQlFormattedError {\n  message: string\n  errorType?: string\n  data?: any\n  errorInfo?: Record\u003Cstring, unknown> | null\n}\n\nexport class ValidationError extends Error {\n  constructor(message: string, issues: GraphQlFormattedError[]) {\n    super(message)\n    this.name = 'ValidationError'\n    this.cause = issues\n  }\n\n  get issues(): GraphQlFormattedError[] {\n    return this.cause as GraphQlFormattedError[]\n  }\n}\n\n// 1. Parser for Client-Side Zod Validation Errors\nexport function zodErrorParser(error: Error) {\n  if (error instanceof z.ZodError) {\n    const errors = {} as Record\u003Cstring, string[]>\n    error.issues.forEach((value) => {\n      const key = value.path.join('.')\n      if (key in errors) {\n        (errors[key] as string[]).push(value.message)\n        return\n      }\n      errors[key] = [value.message]\n    })\n    return { errors, message: 'Validation error' }\n  }\n  return null\n}\n\n// 2. Parser for AppSync GraphQL Validation Errors (Both JS Resolvers & Lambdas)\nexport function graphQlValidation(error: Error) {\n  if (error instanceof ValidationError) {\n    const errors = {} as Record\u003Cstring, string[]>\n    error.issues.forEach((issue) => {\n      const key = typeof issue.errorInfo?.path === 'string'\n        ? issue.errorInfo?.path\n        : Array.isArray(issue.errorInfo?.path)\n          ? issue.errorInfo.path.join('.')\n          : null\n\n      if (!key)\n        return\n\n      if (key in errors) {\n        (errors[key] as string[]).push(issue.message)\n        return\n      }\n      errors[key] = [issue.message]\n    })\n    return { errors, message: error.message || 'Validation error' }\n  }\n  return null\n}\n\n// 3. Central error dispatcher for AppSync responses\nexport function handleAppSyncGraphQlErrors(errors: GraphQlFormattedError[]): never {\n  const firstError = errors.at(0)\n\n  switch (firstError?.errorType) {\n    case 'ValidationError':\n      // Throws ValidationError so Precognition's useForm can catch and parse it\n      throw new ValidationError(firstError.message, errors)\n    case 'ResourceNotFound':\n      throw createError({ statusCode: 404, statusMessage: firstError.message })\n    case 'Unauthorized':\n      throw createError({ statusCode: 401, statusMessage: firstError.message })\n    default:\n      throw createError({ statusCode: 500, statusMessage: firstError?.message || 'GraphQL Error' })\n  }\n}",{"id":289,"title":290,"titles":291,"content":292,"level":15},"/appsync-client-integration#_2-amplify-client-plugin-01amplifyclientts","2. Amplify Client Plugin (01.amplify.client.ts)",[275],"Create a Nuxt plugin that initializes AWS Amplify and wraps the GraphQL client mutations and queries with a Proxy. When AppSync returns GraphQL errors, the proxy intercepts them and calls handleAppSyncGraphQlErrors: import type { Schema } from '@amplify-data-schema'\nimport outputs from '#amplify-outputs'\nimport { Amplify } from 'aws-amplify'\nimport { generateClient } from 'aws-amplify/api'\nimport { handleAppSyncGraphQlErrors } from '~/utils/zodParser'\n\nexport default defineNuxtPlugin({\n  name: 'AmplifyClientPlugin',\n  enforce: 'pre',\n  setup() {\n    Amplify.configure(outputs, { ssr: false })\n\n    const rawClient = generateClient\u003CSchema>({\n      authMode: 'userPool',\n    })\n\n    // Proxy client mutations to intercept GraphQL errors\n    const client = {\n      ...rawClient,\n      mutations: new Proxy(rawClient.mutations, {\n        get(target, prop) {\n          if (typeof target[prop as keyof typeof target] !== 'function') {\n            return target[prop as keyof typeof target]\n          }\n          return async (...input: any[]) => {\n            const result = await (target[prop as keyof typeof target] as any)(...input)\n\n            if (result.errors?.length) {\n              handleAppSyncGraphQlErrors(result.errors)\n            }\n\n            return result\n          }\n        },\n      }),\n      queries: new Proxy(rawClient.queries, {\n        get(target, prop) {\n          if (typeof target[prop as keyof typeof target] !== 'function') {\n            return target[prop as keyof typeof target]\n          }\n          return async (...input: any[]) => {\n            const result = await (target[prop as keyof typeof target] as any)(...input)\n\n            if (result.errors?.length) {\n              handleAppSyncGraphQlErrors(result.errors)\n            }\n\n            return result\n          }\n        },\n      }),\n    }\n\n    return {\n      provide: {\n        Amplify: {\n          GraphQL: {\n            client,\n          },\n        },\n      },\n    }\n  },\n})",{"id":294,"title":295,"titles":296,"content":297,"level":15},"/appsync-client-integration#_3-registering-the-precognition-parser-02precognitionparserts","3. Registering the Precognition Parser (02.precognitionParser.ts)",[275],"Register the graphQlValidation and zodErrorParser handlers in Nuxt Precognition during app initialization: import { graphQlValidation, zodErrorParser } from '~/utils/zodParser'\n\nexport default defineNuxtPlugin({\n  name: 'PrecognitionParser',\n  setup() {\n    const { $precognition } = useNuxtApp()\n    $precognition.errorParsers.push(graphQlValidation, zodErrorParser)\n  },\n})",{"id":299,"title":300,"titles":301,"content":302,"level":15},"/appsync-client-integration#_4-using-in-forms-with-useform","4. Using in Forms with useForm",[275],"In your form composables or page components, use Nuxt Precognition's useForm and pass the second argument (headers) directly to the GraphQL mutation options. This automatically sends Precognition: true and Precognition-Validate-Only: \u003Cfield> headers when validating individual fields: import { useForm } from 'nuxt-precognition'\n\nexport function useRegisterForm() {\n  const { $Amplify } = useNuxtApp()\n\n  const form = useForm(\n    () => ({\n      email: '',\n      name: '',\n      password: '',\n    }),\n    (data, headers) => {\n      return $Amplify.GraphQL.client.mutations.createUser({\n        email: data.email,\n        name: data.name,\n        password: data.password,\n      }, {\n        // Forward Precognition headers to the AppSync GraphQL request\n        headers,\n      })\n    },\n  )\n\n  return { form }\n} \u003Cscript setup lang=\"ts\">\nconst { form } = useRegisterForm()\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"form.submit()\">\n    \u003Cdiv>\n      \u003Clabel>Email\u003C/label>\n      \u003Cinput\n        v-model=\"form.email\"\n        type=\"email\"\n        @change=\"form.validate('email')\"\n      >\n      \u003Cspan v-if=\"form.invalid('email')\" class=\"text-red-500 text-sm\">\n        {{ form.errors.email }}\n      \u003C/span>\n    \u003C/div>\n\n    \u003Cdiv>\n      \u003Clabel>Name\u003C/label>\n      \u003Cinput\n        v-model=\"form.name\"\n        type=\"text\"\n        @change=\"form.validate('name')\"\n      >\n      \u003Cspan v-if=\"form.invalid('name')\" class=\"text-red-500 text-sm\">\n        {{ form.errors.name }}\n      \u003C/span>\n    \u003C/div>\n\n    \u003Cbutton type=\"submit\" :disabled=\"form.processing\">\n      {{ form.processing ? 'Submitting...' : 'Register' }}\n    \u003C/button>\n  \u003C/form>\n\u003C/template> html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}",[304,308,310,312,315,317,320,322,324,327,329,331],{"title":6,"path":5,"stem":305,"section":306,"navigation":307},"01.index","intro",true,{"title":29,"path":28,"stem":309,"section":306,"navigation":307},"02.installation",{"title":39,"path":38,"stem":311,"section":306,"navigation":307},"03.how-it-works",{"title":54,"path":53,"stem":313,"section":314,"navigation":307},"04.base-example","example",{"title":90,"path":89,"stem":316,"section":314,"navigation":307},"05.file-upload",{"title":100,"path":99,"stem":318,"section":319,"navigation":307},"06.config","advanced",{"title":115,"path":114,"stem":321,"section":319,"navigation":307},"07.laravel-integration",{"title":125,"path":124,"stem":323,"section":319,"navigation":307},"08.protocol",{"title":130,"path":129,"stem":325,"section":326,"navigation":307},"09.appsync","appsync",{"title":150,"path":149,"stem":328,"section":326,"navigation":307},"10.appsync-resolver-js",{"title":200,"path":199,"stem":330,"section":326,"navigation":307},"11.appsync-lambda-functions",{"title":275,"path":274,"stem":332,"section":326,"navigation":307},"12.appsync-client-integration",{"id":334,"title":200,"body":335,"description":2515,"excerpt":2516,"extension":2524,"meta":2525,"navigation":307,"path":199,"section":326,"seo":2526,"sitemap":2527,"stem":330,"__hash__":2528},"pages/11.appsync-lambda-functions.md",{"type":336,"value":337,"toc":2494},"minimark",[338,352,356,363,393,396,403,440,444,447,482,489,492,502,513,775,782,788,884,924,930,933,1056,1062,1071,1081,1244,1251,1273,1293,1296,1470,1473,1489,1492,1506,1862,1865,1874,2225,2228,2299,2302,2305,2403,2406,2490],[339,340,341,342,346,347,351],"p",{},"While ",[343,344,345],"code",{},"APPSYNC_JS"," resolvers provide high performance for direct data-source mapping, writing all business and validation logic exclusively within JS resolvers is not always practical. Many real-world applications require more flexibility—such as querying external databases via ORMs, interacting with third-party APIs, or leveraging complex validation libraries—which can only be achieved inside ",[348,349,350],"strong",{},"AWS Lambda"," functions.",[353,354,205],"h2",{"id":355},"how-appsync-invokes-lambda",[339,357,358,359,362],{},"In AWS AppSync, Lambda functions are never invoked directly by the client; they are always executed through an ",[348,360,361],{},"AppSync Resolver"," (either a Unit Resolver or a Pipeline Resolver step) configured with a Lambda Data Source:",[364,365,366,373,379],"ol",{},[367,368,369,372],"li",{},[348,370,371],{},"Request Phase:"," The resolver prepares the invoke payload and calls the Lambda Data Source.",[367,374,375,378],{},[348,376,377],{},"Lambda Execution:"," The Lambda function executes your validation and business logic.",[367,380,381,384,385,388,389,392],{},[348,382,383],{},"Response Phase:"," The resolver receives the Lambda output (",[343,386,387],{},"ctx.result",") or execution error (",[343,390,391],{},"ctx.error",") and formats the GraphQL response.",[353,394,210],{"id":395},"the-appsync-lambda-error-challenge",[339,397,398,399,402],{},"When a Lambda function throws an uncaught exception (e.g. ",[343,400,401],{},"throw new Error(...)","), AppSync intercepts the failure at the resolver level with significant limitations:",[404,405,406,420,434],"ul",{},[367,407,408,411,412,415,416,419],{},[348,409,410],{},"Generic Error Types:"," AppSync automatically marks the error type as ",[343,413,414],{},"Lambda:Unhandled"," (or ",[343,417,418],{},"LambdaUnhandled",").",[367,421,422,425,426,429,430,433],{},[348,423,424],{},"No Structured Metadata:"," AppSync only extracts the raw string message (",[343,427,428],{},"ctx.error.message","). No structured ",[343,431,432],{},"errorInfo"," (such as field paths, rejected values, or granular validation error lists) is shared with the client.",[367,435,436,439],{},[348,437,438],{},"Strict GraphQL Schema Constraints:"," Because GraphQL enforces strict return types for fields, the Lambda function cannot simply return an arbitrary error object as normal data without violating the schema.",[441,442,215],"h3",{"id":443},"the-solution-the-appsyncerror-bridge",[339,445,446],{},"To communicate rich validation error details (including field paths, error codes, and messages) back to the GraphQL client without triggering unhandled Lambda failures:",[364,448,449,462],{},[367,450,451,454,455,458,459,419],{},[348,452,453],{},"Lambda returns a structured error payload:"," Instead of throwing an uncaught exception, the Lambda function returns an ",[343,456,457],{},"AppSyncError"," structure inside the invocation result (",[343,460,461],{},"ctx.result.error",[367,463,464,467,468,470,471,473,474,477,478,481],{},[348,465,466],{},"The Resolver transforms the payload:"," The AppSync resolver's response handler inspects ",[343,469,387],{},". When it identifies an ",[343,472,457],{}," object, it intercepts the result and converts each entry into native GraphQL errors using ",[343,475,476],{},"$util.appendError()"," and ",[343,479,480],{},"$util.error()",".",[339,483,484,485,488],{},"The ",[343,486,487],{},"@sot1986/appsync-precognition/middy"," package provides the tools to implement this workflow effortlessly across your Amplify backend and Lambda functions.",[353,490,220],{"id":491},"overriding-the-lambda-resolver-response-template-in-amplify",[339,493,494,495,498,499,501],{},"In AWS Amplify (Gen 2), resolvers connected to Lambda data sources are provisioned using default VTL templates. These standard templates only check for runtime crashes (",[343,496,497],{},"$ctx.error",") and do not know how to unpack structured ",[343,500,457],{}," payloads.",[339,503,504,505,508,509,512],{},"To enable rich error mapping, use the ",[343,506,507],{},"resolveLambdaResponseTemplate()"," helper in your ",[343,510,511],{},"amplify/backend.ts"," file to override the response mapping template across all Lambda function resolvers in the AppSync GraphQL API stack:",[514,515,520],"pre",{"className":516,"code":517,"filename":511,"language":518,"meta":519,"style":519},"language-typescript shiki shiki-themes github-dark github-dark","import { defineBackend } from '@aws-amplify/backend'\nimport { resolveLambdaResponseTemplate } from '@sot1986/appsync-precognition/middy'\nimport { CfnFunctionConfiguration } from 'aws-cdk-lib/aws-appsync'\nimport { auth } from './auth/resource'\nimport { data } from './data/resource'\nimport { registerUserHandler } from './functions/register-user/resource'\n\nconst backend = defineBackend({\n  auth,\n  data,\n  registerUserHandler,\n})\n\n// Override response mapping templates for all VTL Lambda function resolvers\nbackend.data.resources.graphqlApi.stack.node.findAll().forEach((child) => {\n  if (child instanceof CfnFunctionConfiguration && !child.runtime) {\n    child.responseMappingTemplate = resolveLambdaResponseTemplate()\n    child.responseMappingTemplateS3Location = undefined\n    child.addPropertyDeletionOverride('ResponseMappingTemplateS3Location')\n  }\n})\n","typescript","",[343,521,522,541,553,565,577,590,603,609,629,635,641,647,653,658,665,696,720,735,746,764,770],{"__ignoreMap":519},[523,524,526,530,534,537],"span",{"class":525,"line":9},"line",[523,527,529],{"class":528},"sOPea","import",[523,531,533],{"class":532},"suv1-"," { defineBackend } ",[523,535,536],{"class":528},"from",[523,538,540],{"class":539},"s4wv1"," '@aws-amplify/backend'\n",[523,542,543,545,548,550],{"class":525,"line":15},[523,544,529],{"class":528},[523,546,547],{"class":532}," { resolveLambdaResponseTemplate } ",[523,549,536],{"class":528},[523,551,552],{"class":539}," '@sot1986/appsync-precognition/middy'\n",[523,554,555,557,560,562],{"class":525,"line":21},[523,556,529],{"class":528},[523,558,559],{"class":532}," { CfnFunctionConfiguration } ",[523,561,536],{"class":528},[523,563,564],{"class":539}," 'aws-cdk-lib/aws-appsync'\n",[523,566,567,569,572,574],{"class":525,"line":72},[523,568,529],{"class":528},[523,570,571],{"class":532}," { auth } ",[523,573,536],{"class":528},[523,575,576],{"class":539}," './auth/resource'\n",[523,578,580,582,585,587],{"class":525,"line":579},5,[523,581,529],{"class":528},[523,583,584],{"class":532}," { data } ",[523,586,536],{"class":528},[523,588,589],{"class":539}," './data/resource'\n",[523,591,593,595,598,600],{"class":525,"line":592},6,[523,594,529],{"class":528},[523,596,597],{"class":532}," { registerUserHandler } ",[523,599,536],{"class":528},[523,601,602],{"class":539}," './functions/register-user/resource'\n",[523,604,606],{"class":525,"line":605},7,[523,607,608],{"emptyLinePlaceholder":307},"\n",[523,610,612,615,619,622,626],{"class":525,"line":611},8,[523,613,614],{"class":528},"const",[523,616,618],{"class":617},"s8ozJ"," backend",[523,620,621],{"class":528}," =",[523,623,625],{"class":624},"sFR8T"," defineBackend",[523,627,628],{"class":532},"({\n",[523,630,632],{"class":525,"line":631},9,[523,633,634],{"class":532},"  auth,\n",[523,636,638],{"class":525,"line":637},10,[523,639,640],{"class":532},"  data,\n",[523,642,644],{"class":525,"line":643},11,[523,645,646],{"class":532},"  registerUserHandler,\n",[523,648,650],{"class":525,"line":649},12,[523,651,652],{"class":532},"})\n",[523,654,656],{"class":525,"line":655},13,[523,657,608],{"emptyLinePlaceholder":307},[523,659,661],{"class":525,"line":660},14,[523,662,664],{"class":663},"sJ8bj","// Override response mapping templates for all VTL Lambda function resolvers\n",[523,666,668,671,674,677,680,683,687,690,693],{"class":525,"line":667},15,[523,669,670],{"class":532},"backend.data.resources.graphqlApi.stack.node.",[523,672,673],{"class":624},"findAll",[523,675,676],{"class":532},"().",[523,678,679],{"class":624},"forEach",[523,681,682],{"class":532},"((",[523,684,686],{"class":685},"s-3mD","child",[523,688,689],{"class":532},") ",[523,691,692],{"class":528},"=>",[523,694,695],{"class":532}," {\n",[523,697,699,702,705,708,711,714,717],{"class":525,"line":698},16,[523,700,701],{"class":528},"  if",[523,703,704],{"class":532}," (child ",[523,706,707],{"class":528},"instanceof",[523,709,710],{"class":624}," CfnFunctionConfiguration",[523,712,713],{"class":528}," &&",[523,715,716],{"class":528}," !",[523,718,719],{"class":532},"child.runtime) {\n",[523,721,723,726,729,732],{"class":525,"line":722},17,[523,724,725],{"class":532},"    child.responseMappingTemplate ",[523,727,728],{"class":528},"=",[523,730,731],{"class":624}," resolveLambdaResponseTemplate",[523,733,734],{"class":532},"()\n",[523,736,738,741,743],{"class":525,"line":737},18,[523,739,740],{"class":532},"    child.responseMappingTemplateS3Location ",[523,742,728],{"class":528},[523,744,745],{"class":617}," undefined\n",[523,747,749,752,755,758,761],{"class":525,"line":748},19,[523,750,751],{"class":532},"    child.",[523,753,754],{"class":624},"addPropertyDeletionOverride",[523,756,757],{"class":532},"(",[523,759,760],{"class":539},"'ResponseMappingTemplateS3Location'",[523,762,763],{"class":532},")\n",[523,765,767],{"class":525,"line":766},20,[523,768,769],{"class":532},"  }\n",[523,771,773],{"class":525,"line":772},21,[523,774,652],{"class":532},[441,776,778,779,781],{"id":777},"what-resolvelambdaresponsetemplate-generates","What ",[343,780,507],{}," Generates",[339,783,784,785,787],{},"Under the hood, ",[343,786,507],{}," outputs the following Velocity Template Language (VTL) logic:",[514,789,793],{"className":790,"code":791,"language":792,"meta":519,"style":519},"language-vtl shiki shiki-themes github-dark github-dark","## [Start] Handle error or return result for request response invocation type. **\n#if( $ctx.error )\n  $util.error($ctx.error.message, $ctx.error.type)\n#end\n## [Custom Error Handling]\n#if( $ctx.result.error && $ctx.result.error.type == 'AppSyncError' && $ctx.result.error.errorsCount > 0 )\n  #set( $errIndex = 0 )\n  #foreach( $err in $ctx.result.error.errors )\n    #set( $errIndex = $errIndex + 1 )\n    #if( $errIndex \u003C $ctx.result.error.errorsCount )\n      $util.appendError($err.message, $err.errorType, null, $err.errorInfo)\n    #else\n      $util.error($err.message, $err.errorType, null, $err.errorInfo)\n    #end\n  #end\n#end\n$util.toJson($ctx.result)\n## [End] Handle error or return result for request response invocation type. **\n","vtl",[343,794,795,800,805,810,815,820,825,830,835,840,845,850,855,860,865,870,874,879],{"__ignoreMap":519},[523,796,797],{"class":525,"line":9},[523,798,799],{},"## [Start] Handle error or return result for request response invocation type. **\n",[523,801,802],{"class":525,"line":15},[523,803,804],{},"#if( $ctx.error )\n",[523,806,807],{"class":525,"line":21},[523,808,809],{},"  $util.error($ctx.error.message, $ctx.error.type)\n",[523,811,812],{"class":525,"line":72},[523,813,814],{},"#end\n",[523,816,817],{"class":525,"line":579},[523,818,819],{},"## [Custom Error Handling]\n",[523,821,822],{"class":525,"line":592},[523,823,824],{},"#if( $ctx.result.error && $ctx.result.error.type == 'AppSyncError' && $ctx.result.error.errorsCount > 0 )\n",[523,826,827],{"class":525,"line":605},[523,828,829],{},"  #set( $errIndex = 0 )\n",[523,831,832],{"class":525,"line":611},[523,833,834],{},"  #foreach( $err in $ctx.result.error.errors )\n",[523,836,837],{"class":525,"line":631},[523,838,839],{},"    #set( $errIndex = $errIndex + 1 )\n",[523,841,842],{"class":525,"line":637},[523,843,844],{},"    #if( $errIndex \u003C $ctx.result.error.errorsCount )\n",[523,846,847],{"class":525,"line":643},[523,848,849],{},"      $util.appendError($err.message, $err.errorType, null, $err.errorInfo)\n",[523,851,852],{"class":525,"line":649},[523,853,854],{},"    #else\n",[523,856,857],{"class":525,"line":655},[523,858,859],{},"      $util.error($err.message, $err.errorType, null, $err.errorInfo)\n",[523,861,862],{"class":525,"line":660},[523,863,864],{},"    #end\n",[523,866,867],{"class":525,"line":667},[523,868,869],{},"  #end\n",[523,871,872],{"class":525,"line":698},[523,873,814],{},[523,875,876],{"class":525,"line":722},[523,877,878],{},"$util.toJson($ctx.result)\n",[523,880,881],{"class":525,"line":737},[523,882,883],{},"## [End] Handle error or return result for request response invocation type. **\n",[404,885,886,895,914],{},[367,887,888,891,892,894],{},[348,889,890],{},"Runtime Crashes:"," If the Lambda unhandled crash occurs, ",[343,893,497],{}," triggers a standard AppSync error.",[367,896,897,900,901,904,905,477,908,910,911,419],{},[348,898,899],{},"Structured Error Bridge:"," If ",[343,902,903],{},"$ctx.result.error.type == 'AppSyncError'",", the template iterates through all errors, appending each error with its specific ",[343,906,907],{},"errorType",[343,909,432],{}," (e.g. ",[343,912,913],{},"{ path: \"email\", value: \"invalid\" }",[367,915,916,919,920,923],{},[348,917,918],{},"Success:"," If there are no errors, ",[343,921,922],{},"$util.toJson($ctx.result)"," returns the Lambda result directly to the GraphQL execution engine.",[353,925,927,928],{"id":926},"the-error-interface-appsyncerror","The Error Interface & ",[343,929,457],{},[339,931,932],{},"Sharing error details between AWS Lambda and AppSync resolvers is only possible if the Lambda invocation result matches the exact interface expected by the resolver response template:",[514,934,936],{"className":516,"code":935,"language":518,"meta":519,"style":519},"interface AppSyncErrorPayload {\n  error: {\n    type: 'AppSyncError'\n    errors: Array\u003C{\n      message: string\n      errorType: string\n      errorInfo?: Record\u003Cstring, any> | null\n    }>\n    errorsCount: number\n  }\n}\n",[343,937,938,948,958,968,981,991,1000,1032,1037,1047,1051],{"__ignoreMap":519},[523,939,940,943,946],{"class":525,"line":9},[523,941,942],{"class":528},"interface",[523,944,945],{"class":624}," AppSyncErrorPayload",[523,947,695],{"class":532},[523,949,950,953,956],{"class":525,"line":15},[523,951,952],{"class":685},"  error",[523,954,955],{"class":528},":",[523,957,695],{"class":532},[523,959,960,963,965],{"class":525,"line":21},[523,961,962],{"class":685},"    type",[523,964,955],{"class":528},[523,966,967],{"class":539}," 'AppSyncError'\n",[523,969,970,973,975,978],{"class":525,"line":72},[523,971,972],{"class":685},"    errors",[523,974,955],{"class":528},[523,976,977],{"class":624}," Array",[523,979,980],{"class":532},"\u003C{\n",[523,982,983,986,988],{"class":525,"line":579},[523,984,985],{"class":685},"      message",[523,987,955],{"class":528},[523,989,990],{"class":617}," string\n",[523,992,993,996,998],{"class":525,"line":592},[523,994,995],{"class":685},"      errorType",[523,997,955],{"class":528},[523,999,990],{"class":617},[523,1001,1002,1005,1008,1011,1014,1017,1020,1023,1026,1029],{"class":525,"line":605},[523,1003,1004],{"class":685},"      errorInfo",[523,1006,1007],{"class":528},"?:",[523,1009,1010],{"class":624}," Record",[523,1012,1013],{"class":532},"\u003C",[523,1015,1016],{"class":617},"string",[523,1018,1019],{"class":532},", ",[523,1021,1022],{"class":617},"any",[523,1024,1025],{"class":532},"> ",[523,1027,1028],{"class":528},"|",[523,1030,1031],{"class":617}," null\n",[523,1033,1034],{"class":525,"line":611},[523,1035,1036],{"class":532},"    }>\n",[523,1038,1039,1042,1044],{"class":525,"line":631},[523,1040,1041],{"class":685},"    errorsCount",[523,1043,955],{"class":528},[523,1045,1046],{"class":617}," number\n",[523,1048,1049],{"class":525,"line":637},[523,1050,769],{"class":532},[523,1052,1053],{"class":525,"line":643},[523,1054,1055],{"class":532},"}\n",[441,1057,484,1059,1061],{"id":1058},"the-appsyncerror-base-class",[343,1060,457],{}," Base Class",[339,1063,1064,1065,1067,1068,419],{},"To satisfy this contract, the package exports the ",[343,1066,457],{}," class (which implements ",[343,1069,1070],{},"AppSyncMappedError",[339,1072,1073,1074,1076,1077,1080],{},"If you want a common error interface across both AppSync JS Resolvers and Lambda functions, all custom business errors thrown inside your Lambda function should extend ",[343,1075,457],{}," (or implement the ",[343,1078,1079],{},"errorItems()"," method):",[514,1082,1084],{"className":516,"code":1083,"language":518,"meta":519,"style":519},"import { AppSyncError } from '@sot1986/appsync-precognition/middy'\n\nexport class NotFoundError extends AppSyncError {\n  constructor(message: string = 'Resource not found', resourceId?: string) {\n    super(message, 'NotFoundError', resourceId ? { id: resourceId } : undefined)\n  }\n}\n\nexport class UnauthorizedError extends AppSyncError {\n  constructor(message: string = 'Unauthorized access') {\n    super(message, 'UnauthorizedError')\n  }\n}\n",[343,1085,1086,1097,1101,1120,1152,1179,1183,1187,1191,1206,1225,1236,1240],{"__ignoreMap":519},[523,1087,1088,1090,1093,1095],{"class":525,"line":9},[523,1089,529],{"class":528},[523,1091,1092],{"class":532}," { AppSyncError } ",[523,1094,536],{"class":528},[523,1096,552],{"class":539},[523,1098,1099],{"class":525,"line":15},[523,1100,608],{"emptyLinePlaceholder":307},[523,1102,1103,1106,1109,1112,1115,1118],{"class":525,"line":21},[523,1104,1105],{"class":528},"export",[523,1107,1108],{"class":528}," class",[523,1110,1111],{"class":624}," NotFoundError",[523,1113,1114],{"class":528}," extends",[523,1116,1117],{"class":624}," AppSyncError",[523,1119,695],{"class":532},[523,1121,1122,1125,1127,1130,1132,1135,1137,1140,1142,1145,1147,1149],{"class":525,"line":72},[523,1123,1124],{"class":528},"  constructor",[523,1126,757],{"class":532},[523,1128,1129],{"class":685},"message",[523,1131,955],{"class":528},[523,1133,1134],{"class":617}," string",[523,1136,621],{"class":528},[523,1138,1139],{"class":539}," 'Resource not found'",[523,1141,1019],{"class":532},[523,1143,1144],{"class":685},"resourceId",[523,1146,1007],{"class":528},[523,1148,1134],{"class":617},[523,1150,1151],{"class":532},") {\n",[523,1153,1154,1157,1160,1163,1166,1169,1172,1174,1177],{"class":525,"line":579},[523,1155,1156],{"class":617},"    super",[523,1158,1159],{"class":532},"(message, ",[523,1161,1162],{"class":539},"'NotFoundError'",[523,1164,1165],{"class":532},", resourceId ",[523,1167,1168],{"class":528},"?",[523,1170,1171],{"class":532}," { id: resourceId } ",[523,1173,955],{"class":528},[523,1175,1176],{"class":617}," undefined",[523,1178,763],{"class":532},[523,1180,1181],{"class":525,"line":592},[523,1182,769],{"class":532},[523,1184,1185],{"class":525,"line":605},[523,1186,1055],{"class":532},[523,1188,1189],{"class":525,"line":611},[523,1190,608],{"emptyLinePlaceholder":307},[523,1192,1193,1195,1197,1200,1202,1204],{"class":525,"line":631},[523,1194,1105],{"class":528},[523,1196,1108],{"class":528},[523,1198,1199],{"class":624}," UnauthorizedError",[523,1201,1114],{"class":528},[523,1203,1117],{"class":624},[523,1205,695],{"class":532},[523,1207,1208,1210,1212,1214,1216,1218,1220,1223],{"class":525,"line":637},[523,1209,1124],{"class":528},[523,1211,757],{"class":532},[523,1213,1129],{"class":685},[523,1215,955],{"class":528},[523,1217,1134],{"class":617},[523,1219,621],{"class":528},[523,1221,1222],{"class":539}," 'Unauthorized access'",[523,1224,1151],{"class":532},[523,1226,1227,1229,1231,1234],{"class":525,"line":643},[523,1228,1156],{"class":617},[523,1230,1159],{"class":532},[523,1232,1233],{"class":539},"'UnauthorizedError'",[523,1235,763],{"class":532},[523,1237,1238],{"class":525,"line":649},[523,1239,769],{"class":532},[523,1241,1242],{"class":525,"line":655},[523,1243,1055],{"class":532},[441,1245,484,1247,1250],{"id":1246},"the-appsyncerrorhandler-middy-middleware",[343,1248,1249],{},"appsyncErrorHandler"," Middy Middleware",[339,1252,1253,1254,1263,1264,1267,1268,1272],{},"To manage cross-cutting concerns (such as error handling, validation, and precognitive checks) cleanly without cluttering your core business logic, we use ",[348,1255,1256],{},[1257,1258,1262],"a",{"href":1259,"rel":1260},"https://middy.js.org/",[1261],"nofollow","Middy"," (",[343,1265,1266],{},"@middy/core","), the ",[1269,1270,1271],"em",{},"de facto"," standard middleware engine for AWS Lambda in Node.js environments.",[339,1274,1275,1276,1279,1280,1282,1283,1285,1286,1288,1289,1292],{},"The module exports the ",[343,1277,1278],{},"appsyncErrorHandler()"," middleware for Middy. It automatically intercepts any error thrown during Lambda execution that subclasses ",[343,1281,457],{}," (or implements ",[343,1284,1070],{},"), converts it into the expected ",[343,1287,457],{}," payload, and sets ",[343,1290,1291],{},"request.error = null"," so the Lambda execution completes cleanly.",[339,1294,1295],{},"The AppSync resolver can then parse the payload and proxy the structured errors to the GraphQL client:",[514,1297,1299],{"className":516,"code":1298,"language":518,"meta":519,"style":519},"import middy from '@middy/core'\nimport { AppSyncError, appsyncErrorHandler } from '@sot1986/appsync-precognition/middy'\n\nasync function baseHandler(event: any) {\n  const item = await findItem(event.arguments.id)\n\n  if (!item) {\n    // Thrown AppSyncError is captured by appsyncErrorHandler\n    throw new AppSyncError('Item not found', 'NotFoundError', { id: event.arguments.id })\n  }\n\n  return item\n}\n\nexport const handler = middy(baseHandler)\n  .use(appsyncErrorHandler())\n",[343,1300,1301,1313,1324,1328,1351,1370,1374,1386,1391,1413,1417,1421,1429,1433,1437,1455],{"__ignoreMap":519},[523,1302,1303,1305,1308,1310],{"class":525,"line":9},[523,1304,529],{"class":528},[523,1306,1307],{"class":532}," middy ",[523,1309,536],{"class":528},[523,1311,1312],{"class":539}," '@middy/core'\n",[523,1314,1315,1317,1320,1322],{"class":525,"line":15},[523,1316,529],{"class":528},[523,1318,1319],{"class":532}," { AppSyncError, appsyncErrorHandler } ",[523,1321,536],{"class":528},[523,1323,552],{"class":539},[523,1325,1326],{"class":525,"line":21},[523,1327,608],{"emptyLinePlaceholder":307},[523,1329,1330,1333,1336,1339,1341,1344,1346,1349],{"class":525,"line":72},[523,1331,1332],{"class":528},"async",[523,1334,1335],{"class":528}," function",[523,1337,1338],{"class":624}," baseHandler",[523,1340,757],{"class":532},[523,1342,1343],{"class":685},"event",[523,1345,955],{"class":528},[523,1347,1348],{"class":617}," any",[523,1350,1151],{"class":532},[523,1352,1353,1356,1359,1361,1364,1367],{"class":525,"line":579},[523,1354,1355],{"class":528},"  const",[523,1357,1358],{"class":617}," item",[523,1360,621],{"class":528},[523,1362,1363],{"class":528}," await",[523,1365,1366],{"class":624}," findItem",[523,1368,1369],{"class":532},"(event.arguments.id)\n",[523,1371,1372],{"class":525,"line":592},[523,1373,608],{"emptyLinePlaceholder":307},[523,1375,1376,1378,1380,1383],{"class":525,"line":605},[523,1377,701],{"class":528},[523,1379,1263],{"class":532},[523,1381,1382],{"class":528},"!",[523,1384,1385],{"class":532},"item) {\n",[523,1387,1388],{"class":525,"line":611},[523,1389,1390],{"class":663},"    // Thrown AppSyncError is captured by appsyncErrorHandler\n",[523,1392,1393,1396,1399,1401,1403,1406,1408,1410],{"class":525,"line":631},[523,1394,1395],{"class":528},"    throw",[523,1397,1398],{"class":528}," new",[523,1400,1117],{"class":624},[523,1402,757],{"class":532},[523,1404,1405],{"class":539},"'Item not found'",[523,1407,1019],{"class":532},[523,1409,1162],{"class":539},[523,1411,1412],{"class":532},", { id: event.arguments.id })\n",[523,1414,1415],{"class":525,"line":637},[523,1416,769],{"class":532},[523,1418,1419],{"class":525,"line":643},[523,1420,608],{"emptyLinePlaceholder":307},[523,1422,1423,1426],{"class":525,"line":649},[523,1424,1425],{"class":528},"  return",[523,1427,1428],{"class":532}," item\n",[523,1430,1431],{"class":525,"line":655},[523,1432,1055],{"class":532},[523,1434,1435],{"class":525,"line":660},[523,1436,608],{"emptyLinePlaceholder":307},[523,1438,1439,1441,1444,1447,1449,1452],{"class":525,"line":667},[523,1440,1105],{"class":528},[523,1442,1443],{"class":528}," const",[523,1445,1446],{"class":617}," handler",[523,1448,621],{"class":528},[523,1450,1451],{"class":624}," middy",[523,1453,1454],{"class":532},"(baseHandler)\n",[523,1456,1457,1460,1463,1465,1467],{"class":525,"line":698},[523,1458,1459],{"class":532},"  .",[523,1461,1462],{"class":624},"use",[523,1464,757],{"class":532},[523,1466,1249],{"class":624},[523,1468,1469],{"class":532},"())\n",[353,1471,245],{"id":1472},"full-example-validating-with-zod",[339,1474,1475,1476,477,1479,1481,1482,1485,1486,1488],{},"By combining ",[343,1477,1478],{},"precognition()",[343,1480,1278],{},", you can create a reusable Middy validation middleware that converts ",[348,1483,1484],{},"Zod"," schema errors directly into ",[343,1487,457],{}," validation responses.",[441,1490,250],{"id":1491},"_1-create-the-reusable-zod-middleware",[339,1493,1494,1495,1498,1499,1501,1502,1505],{},"Create a ",[343,1496,1497],{},"parser"," helper that wraps ",[343,1500,1478],{}," and maps ",[343,1503,1504],{},"z.ZodError"," issues into standard validation paths and messages:",[514,1507,1510],{"className":516,"code":1508,"filename":1509,"language":518,"meta":519,"style":519},"import type middy from '@middy/core'\nimport { precognition } from '@sot1986/appsync-precognition/middy'\nimport * as z from 'zod'\n\nexport function parser\u003CTSchema extends z.ZodType, TResult>(options: {\n  schema: TSchema\n}): middy.MiddlewareObj\u003Cz.infer\u003CTSchema>, TResult> {\n  return precognition({\n    validator: event => options.schema.parse(event),\n    toValidationErrors: (error) => {\n      if (error instanceof z.ZodError === false)\n        return null\n\n      return error.issues.map((issue) => {\n        // Strip top-level 'arguments' prefix so path matches form keys (e.g. 'email' instead of 'arguments.email')\n        const path = issue.path.at(0) === 'arguments' ? issue.path.slice(1) : issue.path\n\n        return {\n          path: path.map(String),\n          message: issue.message,\n          value: issue.input,\n        }\n      })\n    },\n  })\n}\n","amplify/functions/_middlewares/validate.ts",[343,1511,1512,1525,1536,1554,1558,1597,1607,1643,1652,1674,1691,1716,1723,1727,1749,1754,1803,1807,1813,1823,1828,1833,1839,1845,1851,1857],{"__ignoreMap":519},[523,1513,1514,1516,1519,1521,1523],{"class":525,"line":9},[523,1515,529],{"class":528},[523,1517,1518],{"class":528}," type",[523,1520,1307],{"class":532},[523,1522,536],{"class":528},[523,1524,1312],{"class":539},[523,1526,1527,1529,1532,1534],{"class":525,"line":15},[523,1528,529],{"class":528},[523,1530,1531],{"class":532}," { precognition } ",[523,1533,536],{"class":528},[523,1535,552],{"class":539},[523,1537,1538,1540,1543,1546,1549,1551],{"class":525,"line":21},[523,1539,529],{"class":528},[523,1541,1542],{"class":617}," *",[523,1544,1545],{"class":528}," as",[523,1547,1548],{"class":532}," z ",[523,1550,536],{"class":528},[523,1552,1553],{"class":539}," 'zod'\n",[523,1555,1556],{"class":525,"line":72},[523,1557,608],{"emptyLinePlaceholder":307},[523,1559,1560,1562,1564,1567,1569,1572,1574,1577,1579,1582,1584,1587,1590,1593,1595],{"class":525,"line":579},[523,1561,1105],{"class":528},[523,1563,1335],{"class":528},[523,1565,1566],{"class":624}," parser",[523,1568,1013],{"class":532},[523,1570,1571],{"class":624},"TSchema",[523,1573,1114],{"class":528},[523,1575,1576],{"class":624}," z",[523,1578,481],{"class":532},[523,1580,1581],{"class":624},"ZodType",[523,1583,1019],{"class":532},[523,1585,1586],{"class":624},"TResult",[523,1588,1589],{"class":532},">(",[523,1591,1592],{"class":685},"options",[523,1594,955],{"class":528},[523,1596,695],{"class":532},[523,1598,1599,1602,1604],{"class":525,"line":592},[523,1600,1601],{"class":685},"  schema",[523,1603,955],{"class":528},[523,1605,1606],{"class":624}," TSchema\n",[523,1608,1609,1612,1614,1616,1618,1621,1623,1626,1628,1631,1633,1635,1638,1640],{"class":525,"line":605},[523,1610,1611],{"class":532},"})",[523,1613,955],{"class":528},[523,1615,1451],{"class":624},[523,1617,481],{"class":532},[523,1619,1620],{"class":624},"MiddlewareObj",[523,1622,1013],{"class":532},[523,1624,1625],{"class":624},"z",[523,1627,481],{"class":532},[523,1629,1630],{"class":624},"infer",[523,1632,1013],{"class":532},[523,1634,1571],{"class":624},[523,1636,1637],{"class":532},">, ",[523,1639,1586],{"class":624},[523,1641,1642],{"class":532},"> {\n",[523,1644,1645,1647,1650],{"class":525,"line":611},[523,1646,1425],{"class":528},[523,1648,1649],{"class":624}," precognition",[523,1651,628],{"class":532},[523,1653,1654,1657,1660,1662,1665,1668,1671],{"class":525,"line":631},[523,1655,1656],{"class":624},"    validator",[523,1658,1659],{"class":532},": ",[523,1661,1343],{"class":685},[523,1663,1664],{"class":528}," =>",[523,1666,1667],{"class":532}," options.schema.",[523,1669,1670],{"class":624},"parse",[523,1672,1673],{"class":532},"(event),\n",[523,1675,1676,1679,1682,1685,1687,1689],{"class":525,"line":637},[523,1677,1678],{"class":624},"    toValidationErrors",[523,1680,1681],{"class":532},": (",[523,1683,1684],{"class":685},"error",[523,1686,689],{"class":532},[523,1688,692],{"class":528},[523,1690,695],{"class":532},[523,1692,1693,1696,1699,1701,1703,1705,1708,1711,1714],{"class":525,"line":643},[523,1694,1695],{"class":528},"      if",[523,1697,1698],{"class":532}," (error ",[523,1700,707],{"class":528},[523,1702,1576],{"class":624},[523,1704,481],{"class":532},[523,1706,1707],{"class":624},"ZodError",[523,1709,1710],{"class":528}," ===",[523,1712,1713],{"class":617}," false",[523,1715,763],{"class":532},[523,1717,1718,1721],{"class":525,"line":649},[523,1719,1720],{"class":528},"        return",[523,1722,1031],{"class":617},[523,1724,1725],{"class":525,"line":655},[523,1726,608],{"emptyLinePlaceholder":307},[523,1728,1729,1732,1735,1738,1740,1743,1745,1747],{"class":525,"line":660},[523,1730,1731],{"class":528},"      return",[523,1733,1734],{"class":532}," error.issues.",[523,1736,1737],{"class":624},"map",[523,1739,682],{"class":532},[523,1741,1742],{"class":685},"issue",[523,1744,689],{"class":532},[523,1746,692],{"class":528},[523,1748,695],{"class":532},[523,1750,1751],{"class":525,"line":667},[523,1752,1753],{"class":663},"        // Strip top-level 'arguments' prefix so path matches form keys (e.g. 'email' instead of 'arguments.email')\n",[523,1755,1756,1759,1762,1764,1767,1770,1772,1775,1777,1780,1783,1786,1788,1791,1793,1796,1798,1800],{"class":525,"line":698},[523,1757,1758],{"class":528},"        const",[523,1760,1761],{"class":617}," path",[523,1763,621],{"class":528},[523,1765,1766],{"class":532}," issue.path.",[523,1768,1769],{"class":624},"at",[523,1771,757],{"class":532},[523,1773,1774],{"class":617},"0",[523,1776,689],{"class":532},[523,1778,1779],{"class":528},"===",[523,1781,1782],{"class":539}," 'arguments'",[523,1784,1785],{"class":528}," ?",[523,1787,1766],{"class":532},[523,1789,1790],{"class":624},"slice",[523,1792,757],{"class":532},[523,1794,1795],{"class":617},"1",[523,1797,689],{"class":532},[523,1799,955],{"class":528},[523,1801,1802],{"class":532}," issue.path\n",[523,1804,1805],{"class":525,"line":722},[523,1806,608],{"emptyLinePlaceholder":307},[523,1808,1809,1811],{"class":525,"line":737},[523,1810,1720],{"class":528},[523,1812,695],{"class":532},[523,1814,1815,1818,1820],{"class":525,"line":748},[523,1816,1817],{"class":532},"          path: path.",[523,1819,1737],{"class":624},[523,1821,1822],{"class":532},"(String),\n",[523,1824,1825],{"class":525,"line":766},[523,1826,1827],{"class":532},"          message: issue.message,\n",[523,1829,1830],{"class":525,"line":772},[523,1831,1832],{"class":532},"          value: issue.input,\n",[523,1834,1836],{"class":525,"line":1835},22,[523,1837,1838],{"class":532},"        }\n",[523,1840,1842],{"class":525,"line":1841},23,[523,1843,1844],{"class":532},"      })\n",[523,1846,1848],{"class":525,"line":1847},24,[523,1849,1850],{"class":532},"    },\n",[523,1852,1854],{"class":525,"line":1853},25,[523,1855,1856],{"class":532},"  })\n",[523,1858,1860],{"class":525,"line":1859},26,[523,1861,1055],{"class":532},[441,1863,255],{"id":1864},"_2-implement-the-lambda-handler",[339,1866,1867,1868,1870,1871,1873],{},"Use the ",[343,1869,1497],{}," middleware alongside ",[343,1872,1278],{}," in your Lambda function:",[514,1875,1878],{"className":516,"code":1876,"filename":1877,"language":518,"meta":519,"style":519},"import middy from '@middy/core'\nimport { appsyncErrorHandler } from '@sot1986/appsync-precognition/middy'\nimport * as z from 'zod'\nimport { parser } from '../_middlewares/validate'\n\n// 1. Define the input validation schema\nconst CreateUserEventSchema = z.object({\n  arguments: z.object({\n    email: z.string().email('Please enter a valid email address'),\n    name: z.string().min(3, 'Name must be at least 3 characters'),\n    age: z.number().min(18, 'Must be at least 18 years old'),\n  }),\n})\n\n// 2. Base handler (only runs if validation passes and request is NOT precognitive)\nasync function baseHandler(event: z.infer\u003Ctypeof CreateUserEventSchema>) {\n  const { email, name, age } = event.arguments\n\n  // Perform database write / business logic\n  return {\n    id: 'user_123',\n    email,\n    name,\n    age,\n    createdAt: new Date().toISOString(),\n  }\n}\n\n// 3. Compose with Middy\nexport const handler = middy(baseHandler)\n  .use(appsyncErrorHandler())\n  .use(parser({ schema: CreateUserEventSchema }))\n","amplify/functions/create-user/handler.ts",[343,1879,1880,1890,1901,1915,1927,1931,1936,1953,1962,1982,2006,2030,2035,2039,2043,2048,2076,2103,2107,2112,2118,2129,2134,2139,2144,2163,2167,2172,2177,2183,2198,2211],{"__ignoreMap":519},[523,1881,1882,1884,1886,1888],{"class":525,"line":9},[523,1883,529],{"class":528},[523,1885,1307],{"class":532},[523,1887,536],{"class":528},[523,1889,1312],{"class":539},[523,1891,1892,1894,1897,1899],{"class":525,"line":15},[523,1893,529],{"class":528},[523,1895,1896],{"class":532}," { appsyncErrorHandler } ",[523,1898,536],{"class":528},[523,1900,552],{"class":539},[523,1902,1903,1905,1907,1909,1911,1913],{"class":525,"line":21},[523,1904,529],{"class":528},[523,1906,1542],{"class":617},[523,1908,1545],{"class":528},[523,1910,1548],{"class":532},[523,1912,536],{"class":528},[523,1914,1553],{"class":539},[523,1916,1917,1919,1922,1924],{"class":525,"line":72},[523,1918,529],{"class":528},[523,1920,1921],{"class":532}," { parser } ",[523,1923,536],{"class":528},[523,1925,1926],{"class":539}," '../_middlewares/validate'\n",[523,1928,1929],{"class":525,"line":579},[523,1930,608],{"emptyLinePlaceholder":307},[523,1932,1933],{"class":525,"line":592},[523,1934,1935],{"class":663},"// 1. Define the input validation schema\n",[523,1937,1938,1940,1943,1945,1948,1951],{"class":525,"line":605},[523,1939,614],{"class":528},[523,1941,1942],{"class":617}," CreateUserEventSchema",[523,1944,621],{"class":528},[523,1946,1947],{"class":532}," z.",[523,1949,1950],{"class":624},"object",[523,1952,628],{"class":532},[523,1954,1955,1958,1960],{"class":525,"line":611},[523,1956,1957],{"class":532},"  arguments: z.",[523,1959,1950],{"class":624},[523,1961,628],{"class":532},[523,1963,1964,1967,1969,1971,1974,1976,1979],{"class":525,"line":631},[523,1965,1966],{"class":532},"    email: z.",[523,1968,1016],{"class":624},[523,1970,676],{"class":532},[523,1972,1973],{"class":624},"email",[523,1975,757],{"class":532},[523,1977,1978],{"class":539},"'Please enter a valid email address'",[523,1980,1981],{"class":532},"),\n",[523,1983,1984,1987,1989,1991,1994,1996,1999,2001,2004],{"class":525,"line":637},[523,1985,1986],{"class":532},"    name: z.",[523,1988,1016],{"class":624},[523,1990,676],{"class":532},[523,1992,1993],{"class":624},"min",[523,1995,757],{"class":532},[523,1997,1998],{"class":617},"3",[523,2000,1019],{"class":532},[523,2002,2003],{"class":539},"'Name must be at least 3 characters'",[523,2005,1981],{"class":532},[523,2007,2008,2011,2014,2016,2018,2020,2023,2025,2028],{"class":525,"line":643},[523,2009,2010],{"class":532},"    age: z.",[523,2012,2013],{"class":624},"number",[523,2015,676],{"class":532},[523,2017,1993],{"class":624},[523,2019,757],{"class":532},[523,2021,2022],{"class":617},"18",[523,2024,1019],{"class":532},[523,2026,2027],{"class":539},"'Must be at least 18 years old'",[523,2029,1981],{"class":532},[523,2031,2032],{"class":525,"line":649},[523,2033,2034],{"class":532},"  }),\n",[523,2036,2037],{"class":525,"line":655},[523,2038,652],{"class":532},[523,2040,2041],{"class":525,"line":660},[523,2042,608],{"emptyLinePlaceholder":307},[523,2044,2045],{"class":525,"line":667},[523,2046,2047],{"class":663},"// 2. Base handler (only runs if validation passes and request is NOT precognitive)\n",[523,2049,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2073],{"class":525,"line":698},[523,2051,1332],{"class":528},[523,2053,1335],{"class":528},[523,2055,1338],{"class":624},[523,2057,757],{"class":532},[523,2059,1343],{"class":685},[523,2061,955],{"class":528},[523,2063,1576],{"class":624},[523,2065,481],{"class":532},[523,2067,1630],{"class":624},[523,2069,1013],{"class":532},[523,2071,2072],{"class":528},"typeof",[523,2074,2075],{"class":532}," CreateUserEventSchema>) {\n",[523,2077,2078,2080,2083,2085,2087,2090,2092,2095,2098,2100],{"class":525,"line":722},[523,2079,1355],{"class":528},[523,2081,2082],{"class":532}," { ",[523,2084,1973],{"class":617},[523,2086,1019],{"class":532},[523,2088,2089],{"class":617},"name",[523,2091,1019],{"class":532},[523,2093,2094],{"class":617},"age",[523,2096,2097],{"class":532}," } ",[523,2099,728],{"class":528},[523,2101,2102],{"class":532}," event.arguments\n",[523,2104,2105],{"class":525,"line":737},[523,2106,608],{"emptyLinePlaceholder":307},[523,2108,2109],{"class":525,"line":748},[523,2110,2111],{"class":663},"  // Perform database write / business logic\n",[523,2113,2114,2116],{"class":525,"line":766},[523,2115,1425],{"class":528},[523,2117,695],{"class":532},[523,2119,2120,2123,2126],{"class":525,"line":772},[523,2121,2122],{"class":532},"    id: ",[523,2124,2125],{"class":539},"'user_123'",[523,2127,2128],{"class":532},",\n",[523,2130,2131],{"class":525,"line":1835},[523,2132,2133],{"class":532},"    email,\n",[523,2135,2136],{"class":525,"line":1841},[523,2137,2138],{"class":532},"    name,\n",[523,2140,2141],{"class":525,"line":1847},[523,2142,2143],{"class":532},"    age,\n",[523,2145,2146,2149,2152,2155,2157,2160],{"class":525,"line":1853},[523,2147,2148],{"class":532},"    createdAt: ",[523,2150,2151],{"class":528},"new",[523,2153,2154],{"class":624}," Date",[523,2156,676],{"class":532},[523,2158,2159],{"class":624},"toISOString",[523,2161,2162],{"class":532},"(),\n",[523,2164,2165],{"class":525,"line":1859},[523,2166,769],{"class":532},[523,2168,2170],{"class":525,"line":2169},27,[523,2171,1055],{"class":532},[523,2173,2175],{"class":525,"line":2174},28,[523,2176,608],{"emptyLinePlaceholder":307},[523,2178,2180],{"class":525,"line":2179},29,[523,2181,2182],{"class":663},"// 3. Compose with Middy\n",[523,2184,2186,2188,2190,2192,2194,2196],{"class":525,"line":2185},30,[523,2187,1105],{"class":528},[523,2189,1443],{"class":528},[523,2191,1446],{"class":617},[523,2193,621],{"class":528},[523,2195,1451],{"class":624},[523,2197,1454],{"class":532},[523,2199,2201,2203,2205,2207,2209],{"class":525,"line":2200},31,[523,2202,1459],{"class":532},[523,2204,1462],{"class":624},[523,2206,757],{"class":532},[523,2208,1249],{"class":624},[523,2210,1469],{"class":532},[523,2212,2214,2216,2218,2220,2222],{"class":525,"line":2213},32,[523,2215,1459],{"class":532},[523,2217,1462],{"class":624},[523,2219,757],{"class":532},[523,2221,1497],{"class":624},[523,2223,2224],{"class":532},"({ schema: CreateUserEventSchema }))\n",[441,2226,260],{"id":2227},"what-happens-behind-the-scenes",[364,2229,2230,2254,2266,2290],{},[367,2231,2232,2235,2236,2239,2240,2242,2243,2246,2247,2250,2251,481],{},[348,2233,2234],{},"Precognitive Request:"," If headers contain ",[343,2237,2238],{},"Precognition: true",", the ",[343,2241,1497],{}," middleware validates the schema. On success, it immediately returns ",[343,2244,2245],{},"{ data: null }"," with ",[343,2248,2249],{},"Precognition-Success: true",", skipping ",[343,2252,2253],{},"baseHandler",[367,2255,2256,900,2259,2262,2263,2265],{},[348,2257,2258],{},"Validate-Only:",[343,2260,2261],{},"Precognition-Validate-Only: email"," is sent, only errors on the ",[343,2264,1973],{}," field will be reported back.",[367,2267,2268,2271,2272,1019,2274,2276,2277,2280,2281,2283,2284,2286,2287,481],{},[348,2269,2270],{},"Validation Errors:"," When Zod throws a ",[343,2273,1707],{},[343,2275,1497],{}," converts it to ",[343,2278,2279],{},"PrecognitionValidationError"," (a subclass of ",[343,2282,457],{},"), which ",[343,2285,1249],{}," transforms into ",[343,2288,2289],{},"{ error: { type: 'AppSyncError', errors, errorsCount } }",[367,2291,2292,2295,2296,2298],{},[348,2293,2294],{},"GraphQL Error Response:"," The AppSync resolver response template unpacks this error structure with ",[343,2297,476],{},", delivering granular field errors to your Nuxt frontend form.",[353,2300,265],{"id":2301},"summary-flow-chart",[339,2303,2304],{},"The following diagram illustrates the complete end-to-end lifecycle of a request across the AppSync resolver, Lambda function, Middy middleware pipeline, and the client:",[2306,2307,2308,2322,2335,2350,2368,2391],"step-flow",{},[2309,2310,2313],"step-item",{"badge":2238,"color":2311,"step":1795,"title":2312},"sky","Client Form",[339,2314,2315,2316,1019,2319,419],{},"Client dispatches a GraphQL mutation with Precognition headers (",[343,2317,2318],{},"Precognition",[343,2320,2321],{},"Precognition-Validate-Only",[2309,2323,2328],{"badge":2324,"color":2325,"step":2326,"title":2327},"Invoke Lambda","indigo","2","AppSync Resolver (Request)",[339,2329,2330,2331,2334],{},"Resolver receives the GraphQL request and invokes the AWS Lambda Data Source passing the context payload (",[343,2332,2333],{},"$ctx.args"," and request headers).",[2309,2336,2340],{"badge":2337,"color":2338,"step":1998,"title":2339},"Validation Hook","amber","Lambda • precognition()",[339,2341,484,2342,2344,2345,415,2347,2349],{},[343,2343,1478],{}," middleware validates input. If validation fails or business checks fail, an ",[343,2346,457],{},[343,2348,2279],{},") is thrown.",[2309,2351,2356],{"badge":2352,"color":2353,"step":2354,"title":2355},"Clean Payload Map","purple","4","Lambda • appsyncErrorHandler()",[339,2357,484,2358,2360,2361,2363,2364,2367],{},[343,2359,1278],{}," middleware catches the error, formats it into the structured result payload ",[343,2362,2289],{},", and clears ",[343,2365,2366],{},"request.error"," so Lambda succeeds without crashing.",[2309,2369,2373],{"badge":507,"color":2370,"step":2371,"title":2372},"emerald","5","AppSync Resolver (Response)",[339,2374,2375,2376,2379,2380,2382,2383,477,2385,2246,2387,477,2389,481],{},"The VTL response template inspects ",[343,2377,2378],{},"$ctx.result.error",", recognizes ",[343,2381,457],{},", and iterates calling ",[343,2384,476],{},[343,2386,480],{},[343,2388,907],{},[343,2390,432],{},[2309,2392,2397],{"badge":2393,"color":2394,"step":2395,"title":2396},"GraphQL errors[]","teal","6","Client Form Error Mapping",[339,2398,2399,2400,2402],{},"The Nuxt client receives a standard GraphQL error response with full validation metadata in ",[343,2401,432],{},", seamlessly binding error messages to each corresponding form input.",[441,2404,270],{"id":2405},"flow-steps-breakdown",[364,2407,2408,2418,2424,2437,2448,2457,2479],{},[367,2409,2410,2413,2414,1019,2416,419],{},[348,2411,2412],{},"Client Request:"," The client dispatches a GraphQL mutation along with Precognition HTTP headers (",[343,2415,2318],{},[343,2417,2321],{},[367,2419,2420,2423],{},[348,2421,2422],{},"Resolver Invocation:"," The AppSync resolver request mapping forwards the request arguments and headers to the Lambda Data Source.",[367,2425,2426,2429,2430,2432,2433,415,2435,2349],{},[348,2427,2428],{},"Middy Validation:"," The ",[343,2431,1478],{}," middleware validates the input before the handler runs. If validation fails or business logic encounters an issue, an ",[343,2434,457],{},[343,2436,2279],{},[367,2438,2439,2429,2442,2444,2445,2447],{},[348,2440,2441],{},"Error Transformation:",[343,2443,1249],{}," middleware catches the error and formats it into the expected ",[343,2446,2289],{}," structure, completing the Lambda invocation cleanly without crashing.",[367,2449,2450,2453,2454,419],{},[348,2451,2452],{},"Lambda Response:"," The Lambda function returns this structured object as its invocation result (",[343,2455,2456],{},"$ctx.result",[367,2458,2459,2462,2463,2465,2466,2469,2470,2473,2474,2476,2477,481],{},[348,2460,2461],{},"Resolver Error Mapping:"," The AppSync resolver response template (",[343,2464,507],{},") detects ",[343,2467,2468],{},"type == 'AppSyncError'",", loops over ",[343,2471,2472],{},"errors",", and invokes ",[343,2475,476],{}," / ",[343,2478,480],{},[367,2480,2481,2484,2485,477,2487,2489],{},[348,2482,2483],{},"Client Error Handling:"," AppSync delivers a standard GraphQL errors response with complete ",[343,2486,907],{},[343,2488,432],{}," metadata, which the Nuxt Precognition client parses and maps directly to the corresponding form fields.",[2491,2492,2493],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":519,"searchDepth":15,"depth":15,"links":2495},[2496,2497,2500,2503,2507,2512],{"id":355,"depth":15,"text":205},{"id":395,"depth":15,"text":210,"children":2498},[2499],{"id":443,"depth":21,"text":215},{"id":491,"depth":15,"text":220,"children":2501},[2502],{"id":777,"depth":21,"text":225},{"id":926,"depth":15,"text":230,"children":2504},[2505,2506],{"id":1058,"depth":21,"text":235},{"id":1246,"depth":21,"text":240},{"id":1472,"depth":15,"text":245,"children":2508},[2509,2510,2511],{"id":1491,"depth":21,"text":250},{"id":1864,"depth":21,"text":255},{"id":2227,"depth":21,"text":260},{"id":2301,"depth":15,"text":265,"children":2513},[2514],{"id":2405,"depth":21,"text":270},"Run advanced and database-backed form validations inside AWS Lambda for AppSync using Middy.",{"type":336,"value":2517},[2518],[339,2519,341,2520,346,2522,351],{},[343,2521,345],{},[348,2523,350],{},"md",{},{"title":200,"description":2515},{"loc":199},"rx03zAEUFMlSTJcDHOKcludypQZUuoOkDuESKI5ZFRQ",[2530,2531],{"title":150,"path":149,"stem":328,"children":-1},{"title":275,"path":274,"stem":332,"children":-1},1787496571836]