Catálogo de Statement

Cada elemento del array statements es un Statement. Este documento recopila los campos, el comportamiento y el resultado de los 25 tipos de Statement. Todas las posiciones de valor siguen las reglas de Expresiones de valor (referencias, literales, JsonLogic, mapas de locale), con tres excepciones: el pattern de Regex, la key de Cache y el color de PdfWatermark (véanse Regex, Cache y Transformación de archivos).

Resumen de Statement

CategoríatypeResumen en una línea
Escritura de recursosResourceCreateCrea Content/Media (opcionalmente publica)
ResourceUpdateReemplazo completo de los campos de Content/Media (elimina los field y locale no incluidos)
ResourcePatchFusión parcial de los campos de Content/Media (solo los field y locale indicados. El null literal elimina)
ResourceDeleteElimina (solo Draft y Archived. Si está Published, primero hay que anular la publicación)
ResourcePublish / ResourceUnpublishPublica / anula la publicación
ResourceArchive / ResourceUnarchiveArchiva / desarchiva
Lectura de recursosResourceReadConsulta un único elemento por id
ResourceFindPrimera coincidencia por filtro (null si no hay)
ResourceForEachRecorre internamente los recursos que coinciden con el filtro y ejecuta onEach en cada elemento
ResourceCountCuenta solo el número de registros que coinciden con el filtro (no lee los elementos)
ExternoHttpLlamada HTTP externa ({ status, body }). Puede enviar un archivo como cuerpo
EmailSendEnvía 1 correo con un EmailAccount registrado
VariablesSetVarDeclara/actualiza una variable de ámbito de script
CachéCacheLee, escribe o elimina en la caché de vida corta propia de ese Script
Parseo de valoresParseJsonParsea texto JSON a un valor (objeto, array, escalar) y lo vincula
Firma y textoSignatureVerifica si el código de firma recibido coincide con el código generado con la clave secreta (Boolean)
HashCalcula un digest sin clave (cadena)
RegexAplica una expresión regular. Si hay coincidencia (Boolean) o los grupos de captura (array)
Flujo de controlIfBifurcación condicional
LoopBucle (foreach / while / counted)
ParallelEjecución en paralelo de ramas
ReturnDevuelve el resultado y sale anticipadamente
TryManejo de excepciones (catch/finally)

Todo Statement de Content que no indica su destino por id tiene que declarar el Content Type con el que trabaja. En ResourceFind, ResourceForEach y ResourceCount, cuando resource es "Content", contentType es obligatorio. No existe ninguna consulta de Content que atraviese todo el Space. ResourceCreate también declara el Content Type que va a crear. Un Media no lleva ámbito, porque hay uno solo para todo el Space, y los Statement que indican su destino por id (ResourceRead, ResourceUpdate, ResourcePatch, ResourceDelete y los de publicación y archivado) tienen target, así que no necesitan ámbito.

Las llamadas cíclicas se limitan a 3. Si en los Statement de escritura de recursos anteriores (ResourceCreate, ResourceUpdate, ResourcePublish, etc.) se activa propagateEvents (por defecto está desactivado), esa escritura genera un evento de cambio, y ese evento puede volver a ejecutar un Script a través de un Webhook. Una cadena así (Script → evento → Webhook → Script → …) continúa como máximo 3 veces. A partir de ahí se interrumpe automáticamente para evitar bucles infinitos.

Campos comunes

{ "type": "<StatementType>", "name": "<opcional, único dentro del script>", /* ...campos según el tipo... */ }
  • type: es el discriminador. Es uno de los valores de la tabla anterior (obligatorio).
  • name: es opcional. Si se pone, el resultado se vincula al contexto como /<name> y los Statement posteriores lo referencian con { /<name>/... }. Si no se usa el resultado, se omite.
  • Reglas del nombre de vinculación: name es una clave que se coloca directamente en la raíz del contexto, por lo que se valida al guardar. Solo puede usar letras del alfabeto latino, dígitos, _ y - (debe poder usarse como clave de JSON Pointer, así que cualquier otro carácter, o un nombre vacío, se rechaza), no puede coincidir con las raíces reservadas (payload, rawPayload, headers, vars, error, now, secrets) y debe ser único dentro de un mismo Script. Si se incumple el formato, se usa una palabra reservada o hay un duplicado, el guardado se rechaza.

Forma de referencia a entidades

Las referencias a entidades como contentType y target se unifican en una única forma: { "sys": { "id": <expresión de valor> } }. Solo se necesita sys.id, y el tipo de destino se infiere de resource (sys.type y sys.targetType se omiten).

  • contentType.sys.id suele ser un literal (por ejemplo, "ct_post").
  • target.sys.id es normalmente una expresión de valor de la forma { /ptr } (se resuelve en tiempo de ejecución; por ejemplo, { /payload/sys/id }).

resource

Los Statement de recursos indican el tipo de destino con resource: "Content" | "ContentType" | "Media" | "ServiceUser".

Content Type solo lo acepta ResourceCount. Si se pone en cualquier otro Statement, el guardado se rechaza. Crear o modificar el propio Content Type no es cosa de un Script, sino de CMA.

Los Statement que aceptan un ServiceUser (el miembro que se ha registrado en el producto) son los tres Statement de lectura (ResourceRead, ResourceFind, ResourceForEach) y ResourceUpdate y ResourcePatch. Los únicos campos que estos dos pueden escribir son roleOverride y enableLogin. Si se pone en cualquier otro Statement de escritura, el guardado se rechaza (véase Errores). Las reglas se tratan en Manejar el directorio de miembros.

Escritura de recursos

Todos los Statement de escritura tienen propagateEvents (por defecto false) (los ResourceUpdate y ResourcePatch que escriben un ServiceUser no aceptan este campo). Si se pone en true, esa escritura genera un evento de cambio y con él se ejecutan acciones posteriores como un Webhook. Por defecto no lo genera (escritura silenciosa del sistema).

ResourceCreate

Crea un Content o un Media. Content y Media comparten el modelo fields, y los valores son mapas de locale.

CampoAplica aDescripción
resourceComún"Content" o "Media" (obligatorio)
contentTypeContentEl Content Type que se va a crear ({ sys: { id } }). Obligatorio cuando es Content
fieldsComúnMapa de campos { "<field>": { "<locale>": valor } }. Cada campo poblado requiere el bucket del locale por defecto. Las claves de Content siguen la definición del Content Type, y las de Media son fijas (title, description, file)
localeComún(comodidad) Si se indica, envuelve automáticamente cada valor de fields como { <locale>: valor }
publishComúnPublica tras la escritura (exposición en CDA/ACDA). Por defecto true
  • file de Media: el valor de fields.file.{locale} es una instrucción de ingesta { "source": <expresión de valor>, "encoding": "url"|"base64", "transformations": [ … ] }. source y encoding son obligatorios, y transformations solo se escribe cuando se quiere modificar el archivo recibido antes de guardarlo (Transformación de archivos). En una escritura que incluye archivo, el motor realiza la ingesta (si es url, lo descarga; si es base64, lo decodifica y después lo sube y lo procesa). Esta ingesta no declara ningún tiempo, así que sale del presupuesto básico de 30 segundos (presupuesto de tiempo), y no cuenta para el límite de llamadas externas. También se puede crear un Media sin archivo (fileless). Si publish:true pero no hay archivo o el procesamiento no ha terminado, se produce un error en la fase de publicación; con publish:false, queda como Draft.
  • Resultado (vinculado a name): es el recurso creado. { /<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 es la instrucción de ingesta
{ "type": "ResourceCreate", "resource": "Media",
  "fields": {
    "title": { "en-US": "{ /payload/fields/prompt }" },
    "file":  { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } }
  }, "name": "img" }

Transformación de archivos (transformations)

Cuando se venden libros electrónicos, a veces interesa entregar una copia con el correo electrónico del comprador estampado en todas las páginas del PDF. Si se escribe transformations en la instrucción de ingesta, el archivo recibido se puede modificar antes de guardarlo. La única transformación admitida es PdfWatermark.

Campo de la instrucción de ingestaObligatorioDescripción
transformationsOpcionalArray de objetos de transformación. Si se omite o es un array vacío, el archivo recibido se guarda en el Media sin modificarlo. Si no es un array, se rechaza el guardado del Script
  • Se aplican en el orden del array. El resultado de una transformación es la entrada de la siguiente, y el último resultado pasa a ser el archivo del Media.
  • Se puede escribir en cualquier instrucción de ingesta de ResourceCreate, ResourceUpdate y ResourcePatch.
  • El original al que apunta source no cambia. Para estampar un texto en un archivo que ya está en el Space, se lee ese Media con ResourceRead y se pone { /<name>/fields/file/<locale>/url } en source para crear un Media nuevo.
  • La transformación también forma parte de la ingesta. El tiempo que tarda sale del presupuesto básico de 30 segundos (presupuesto de tiempo).

PdfWatermark: estampar un texto en todas las páginas de un PDF

CampoObligatorioDescripción
typeObligatorio"PdfWatermark". Con cualquier otro valor, se rechaza el guardado del Script
textObligatorioEl texto que se estampa. Es una cadena y puede combinarse con referencias { /pointer }. No acepta objetos de operación JsonLogic. Debe ser una sola línea de 1 a 64 caracteres compuesta por caracteres admitidos
colorOpcionalColor del texto. Se escribe como #RRGGBB (seis dígitos hexadecimales) y, si se omite, es #808080 (gris). Solo acepta un literal, no una expresión de valor, y si el formato no es correcto, se rechaza el guardado del Script
  • Los campos que no figuran en la tabla se ignoran. No se puede indicar el tamaño de la letra, la posición ni la transparencia.
  • Se estampa en todas las páginas. Una línea de texto se coloca a lo largo de la diagonal que pasa por el centro de la página (de la esquina inferior izquierda a la superior derecha).
  • Se superpone al contenido con una opacidad del 30%. El contenido original que queda bajo el texto sigue siendo legible.
  • El tamaño de la letra se determina automáticamente en cada página. El texto se estampa con un tamaño que ocupa el 80% de la longitud de la diagonal, sin superar el 15% del lado corto de la página. Los textos cortos se estampan grandes y los largos, pequeños, y nunca se salen de la página.
  • La fuente es Helvetica, una de las fuentes estándar de PDF.
  • Si un archivo es un PDF se determina por su contenido, no por el nombre ni la extensión del archivo.
// Parte del array statements. Un Media nuevo con el correo electrónico del comprador y el número de pedido estampados en el PDF del libro electrónico comprado
{ "type": "ResourceRead", "resource": "Media",
  "target": { "sys": { "id": "{ /payload/fields/ebookMediaId }" } }, "name": "ebook" },
{ "type": "ResourceCreate", "resource": "Media", "locale": "en-US", "publish": false,
  "fields": {
    "title": "{ /ebook/fields/title/en-US }",
    "file": {
      "source": "{ /ebook/fields/file/en-US/url }", "encoding": "url",
      "transformations": [
        { "type": "PdfWatermark", "text": "{ /payload/fields/email } · { /payload/fields/orderId }", "color": "#B22222" }
      ]
    }
  }, "name": "stamped" }

Como es una copia para un solo comprador, publish se ha puesto en false para que no se entregue por CDA/ACDA. Si se omite, se publica según el valor por defecto true.

Limitaciones del texto de PdfWatermark

En text solo se pueden usar los caracteres que la fuente estándar de PDF es capaz de dibujar (el juego de caracteres WinAnsi).

TipoCaracteres
Caracteres imprimibles ASCIIEl espacio, las letras del alfabeto inglés en mayúsculas y minúsculas, los dígitos y símbolos básicos como @ . # { }
Caracteres Latin-1Letras y símbolos de Europa occidental como é ü ñ ß · © °. Se excluyen el espacio de no separación (U+00A0) y el guion virtual (U+00AD)
Símbolos adicionales€ ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ Ž ‘ ’ “ ” • ˜ ™ š › œ ž Ÿ, y los dos tipos de raya (U+2013, U+2014)

No se puede usar ningún otro carácter. Esto incluye las letras latinas extendidas que no figuran en la tabla (como ł, ő o ș), los caracteres no latinos, los emojis, y los saltos de línea y las tabulaciones.

Al guardar el Script, se comprueba el text tal como está escrito. En un text que contiene { /pointer }, el valor resuelto solo se conoce en el momento de la ejecución, así que ese valor resuelto se vuelve a comprobar al ejecutar. Los códigos de los errores que se producen al guardar están en Errores.

Limitacióntext literaltext con { /pointer }
Una sola línea compuesta por caracteres admitidosSe rechaza el guardado del ScriptAl guardar se comprueba toda la cadena escrita (incluidas las rutas de los punteros) y se rechaza el guardado del Script. El valor resuelto se vuelve a comprobar al ejecutar y falla con status 400
De 1 a 64 caracteresSe rechaza el guardado del ScriptNo se comprueba al guardar. Al ejecutar se comprueba la longitud del valor resuelto y falla con status 400
  • Calcule la longitud del texto fijo antes de escribirlo. En "Purchased by { /payload/fields/email } · Order { /payload/fields/orderId }", el texto fijo fuera de las plantillas tiene 22 caracteres, así que al correo electrónico y al número de pedido les quedan 42 caracteres entre los dos. Si llegan valores reales más largos, el Script se guarda, pero esa ejecución falla.
  • Estampe identificadores en lugar de nombres. Un nombre de comprador que contenga caracteres que no figuran en la tabla hace fallar la ejecución. En cambio, un correo electrónico o un número de pedido en ASCII sí se pueden estampar.

Casos en los que falla la transformación de archivos

En los casos siguientes, ese Statement falla. Como cualquier otro fallo de tiempo de ejecución, se puede manejar con Try/catch. La transformación se produce antes de escribir el Media, así que, si falla, no se crea ningún Media nuevo y el destino de ResourceUpdate o ResourcePatch tampoco cambia.

SituaciónResultado
El archivo de source no es un PDFFalla con status 422
El PDF no se puede leer o está cifrado (incluidos los PDF que no piden contraseña al abrirlos y solo tienen restringidos los permisos)Falla con status 422
El text resuelto está vacío o solo contiene espaciosFalla con status 400
El text resuelto no tiene entre 1 y 64 caracteres o contiene caracteres no admitidosFalla con status 400

ResourceUpdate

Reemplaza por completo los campos del Content, Media o ServiceUser de destino (PUT). Lo que se pasa en fields se convierte tal cual en los nuevos campos, y los field y locale que no aparezcan aquí se eliminan. Para cambiar solo una parte, se usa ResourcePatch.

CampoDescripción
resource"Content", "Media" o "ServiceUser". Los campos que se pueden escribir en un ServiceUser y sus restricciones están en Manejar el directorio de miembros
targetDestino ({ sys: { id } }, obligatorio). El id suele ser { /ptr }
fieldsTodos los campos que se van a escribir. El valor es un mapa de locale. Al ser un reemplazo completo, se eliminan los field y locale que no aparezcan aquí. En Media, file es la instrucción de ingesta (véase ResourceCreate arriba). Los archivos enumerados se vuelven a ingerir siempre, y los archivos de los locale no incluidos se eliminan
locale(comodidad) Envuelve fields automáticamente
version(opcional) Expresión de valor (Int). Bloqueo optimista. Si se indica, actualiza solo cuando coincide con el sys.version actual del destino; si no coincide, aborta con un error de conflicto de versión (se puede capturar con Try). Si se omite, no hay comprobación (last-write-wins). Un ServiceUser no lo acepta
publishVuelve a publicar tras la actualización. Por defecto true

Si se usa Update para cambiar solo los metadatos de un Media, al faltar file se eliminan todos los archivos (porque es un reemplazo completo). Para un cambio parcial hay que usar ResourcePatch.

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

ResourcePatch

Fusiona parcialmente los campos del Content, Media o ServiceUser de destino (PATCH). Sobrescribe solo los campos (y los locales dentro de ellos) que se indiquen, y mantiene sin cambios los campos y locales no mencionados. La forma del valor, locale, version y publish son iguales que en ResourceUpdate.

CampoDescripción
resource"Content", "Media" o "ServiceUser". Los campos que se pueden escribir en un ServiceUser y sus restricciones están en Manejar el directorio de miembros
targetDestino ({ sys: { id } }, obligatorio). El id suele ser { /ptr }
fieldsLos campos que se van a sobrescribir. El valor es un mapa de locale. Actualiza solo los campos y buckets de locale indicados (el resto se mantiene). Si el valor es un null literal, elimina ese (field, locale). En Media, file es la instrucción de ingesta (véase ResourceCreate arriba)
locale(comodidad) Envuelve fields automáticamente
version(opcional) Igual que en ResourceUpdate (bloqueo optimista). Un ServiceUser no lo acepta
publishVuelve a publicar tras la actualización. Por defecto true
  • Eliminar un locale o un archivo concreto: se pasa un null literal como valor. Por ejemplo: "title": { "fr-FR": null } (elimina el título fr-FR), "file": { "en-US": null } (elimina el archivo en-US). Que una expresión de valor se evalúe como null en tiempo de ejecución no es una eliminación, sino un error (solo el null literal elimina).
  • Si se da una instrucción de ingesta al file de un Media, se reemplaza el archivo de ese locale. Si no se da el archivo, se mantiene.
// +1 solo a viewCount(en-US). title, otros locales, etc. se conservan
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
  "fields": { "viewCount": { "en-US": { "$+": [ "{ /payload/fields/viewCount }", 1 ] } } } }

ResourceDelete

Elimina el destino. Solo se puede eliminar en los estados Draft y Archived. Si está Published o Changed, se rechaza, por lo que primero hay que usar ResourceUnpublish. En un Media, la eliminación se rechaza mientras se están procesando sus archivos. No anula la publicación automáticamente (igual en CMA/ACMA).

CampoDescripción
resource"Content" o "Media"
targetDestino ({ sys: { id } }, obligatorio)
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }

ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive

Controla de forma independiente el estado de publicación y de archivado del destino. Los cuatro tienen los mismos campos. Las condiciones previas de status de cada operación son iguales que en CMA/ACMA. ResourcePublish no se puede hacer desde Archived y requiere que el procesamiento del archivo haya terminado. ResourceUnpublish solo se puede hacer desde Published y Changed, ResourceArchive solo desde Draft y ResourceUnarchive solo desde Archived.

CampoDescripción
resource"Content" o "Media"
targetDestino ({ sys: { id } }, obligatorio)
version(opcional) Expresión de valor (Int). Bloqueo optimista. Si se indica, se ejecuta solo cuando coincide con el sys.version actual
{ "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 }" } } }

Lectura de recursos

ResourceRead y ResourceFind leen el recurso y lo vinculan como valor, y ResourceCount solo cuenta registros. Ninguno de los tres cambia el estado (no tienen propagateEvents). ResourceForEach también es una lectura en cuanto a la consulta en sí, pero si en onEach se colocan Statement de escritura de recursos, esa escritura se ejecuta en cada elemento y sí cambia el estado.

Los cuatro Statement (ResourceRead, ResourceFind, ResourceForEach, ResourceCount) determinan de qué versión almacenada leer con from (por defecto Current). Current es el borrador más reciente que ve el estudio de contenidos (el valor que leen CMA/ACMA), y Published es la instantánea publicada (el valor en el momento de la última publicación, que entregan CDA/ACDA). Un ServiceUser no se publica, así que solo acepta Current (véase Manejar el directorio de miembros).

Además, ResourceFind, ResourceForEach y ResourceCount activan y desactivan la búsqueda avanzada (Advanced Search) con advanced (por defecto true). Si no se indica, está activada. Es exclusiva de Content, por lo que se ignora en las lecturas de Media y de ServiceUser. Cuando está activada, en where se pueden usar los operadores regex, near y within y la búsqueda de texto completo (en un campo LongText con la búsqueda de texto completo activada, eq también encuentra los elementos que contienen el valor, por coincidencia parcial y aproximada), y order puede ordenar por fields.*. Cuando está desactivada, esos tres operadores se rechazan, eq sobre texto es coincidencia exacta, y prefix y los operadores de comparación y de lista funcionan con independencia de la búsqueda avanzada. Un elemento recién creado o modificado tarda un breve instante (alrededor de 1 segundo) en reflejarse en la búsqueda avanzada, por lo que la consulta de búsqueda avanzada inmediatamente posterior puede no encontrarlo. Como está activada por defecto, ese retardo afecta a todas las consultas salvo que se ponga advanced en false. Para leer de inmediato un elemento recién escrito, se usa ResourceRead por id (el almacén principal, sin retardo de reflejo) o se consulta por el sys.id que devolvió la escritura.

El createdBy: ":self" de where significa «solo lo creado por el usuario que llama en este momento». Sin embargo, no se puede usar en un Script que permite la llamada anónima (anonymousCallEnabled). En ese caso, :self se resuelve como el autor y no como el llamante, y así quedarían abiertos en silencio los recursos del autor, por lo que una definición de ese tipo se rechaza al guardar (véase Llamada anónima).

En where y order, los campos de contenido se escriben como fields.<field> (el nombre por sí solo no se reconoce). A fields.<field> se le aplica automáticamente el locale predeterminado del Space, por lo que no se le añade el locale directamente. En los ejemplos de abajo, fields.status y fields.slug son, tal cual, consultas del locale predeterminado. Solo cuando se quiere consultar un locale específico (no predeterminado) se especifica como fields.<field>.<locale> (por ejemplo, fields.title.ko-KR). sys.* (como sys.createdAt) y createdBy (:self) se escriben tal cual, sin fields.. Las reglas detalladas están en El locale en where y order.

Manejar el directorio de miembros (ServiceUser)

ResourceRead, ResourceFind y ResourceForEach aceptan "ServiceUser" en resource y leen el directorio de miembros de ese Space (ResourceCount no lo acepta; véase ResourceCount más abajo). Se usan para comprobar de quién es un pedido, o para buscar a un miembro por su correo y pasar su sys.id al Statement siguiente. Las reglas de abajo abarcan los cinco Statement que manejan el directorio de miembros.

  • Solo hay dos campos modificables. ResourceUpdate y ResourcePatch aceptan "ServiceUser" y cambian roleOverride y enableLogin. Si en ResourcePatch se pasa null a roleOverride, se quita el rol que estaba asignado. ResourceCreate y ResourceDelete, y los Statement de publicación y archivado, no aceptan "ServiceUser", y una definición así se rechaza en el momento de guardar. No es algo que se pueda abrir añadiendo permisos: un Script no puede crear ni eliminar miembros, así que se rechaza como un Statement mal escrito, no como un error de permisos.
  • Un miembro no tiene version ni propagateEvents. Si se indica cualquiera de los dos, la ejecución falla. Como ResourceUpdate reemplaza por completo los campos que se pueden escribir, enableLogin es obligatorio, y si no se indica roleOverride se quita el rol que estaba asignado. Para cambiar solo uno de los dos se usa ResourcePatch, y en ese caso hay que indicar al menos uno de los dos campos.
  • Para guardar, el autor debe tener permiso sobre el directorio de miembros. No se comprueba con el mapa de permisos, como en Content y Media, sino mirando si el settings del SpaceRole del autor tiene SETTING_SERVICE_LOGIN (o SETTING_ALL). El directorio de miembros es un recurso que, en todas las demás vías, gobierna la configuración del Space. Si no lo tiene, el guardado se rechaza (véase el modelo de seguridad).
  • from solo acepta Current. Un miembro no es un recurso que se publique, así que, si se pasa Published, la ejecución falla.
  • contentType y advanced se ignoran. El directorio de miembros no se divide por Content Type (hay uno solo para todo el Space), y la búsqueda avanzada es exclusiva de Content.
  • El sys.email de where solo acepta operadores de coincidencia exacta (eq, ne, in, nin). La dirección del miembro se almacena cifrada, así que las comparaciones de orden o prefix no tienen sentido. Con cualquier otro operador, en lugar de devolver 0 resultados en silencio, la ejecución falla.
  • El resultado es el recurso ServiceUser en sí. Se referencia como { /<name>/sys/id } o { /<name>/nickname }. Su estructura se trata en la referencia de ServiceUser. Para enviar un correo al miembro encontrado, no se extrae su dirección: se pasa su sys.id en el toServiceUser de EmailSend (como el motor resuelve la dirección justo antes del envío, la dirección del miembro no entra en el espacio de variables del Script).
// Busca a un miembro por su correo. Si no lo hay, null
{ "type": "ResourceFind", "resource": "ServiceUser",
  "where": { "sys.email": { "eq": "{ /payload/fields/email }" } }, "name": "member" }

ResourceRead

Es una consulta de un único elemento por id (get-by-id). El resultado vincula el recurso completo al nombre.

CampoDescripción
resource"Content", "Media" o "ServiceUser"
targetDestino ({ sys: { id } }). El id es una expresión de valor
from(opcional) Current (por defecto, el borrador más reciente) o Published (la instantánea publicada). Un ServiceUser solo admite Current
  • Resultado: se vincula el recurso en sí. Si ha dado un name a este Statement, se referencia directamente con { /<name>/sys/id } y { /<name>/fields/<field>/<locale> } (con el "name": "order" del ejemplo de abajo, { /order/sys/id }). No es una lista, así que no interviene ningún índice de array.
  • Si el destino no existe, es un error. Se puede manejar envolviéndolo con Try.
{ "type": "ResourceRead", "resource": "Content",
  "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }

ResourceFind

Lee el primer registro que coincide con el filtro. Si no hay ninguna, es null. Se usa para encontrar un único registro por una clave de negocio única (slug, email, sku).

CampoDescripción
resource"Content", "Media" o "ServiceUser"
contentTypeEl Content Type que define el ámbito de búsqueda ({ sys: { id } }). Obligatorio cuando es Content. En Media y ServiceUser se ignora
whereFiltro ({ "<field>": { "<op>": <valor> } }). Los operadores son los de la lista de operadores (regex/near/within requieren advanced). Admite createdBy: ":self". El sys.email de un ServiceUser solo admite eq, ne, in y nin (Manejar el directorio de miembros)
orderOrden que determina cuál es «la primera» cuando hay varias coincidencias (por ejemplo, "-sys.createdAt")
from(opcional) Current (por defecto, el borrador más reciente) o Published (la instantánea publicada). Un ServiceUser solo admite Current
advanced(opcional) Ejecutar mediante búsqueda avanzada (Advanced Search). Solo Content (Media y ServiceUser se ignoran). Por defecto true. Véase la nota Lectura de recursos anterior.
  • Resultado: vincula al name de este Statement el recurso de la primera coincidencia. Se referencia directamente con { /<name>/fields/<field>/<locale> }. Como es null cuando no hay ninguna, se bifurca según su existencia con { "==": [ "{ /<name> }", null ] } (el patrón típico de find-then-upsert).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
  "where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }

ResourceForEach

Recorre internamente los recursos que coinciden con el filtro y ejecuta onEach en cada elemento. Es un Statement para realizar una operación sobre cada elemento, no para construir una colección que usar como valor. Se usa en tareas repetitivas como publicar borradores en lote, modificar en lote los Content que cumplen una condición o enviar/sincronizar cada elemento al exterior. Para leer un solo registro se usa ResourceRead (por id) o ResourceFind (por filtro).

CampoDescripción
resource"Content", "Media" o "ServiceUser" (obligatorio)
contentTypeEl Content Type que define el ámbito del recorrido ({ sys: { id } }). Obligatorio cuando es Content. En Media y ServiceUser se ignora
whereFiltro ({ "<field>": { "<op>": <valor> } }). El significado es igual que el where de ResourceFind (también la restricción del sys.email de un ServiceUser). Los operadores son los de la lista de operadores (regex/near/within requieren advanced). Admite createdBy: ":self"
orderOrden (por ejemplo, "sys.createdAt,sys.id"). Si no lo hay, el orden predeterminado de la plataforma
fromCurrent (por defecto, el borrador más reciente) o Published (la instantánea publicada). Un ServiceUser solo admite Current
advancedRecorrer mediante búsqueda avanzada (Advanced Search). Solo Content (Media y ServiceUser se ignoran). Por defecto true. Véase la nota Lectura de recursos anterior
limit(opcional, 1 o más) Límite superior del número total procesado (no es el tamaño de página). Si no lo hay, recorre hasta el límite de la plataforma (10.000 elementos)
name(opcional) El nombre al que vincular el elemento actual. Se vincula de nuevo en cada iteración y se referencia con { /<name> } dentro de onEach (con la misma vida que el name de Loop; tras terminar el recorrido, permanece vinculado el último elemento). Si no se referencia el elemento, se omite
onEachArray de Statement hijos a ejecutar en cada elemento (obligatorio)
  • No vincula una colección (foreach, no map). No hay { items, next } ni cursor. No se devuelve el resultado del recorrido como valor, sino que se ejecuta onEach en cada elemento. Si se necesita la lista, se recopila directamente con SetVar. Si solo hace falta el número, se usa ResourceCount.
  • Aunque no haya limit, no es un recorrido infinito. Si no lo hay, recorre hasta el límite de la plataforma (10.000 elementos) y, si llega a ese límite quedando coincidencias, falla (para no informar de éxito dejando elementos sin tocar). Por el contrario, alcanzar el limit declarado es una parada intencionada, así que es una terminación normal. Un limit que supere el límite superior se rechaza al guardar.
  • No hay cursor. Si recorre hasta el final, es un éxito; si se corta a mitad (superación del tiempo de reloj o de la cuota, un fallo no gestionado de onEach), es un fallo, y el error señala en qué elemento y por qué falló. La reanudación la expresa el autor con sus propios datos (si se deja el where como «no procesado» y al final de onEach se marca como completado, al reejecutar continúa desde lo que quedó).
  • En el presupuesto de tiempo se contabiliza como una multiplicación. El tiempo que declara este Statement es el tiempo declarado por onEach multiplicado por el número de elementos procesados (limit, o 10.000 si no lo hay) (presupuesto de tiempo). Como es un Statement compuesto que posee hijos, él mismo no cuenta para el presupuesto de leaf de llamadas externas; son los Statement de llamada externa dentro de onEach los que cuentan para el presupuesto.
  • En onEach, como en cualquier otro Statement, se pueden colocar llamadas externas (Http, EmailSend) o ingesta de archivos de Media (igual que el body de Loop). La razón de ser de este Statement es procesar el resultado de una consulta de recursos una vez por cada elemento.
// Encuentra todos los posts en estado draft y publica cada uno
{ "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

Cuenta solo el número de registros que coinciden con el filtro. Como no trae los elementos, se usa cuando lo que hace falta es la cantidad y no la lista. Es el lugar para comprobar el stock restante, para determinar si ya existe un valor igual o para verificar si se ha superado un límite.

CampoDescripción
resource"Content" o "ContentType" (obligatorio). Media y ServiceUser no se pueden contar, y escribirlos así hace que el guardado se rechace
contentTypeEl Content Type que define el ámbito del recuento ({ sys: { id } }). Obligatorio cuando es Content. Cuando se cuentan Content Type, se ignora (hay uno solo para todo el Space)
whereFiltro. El significado es igual que el where de ResourceFind. Cuenta todos los elementos que coinciden
from(opcional) Current (por defecto, el borrador más reciente) o Published (la instantánea publicada)
advanced(opcional) Ejecutar mediante búsqueda avanzada (Advanced Search). Solo Content (se ignora cuando se cuentan Content Type). Por defecto true. Véase la nota Lectura de recursos anterior
name(opcional) El nombre al que vincular el número
  • Resultado: vincula al name de este Statement el número de coincidencias. Se referencia con { /<name> } y se usa en comparaciones y bifurcaciones.
  • No devuelve los elementos. Si se necesitan los elementos, se usa ResourceFind (la primera coincidencia) o ResourceForEach (ejecutar en cada elemento).
  • No cuente recorriendo con ResourceForEach para obtener un número. El recorrido reserva el presupuesto de tiempo multiplicado por el número de elementos (presupuesto de tiempo) y falla si llega al límite de la plataforma quedando coincidencias. Si solo hace falta contar, este Statement lo resuelve de una vez.
  • No hay order ni limit. Para contar no hace falta ningún orden, y se cuentan todas las coincidencias.
// Cuenta cuántos comentarios tiene este post
{ "type": "ResourceCount", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
  "where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "name": "commentCount" }

Externo

Http

Llama a un HTTP externo. Al ser una llamada externa, cuenta para el límite de llamadas externas por plan. Al presupuesto de tiempo se suma timeoutMs y, si se envían archivos, también se suma el tiempo de descarga de cada archivo. Si se indica retry, esa suma se multiplica por el número de intentos (1 + retry).

CampoDescripción
method"GET", "POST", "PUT", "PATCH", "DELETE"
urlLa URL de destino (expresión de valor; se puede insertar { /ptr })
headers[{ "key", "value" }]. value es una expresión de valor. Los valores secretos, como una clave de API, no se escriben tal cual: se declaran en secrets y se leen con { /secrets/<key> } (por ejemplo: "Bearer { /secrets/LLM_API_KEY }"). Si pone aquí Content-Type, el body se serializa en ese formato (más abajo)
bodyEl cuerpo de la petición (expresión de valor o JSON). En qué formato se envía lo fija la cabecera Content-Type
urlBodyLa URL del archivo que se envía como cuerpo de la petición (expresión de valor). La plataforma descarga el archivo de esa URL y envía sus bytes tal cual (Enviar un archivo como cuerpo)
multipartBodyArray de partes que se envían como multipart/form-data (1 o más). Se pueden mezclar partes de texto y partes de archivo (Enviar un archivo como cuerpo)
timeoutMsEl tiempo de espera de esta llamada (ms)
retryNúmero de reintentos cuando el status de la respuesta es 400 o superior. Por defecto 0; el máximo es 2
ignoreStatusCodeDetermina si esta llamada se trata como un fallo cuando el status final (tras los reintentos) es 400 o superior. Con false (por defecto), se trata como un fallo y pasa a ser objeto de Try/catch. Con true, no se trata como un fallo y { status, body } se vincula tal cual (el llamante bifurca según el propio status)
responseTypeEn qué forma se recibe el cuerpo de la respuesta. "Json" (por defecto) lo parsea como un objeto o un array; "Text" lo recibe como cadena
  • Solo se usa uno de los tres: body, urlBody o multipartBody. Si se indican dos o más, el guardado se rechaza. Si no hay ninguno de los tres, se envía sin cuerpo.
  • Resultado: es { status, body }. Si ha dado un name a este Statement, { /<name>/status }, { /<name>/body/... }. La forma de body la fija responseType.
  • responseType solo se aplica a una respuesta correcta. El cuerpo de una respuesta con status 400 o superior se vincula para diagnóstico sea cual sea el valor declarado (el valor parseado si es JSON, una cadena si no).
  • Si es "Json" y el cuerpo no es JSON, esta llamada falla (pasa a ser objeto de Try/catch). Para las API que no devuelven JSON, reciba el cuerpo como "Text" y parséelo con ParseJson cuando necesite usarlo como valor.
  • "Text" se decodifica con el charset del Content-Type de la respuesta y se toma como UTF-8 cuando no hay charset. Si el cuerpo está vacío, body es null en ambos casos.
  • Límite de tamaño de la respuesta: el cuerpo de la respuesta es de 10MiB como máximo. Si lo supera, esta llamada falla con una excepción y se puede manejar como cualquier otro fallo de tiempo de ejecución con Try/catch (es un fallo basado en el tamaño, por lo que no se suprime con ignoreStatusCode).
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
  "headers": [ { "key": "Authorization", "value": "Bearer { /secrets/LLM_API_KEY }" } ],
  "body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "retry": 1,
  "responseType": "Json", "name": "resp" }

En qué formato se envía el body

El Content-Type que haya puesto en headers fija el formato de serialización del body. La comparación no distingue mayúsculas de minúsculas e ignora los parámetros como ;charset=…, y solo tiene en cuenta la parte inicial. Si falta esa cabecera o su valor está vacío, se envía como application/json. Esta cabecera solo se añade cuando hay body, de modo que, si no hay body, sale tal cual la cabecera que haya escrito. Si pone la misma clave varias veces, solo se usa el primer valor y se unifican en una sola.

Un body que no cabe en el formato declarado se envía corregido a un formato en el que sí cabe. La cabecera nunca declara un formato distinto del que realmente lleva el body.

Estas son las combinaciones que salen con el valor declarado tal cual.

Content-Type declaradoForma del bodybody que sale
application/jsonCualquieraJSON
application/x-www-form-urlencodedObjeto o arrayorder[id]=A-2481&order[amount]=34000
text/plainEscalarEl valor tal cual
Otros (text/xml, etc.)CualquieraJSON

Estas son las combinaciones que se corrigen porque no se pueden contener en el formato declarado.

Content-Type declaradoForma del bodyContent-Type que sale realmentebody que sale
application/x-www-form-urlencodedEscalartext/plain;charset=UTF-8El valor tal cual
text/plainObjeto o arrayapplication/jsonJSON

Estas dos filas aclaran cómo sale la petición cuando la cabecera y la forma del body no encajan, y no son la manera de obtener el formato que se pretende. Si el body se compone con expresiones de valor, puede resultar un escalar según el payload del momento de la ejecución, y entonces esta corrección se produce sin error. Si la contraparte receptora pone objeciones al formato, corrija la forma del body o el Content-Type, uno de los dos, conforme a su intención.

form-urlencoded despliega los objetos con claves entre corchetes y los arrays con índices.

bodyClaves y valores desplegados
{ "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=

Las claves y los valores salen con codificación porcentual en UTF-8. La tabla anterior está decodificada para mostrar la estructura de las claves. Aunque el valor contenga & o +, no se confunden con el separador de pares ni con un espacio, y se transmiten tal cual.

La notación que despliega el anidamiento con claves entre corchetes es una convención muy extendida, y no una especificación del formato en sí. Compruebe si la contraparte receptora restaura order[id] como un objeto anidado y, si no lo restaura, construya el body con claves 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" }

Enviar un archivo como cuerpo

A veces hay que pasar a una API externa un archivo subido a Media. Los bytes del archivo no se pueden meter en una expresión de valor. En su lugar, si se indica la URL en la que está el archivo, la plataforma descarga el archivo de esa URL y lo envía como cuerpo de la petición. Para enviar un único archivo como cuerpo completo se usa urlBody, y para enviar archivos junto con campos de texto en un formulario se usa multipartBody.

urlBody: enviar un único archivo como cuerpo completo

Envía como cuerpo de la petición, tal cual y sin transformarlo, el archivo descargado de la URL indicada. Es adecuado para las API que suben un archivo con PUT a una URL de subida. A continuación se muestra un ejemplo que lee un contrato en PDF (Media) y lo envía a la URL de subida de un servicio de firma electrónica.

// Parte del array statements
{ "type": "ResourceRead", "resource": "Media",
  "target": { "sys": { "id": "{ /payload/fields/contractMediaId }" } }, "name": "contract" },
{ "type": "Http", "method": "PUT",
  "url": "https://api.esign.example/v1/envelopes/{ /payload/fields/envelopeId }/document",
  "headers": [ { "key": "Authorization", "value": "Bearer { /secrets/ESIGN_API_KEY }" },
               { "key": "Content-Type", "value": "application/pdf" } ],
  "urlBody": "{ /contract/fields/file/en-US/url }", "name": "upload" }

El Content-Type de la petición se determina en el siguiente orden.

  1. Si se ha indicado Content-Type en headers, se usa ese valor tal cual. No se aplica la corrección de la serialización del body.
  2. Si no se ha indicado o su valor está vacío, se usa el Content-Type con el que respondió la URL del archivo.
  3. Si tampoco lo hay, se usa application/octet-stream.

multipartBody: enviar archivos y texto en un formulario

Se envía como multipart/form-data. Cada elemento del array es una parte, y las partes se transmiten en el orden del array. El array debe tener 1 elemento o más, y un array vacío hace que el guardado se rechace. El Content-Type es siempre multipart/form-data (la cadena delimitadora se genera automáticamente), y el Content-Type escrito en headers se ignora.

Campo de la parteDescripción
nameNombre del campo del formulario (obligatorio). No es una expresión de valor, sino un literal. Si se usa el mismo nombre varias veces, cada uno sale como una parte independiente
valueContenido de una parte de texto (expresión de valor). Si el resultado está vacío, se envía como valor vacío
urlURL de la que se obtienen los bytes de una parte de archivo (expresión de valor). Por ejemplo: la url del archivo de un Media
fileNameNombre de archivo que va en el Content-Disposition de la parte (expresión de valor). Solo se puede usar en partes con url
contentTypeEl Content-Type de la parte (expresión de valor)
  • Se indica exactamente uno de value y url. Si se indican los dos o no se indica ninguno, el guardado se rechaza. Indicar fileName en una parte con value también hace que el guardado se rechace.
  • Si se omite fileName, se usa el último segmento de la ruta de url, decodificado (sin la cadena de consulta). Si ese segmento está vacío o contiene caracteres de control, se envía como file.
  • Si se omite contentType, una parte con value sale sin Content-Type, y una parte con url usa el Content-Type con el que respondió la URL del archivo (application/octet-stream si no lo hay). Si se indica un valor que no tiene formato de tipo de medio, ese Http falla con status 400 al ejecutarse.
  • Si el resultado de la expresión de valor de fileName o de contentType contiene un carácter de salto de línea, ese Http falla con status 400 antes de enviar la petición.

A continuación se muestra un ejemplo que envía juntas la foto de un producto (Media) y las opciones de análisis a un servicio de análisis de imágenes. La parte image no indica fileName, así que como nombre de archivo sale el último segmento de la URL del archivo del Media (por ejemplo, tumbler.png).

// Parte del array statements
{ "type": "ResourceRead", "resource": "Media",
  "target": { "sys": { "id": "{ /payload/fields/photoMediaId }" } }, "name": "photo" },
{ "type": "Http", "method": "POST", "url": "https://api.vision.example/v2/analyze",
  "headers": [ { "key": "x-api-key", "value": "{ /secrets/VISION_API_KEY }" } ],
  "multipartBody": [
    { "name": "image", "url": "{ /photo/fields/file/en-US/url }" },
    { "name": "language", "value": "ko" },
    { "name": "features", "value": "labels" },
    { "name": "features", "value": "text" }
  ], "timeoutMs": 20000, "name": "analysis" }

Los fallos que se producen en un Http que envía archivos también se dividen en dos tipos. El tiempo de espera agotado se trata como una respuesta con status 504, así que se le aplican retry e ignoreStatusCode. A los fallos relacionados con archivos de la tabla de abajo no se les aplica ninguno de los dos. La clasificación completa de los fallos de Http está recogida en Las dos formas en que falla Http.

Con ignoreStatusCode: true, cuando se agota el tiempo de espera se vincula como resultado { "status": 504, "body": "timed out after …ms" }.

Una petición debe terminar dentro de timeoutMs, y el tiempo que se espera la respuesta de la URL del archivo también se incluye en él. Cuando se envía un archivo, el límite de tiempo se amplía en lo que se tarda en enviar ese archivo a una velocidad de 10.000.000 bytes por segundo. El tiempo ampliado no supera el tiempo de descarga del plan multiplicado por el número de archivos. No obstante, si la URL del archivo no informa del tamaño del archivo, el límite solo se amplía en la cantidad realmente enviada, así que, si la transmisión es lenta o se detiene a mitad, el tiempo de espera puede agotarse antes.

En los casos siguientes, ese Http falla de inmediato. No se reintenta con retry, y se trata como un fallo aunque se indique ignoreStatusCode: true. Como cualquier otro fallo de tiempo de ejecución, se puede manejar con Try/catch.

SituaciónResultado
El archivo que se va a descargar supera el límite de descarga del planFalla con status 413. Si la URL del archivo informa del tamaño de antemano, falla antes de descargarlo; si no, falla durante la descarga en el momento en que se supera el límite
La URL del archivo responde con un status que no es 2xx (incluidas las redirecciones), o no se puede conectar con la URL del archivoFalla con status 502
El contentType de una parte no tiene formato de tipo de medioFalla con status 400
El valor de fileName o contentType de una parte contiene un carácter de salto de líneaFalla con status 400. Falla antes de enviar la petición
  • El límite de descarga se aplica a cada URL de archivo. Si multipartBody tiene varias partes de archivo, el límite se aplica por separado a cada archivo.
  • Los bytes enviados, incluidos los archivos, se suman a «Llamadas externas» de la transferencia de datos en la comparativa de planes.

EmailSend

Envía 1 correo a través de un EmailAccount registrado. Los campos que recibe son únicamente los que se mapean directamente a SMTP/MIME. No hay id de plantilla, envío programado ni extensiones específicas del proveedor (si se necesita algo así, se llama directamente a la API de ese servicio de correo con Http). El remitente (la dirección de envío) no se define aquí, sino que viene del EmailAccount al que apunta account.

CampoDescripción
accountReferencia al EmailAccount que envía ({ sys: { id } }, obligatorio). Normalmente es un id literal. Si se da como expresión de valor, se resuelve en el momento del envío, así que no se puede comprobar al guardar
toDirección del destinatario (expresión de valor). Se usa exactamente uno de to o toServiceUser
toServiceUserIndica el destinatario como una referencia a un ServiceUser ({ sys: { id } }; ese sys.id puede ser una expresión de valor). El motor resuelve la dirección justo antes del envío, así que la dirección del miembro no entra en el espacio de variables del Script
ccArray de direcciones en copia (expresión de valor)
bccArray de direcciones en copia oculta (expresión de valor)
subjectAsunto (expresión de valor, obligatorio)
bodyCuerpo (expresión de valor). Siempre se envía como text/html, así que se usa marcado y no texto plano (los saltos de línea se convierten en espacios y < se interpreta como una etiqueta). El resultado de las expresiones de valor interpoladas se escapa como HTML salvo que autoEscape sea false. Se usa exactamente uno de body o templateBody
templateBodyConstruye el cuerpo a partir de una plantilla. Tiene la forma { source, data } y se usa exactamente uno entre este campo y body. Véase Crear el cuerpo con una plantilla más abajo
autoEscape(opcional, true por omisión) Si se escapan como HTML los valores interpolados en el cuerpo. Afecta al resultado de los { /pointer } de body y a los valores de templateBody.data. Con false, el marcado contenido en los valores se renderiza tal cual. El marcado escrito por la persona que redacta el Script y la plantilla de templateBody.source no son objeto de este escape, y los campos de cabecera tampoco se ven afectados
replyTo(opcional) Cabecera Reply-To (expresión de valor). Puede ser distinta del remitente (por ejemplo, enviar desde no-reply pero que las respuestas vayan a una dirección de soporte)
timeoutMs(opcional, 1 o más) El tiempo de espera de este envío (ms). Si no lo hay, el valor predeterminado de la plataforma; un valor que supere el límite superior se rechaza al guardar
  • El cuerpo se escribe con body o con templateBody. Si se indican los dos o se dejan los dos vacíos, se rechaza al guardar.
  • Los valores que envía quien llama se renderizan como texto por omisión. Con autoEscape activado, el marcado contenido en un valor, como un <a>, se ve como texto y no como una etiqueta. Es el mecanismo que impide que un valor recibido de otra persona inserte enlaces o etiquetas en el correo de alguien. Solo se deja en false cuando hay que conservar el marcado, como en el cuerpo de una newsletter redactada por una persona administradora.
  • En los campos de cabecera no se pueden poner valores con saltos de línea. Afecta a subject, a replyTo y a todas las direcciones. Si entra un salto de línea, la ejecución falla. En el cuerpo los saltos de línea son contenido normal, así que no se comprueban.
  • El total de destinatarios es de 50 como máximo. Se cuentan sumando to (1), cc y bcc (en el sobre SMTP no hay distinción de cc/bcc y todos salen como destinatarios, así que se cuentan en total). Si se supera, se rechaza al guardar y al ejecutar. Para enviar a muchas personas, se envía 1 correo por elemento con ResourceForEach + EmailSend.
  • No vincula un resultado. El éxito solo significa «el proveedor aceptó el correo», así que no hay valor que devolver y no recibe name. Tampoco reintenta (el correo no es idempotente, así que reintentar tras un fallo ambiguo produce un envío duplicado; por eso no sigue el retry de Http). El fallo se lanza (throw) y se maneja con el catch de Try.
  • Es una llamada externa. Cuenta para el límite de llamadas externas por plan, y en el presupuesto de tiempo se contabiliza como un único timeoutMs (10 segundos si no lo hay), ya que, al no reintentar, no se multiplica por un número de veces como en Http. Se puede usar dentro del onEach de ResourceForEach (la forma estándar del envío múltiple).
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
  "to": "{ /order/fields/email/en-US }",
  "subject": "Pedido recibido (número de pedido { /order/sys/id })",
  "body": "<p>Hemos recibido tu pedido. Te avisaremos de nuevo cuando comience el envío.</p>",
  "replyTo": "support@my-shop.example" }

Crear el cuerpo con una plantilla (templateBody)

Si el cuerpo del correo se escribe directamente en la definición del Script, hay que modificar el Script cada vez que se retoca el texto. templateBody permite guardar la plantilla del cuerpo en un Content y rellenar solo sus huecos, de modo que el texto se edita en el estudio de contenidos y el Script se queda como está.

ClaveDescripción
sourceUn único puntero que señala el lugar donde está la plantilla (obligatorio). Debe apuntar a un Content leído por un ResourceRead o un ResourceFind de este mismo Script, y no se pueden escribir otros caracteres delante ni detrás de ese puntero. El valor tiene que ser una cadena, así que en un Field multilingüe se indica también la configuración regional (/tpl/fields/html/en-US)
dataValores con los que se rellenan los huecos de la plantilla (opcional). El nombre de cada clave es el nombre del hueco. Como es un lugar de datos, para usar una operación hay que anteponer $ al operador ($cat), y los huecos cuyo nombre empieza por $ se escriben con $$ (Lugar de datos y lugar de expresión)

Los huecos de la plantilla se escriben como { /<clave> }. Esos huecos solo leen data: la plantilla no puede leer directamente /payload ni /secrets. Un hueco que señale una clave que no está en data se renderiza como texto vacío. Lo mismo ocurre con una clave cuyo valor es null. Es lo contrario de lo que pasa en body, donde un puntero que señala a null imprime el texto null, así que conviene tenerlo presente al pasar de un método al otro.

Si en la plantilla hay un lugar donde a { le sigue / (también cuando hay un espacio entre medias, por ejemplo a{/*comentario*/} en CSS), se lee como un hueco, así que se escribe \{.

Hay tres puntos que pasan el guardado pero cortan la ejecución. Si source se resuelve como un objeto (cuando se omite la configuración regional en un Field multilingüe), falla. Si source queda vacío, falla (porque el ResourceFind no encontró nada, o porque el Statement que lee la plantilla va después del EmailSend). Y si el resultado de rellenar todos los huecos son solo espacios, también falla.

La plantilla a la que apunta source no se escapa. Lo que se escapa son los valores de data. Por eso, para que el HTML enviado por quien llama se conserve en el correo, ese valor se pone en data y se deja autoEscape en false.

{ "type": "ResourceRead", "resource": "Content", "name": "tpl",
  "target": { "sys": { "id": "cnt_mail_shipping" } } },
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
  "toServiceUser": { "sys": { "id": "{ /order/fields/buyer/en-US/sys/id }" } },
  "subject": "Tu pedido ya está en camino",
  "templateBody": {
    "source": "{ /tpl/fields/html/en-US }",
    "data": { "name": "{ /order/fields/buyerName/en-US }",
              "tracking": "{ /order/fields/trackingNo/en-US }" } } }

La plantilla (el Field html de cnt_mail_shipping) tiene esta forma.

<p>{ /name }, el envío de tu pedido ya ha comenzado.</p>
<p>Número de seguimiento: { /tracking }</p>

Variables

SetVar

Declara o actualiza una variable mutable de ámbito de script. Se referencia con { /vars/<var> } (como JsonLogic no tiene declaración de variables, se ofrece como Statement).

CampoDescripción
varEl nombre de la variable. Se referencia con { /vars/<var> }
valueExpresión de valor. Puede acumular haciendo referencia a sí misma
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "$+": [ "{ /vars/total }", "{ /row/qty }" ] } }   // acumulación
{ "type": "SetVar", "var": "ids",   "value": { "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } }  // recolección en array

Caché

Cache

Lee y escribe en la caché de vida corta propia de ese Script. Es el lugar en el que se retiene durante unos segundos un valor que no compensa volver a traer cada vez, como el resultado de una llamada externa, para reutilizarlo en la llamada siguiente. Como no es una llamada externa, no se incluye en el número de llamadas externas por definición y tampoco declara ningún tiempo en el presupuesto de tiempo.

CampoDescripción
actionUno de "Set" (escritura), "Get" (lectura) o "Delete" (eliminación) (obligatorio)
keyLa clave de caché (Cache Key) (obligatorio). No es una expresión de valor, sino un literal (véase más abajo). Son 128 caracteres como máximo y, si se superan, el guardado se rechaza
valueEl valor que se va a guardar (exclusivo de Set)
ttlEl tiempo que la caché permanece viva (exclusivo de Set, en segundos). Está entre 1 y 30 y, si se omite, es 5
defaultValueEl valor que Get vincula cuando no hay datos en caché (exclusivo de Get). Si se omite, es null
nameEl nombre en el que se guarda el resultado. En Get es obligatorio (si el valor leído no tiene adónde ir, no hay razón para leerlo). Set vincula el valor guardado y Delete, si se ha llevado a cabo la eliminación; en ambos es opcional
  • Solo se escriben los campos que corresponden a la operación. Si se pone ttl en un Get o defaultValue en un Set, el guardado se rechaza.
  • No se distingue lo que no existe de lo que ha expirado. En ambos casos se vincula defaultValue. Lo mismo ocurre cuando lo que se ha guardado es un null.
  • El ámbito de almacenamiento es ese único Script. Otros Script del mismo Space no ven los datos ajenos aunque usen la misma clave de caché. Si ese Script se modifica o se elimina, todos sus datos desaparecen.
  • La key es un literal. Dejar que los datos se elijan con una clave de caché que llega en la petición pondría en manos del llamante qué se lee, y un Script que ha guardado un dato por cada miembro acabaría entregando el valor de un miembro a otro. Por eso, si key contiene un { /pointer }, no se convierte en valor ni se usa carácter a carácter: el guardado en sí se rechaza.
  • No se puede colocar dentro de una iteración. Si hay un Cache dentro del bloque de un Loop o de un ResourceForEach, el guardado se rechaza. Es porque el límite de cantidad de más abajo no supone ninguna restricción dentro de una iteración: como en cada vuelta se escribe un dato, el número de Statement escritos en la definición y el número de claves de caché que realmente se usan no coinciden.
  • Se pueden poner hasta 5 por definición (anidados incluidos, sumando con independencia de la operación). Si se supera, el guardado se rechaza.
  • El valor que se guarda es de 10.240 bytes (10KiB) como máximo. Si se supera, ese Statement falla (status 422). Es como cualquier otro fallo en tiempo de ejecución, así que se puede tratar de forma local con Try/catch.
// Reutiliza el tipo de cambio durante 30 segundos.
{ "type": "Cache", "action": "Get", "name": "cached", "key": "rates" }
 
// Si hay un valor retenido, lo devuelve tal cual sin llamada 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 antes de que expire el valor que se tenía retenido
{ "type": "Cache", "action": "Delete", "key": "rates" }

Parseo de valores

ParseJson

Parsea un texto JSON al valor que representa y lo vincula a un nombre. Se usa para el cuerpo recibido de Http con responseType: "Text", para una cadena JSON que llega en el payload, o para JSON guardado como cadena en un campo. Como no es una llamada externa, no cuenta para el límite de llamadas externas y tampoco declara ningún tiempo en el presupuesto de tiempo.

CampoDescripción
nameEl nombre en el que se vincula el valor parseado (obligatorio). En los demás Statement es opcional, pero aquí es obligatorio. El Statement no hace nada más que vincular su resultado, así que uno sin nombre no tiene ningún efecto
valueEl texto JSON que se va a parsear (expresión de valor, obligatorio). Apunte a un valor de un paso anterior, como en { /resp/body }, o escriba el texto JSON tal cual como literal (una { dentro del literal no se interpreta como plantilla { puntero })
  • Resultado: el valor parseado en sí. Un objeto sigue siendo un objeto, un array sigue siendo un array, y un valor único como 42 o "a" también se parsea. Después se apunta al interior con { /<name>/... }.
  • Si llega un valor ya parseado, se vincula tal cual. Cuando value se resuelve en algo que no es una cadena, no hay texto que parsear, así que ese valor se vincula como está.
  • Un { /pointer } dentro del texto parseado no se vuelve a resolver. Aunque una cadena recibida del exterior contenga una expresión como { /payload/... }, no se sustituye por un valor y se queda como texto.
  • null cubre dos casos distintos. Si el texto que se va a parsear es solo la palabra null, es normal y el resultado también es null. En cambio, si el lugar al que apunta value está vacío y no hay valor alguno, no hay nada que parsear y el Statement falla.
  • Fallo: cuando value se resuelve sin valor o solo con espacios, y cuando el texto no es JSON. Se trata con Try/catch como cualquier otro fallo en ejecución, y el mensaje de error lleva el texto que intentó parsear.
  • Cuenta como un Statement en el número de Statement por definición, pero no tiene relación con el límite de llamadas externas ni con el límite de SetVar.
// 1) Una API que no devuelve JSON: recibir como Text y parsear
{ "type": "Http", "method": "GET", "url": "https://api.partner.example/v1/quote",
  "responseType": "Text", "name": "resp" },
{ "type": "ParseJson", "name": "quote", "value": "{ /resp/body }" },
 
// 2) Parsear una cadena JSON que llegó en el payload
{ "type": "ParseJson", "name": "spec", "value": "{ /payload/fields/specJson }" }

Verificación de firma y tratamiento de texto

Son los Statement que comprueban la firma que una pasarela de pago ha enviado por webhook y que desempaquetan la cadena en la que viene envuelta esa firma. Los tres son cálculos, no llamadas externas, así que no cuentan para el límite de llamadas externas ni declaran ningún tiempo en el presupuesto de tiempo; y, al no tener posiciones de datos, son ajenos a la regla del prefijo $. Un ejemplo completo que combina los tres está en la verificación de la firma de un webhook del Cookbook.

Los tres Statement tienen un límite superior en la longitud del valor resuelto. No es la longitud de la expresión, sino la del valor al que esa expresión apunta (los dieciséis caracteres de { /rawPayload } pueden apuntar a decenas de KB), y si se supera, la ejecución falla y se puede gestionar con Try. Las cifras están reunidas en Límites de longitud de los valores.

Signature

Comprueba si el código de firma recibido coincide con el código generado con secret y vincula esa respuesta como un valor Boolean. La firma que una pasarela de pago (PG, MoR) envía por webhook se verifica con este Statement.

CampoDescripción
nameNombre en el que se guarda el resultado de la verificación (obligatorio). { /<name> } es true o false. Verificar y no usar el resultado equivale a no haber verificado, así que no se puede omitir
algorithmEl hash con el que se genera el código (obligatorio). SHA1, SHA256, SHA384, SHA512
secretLa clave secreta compartida con la contraparte (expresión de valor, obligatorio). La clave se declara en secrets y se escribe como { /secrets/<key> }
secretEncodingEn qué notación está la cadena de la clave a la que apunta secret: Utf8 (por defecto, clave de texto), Hex o Base64. Dejar como texto una clave emitida en hex o en base64 la convierte en otra clave, de modo que se genera un código verosímil que no coincidirá nunca
valueEl mensaje sobre el que se calcula el código (expresión de valor, obligatorio). Debe ser literalmente igual a los bytes que firmó la contraparte, así que suele ser { /rawPayload }, o eso mismo con la marca de tiempo que el proveedor envía en la cabecera puesta delante
expectedEl código que envió el llamante (expresión de valor, obligatorio). Por ejemplo: { /headers/x-signature }
  • Resultado: es un Boolean. Después se usa { /<name> } tal cual en la condición de un If.
  • El value se escribe con /rawPayload, no con el /payload parseado. Al volver a convertir en cadena el payload parseado, los espacios en blanco, la notación de los números y los escapes quedan normalizados, y ya no se recuperan los bytes que firmó la contraparte (raíces de contexto).
  • No hay ningún campo que indique la notación de salida. algorithm fija la longitud en bytes del código y, a igual longitud, las longitudes de cadena de hex y de base64 no se solapan, así que el motor reconstruye los bytes sin que la contraparte tenga que avisar en cuál de las dos lo ha enviado. Por la misma razón, tampoco distingue las mayúsculas y minúsculas del hex, ni base64 de base64url (incluido si llevan relleno o no).
  • Lo que separa un fallo de un false es quién aporta ese valor.
    • Si no hay expected o el código no coincide, el resultado es solo false, no un fallo. Informar por separado de la ausencia de la cabecera y de la discrepancia del código enseñaría a quien envía cuál de las dos cosas está mal.
    • Si value viene vacío, se calcula con un mensaje vacío. Un cuerpo vacío también es objeto de firma.
    • Si no hay secret, o no está en la notación que declara secretEncoding, es un fallo. De los tres, esta es la única entrada del propio autor. El mensaje de fallo no lleva ni secret ni value.
  • El límite superior de value es de 65.536 caracteres (sobre el valor resuelto). Es una cifra ajustada al tamaño de cuerpo de webhook que envían los proveedores reales.
  • La comparación determina la igualdad de los valores en constant-time. Cuántos bytes iniciales han coincidido no se filtra a través del tiempo de respuesta.
  • La clave de firma no se escribe tal cual en secret. Se declara en los secrets de la definición y se escribe como "{ /secrets/<key> }". WEEGLOO cifra y almacena de forma segura los valores de secrets. secretEncoding indica en qué notación está la cadena de la clave guardada en secrets.
// Un proveedor que firma todo el cuerpo
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "{ /secrets/WEBHOOK_SIGNING_SECRET }", "value": "{ /rawPayload }",
  "expected": "{ /headers/x-webhook-signature }" }
 
// Un proveedor que emite la clave en base64 (la cadena base64 se guarda tal cual en secrets)
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "{ /secrets/WEBHOOK_SIGNING_KEY }", "secretEncoding": "Base64",
  "value": "{ /rawPayload }", "expected": "{ /headers/webhook-signature }" }

Hash

Calcula el digest de value y lo vincula como cadena en la notación que fija encoding. Se usa para reproducir esquemas de firma que no son HMAC, sino del tipo "concatenar unos cuantos campos y la clave secreta y calcular su SHA256".

CampoDescripción
nameNombre en el que se guarda el digest (obligatorio)
algorithmMD5, SHA1, SHA256, SHA384, SHA512 (obligatorio). MD5 está para reproducir esquemas antiguos que lo exigen, no es un valor que elegir para una firma nueva
valueEl mensaje del que se calcula el digest (expresión de valor, obligatorio)
encodingNotación del resultado. Hex (por defecto), HexUpper, Base64, Base64Url
  • No hay campo secret. Dentro de value se escribe { /secrets/<key> } en la posición en la que ese esquema coloca la clave. Cada esquema pone la clave delante, detrás o en medio, y así se puede expresar cualquier posición. La clave se declara de antemano en secrets.
  • Resultado: es una cadena. Para compararlo con el código que envió la contraparte se escribe { "==": [ "{ /<name> }", "{ /headers/... }" ] }. Esta comparación, a diferencia de la comparación en constant-time de Signature, es una comparación de igualdad normal.
  • Si value se resuelve sin valor o solo con espacios en blanco, es un fallo (por ser una expresión del propio autor).
  • El límite superior de value es de 128 caracteres. Al ser el lugar en el que se ponen unos cuantos campos concatenados, es mucho más estrecho que en Signature. Si hay que calcular sobre todo el cuerpo de un webhook, se usa Signature.
// SHA256(número de pedido + importe + MERCHANT_KEY) en hex mayúsculas
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
  "value": "{ /payload/orderId }{ /payload/amount }{ /secrets/MERCHANT_KEY }" }

Regex

Aplica pattern a value y vincula lo que pide mode. Como las expresiones de valor no tienen ningún medio para cortar cadenas (solo hay cat, que concatena, e in, que comprueba la inclusión), este Statement se usa para desempaquetar varios valores que vienen envueltos en una sola cabecera, como en t=…,v1=….

CampoDescripción
nameNombre en el que se guarda el resultado (obligatorio). Con Capture, los elementos se apuntan como { /<name>/1 }
mode"Match" vincula si hay coincidencia como Boolean; "Capture" vincula la primera coincidencia como array (obligatorio)
patternLa expresión regular (obligatorio). No es una expresión de valor, sino un literal (véase más abajo). Las banderas se escriben dentro del patrón, como (?i). Son 128 caracteres como máximo y, si se superan, el guardado se rechaza
valueEl texto al que se aplica el patrón (expresión de valor, obligatorio). Si el valor resuelto supera los 10.240 caracteres (10KiB), la ejecución falla
  • Resultado: con Match, un Boolean; con Capture, un array o null. En el array, el índice 0 es la coincidencia completa y desde 1 están los grupos de captura, y los grupos que no han participado son null (no una cadena vacía: eso sería haber coincidido). Si el patrón no aparece, Capture no es un array vacío, sino null.
  • Los dos modos preguntan "¿aparece el patrón en alguna parte?". Si el texto completo debe ser igual al patrón, se fija con ^…$. La pregunta se ha dejado igual en ambos para que los dos Statement, el que comprueba con Match y el que extrae con Capture, no den respuestas distintas.
  • pattern es uno de los tres únicos campos de este motor que no son expresiones de valor (los otros son la key de Cache y el color de PdfWatermark). Ejecutar tal cual un patrón que llega en la petición dejaría que el llamante eligiera la expresión que se va a ejecutar, y el backtracking de las expresiones regulares lo convierte en un medio de denegación de servicio. Por eso, tampoco un { /pointer } dentro del patrón se convierte en valor: pasa a formar parte del patrón carácter a carácter.
  • El patrón se compila una sola vez para toda la definición, al arrancar la ejecución. Aunque esté dentro de un Loop o de un ResourceForEach, no se recompila en cada iteración, y un patrón inservible falla antes de que el primer Statement haga nada (se puede gestionar con Try).
// Desempaqueta "t=1492774577,v1=<hex de 64 caracteres>" en { /sig/1 } = marca de tiempo, { /sig/2 } = código
{ "type": "Regex", "name": "sig", "mode": "Capture",
  "pattern": "^t=(\\d+),v1=([0-9a-f]{64})$", "value": "{ /headers/x-provider-signature }" }
 
// Comprueba solo el formato
{ "type": "Regex", "name": "isOrderId", "mode": "Match",
  "pattern": "^ORD-\\d{8}-\\d{4}$", "value": "{ /payload/orderId }" }

Flujo de control

If

Es una bifurcación condicional. condition es JsonLogic, y lo verdadero y lo falso siguen las reglas de evaluación de verdadero y falso.

CampoDescripción
conditionJsonLogic (se evalúa como boolean)
thenArray de Statement a ejecutar cuando es verdadero
else(opcional) Array de Statement a ejecutar cuando es falso
{ "type": "If",
  "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
  "then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" } } ],
  "else": [ /* ... */ ] }

Loop

Es un bucle. Se elige un solo modo: over (foreach), while (condición) o for (recuento). En cualquier modo, el motor impone un límite superior de iteraciones (para evitar bucles infinitos). El límite se declara con maxIterations y, si no se indica, se aplica el límite superior de la plataforma. Dentro de body también se pueden colocar llamadas externas (Http, EmailSend) e ingesta de archivos de Media, y los Statement de llamada externa se llaman realmente en cada iteración durante la ejecución. El límite máximo de llamadas externas por definición se aplica igualmente.

En el presupuesto de tiempo se contabiliza como una multiplicación. El tiempo que declara este Statement es el tiempo declarado por body multiplicado por maxIterations (10.000 si no lo hay) (presupuesto de tiempo). Si en body no hay llamadas externas, el tiempo declarado es 0, así que el límite efectivo es el presupuesto básico de 30 segundos.

CampoDescripción
overforeach: expresión de valor que se resuelve como array
whilecondición: JsonLogic (repite mientras sea verdadero)
forrecuento: { "from", "to", "step"? }. Desde from hasta to inclusive, step por defecto 1
maxIterationsNúmero máximo de iteraciones (opcional). Si no se indica, se aplica el límite superior de la plataforma, 10.000, y un valor mayor se rechaza al guardar
name(opcional) Nombre al que se vincula el elemento actual (foreach) o el índice (while, for) ({ /<name> })
bodyArray de Statement del cuerpo del bucle
// 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

Ejecuta las ramas en paralelo y continúa tras la unión (join). No se puede hacer referencia entre ramas (si hay dependencias, se colocan de forma secuencial).

CampoDescripción
branchesStatement[][]. Cada elemento es una rama (array de Statement)
{ "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

Es el return habitual de la programación. Devuelve el resultado del Script al llamante y termina de forma normal en ese punto.

CampoDescripción
value(opcional) Expresión de valor a devolver
isErrorPor defecto false. Si es true, value sale como el error de la respuesta (si no, como return)
statusCodeCódigo de estado de la respuesta. Por defecto 200
  • Si no se llega a un Return, no hay valor de retorno. Para devolver un resultado, se especifica value.
  • Al ser una terminación normal y no una excepción o un throw, no es objeto de catch (incluso dentro de un Try termina todo el Script, pero el finally sí se ejecuta).
  • Los guards también se expresan con este Statement. Si se coloca un Return en el then de un If, cuando se incumple la condición se devuelve un valor y no se ejecutan los Statement posteriores. Es uno de los varios 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

Es el manejo de excepciones.

CampoDescripción
bodyArray de Statement a intentar
catch(opcional) Se ejecuta si body falla. Expone { message } en /error (no se incluye en qué Statement se produjo el fallo)
finally(opcional) Se ejecuta siempre, con éxito o con fallo
  • Si catch lo maneja, el Script no se interrumpe. Solo un fallo sin catch interrumpe el Script (incluido el intento de compensación).
  • Qué se considera un «fallo» y los límites de la compensación (compensation) se tratan en Semántica de ejecución, restricciones y seguridad.
{ "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": [ /* siempre se ejecuta */ ] }