Saltar al contenido principal

Valores de atributos

OneEntry describe el contenido con atributos: una página, un producto, un bloque, un usuario, un pedido o un campo de formulario contiene un mapa de valores de atributos cuyas claves son los marcadores. El SDK normaliza todos los atributos de todas las respuestas a la misma forma, de modo que un campo tiene el mismo aspecto sea cual sea el módulo que lo devolvió.

const page = await Pages.getPageByUrl('catalog');
if ('statusCode' in page) throw new Error(page.message);

page.attributeValues.title.value; // "Catalog" — string
page.attributeValues.amount.value; // 5 — number
page.attributeValues.cover.value; // { downloadLink } — the file object itself
page.attributeValues.notes.value; // null — no value set

La forma normalizada​

Un valor de atributo es un IAttributeValue: { type, value, position?, additionalFields? }. Lo que contiene value depende de type:

Tipo de atributovalue
string, textstring
integer, float, realnumber, convertido desde la forma de cadena de la API
image, file con un archivoel propio objeto de archivo
image, file con varios archivosun array de objetos de archivo
groupOfImagessiempre un array (por definición es una colección)
listun array
timeIntervalun array de grupos; consulta Intervalos de tiempo
sin valor configuradosiempre null

Los atributos de un solo archivo se desenvuelven​

Cuando un atributo image o file contiene exactamente un archivo, su value es el propio objeto de archivo. Solo los valores con dos o más archivos siguen siendo arrays.

const block = await Blocks.getBlockByMarker('promo');

// before: block.attributeValues.img.value[0].downloadLink
// now: block.attributeValues.img.value.downloadLink

Esto se aplica en todos los módulos.

Migración: las versiones anteriores desenvolvían los archivos únicos solo en products, menus, forms, forms-data, attribute-sets, integration-collections y Pages.searchPage, y solo bajo la clave attributeValues. En el resto de casos (blocks, todos los demás métodos de pages, Products.getProductsEmptyPage, Products.getProductBlockById, admins, discounts, templates, orders, users), el mismo atributo llegaba como un array de un solo elemento, y los attributes de los formularios, los campos de forms-data y los additionalFields anidados nunca se desenvolvían. El código para products o menus ya lee el objeto y no se ve afectado. El código que lee value[0] de blocks, pages, users u orders debe eliminar el índice.

groupOfImages siempre sigue siendo un array. Los envíos siguen sus propias reglas: IBodyTypeFile.value acepta un File/Blob (o una FileList), el descriptor que devuelve FileUploading.upload o un IFileValue, y se pueden mezclar en un mismo array. Un File/Blob suelto sirve, pero un descriptor o un IFileValue debe enviarse dentro de un array, o la API responde con un error 400.

Los números son números​

Los valores integer, float y real se convierten a números. (Las versiones anteriores dejaban real como cadena, de modo que el mismo campo numérico llegaba como 10 o como "10" según cuál de los tres tipos se hubiera declarado).

const page = await Pages.getPageByUrl('catalog');

// before: page.attributeValues.amount.value // "5"
// now: page.attributeValues.amount.value // 5

La normalización numérica también se aplica a los atributos de formulario y a los campos de form-data: un campo rating de un atributo de formulario integer es un number, no una cadena. Las versiones anteriores los omitían.

Al enviar datos, envía una cadena: IBodyTypeStringNumberFloat.value es string | number | null, y las respuestas vuelven normalizadas.

Un valor vacío es siempre null​

La API devuelve un mapa de localización vacío para un valor sin configurar, y el SDK lo convierte en null para todos los tipos. (Las versiones anteriores dejaban pasar el mapa para los tipos de texto y devolvían null para los numéricos, así que "sin valor" tenía tres representaciones).

if (page.attributeValues.notes.value === null) {
// nothing configured for this attribute
}

Migración: un integer/float sin configurar ya no es 0. Number(null) es 0, así que un null explícito de la API se notificaba como un cero real, indistinguible de un 0 configurado.

Todo se ordena por posición​

attributeValues y los attributes de los formularios se devuelven en el orden de position. La API devuelve los campos de formulario sin ordenar (un campo con position: 10 puede llegar después de position: 14); el SDK los ordena para que tú no tengas que hacerlo.

Un formulario sin atributos devuelve attributes: []. En ese caso la API envía un objeto vacío, y el SDK lo normaliza a un array vacío, así que attributes es siempre IFormAttribute[] y form.attributes.map(...) es seguro en todos los formularios.

Campos anidados: additionalFields​

Los valores de los atributos anidados llegan en additionalFields. De forma predeterminada, el SDK convierte el array que devuelve la API en un objeto indexado por marker; define rawData: true en la configuración para conservar el array original. Consulta Formato de los campos adicionales.

// default (rawData: false)
attribute.additionalFields['my_field'].value;

// rawData: true
attribute.additionalFields.find((f) => f.marker === 'my_field').value;

Los additionalFields anidados pasan por la misma normalización que los atributos de nivel superior: los archivos únicos se desenvuelven y los números se convierten.

Tipar un valor de atributo​

IAttributeValue.value tiene el tipo unknown, porque su forma depende de type en tiempo de ejecución. Eso también significa que el compilador no puede detectar una suposición errónea sobre él. La regla del archivo único descrita arriba es donde esto se nota: una utilidad escrita como

const file = Array.isArray(value) ? value[0] : undefined; // single image → undefined

compila, se construye y muestra una página sin imágenes y sin ningún error. En su lugar, lee el valor mediante las guardas y las utilidades.

Archivos​

getAttributeFiles devuelve todos los archivos de un atributo image, file o groupOfImages como una sola lista, tanto si el valor es un único archivo como si es un array. Para cualquier otro atributo devuelve un array vacío, así que es seguro llamarla con cualquier atributo:

import { getAttributeFile, getAttributeFiles } from 'oneentry';

const gallery = getAttributeFiles(product.attributeValues.images); // IAttributeFile[]

const cover = getAttributeFile(page.attributeValues.cover); // IAttributeFile | null
const blurDataURL = cover?.previewLink?.default?.[0]; // base64 placeholder

IAttributeFile se exporta. Tiene filename, downloadLink, size y contentType, además de los opcionales previewLink (vistas previas por plantilla, cada una un par [base64 placeholder, url]) y defaultPreview.

Campos anidados​

additionalFields es Record<string, IAttributeValue> | unknown[], porque la API devuelve un array vacío cuando un atributo no tiene ninguno. Con esa unión, cualquier lectura de un campo es un error de tipo. getAdditionalFields la reduce a un mapa (y asigna como claves los marker del array con rawData: true):

import { getAdditionalFields } from 'oneentry';

const alt = getAdditionalFields(page.attributeValues.cover).alt?.value;

Guardas​

isFileAttribute, isStringAttribute, isNumberAttribute, isListAttribute e isTimeIntervalAttribute acotan un IAttributeValue para que su value sea accesible sin una conversión de tipo:

import { isStringAttribute } from 'oneentry';
import type { IAttributeValues } from 'oneentry';

function readText(values: IAttributeValues, marker: string): string {
const attr = values[marker];
return isStringAttribute(attr) ? (attr.value ?? '') : '';
}

La unión discriminada​

ITypedAttributeValue es IAttributeValue como unión sobre type: IFileAttributeValue, IStringAttributeValue, INumberAttributeValue, IListAttributeValue, ITimeIntervalAttributeValue. Está cerrada a propósito: un miembro laxo (type: string) coincidiría con todos los case y devolvería el acotamiento a unknown. Los tipos que el SDK no modela (entity, date, los personalizados) siguen en IAttributeValue.

La unión es opcional. IAttributeValues mantiene el IAttributeValue laxo, así que acota hacia la unión con las guardas o anota un valor que controles:

import type { ITypedAttributeValue } from 'oneentry';

function render(attr: ITypedAttributeValue) {
switch (attr.type) {
case 'image':
case 'file':
return attr.value; // IAttributeFile | IAttributeFile[] | null
case 'integer':
return attr.value; // number | null
}
}

Cuando la API cambia​

Los tipos describen la API tal como era cuando se construyó el SDK; no pueden detectar cambios posteriores. En los valores de atributos el fallo es silencioso: si se cambiara el nombre de un campo de archivo, getAttributeFiles devolvería una lista vacía y la página se mostraría sin imágenes.

La validación de respuestas lo hace visible:

const api = defineOneEntry('https://your-project.oneentry.cloud', {
token: 'your-app-token',
validation: { enabled: true, strictMode: false, logErrors: true },
});

La validación comprueba attributeValues allí donde el contrato de la API es fijo: la forma de los archivos de los atributos image, file y groupOfImages, incluidos los campos anidados. Todo lo definido por tu propio conjunto de atributos sigue abierto. Un archivo que ha perdido un campo o al que se le ha cambiado el nombre se notifica como un problema de validación con el marcador y el índice; los campos que añade la API se aceptan. (Las versiones anteriores no validaban attributeValues en absoluto).

Con strictMode: false, los datos llegan igualmente y el problema se registra en el log; con strictMode: true, la llamada devuelve un IError que incluye validationErrors.


Relacionado​