# Como declaro e troco eventos tipados de app? [#como-declaro-e-troco-eventos-tipados-de-app] > Pergunta: "como declaro e troco eventos tipados de app?" ## `defineContract` — forma é dado, tipo deriva [#definecontract--forma-é-dado-tipo-deriva] ```ts import { defineContract } from '@sofya-sdk/event-room'; const contract = defineContract({ transcription: { text: 'string', partial: 'boolean' }, control: { action: 'string' }, }); // contract.events.transcription.validate(value) -> {success, data} | {success:false, issues?} // O tipo do payload é inferido da forma, sem repetir a declaração em TypeScript à mão. ``` A mini-linguagem de forma aceita `'string'`, `'number'`, `'boolean'` (e as variantes opcionais `'string?'`/`'number?'`/`'boolean?'`), objetos aninhados, e arrays (`[T]` = array de `T`; `[A, B]` = união de alternativas). ## Escape hatch: Standard Schema V1 [#escape-hatch-standard-schema-v1] Se você já tem um validador Standard Schema (Zod, Valibot, etc.), passe-o direto no lugar da forma: ```ts import { z } from 'zod'; const contract = defineContract({ transcription: z.object({ text: z.string(), partial: z.boolean() }), }); ``` O validador precisa ser **síncrono** — `defineContract` lança `usage` (`contract_async_validator`) se detectar um validador que retorna Promise. ## Opt-out total: `payload()` [#opt-out-total-payloadt] Para um evento cujo formato você não quer validar em runtime, mas ainda quer o tipo em TypeScript: ```ts import { payload } from '@sofya-sdk/event-room'; const contract = defineContract({ raw: payload<{ anything: unknown }>(), }); ``` Sem validação de runtime — e um custo específico de rede de proteção, ver a nota abaixo. ## Lado do host: `send`/`on`, do mesmo contrato [#lado-do-host-sendon-do-mesmo-contrato] Passe o mesmo `contract` para `createHost` — `room.send`/`room.on` trocam eventos de app pelo lado do host, simétrico ao `guest.sendEvent` acima: ```ts const { room } = await createHost({ url: 'ws://localhost:8080/ws', credential: 'dev-host-credential', guestUrl: 'https://guest.example.com/join', contract, room: { accessMode: 'approval_and_code' }, }); room.on('transcription', (payload) => { console.log(payload.text, payload.partial); }); room.send('control', { action: 'pause' }); ``` Sem `contract` em `createHost`, `room.send`/`room.on` lançam `usage` (`contract_required`) — os dois verbos só existem de verdade quando o host declarou o que fala. ## Limite: 16 KiB de payload [#limite-16-kib-de-payload] O limite é do **runtime do servidor**, não do contrato — `MAX_EVENT_PAYLOAD_BYTES` (16 KiB), medido no JSON compacto do `payload` sozinho. `getEventPayloadBudgetBytes(name)` devolve o orçamento exato já descontando a moldura do envelope para aquele nome de evento; `guest.sendEvent`/`room.send` medem o envelope serializado exatamente como o servidor mede e lançam `usage` (`payload_too_large_client`) síncrono, antes de tocar o socket, se passar do limite. A lib nunca fatia o payload — encolher é responsabilidade do app. ## O handshake carrega `{protocolVersion, contractFingerprint}` [#o-handshake-carrega-protocolversion-contractfingerprint] Antes de qualquer evento de app trafegar, host e guest trocam um handshake com a versão de protocolo (ver [versionamento-e-deploy.md](/pt/docs/event-room/latest/markdown/versionamento-e-deploy)) e o **fingerprint** do contrato — um mapa `nome do evento → digest da forma` (`EventContract['digestMap']`). O algoritmo: FNV-1a rodado duas vezes com seeds diferentes sobre a forma canonicalizada (chaves de objeto ordenadas, tokens primitivos já codificando opcionalidade e aninhamento) — determinístico entre engines, sem depender de uma API de crypto. Eventos declarados via Standard Schema ou `payload()` entram no mapa como `'opaque'` em vez de um digest real. A comparação (`compareFingerprints`) resolve cada nome em uma de cinco categorias — só meu contrato, só do peer, comparado por forma, comparado só por nome (quando um dos lados é opaco), ou **divergente** (mesmo nome, formas concretas diferentes — fatal, ver [maquina-de-estados.md](/pt/docs/event-room/latest/markdown/maquina-de-estados)). O resultado do handshake é **dado consultável**, não log — é assim que se descobre, em runtime, que um evento perdeu a rede de proteção do fingerprint (ver [seguranca.md](/pt/docs/event-room/latest/markdown/seguranca) para o custo da forma opaca). ## Guarda de envio: nome que o peer não conhece, fora do contrato, ou payload inválido [#guarda-de-envio-nome-que-o-peer-não-conhece-fora-do-contrato-ou-payload-inválido] `sendEvent`/`send` lançam, sempre síncrono, antes de tocar o socket: * `event_unknown_to_peer` (`usage`) — o fingerprint do peer não declara esse nome. Só existe **depois** do handshake fechar (ver [erros.md](/pt/docs/event-room/latest/markdown/erros#limite-o-que-usage-garante-sobre-detectabilidade-sem-rede) para o porquê disso não contradizer "usage é erro de programação"). * `event_not_in_contract` (`usage`, só do lado do host) — o nome não está no `contract` local. * `event_payload_invalid` (`usage`) — o payload não bate com a forma declarada para esse nome. * `contract_required` (`usage`, só do lado do host) — `room.send`/`room.on` chamados sem `contract` em `createHost`. Do lado do recebimento, um evento que o contrato local não conhece (ou cujo payload não bate) nunca é entregue a um handler — vira observação não-terminal `event_out_of_contract` (`infra`) via `onObservation`, nunca silenciada.