Sigue cada sección en orden. Cada paso tiene qué hacer exactamente y qué resultado esperar. Marca cada uno como completo para rastrear el progreso.
Dentro de tu proyecto CDK crea esta estructura. La extensión la detecta automáticamente.
Al correr AWS: Detect AppSync Project desde el Command Palette, la extensión debería encontrar el schema.graphql y la carpeta resolvers/ automáticamente.
Guarda esto como schema.graphql. Incluye tipos para probar todos los resolvers.
type User { id: ID! name: String! email: String! status: String role: String createdAt: String } type Order { id: ID! userId: ID! total: Float! status: String! createdAt: String } type UserConnection { items: [User], nextToken: String, scannedCount: Int } type OrderConnection { items: [Order], nextToken: String, scannedCount: Int } type Query { getUser(id: ID!): User listUsers(limit: Int, nextToken: String): UserConnection getUsersByStatus(status: String!): UserConnection listOrders(userId: ID!): OrderConnection } type Mutation { createUser(name: String!, email: String!, role: String): User updateUserStatus(id: ID!, status: String!): User deleteUser(id: ID!): User } schema { query: Query; mutation: Mutation }
Guarda en la raíz del proyecto como mock-data.json.
{
"User": [
{ "id": "usr-001", "name": "Ana García", "email": "ana@example.com", "status": "active", "role": "admin" },
{ "id": "usr-002", "name": "Carlos Pérez", "email": "carlos@example.com", "status": "active", "role": "user" },
{ "id": "usr-003", "name": "Laura Soto", "email": "laura@example.com", "status": "inactive", "role": "user" }
],
"Order": [
{ "id": "ord-001", "userId": "usr-001", "total": 150.00, "status": "completed" },
{ "id": "ord-002", "userId": "usr-001", "total": 75.50, "status": "pending" },
{ "id": "ord-003", "userId": "usr-002", "total": 200.00, "status": "completed" }
]
}
Abre el Command Palette (Cmd+Shift+P) y ejecuta:
AWS: Detect and Start AppSync Offline
1. Si hay múltiples proyectos en el workspace, aparece un Quick Pick para seleccionar.
2. La notificación dice: AppSync Offline running at http://localhost:4000/graphql
3. El panel "AppSync Offline Studio" se abre con el dot verde parpadeando.
4. La pestaña Schema muestra todos los tipos del schema.
Verifica que el archivo se llame exactamente schema.graphql (no .graphqls). La extensión también soporta carpetas con múltiples archivos *.graphql.
Abre una terminal y ejecuta:
curl http://localhost:4000/health
{"status":"ok","port":4000,"timestamp":"..."}
Crea estos archivos en resolvers/:
Query.getUser.request.js
export function request(ctx) { return { operation: 'GetItem', key: { id: { S: ctx.arguments.id } }, }; }
Query.getUser.response.js
export function response(ctx) { return ctx.result; }
Query.listUsers.request.js
export function request(ctx) { return { operation: 'Scan', limit: ctx.arguments.limit || 20, }; }
Query.listUsers.response.js
export function response(ctx) { return ctx.result; }
En el panel AppSync Studio, pestaña Query, pega y ejecuta con Ctrl+Enter:
query {
getUser(id: "usr-001") {
id
name
email
status
}
}{ "data": { "getUser": { "id": "usr-001", "name": "Ana García", "email": "ana@example.com", "status": "active" } } }
Con la misma query activa, haz clic en el botón ⚡ All Identities.
La respuesta muestra un objeto con 5 claves: apiKey, cognitoUser, iam, admin, guest — cada uno con el mismo resultado (el resolver no valida identidad en este ejemplo). Los logs muestran 5 trazas agrupadas, cada una con su badge de identidad.
Crea Query.getUsersByStatus.request.js:
export function request(ctx) { return { operation: 'Scan', filter: { expression: '#s = :status', expressionNames: { '#s': 'status' }, expressionValues: { ':status': { S: ctx.arguments.status } }, }, }; } export function response(ctx) { return ctx.result; }
Puedes poner request y response en el mismo archivo o en archivos separados. Ambos formatos funcionan.
Ve a la pestaña Resolver Runner en el panel y completa:
Query.getUsersByStatuscognitoUser{ "status": "active" }Haz clic en ▶ Run Resolver.
El trace muestra:
• [REQUEST] — operación Scan con FilterExpression
• [RESPONSE] — items filtrados: solo usr-001 y usr-002 (status = active)
• El resultado NO incluye usr-003 (status = inactive)
Haz clic en Recent Runs (abajo del trace) para expandir.
Aparece la ejecución anterior con ✓ verde, badge de identidad cognitoUser y timestamp. Al hacer clic sobre ella, los campos Type.Field, Identity y Arguments se restauran automáticamente.
Crea Query.listOrders.request.vtl:
{
"operation": "Query",
"query": {
"expression": "userId = :uid",
"expressionValues": {
":uid": $util.dynamodb.toDynamoDBJson($ctx.args.userId)
}
}
}Crea Query.listOrders.response.vtl:
$util.toJson($ctx.result)
En Resolver Runner:
Query.listOrders{ "userId": "usr-001" }El trace muestra la operación Query con la expresión VTL ya evaluada. Resultado: items con ord-001 y ord-002 (userId = usr-001), NO ord-003.
Aunque userId no es la PK de la tabla Order, la extensión simula el GSI filtrando todos los items por ese atributo. No necesitas definir el índice explícitamente en mock-data.json.
Agrega la mutación al schema. Luego crea estos archivos:
Mutation.createUser.pipeline.json
{
"functions": [
{ "name": "ValidateEmail", "dataSource": "NONE" },
{ "name": "SaveUser", "dataSource": "User" }
]
}functions/ValidateEmail.request.js
export function request(ctx) { // Guardar en stash para las funciones siguientes ctx.stash.email = ctx.arguments.email; ctx.stash.validatedAt = '2024-01-01T00:00:00Z'; if (!ctx.arguments.email.includes('@')) { $util.error('Invalid email format', 'ValidationError'); } return {}; } export function response(ctx) { return { validated: true }; }
functions/SaveUser.request.js
export function request(ctx) { // ctx.prev.result = resultado de ValidateEmail return { operation: 'PutItem', key: { id: { S: $util.autoId() } }, attributeValues: { name: { S: ctx.arguments.name }, email: { S: ctx.stash.email }, role: { S: ctx.arguments.role || 'user' }, status: { S: 'active' }, }, }; } export function response(ctx) { return ctx.result; }
En Resolver Runner:
Mutation.createUser{ "name": "Pedro Ruiz", "email": "pedro@example.com", "role": "admin" }
[ValidateEmail → REQUEST] — retorna {}
[ValidateEmail → RESPONSE] — retorna { validated: true }
[SaveUser → REQUEST] — PutItem con id autogenerado
[SaveUser → RESPONSE] — el usuario creado
Respuesta final: el objeto User con el id generado
Ahora prueba con email inválido:
{ "name": "Bad User", "email": "no-es-un-email" }El trace muestra [ValidateEmail → ERROR] con mensaje Invalid email format. SaveUser NO se ejecuta. El pipeline se detiene en la primera función fallida.
Crea Mutation.updateUserStatus.request.js:
export function request(ctx) { return { operation: 'UpdateItem', key: { id: { S: ctx.arguments.id } }, update: { expression: 'SET #st = :status', expressionNames: { '#st': 'status' }, expressionValues: { ':status': { S: ctx.arguments.status } }, }, }; } export function response(ctx) { return ctx.result; }
En Resolver Runner con:
{ "id": "usr-003", "status": "active" }El usuario usr-003 (Laura) ahora tiene status: "active". Corre Query.getUsersByStatus de nuevo con "status": "active" — ahora debe devolver los 3 usuarios.
Crea Query.listUsers.request.js con soporte de paginación:
export function request(ctx) { return { operation: 'Scan', limit: ctx.arguments.limit || 2, nextToken: ctx.arguments.nextToken || null, }; }
En el Resolver Runner usa { "limit": 2 }. El resultado devuelve 2 usuarios y un nextToken. Pega ese token como argumento para obtener la siguiente página.
Página 1: 2 items + nextToken: "MQ==" (o similar en base64).
Página 2 con ese nextToken: 1 item + nextToken: null (fin de datos).
1. Ejecuta Mutation.deleteUser para eliminar usr-001.
2. Verifica con Query.getUser que no existe.
3. Haz clic en el botón ↺ Reload Data del panel.
4. Ejecuta Query.getUser nuevamente.
Después del reload, usr-001 aparece de nuevo. El banner del panel muestra ✓ Mock data reloaded en azul.
Corre cdk synth en tu proyecto para generar cdk.out/. Luego ejecuta desde el Command Palette:
AWS: Generate Mock Data from CDK
La extensión lee cdk.out/*.template.json, encuentra las tablas DynamoDB y genera un mock-data.json con la estructura de keys correcta. El archivo se abre en el editor y los datos se cargan automáticamente.
Con el servidor corriendo, edita cualquier resolver (por ejemplo cambia 'active' por un valor distinto en SaveUser) y guarda el archivo.
En la pestaña Schema del panel aparece el badge ↻ reloaded en verde por 3 segundos. Las ejecuciones posteriores usan el resolver nuevo sin necesidad de reiniciar el servidor.
Después de un cdk synth, ejecuta:
AWS: Validate Stack Intent (cdk.out)
Abre un panel con findings agrupados por severidad (Critical → Low) y un Confidence Score 0–100 con label: Ready to Deploy / Review Needed / etc. Cero findings críticos en un proyecto limpio.
AWS: Validate Environment Variables
Lista cada Lambda con sus env vars clasificadas: static (resuelta), ssm (necesita cloud), secret, token (CDK intrínseco). Si hay SSM refs no resueltas, sugiere el snippet para .env.local.
Cambia algo en tu stack CDK (por ejemplo agrega un env var a una Lambda) y ejecuta:
AWS: Run CDK Diff Explainer
Cada cambio tiene: tipo de recurso, nivel de riesgo (🔴 Critical / 🟠 High / 🟡 Medium / 🟢 Low), explicación en lenguaje natural, y recomendación. El botón "Export Markdown" genera el reporte para PR review.
En .vscode/settings.json:
{
"awsToolkit.appsync.lambdaHandlers": {
"UserLambdaDS": "src/handlers/users.ts#handler"
},
"awsToolkit.appsync.lambdaDebugPort": 9229
}Crea el handler en src/handlers/users.ts:
export async function handler(event: any) { // Pon un breakpoint aquí ↓ const { arguments: args, identity } = event; return { id: args.id, name: 'From Lambda', identity }; }
1. Pon un breakpoint en el handler.
2. En Resolver Runner selecciona el resolver que usa UserLambdaDS.
3. Haz clic en 🐛 Debug Lambda.
4. VS Code adjunta automáticamente el debugger — la ejecución pausa en el breakpoint.
5. Cuando continues (F5), el resultado aparece en el trace del panel.
Requiere que cdk.out/ exista. Si no lo tienes, corre primero:
cdk synth
Luego ejecuta desde el Command Palette (Cmd+Shift+P):
AWS: Generate Service Map
Se abre el panel AWS Service Map con un diagrama React Flow interactivo:
• Un tab por stack CDK (uno por cada *.template.json en cdk.out/).
• Nodos auto-posicionados de izquierda a derecha por tipo de servicio.
• Conexiones con etiquetas que indican el tipo de relación.
• Leyenda dinámica en la barra superior con solo los servicios presentes en el stack.
La extensión lee cdk.out/*.template.json — no el código CDK fuente. Verifica que cdk synth haya completado sin errores y que el directorio no esté vacío.
Para el stack de este proyecto (AppSync + Lambda + DynamoDB + Cognito), el mapa debería mostrar este flujo:
datasource — AppSync DataSource con ApiId: {Ref: ...}
invokes — DataSource tipo AWS_LAMBDA con LambdaFunctionArn
reads/writes — DataSource tipo AMAZON_DYNAMODB con TableName
auth — AppSync con UserPoolConfig.UserPoolId
triggers — AWS::Lambda::EventSourceMapping (SQS/SNS → Lambda)
env ref — Lambda con referencias a tablas/colas en Environment.Variables
Prueba la interactividad del panel:
Las relaciones triggers e invokes muestran un edge animado (línea de puntos en movimiento) para distinguir flujos de datos activos de referencias de configuración.
JS / VTL / Lambda resolvers
Pipeline resolvers (stash, prev.result)
DynamoDB: GetItem, PutItem, UpdateItem, DeleteItem
FilterExpression, KeyConditionExpression
UpdateExpression (SET/REMOVE/ADD)
GSI simulation (query por cualquier atributo)
Composite keys (pk+sk)
Paginación (limit + nextToken)
Mock Data hot reload
Schema hot reload
Multi-identity runner
CDK Preflight + Confidence Score
Env Var classification
CDK Diff risk analysis
AWS Service Map (React Flow)
BatchGetItem / BatchWriteItem
TransactGetItems / TransactWriteItems
Subscriptions (WebSocket)
@auth directive enforcement
Lambda Layers locales
RDS / HTTP / EventBridge data sources