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'attribut | value |
|---|---|
string, text | string |
integer, float, real | number, converti depuis la forme textuelle de l'API |
image, file avec un seul fichier | l'objet fichier lui-même |
image, file avec plusieurs fichiers | un tableau d'objets fichier |
groupOfImages | toujours un tableau (c'est par définition une collection) |
list | un tableau |
timeInterval | un tableau de groupes ; voir Time intervals |
| aucune valeur définie | toujours 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 lesattributesdes formulaires, les champs des données de formulaire et lesadditionalFieldsimbriqué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 litvalue[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/floatnon défini ne vaut plus0.Number(null)vaut0: unnullexplicite de l'API était donc présenté comme un vrai zéro, impossible à distinguer d'un0configuré.
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
- Intervalles de temps : décomposer les attributs
timeIntervalen créneaux - Importer les types :
IAttributeValue,IAttributeValueset les autres - Module AttributesSets : comment les attributs sont configurés