Jogo de Dungeons com IA
Módulo 1: Configuração do monorepo
Vamos começar criando um novo monorepo. A partir do diretório desejado, execute o seguinte comando:
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=pnpm --preset=ts --ci=skip --formatter=prettier
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=yarn --preset=ts --ci=skip --formatter=prettier
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=npm --preset=ts --ci=skip --formatter=prettier
npx create-nx-workspace@~20.6.3 dungeon-adventure --pm=bun --preset=ts --ci=skip --formatter=prettier
Isso configurará um monorepo NX dentro do diretório dungeon-adventure
que você poderá abrir no vscode. Deve parecer com o seguinte:
Directory.nx/
- …
Directory.vscode/
- …
Directorynode_modules/
- …
Directorypackages/ aqui é onde seus subprojetos residirão
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json configura o CLI do NX e padrões do monorepo
- package.json todas as dependências do node são definidas aqui
- pnpm-lock.yaml ou bun.lock, yarn.lock, package-lock.json dependendo do gerenciador de pacotes
- pnpm-workspace.yaml se usar pnpm
- README.md
- tsconfig.base.json todos os subprojetos baseados em node estendem este
- tsconfig.json
Para começar a adicionar componentes do @aws/nx-plugin
ao monorepo, precisamos instalá-lo como uma dependência de desenvolvimento executando o seguinte comando da raiz do monorepo dungeon-adventure
:
pnpm add -Dw @aws/nx-plugin
yarn add -D @aws/nx-plugin
npm install --legacy-peer-deps -D @aws/nx-plugin
bun install -D @aws/nx-plugin
Agora estamos prontos para começar a criar nossos diferentes subprojetos usando o @aws/nx-plugin
.
Game API
Primeiro vamos criar nossa Game API. Para isso, vamos criar uma API tRPC chamada GameApi
seguindo os passos abaixo:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#trpc-api
- Preencha os parâmetros obrigatórios
- apiName: GameApi
- Clique em
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --apiName=GameApi --no-interactive --dry-run
Você deve ver alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do ts#trpc-api
Abaixo está uma lista de todos os arquivos gerados pelo generator ts#trpc-api
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ constructs CDK específicos da aplicação
Directoryhttp-apis/
- game-api.ts construct CDK para criar sua API tRPC
- index.ts
- …
- index.ts
Directorycore/ constructs CDK genéricos
- http-api.ts construct CDK base para uma API HTTP
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Directorytypes/ tipos compartilhados
Directorysrc/
- index.ts
- runtime-config.ts definição de interface usada pelo CDK e website
- project.json
- …
Directorygame-api/
Directorybackend/ código de implementação tRPC
Directorysrc/
Directoryclient/ cliente vanilla usado para chamadas máquina a máquina
- index.ts
- sigv4.ts
Directorymiddleware/ instrumentação com powertools
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
Directoryprocedures/ implementações específicas para os procedimentos/rotas da API
- echo.ts
- index.ts
- init.ts configura contexto e middleware
- local-server.ts usado ao executar o servidor tRPC localmente
- router.ts ponto de entrada para o lambda handler que define todos os procedimentos
- project.json
- …
Directoryschema/
Directorysrc/
Directoryprocedures/
- echo.ts
- index.ts
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
Analisando alguns dos arquivos-chave:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer>, ) => ctx,});
export type AppRouter = typeof appRouter;
O roteador define o ponto de entrada para sua API tRPC e é onde você declarará todos os métodos da API. Como visto acima, temos um método chamado echo
com sua implementação no arquivo ./procedures/echo.ts
.
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from ':dungeon-adventure/game-api-schema';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
Este arquivo é a implementação do método echo
e como visto é fortemente tipado declarando suas estruturas de dados de entrada e saída. Ele importa essas definições do projeto :dungeon-adventure/game-api-schema
que é um alias para o projeto de schema.
import { z } from 'zod';
export const EchoInputSchema = z.object({ message: z.string(),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ result: z.string(),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
Todas as definições de schema tRPC são feitas usando Zod e exportadas como tipos TypeScript via sintaxe z.TypeOf
.
import { Construct } from 'constructs';import * as url from 'url';import { HttpApi } from '../../core/http-api.js';import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';import { Runtime } from 'aws-cdk-lib/aws-lambda';
export class GameApi extends HttpApi { constructor(scope: Construct, id: string) { super(scope, id, { defaultAuthorizer: new HttpIamAuthorizer(), apiName: 'GameApi', runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', handlerFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/backend/bundle', import.meta.url, ), ), }); }}
Este é o construct CDK que define nossa GameApi. Como visto, ele já configurou o caminho do handler para o bundle gerado de nossa implementação backend tRPC. Isso significa que no momento do cdk synth
, o bundling não ocorre (diferente de usar NodeJsFunction), pois já fizemos o bundling como parte do target de build do projeto backend.
Story API
Agora vamos criar nossa Story API. Para isso, vamos criar uma API Fast chamada StoryApi
seguindo os passos abaixo:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - py#fast-api
- Preencha os parâmetros obrigatórios
- name: StoryApi
- Clique em
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --no-interactive --dry-run
Você deve ver alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do py#fast-api
Abaixo está uma lista de todos os arquivos gerados pelo generator py#fast-api
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directory.venv/ ambiente virtual único para o monorepo
- …
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ constructs CDK específicos da aplicação
Directoryhttp-apis/
- story-api.ts construct CDK para criar sua Fast API
- index.ts atualizado para exportar a nova story-api
- project.json atualizado para adicionar dependência de build no story_api
Directorytypes/ tipos compartilhados
Directorysrc/
- runtime-config.ts atualizado para adicionar a StoryApi
Directorystory_api/
Directorystory_api/ módulo python
- init.py configura powertools, FastAPI e middleware
- main.py ponto de entrada para o lambda contendo todas as rotas
Directorytests/
- …
- .python-version
- project.json
- pyproject.toml
- project.json
- .python-version versão do python fixada pelo uv
- pyproject.toml
- uv.lock
import { Construct } from 'constructs';import * as url from 'url';import { HttpApi } from '../../core/http-api.js';import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';import { Runtime } from 'aws-cdk-lib/aws-lambda';
export class StoryApi extends HttpApi { constructor(scope: Construct, id: string) { super(scope, id, { defaultAuthorizer: new HttpIamAuthorizer(), apiName: 'StoryApi', runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', handlerFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), }); }}
Este é o construct CDK que define nossa StoryApi. Como visto, ele já configurou o caminho do handler para o bundle gerado de nossa implementação backend Fast API. Isso significa que no momento do cdk synth
, o bundling não ocorre (diferente de PythonFunction), pois já fizemos o bundling como parte do target de build do projeto backend.
export type ApiUrl = string;// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interfaceexport interface IRuntimeConfig { httpApis: { GameApi: ApiUrl; StoryApi: ApiUrl; };}
Aqui está um exemplo do generator realizando uma transformação AST que preserva todo o código existente e realiza uma atualização. Veja que a StoryApi
foi adicionada à definição IRuntimeConfig
, o que significa que quando isso for consumido pelo frontend, irá impor type safety!
from .init import app, lambda_handler, tracer
handler = lambda_handler
@app.get("/")@tracer.capture_methoddef read_root(): return {"Hello": "World"}
Este é onde todos os métodos da API serão definidos. Como visto aqui, temos um método read_root
mapeado para a rota GET /
. Você pode usar Pydantic para declarar entradas e saídas dos métodos e garantir type safety.
Game UI: Website
Agora vamos criar a UI que permitirá interagir com o jogo. Para isso, vamos criar um website chamado GameUI
seguindo os passos abaixo:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#cloudscape-website
- Preencha os parâmetros obrigatórios
- name: GameUI
- Clique em
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website --name=GameUI --no-interactive --dry-run
Você deve ver alguns novos arquivos aparecerem na sua árvore de arquivos.
Arquivos atualizados do ts#cloudscape-website
Abaixo está uma lista de todos os arquivos gerados pelo generator ts#cloudscape-website
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directoryapp/ constructs CDK específicos da aplicação
Directorystatic-websites/
- game-ui.ts construct CDK para criar sua Game UI
Directorycore/
- static-website.ts construct genérico de website estático
Directorygame-ui/
Directorypublic/
- …
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.ts layout geral da página: cabeçalho, rodapé, sidebar, etc
- navitems.ts itens de navegação da sidebar
Directoryhooks/
- useAppLayout.tsx permite definir dinamicamente notificações, estilo da página, etc
Directoryroutes/ rotas baseadas em arquivo do @tanstack/react-router
- index.tsx página raiz ’/’ redireciona para ‘/welcome’
- __root.tsx todos os páginas usam este componente como base
Directorywelcome/
- index.tsx
- config.ts
- main.tsx ponto de entrada do React
- routeTree.gen.ts atualizado automaticamente pelo @tanstack/react-router
- styles.css
- index.html
- project.json
- vite.config.ts
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
Este é o construct CDK que define nossa GameUI. Como visto, ele já configurou o caminho para o bundle gerado de nossa UI baseada em Vite. Isso significa que no momento do build
, o bundling ocorre dentro do target de build do projeto game-ui e sua saída é usada aqui.
import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';
import '@cloudscape-design/global-styles/index.css';
const router = createRouter({ routeTree });
// Registra a instância do router para type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RouterProvider router={router} /> </I18nProvider> </React.StrictMode>, );
Este é o ponto de entrada onde o React é montado. Como mostrado, ele inicialmente configura um @tanstack/react-router
em uma configuração de roteamento baseado em arquivos
. Isso significa que, enquanto seu servidor de desenvolvimento estiver rodando, você pode simplesmente criar arquivos na pasta routes
e o @tanstack/react-router
criará a configuração boilerplate para você, atualizando o arquivo routeTree.gen.ts
. Este arquivo mantém todas as rotas de forma type-safe, o que significa que ao usar <Link>
, a opção to
só mostrará rotas válidas. Para mais informações, consulte a documentação do @tanstack/react-router
.
import { ContentLayout, Header, SpaceBetween, Container,} from '@cloudscape-design/components';import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/welcome/')({ component: RouteComponent,});
function RouteComponent() { return ( <ContentLayout header={<Header>Welcome</Header>}> <SpaceBetween size="l"> <Container>Welcome to your new Cloudscape website!</Container> </SpaceBetween> </ContentLayout> );}
Um componente que será renderizado ao navegar para a rota /welcome
. O @tanstack/react-router
gerenciará a Route
para você sempre que criar/mover este arquivo (desde que o servidor de desenvolvimento esteja rodando). Isso será mostrado em uma seção posterior deste tutorial.
Game UI: Auth
Agora vamos configurar nossa Game UI para exigir acesso autenticado via Amazon Cognito seguindo os passos abaixo:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#cloudscape-website#auth
- Preencha os parâmetros obrigatórios
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- Clique em
Generate
pnpm nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#cloudscape-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
Você deve ver alguns arquivos novos/alterados em sua árvore de arquivos.
Arquivos atualizados do ts#cloudscape-website#auth
Abaixo está uma lista de todos os arquivos gerados/atualizados pelo generator ts#cloudscape-website#auth
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
- user-identity.ts construct CDK para criar pools de usuários/identidade
Directorytypes/
Directorysrc/
- runtime-config.ts atualizado para adicionar cognitoProps
Directorygame-ui/
Directorysrc/
Directorycomponents/
DirectoryAppLayout/
- index.tsx adiciona o usuário logado/logout ao cabeçalho
DirectoryCognitoAuth/
- index.ts gerencia login no Cognito
DirectoryRuntimeConfig/
- index.tsx busca o
runtime-config.json
e fornece aos filhos via contexto
- index.tsx busca o
Directoryhooks/
- useRuntimeConfig.tsx
- main.tsx Atualizado para adicionar Cognito
import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// Registra a instância do router para type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <RouterProvider router={router} /> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
Os componentes RuntimeConfigProvider
e CognitoAuth
foram adicionados ao arquivo main.tsx
via transformação AST. Isso permite que o componente CognitoAuth
autentique com o Amazon Cognito buscando o runtime-config.json
que contém a configuração de conexão Cognito necessária para fazer chamadas backend ao destino correto.
Game UI: Conectar à Story API
Agora vamos configurar nossa Game UI para se conectar à Story API criada anteriormente:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - api-connection
- Preencha os parâmetros obrigatórios
- sourceProject: @dungeon-adventure/game-ui
- targetProject: dungeon_adventure.story_api
- Clique em
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
Você deve ver alguns arquivos novos/alterados em sua árvore de arquivos.
Arquivos atualizados da conexão UI -> FastAPI
Abaixo está uma lista de todos os arquivos gerados/atualizados pelo generator api-connection
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorygame-ui/
Directorysrc/
Directoryhooks/
- useSigV4.tsx usado pela StoryApi para assinar requests
- useStoryApiClient.tsx hook para construir um cliente StoryApi
- useStoryApi.tsx hook para interagir com a StoryApi usando TanStack Query
Directorycomponents/
- QueryClientProvider.tsx provedor do cliente TanStack Query
- StoryApiProvider.tsx Provedor para o hook TanStack Query da StoryApi
- main.tsx Instrumenta o QueryClientProvider e StoryApiProvider
- .gitignore ignora arquivos de cliente gerados
- project.json atualizado para adicionar targets para gerar hooks openapi
- …
Directorystory_api/
Directoryscripts/
- generate_open_api.py
- project.json atualizado para emitir um arquivo openapi.json
import { StoryApi } from '../generated/story-api/client.gen';import { useSigV4 } from './useSigV4';import { useRuntimeConfig } from './useRuntimeConfig';import { useMemo } from 'react';
export const useStoryApi = (): StoryApi => { const runtimeConfig = useRuntimeConfig(); const apiUrl = runtimeConfig.httpApis.StoryApi; const sigv4Client = useSigV4(); return useMemo( () => new StoryApi({ url: apiUrl, fetch: sigv4Client, }), [apiUrl, sigv4Client], );};
Este hook pode ser usado para fazer requests autenticados à StoryApi
. Como visto na implementação, ele usa o StoryApi
que é gerado no momento do build e, portanto, você verá um erro em sua IDE até construirmos nosso código. Para mais detalhes sobre como o cliente é gerado ou como consumir a API, consulte o guia React para FastAPI.
import { createContext, FC, PropsWithChildren, useMemo } from 'react';import { useStoryApiClient } from '../hooks/useStoryApiClient';import { StoryApiOptionsProxy } from '../generated/story-api/options-proxy.gen';
export const StoryApiContext = createContext<StoryApiOptionsProxy | undefined>( undefined,);
export const StoryApiProvider: FC<PropsWithChildren> = ({ children }) => { const client = useStoryApiClient(); const optionsProxy = useMemo( () => new StoryApiOptionsProxy({ client }), [client], );
return ( <StoryApiContext.Provider value={optionsProxy}> {children} </StoryApiContext.Provider> );};
export default StoryApiProvider;
O componente provedor acima usa o hook useStoryApiClient
e instancia o StoryApiOptionsProxy
, que é usado para construir opções para os hooks TanStack Query. Você pode usar o hook correspondente useStoryApi
para acessar este proxy de opções, que fornece uma maneira de interagir com sua FastAPI de forma consistente com sua API tRPC.
Como useStoryApiClient
nos fornece um iterador assíncrono para nossa API de streaming, usaremos apenas o cliente vanilla diretamente neste tutorial.
Game UI: Conectar à Game API
Agora vamos configurar nossa Game UI para se conectar à Game API criada anteriormente:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - api-connection
- Preencha os parâmetros obrigatórios
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api-backend
- Clique em
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api-backend --no-interactive --dry-run
Você deve ver alguns arquivos novos/alterados em sua árvore de arquivos.
Arquivos atualizados da conexão UI -> tRPC
Abaixo está uma lista de todos os arquivos gerados/atualizados pelo generator api-connection
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorygame-ui/
Directorysrc/
Directorycomponents/
DirectoryTrpcClients/
- index.tsx
- TrpcApis.tsx todas as APIs tRPC configuradas
- TrpcClientProviders.tsx cria um provedor de cliente por API tRPC
- TrpcProvider.tsx
Directoryhooks/
- useGameApi.tsx hooks para chamar a GameApi
- main.tsx injeta os provedores de clientes trpc
- package.json
import { TrpcApis } from '../components/TrpcClients';
export const useGameApi = () => TrpcApis.GameApi.useTRPC();
Este hook usa a integração mais recente do React Query do tRPC, permitindo que usuários interajam com @tanstack/react-query
diretamente sem camadas adicionais de abstração. Para exemplos de como chamar APIs tRPC, consulte o guia de uso do hook tRPC.
import TrpcClientProviders from './components/TrpcClients';import QueryClientProvider from './components/QueryClientProvider';import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// Registra a instância do router para type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <TrpcClientProviders> <RouterProvider router={router} /> </TrpcClientProviders> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
O arquivo main.tsx
foi atualizado via transformação AST para injetar os provedores tRPC.
Game UI: Infraestrutura
Agora o último subprojeto que precisamos criar é para a infraestrutura CDK. Para criá-lo, siga os passos abaixo:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#infra
- Preencha os parâmetros obrigatórios
- name: infra
- Clique em
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
Você deve ver alguns arquivos novos/alterados em sua árvore de arquivos.
Arquivos atualizados do ts#infra
Abaixo está uma lista de todos os arquivos gerados/atualizados pelo generator ts#infra
. Vamos examinar alguns dos arquivos-chave destacados na árvore de arquivos:
Directorypackages/
Directorycommon/
Directoryconstructs/
Directorysrc/
Directorycore/
Directorycfn-guard-rules/
- *.guard
- cfn-guard.ts
- index.ts
Directoryinfra
Directorysrc/
Directorystacks/
- application-stack.ts recursos CDK definidos aqui
- index.ts
- main.ts ponto de entrada que define todas as stacks
- cdk.json
- project.json
- …
- package.json
- tsconfig.json adiciona referências
- tsconfig.base.json adiciona alias
import { ApplicationStack } from './stacks/application-stack.js';import { App, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
// Use isto para implantar seu próprio ambiente sandbox (assume suas credenciais CLI)new ApplicationStack(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, crossRegionReferences: true,});
app.synth();
Este é o ponto de entrada para sua aplicação CDK.
Ele está configurado para usar cfn-guard
para executar validação de infraestrutura baseada no conjunto de regras configurado. Isso é instrumentado pós-síntese.
import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// O código que define sua stack vai aqui }}
Este é onde instanciaremos nossos constructs CDK para construir nosso jogo dungeon adventure.
Atualizar nossa infraestrutura
Vamos atualizar nosso packages/infra/src/stacks/application-stack.ts
para instanciar alguns de nossos constructs já gerados:
import { GameApi, GameUI, StoryApi, UserIdentity,} from ':dungeon-adventure/common-constructs';import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// O código que define sua stack vai aqui const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi'); const storyApi = new StoryApi(this, 'StoryApi');
// concede à nossa função autenticada acesso para invocar nossas APIs [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// Garante que isto seja instanciado por último para que nosso runtime-config.json possa ser configurado automaticamente new GameUI(this, 'GameUI'); }}
Construindo nosso código
Comandos Nx
Targets únicos vs múltiplos
O comando run-many
executará um target em múltiplos subprojetos listados (--all
os alvo todos). Ele garantirá que dependências sejam executadas na ordem correta.
Você também pode disparar um build (ou qualquer outra tarefa) para um target de projeto único executando o target diretamente no projeto. Por exemplo, se quisermos construir o projeto @dungeon-adventure/infra
, você pode executar:
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
Visualizando suas dependências
Você também pode visualizar suas dependências via:
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Cache
O Nx depende de cache para reutilizar artefatos de builds anteriores e acelerar o desenvolvimento. É necessária alguma configuração para que isso funcione corretamente e pode haver casos onde você queira executar um build sem usar o cache. Para isso, basta adicionar o argumento --skip-nx-cache
ao seu comando. Por exemplo:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
Se por algum motivo você quiser limpar seu cache (armazenado na pasta .nx
), execute:
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
Você deve ser questionado com o seguinte:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …Yes, sync the changes and run the tasksNo, run the tasks without syncing the changes
Esta mensagem indica que o NX detectou alguns arquivos que podem ser atualizados automaticamente. Neste caso, refere-se aos arquivos tsconfig.json
que não têm referências TypeScript configuradas para projetos dependentes. Selecione a opção Yes, sync the changes and run the tasks para prosseguir. Você deve notar que todos os erros de importação relacionados à sua IDE são resolvidos automaticamente, pois o generator sync adicionará as referências TypeScript faltantes automaticamente!
Todos os artefatos construídos estão agora disponíveis na pasta dist/
na raiz do monorepo. Esta é uma prática padrão ao usar projetos gerados pelo @aws/nx-plugin
, pois não polui sua árvore de arquivos com arquivos gerados. Caso queira limpar seus arquivos, você pode simplesmente deletar a pasta dist/
sem se preocupar com arquivos gerados espalhados.
Parabéns! Você criou todos os subprojetos necessários para começar a implementar o núcleo do nosso jogo Dunegeon Adventure. 🎉🎉🎉