Skip to content

Réparer une forme historique connue, une seule fois ​

Cet exemple accompagne une migration coordonnée : écritures via les trois services *StorageV2 du SDK app, remplacement des alias V1 et de User.getAppData()/setAppData(), plugins avec le helper V3 et KappappsHost.data(). Le helper V2 2.2.1 lit toujours V1. Voir le guide complet.

La plateforme ne peut pas deviner si [] signifiait une liste ou un objet avant le bug V1. Cet exemple connaît le schéma métier : byId est un dictionnaire ; history est une vraie liste. Il ne convertit aucun autre champ.

Ces extraits sont des fragments à intégrer à ton code : le premier dans l'initialisation asynchrone de script.js, après AppliLoaded ; le second dans une fonction asynchrone de ton plugin. Ils ne remplacent pas les fichiers du bootstrap.

js
async function loadState() {
    const state = (await kapp.dataStorageV2.get()) ?? { byId: {}, history: [] };
    if (state === null || typeof state !== 'object' || Array.isArray(state)) {
        throw new Error('État racine inattendu : réparation manuelle nécessaire');
    }
    if ((state.schemaVersion ?? 0) >= 2) return state;

    // Seul ce champ est un dictionnaire dans le schéma de CETTE appli.
    if (Array.isArray(state.byId)) {
        state.byId = Object.fromEntries(Object.entries(state.byId));
    } else if (state.byId == null) {
        state.byId = {};
    } else if (typeof state.byId !== 'object') {
        throw new Error('byId invalide : aucune écriture');
    }
    if (!Array.isArray(state.history)) {
        throw new Error('history doit rester une liste : aucune écriture');
    }
    state.schemaVersion = 2;
    // Marqueur et réparation sont enregistrés ensemble, après toutes les validations.
    await kapp.dataStorageV2.set(state);
    return state;
}
const state = await loadState();

Exécuter cette réparation au démarrage avant les autres écritures de l'appli. La rejouer laisse le même résultat ; un échec de sauvegarde laisse le marqueur serveur inchangé. Le read/modify/write du SDK n'est pas une opération atomique entre plusieurs exécutions : coordonner les écrivains de l'appli. Appliquer la même discipline aux viewers via userDataStorageV2.get(user.id) et set(user.id, data), avec un marqueur par objet viewer. Ne jamais convertir récursivement toutes les listes en objets.

Tester le brouillon sur le channel développeur, puis la première utilisation sur un autre channel avec des fixtures V1 représentatives. Cette réparation modifie uniquement V2 ; V1 garde son ancienne sauvegarde. Un retour au code V1 perd donc la visibilité des changements effectués en V2.

Dans chaque plugin, charger https://kappapps.app/assets/app-plugin/helper-v3.js puis gérer les erreurs par leur code :

js
try {
    const snapshot = await KappappsHost.data();
    // Streamer : snapshot.data / snapshot.history ; viewer : snapshot.appli_data.
    render(snapshot);
} catch (error) {
    if (error.code === 'HOST_API_UNAVAILABLE') {
        showError('Hôte incompatible avec les nouveaux stockages.');
    } else if (error.code === 'RATE_LIMITED') {
        showError(`Lecture limitée ; délai annoncé : ${error.details.retryAfterMs ?? 'inconnu'} ms.`);
    } else {
        showError(`Chargement impossible (${error.code}).`);
    }
}

render et showError sont des fonctions de l'interface de ton appli, pas des méthodes du helper. Aucun retry manuel immédiat : un timeout ou une erreur réseau laisse l'issue d'une écriture inconnue.