Implementar a API do Jogo e o servidor MCP de Inventário
Tarefa 1: Implementar a API do Jogo
Seção intitulada “Tarefa 1: Implementar a API do Jogo”Vamos implementar as seguintes APIs nesta seção:
saveGame- criar ou atualizar um jogo.queryGames- retornar uma lista paginada de jogos salvos anteriormente.saveAction- salvar uma ação para um determinado jogo.queryActions- retornar uma lista paginada de todas as ações relacionadas a um jogo.queryInventory- retornar uma lista paginada de itens no inventário de um jogador.
Esquema da API
Seção intitulada “Esquema da API”Para definir as entradas e saídas da nossa API, vamos criar nosso esquema usando Zod no arquivo packages/game-api/src/schema/index.ts da seguinte forma:
import { z } from 'zod';
export const QueryInputSchema = z.object({ cursor: z.string().optional(), limit: z.number().optional().default(100),});export type IQueryInput = z.TypeOf<typeof QueryInputSchema>;
export const ActionSchema = z.object({ playerName: z.string(), timestamp: z.iso.datetime(), role: z.enum(['assistant', 'user']), content: z.string(),});export type IAction = z.TypeOf<typeof ActionSchema>;
export const GameSchema = z.object({ playerName: z.string(), genre: z.enum(['zombie', 'superhero', 'medieval']), lastUpdated: z.iso.datetime(),});export type IGame = z.TypeOf<typeof GameSchema>;
export const ItemSchema = z.object({ playerName: z.string(), itemName: z.string(), emoji: z.string().optional(), lastUpdated: z.iso.datetime(), quantity: z.number(),});export type IItem = z.TypeOf<typeof ItemSchema>;
export const createPaginatedQueryOutput = <ItemType extends z.ZodTypeAny>( itemSchema: ItemType,) => { return z.object({ items: z.array(itemSchema), cursor: z.string().nullable(), });};export * from './echo.js'import { z } from 'zod';
export const QueryInputSchema = z.object({ cursor: z.string().optional(), limit: z.number().optional().default(100),});export type IQueryInput = z.TypeOf<typeof QueryInputSchema>;
export const ActionSchema = z.object({ playerName: z.string(), timestamp: z.iso.datetime(), role: z.enum(['assistant', 'user']), content: z.string(),});export type IAction = z.TypeOf<typeof ActionSchema>;
export const GameSchema = z.object({ playerName: z.string(), genre: z.enum(['zombie', 'superhero', 'medieval']), lastUpdated: z.iso.datetime(),});export type IGame = z.TypeOf<typeof GameSchema>;
export const ItemSchema = z.object({ playerName: z.string(), itemName: z.string(), emoji: z.string().optional(), lastUpdated: z.iso.datetime(), quantity: z.number(),});export type IItem = z.TypeOf<typeof ItemSchema>;
export const createPaginatedQueryOutput = <ItemType extends z.ZodTypeAny>( itemSchema: ItemType,) => { return z.object({ items: z.array(itemSchema), cursor: z.string().nullable(), });};Exclua o arquivo packages/game-api/src/schema/echo.ts pois não o usaremos neste projeto.
Modelagem de entidades
Seção intitulada “Modelagem de entidades”Este é o diagrama ER para nossa aplicação.
Vamos implementar nosso banco de dados no DynamoDB e usaremos a biblioteca cliente ElectroDB do DynamoDB para simplificar o processo. Para instalar o electrodb e o DynamoDB Client, execute este comando:
pnpm add -w electrodb@3.5.0 @aws-sdk/client-dynamodb@3.940.0yarn add electrodb@3.5.0 @aws-sdk/client-dynamodb@3.940.0npm install --legacy-peer-deps electrodb@3.5.0 @aws-sdk/client-dynamodb@3.940.0bun install electrodb@3.5.0 @aws-sdk/client-dynamodb@3.940.0Para definir nossas entidades ElectroDB a partir do diagrama ER, vamos criar o arquivo packages/game-api/src/entities/index.ts:
import { Entity } from 'electrodb';import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
export const createActionEntity = (client: DynamoDBClient = new DynamoDBClient()) => new Entity( { model: { entity: 'Action', version: '1', service: 'game', }, attributes: { playerName: { type: 'string', required: true, readOnly: true }, timestamp: { type: 'string', required: true, readOnly: true, set: () => new Date().toISOString(), default: () => new Date().toISOString(), }, role: { type: 'string', required: true, readOnly: true }, content: { type: 'string', required: true, readOnly: true }, }, indexes: { primary: { pk: { field: 'pk', composite: ['playerName'] }, sk: { field: 'sk', composite: ['timestamp'] }, }, }, }, { client, table: process.env.TABLE_NAME }, );
export const createGameEntity = (client: DynamoDBClient = new DynamoDBClient()) => new Entity( { model: { entity: 'Game', version: '1', service: 'game', }, attributes: { playerName: { type: 'string', required: true, readOnly: true }, genre: { type: 'string', required: true, readOnly: true }, lastUpdated: { type: 'string', required: true, default: () => new Date().toISOString(), }, }, indexes: { primary: { pk: { field: 'pk', composite: ['playerName'] }, sk: { field: 'sk', composite: [], }, }, }, }, { client, table: process.env.TABLE_NAME }, );
export const createInventoryEntity = (client: DynamoDBClient = new DynamoDBClient()) => new Entity( { model: { entity: 'Inventory', version: '1', service: 'game', }, attributes: { playerName: { type: 'string', required: true, readOnly: true }, lastUpdated: { type: 'string', required: true, default: () => new Date().toISOString(), }, itemName: { type: 'string', required: true, }, emoji: { type: 'string', required: false, }, quantity: { type: 'number', required: true, }, }, indexes: { primary: { pk: { field: 'pk', composite: ['playerName'] }, sk: { field: 'sk', composite: ['itemName'] }, }, }, }, { client, table: process.env.TABLE_NAME }, );O ElectroDB nos permite não apenas definir nossos tipos, mas também fornecer valores padrão para certos campos como timestamps. Além disso, o ElectroDB segue o design de tabela única, que é a melhor prática ao usar DynamoDB.
Para preparar o servidor MCP para interagir com o inventário, vamos garantir que exportamos a entidade de inventário em packages/game-api/src/index.ts:
export type { AppRouter } from './router.js';export { appRouter } from './router.js';export type { Context } from './init.js';export * from './client/index.js';export * from './schema/index.js';export * from './entities/index.js';Definindo nossos procedimentos
Seção intitulada “Definindo nossos procedimentos”Para implementar os métodos da API, faça as seguintes alterações em packages/game-api/src/procedures:
import { createActionEntity, createGameEntity } from '../entities/index.js';import { ActionSchema, IAction, QueryInputSchema, createPaginatedQueryOutput,} from '../schema/index.js';import { publicProcedure } from '../init.js';import { z } from 'zod';
export const queryActions = publicProcedure .input(QueryInputSchema.extend({ playerName: z.string() })) .output(createPaginatedQueryOutput(ActionSchema)) .query(async ({ input }) => { const actionEntity = createActionEntity(); const result = await actionEntity.query .primary({ playerName: input.playerName }) .go({ cursor: input.cursor, count: input.limit });
return { items: result.data as IAction[], cursor: result.cursor, }; });
export const saveAction = publicProcedure .input(ActionSchema.omit({ timestamp: true })) .output(ActionSchema) .mutation(async ({ input }) => { const actionEntity = createActionEntity(); const gameEntity = createGameEntity();
const action = await actionEntity.put(input).go(); await gameEntity .update({ playerName: input.playerName }) .set({ lastUpdated: action.data.timestamp }) .go(); return action.data as IAction; });import { createGameEntity } from '../entities/index.js';import { GameSchema, IGame, QueryInputSchema, createPaginatedQueryOutput,} from '../schema/index.js';import { publicProcedure } from '../init.js';
export const queryGames = publicProcedure .input(QueryInputSchema) .output(createPaginatedQueryOutput(GameSchema)) .query(async ({ input }) => { const gameEntity = createGameEntity(); const result = await gameEntity.scan.go({ cursor: input.cursor, count: input.limit, });
return { items: result.data as IGame[], cursor: result.cursor, }; });
export const saveGame = publicProcedure .input(GameSchema.omit({ lastUpdated: true })) .output(GameSchema) .mutation(async ({ input }) => { const gameEntity = createGameEntity();
const result = await gameEntity.put(input).go(); return result.data as IGame; });import { ItemSchema, QueryInputSchema, createPaginatedQueryOutput,} from '../schema/index.js';import { publicProcedure } from '../init.js';import { z } from 'zod';import { createInventoryEntity } from '../entities/index.js';
export const queryInventory = publicProcedure .input(QueryInputSchema.extend({ playerName: z.string() })) .output(createPaginatedQueryOutput(ItemSchema)) .query(async ({ input }) => { const inventoryEntity = createInventoryEntity(); const result = await inventoryEntity.query .primary({ playerName: input.playerName }) .go({ cursor: input.cursor, count: input.limit });
return { items: result.data, cursor: result.cursor, }; });Exclua o arquivo echo.ts (de packages/game-api/src/procedures) pois não o usaremos neste projeto.
Configuração do roteador
Seção intitulada “Configuração do roteador”Depois de definir nossos procedimentos, para conectá-los à nossa API, atualize o seguinte arquivo:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';import { queryActions, saveAction } from './procedures/actions.js';import { queryGames, saveGame } from './procedures/games.js';import { queryInventory } from './procedures/inventory.js';
export const router = t.router;
export const appRouter = router({ actions: router({ query: queryActions, save: saveAction, }), games: router({ query: queryGames, save: saveGame, }), inventory: router({ query: queryInventory, }),});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';import { queryActions, saveAction } from './procedures/actions.js';import { queryGames, saveGame } from './procedures/games.js';import { queryInventory } from './procedures/inventory.js';
export const router = t.router;
export const appRouter = router({ echo, actions: router({ query: queryActions, save: saveAction, }), games: router({ query: queryGames, save: saveGame, }), inventory: router({ query: queryInventory, }),});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;Tarefa 2: Criar um servidor MCP de Inventário
Seção intitulada “Tarefa 2: Criar um servidor MCP de Inventário”Vamos criar um servidor MCP que permitirá ao nosso agente gerenciar itens no inventário de um jogador.
Definiremos as seguintes ferramentas para nosso agente:
list-inventory-itemspara recuperar os itens atuais do inventário do jogadoradd-to-inventorypara adicionar itens ao inventário do jogadorremove-from-inventorypara remover itens do inventário do jogador
Para economizar tempo, vamos definir todas as ferramentas inline:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';import z from 'zod-v3';import { createInventoryEntity } from ':dungeon-adventure/game-api';
/** * Create the MCP Server */export const createServer = () => { const server = new McpServer({ name: 'inventory-mcp-server', version: '1.0.0', });
const inventory = createInventoryEntity();
server.tool( 'list-inventory-items', "List items in the player's inventory. Leave cursor blank unless you are requesting subsequent pages", { playerName: z.string(), cursor: z.string().optional(), }, async ({ playerName }) => { const results = await inventory.query .primary({ playerName, }) .go();
return { content: [{ type: 'text', text: JSON.stringify(results) }], }; }, );
server.tool( 'add-to-inventory', "Add an item to the player's inventory. Quantity defaults to 1 if omitted.", { playerName: z.string(), itemName: z.string(), emoji: z.string(), quantity: z.number().optional().default(1), }, async ({ playerName, itemName, emoji, quantity = 1 }) => { await inventory .put({ playerName, itemName, quantity, emoji, }) .go();
return { content: [ { type: 'text', text: `Added ${itemName} (x${quantity}) to inventory`, }, ], }; }, );
server.tool( 'remove-from-inventory', "Remove an item from the player's inventory. If quantity is omitted, all items are removed.", { playerName: z.string(), itemName: z.string(), quantity: z.number().optional(), }, async ({ playerName, itemName, quantity }) => { // If quantity is omitted, remove the entire item if (quantity === undefined) { try { await inventory.delete({ playerName, itemName }).go(); return { content: [ { type: 'text', text: `${itemName} removed from inventory.` }, ], } as const; } catch { return { content: [ { type: 'text', text: `${itemName} not found in inventory` }, ], } as const; } }
// If quantity is specified, fetch current quantity and update const item = await inventory.get({ playerName, itemName }).go();
if (!item.data) { return { content: [ { type: 'text', text: `${itemName} not found in inventory` }, ], } as const; }
const newQuantity = item.data.quantity - quantity;
if (newQuantity <= 0) { await inventory.delete({ playerName, itemName }).go(); return { content: [ { type: 'text', text: `${itemName} removed from inventory.` }, ], } as const; }
await inventory .put({ playerName, itemName, quantity: newQuantity, emoji: item.data.emoji, }) .go();
return { content: [ { type: 'text', text: `Removed ${itemName} (x${quantity}) from inventory. ${newQuantity} remaining.`, }, ], }; }, );
return server;};import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';import { registerAddTool } from './tools/add.js';import { registerSampleGuidanceResource } from './resources/sample-guidance.js';import z from 'zod-v3';import { createInventoryEntity } from ':dungeon-adventure/game-api';
/** * Create the MCP Server */export const createServer = () => { const server = new McpServer({ name: 'inventory-mcp-server', version: '1.0.0', });
registerAddTool(server); registerSampleGuidanceResource(server); const inventory = createInventoryEntity();
server.tool( 'list-inventory-items', "List items in the player's inventory. Leave cursor blank unless you are requesting subsequent pages", { playerName: z.string(), cursor: z.string().optional(), }, async ({ playerName }) => { const results = await inventory.query .primary({ playerName, }) .go();
return { content: [{ type: 'text', text: JSON.stringify(results) }], }; }, );
server.tool( 'add-to-inventory', "Add an item to the player's inventory. Quantity defaults to 1 if omitted.", { playerName: z.string(), itemName: z.string(), emoji: z.string(), quantity: z.number().optional().default(1), }, async ({ playerName, itemName, emoji, quantity = 1 }) => { await inventory .put({ playerName, itemName, quantity, emoji, }) .go();
return { content: [ { type: 'text', text: `Added ${itemName} (x${quantity}) to inventory`, }, ], }; }, );
server.tool( 'remove-from-inventory', "Remove an item from the player's inventory. If quantity is omitted, all items are removed.", { playerName: z.string(), itemName: z.string(), quantity: z.number().optional(), }, async ({ playerName, itemName, quantity }) => { // If quantity is omitted, remove the entire item if (quantity === undefined) { try { await inventory.delete({ playerName, itemName }).go(); return { content: [ { type: 'text', text: `${itemName} removed from inventory.` }, ], } as const; } catch { return { content: [ { type: 'text', text: `${itemName} not found in inventory` }, ], } as const; } }
// If quantity is specified, fetch current quantity and update const item = await inventory.get({ playerName, itemName }).go();
if (!item.data) { return { content: [ { type: 'text', text: `${itemName} not found in inventory` }, ], } as const; }
const newQuantity = item.data.quantity - quantity;
if (newQuantity <= 0) { await inventory.delete({ playerName, itemName }).go(); return { content: [ { type: 'text', text: `${itemName} removed from inventory.` }, ], } as const; }
await inventory .put({ playerName, itemName, quantity: newQuantity, emoji: item.data.emoji, }) .go();
return { content: [ { type: 'text', text: `Removed ${itemName} (x${quantity}) from inventory. ${newQuantity} remaining.`, }, ], }; }, );
return server;};Conforme o número de ferramentas crescer, você pode refatorá-las em arquivos separados se preferir.
Exclua os diretórios tools e resources em packages/inventory/src/mcp-server pois não serão utilizados.
Tarefa 3: Atualizar a infraestrutura
Seção intitulada “Tarefa 3: Atualizar a infraestrutura”A etapa final é atualizar nossa infraestrutura para criar a tabela DynamoDB e conceder permissões para executar operações da Game API.
Para isso, atualize o packages/infra/src conforme segue:
import { CfnOutput } from 'aws-cdk-lib';import { AttributeType, BillingMode, ProjectionType, Table, TableProps,} from 'aws-cdk-lib/aws-dynamodb';import { Construct } from 'constructs';import { suppressRules } from ':dungeon-adventure/common-constructs';
export type ElectrodbDynamoTableProps = Omit< TableProps, 'partitionKey' | 'sortKey' | 'billingMode'>;
export class ElectrodbDynamoTable extends Table { constructor(scope: Construct, id: string, props?: ElectrodbDynamoTableProps) { super(scope, id, { partitionKey: { name: 'pk', type: AttributeType.STRING, }, sortKey: { name: 'sk', type: AttributeType.STRING, }, billingMode: BillingMode.PAY_PER_REQUEST, ...props, });
this.addGlobalSecondaryIndex({ indexName: 'gsi1pk-gsi1sk-index', partitionKey: { name: 'gsi1pk', type: AttributeType.STRING, }, sortKey: { name: 'gsi1sk', type: AttributeType.STRING, }, projectionType: ProjectionType.ALL, });
// Suppress checkov rules that expect a KMS customer managed key and backup to be enabled suppressRules(this, ['CKV_AWS_119', 'CKV_AWS_28'], 'No need for custom encryption or backup');
new CfnOutput(this, 'TableName', { value: this.tableName }); }}import { GameApi, GameUI, InventoryMcpServer, RuntimeConfig, StoryAgent, UserIdentity,} from ':dungeon-adventure/common-constructs';import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';import { Construct } from 'constructs';import { ElectrodbDynamoTable } from '../constructs/electrodb-table.js';import { RuntimeAuthorizerConfiguration } from '@aws-cdk/aws-bedrock-agentcore-alpha';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
const userIdentity = new UserIdentity(this, 'UserIdentity');
const electroDbTable = new ElectrodbDynamoTable(this, 'ElectroDbTable');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this) .withDefaultOptions({ environment: { TABLE_NAME: electroDbTable.tableName, }, }) .build(), });
electroDbTable.grantReadData(gameApi.integrations['actions.query'].handler); electroDbTable.grantReadData(gameApi.integrations['games.query'].handler); electroDbTable.grantReadData(gameApi.integrations['inventory.query'].handler); electroDbTable.grantReadWriteData( gameApi.integrations['actions.save'].handler, ); electroDbTable.grantReadWriteData( gameApi.integrations['games.save'].handler, );
const { userPool, userPoolClient } = userIdentity;
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer', { environmentVariables: { TABLE_NAME: electroDbTable.tableName, }, }); electroDbTable.grantReadWriteData(mcpServer.agentCoreRuntime);
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { authorizerConfiguration: RuntimeAuthorizerConfiguration.usingCognito( userPool, [userPoolClient], ), environmentVariables: { INVENTORY_MCP_ARN: mcpServer.agentCoreRuntime.agentRuntimeArn, }, }); // Add the Story Agent ARN to runtime-config.json so it can be referenced by the website RuntimeConfig.ensure(this).config.agentArn = storyAgent.agentCoreRuntime.agentRuntimeArn;
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.agentRuntimeArn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.agentRuntimeArn, });
// Grant the agent permissions to invoke our mcp server mcpServer.agentCoreRuntime.grantInvoke(storyAgent.agentCoreRuntime);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}import { GameApi, GameUI, InventoryMcpServer, RuntimeConfig, StoryAgent, UserIdentity,} from ':dungeon-adventure/common-constructs';import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';import { Construct } from 'constructs';import { ElectrodbDynamoTable } from '../constructs/electrodb-table.js';import { RuntimeAuthorizerConfiguration } from '@aws-cdk/aws-bedrock-agentcore-alpha';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
const userIdentity = new UserIdentity(this, 'UserIdentity');
const electroDbTable = new ElectrodbDynamoTable(this, 'ElectroDbTable');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), integrations: GameApi.defaultIntegrations(this) .withDefaultOptions({ environment: { TABLE_NAME: electroDbTable.tableName, }, }) .build(), });
electroDbTable.grantReadData(gameApi.integrations['actions.query'].handler); electroDbTable.grantReadData(gameApi.integrations['games.query'].handler); electroDbTable.grantReadData(gameApi.integrations['inventory.query'].handler); electroDbTable.grantReadWriteData( gameApi.integrations['actions.save'].handler, ); electroDbTable.grantReadWriteData( gameApi.integrations['games.save'].handler, );
const { userPool, userPoolClient } = userIdentity;
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer'); const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer', { environmentVariables: { TABLE_NAME: electroDbTable.tableName, }, }); electroDbTable.grantReadWriteData(mcpServer.agentCoreRuntime);
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { authorizerConfiguration: RuntimeAuthorizerConfiguration.usingCognito( userPool, [userPoolClient], ), environmentVariables: { INVENTORY_MCP_ARN: mcpServer.agentCoreRuntime.agentRuntimeArn, }, }); // Add the Story Agent ARN to runtime-config.json so it can be referenced by the website RuntimeConfig.ensure(this).config.agentArn = storyAgent.agentCoreRuntime.agentRuntimeArn;
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.agentRuntimeArn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.agentRuntimeArn, });
// Grant the agent permissions to invoke our mcp server mcpServer.agentCoreRuntime.grantInvoke(storyAgent.agentCoreRuntime);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}Tarefa 4: Implantação e testes
Seção intitulada “Tarefa 4: Implantação e testes”Primeiro, corrija quaisquer problemas de lint:
pnpm nx run-many --target lint --configuration=fix --allyarn nx run-many --target lint --configuration=fix --allnpx nx run-many --target lint --configuration=fix --allbunx nx run-many --target lint --configuration=fix --allDepois construa a base de código:
pnpm nx run-many --target build --allyarn nx run-many --target build --allnpx nx run-many --target build --allbunx nx run-many --target build --allImplantar sua aplicação
Seção intitulada “Implantar sua aplicação”Para implantar sua aplicação, execute o seguinte comando:
pnpm nx deploy infra "dungeon-adventure-infra-sandbox/*"yarn nx deploy infra "dungeon-adventure-infra-sandbox/*"npx nx deploy infra "dungeon-adventure-infra-sandbox/*"bunx nx deploy infra "dungeon-adventure-infra-sandbox/*"Sua primeira implantação levará cerca de 8 minutos para ser concluída. Implantações subsequentes levarão cerca de 2 minutos.
Uma vez concluída a implantação, você verá saídas semelhantes às seguintes (alguns valores foram omitidos):
dungeon-adventure-sandbox-Applicationdungeon-adventure-sandbox-Application: deploying... [2/2]
✅ dungeon-adventure-sandbox-Application
✨ Deployment time: 354s
Outputs:dungeon-adventure-sandbox-Application.ElectroDbTableTableNameXXX = dungeon-adventure-sandbox-Application-ElectroDbTableXXX-YYYdungeon-adventure-sandbox-Application.GameApiEndpointXXX = https://xxx.execute-api.region.amazonaws.com/prod/dungeon-adventure-sandbox-Application.GameUIDistributionDomainNameXXX = xxx.cloudfront.netdungeon-adventure-sandbox-Application.StoryApiEndpointXXX = https://xxx.execute-api.region.amazonaws.com/prod/dungeon-adventure-sandbox-Application.UserIdentityUserIdentityIdentityPoolIdXXX = region:xxxdungeon-adventure-sandbox-Application.UserIdentityUserIdentityUserPoolIdXXX = region_xxxTestar a API
Seção intitulada “Testar a API”Você pode testar a API de duas formas:
- Iniciando uma instância local do backend tRPC e invocando as APIs usando
curl. - Chamando a API implantada usando curl com sigv4 habilitado
curl com Sigv4 habilitado
Você pode adicionar o seguinte script ao seu arquivo
.bashrc(e executarsourcenele) ou colar o seguinte no mesmo terminal onde deseja executar o comando.~/.bashrc acurl () {REGION=$1SERVICE=$2shift; shift;curl --aws-sigv4 "aws:amz:$REGION:$SERVICE" --user "$(aws configure get aws_access_key_id):$(aws configure get aws_secret_access_key)" -H "X-Amz-Security-Token: $(aws configure get aws_session_token)" "$@"}Para fazer uma requisição curl autenticada com
sigv4, invoqueacurlusando estes exemplos:API Gateway
Seção intitulada “API Gateway”Terminal window acurl ap-southeast-2 execute-api -X GET https://xxxURL de função Lambda streaming
Seção intitulada “URL de função Lambda streaming”Terminal window acurl ap-southeast-2 lambda -N -X POST https://xxxVocê pode adicionar a seguinte função ao seu perfil PowerShell ou colar o seguinte na mesma sessão PowerShell onde deseja executar o comando.
Terminal window # Perfil PowerShell ou sessão atualfunction acurl {param([Parameter(Mandatory=$true)][string]$Region,[Parameter(Mandatory=$true)][string]$Service,[Parameter(ValueFromRemainingArguments=$true)][string[]]$CurlArgs)$AccessKey = aws configure get aws_access_key_id$SecretKey = aws configure get aws_secret_access_key$SessionToken = aws configure get aws_session_token& curl --aws-sigv4 "aws:amz:$Region`:$Service" --user "$AccessKey`:$SecretKey" -H "X-Amz-Security-Token: $SessionToken" @CurlArgs}Para fazer uma requisição curl autenticada com
sigv4, invoqueacurlusando estes exemplos:API Gateway
Seção intitulada “API Gateway”Terminal window acurl ap-southeast-2 execute-api -X GET https://xxxURL de função Lambda streaming
Seção intitulada “URL de função Lambda streaming”Terminal window acurl ap-southeast-2 lambda -N -X POST https://xxx
Para iniciar seu servidor local game-api, execute o seguinte comando:
TABLE_NAME=dungeon-adventure-infra-sandbox-Application-ElectroDbTableXXX-YYY pnpm nx run @dungeon-adventure/game-api:serveTABLE_NAME=dungeon-adventure-infra-sandbox-Application-ElectroDbTableXXX-YYY yarn nx run @dungeon-adventure/game-api:serveTABLE_NAME=dungeon-adventure-infra-sandbox-Application-ElectroDbTableXXX-YYY npx nx run @dungeon-adventure/game-api:serveTABLE_NAME=dungeon-adventure-infra-sandbox-Application-ElectroDbTableXXX-YYY bunx nx run @dungeon-adventure/game-api:serveAssim que seu servidor estiver em execução, você pode chamá-lo executando o seguinte comando:
curl -X GET 'http://localhost:2022/games.query?input=%7B%7D'acurl ap-southeast-2 execute-api -X GET 'https://xxx.execute-api.ap-southeast-2.amazonaws.com/prod/games.query?input=%7B%7D'Se o comando for executado com sucesso, você verá uma resposta como:
{"result":{"data":{"items":[],"cursor":null}}}Parabéns, você construiu e implantou sua primeira API usando tRPC! 🎉🎉🎉