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í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 }) |
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 statements 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 las sentencias 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 statements 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) 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 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.
| 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" }(ambos obligatorios). 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" }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.
| Campo | Descripción |
|---|---|
resource | "Content" o "Media" |
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) |
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 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.
| Campo | Descripción |
|---|---|
resource | "Content" o "Media" |
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) |
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 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,ResourceDeletey 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
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 (Lectura del 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 statements 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 statements 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, y en el presupuesto de tiempo se contabiliza como timeoutMs (30 segundos si no lo hay) × (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", "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) |
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 |
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 |
- 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 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 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" }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, 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),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" }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 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.
| Campo | Descripción |
|---|---|
name | El 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 |
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 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.
| 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) |
secretEncoding | En 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 |
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.
- El
secretno se almacena cifrado. A diferencia delsecret: truede las cabeceras deHttp(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".
| 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. Como cada esquema pone la clave delante, detrás o en medio, escribir la clave directamente dentro devalueexpresa 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 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 + 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=….
| 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 statements, el que comprueba conMatchy el que extrae conCapture, no den respuestas distintas. patternes uno de los dos únicos campos de este motor que no son expresiones de valor (el otro es lakeydeCache). 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 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.
| 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 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).
| Campo | Descripción |
|---|---|
branches | Statement[][]. 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.
| 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 statements 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 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
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 statements.
- Descripción general de Script: la estructura de nivel superior y el tiempo concedido a una ejecución.
