Aller au contenu principal

Valeurs d'attributs

OneEntry décrit le contenu à l'aide d'attributs : une page, un produit, un bloc, un utilisateur, une commande ou un champ de formulaire porte une table de valeurs d'attributs indexée par marqueur. Le SDK normalise chaque attribut de chaque réponse sous la même forme : un champ a donc le même aspect, quel que soit le module qui l'a renvoyé.

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 forme normalisée​

Une valeur d'attribut est un IAttributeValue : { type, value, position?, additionalFields? }. Le contenu de value dépend de type :

Type d'attributvalue
string, textstring
integer, float, realnumber, converti depuis la forme textuelle de l'API
image, file avec un seul fichierl'objet fichier lui-même
image, file avec plusieurs fichiersun tableau d'objets fichier
groupOfImagestoujours un tableau (c'est par définition une collection)
listun tableau
timeIntervalun tableau de groupes ; voir Time intervals
aucune valeur définietoujours null

Les attributs à fichier unique sont déballés​

Lorsqu'un attribut image ou file contient exactement un fichier, sa value est l'objet fichier lui-même. Seules les valeurs avec deux fichiers ou plus restent des tableaux.

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

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

Cela s'applique dans tous les modules.

Migration : les versions précédentes ne déballaient les fichiers uniques que dans products, menus, forms, forms-data, attribute-sets, integration-collections et Pages.searchPage, et uniquement sous la clé attributeValues. Partout ailleurs (blocks, toutes les autres méthodes de pages, Products.getProductsEmptyPage, Products.getProductBlockById, admins, discounts, templates, orders, users), le même attribut arrivait sous forme de tableau à un seul élément, et les attributes des formulaires, les champs des données de formulaire et les additionalFields imbriqués n'étaient jamais déballés. Le code destiné aux produits ou aux menus lit déjà l'objet et n'est pas concerné. Le code qui lit value[0] dans les blocs, les pages, les utilisateurs ou les commandes doit supprimer l'index.

groupOfImages reste toujours un tableau. Les soumissions suivent leurs propres règles : IBodyTypeFile.value accepte un File/Blob (ou un FileList), le descripteur renvoyé par FileUploading.upload, ou un IFileValue, et ceux-ci peuvent être mélangés dans un même tableau. Un File/Blob seul convient, mais un descripteur ou un IFileValue doit être envoyé dans un tableau, sinon l'API répond par une erreur 400.

Les nombres sont des nombres​

Les valeurs integer, float et real sont converties en nombres. (Les versions précédentes laissaient real sous forme de chaîne : le même champ numérique arrivait donc en 10 ou en "10" selon celui des trois types avec lequel il était déclaré.)

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

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

La normalisation numérique s'applique aussi aux attributs de formulaire et aux champs de form-data : un champ rating d'un attribut de formulaire integer est un number, pas une chaîne. Les versions précédentes les ignoraient.

Lors de l'envoi de données, transmettez une chaîne : IBodyTypeStringNumberFloat.value est string | number | null, et les réponses reviennent normalisées.

Une valeur vide vaut toujours null​

L'API renvoie une table de localisation vide pour une valeur non définie, et le SDK la transforme en null pour tous les types. (Les versions précédentes transmettaient la table telle quelle pour les types textuels et renvoyaient null pour les types numériques : « aucune valeur » avait donc trois représentations.)

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

Migration : un integer/float non défini ne vaut plus 0. Number(null) vaut 0 : un null explicite de l'API était donc présenté comme un vrai zéro, impossible à distinguer d'un 0 configuré.

Tout est trié par position​

attributeValues et les attributes de formulaire sont renvoyés dans l'ordre de position. L'API renvoie les champs de formulaire sans ordre (un champ avec position: 10 peut arriver après position: 14) ; le SDK les trie, vous n'avez donc pas à le faire.

Un formulaire sans attributs renvoie attributes: []. L'API envoie un objet vide dans ce cas, et le SDK le normalise en tableau vide : attributes est donc toujours un IFormAttribute[] et form.attributes.map(...) est sûr sur tous les formulaires.

Champs imbriqués : additionalFields​

Les valeurs d'attributs imbriquées arrivent dans additionalFields. Par défaut, le SDK convertit le tableau renvoyé par l'API en un objet indexé par marker ; définissez rawData: true dans la configuration pour conserver le tableau d'origine. Voir Additional fields format.

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

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

Les additionalFields imbriqués subissent la même normalisation que les attributs de premier niveau : les fichiers uniques sont déballés et les nombres convertis.

Typer une valeur d'attribut​

IAttributeValue.value est typé unknown, car sa forme dépend de type à l'exécution. Cela signifie aussi que le compilateur ne peut pas détecter une hypothèse erronée à son sujet. C'est la règle du fichier unique ci-dessus qui pose problème : un utilitaire écrit ainsi

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

compile, se construit et affiche une page sans images et sans aucune erreur. Lisez plutôt la valeur via les gardes de type et les utilitaires.

Fichiers​

getAttributeFiles renvoie tous les fichiers d'un attribut image, file ou groupOfImages sous forme d'une seule liste, que la valeur soit un fichier unique ou un tableau. Pour tout autre attribut, il renvoie un tableau vide : vous pouvez donc l'appeler sur n'importe quel attribut sans risque :

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 est exporté. Il contient filename, downloadLink, size et contentType, plus les champs facultatifs previewLink (aperçus indexés par modèle, chacun étant une paire [base64 placeholder, url]) et defaultPreview.

Champs imbriqués​

additionalFields est Record<string, IAttributeValue> | unknown[], car l'API renvoie un tableau vide lorsqu'un attribut n'en a pas. Avec cette union, chaque lecture de champ est une erreur de type. getAdditionalFields la réduit à une table (et indexe le tableau rawData: true par marker) :

import { getAdditionalFields } from 'oneentry';

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

Gardes de type​

isFileAttribute, isStringAttribute, isNumberAttribute, isListAttribute et isTimeIntervalAttribute affinent un IAttributeValue pour que sa value soit accessible sans conversion de type :

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 ?? '') : '';
}

L'union discriminée​

ITypedAttributeValue est IAttributeValue sous forme d'union sur type : IFileAttributeValue, IStringAttributeValue, INumberAttributeValue, IListAttributeValue, ITimeIntervalAttributeValue. Elle est fermée volontairement : un membre lâche (type: string) correspondrait à chaque case et ramènerait l'affinage à unknown. Les types que le SDK ne modélise pas (entity, date, types personnalisés) restent sur IAttributeValue.

L'union est facultative. IAttributeValues conserve le type lâche IAttributeValue : affinez vers l'union avec les gardes de type, ou annotez une valeur que vous contrôlez :

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
}
}

Quand l'API change​

Les types décrivent l'API telle qu'elle était lors de la construction du SDK ; ils ne peuvent pas remarquer les changements ultérieurs. Pour les valeurs d'attributs, l'échec est silencieux : si un champ de fichier était renommé, getAttributeFiles renverrait une liste vide et la page s'afficherait sans images.

La validation des réponses le rend visible :

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

La validation vérifie attributeValues là où le contrat de l'API est fixe : la forme des fichiers dans les attributs image, file et groupOfImages, champs imbriqués compris. Tout ce qui est défini par votre propre ensemble d'attributs reste ouvert. Un fichier qui a perdu ou renommé un champ est signalé comme problème de validation avec le marqueur et l'index ; les champs ajoutés par l'API sont acceptés. (Les versions précédentes ne validaient pas du tout attributeValues.)

Avec strictMode: false, les données passent quand même et le problème est journalisé ; avec strictMode: true, l'appel renvoie un IError contenant validationErrors.


Voir aussi​