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ía | type | Resumen en una línea |
|---|---|---|
| Escritura de recursos | ResourceCreate | Crea Content/Media (opcionalmente publica) |
ResourceUpdate | Reemplazo completo de los campos de Content/Media (elimina los field y locale no incluidos) | |
ResourcePatch | Fusión parcial de los campos de Content/Media (solo los field y locale indicados. El null literal elimina) | |
ResourceDelete | Elimina (solo Draft y Archived. Si está Published, primero hay que anular la publicación) | |
ResourcePublish / ResourceUnpublish | Publica / anula la publicación | |
ResourceArchive / ResourceUnarchive | Archiva / desarchiva | |
| Lectura de recursos | ResourceRead | Consulta un único elemento por id |
ResourceFind | Primera coincidencia por filtro (null si no hay) | |
ResourceForEach | Recorre internamente los recursos que coinciden con el filtro y ejecuta onEach en cada elemento | |
ResourceCount | Cuenta solo el número de registros que coinciden con el filtro (no lee los elementos) | |
| Externo | Http | Llamada HTTP externa ({ status, body }). Puede enviar un archivo como cuerpo |
EmailSend | Envía 1 correo con un EmailAccount registrado | |
| Variables | SetVar | Declara/actualiza una variable de ámbito de script |
| Caché | Cache | Lee, escribe o elimina en la caché de vida corta propia de ese Script |
| Parseo de valores | ParseJson | Parsea texto JSON a un valor (objeto, array, escalar) y lo vincula |
| Firma y texto | Signature | Verifica si el código de firma recibido coincide con el código generado con la clave secreta (Boolean) |
Hash | Calcula un digest sin clave (cadena) | |
Regex | Aplica una expresión regular. Si hay coincidencia (Boolean) o los grupos de captura (array) | |
| Flujo de control | If | Bifurcación condicional |
Loop | Bucle (foreach / while / counted) | |
Parallel | Ejecución en paralelo de ramas | |
Return | Devuelve el resultado y sale anticipadamente | |
Try | Manejo 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,ResourceForEachyResourceCount, cuandoresourcees"Content",contentTypees obligatorio. No existe ninguna consulta de Content que atraviese todo el Space.ResourceCreatetambié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,ResourceDeletey los de publicación y archivado) tienentarget, 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 activapropagateEvents(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:
namees 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.idsuele ser un literal (por ejemplo,"ct_post").target.sys.ides 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.
| Campo | Aplica a | Descripción |
|---|---|---|
resource | Común | "Content" o "Media" (obligatorio) |
contentType | Content | El Content Type que se va a crear ({ sys: { id } }). Obligatorio cuando es Content |
fields | Común | Mapa 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) |
locale | Común | (comodidad) Si se indica, envuelve automáticamente cada valor de fields como { <locale>: valor } |
publish | Común | Publica tras la escritura (exposición en CDA/ACDA). Por defecto true |
filedeMedia: el valor defields.file.{locale}es una instrucción de ingesta{ "source": <expresión de valor>, "encoding": "url"|"base64", "transformations": [ … ] }.sourceyencodingson obligatorios, ytransformationssolo 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 esurl, lo descarga; si esbase64, 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). Sipublish:truepero no hay archivo o el procesamiento no ha terminado, se produce un error en la fase de publicación; conpublish:false, queda comoDraft.- 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 ingesta | Obligatorio | Descripción |
|---|---|---|
transformations | Opcional | Array 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,ResourceUpdateyResourcePatch. - El original al que apunta
sourceno cambia. Para estampar un texto en un archivo que ya está en el Space, se lee ese Media conResourceReady se pone{ /<name>/fields/file/<locale>/url }ensourcepara 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
| Campo | Obligatorio | Descripción |
|---|---|---|
type | Obligatorio | "PdfWatermark". Con cualquier otro valor, se rechaza el guardado del Script |
text | Obligatorio | El 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 |
color | Opcional | Color 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).
| Tipo | Caracteres |
|---|---|
| Caracteres imprimibles ASCII | El espacio, las letras del alfabeto inglés en mayúsculas y minúsculas, los dígitos y símbolos básicos como @ . # { } |
| Caracteres Latin-1 | Letras 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ón | text literal | text con { /pointer } |
|---|---|---|
| Una sola línea compuesta por caracteres admitidos | Se rechaza el guardado del Script | Al 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 caracteres | Se rechaza el guardado del Script | No 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ón | Resultado |
|---|---|
El archivo de source no es un PDF | Falla 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 espacios | Falla con status 400 |
El text resuelto no tiene entre 1 y 64 caracteres o contiene caracteres no admitidos | Falla 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.
| Campo | Descripció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 |
target | Destino ({ sys: { id } }, obligatorio). El id suele ser { /ptr } |
fields | Todos 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 |
publish | Vuelve 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.
| Campo | Descripció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 |
target | Destino ({ sys: { id } }, obligatorio). El id suele ser { /ptr } |
fields | Los 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 |
publish | Vuelve a publicar tras la actualización. Por defecto true |
- Eliminar un locale o un archivo concreto: se pasa un
nullliteral 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
filede 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).
| Campo | Descripción |
|---|---|
resource | "Content" o "Media" |
target | Destino ({ 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.
| Campo | Descripción |
|---|---|
resource | "Content" o "Media" |
target | Destino ({ 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.
ResourceUpdateyResourcePatchaceptan"ServiceUser"y cambianroleOverrideyenableLogin. Si enResourcePatchse pasanullaroleOverride, se quita el rol que estaba asignado.ResourceCreateyResourceDelete, 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
versionnipropagateEvents. Si se indica cualquiera de los dos, la ejecución falla. ComoResourceUpdatereemplaza por completo los campos que se pueden escribir,enableLogines obligatorio, y si no se indicaroleOverridese quita el rol que estaba asignado. Para cambiar solo uno de los dos se usaResourcePatch, 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
settingsdel SpaceRole del autor tieneSETTING_SERVICE_LOGIN(oSETTING_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). fromsolo aceptaCurrent. Un miembro no es un recurso que se publique, así que, si se pasaPublished, la ejecución falla.contentTypeyadvancedse 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.emaildewheresolo acepta operadores de coincidencia exacta (eq,ne,in,nin). La dirección del miembro se almacena cifrada, así que las comparaciones de orden oprefixno 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 susys.iden eltoServiceUserdeEmailSend(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.
| Campo | Descripción |
|---|---|
resource | "Content", "Media" o "ServiceUser" |
target | Destino ({ 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
namea 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).
| Campo | Descripción |
|---|---|
resource | "Content", "Media" o "ServiceUser" |
contentType | El Content Type que define el ámbito de búsqueda ({ sys: { id } }). Obligatorio cuando es Content. En Media y ServiceUser se ignora |
where | Filtro ({ "<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) |
order | Orden 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
namede este Statement el recurso de la primera coincidencia. Se referencia directamente con{ /<name>/fields/<field>/<locale> }. Como esnullcuando 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).
| Campo | Descripción |
|---|---|
resource | "Content", "Media" o "ServiceUser" (obligatorio) |
contentType | El Content Type que define el ámbito del recorrido ({ sys: { id } }). Obligatorio cuando es Content. En Media y ServiceUser se ignora |
where | Filtro ({ "<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" |
order | Orden (por ejemplo, "sys.createdAt,sys.id"). Si no lo hay, el orden predeterminado de la plataforma |
from | Current (por defecto, el borrador más reciente) o Published (la instantánea publicada). Un ServiceUser solo admite Current |
advanced | Recorrer 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 |
onEach | Array de Statement hijos a ejecutar en cada elemento (obligatorio) |
- No vincula una colección (
foreach, nomap). No hay{ items, next }ni cursor. No se devuelve el resultado del recorrido como valor, sino que se ejecutaonEachen cada elemento. Si se necesita la lista, se recopila directamente conSetVar. Si solo hace falta el número, se usaResourceCount. - 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 ellimitdeclarado es una parada intencionada, así que es una terminación normal. Unlimitque 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 elwherecomo «no procesado» y al final deonEachse 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
onEachmultiplicado 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 deonEachlos 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 elbodydeLoop). 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.
| Campo | Descripción |
|---|---|
resource | "Content" o "ContentType" (obligatorio). Media y ServiceUser no se pueden contar, y escribirlos así hace que el guardado se rechace |
contentType | El 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) |
where | Filtro. 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
namede 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) oResourceForEach(ejecutar en cada elemento). - No cuente recorriendo con
ResourceForEachpara 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
ordernilimit. 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).
| Campo | Descripción |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | La 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) |
body | El cuerpo de la petición (expresión de valor o JSON). En qué formato se envía lo fija la cabecera Content-Type |
urlBody | La 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) |
multipartBody | Array 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) |
timeoutMs | El tiempo de espera de esta llamada (ms) |
retry | Número de reintentos cuando el status de la respuesta es 400 o superior. Por defecto 0; el máximo es 2 |
ignoreStatusCode | Determina 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) |
responseType | En 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,urlBodyomultipartBody. 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 unnamea este Statement,{ /<name>/status },{ /<name>/body/... }. La forma debodyla fijaresponseType. responseTypesolo 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 deTry/catch). Para las API que no devuelven JSON, reciba el cuerpo como"Text"y parséelo conParseJsoncuando necesite usarlo como valor. "Text"se decodifica con el charset delContent-Typede la respuesta y se toma como UTF-8 cuando no hay charset. Si el cuerpo está vacío,bodyesnullen 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 conignoreStatusCode).
{ "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 declarado | Forma del body | body que sale |
|---|---|---|
application/json | Cualquiera | JSON |
application/x-www-form-urlencoded | Objeto o array | order[id]=A-2481&order[amount]=34000 |
text/plain | Escalar | El valor tal cual |
Otros (text/xml, etc.) | Cualquiera | JSON |
Estas son las combinaciones que se corrigen porque no se pueden contener en el formato declarado.
Content-Type declarado | Forma del body | Content-Type que sale realmente | body que sale |
|---|---|---|---|
application/x-www-form-urlencoded | Escalar | text/plain;charset=UTF-8 | El valor tal cual |
text/plain | Objeto o array | application/json | JSON |
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.
body | Claves 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.
- Si se ha indicado
Content-Typeenheaders, se usa ese valor tal cual. No se aplica la corrección de la serialización del body. - Si no se ha indicado o su valor está vacío, se usa el
Content-Typecon el que respondió la URL del archivo. - 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 parte | Descripción |
|---|---|
name | Nombre 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 |
value | Contenido de una parte de texto (expresión de valor). Si el resultado está vacío, se envía como valor vacío |
url | URL 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 |
fileName | Nombre de archivo que va en el Content-Disposition de la parte (expresión de valor). Solo se puede usar en partes con url |
contentType | El Content-Type de la parte (expresión de valor) |
- Se indica exactamente uno de
valueyurl. Si se indican los dos o no se indica ninguno, el guardado se rechaza. IndicarfileNameen una parte convaluetambién hace que el guardado se rechace. - Si se omite
fileName, se usa el último segmento de la ruta deurl, decodificado (sin la cadena de consulta). Si ese segmento está vacío o contiene caracteres de control, se envía comofile. - Si se omite
contentType, una parte convaluesale sinContent-Type, y una parte conurlusa elContent-Typecon el que respondió la URL del archivo (application/octet-streamsi no lo hay). Si se indica un valor que no tiene formato de tipo de medio, eseHttpfalla con status 400 al ejecutarse. - Si el resultado de la expresión de valor de
fileNameo decontentTypecontiene un carácter de salto de línea, eseHttpfalla 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ón | Resultado |
|---|---|
| El archivo que se va a descargar supera el límite de descarga del plan | Falla 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 archivo | Falla con status 502 |
El contentType de una parte no tiene formato de tipo de medio | Falla con status 400 |
El valor de fileName o contentType de una parte contiene un carácter de salto de línea | Falla con status 400. Falla antes de enviar la petición |
- El límite de descarga se aplica a cada URL de archivo. Si
multipartBodytiene 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.
| Campo | Descripción |
|---|---|
account | Referencia 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 |
to | Dirección del destinatario (expresión de valor). Se usa exactamente uno de to o toServiceUser |
toServiceUser | Indica 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 |
cc | Array de direcciones en copia (expresión de valor) |
bcc | Array de direcciones en copia oculta (expresión de valor) |
subject | Asunto (expresión de valor, obligatorio) |
body | Cuerpo (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 |
templateBody | Construye 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
bodyo contemplateBody. 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
autoEscapeactivado, 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 enfalsecuando 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, areplyToy 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),ccybcc(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 conResourceForEach+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 elretrydeHttp). El fallo se lanza (throw) y se maneja con elcatchdeTry. - 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 enHttp. Se puede usar dentro delonEachdeResourceForEach(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á.
| Clave | Descripción |
|---|---|
source | Un ú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) |
data | Valores 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).
| Campo | Descripción |
|---|---|
var | El nombre de la variable. Se referencia con { /vars/<var> } |
value | Expresió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 arrayCaché
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.
| Campo | Descripción |
|---|---|
action | Uno de "Set" (escritura), "Get" (lectura) o "Delete" (eliminación) (obligatorio) |
key | La 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 |
value | El valor que se va a guardar (exclusivo de Set) |
ttl | El tiempo que la caché permanece viva (exclusivo de Set, en segundos). Está entre 1 y 30 y, si se omite, es 5 |
defaultValue | El valor que Get vincula cuando no hay datos en caché (exclusivo de Get). Si se omite, es null |
name | El 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
ttlen unGetodefaultValueen unSet, 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 unnull. - 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
keyes 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, sikeycontiene 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
Cachedentro del bloque de unLoopo de unResourceForEach, 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.
| Campo | Descripción |
|---|---|
name | El 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 |
value | El 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
42o"a"también se parsea. Después se apunta al interior con{ /<name>/... }. - Si llega un valor ya parseado, se vincula tal cual. Cuando
valuese 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. nullcubre dos casos distintos. Si el texto que se va a parsear es solo la palabranull, es normal y el resultado también esnull. En cambio, si el lugar al que apuntavalueestá vacío y no hay valor alguno, no hay nada que parsear y el Statement falla.- Fallo: cuando
valuese resuelve sin valor o solo con espacios, y cuando el texto no es JSON. Se trata conTry/catchcomo 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.
| Campo | Descripción |
|---|---|
name | Nombre 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 |
algorithm | El hash con el que se genera el código (obligatorio). SHA1, SHA256, SHA384, SHA512 |
secret | La clave secreta compartida con la contraparte (expresión de valor, obligatorio). La clave se declara en secrets y se escribe como { /secrets/<key> } |
secretEncoding | En 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 |
value | El 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 |
expected | El 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 unIf. - El
valuese escribe con/rawPayload, no con el/payloadparseado. 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.
algorithmfija 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
falsees quién aporta ese valor.- Si no hay
expectedo el código no coincide, el resultado es solofalse, 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
valueviene 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 declarasecretEncoding, es un fallo. De los tres, esta es la única entrada del propio autor. El mensaje de fallo no lleva nisecretnivalue.
- Si no hay
- El límite superior de
valuees 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 lossecretsde la definición y se escribe como"{ /secrets/<key> }". WEEGLOO cifra y almacena de forma segura los valores desecrets.secretEncodingindica en qué notación está la cadena de la clave guardada ensecrets.
// 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".
| Campo | Descripción |
|---|---|
name | Nombre en el que se guarda el digest (obligatorio) |
algorithm | MD5, SHA1, SHA256, SHA384, SHA512 (obligatorio). MD5 está para reproducir esquemas antiguos que lo exigen, no es un valor que elegir para una firma nueva |
value | El mensaje del que se calcula el digest (expresión de valor, obligatorio) |
encoding | Notación del resultado. Hex (por defecto), HexUpper, Base64, Base64Url |
- No hay campo
secret. Dentro devaluese 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 ensecrets. - 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 deSignature, es una comparación de igualdad normal. - Si
valuese 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
valuees de 128 caracteres. Al ser el lugar en el que se ponen unos cuantos campos concatenados, es mucho más estrecho que enSignature. Si hay que calcular sobre todo el cuerpo de un webhook, se usaSignature.
// 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=….
| Campo | Descripción |
|---|---|
name | Nombre 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) |
pattern | La 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 |
value | El 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, unBoolean; conCapture, un array onull. En el array, el índice0es la coincidencia completa y desde1están los grupos de captura, y los grupos que no han participado sonnull(no una cadena vacía: eso sería haber coincidido). Si el patrón no aparece,Captureno es un array vacío, sinonull. - 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 conMatchy el que extrae conCapture, no den respuestas distintas. patternes uno de los tres únicos campos de este motor que no son expresiones de valor (los otros son lakeydeCachey elcolordePdfWatermark). 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
Loopo de unResourceForEach, no se recompila en cada iteración, y un patrón inservible falla antes de que el primer Statement haga nada (se puede gestionar conTry).
// 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.
| Campo | Descripción |
|---|---|
condition | JsonLogic (se evalúa como boolean) |
then | Array 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.
| Campo | Descripción |
|---|---|
over | foreach: expresión de valor que se resuelve como array |
while | condición: JsonLogic (repite mientras sea verdadero) |
for | recuento: { "from", "to", "step"? }. Desde from hasta to inclusive, step por defecto 1 |
maxIterations | Nú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> }) |
body | Array 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).
| Campo | Descripción |
|---|---|
branches | Statement[][]. 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.
| Campo | Descripción |
|---|---|
value | (opcional) Expresión de valor a devolver |
isError | Por defecto false. Si es true, value sale como el error de la respuesta (si no, como return) |
statusCode | Có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 especificavalue. - Al ser una terminación normal y no una excepción o un throw, no es objeto de
catch(incluso dentro de unTrytermina todo el Script, pero elfinallysí se ejecuta). - Los guards también se expresan con este Statement. Si se coloca un
Returnen elthende unIf, cuando se incumple la condición se devuelve un valor y no se ejecutan los Statement posteriores. Es uno de los varios usos deReturn.
{ "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.
| Campo | Descripción |
|---|---|
body | Array 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
catchlo maneja, el Script no se interrumpe. Solo un fallo sincatchinterrumpe 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 */ ] }Documentos relacionados
- Expresiones de valor: las reglas de valor que siguen todos los campos anteriores.
- Semántica de ejecución, restricciones y seguridad: orden de ejecución, errores, restricciones estáticas y seguridad.
- Cookbook: ejemplos completos que combinan estos Statement.
- Descripción general de Script: la estructura de nivel superior y el tiempo concedido a una ejecución.
