Catálogo de statements

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 dos excepciones: el pattern de Regex y la key de Cache (véanse Regex y Cache).

Resumen de statements

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 })
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 statements 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 las sentencias 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 statements 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) 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 statements 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.

Un ServiceUser (el miembro que se ha registrado en el producto) es de solo lectura. Únicamente los tres statements de lectura (ResourceRead, ResourceFind, ResourceForEach) aceptan este valor; si se pone en un statement de escritura, el guardado se rechaza (véase Errores). Las reglas se tratan en Lectura del directorio de miembros.

Escritura de recursos

Todos los statements de escritura tienen propagateEvents (por defecto false). 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" } (ambos obligatorios). 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" }

ResourceUpdate

Reemplaza por completo los campos del Content o Media 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" o "Media"
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)
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 o Media 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" o "Media"
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)
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 statements de escritura de recursos, esa escritura se ejecuta en cada elemento y sí cambia el estado.

Los cuatro statements (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 Lectura del 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.

Lectura del 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 son comunes a los tres statements.

  • Solo se puede leer. ResourceCreate, ResourceUpdate, ResourcePatch, ResourceDelete y los statements 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: en un Script no existe ninguna vía para modificar miembros, así que se rechaza como un statement mal escrito, no como un error de permisos.
  • 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 (Lectura del 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 statements 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 statements 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, y en el presupuesto de tiempo se contabiliza como timeoutMs (30 segundos si no lo hay) × (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", "secret"? }]. value es una expresión de valor. Las cabeceras con secret:true se tratan como de uso exclusivo de CMA (administrador): no se exponen al usuario final y solo se descifran justo antes de la transmisión. 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
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
  • 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 sk-...", "secret": true } ],
  "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" }

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, obligatorio). 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
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 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" }

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 statements 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 statements 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 statements 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 statements 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 statements 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)
secretEncodingEn qué notación se ha escrito 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.
  • El secret no se almacena cifrado. A diferencia del secret: true de las cabeceras de Http (almacenamiento cifrado y descifrado justo antes del envío), queda tal cual se escribió en la definición, así que los roles que pueden leer ese Script ven el valor. Un miembro (ServiceUser) no puede leer la definición de un Script (la autoría y la consulta son exclusivas de CMA).
// Un proveedor que firma todo el cuerpo
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "whsec_9f2c1b7ae4", "value": "{ /rawPayload }",
  "expected": "{ /headers/x-webhook-signature }" }
 
// Un proveedor que emite la clave en base64
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "aGVsbG8td2VlZ2xvbw==", "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. Como cada esquema pone la clave delante, detrás o en medio, escribir la clave directamente dentro de value expresa todas las posiciones.
  • 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 + merchantKey) en hex mayúsculas
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
  "value": "{ /payload/orderId }{ /payload/amount }9f2c1b7ae4" }

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 statements, el que comprueba con Match y el que extrae con Capture, no den respuestas distintas.
  • pattern es uno de los dos únicos campos de este motor que no son expresiones de valor (el otro es la key de Cache). 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 statements a ejecutar cuando es verdadero
else(opcional) Array de statements 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 statements 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 statements 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 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

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 statements 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 statements 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 */ ] }