Catálogo de statements

Cada elemento do array statements é um único statement. Este documento cataloga os campos, o comportamento e o resultado dos 25 tipos de statement. Toda posição de valor segue as regras de Expressões de valor (referência, literal, JsonLogic, mapa de locale). As exceções são duas: o pattern de Regex e a key de Cache (veja Regex e Cache).

Resumo dos statements

CategoriatypeResumo em uma linha
Escritas de recursoResourceCreateCria Content/Media (opcionalmente publica)
ResourceUpdateSubstituição total dos campos de Content/Media (todo campo/locale não fornecido é excluído)
ResourcePatchMesclagem parcial dos campos de Content/Media (apenas os campos/locales especificados; um null literal exclui)
ResourceDeleteExclui (apenas Draft/Archived; se Published, anule a publicação primeiro)
ResourcePublish / ResourceUnpublishPublica / anula a publicação
ResourceArchive / ResourceUnarchiveArquiva / desarquiva
Leituras de recursoResourceReadLê um único item por id
ResourceFindPrimeiro item único correspondente por filtro (null se não houver)
ResourceForEachPercorre internamente os recursos que atendem ao filtro e executa onEach em cada item
ResourceCountConta apenas a quantidade de itens que atendem ao filtro (não lê os itens)
ExternoHttpChamada HTTP externa ({ status, body })
EmailSendEnvia 1 e-mail por meio de um EmailAccount registrado
VariáveisSetVarDeclara/atualiza uma variável com escopo de script
Armazenamento em cacheCacheLê/grava/remove no cache de vida curta exclusivo daquele Script
Parse de valoresParseJsonFaz o parse de texto JSON em um valor (objeto, array, escalar) e o vincula
Assinatura e textoSignatureVerifica se o código de assinatura recebido é igual ao código gerado com a chave secreta (Boolean)
HashCalcula um digest sem chave (string)
RegexAplica uma expressão regular. Se houve correspondência (Boolean) ou os grupos de captura (array)
Controle de fluxoIfRamificação condicional
LoopItera (foreach / while / counted)
ParallelExecuta branches de forma concorrente
ReturnRetorna um resultado e encerra antecipadamente
TryTratamento de exceções (catch/finally)

Todo statement de Content que não indica o alvo por id precisa declarar o Content Type com que trabalha. Em ResourceFind·ResourceForEach·ResourceCount, quando resource é "Content", o contentType é obrigatório. Não existe consulta de Content que atravesse o Space inteiro. O ResourceCreate também declara o Content Type que vai criar. Media não delimita escopo, porque o Space inteiro é um único conjunto, e os statements que indicam o alvo por id (ResourceRead·ResourceUpdate·ResourcePatch·ResourceDelete e os statements de publicação e arquivamento) têm target, portanto não precisam de escopo.

Chamadas cíclicas são limitadas a 3. Se você ativar propagateEvents nos statements de escrita de recursos acima (ResourceCreate·ResourceUpdate·ResourcePublish etc.), cujo padrão é vir desativado, eles geram eventos de alteração, e esses eventos podem executar um Script novamente por meio de um Webhook. Uma cadeia assim (Script → evento → Webhook → Script → …) continua no máximo 3 vezes. A partir daí, ela é interrompida automaticamente para evitar loops infinitos.

Campos comuns

{ "type": "<StatementType>", "name": "<opcional, único dentro do script>", /* ...campos específicos do tipo... */ }
  • type: o discriminador. Um dos valores da tabela acima (obrigatório).
  • name: opcional. Se definido, o resultado é vinculado ao contexto em /<name>, de modo que statements posteriores podem referenciá-lo como { /<name>/... }. Omita-o se você não usar o resultado.
  • Regras de nome de vínculo: name é uma chave adicionada diretamente à raiz do contexto, por isso é validada ao salvar. Ela só pode usar letras ASCII (a-z, A-Z), dígitos, _ e - (precisa poder servir como chave de JSON Pointer, portanto qualquer outro caractere, ou um nome vazio, é rejeitado), não pode coincidir com as raízes reservadas (payload, rawPayload, headers, vars, error, now) e deve ser única dentro de um Script. Em caso de violação de formato, uso de palavra reservada ou duplicidade, o salvamento é rejeitado.

Formato da referência de entidade

Referências de entidade como contentType e target são unificadas em um único formato: { "sys": { "id": <expressão de valor> } }. Apenas sys.id é necessário; o tipo de destino é inferido a partir de resource (sys.type e sys.targetType são omitidos).

  • contentType.sys.id geralmente é um literal (ex.: "ct_post").
  • target.sys.id geralmente é uma expressão de valor { /ptr } (resolvida em tempo de execução; ex.: { /payload/sys/id }).

resource

Os statements da família de recursos especificam o tipo de destino com resource: "Content" | "ContentType" | "Media" | "ServiceUser".

Content Type é aceito somente pelo ResourceCount. Se você o escrever em outro statement, o salvamento é rejeitado. Criar ou alterar o próprio molde é da alçada da CMA, e não do Script.

ServiceUser (o membro inscrito no produto) é somente leitura. Apenas os três statements de leitura (ResourceRead·ResourceFind·ResourceForEach) aceitam esse valor; se você o escrever em um statement de escrita, o salvamento é rejeitado (consulte Erros). As regras são abordadas em Leitura do diretório de membros.

Escritas de recurso

Todo statement de escrita tem propagateEvents (padrão false). Defini-lo como true faz com que essa escrita gere um evento de alteração, de modo que ações subsequentes como Webhook são executadas. O padrão é não gerar (uma escrita silenciosa do sistema).

ResourceCreate

Cria um Content ou Media. Content e Media compartilham o modelo fields, e os valores são mapas de locale.

CampoAplica-se aDescrição
resourceComum"Content" ou "Media" (obrigatório)
contentTypeContentO Content Type a criar ({ sys: { id } }). Obrigatório para Content
fieldsComumMapa de campos { "<field>": { "<locale>": valor } }. Cada campo preenchido exige o bucket do locale padrão. As chaves de Content seguem a definição do Content Type; as chaves de Media são fixas (title, description, file)
localeComum(Conveniência) Se fornecido, cada valor em fields é automaticamente encapsulado como { <locale>: valor }
publishComumPublica após a escrita (exposto na CDA/ACDA). Padrão true
  • Media file: o valor de fields.file.{locale} é uma instrução de ingestão { "source": <expressão de valor>, "encoding": "url"|"base64" } (ambos obrigatórios). Em uma escrita que inclui um arquivo, o motor realiza a ingestão (se for url, baixa; se for base64, decodifica, e então faz o upload e o processa). Essa ingestão não tem tempo declarado, portanto sai do orçamento base de 30 segundos (veja Orçamento de tempo) e não é contada no limite de chamadas externas. Você também pode criar um Media sem arquivo (fileless). Se publish:true mas não houver arquivo ou o processamento estiver incompleto, a etapa de publicação gera erro; se publish:false, permanece Draft.
  • Resultado (vínculo de name): o recurso criado. { /<name>/sys/id }, { /<name>/fields/<field>/<locale> }.
// Content
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
  "fields": { "title": { "en-US": "{ /payload/fields/title }" } }, "publish": true, "name": "post" }
 
// Media. file é uma instrução de ingestão
{ "type": "ResourceCreate", "resource": "Media",
  "fields": {
    "title": { "en-US": "{ /payload/fields/prompt }" },
    "file":  { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } }
  }, "name": "img" }

ResourceUpdate

Substitui totalmente os campos do Content ou Media de destino (PUT). O que você passa em fields se torna o novo conjunto de campos, e todo campo e locale que não estiver presente aqui é removido. Para alterar apenas parte dele, use ResourcePatch.

CampoDescrição
resource"Content" ou "Media"
targetO destino ({ sys: { id } }, obrigatório). O id geralmente é { /ptr }
fieldsO conjunto completo de campos a escrever. Os valores são mapas de locale. Por ser uma substituição total, todo campo e locale que não estiver presente aqui é removido. O file de Media é uma instrução de ingestão (veja ResourceCreate acima). Os arquivos listados são sempre reingeridos, e os arquivos de locales não fornecidos são excluídos
locale(Conveniência) Encapsula fields automaticamente
version(Opcional) Uma expressão de valor (Int). Bloqueio otimista. Se fornecido, a atualização é executada somente se corresponder ao sys.version atual do destino; em caso de divergência, ela é abortada com um erro de conflito de versão (que pode ser capturado com Try). Se omitido, não há verificação (last-write-wins)
publishRepublica após a atualização. Padrão true

Se você usar o Update apenas para alterar os metadados de um Media, o file fica de fora e todos os arquivos são excluídos (por ser uma substituição total). Para alterações parciais, use obrigatoriamente ResourcePatch.

{ "type": "ResourceUpdate", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
  "fields": { "title": { "en-US": "Hello", "ko-KR": "안녕" }, "status": { "en-US": "published" } } }

ResourcePatch

Mescla parcialmente os campos do Content ou Media de destino (PATCH). Ela sobrescreve apenas os campos (e os locales dentro deles) que você passa em fields e mantém intactos os campos e locales que você não menciona. O formato dos valores, locale, version e publish são os mesmos de ResourceUpdate.

CampoDescrição
resource"Content" ou "Media"
targetO destino ({ sys: { id } }, obrigatório). O id geralmente é { /ptr }
fieldsOs campos a sobrescrever. Os valores são mapas de locale. Atualiza apenas os campos e buckets de locale especificados (o restante é mantido). Se um valor for um null literal, esse par (campo, locale) é excluído. O file de Media é uma instrução de ingestão (veja ResourceCreate acima)
locale(Conveniência) Encapsula fields automaticamente
version(Opcional) O mesmo que ResourceUpdate (bloqueio otimista)
publishRepublica após a atualização. Padrão true
  • Excluir um locale ou arquivo específico: forneça um null literal como valor. Por exemplo: "title": { "fr-FR": null } (exclui o título fr-FR), "file": { "en-US": null } (exclui o arquivo en-US). Uma expressão de valor que é avaliada como null em tempo de execução não é uma exclusão, mas um erro (apenas um null literal exclui).
  • Se você fornecer uma instrução de ingestão ao file de Media, ela substitui o arquivo daquele locale. Se você não fornecer um arquivo, ele é mantido.
// +1 apenas em viewCount(en-US). title, outros locales e todo o resto são mantidos como estão
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
  "fields": { "viewCount": { "en-US": { "$+": [ "{ /payload/fields/viewCount }", 1 ] } } } }

ResourceDelete

Exclui o destino. Apenas os status Draft e Archived podem ser excluídos. Se estiver Published ou Changed, a exclusão é rejeitada, portanto você deve fazer ResourceUnpublish primeiro. Para o Media, a exclusão é rejeitada enquanto o arquivo está sendo processado. Não faz auto-unpublish (igual à CMA/ACMA).

CampoDescrição
resource"Content" ou "Media"
targetO destino ({ sys: { id } }, obrigatório)
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }

ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive

Controlam de forma independente o estado de publicação e de arquivamento do destino. Os quatro compartilham os mesmos campos. A pré-condição de status de cada operação é a mesma da CMA/ACMA. O ResourcePublish não pode ser feito a partir de Archived e exige que o processamento do arquivo esteja concluído. O ResourceUnpublish só pode ser feito a partir de Published·Changed, o ResourceArchive só a partir de Draft, e o ResourceUnarchive só a partir de Archived.

CampoDescrição
resource"Content" ou "Media"
targetO destino ({ sys: { id } }, obrigatório)
version(Opcional) Uma expressão de valor (Int). Bloqueio otimista. Se fornecido, a operação é executada apenas se corresponder ao sys.version atual
{ "type": "ResourcePublish",   "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceUnpublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceArchive",   "resource": "Media",   "target": { "sys": { "id": "{ /m/sys/id }" } } }

Leituras de recurso

ResourceRead e ResourceFind leem o recurso e o vinculam como valor, e ResourceCount apenas conta. Nenhum dos três altera o estado (sem propagateEvents). O ResourceForEach também é uma leitura na própria consulta, mas, se você colocar em onEach um statement de escrita de recurso, essa escrita é executada em cada item e altera o estado.

Os quatro statements (ResourceRead·ResourceFind·ResourceForEach·ResourceCount) usam from (padrão Current) para definir de qual versão armazenada ler. Current é o rascunho mais recente visto no estúdio de conteúdo (o valor que a CMA/ACMA lê); Published é o snapshot de publicação (o valor no momento da última publicação, entregue pela CDA/ACDA). (ServiceUser não é publicado, portanto aceita apenas Current; veja Leitura do diretório de membros.)

Além disso, ResourceFind·ResourceForEach·ResourceCount ativam e desativam a Busca avançada (Advanced Search) por meio de advanced (padrão true). Se você não a declarar, ela vem ativada. É exclusiva de Content, portanto é ignorada nas leituras de Media e de ServiceUser. Quando ativada, em where é possível usar os operadores regex, near e within e a busca de texto completo (em um campo LongText com a busca de texto completo ativada, eq também encontra os itens que contêm o valor, por correspondência parcial ou aproximada), e order pode ordenar por fields.*. Quando desativada, esses três operadores são rejeitados, eq em texto é correspondência exata, e prefix e os operadores de comparação e de lista funcionam independentemente da busca avançada. Um item recém-criado ou editado leva um breve instante (cerca de 1 segundo) para ser refletido na busca avançada, portanto pode não ser capturado pela consulta de busca avançada imediatamente seguinte. Como ela vem ativada por padrão, esse atraso vale para toda consulta, a menos que você deixe advanced como false. Para ler de imediato um item recém-escrito, use ResourceRead por id (o armazenamento primário, sem esse atraso) ou consulte pelo sys.id que a escrita retornou.

O createdBy: ":self" de where significa "apenas o que o usuário que está chamando agora criou". Porém, ele não pode ser usado em um Script que permite chamada anônima (anonymousCallEnabled). Nesse caso, :self é resolvido para o autor, e não para o chamador, de modo que os recursos do autor seriam silenciosamente abertos; por isso o salvamento de uma definição assim é rejeitado (veja Chamada anônima).

Em where e order, os campos de conteúdo são escritos como fields.<field> (o nome sozinho não é reconhecido). Para fields.<field>, aplica-se automaticamente o locale padrão do Space, portanto você não anexa um locale diretamente. Os fields.status e fields.slug nos exemplos abaixo já são consultas no locale padrão. Somente para consultar apenas um locale específico (não padrão), você o indica explicitamente como fields.<field>.<locale> (por exemplo, fields.title.ko-KR). sys.* (como sys.createdAt) e createdBy (:self) são escritos tal como estão, sem fields.. As regras detalhadas estão em Expressões de valor: o locale em where e order.

Leitura do diretório de membros (ServiceUser)

ResourceRead·ResourceFind·ResourceForEach aceitam "ServiceUser" em resource para ler o diretório de membros daquele Space (o ResourceCount não o aceita; veja ResourceCount abaixo). Use-os em fluxos como conferir quem é o dono de um pedido, ou encontrar um membro pelo e-mail e passar o sys.id dele ao statement seguinte. As regras abaixo são comuns aos três statements.

  • Somente leitura. ResourceCreate·ResourceUpdate·ResourcePatch·ResourceDelete e os statements de publicação e arquivamento não aceitam "ServiceUser", e uma definição assim é rejeitada no momento do salvamento. Não é algo que se libere acrescentando permissões: no Script não existe caminho algum para alterar um membro, portanto a recusa não vem de um erro de permissão, mas de um statement escrito de forma inválida.
  • O autor precisa ter permissão sobre o diretório de membros para que a definição seja salva. A verificação não usa o mapa de permissões, como em Content e Media, mas confere se o settings do SpaceRole do autor tem SETTING_SERVICE_LOGIN (ou SETTING_ALL). Isso porque o diretório de membros é, em todos os outros caminhos também, um recurso governado pelas configurações do Space. Se não tiver, o salvamento é rejeitado (veja Modelo de segurança).
  • from aceita apenas Current. Como o membro não é um recurso publicado, passar Published faz a execução falhar.
  • contentType e advanced são ignorados. O diretório de membros não se divide por Content Type (é um único conjunto para todo o Space), e a busca avançada também é exclusiva de Content.
  • O sys.email de where aceita apenas operadores da família de igualdade exata (eq·ne·in·nin). Como o endereço do membro é armazenado criptografado, comparações de ordem e prefix não têm sentido. Se você passar outro operador, em vez de devolver silenciosamente 0 resultado, a execução falha.
  • O resultado é o próprio recurso ServiceUser. Referencie-o como { /<name>/sys/id } ou { /<name>/nickname }. A estrutura é abordada na referência de ServiceUser. Para enviar um e-mail ao membro encontrado, não extraia o endereço: passe o sys.id dele ao toServiceUser de EmailSend (o motor resolve o endereço imediatamente antes do envio, de modo que o endereço do membro não entra no espaço de variáveis do Script).
// encontra um membro pelo e-mail. null se não houver
{ "type": "ResourceFind", "resource": "ServiceUser",
  "where": { "sys.email": { "eq": "{ /payload/fields/email }" } }, "name": "member" }

ResourceRead

Lê um único item por id (get-by-id). O resultado vincula o recurso inteiro ao nome.

CampoDescrição
resource"Content"·"Media"·"ServiceUser"
targetO destino ({ sys: { id } }). O id é uma expressão de valor
from(Opcional) Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação). Para ServiceUser, apenas Current
  • Resultado: o que é vinculado é o próprio recurso. Se você deu um name a este statement, referencie-o diretamente como { /<name>/sys/id } e { /<name>/fields/<field>/<locale> } (com o "name": "order" do exemplo abaixo, { /order/sys/id }). Não é uma lista, então nenhum índice de array entra em jogo.
  • Se o destino não existir, gera erro. Você pode envolvê-lo em Try para tratar isso.
{ "type": "ResourceRead", "resource": "Content",
  "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }

ResourceFind

Lê o primeiro item único correspondente por filtro. Se não houver nenhum, é null. Use-o para encontrar um registro por uma chave de negócio única (slug, email, sku).

CampoDescrição
resource"Content"·"Media"·"ServiceUser"
contentTypeO Content Type no qual pesquisar ({ sys: { id } }). Obrigatório quando é Content. Ignorado em Media e ServiceUser
whereO filtro ({ "<field>": { "<op>": <valor> } }). Os operadores são os da lista de operadores (regex/near/within exigem advanced). createdBy: ":self" suportado. Para o sys.email de ServiceUser, apenas eq·ne·in·nin (Leitura do diretório de membros)
orderA ordenação que determina o "primeiro" quando há várias correspondências (ex.: "-sys.createdAt")
from(Opcional) Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação). Para ServiceUser, apenas Current
advanced(Opcional) Executar via busca avançada (Advanced Search). Somente Content (Media e ServiceUser são ignorados). Padrão true. Veja a nota Leituras de recurso acima
  • Resultado: vincula o primeiro recurso correspondente ao name deste statement. Referencie-o diretamente como { /<name>/fields/<field>/<locale> }. Como é null quando não há nenhum, ramifique conforme a existência com { "==": [ "{ /<name> }", null ] } (o padrão típico find-then-upsert).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
  "where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }

ResourceForEach

Percorre internamente os recursos que atendem ao filtro e executa onEach em cada item. É um statement para realizar uma operação em cada item, sem construir uma coleção para usar como valor. Use-o em trabalhos repetitivos como publicar rascunhos em lote, editar em lote os Content que atendem a uma condição, ou enviar/sincronizar cada item para fora. Para ler um único registro, use ResourceRead (id) ou ResourceFind (filtro).

CampoDescrição
resource"Content"·"Media"·"ServiceUser" (obrigatório)
contentTypeO Content Type do escopo do percurso ({ sys: { id } }). Obrigatório quando é Content. Ignorado em Media e ServiceUser
whereO filtro ({ "<field>": { "<op>": <valor> } }). O significado é o mesmo do where de ResourceFind (a restrição do sys.email de ServiceUser também é idêntica). Os operadores são os da lista de operadores (regex/near/within exigem advanced). createdBy: ":self" suportado
orderA ordenação (ex.: "sys.createdAt,sys.id"). Se ausente, a ordem padrão da plataforma
fromCurrent (padrão, o rascunho mais recente) ou Published (o snapshot de publicação). Para ServiceUser, apenas Current
advancedPercorre via busca avançada (Advanced Search). Somente Content (Media e ServiceUser ignorados). Padrão true. Veja a nota Leituras de recurso acima
limit(Opcional, 1 ou mais) O limite máximo do total de itens processados (não é o tamanho da página). Se ausente, percorre até o limite máximo da plataforma (10.000 itens)
name(Opcional) O nome ao qual vincular o item atual. É revinculado a cada iteração e referenciado dentro de onEach como { /<name> } (mesma vida útil do name de Loop; permanece vinculado ao último item mesmo após o fim do percurso). Omita se você não referenciar o item
onEachO array de statements filhos a executar em cada item (obrigatório)
  • Não vincula uma coleção (foreach, não map). Não há { items, next } nem cursor. Você não recebe o resultado do percurso como um valor; em vez disso, executa onEach em cada item. Se precisar de uma lista, colete você mesmo com SetVar. Se precisar apenas da contagem, use o ResourceCount.
  • Mesmo sem limit, não é um percurso infinito. Sem ele, percorre até o limite máximo da plataforma (10.000 itens) e, se atingir esse limite ainda havendo correspondências, falha (para não reportar sucesso deixando itens intocados). Por outro lado, atingir o limit declarado é uma parada intencional, portanto um encerramento normal. Um limit que ultrapasse o limite máximo é recusado ao salvar.
  • Não há cursor. Se concluir tudo, é sucesso; se for interrompido no meio (estouro de relógio de parede ou de cota, uma falha não tratada em onEach), é falha, e o erro aponta em qual item e por que falhou. A retomada é expressa pelo autor com os próprios dados (deixando where como "não processado" e marcando a conclusão ao fim de onEach, uma reexecução continua a partir do que restou).
  • No orçamento de tempo, ele entra como multiplicação. O tempo que este statement declara é o tempo declarado por onEach multiplicado pela quantidade de itens processados (limit; 10.000 se não houver) (veja Orçamento de tempo). Por ser um statement composto que possui filhos, ele próprio não conta no orçamento de chamadas externas leaf; são os statements de chamada externa dentro de onEach que entram no orçamento.
  • Em onEach, como em qualquer outro statement, você pode incluir chamadas externas (Http·EmailSend) ou ingestão de arquivo de Media (igual ao body de Loop). A razão de existir deste statement é processar o resultado de uma consulta de recurso uma vez por item.
// encontra todos os posts em rascunho e publica cada item
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
  "where": { "fields.status": { "eq": "draft" } }, "order": "sys.createdAt,sys.id",
  "from": "Current", "advanced": false, "name": "post",
  "onEach": [
    { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /post/sys/id }" } } }
  ] }

ResourceCount

Conta apenas a quantidade de itens que atendem ao filtro. Como não traz os itens, use-o quando você precisa do número, e não da lista. É o lugar para conferir o estoque restante, julgar se já existe um registro com o mesmo valor, ou verificar se um limite foi ultrapassado.

CampoDescrição
resource"Content" ou "ContentType" (obrigatório). Media e ServiceUser não podem ser contados, e escrevê-los assim faz o salvamento ser rejeitado
contentTypeO Content Type do escopo da contagem ({ sys: { id } }). Obrigatório quando é Content. Ao contar Content Type, é ignorado (o Space inteiro é um único conjunto)
whereO filtro. O significado é o mesmo do where de ResourceFind. Conta todos os itens correspondentes
from(Opcional) Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação)
advanced(Opcional) Executar via busca avançada (Advanced Search). Somente Content (ignorado ao contar Content Type). Padrão true. Veja a nota Leituras de recurso acima
name(Opcional) O nome ao qual vincular a contagem
  • Resultado: vincula a quantidade de correspondências ao name deste statement. Referencie-a como { /<name> } para usá-la em comparações e ramificações.
  • Ele não devolve os itens. Se você precisa dos itens, use ResourceFind (o primeiro item único correspondente) ou ResourceForEach (executa em cada item).
  • Não percorra com ResourceForEach só para obter uma contagem. O percurso reserva o orçamento de tempo multiplicado pela quantidade de itens (veja Orçamento de tempo) e falha se atingir o limite máximo da plataforma ainda havendo correspondências. Se você só precisa contar, este statement resolve de uma vez.
  • Não existem order nem limit. Para contar não é necessária uma ordem, e tudo o que corresponde é contado.
// conta quantos comentários este post tem
{ "type": "ResourceCount", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
  "where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "name": "commentCount" }

Externo

Http

Chama um endpoint HTTP externo. Por ser uma chamada externa, ele é contado no limite de chamadas externas por plano e, no orçamento de tempo, entra como timeoutMs (30 segundos se ausente) × (1 + retry).

CampoDescrição
method"GET", "POST", "PUT", "PATCH", "DELETE"
urlA URL de destino (uma expressão de valor; { /ptr } pode ser interpolado)
headers[{ "key", "value", "secret"? }]. value é uma expressão de valor. Um cabeçalho secret:true é tratado como exclusivo da CMA (administrador): não é exposto aos usuários finais e é descriptografado apenas imediatamente antes de a requisição ser enviada. Se você colocar Content-Type aqui, o body é serializado nesse formato (abaixo)
bodyO corpo da requisição (uma expressão de valor ou JSON). O formato em que ele é enviado é definido pelo cabeçalho Content-Type
timeoutMsO tempo limite desta chamada (ms)
retryO número de novas tentativas quando o status da resposta é 400 ou superior. Padrão 0; limite máximo 2
ignoreStatusCodeSe esta chamada deve ser tratada como uma falha quando o status final (após as novas tentativas) é 400 ou superior. Quando false (padrão), ela é tratada como uma falha e se torna um alvo de Try/catch. Quando true, ela não é tratada como uma falha e { status, body } é vinculado como está (o chamador ramifica com base no próprio status)
responseTypeComo o corpo da resposta é recebido. "Json" (padrão) faz o parse dele em objeto ou array; "Text" recebe como string
  • Resultado: { status, body }. Se você deu um name a este statement, { /<name>/status }, { /<name>/body/... }. A forma de body é definida por responseType.
  • responseType se aplica apenas a uma resposta bem-sucedida. O corpo de uma resposta com status 400 ou superior é vinculado para diagnóstico independentemente do valor declarado (o valor parseado quando é JSON, uma string caso contrário).
  • Se for "Json" e o corpo não for JSON, esta chamada falha (alvo de Try/catch). Para APIs que não devolvem JSON, receba o corpo como "Text" e faça o parse com ParseJson quando precisar tratá-lo como valor.
  • "Text" é decodificado com o charset do Content-Type da resposta e tratado como UTF-8 quando não há charset. Se o corpo estiver vazio, body é null nos dois casos.
  • Limite de tamanho da resposta: O corpo da resposta tem no máximo 10MiB. Se exceder isso, esta chamada falha com uma exceção e pode ser tratada como qualquer outra falha em tempo de execução com Try/catch (é uma falha baseada em tamanho, portanto não é suprimida por ignoreStatusCode).
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
  "headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
  "body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "retry": 1,
  "responseType": "Json", "name": "resp" }

Em que formato o body é enviado

O Content-Type que você coloca em headers define o formato de serialização do body. A comparação ignora maiúsculas e minúsculas e parâmetros como ;charset=…, olhando apenas a parte inicial. Quando o cabeçalho está ausente ou o valor está vazio, a requisição é enviada como application/json. Esse cabeçalho só é anexado quando existe um body, portanto, se não houver body, o cabeçalho que você escreveu é enviado como está. Se você colocar a mesma chave várias vezes, apenas o primeiro valor é usado e elas são unificadas em uma só.

Um body que não cabe no formato declarado é enviado com uma correção para um formato em que ele caiba. O cabeçalho nunca diz algo diferente do corpo que é realmente enviado.

Estas são as combinações que são enviadas exatamente como declaradas.

Content-Type declaradoForma do bodybody enviado
application/jsonQualquer umaJSON
application/x-www-form-urlencodedObjeto ou arrayorder[id]=A-2481&order[amount]=34000
text/plainEscalarO valor como está
Outros (text/xml etc.)Qualquer umaJSON

Estas são as combinações que são corrigidas porque não cabem no formato declarado.

Content-Type declaradoForma do bodyContent-Type realmente enviadobody enviado
application/x-www-form-urlencodedEscalartext/plain;charset=UTF-8O valor como está
text/plainObjeto ou arrayapplication/jsonJSON

Estas duas linhas esclarecem como a requisição é enviada quando o par está desalinhado, e não são uma forma de obter o formato pretendido. Quando o body é montado por uma expressão de valor, ele pode resultar em um escalar conforme o payload do momento da execução, e nesse caso a correção acontece sem erro. Se o lado que recebe questionar o formato, ajuste a forma do body ou o Content-Type de acordo com a sua intenção.

O form-urlencoded expande objetos em chaves com colchetes e arrays em índices.

bodyChaves e valores expandidos
{ "order": { "id": "A-2481", "amount": 34000 } }order[id]=A-2481&order[amount]=34000
{ "tags": ["outerwear", "winter"] }tags[0]=outerwear&tags[1]=winter
{ "items": [{ "sku": "TUMBLER-500" }] }items[0][sku]=TUMBLER-500
{ "memo": null }memo=

As chaves e os valores são enviados com codificação percentual em UTF-8. A tabela acima está na forma decodificada para mostrar a estrutura das chaves. Mesmo que um valor contenha & ou +, ele é transmitido como está, sem ser confundido com um separador de pares ou um espaço.

A notação que expande o aninhamento em chaves com colchetes é uma convenção amplamente usada, e não uma especificação do próprio formato. Verifique se o lado que recebe restaura order[id] como um objeto aninhado e, se não restaurar, monte o body com chaves planas.

{ "type": "Http", "method": "POST", "url": "https://api.example.com/oauth/token",
  "headers": [ { "key": "Content-Type", "value": "application/x-www-form-urlencoded" } ],
  "body": { "grant_type": "client_credentials", "client_id": "{ /vars/clientId }" },
  "name": "token" }

EmailSend

Envia 1 e-mail por meio de um EmailAccount registrado. Os campos aceitos são apenas os que mapeiam diretamente para SMTP/MIME. Não há id de template, envio agendado nem extensões por provedor (se você precisar de recursos assim, chame diretamente a API do serviço de e-mail correspondente com Http). O remetente (o endereço que envia) não é definido aqui, mas vem do EmailAccount apontado por account.

CampoDescrição
accountReferência ao EmailAccount por onde enviar ({ sys: { id } }, obrigatório). Geralmente um id literal. Se fornecido como expressão de valor, é resolvido no momento do envio, portanto não pode ser verificado ao salvar
toEndereço do destinatário (expressão de valor). Use exatamente um entre to e toServiceUser
toServiceUserEspecifica o destinatário como referência a um ServiceUser ({ sys: { id } }; esse sys.id pode ser uma expressão de valor). O motor resolve o endereço imediatamente antes do envio, de modo que o endereço do membro não entra no espaço de variáveis do Script
ccArray de endereços em cópia (expressão de valor)
bccArray de endereços em cópia oculta (expressão de valor)
subjectO assunto (expressão de valor, obrigatório)
bodyO corpo (expressão de valor, obrigatório). Sempre enviado como text/html, portanto escreva markup, não texto puro (quebras de linha viram espaço, < é interpretado como tag). O resultado das expressões de valor interpoladas passa por escape de HTML
replyTo(Opcional) O cabeçalho Reply-To (expressão de valor). Pode ser diferente do remetente (ex.: enviar como no-reply, mas com as respostas indo para um endereço de suporte)
timeoutMs(Opcional, 1 ou mais) O tempo limite deste envio (ms). Se ausente, o padrão da plataforma; um valor que ultrapasse o limite máximo é recusado ao salvar
  • O total de destinatários é no máximo 50. Conta-se somando to (1), cc e bcc (o envelope SMTP não distingue cc/bcc e todos saem como destinatários, por isso a contagem é somada). Se ultrapassar, é recusado ao salvar e na execução. Para enviar a muitas pessoas, use ResourceForEach + EmailSend para enviar 1 e-mail por item.
  • Não vincula resultado. O sucesso significa apenas que "o provedor aceitou o e-mail", portanto não há valor a devolver e ele não aceita name. Também não faz novas tentativas (o e-mail não é idempotente; tentar de novo após uma falha ambígua causaria envio duplicado, por isso ele não segue o retry de Http). A falha é lançada (throw) e tratada pelo catch de um Try.
  • É uma chamada externa. Ela é contada no limite de chamadas externas por plano e, no orçamento de tempo, entra como timeoutMs (10 segundos se ausente) uma única vez (como ela não tenta de novo, não se multiplica pela quantidade de tentativas, como em Http). Pode ser usada dentro de onEach de um ResourceForEach (a forma padrão de envio em lote).
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
  "to": "{ /order/fields/email/en-US }",
  "subject": "Pedido recebido com sucesso (número do pedido { /order/sys/id })",
  "body": "<p>Seu pedido foi recebido. Avisaremos novamente quando a entrega começar.</p>",
  "replyTo": "support@my-shop.example" }

Variáveis

SetVar

Declara ou atualiza uma variável mutável com escopo de script. Referencie-a como { /vars/<var> } (o JsonLogic não tem declaração de variável, por isso ela é fornecida como um statement).

CampoDescrição
varO nome da variável. Referenciada como { /vars/<var> }
valueUma expressão de valor. Pode referenciar a si mesma para acumular
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "$+": [ "{ /vars/total }", "{ /row/qty }" ] } }   // acumula
{ "type": "SetVar", "var": "ids",   "value": { "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } }  // coleta em array

Armazenamento em cache

Cache

Lê e grava no cache de vida curta exclusivo daquele Script. É a posição para segurar por alguns segundos um valor que não compensa buscar de novo a cada execução, como o resultado de uma chamada externa, e reutilizá-lo na chamada seguinte. Não é uma chamada externa, portanto não entra no número de chamadas externas por definição e também não declara tempo algum no orçamento de tempo.

CampoDescrição
actionUm entre "Set" (gravar), "Get" (ler) e "Delete" (remover) (obrigatório)
keyA chave de cache (Cache Key) (obrigatório). Não é uma expressão de valor, mas um literal (veja abaixo). No máximo 128 caracteres; acima disso, o salvamento é rejeitado
valueO valor a guardar (exclusivo de Set)
ttlO tempo em que o cache permanece vivo (exclusivo de Set, em segundos). Entre 1 e 30; se omitido, 5
defaultValueO valor que Get vincula quando não há dados em cache (exclusivo de Get). Se omitido, null
nameO nome que guarda o resultado. Em Get é obrigatório (se o valor lido não tem para onde ir, não há razão para lê-lo). Set vincula o valor guardado e Delete vincula se a remoção foi realizada; nos dois é opcional
  • Escreva apenas os campos que correspondem à operação. Se você escrever ttl em um Get ou defaultValue em um Set, o salvamento é rejeitado.
  • Não existir e ter expirado não se distinguem. Nos dois casos, o que é vinculado é defaultValue. O mesmo vale quando o que está guardado é null.
  • O escopo de armazenamento é aquele Script, e só ele. Outro Script do mesmo Space não enxerga os dados dele, ainda que use a mesma chave de cache. Se aquele Script for editado ou excluído, todos os dados dele desaparecem.
  • key é um literal. Deixar que os dados sejam escolhidos por uma chave de cache vinda da requisição faria o chamador decidir o que é lido, e um Script que guarda um valor por membro passaria o valor de um membro a outro. Por isso, um { /pointer } dentro de key não é convertido em valor nem é usado literalmente: o próprio salvamento é rejeitado.
  • Não pode ficar dentro de uma iteração. Se houver um Cache dentro do bloco de um Loop ou de um ResourceForEach, o salvamento é rejeitado. É porque o limite máximo de quantidade abaixo não impõe restrição alguma dentro de uma iteração. Como se grava um dado por volta, o número de statements escrito na definição e o número de chaves de cache realmente usadas divergem.
  • Você pode colocar até 5 por definição (incluindo aninhados, somados independentemente da operação). Acima disso, o salvamento é rejeitado.
  • O valor guardado vai até 10.240 bytes (10KiB). Acima disso, esse statement falha (status 422). É igual a qualquer outra falha em tempo de execução, portanto pode ser tratado localmente com Try/catch.
// reutiliza a taxa de câmbio por 30 segundos.
{ "type": "Cache", "action": "Get", "name": "cached", "key": "rates" }
 
// se houver um valor guardado, devolve-o tal como está, sem chamada externa
{ "type": "If", "condition": { "!!": [ "{ /cached }" ] },
  "then": [ { "type": "Return", "value": "{ /cached }" } ] }
 
{ "type": "Http", "name": "fetched", "method": "GET", "url": "https://api.example.com/rates" }
{ "type": "Cache", "action": "Set", "key": "rates", "value": "{ /fetched/body }", "ttl": 30 }
{ "type": "Return", "value": "{ /fetched/body }" }
 
// descarta o valor guardado antes de ele expirar
{ "type": "Cache", "action": "Delete", "key": "rates" }

Parse de valores

ParseJson

Faz o parse de um texto JSON no valor que ele representa e o vincula a um nome. Use para o corpo recebido de Http com responseType: "Text", para uma string JSON que chegou no payload, ou para JSON guardado como string em um campo. Não é uma chamada externa, portanto não é contado no limite de chamadas externas e também não declara tempo algum no orçamento de tempo.

CampoDescrição
nameO nome que guarda o valor parseado (obrigatório). Nos outros statements é opcional, mas aqui é obrigatório. O statement não faz nada além de vincular seu resultado, então um sem nome não tem efeito algum
valueO texto JSON a ser parseado (expressão de valor, obrigatório). Aponte para um valor de uma etapa anterior, como em { /resp/body }, ou escreva o texto JSON literalmente (uma { dentro do literal não é lida como template { ponteiro })
  • Resultado: o próprio valor parseado. Um objeto continua objeto, um array continua array, e um valor único como 42 ou "a" também é parseado. Depois você aponta para o interior com { /<name>/... }.
  • Um valor já parseado é vinculado sem alteração. Quando value resolve para algo que não é string, não há texto a parsear, então esse valor é vinculado como está.
  • Um { /pointer } dentro do texto parseado não é resolvido de novo. Mesmo que uma string recebida de fora contenha uma expressão como { /payload/... }, ela não é substituída por um valor e permanece texto.
  • null distingue dois casos. Se o texto a parsear for apenas a palavra null, isso é normal e o resultado também é null. Já se o lugar apontado por value estiver vazio, sem valor nenhum, não há nada a parsear e o statement falha.
  • Falha: quando value resolve sem valor ou apenas com espaços, e quando o texto não é JSON. Trate com Try/catch como qualquer outra falha em tempo de execução; a mensagem de erro leva o texto que se tentou parsear.
  • Conta como um statement no número de statements por definição, mas não tem relação com o limite de chamadas externas nem com o limite de SetVar.
// 1) Uma API que não devolve JSON: receber como Text e fazer o parse
{ "type": "Http", "method": "GET", "url": "https://api.partner.example/v1/quote",
  "responseType": "Text", "name": "resp" },
{ "type": "ParseJson", "name": "quote", "value": "{ /resp/body }" },
 
// 2) Fazer o parse de uma string JSON que chegou no payload
{ "type": "ParseJson", "name": "spec", "value": "{ /payload/fields/specJson }" }

Verificação de assinatura e tratamento de texto

São os statements que conferem a assinatura que uma processadora de pagamentos enviou por webhook e que desempacotam a string na qual essa assinatura vem embalada. Os três são cálculo, não chamada externa, portanto não são contados no limite de chamadas externas e também não declaram tempo algum no orçamento de tempo; e, como não têm posição de dados, não têm relação com a regra do prefixo $. Um exemplo completo combinando os três está na verificação de assinatura de webhook do cookbook.

Os três statements têm um limite máximo para o comprimento do valor resolvido. Não é o comprimento da expressão, mas o comprimento do valor que ela aponta (os dezesseis caracteres de { /rawPayload } apontam para dezenas de KB) e, se exceder, a execução falha e pode ser tratada com Try. Os números estão reunidos em Limites de comprimento de valor.

Signature

Confere se o código de assinatura recebido é igual ao código gerado com secret e vincula essa resposta como um valor Boolean. A assinatura que uma processadora de pagamentos (PG·MoR) envia por webhook é verificada com este statement.

CampoDescrição
nameO nome que guarda o resultado da verificação (obrigatório). { /<name> } é true ou false. Verificar e não usar o resultado equivale a não verificar, por isso não pode ser omitido
algorithmO hash com que o código é gerado (obrigatório). SHA1·SHA256·SHA384·SHA512
secretA chave secreta compartilhada com a outra parte (expressão de valor, obrigatório)
secretEncodingEm que notação você escreveu secret. Utf8 (padrão, chave em texto)·Hex·Base64. Deixar em texto uma chave emitida em hex ou base64 resulta em outra chave, de modo que um código plausível é gerado, mas nunca coincide
valueA mensagem sobre a qual o código é calculado (expressão de valor, obrigatório). Precisa ser literalmente igual aos bytes que a outra parte assinou, portanto normalmente é { /rawPayload }, ou esse valor com o timestamp que o provedor enviou junto no cabeçalho prefixado a ele
expectedO código que o chamador enviou (expressão de valor, obrigatório). Ex.: { /headers/x-signature }
  • Resultado: um Boolean. Depois, use { /<name> } tal como está na condição de um If.
  • Escreva value com /rawPayload, não com o /payload parseado. Transformar de novo em string o payload parseado normaliza espaços, notação numérica e escapes, e não devolve os bytes que a outra parte assinou (Raízes de contexto).
  • Não existe campo para indicar a notação de saída. algorithm fixa o comprimento em bytes do código, e hex e base64 de um mesmo comprimento não coincidem em comprimento de string, portanto o motor recupera os bytes sem que a outra parte informe qual notação usou. Pela mesma razão, ele também não distingue maiúsculas de minúsculas no hex, nem base64 de base64url (inclusive com ou sem padding).
  • A diferença entre falha e false está em quem fornece o valor.
    • Se expected não vier ou o código não coincidir, o resultado é apenas false, não é falha. Informar separadamente a ausência do cabeçalho e a divergência do código ensinaria a quem enviou qual dos dois estava errado.
    • Se value estiver vazio, o cálculo é feito com uma mensagem vazia. Um corpo vazio também é objeto de assinatura.
    • Se secret não vier ou não estiver na notação declarada por secretEncoding, é falha. Dos três casos, esta é a única entrada do próprio autor. A mensagem de falha não leva secret nem value.
  • O limite máximo de value é 65.536 caracteres (com base no valor resolvido). É um valor ajustado ao tamanho dos corpos de webhook que os provedores realmente enviam.
  • A comparação decide a igualdade dos valores em constant-time. Quantos bytes iniciais coincidiram não vaza pelo tempo de resposta.
  • secret não é armazenado de forma criptografada. Diferente do secret: true de um cabeçalho Http (armazenado criptografado e descriptografado imediatamente antes do envio), ele permanece exatamente como escrito na definição, portanto seu valor fica visível para os papéis que podem ler aquele Script. Um membro (ServiceUser) não consegue ler a definição de um Script (a autoria e a consulta são exclusivas da CMA).
// provedor que assina o corpo inteiro
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "whsec_9f2c1b7ae4", "value": "{ /rawPayload }",
  "expected": "{ /headers/x-webhook-signature }" }
 
// provedor que emite a chave em base64
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "aGVsbG8td2VlZ2xvbw==", "secretEncoding": "Base64",
  "value": "{ /rawPayload }", "expected": "{ /headers/webhook-signature }" }

Hash

Faz o digest de value e o vincula como uma string na notação definida por encoding. Use-o para reproduzir esquemas de assinatura que, em vez de HMAC, "concatenam alguns campos com a chave secreta e calculam SHA256".

CampoDescrição
nameO nome que guarda o digest (obrigatório)
algorithmMD5·SHA1·SHA256·SHA384·SHA512 (obrigatório). MD5 existe para reproduzir esquemas antigos que o exigem, e não é um valor a escolher para uma assinatura nova
valueA mensagem a digerir (expressão de valor, obrigatório)
encodingA notação do resultado. Hex (padrão)·HexUpper·Base64·Base64Url
  • Não existe campo secret. Como cada esquema coloca a chave no início, no fim ou no meio, escrever a chave diretamente dentro de value é o que permite expressar todas as posições.
  • Resultado: uma string. Para comparar com o código que a outra parte enviou, escreva { "==": [ "{ /<name> }", "{ /headers/... }" ] }. Essa comparação, diferente da comparação constant-time de Signature, é uma comparação de igualdade comum.
  • Se value resolver sem valor algum ou apenas com espaços, é falha (por ser uma expressão do próprio autor).
  • O limite máximo de value é 128 caracteres. Por ser a posição que guarda uns poucos campos concatenados, é muito mais estreito que o de Signature. Se você precisar calcular sobre o corpo inteiro de um webhook, use Signature.
// SHA256(número do pedido + valor + merchantKey) em hex maiúsculo
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
  "value": "{ /payload/orderId }{ /payload/amount }9f2c1b7ae4" }

Regex

Aplica pattern a value e vincula o que mode pediu. Como as expressões de valor não têm nenhum meio de cortar strings (há apenas cat, que concatena, e in, que verifica inclusão), use este statement para desempacotar algo que chega com vários valores embalados em um só cabeçalho, como t=…,v1=….

CampoDescrição
nameO nome que guarda o resultado (obrigatório). Em Capture, os elementos são apontados como { /<name>/1 }
mode"Match" vincula como Boolean se houve correspondência; "Capture" vincula a primeira correspondência como array (obrigatório)
patternA expressão regular (obrigatório). Não é uma expressão de valor, mas um literal (veja abaixo). As flags são escritas dentro do padrão, como (?i). No máximo 128 caracteres; acima disso, o salvamento é rejeitado
valueO texto ao qual aplicar o padrão (expressão de valor, obrigatório). Se o valor resolvido passar de 10.240 caracteres (10KiB), a execução falha
  • Resultado: Match é Boolean; Capture é um array ou null. No array, o índice 0 é a correspondência inteira e, de 1 em diante, vêm os grupos de captura; um grupo que não participou é null (não uma string vazia, que significaria ter havido correspondência). Se o padrão não aparecer, Capture é null, e não um array vazio.
  • Os dois modos perguntam "o padrão aparece em algum lugar?". Se o texto inteiro precisar ser igual ao padrão, ancore-o com ^…$. A pergunta foi mantida igual nos dois para que os dois statements, um verificando com Match e outro extraindo com Capture, não devolvam respostas diferentes.
  • pattern é um dos dois campos deste motor que não são expressões de valor (o outro é a key de Cache). Executar tal e qual um padrão vindo da requisição deixaria o chamador escolher a expressão a ser executada, e o backtracking das expressões regulares transformaria isso em um meio de negação de serviço. Por isso, um { /pointer } dentro do padrão também não é convertido em valor: passa a fazer parte do padrão, literalmente.
  • O padrão é compilado uma única vez, para toda a definição, quando a execução começa. Mesmo dentro de um Loop ou de um ResourceForEach, ele não é recompilado a cada iteração, e um padrão inutilizável falha antes que o primeiro statement faça qualquer coisa (tratável com Try).
// desempacota "t=1492774577,v1=<64 caracteres hex>" em { /sig/1 } = timestamp, { /sig/2 } = código
{ "type": "Regex", "name": "sig", "mode": "Capture",
  "pattern": "^t=(\\d+),v1=([0-9a-f]{64})$", "value": "{ /headers/x-provider-signature }" }
 
// verifica apenas o formato
{ "type": "Regex", "name": "isOrderId", "mode": "Match",
  "pattern": "^ORD-\\d{8}-\\d{4}$", "value": "{ /payload/orderId }" }

Controle de fluxo

If

Uma ramificação condicional. condition é JsonLogic, e verdadeiro/falso segue as regras de Avaliação de verdadeiro e falso.

CampoDescrição
conditionJsonLogic (avaliado como booleano)
thenO array de Statement a executar quando verdadeiro
else(Opcional) O array de Statement a executar quando falso
{ "type": "If",
  "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
  "then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" } } ],
  "else": [ /* ... */ ] }

Loop

Iteração. Escolha um modo: over (foreach), while (condição) ou for (contado). Em qualquer modo, o motor impõe um limite máximo de iterações (para evitar loops infinitos). Esse limite é declarado em maxIterations e, se você não o escrever, aplica-se o limite máximo da plataforma. Dentro de body você também pode incluir chamadas externas (Http·EmailSend) e ingestão de arquivo de Media, e os statements de chamada externa são de fato chamados a cada iteração na execução. O limite de número máximo de chamadas externas por definição continua valendo.

No orçamento de tempo, ele entra como multiplicação. O tempo que este statement declara é o tempo declarado por body multiplicado por maxIterations (10.000 se não houver) (veja Orçamento de tempo). Se não houver chamada externa em body, o tempo declarado é 0, portanto o orçamento base de 30 segundos é o limite efetivo.

CampoDescrição
overforeach: uma expressão de valor que resolve para um array
whilecondição: JsonLogic (repete enquanto verdadeiro)
forcontado: { "from", "to", "step"? }. De from até to inclusive; step tem padrão 1
maxIterationsO número máximo de iterações (opcional). Se você não o escrever, aplica-se o limite máximo da plataforma, 10.000, e um valor maior que esse é recusado ao salvar
name(Opcional) O nome ao qual vincular o item atual (foreach) ou o índice (while·for) ({ /<name> })
bodyO array de Statement do corpo do loop
// foreach
{ "type": "Loop", "over": "{ /payload/fields/items }", "name": "item", "maxIterations": 100,
  "body": [ { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
             "fields": { "name": { "en-US": "{ /item/name }" } } } ] }
 
// while
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000, "body": [ /* ... */ ] }
 
// counted (1..10 step 2)
{ "type": "Loop", "for": { "from": 1, "to": 10, "step": 2 }, "name": "i", "maxIterations": 100, "body": [ /* ... */ ] }

Parallel

Executa as branches de forma concorrente e prossegue após a junção delas. Referências entre branches não são permitidas (se houver uma dependência, coloque-as sequencialmente).

CampoDescrição
branchesStatement[][]. Cada elemento é uma branch (um array de statements)
{ "type": "Parallel", "branches": [
  [ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
  [ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ]
] }

Return

É o return da programação comum. Retorna o resultado do Script ao chamador e encerra normalmente naquele ponto.

CampoDescrição
value(Opcional) A expressão de valor a retornar
isErrorPadrão false. Quando true, value retorna como o error da resposta (caso contrário, como return)
statusCodeO código de status da resposta. Padrão 200
  • Se Return nunca for alcançado, não há valor de retorno. Para retornar um resultado, especifique value explicitamente.
  • Por ser um encerramento normal, não uma exceção ou throw, não é alvo de catch (mesmo dentro de Try, encerra o Script inteiro, mas finally ainda é executado).
  • Um guard também é expresso com este statement. Se você colocar um Return no then de um If, ele retorna um valor quando a condição é violada e não executa os statements seguintes. Este é um dos vários usos de Return.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "payment failed" }, "isError": true, "statusCode": 402 }

Try

Tratamento de exceções.

CampoDescrição
bodyO array de Statement a tentar
catch(Opcional) Executa quando body falha. Expõe { message } em /error (em qual statement a falha ocorreu não é incluído)
finally(Opcional) Executa sempre, independentemente de sucesso ou falha
  • Se catch o tratar, o Script não é interrompido. Apenas uma falha sem catch interrompe o Script (incluindo uma tentativa de compensação).
  • O que conta como "falha" e os limites da compensação são abordados em Semântica de execução, restrições e segurança.
{ "type": "Try",
  "body":    [ { "type": "Http", "method": "POST", "url": "https://primary.api/gen", "name": "resp" },
               { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
                 "fields": { "text": { "en-US": "{ /resp/body/text }" } } } ],
  "catch":   [ { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
                 "fields": { "text": { "en-US": "Generation failed" }, "error": { "en-US": "{ /error/message }" } } } ],
  "finally": [ /* sempre é executado */ ] }