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 atributo | value |
|---|---|
string, text | string |
integer, float, real | number, convertido desde la forma de cadena de la API |
image, file con un archivo | el propio objeto de archivo |
image, file con varios archivos | un array de objetos de archivo |
groupOfImages | siempre un array (por definición es una colección) |
list | un array |
timeInterval | un array de grupos; consulta Intervalos de tiempo |
| sin valor configurado | siempre 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 claveattributeValues. 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 losattributesde los formularios, los campos de forms-data y losadditionalFieldsanidados nunca se desenvolvían. El código para products o menus ya lee el objeto y no se ve afectado. El código que leevalue[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/floatsin configurar ya no es0.Number(null)es0, así que unnullexplícito de la API se notificaba como un cero real, indistinguible de un0configurado.
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
- Intervalos de tiempo: convertir los atributos
timeIntervalen franjas - Importar tipos:
IAttributeValue,IAttributeValuesy los demás - Módulo AttributesSets: cómo se configuran los atributos