> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construa/conceitos/functions-e-commands.md).

# Functions e commands

Dois elementos formam todo código skail: a function, que orquestra, e o command, que executa as operações de I/O. O SDK de cada linguagem completa os dois com o tipo de retorno e as primitivas de espera. Esta página define o que cada um é, como eles se combinam e o que a linha do tempo de uma execução guarda.

<figure><img src="https://1898946878-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJmS5ILPG5x2x5j0WyiV%2Fuploads%2FDlNIKDYjBI1dMBCSgXwy%2Fap-modelo-programacao.svg?alt=media" alt="Linha do tempo da function que emite a fatura, com a cobrança aprovada. A function orquestra: chama os commands que carregam a fatura, cobram o cartão e emitem a nota fiscal, e espera um dia com um delay. Cada passo vira uma linha do histórico da execução, do passo 0 (início) ao passo 5 (fim)."><figcaption></figcaption></figure>

## Function: o orquestrador

A function descreve a sequência: chame A, depois B e C em paralelo, espere o evento D, decida. O corpo é reexecutado a partir do histórico da execução a cada retomada, então ele precisa ser determinístico: mesma entrada e mesmo histórico, mesma sequência de `await`s. Não faz I/O, não lê o relógio, não gera aleatoriedade. Pode chamar commands, outras functions (que viram orquestrações filhas) e as primitivas de espera do SDK.

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailFunction]
public async SkailTask EmitirFatura(Guid faturaId)
{
    var fatura   = await CarregarFatura(faturaId);                 // command
    var cobranca = await CobrarCartao(fatura);                     // command
    if (cobranca == ResultadoCobranca.Recusado) { await NotificarRecusa(faturaId); return; }

    await SkailTask.Delay(TimeSpan.FromDays(1));                   // primitiva: espera durável
    await EmitirNotaFiscal(faturaId);                              // command
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

interface Fatura {
  id: string;
  valor: number;
}
type ResultadoCobranca = "Aprovado" | "Recusado";

const UM_DIA_MS = 24 * 60 * 60 * 1000;

export class Faturas {
  @SkailFunction()
  async emitirFatura(faturaId: string): Promise<void> {
    const fatura = await this.carregarFatura(faturaId); // command
    const cobranca = await this.cobrarCartao(fatura); // command
    if (cobranca === "Recusado") {
      await this.notificarRecusa(faturaId);
      return;
    }

    await Skail.delay(UM_DIA_MS); // primitiva: espera durável
    await this.emitirNotaFiscal(faturaId); // command
  }

  @SkailCommand()
  async carregarFatura(faturaId: string): Promise<Fatura> {
    return { id: faturaId, valor: 100 };
  }

  @SkailCommand()
  async cobrarCartao(fatura: Fatura): Promise<ResultadoCobranca> {
    return fatura.valor > 0 ? "Aprovado" : "Recusado";
  }

  @SkailCommand()
  async notificarRecusa(faturaId: string): Promise<void> {
    /* ... */
  }

  @SkailCommand()
  async emitirNotaFiscal(faturaId: string): Promise<void> {
    /* ... */
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Command: a unidade de trabalho

O command faz a operação de I/O: grava no banco, chama a API, publica na fila, lê o relógio. Executa uma vez por resultado; argumentos e retorno são gravados no histórico; no replay o runtime devolve o gravado sem executar. Dentro dele vale o código comum da aplicação, inclusive pausas e timers da linguagem, concorrência comum, o cliente HTTP e o acesso ao banco. Em TypeScript, os módulos Node de I/O entram com `await import(...)` dentro do command, porque o build rejeita imports estáticos deles. Se falhar, é retentado até `retryCount` (5 por padrão).

{% hint style="warning" %}
Um command não chama outro command nem uma function: quem compõe os passos é sempre a function, e commands são chamados só de dentro de functions. Se dois passos precisam ser duráveis separadamente, a function chama os dois em sequência; para reaproveitar lógica dentro de um command, use métodos comuns, que não são function nem command. Ver a referência de [command](/referencia/sdk-csharp/skailcommand.md).
{% endhint %}

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailCommand(retryCount: 8)]
public async SkailTask<ResultadoCobranca> CobrarCartao(Fatura fatura)
{
    var resposta = await _gateway.CobrarAsync(fatura.Id, fatura.Valor, idempotencyKey: fatura.Id.ToString());
    return resposta.Aprovada ? ResultadoCobranca.Aprovado : ResultadoCobranca.Recusado;
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
import { SkailCommand } from "@skail/runtime";
import type { Provider } from "@skail/runtime";
import { GatewayFatura } from "./servicos.js";

interface Fatura {
  id: string;
  valor: number;
}
type ResultadoCobranca = "Aprovado" | "Recusado";

interface IGateway {
  cobrarAsync(id: string, valor: number, opts: { idempotencyKey: string }): Promise<{ aprovada: boolean }>;
}

export class Cobrancas {
  private readonly gateway: IGateway;
  constructor(gateway: IGateway) {
    this.gateway = gateway;
  }

  @SkailCommand({ retryCount: 8 })
  async cobrarCartao(fatura: Fatura): Promise<ResultadoCobranca> {
    const resposta = await this.gateway.cobrarAsync(fatura.id, fatura.valor, { idempotencyKey: fatura.id });
    return resposta.aprovada ? "Aprovado" : "Recusado";
  }
}

export const providers: Provider[] = [
  { token: GatewayFatura, useClass: GatewayFatura },
  { token: Cobrancas, useFactory: (s) => new Cobrancas(s.resolve(GatewayFatura)) },
];
```

{% endcode %}
{% endtab %}
{% endtabs %}

A regra que separa os dois: se o resultado pode diferir entre duas execuções com as mesmas entradas, é command. O restante pode ser function.

| Quero...                                             | Uso                                                           |
| ---------------------------------------------------- | ------------------------------------------------------------- |
| Encadear passos, decidir, repetir, paralelizar       | Function                                                      |
| Chamar HTTP, banco, fila, arquivo                    | Command                                                       |
| Ler a hora atual, gerar um id ou um número aleatório | Command que devolve o valor                                   |
| Esperar um tempo                                     | Um delay na function                                          |
| Esperar um webhook, uma pessoa, outro sistema        | Espera por evento na function                                 |
| Rodar passos em paralelo                             | Esperar todos ou esperar o primeiro que terminar, na function |
| Iniciar um sub-fluxo                                 | Chamar outra function                                         |
| Devolver um valor de um passo                        | Retorno com valor, em qualquer dos dois; se faz I/O, command  |

## Tipo de retorno e primitivas

Functions e commands são métodos `async`. O SDK de cada linguagem define o tipo de retorno desses métodos e as primitivas de espera: delay, espera por evento, esperar todos e esperar o primeiro que terminar.

{% tabs %}
{% tab title="C#" %}

* Retorno: `SkailTask` ou `SkailTask<T>`, nunca `Task`. É um awaitable com method builder próprio: é ele que permite ao runtime interceptar cada `await`, consultar o histórico e gravar o passo.
* Do lado de quem escreve, a única diferença de `Task` é o nome no retorno; `await`, `try/catch`, `using`, `try/finally` e LINQ funcionam normalmente.
* O que não funciona: `.Result`, `.Wait()`, `.GetAwaiter().GetResult()` (o analyzer bloqueia) e misturar `Task` e `SkailTask` no mesmo caminho durável.
* Primitivas, estáticas em `SkailTask`: `Delay(TimeSpan)`, `DelaySeconds(ulong)`, `DelayMinutes(ulong)`, `WhenAll(params SkailTask[])`, `WhenAny(params SkailTask[])`, `WaitForEvent(nome, instanceId)` e `WaitForEvent<T>(nome, instanceId)`.
  {% endtab %}

{% tab title="TypeScript" %}

* Retorno: `Promise<T>`, em métodos `async`.
* Primitivas, no pacote `@skail/runtime`: `Skail.delay(ms)`, `Skail.delaySeconds(n)` e `Skail.delayMinutes(n)`, que resolvem `undefined` e rejeitam com `RangeError` se o valor for negativo ou não finito; `Skail.waitForEvent<T>(nome, instanceId)`, com `instanceId` do tipo `string`; `Skail.whenAll(...)`, que devolve os resultados na ordem dos argumentos; e `Skail.whenAny(...)`, que devolve o valor de quem terminou primeiro (por isso um prazo vencido aparece como `undefined`).
* Só promises do runtime entram em `Skail.whenAll` e `Skail.whenAny`; `Promise.all`, `Promise.race` e `.then` não.
  {% endtab %}
  {% endtabs %}

Cada primitiva grava um passo e suporta replay. Quem dispara eventos de fora da execução usa a API HTTP.

## Regras de assinatura

Functions e commands são métodos públicos, de instância (nunca `static`) e `async`, com argumentos e retornos serializáveis.

{% tabs %}
{% tab title="C#" %}

* Retorno `SkailTask` ou `SkailTask<T>`.
* Argumentos e retornos serializáveis por `System.Text.Json`: primitivos, `Guid`, `DateTime`, records, DTOs, coleções deles. Um `SkailTask` não é serializável e não pode ser argumento de function.
* O analyzer `Skail.Platform.Analyzer` cobra as regras na compilação: SKAIL001 (assinatura), SKAIL002 (`async`), SKAIL003 (`.GetResult()`). Ver [Regras do analyzer](/referencia/sdk-csharp/regras-do-analyzer.md).
  {% endtab %}

{% tab title="TypeScript" %}

* Retorno `Promise<T>`.
* Argumentos e retornos são JSON; datas trafegam como string ISO-8601.
* O `skail build` cobra as regras no build: métodos `static`, `private` ou sem `async` são rejeitados ali.
  {% endtab %}
  {% endtabs %}

{% hint style="info" %}
Em C#, a classe que contém os métodos é registrada no contêiner de injeção de dependência automaticamente por `builder.UseSkail()`, como transient, e você injeta `ILogger<T>`, `DbContext`, `IHttpClientFactory` e seus serviços pelo construtor. Em TypeScript, o runtime não infere parâmetros de construtor: os serviços entram pelo array `providers` exportado no entry point (com `useFactory`) ou são instanciados na própria classe. Mantenha os serviços que fazem I/O em uso dentro de commands.
{% endhint %}

## A linha do tempo de uma execução

A function acima, com a cobrança aprovada, deixa este histórico da execução:

```
passo 0  início      EmitirFatura(faturaId)
passo 1  command     CarregarFatura(faturaId)        → Fatura {...}         gravado
passo 2  command     CobrarCartao(fatura)            → Aprovado              gravado
passo 3  delay       1 dia                            agendado → concluído
passo 4  command     EmitirNotaFiscal(faturaId)      → void                  gravado
passo 5  fim
```

Quando o delay do passo 3 vence, um dia depois, o runtime chama `EmitirFatura` do início. Em cada `await`, encontra o passo correspondente e devolve o resultado gravado: `CarregarFatura` e `CobrarCartao` não executam. O delay está concluído. `EmitirNotaFiscal` não tem passo: executa, grava o passo 4, e a function termina. É esse histórico que o Monitor desenha como linha do tempo, e é ele que o Time Travel Debug reproduz na sua IDE.

## Próximos passos

[Determinismo e replay](/construa/conceitos/replay-e-determinismo.md) detalha o que pode e o que não pode dentro da function. [Falhas, retries e idempotência](/construa/conceitos/retries-e-idempotencia.md) explica o que acontece quando um command falha. A referência de [function](/referencia/sdk-csharp/skailfunction.md), [command](/referencia/sdk-csharp/skailcommand.md) e [tipo de retorno e primitivas](/referencia/sdk-csharp/skailtask-e-skailtask-less-than-t-greater-than.md) tem assinaturas e parâmetros.
