Résilience — retry, chunking, helpers
Pousser des données vers l'API ShopiMind, c'est composer avec des pics de charge, des indisponibilités transitoires et des lots de plusieurs milliers d'entités. Le SDK @shopimind/sdk-js intègre trois mécanismes pour absorber tout cela sans que vous ayez à écrire la moindre boucle de retry : un retry automatique, un découpage (chunking) des gros tableaux, et une boîte à outils SpmHelpers pour agréger et formater les résultats.
Tout est dans l'enveloppe
Le SDK ne lève jamais d'exception sur une erreur HTTP ou réseau : tout est encodé dans l'enveloppe { ok, statusCode, data, error }. La résilience se lit donc dans res.ok, res.error.retryable et res.error.attempts, jamais dans un try/catch. → Voir l'enveloppe de réponse
Retry automatique
Le retry est activé par défaut. Sans rien configurer, le SDK ré-essaie 3 fois une requête qui échoue sur l'une de ces conditions :
- HTTP
408,429,500,502,503,504; - codes réseau :
ECONNRESET,ETIMEDOUT,ECONNREFUSED,ENETUNREACH,ENOTFOUND,EAI_AGAIN.
Entre deux tentatives, le délai suit un backoff exponentiel avec jitter : min(backoffBaseMs * 2^(tentative-1) + jitter, backoffCapMs), où jitter est un aléa de 0 à 500 ms. Avec les valeurs par défaut (backoffBaseMs: 1000, backoffCapMs: 10000), cela donne approximativement 1s → 2s → 4s, plafonné à 10s, avec une petite dispersion aléatoire pour éviter que toutes vos requêtes ne retentent en même temps.
Configurer le retry
Passez un objet retry à SpmClient.getClient. Tous ses champs sont facultatifs ; ceux que vous omettez gardent leur valeur par défaut.
import { SpmClient } from '@shopimind/sdk-js';
// Personnaliser
const client = SpmClient.getClient('v1', process.env.SHOPIMIND_API_KEY!, {
retry: {
maxRetries: 5,
backoffBaseMs: 2000,
backoffCapMs: 30000,
retryableStatus: [429, 502, 503, 504],
},
});| Champ | Défaut | Rôle |
|---|---|---|
maxRetries | 3 | Nombre de ré-essais après l'échec initial |
backoffBaseMs | 1000 | Base du backoff exponentiel (ms) |
backoffCapMs | 10000 | Plafond du délai entre deux tentatives (ms) |
retryableStatus | [408, 429, 500, 502, 503, 504] | Statuts HTTP qui déclenchent un ré-essai |
retryableCodes | ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENETUNREACH', 'ENOTFOUND', 'EAI_AGAIN'] | Codes réseau retryables |
retryablePatterns | ['timeout', 'timed out', 'connection refused', …] | Motifs de message d'erreur retryables |
Pour désactiver complètement le retry (par exemple si vous gérez vous-même une file d'attente avec sa propre politique de ré-essai), passez retry: false :
import { SpmClient } from '@shopimind/sdk-js';
// Désactiver
const client = SpmClient.getClient('v1', process.env.SHOPIMIND_API_KEY!, { retry: false });Lire le résultat après épuisement des tentatives
Quand toutes les tentatives échouent, l'enveloppe renvoie ok: false. L'objet error porte retryable (l'échec était-il d'une nature ré-essayable ?) et attempts (le nombre de tentatives réellement effectuées) :
const res = await SpmOrders.bulkSave(client, orders);
if (!res.ok && res.error.retryable) {
// l'échec est de nature transitoire : remettre en file d'attente / réessayer plus tard
console.warn(`Échec après ${res.error.attempts} tentative(s) : ${res.error.message}`);
}TIP
error.attempts reflète le nombre de tentatives réelles : 1 signifie qu'aucun ré-essai n'a eu lieu (échec non retryable, ou retry désactivé), 4 signifie l'appel initial plus les 3 ré-essais par défaut.
Découpage des gros lots (chunking)
Les méthodes bulkSave / bulkUpdate acceptent un objet d'options { chunk?: boolean; chunkSize?: number }. Le découpage est désactivé par défaut : sans chunk: true, le tableau entier part en une seule requête. Pour l'activer, passez { chunk: true } — le SDK découpe alors le tableau en lots, les envoie séquentiellement (un await par lot) et agrège les résultats.
import { SpmCustomers } from '@shopimind/sdk-js';
const hugeArray = Array.from({ length: 500 }, (_, i) => ({ /* données client */ }));
const res = await SpmCustomers.bulkSave(client, hugeArray, { chunk: true });
console.log(res.data.sent_count); // total agrégé sur tous les lots
console.log(res.data.chunks); // détail par lot (ici 10 × 50)Taille de lot par ressource
Sans chunkSize, chaque ressource utilise sa propre constante CHUNK_SIZE, alignée sur la limite que l'API accepte par requête pour cette entité. Les ressources sans CHUNK_SIZE dédiée retombent sur la valeur générique 50.
| Ressource | CHUNK_SIZE |
|---|---|
SpmCustomDataRecords | 20 |
SpmCustomers, SpmCustomersAddresses, SpmProducts, SpmProductsImages, SpmProductsVariations, SpmOrders, SpmVouchers | 50 |
SpmCustomersGroups, SpmProductsCategories, SpmProductsManufacturers, SpmOrdersCarriers, SpmOrdersStatuses | 100 |
Vous pouvez surcharger la taille de lot ressource par ressource via chunkSize :
await SpmProducts.bulkSave(client, items, { chunk: true, chunkSize: 25 }); // surcharge
console.log(SpmCustomers.CHUNK_SIZE); // 50Le découpage change la forme de data
Avec { chunk: true }, la propriété data de l'enveloppe n'est plus le corps de l'API mais un résumé agrégé de type SpmChunkedData. Adaptez votre lecture en conséquence.
Forme d'une réponse découpée (SpmChunkedData)
Quand le découpage est actif, res.data agrège tous les lots :
interface SpmChunkedData {
sent_count: number; // total envoyé avec succès
rejected_count: number; // total rejeté par l'API
failed_count: number; // total des items des lots en échec (HTTP/réseau)
rejected_items: unknown[]; // items rejetés, consolidés
chunks: SpmChunkSummary[]; // un résumé par lot
}
interface SpmChunkSummary {
statusCode: number;
ok: boolean;
sent_count: number;
rejected_count: number;
}L'enveloppe globale est ok: true uniquement si tous les lots ont réussi. Si au moins un lot échoue, l'enveloppe est ok: false avec un error consolidé :
code: 'PARTIAL_FAILURE';messagedu type« <n> chunk(s) failed out of <m> »;retryable: trueseulement si tous les lots en échec étaient retryables ;attempts= le maximum des tentatives parmi les lots ;details= la liste des erreurs par lot.
const res = await SpmCustomers.bulkSave(client, hugeArray, { chunk: true });
if (!res.ok) {
console.error(res.error.code); // 'PARTIAL_FAILURE'
console.error(res.error.message); // ex. « 2 chunk(s) failed out of 10 »
}
console.log(res.data.failed_count); // nombre d'items dans les lots en échecHelpers partagés — SpmHelpers
SpmHelpers regroupe six fonctions utilitaires pour découper, agréger, compter, formater et déballer sans dupliquer la logique du SDK. Elles sont particulièrement utiles si vous orchestrez vous-même l'envoi de lots, ou pour produire des logs structurés.
import { SpmHelpers } from '@shopimind/sdk-js';
// Découper un tableau en sous-tableaux de taille fixe
SpmHelpers.chunk(array, 50);
// → [[...50], [...50], ...]
// Agréger plusieurs enveloppes en une seule (même logique que le chunking interne)
SpmHelpers.mergeResponses([envelope1, envelope2, /* ... */]);
// → enveloppe dont data est un SpmChunkedData :
// { sent_count, rejected_count, failed_count, rejected_items, chunks }
// Extraire les compteurs, que l'on passe une enveloppe ou un corps brut
SpmHelpers.extractCounts(envelope);
// → { sent: number, rejected: number, failed: number }
// Tester si une erreur / enveloppe est de nature retryable
SpmHelpers.isRetryable(error);
// → false sur une enveloppe en succès ; sinon honore error.retryable (429 / 5xx / réseau)
// Formater une erreur, sûre à sérialiser pour des logs
SpmHelpers.formatError(envelope);
// → { message, code, statusCode, retryable, attempts, details? } (ou null si succès)
// Mode exception opt-in : déballe le double-nesting (res.data.data) et lève si !ok
SpmHelpers.unwrapOrThrow(envelope);
// → le payload métier déballé ; lève SpmApiError si l'enveloppe est en échec| Fonction | Signature | Retour |
|---|---|---|
chunk | chunk<T>(array: T[], size: number) | T[][] |
mergeResponses | mergeResponses(envelopes: SpmEnvelope[]) | SpmEnvelope<SpmChunkedData> |
extractCounts | extractCounts(envelopeOrData) | { sent, rejected, failed } |
isRetryable | isRetryable(errOrEnvelope, options?) | boolean (false si succès) |
formatError | formatError(errOrEnvelope) | SpmFormattedError | null (null si succès) |
unwrapOrThrow | unwrapOrThrow<T>(envelope) | payload métier déballé (lève SpmApiError si !ok) |
Mode exception opt-in — unwrapOrThrow et SpmApiError
Par défaut le SDK ne lève jamais : tout passe par l'enveloppe. Si vous préférez un style try/catch, SpmHelpers.unwrapOrThrow est le point d'entrée opt-in de ce mode. Il déballe le double-nesting (le payload métier des lectures se trouve à res.data.data) et renvoie directement la donnée utile ; si l'enveloppe est en échec (ok: false), il lève une SpmApiError.
import { SpmHelpers, SpmApiError } from '@shopimind/sdk-js';
try {
const customer = SpmHelpers.unwrapOrThrow(await SpmCustomers.get(client, '42'));
// `customer` est déjà le payload déballé (équivalent de res.data.data)
} catch (e) {
if (e instanceof SpmApiError) {
console.error(e.statusCode, e.code); // l'enveloppe d'origine est sur e.envelope
}
}SpmApiError est la seule exception levée par unwrapOrThrow. Elle porte statusCode, code et envelope (l'enveloppe complète d'origine), pour basculer du mode exception au mode enveloppe sans perdre d'information.
Pattern de synchronisation robuste
Combinez bulkSave(client, items, { chunk: true }) (découpage + agrégation) avec le retry intégré et SpmHelpers.formatError pour des logs exploitables : vous obtenez une synchro résistante aux pics de charge et aux indisponibilités transitoires, sans écrire une seule boucle de retry.
import { SpmCustomers, SpmHelpers } from '@shopimind/sdk-js';
const res = await SpmCustomers.bulkSave(client, customers, { chunk: true });
if (!res.ok) {
const err = SpmHelpers.formatError(res);
logger.error('Synchro clients en échec', err); // { message, code, statusCode, retryable, attempts }
}
const { sent, rejected, failed } = SpmHelpers.extractCounts(res);
logger.info(`Clients : ${sent} envoyés, ${rejected} rejetés, ${failed} en échec`);Pour aller plus loin
- → Premiers appels avec le SDK — l'enveloppe, la double imbrication
res.data.data. - → Référence des ressources — méthodes, endpoints et
CHUNK_SIZEde chaque ressource. - → Types TypeScript —
SpmEnvelope,SpmChunkedData,SpmRetryOptions,SpmFormattedError.