[{"data":1,"prerenderedAt":2219},["ShallowReactive",2],{"navigation_docs_fr":3,"-fr-examples-mutations-optimistic":342,"-fr-examples-mutations-optimistic-surround":2214},[4,45,91,137,195,236,257,291,327],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Prise en main","i-lucide-rocket","/fr/getting-started","fr/01.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Qu'est-ce que Qora ?","/fr/getting-started/what-is-qora","fr/01.getting-started/2.what-is-qora","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","/fr/getting-started/installation","fr/01.getting-started/3.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Démarrage rapide","/fr/getting-started/quick-start","fr/01.getting-started/4.quick-start",{"title":25,"path":26,"stem":27,"icon":28},"Comparaison","/fr/getting-started/comparison","fr/01.getting-started/5.comparison","i-lucide-scale",{"title":30,"path":31,"stem":32,"icon":33},"Venant de TanStack Query","/fr/getting-started/coming-from-tanstack-query","fr/01.getting-started/6.coming-from-tanstack-query","i-lucide-arrow-left-right",{"title":35,"path":36,"stem":37,"icon":38},"Migrer depuis d'Autres Librairies","/fr/getting-started/migrating-from-other-libraries","fr/01.getting-started/7.migrating-from-other-libraries","i-lucide-arrow-right-left",{"title":40,"path":41,"stem":42,"icon":43},"Assistants IA","/fr/getting-started/ai-assistants","fr/01.getting-started/8.ai-assistants","i-lucide-sparkles",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Concepts clés","i-lucide-book-open","/fr/core-concepts","fr/02.core-concepts",[51,56,61,66,71,76,81,86],{"title":52,"path":53,"stem":54,"icon":55},"Requêtes","/fr/core-concepts/queries","fr/02.core-concepts/1.queries","i-lucide-database",{"title":57,"path":58,"stem":59,"icon":60},"Clés de requête","/fr/core-concepts/query-keys","fr/02.core-concepts/2.query-keys","i-lucide-key-round",{"title":62,"path":63,"stem":64,"icon":65},"États des requêtes","/fr/core-concepts/query-states","fr/02.core-concepts/3.query-states","i-lucide-activity",{"title":67,"path":68,"stem":69,"icon":70},"Cache","/fr/core-concepts/caching","fr/02.core-concepts/4.caching","i-lucide-layers-3",{"title":72,"path":73,"stem":74,"icon":75},"Stale-While-Revalidate","/fr/core-concepts/stale-while-revalidate","fr/02.core-concepts/5.stale-while-revalidate","i-lucide-timer-reset",{"title":77,"path":78,"stem":79,"icon":80},"Déduplication","/fr/core-concepts/deduplication","fr/02.core-concepts/6.deduplication","i-lucide-copy-minus",{"title":82,"path":83,"stem":84,"icon":85},"Mutations et invalidation","/fr/core-concepts/mutations","fr/02.core-concepts/7.mutations","i-lucide-pencil-line",{"title":87,"path":88,"stem":89,"icon":90},"Hors-ligne et réseau","/fr/core-concepts/offline","fr/02.core-concepts/8.offline","i-lucide-wifi-off",{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":44},"Usage avec Flutter","i-lucide-smartphone","/fr/flutter-integration","fr/03.flutter-integration",[97,102,107,112,117,122,127,132],{"title":98,"path":99,"stem":100,"icon":101},"Configuration","/fr/flutter-integration/setup","fr/03.flutter-integration/1.setup","i-lucide-wrench",{"title":103,"path":104,"stem":105,"icon":106},"QoraScope","/fr/flutter-integration/qora-scope","fr/03.flutter-integration/2.qora-scope","i-lucide-share-2",{"title":108,"path":109,"stem":110,"icon":111},"QoraBuilder","/fr/flutter-integration/qora-builder","fr/03.flutter-integration/3.qora-builder","i-lucide-blocks",{"title":113,"path":114,"stem":115,"icon":116},"QoraMutationBuilder","/fr/flutter-integration/qora-mutation-builder","fr/03.flutter-integration/4.qora-mutation-builder","i-lucide-pencil",{"title":118,"path":119,"stem":120,"icon":121},"InfiniteQueryBuilder","/fr/flutter-integration/infinite-query-builder","fr/03.flutter-integration/5.infinite-query-builder","i-lucide-infinity",{"title":123,"path":124,"stem":125,"icon":126},"Hooks (flutter_hooks)","/fr/flutter-integration/hooks","fr/03.flutter-integration/6.hooks","i-lucide-anchor",{"title":128,"path":129,"stem":130,"icon":131},"Widgets de statut réseau","/fr/flutter-integration/network-status","fr/03.flutter-integration/7.network-status","i-lucide-wifi",{"title":133,"path":134,"stem":135,"icon":136},"Bonnes pratiques","/fr/flutter-integration/best-practices","fr/03.flutter-integration/8.best-practices","i-lucide-badge-check",{"title":138,"icon":139,"path":140,"stem":141,"children":142,"page":44},"Guides Pratiques","i-lucide-chef-hat","/fr/recipes","fr/04.recipes",[143,147,152,157,162,166,171,176,181,186,191],{"title":144,"path":145,"stem":146,"icon":85},"Mutations","/fr/recipes/mutations","fr/04.recipes/02.mutations",{"title":148,"path":149,"stem":150,"icon":151},"Mises à jour optimistes","/fr/recipes/optimistic-updates","fr/04.recipes/03.optimistic-updates","i-lucide-zap",{"title":153,"path":154,"stem":155,"icon":156},"Persistance","/fr/recipes/persistence","fr/04.recipes/04.persistence","i-lucide-hard-drive",{"title":158,"path":159,"stem":160,"icon":161},"Réseau adaptatif","/fr/recipes/network-aware","fr/04.recipes/05.network-aware","i-lucide-signal",{"title":163,"path":164,"stem":165,"icon":121},"Requêtes infinies","/fr/recipes/infinite-queries","fr/04.recipes/06.infinite-queries",{"title":167,"path":168,"stem":169,"icon":170},"Requêtes dépendantes","/fr/recipes/dependent-queries","fr/04.recipes/07.dependent-queries","i-lucide-link-2",{"title":172,"path":173,"stem":174,"icon":175},"Annulation","/fr/recipes/cancel-token","fr/04.recipes/08.cancel-token","i-lucide-circle-slash",{"title":177,"path":178,"stem":179,"icon":180},"Hydratation SSR","/fr/recipes/ssr-hydration","fr/04.recipes/09.ssr-hydration","i-lucide-server",{"title":182,"path":183,"stem":184,"icon":185},"Gestion des erreurs","/fr/recipes/error-handling","fr/04.recipes/10.error-handling","i-lucide-shield-alert",{"title":187,"path":188,"stem":189,"icon":190},"Tests","/fr/recipes/testing","fr/04.recipes/11.testing","i-lucide-flask-conical",{"title":192,"path":193,"stem":194},"Prefetch au Niveau des Routes","/fr/recipes/prefetch","fr/04.recipes/12.prefetch",{"title":196,"icon":197,"path":198,"stem":199,"children":200,"page":44},"Référence de l'API","i-lucide-file-code","/fr/api-reference","fr/05.api-reference",[201,206,210,214,219,224,228,232],{"title":202,"path":203,"stem":204,"icon":205},"QoraClient","/fr/api-reference/qora-client","fr/05.api-reference/1.qora-client","i-lucide-cpu",{"title":207,"path":208,"stem":209,"icon":60},"QoraKey","/fr/api-reference/qora-key","fr/05.api-reference/2.qora-key",{"title":211,"path":212,"stem":213,"icon":65},"QoraState","/fr/api-reference/qora-state","fr/05.api-reference/3.qora-state",{"title":215,"path":216,"stem":217,"icon":218},"QoraOptions","/fr/api-reference/qora-options","fr/05.api-reference/4.qora-options","i-lucide-sliders-horizontal",{"title":220,"path":221,"stem":222,"icon":223},"QoraClientConfig","/fr/api-reference/qora-client-config","fr/05.api-reference/5.qora-client-config","i-lucide-settings-2",{"title":225,"path":226,"stem":227,"icon":85},"API Mutation","/fr/api-reference/qora-mutation","fr/05.api-reference/6.qora-mutation",{"title":229,"path":230,"stem":231,"icon":156},"PersistQoraClient","/fr/api-reference/persist-qora-client","fr/05.api-reference/7.persist-qora-client",{"title":233,"path":234,"stem":235,"icon":121},"Requête infinie","/fr/api-reference/infinite-query","fr/05.api-reference/8.infinite-query",{"title":237,"icon":238,"path":239,"stem":240,"children":241,"page":44},"Outils de debug","i-lucide-bug","/fr/devtools","fr/06.devtools",[242,247,252],{"title":243,"path":244,"stem":245,"icon":246},"Vue d'ensemble","/fr/devtools/overview","fr/06.devtools/1.overview","i-lucide-layout-dashboard",{"title":248,"path":249,"stem":250,"icon":251},"Overlay intégré","/fr/devtools/overlay","fr/06.devtools/2.overlay","i-lucide-layers",{"title":253,"path":254,"stem":255,"icon":256},"Sérialisation des données","/fr/devtools/data-display","fr/06.devtools/4.data-display","i-lucide-eye",{"title":258,"icon":259,"path":260,"stem":261,"children":262,"page":44},"Intégrations tierces","i-lucide-puzzle","/fr/third-party","fr/07.third-party",[263,268,272,277,282,286],{"title":264,"path":265,"stem":266,"icon":267},"Avec Provider","/fr/third-party/with-provider","fr/07.third-party/1.with-provider","i-lucide-plug",{"title":269,"path":270,"stem":271,"icon":111},"Avec BLoC","/fr/third-party/with-bloc","fr/07.third-party/2.with-bloc",{"title":273,"path":274,"stem":275,"icon":276},"Avec Riverpod","/fr/third-party/with-riverpod","fr/07.third-party/3.with-riverpod","i-lucide-droplets",{"title":278,"path":279,"stem":280,"icon":281},"Avec RxDart","/fr/third-party/with-rxdart","fr/07.third-party/4.with-rxdart","i-lucide-git-branch",{"title":283,"path":284,"stem":285},"Avec Dio","/fr/third-party/with-dio","fr/07.third-party/5.with-dio",{"title":287,"path":288,"stem":289,"icon":290},"Avec GraphQL","/fr/third-party/with-graphql","fr/07.third-party/6.with-graphql","i-lucide-binary",{"title":292,"icon":190,"path":293,"stem":294,"children":295,"page":44},"Exemples","/fr/examples","fr/08.examples",[296,301,305,310,314,318,322],{"title":297,"path":298,"stem":299,"icon":300},"Étude de cas : Liste et détail utilisateur","/fr/examples/basic-query","fr/08.examples/1.basic-query","i-lucide-play-circle",{"title":302,"path":303,"stem":304,"icon":151},"Étude de cas : Mutations optimistes et rollback","/fr/examples/mutations-optimistic","fr/08.examples/2.mutations-optimistic",{"title":306,"path":307,"stem":308,"icon":309},"Étude de cas : Feed à défilement infini","/fr/examples/infinite-scroll","fr/08.examples/3.infinite-scroll","i-lucide-scroll",{"title":311,"path":312,"stem":313,"icon":90},"Étude de cas : Application Todo offline-first","/fr/examples/offline-first","fr/08.examples/4.offline-first",{"title":315,"path":316,"stem":317,"icon":161},"Étude de cas : Conscience réseau","/fr/examples/network-awareness","fr/08.examples/5.network-awareness",{"title":319,"path":320,"stem":321,"icon":126},"Étude de cas : Intégration des hooks","/fr/examples/hooks-integration","fr/08.examples/6.hooks-integration",{"title":323,"path":324,"stem":325,"icon":326},"Étude de cas : Application Todo complète","/fr/examples/complete-todo-app","fr/08.examples/7.complete-todo-app","i-lucide-check-square",{"title":328,"icon":329,"path":330,"stem":331,"children":332,"page":44},"Aide","i-lucide-life-buoy","/fr/help","fr/09.help",[333,338],{"title":334,"path":335,"stem":336,"icon":337},"FAQ","/fr/help/faq","fr/09.help/1.faq","i-lucide-circle-help",{"title":339,"path":340,"stem":341,"icon":101},"Dépannage","/fr/help/troubleshooting","fr/09.help/2.troubleshooting",{"id":343,"title":302,"body":344,"description":2205,"extension":2206,"links":2207,"meta":2208,"navigation":2209,"path":303,"seo":2210,"stem":304,"__hash__":2213},"docs_fr/fr/08.examples/2.mutations-optimistic.md",{"type":345,"value":346,"toc":2189},"minimark",[347,352,360,363,378,381,385,489,491,495,501,511,531,721,723,727,732,739,759,931,957,961,970,1351,1362,1366,1384,1493,1496,1500,1508,1581,1593,1598,1610,2051,2073,2075,2079,2082,2120,2122,2126,2132,2141,2143,2147,2185],[348,349,351],"h2",{"id":350},"objectif","Objectif",[353,354,355,359],"p",{},[356,357,358],"strong",{},"Contexte"," : Les opérations d'écriture introduisent un problème de latence. Un aller-retour vers le serveur prend 1 à 3 secondes. Bloquer l'UI jusqu'à la réponse du serveur produit des interactions lentes et brise la réactivité perçue de l'application.",[353,361,362],{},"Les mises à jour optimistes résolvent ce problème en appliquant le résultat attendu au cache local immédiatement, avant la réponse du serveur. Si le serveur confirme l'opération, les données optimistes sont remplacées par la réponse autoritaire du serveur. Si le serveur la rejette, le cache est restauré à son état pré-mutation.",[353,364,365,366,369,370,373,374,377],{},"Cet exemple implémente le protocole complet de mise à jour optimiste en utilisant ",[367,368,113],"code",{},", ",[367,371,372],{},"setQueryData"," et ",[367,375,376],{},"restoreQueryData",". L'API simulée échoue ~30 % du temps pour rendre le rollback observable.",[379,380],"hr",{},[348,382,384],{"id":383},"fonctionnalités-démontrées","Fonctionnalités démontrées",[386,387,388,401,416,430,441,456,478],"ul",{},[389,390,391,396,397,400],"li",{},[356,392,393],{},[367,394,395],{},"QoraMutationBuilder\u003CTData, TVariables, TContext>"," : gère le cycle de vie du ",[367,398,399],{},"MutationController"," (création au montage, dispose au démontage), câble la file hors ligne automatiquement",[389,402,403,408,409,412,413,415],{},[356,404,405],{},[367,406,407],{},"MutationOptions.onMutate"," : capture un snapshot de cache pré-mutation (",[367,410,411],{},"TContext",") et applique ",[367,414,372],{}," avant l'appel serveur",[389,417,418,423,424,426,427,429],{},[356,419,420],{},[367,421,422],{},"MutationOptions.onError"," : appelle ",[367,425,376],{}," avec le snapshot ",[367,428,411],{}," pour annuler simultanément les deux entrées de cache affectées",[389,431,432,423,437,440],{},[356,433,434],{},[367,435,436],{},"MutationOptions.onSuccess",[367,438,439],{},"invalidateWhere"," pour déclencher un refetch en arrière-plan et réconcilier le cache avec l'état confirmé par le serveur",[389,442,443,446,447,373,450,453,454],{},[356,444,445],{},"Mise à jour optimiste cross-cache"," : une seule mutation met à jour deux entrées de cache indépendantes (",[367,448,449],{},"['users', id]",[367,451,452],{},"['users']",") atomiquement via ",[367,455,411],{},[389,457,458,464,465,369,468,369,471,369,474,477],{},[356,459,460,461],{},"Pattern matching sur ",[367,462,463],{},"MutationState"," : quatre états (",[367,466,467],{},"MutationIdle",[367,469,470],{},"MutationPending",[367,472,473],{},"MutationSuccess",[367,475,476],{},"MutationFailure",") traités exhaustivement avec des expressions switch Dart 3",[389,479,480,488],{},[356,481,482,483,485,486],{},"Composition ",[367,484,108],{}," + ",[367,487,113],{}," : opérations de lecture et d'écriture coexistent sur le même écran sans état mutable partagé",[379,490],{},[348,492,494],{"id":493},"points-darchitecture","Points d'architecture",[353,496,497,498,500],{},"Le cycle de vie de la mutation est entièrement géré par ",[367,499,113],{},". L'arbre de widgets déclare l'intention ; Qora coordonne le cache, le réseau et les transitions d'état :",[502,503,509],"pre",{"className":504,"code":506,"language":507,"meta":508},[505],"language-text","ex_02_mutations_optimistic/lib/\n├── main.dart                      ← QoraClient + QoraScope à la racine\n├── models/\n│   └── user.dart                  ← classe de données pure avec copyWith\n├── services/\n│   └── fake_api.dart              ← API async (latence 2 s, ~30 % d'échec sur updateUser)\n└── screens/\n    ├── user_list_screen.dart      ← QoraBuilder\u003CList\u003CUser>>, key: ['users']\n    └── rename_user_sheet.dart     ← QoraMutationBuilder + protocole optimiste en 3 étapes\n","text","",[367,510,506],{"__ignoreMap":508},[353,512,513,515,516,519,520,522,523,526,527,530],{},[367,514,202],{}," est configuré une seule fois à la racine. ",[367,517,518],{},"UserListScreen"," lit la liste d'utilisateurs via ",[367,521,108],{},". L'action d'édition capture ",[367,524,525],{},"context.qora"," et le passe explicitement à ",[367,528,529],{},"RenameUserSheet",", car les routes modales s'exécutent dans un overlay de navigator séparé :",[502,532,537],{"className":533,"code":534,"filename":535,"language":536,"meta":508,"style":508},"language-dart shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Future\u003Cvoid> _showRenameSheet(BuildContext context, User user) {\n  // Capturer le client avant l'ouverture de la sheet : les routes modales s'exécutent dans\n  // un overlay de navigator séparé et peuvent ne pas résoudre QoraScope par elles-mêmes.\n  final client = context.qora;\n  return showModalBottomSheet\u003Cvoid>(\n    context: context,\n    isScrollControlled: true,\n    useSafeArea: true,\n    builder: (_) => RenameUserSheet(user: user, client: client),\n  );\n}\n","lib/screens/user_list_screen.dart","dart",[367,538,539,582,589,595,618,635,649,660,670,707,715],{"__ignoreMap":508},[540,541,544,548,552,556,559,563,566,569,572,576,579],"span",{"class":542,"line":543},"line",1,[540,545,547],{"class":546},"sBMFI","Future",[540,549,551],{"class":550},"sTEyZ","\u003C",[540,553,555],{"class":554},"spNyl","void",[540,557,558],{"class":550},"> ",[540,560,562],{"class":561},"s2Zo4","_showRenameSheet",[540,564,565],{"class":550},"(",[540,567,568],{"class":546},"BuildContext",[540,570,571],{"class":550}," context",[540,573,575],{"class":574},"sMK4o",",",[540,577,578],{"class":546}," User",[540,580,581],{"class":550}," user) {\n",[540,583,585],{"class":542,"line":584},2,[540,586,588],{"class":587},"sHwdD","  // Capturer le client avant l'ouverture de la sheet : les routes modales s'exécutent dans\n",[540,590,592],{"class":542,"line":591},3,[540,593,594],{"class":587},"  // un overlay de navigator séparé et peuvent ne pas résoudre QoraScope par elles-mêmes.\n",[540,596,598,601,604,607,609,612,615],{"class":542,"line":597},4,[540,599,600],{"class":554},"  final",[540,602,603],{"class":550}," client ",[540,605,606],{"class":574},"=",[540,608,571],{"class":550},[540,610,611],{"class":574},".",[540,613,614],{"class":550},"qora",[540,616,617],{"class":574},";\n",[540,619,621,625,628,630,632],{"class":542,"line":620},5,[540,622,624],{"class":623},"s7zQu","  return",[540,626,627],{"class":561}," showModalBottomSheet",[540,629,551],{"class":550},[540,631,555],{"class":554},[540,633,634],{"class":550},">(\n",[540,636,638,641,644,646],{"class":542,"line":637},6,[540,639,640],{"class":550},"    context",[540,642,643],{"class":574},":",[540,645,571],{"class":550},[540,647,648],{"class":574},",\n",[540,650,652,655,657],{"class":542,"line":651},7,[540,653,654],{"class":550},"    isScrollControlled",[540,656,643],{"class":574},[540,658,659],{"class":574}," true,\n",[540,661,663,666,668],{"class":542,"line":662},8,[540,664,665],{"class":550},"    useSafeArea",[540,667,643],{"class":574},[540,669,659],{"class":574},[540,671,673,676,678,681,684,687,690,692,695,697,700,702,705],{"class":542,"line":672},9,[540,674,675],{"class":550},"    builder",[540,677,643],{"class":574},[540,679,680],{"class":550}," (_) ",[540,682,683],{"class":574},"=>",[540,685,686],{"class":546}," RenameUserSheet",[540,688,689],{"class":550},"(user",[540,691,643],{"class":574},[540,693,694],{"class":550}," user",[540,696,575],{"class":574},[540,698,699],{"class":550}," client",[540,701,643],{"class":574},[540,703,704],{"class":550}," client)",[540,706,648],{"class":574},[540,708,710,713],{"class":542,"line":709},10,[540,711,712],{"class":550},"  )",[540,714,617],{"class":574},[540,716,718],{"class":542,"line":717},11,[540,719,720],{"class":550},"}\n",[379,722],{},[348,724,726],{"id":725},"détails-dimplémentation","Détails d'implémentation",[728,729,731],"h3",{"id":730},"le-protocole-de-mise-à-jour-optimiste-en-3-étapes","Le protocole de mise à jour optimiste en 3 étapes",[353,733,734,735,738],{},"Chaque mutation optimiste suit trois hooks séquentiels définis dans ",[367,736,737],{},"MutationOptions",". Les paramètres de type encodent les types du domaine avec précision :",[386,740,741,747,753],{},[389,742,743,746],{},[367,744,745],{},"TData = User",": valeur retournée par le serveur en cas de succès",[389,748,749,752],{},[367,750,751],{},"TVariables = ({String id, String name})",": entrée du mutateur",[389,754,755,758],{},[367,756,757],{},"TContext = ({User? user, List\u003CUser>? list})",": snapshot pré-mutation pour le rollback",[502,760,763],{"className":533,"code":761,"filename":762,"language":536,"meta":508,"style":508},"QoraMutationBuilder\u003CUser, _UpdateVars, _Snapshot>(\n  mutator: (vars) => FakeApi.updateUser(vars.id, name: vars.name),\n  options: MutationOptions\u003CUser, _UpdateVars, _Snapshot>(\n    onMutate: ...,   // Étape 1 : snapshot + setQueryData\n    onError: ...,    // Étape 2 : restoreQueryData (rollback)\n    onSuccess: ...,  // Étape 3 : invalidateWhere (réconciliation)\n  ),\n  builder: (context, state, mutate) { ... },\n)\n","lib/screens/rename_user_sheet.dart",[367,764,765,786,831,855,868,880,892,898,926],{"__ignoreMap":508},[540,766,767,769,771,774,776,779,781,784],{"class":542,"line":543},[540,768,113],{"class":546},[540,770,551],{"class":550},[540,772,773],{"class":546},"User",[540,775,369],{"class":550},[540,777,778],{"class":546},"_UpdateVars",[540,780,369],{"class":550},[540,782,783],{"class":546},"_Snapshot",[540,785,634],{"class":550},[540,787,788,791,793,796,798,801,803,806,809,811,814,816,819,821,824,826,829],{"class":542,"line":584},[540,789,790],{"class":550},"  mutator",[540,792,643],{"class":574},[540,794,795],{"class":550}," (vars) ",[540,797,683],{"class":574},[540,799,800],{"class":546}," FakeApi",[540,802,611],{"class":574},[540,804,805],{"class":561},"updateUser",[540,807,808],{"class":550},"(vars",[540,810,611],{"class":574},[540,812,813],{"class":550},"id",[540,815,575],{"class":574},[540,817,818],{"class":550}," name",[540,820,643],{"class":574},[540,822,823],{"class":550}," vars",[540,825,611],{"class":574},[540,827,828],{"class":550},"name)",[540,830,648],{"class":574},[540,832,833,836,838,841,843,845,847,849,851,853],{"class":542,"line":591},[540,834,835],{"class":550},"  options",[540,837,643],{"class":574},[540,839,840],{"class":546}," MutationOptions",[540,842,551],{"class":550},[540,844,773],{"class":546},[540,846,369],{"class":550},[540,848,778],{"class":546},[540,850,369],{"class":550},[540,852,783],{"class":546},[540,854,634],{"class":550},[540,856,857,860,862,865],{"class":542,"line":597},[540,858,859],{"class":550},"    onMutate",[540,861,643],{"class":574},[540,863,864],{"class":574}," ...,",[540,866,867],{"class":587},"   // Étape 1 : snapshot + setQueryData\n",[540,869,870,873,875,877],{"class":542,"line":620},[540,871,872],{"class":550},"    onError",[540,874,643],{"class":574},[540,876,864],{"class":574},[540,878,879],{"class":587},"    // Étape 2 : restoreQueryData (rollback)\n",[540,881,882,885,887,889],{"class":542,"line":637},[540,883,884],{"class":550},"    onSuccess",[540,886,643],{"class":574},[540,888,864],{"class":574},[540,890,891],{"class":587},"  // Étape 3 : invalidateWhere (réconciliation)\n",[540,893,894,896],{"class":542,"line":651},[540,895,712],{"class":550},[540,897,648],{"class":574},[540,899,900,903,905,908,910,913,915,918,921,924],{"class":542,"line":662},[540,901,902],{"class":550},"  builder",[540,904,643],{"class":574},[540,906,907],{"class":550}," (context",[540,909,575],{"class":574},[540,911,912],{"class":550}," state",[540,914,575],{"class":574},[540,916,917],{"class":550}," mutate) { ",[540,919,920],{"class":574},"...",[540,922,923],{"class":550}," }",[540,925,648],{"class":574},[540,927,928],{"class":542,"line":672},[540,929,930],{"class":550},")\n",[353,932,933,934,937,938,941,942,945,946,948,949,951,952,373,954,956],{},"Les hooks s'exécutent en séquence : ",[367,935,936],{},"onMutate"," s'exécute avant le mutateur ; ",[367,939,940],{},"onError"," ou ",[367,943,944],{},"onSuccess"," s'exécutent après sa résolution. La valeur ",[367,947,411],{}," retournée par ",[367,950,936],{}," est transmise à ",[367,953,940],{},[367,955,944],{}," en tant que troisième paramètre.",[728,958,960],{"id":959},"snapshot-atomique-cross-cache","Snapshot atomique cross-cache",[353,962,963,965,966,969],{},[367,964,936],{}," capture deux entrées de cache indépendantes avant d'en modifier l'une ou l'autre. Le type record Dart 3 ",[367,967,968],{},"({User? user, List\u003CUser>? list})"," porte les deux snapshots comme une seule valeur typée :",[502,971,973],{"className":533,"code":972,"filename":762,"language":536,"meta":508,"style":508},"onMutate: (vars) async {\n  // Capturer les données actuelles des deux entrées de cache avant mutation.\n  final prevUser = client.getQueryData\u003CUser>(['users', vars.id]);\n  final prevList = client.getQueryData\u003CList\u003CUser>>(['users']);\n\n  // Appliquer le changement de nom optimiste au cache de détail par utilisateur.\n  if (prevUser != null) {\n    client.setQueryData\u003CUser>(\n      ['users', vars.id],\n      prevUser.copyWith(name: vars.name),\n    );\n  }\n\n  // Appliquer le même changement à l'entrée correspondante dans le cache de liste.\n  if (prevList != null) {\n    client.setQueryData\u003CList\u003CUser>>(\n      ['users'],\n      prevList\n          .map((u) => u.id == vars.id ? u.copyWith(name: vars.name) : u)\n          .toList(),\n    );\n  }\n\n  // Retourner les deux snapshots comme contexte de rollback (TContext).\n  return (user: prevUser, list: prevList);\n},\n",[367,974,975,989,994,1032,1066,1072,1077,1094,1109,1127,1150,1157,1163,1168,1174,1188,1208,1220,1226,1282,1295,1302,1307,1312,1318,1343],{"__ignoreMap":508},[540,976,977,979,981,983,986],{"class":542,"line":543},[540,978,936],{"class":550},[540,980,643],{"class":574},[540,982,795],{"class":550},[540,984,985],{"class":623},"async",[540,987,988],{"class":550}," {\n",[540,990,991],{"class":542,"line":584},[540,992,993],{"class":587},"  // Capturer les données actuelles des deux entrées de cache avant mutation.\n",[540,995,996,998,1001,1003,1005,1007,1010,1012,1014,1017,1021,1023,1025,1027,1030],{"class":542,"line":591},[540,997,600],{"class":554},[540,999,1000],{"class":550}," prevUser ",[540,1002,606],{"class":574},[540,1004,699],{"class":550},[540,1006,611],{"class":574},[540,1008,1009],{"class":561},"getQueryData",[540,1011,551],{"class":550},[540,1013,773],{"class":546},[540,1015,1016],{"class":550},">([",[540,1018,1020],{"class":1019},"sfazB","'users'",[540,1022,575],{"class":574},[540,1024,823],{"class":550},[540,1026,611],{"class":574},[540,1028,1029],{"class":550},"id])",[540,1031,617],{"class":574},[540,1033,1034,1036,1039,1041,1043,1045,1047,1049,1052,1054,1056,1059,1061,1064],{"class":542,"line":597},[540,1035,600],{"class":554},[540,1037,1038],{"class":550}," prevList ",[540,1040,606],{"class":574},[540,1042,699],{"class":550},[540,1044,611],{"class":574},[540,1046,1009],{"class":561},[540,1048,551],{"class":550},[540,1050,1051],{"class":546},"List",[540,1053,551],{"class":550},[540,1055,773],{"class":546},[540,1057,1058],{"class":550},">>([",[540,1060,1020],{"class":1019},[540,1062,1063],{"class":550},"])",[540,1065,617],{"class":574},[540,1067,1068],{"class":542,"line":620},[540,1069,1071],{"emptyLinePlaceholder":1070},true,"\n",[540,1073,1074],{"class":542,"line":637},[540,1075,1076],{"class":587},"  // Appliquer le changement de nom optimiste au cache de détail par utilisateur.\n",[540,1078,1079,1082,1085,1088,1091],{"class":542,"line":651},[540,1080,1081],{"class":623},"  if",[540,1083,1084],{"class":550}," (prevUser ",[540,1086,1087],{"class":574},"!=",[540,1089,1090],{"class":574}," null",[540,1092,1093],{"class":550},") {\n",[540,1095,1096,1099,1101,1103,1105,1107],{"class":542,"line":662},[540,1097,1098],{"class":550},"    client",[540,1100,611],{"class":574},[540,1102,372],{"class":561},[540,1104,551],{"class":550},[540,1106,773],{"class":546},[540,1108,634],{"class":550},[540,1110,1111,1114,1116,1118,1120,1122,1125],{"class":542,"line":672},[540,1112,1113],{"class":550},"      [",[540,1115,1020],{"class":1019},[540,1117,575],{"class":574},[540,1119,823],{"class":550},[540,1121,611],{"class":574},[540,1123,1124],{"class":550},"id]",[540,1126,648],{"class":574},[540,1128,1129,1132,1134,1137,1140,1142,1144,1146,1148],{"class":542,"line":709},[540,1130,1131],{"class":550},"      prevUser",[540,1133,611],{"class":574},[540,1135,1136],{"class":561},"copyWith",[540,1138,1139],{"class":550},"(name",[540,1141,643],{"class":574},[540,1143,823],{"class":550},[540,1145,611],{"class":574},[540,1147,828],{"class":550},[540,1149,648],{"class":574},[540,1151,1152,1155],{"class":542,"line":717},[540,1153,1154],{"class":550},"    )",[540,1156,617],{"class":574},[540,1158,1160],{"class":542,"line":1159},12,[540,1161,1162],{"class":550},"  }\n",[540,1164,1166],{"class":542,"line":1165},13,[540,1167,1071],{"emptyLinePlaceholder":1070},[540,1169,1171],{"class":542,"line":1170},14,[540,1172,1173],{"class":587},"  // Appliquer le même changement à l'entrée correspondante dans le cache de liste.\n",[540,1175,1177,1179,1182,1184,1186],{"class":542,"line":1176},15,[540,1178,1081],{"class":623},[540,1180,1181],{"class":550}," (prevList ",[540,1183,1087],{"class":574},[540,1185,1090],{"class":574},[540,1187,1093],{"class":550},[540,1189,1191,1193,1195,1197,1199,1201,1203,1205],{"class":542,"line":1190},16,[540,1192,1098],{"class":550},[540,1194,611],{"class":574},[540,1196,372],{"class":561},[540,1198,551],{"class":550},[540,1200,1051],{"class":546},[540,1202,551],{"class":550},[540,1204,773],{"class":546},[540,1206,1207],{"class":550},">>(\n",[540,1209,1211,1213,1215,1218],{"class":542,"line":1210},17,[540,1212,1113],{"class":550},[540,1214,1020],{"class":1019},[540,1216,1217],{"class":550},"]",[540,1219,648],{"class":574},[540,1221,1223],{"class":542,"line":1222},18,[540,1224,1225],{"class":550},"      prevList\n",[540,1227,1229,1232,1235,1238,1240,1243,1245,1248,1251,1253,1255,1257,1260,1262,1264,1266,1268,1270,1272,1274,1277,1279],{"class":542,"line":1228},19,[540,1230,1231],{"class":574},"          .",[540,1233,1234],{"class":561},"map",[540,1236,1237],{"class":550},"((u) ",[540,1239,683],{"class":574},[540,1241,1242],{"class":550}," u",[540,1244,611],{"class":574},[540,1246,1247],{"class":550},"id ",[540,1249,1250],{"class":574},"==",[540,1252,823],{"class":550},[540,1254,611],{"class":574},[540,1256,1247],{"class":550},[540,1258,1259],{"class":574},"?",[540,1261,1242],{"class":550},[540,1263,611],{"class":574},[540,1265,1136],{"class":561},[540,1267,1139],{"class":550},[540,1269,643],{"class":574},[540,1271,823],{"class":550},[540,1273,611],{"class":574},[540,1275,1276],{"class":550},"name) ",[540,1278,643],{"class":574},[540,1280,1281],{"class":550}," u)\n",[540,1283,1285,1287,1290,1293],{"class":542,"line":1284},20,[540,1286,1231],{"class":574},[540,1288,1289],{"class":561},"toList",[540,1291,1292],{"class":550},"()",[540,1294,648],{"class":574},[540,1296,1298,1300],{"class":542,"line":1297},21,[540,1299,1154],{"class":550},[540,1301,617],{"class":574},[540,1303,1305],{"class":542,"line":1304},22,[540,1306,1162],{"class":550},[540,1308,1310],{"class":542,"line":1309},23,[540,1311,1071],{"emptyLinePlaceholder":1070},[540,1313,1315],{"class":542,"line":1314},24,[540,1316,1317],{"class":587},"  // Retourner les deux snapshots comme contexte de rollback (TContext).\n",[540,1319,1321,1323,1326,1328,1331,1333,1336,1338,1341],{"class":542,"line":1320},25,[540,1322,624],{"class":623},[540,1324,1325],{"class":550}," (user",[540,1327,643],{"class":574},[540,1329,1330],{"class":550}," prevUser",[540,1332,575],{"class":574},[540,1334,1335],{"class":550}," list",[540,1337,643],{"class":574},[540,1339,1340],{"class":550}," prevList)",[540,1342,617],{"class":574},[540,1344,1346,1349],{"class":542,"line":1345},26,[540,1347,1348],{"class":550},"}",[540,1350,648],{"class":574},[353,1352,1353,1355,1356,1358,1359,1361],{},[367,1354,372],{}," pousse la nouvelle valeur vers tous les streams ",[367,1357,108],{}," actifs abonnés à la clé correspondante. ",[367,1360,518],{}," et tout widget de détail ouvert reçoivent la mise à jour à la frame suivante, avant la complétion de l'appel réseau.",[728,1363,1365],{"id":1364},"rollback-automatique-en-cas-déchec","Rollback automatique en cas d'échec",[353,1367,1368,1370,1371,1373,1374,1376,1377,1379,1380,1383],{},[367,1369,940],{}," reçoit le snapshot ",[367,1372,411],{}," retourné par ",[367,1375,936],{},". ",[367,1378,376],{}," est un no-op quand le snapshot est ",[367,1381,1382],{},"null",", la null-safety est donc préservée sans branchement défensif :",[502,1385,1387],{"className":533,"code":1386,"filename":762,"language":536,"meta":508,"style":508},"onError: (error, vars, ctx) async {\n  // Restaurer les deux entrées de cache depuis le snapshot retourné par onMutate.\n  client.restoreQueryData\u003CUser>(['users', vars.id], ctx?.user);\n  client.restoreQueryData\u003CList\u003CUser>>(['users'], ctx?.list);\n},\n",[367,1388,1389,1411,1416,1454,1487],{"__ignoreMap":508},[540,1390,1391,1393,1395,1398,1400,1402,1404,1407,1409],{"class":542,"line":543},[540,1392,940],{"class":550},[540,1394,643],{"class":574},[540,1396,1397],{"class":550}," (error",[540,1399,575],{"class":574},[540,1401,823],{"class":550},[540,1403,575],{"class":574},[540,1405,1406],{"class":550}," ctx) ",[540,1408,985],{"class":623},[540,1410,988],{"class":550},[540,1412,1413],{"class":542,"line":584},[540,1414,1415],{"class":587},"  // Restaurer les deux entrées de cache depuis le snapshot retourné par onMutate.\n",[540,1417,1418,1421,1423,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443,1446,1449,1452],{"class":542,"line":591},[540,1419,1420],{"class":550},"  client",[540,1422,611],{"class":574},[540,1424,376],{"class":561},[540,1426,551],{"class":550},[540,1428,773],{"class":546},[540,1430,1016],{"class":550},[540,1432,1020],{"class":1019},[540,1434,575],{"class":574},[540,1436,823],{"class":550},[540,1438,611],{"class":574},[540,1440,1124],{"class":550},[540,1442,575],{"class":574},[540,1444,1445],{"class":550}," ctx",[540,1447,1448],{"class":574},"?.",[540,1450,1451],{"class":550},"user)",[540,1453,617],{"class":574},[540,1455,1456,1458,1460,1462,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1485],{"class":542,"line":597},[540,1457,1420],{"class":550},[540,1459,611],{"class":574},[540,1461,376],{"class":561},[540,1463,551],{"class":550},[540,1465,1051],{"class":546},[540,1467,551],{"class":550},[540,1469,773],{"class":546},[540,1471,1058],{"class":550},[540,1473,1020],{"class":1019},[540,1475,1217],{"class":550},[540,1477,575],{"class":574},[540,1479,1445],{"class":550},[540,1481,1448],{"class":574},[540,1483,1484],{"class":550},"list)",[540,1486,617],{"class":574},[540,1488,1489,1491],{"class":542,"line":620},[540,1490,1348],{"class":550},[540,1492,648],{"class":574},[353,1494,1495],{},"Quand le serveur rejette la mutation, les deux entrées de cache reviennent à leurs valeurs pré-mutation en une seule opération synchrone. Tous les widgets abonnés rebuilden immédiatement, affichant les données originales sans aucune intervention utilisateur.",[728,1497,1499],{"id":1498},"réconciliation-serveur-en-cas-de-succès","Réconciliation serveur en cas de succès",[353,1501,1502,1504,1505,1507],{},[367,1503,944],{}," utilise ",[367,1506,439],{}," pour marquer toutes les entrées de cache liées aux utilisateurs comme périmées. Qora déclenche des refetchs en arrière-plan pour chaque abonné actif, remplaçant les données optimistes par l'état confirmé par le serveur :",[502,1509,1511],{"className":533,"code":1510,"filename":762,"language":536,"meta":508,"style":508},"onSuccess: (user, vars, _) async {\n  // Déclencher un refetch en arrière-plan pour toutes les entrées liées aux utilisateurs\n  // afin que le cache reflète l'état confirmé par le serveur.\n  client.invalidateWhere((key) => key.firstOrNull == 'users');\n},\n",[367,1512,1513,1534,1539,1544,1575],{"__ignoreMap":508},[540,1514,1515,1517,1519,1521,1523,1525,1527,1530,1532],{"class":542,"line":543},[540,1516,944],{"class":550},[540,1518,643],{"class":574},[540,1520,1325],{"class":550},[540,1522,575],{"class":574},[540,1524,823],{"class":550},[540,1526,575],{"class":574},[540,1528,1529],{"class":550}," _) ",[540,1531,985],{"class":623},[540,1533,988],{"class":550},[540,1535,1536],{"class":542,"line":584},[540,1537,1538],{"class":587},"  // Déclencher un refetch en arrière-plan pour toutes les entrées liées aux utilisateurs\n",[540,1540,1541],{"class":542,"line":591},[540,1542,1543],{"class":587},"  // afin que le cache reflète l'état confirmé par le serveur.\n",[540,1545,1546,1548,1550,1552,1555,1557,1560,1562,1565,1567,1570,1573],{"class":542,"line":597},[540,1547,1420],{"class":550},[540,1549,611],{"class":574},[540,1551,439],{"class":561},[540,1553,1554],{"class":550},"((key) ",[540,1556,683],{"class":574},[540,1558,1559],{"class":550}," key",[540,1561,611],{"class":574},[540,1563,1564],{"class":550},"firstOrNull ",[540,1566,1250],{"class":574},[540,1568,1569],{"class":1019}," 'users'",[540,1571,1572],{"class":550},")",[540,1574,617],{"class":574},[540,1576,1577,1579],{"class":542,"line":620},[540,1578,1348],{"class":550},[540,1580,648],{"class":574},[353,1582,1583,1584,373,1586,1588,1589,1592],{},"Le prédicat correspond à la fois à ",[367,1585,452],{},[367,1587,449],{}," simultanément. Les abonnés actifs transitent vers ",[367,1590,1591],{},"Loading(previousData: data)"," : les données optimistes restent visibles pendant le fetch en arrière-plan, et la réponse finale du serveur les remplace sans transition à écran vide.",[728,1594,460,1596],{"id":1595},"pattern-matching-sur-mutationstate",[367,1597,463],{},[353,1599,1600,1602,1603,1605,1606,1609],{},[367,1601,113],{}," rebuilde à chaque fois que le ",[367,1604,399],{}," émet un nouvel état. Le ",[367,1607,1608],{},"builder"," mappe les quatre états vers l'UI :",[502,1611,1613],{"className":533,"code":1612,"filename":762,"language":536,"meta":508,"style":508},"builder: (context, state, mutate) {\n  return Column(\n    children: [\n      TextField(\n        controller: _nameController,\n        enabled: !state.isPending,   // désactiver la saisie pendant l'appel serveur\n        ...\n      ),\n\n      // Retour de statut inline via l'expression switch Dart 3\n      switch (state) {\n        MutationFailure(:final error) => _FeedbackBanner(\n            icon: Icons.undo_rounded,\n            message: 'Sauvegarde échouée, nom restauré. $error',\n            color: Colors.red,\n          ),\n        MutationSuccess() => const _FeedbackBanner(\n            icon: Icons.check_circle_outline,\n            message: 'Sauvegardé. Cache mis à jour depuis le serveur.',\n            color: Colors.green,\n          ),\n        _ => const SizedBox.shrink(),  // MutationIdle, MutationPending\n      },\n\n      FilledButton(\n        onPressed: state.isPending ? null : () => mutate((id: user.id, name: name)),\n        child: state.isPending\n            ? const CircularProgressIndicator(strokeWidth: 2.5)\n            : const Text('Sauvegarder'),\n      ),\n    ],\n  );\n},\n",[367,1614,1615,1632,1642,1652,1659,1671,1694,1699,1706,1710,1715,1723,1745,1762,1784,1801,1808,1825,1840,1851,1866,1872,1896,1903,1907,1914,1965,1980,2002,2022,2029,2037,2044],{"__ignoreMap":508},[540,1616,1617,1619,1621,1623,1625,1627,1629],{"class":542,"line":543},[540,1618,1608],{"class":550},[540,1620,643],{"class":574},[540,1622,907],{"class":550},[540,1624,575],{"class":574},[540,1626,912],{"class":550},[540,1628,575],{"class":574},[540,1630,1631],{"class":550}," mutate) {\n",[540,1633,1634,1636,1639],{"class":542,"line":584},[540,1635,624],{"class":623},[540,1637,1638],{"class":546}," Column",[540,1640,1641],{"class":550},"(\n",[540,1643,1644,1647,1649],{"class":542,"line":591},[540,1645,1646],{"class":550},"    children",[540,1648,643],{"class":574},[540,1650,1651],{"class":550}," [\n",[540,1653,1654,1657],{"class":542,"line":597},[540,1655,1656],{"class":546},"      TextField",[540,1658,1641],{"class":550},[540,1660,1661,1664,1666,1669],{"class":542,"line":620},[540,1662,1663],{"class":550},"        controller",[540,1665,643],{"class":574},[540,1667,1668],{"class":550}," _nameController",[540,1670,648],{"class":574},[540,1672,1673,1676,1678,1681,1684,1686,1689,1691],{"class":542,"line":637},[540,1674,1675],{"class":550},"        enabled",[540,1677,643],{"class":574},[540,1679,1680],{"class":574}," !",[540,1682,1683],{"class":550},"state",[540,1685,611],{"class":574},[540,1687,1688],{"class":550},"isPending",[540,1690,575],{"class":574},[540,1692,1693],{"class":587},"   // désactiver la saisie pendant l'appel serveur\n",[540,1695,1696],{"class":542,"line":651},[540,1697,1698],{"class":574},"        ...\n",[540,1700,1701,1704],{"class":542,"line":662},[540,1702,1703],{"class":550},"      )",[540,1705,648],{"class":574},[540,1707,1708],{"class":542,"line":672},[540,1709,1071],{"emptyLinePlaceholder":1070},[540,1711,1712],{"class":542,"line":709},[540,1713,1714],{"class":587},"      // Retour de statut inline via l'expression switch Dart 3\n",[540,1716,1717,1720],{"class":542,"line":717},[540,1718,1719],{"class":623},"      switch",[540,1721,1722],{"class":550}," (state) {\n",[540,1724,1725,1728,1730,1732,1735,1738,1740,1743],{"class":542,"line":1159},[540,1726,1727],{"class":546},"        MutationFailure",[540,1729,565],{"class":550},[540,1731,643],{"class":574},[540,1733,1734],{"class":554},"final",[540,1736,1737],{"class":550}," error) ",[540,1739,683],{"class":574},[540,1741,1742],{"class":546}," _FeedbackBanner",[540,1744,1641],{"class":550},[540,1746,1747,1750,1752,1755,1757,1760],{"class":542,"line":1165},[540,1748,1749],{"class":550},"            icon",[540,1751,643],{"class":574},[540,1753,1754],{"class":546}," Icons",[540,1756,611],{"class":574},[540,1758,1759],{"class":550},"undo_rounded",[540,1761,648],{"class":574},[540,1763,1764,1767,1769,1772,1775,1779,1782],{"class":542,"line":1170},[540,1765,1766],{"class":550},"            message",[540,1768,643],{"class":574},[540,1770,1771],{"class":1019}," 'Sauvegarde échouée, nom restauré. ",[540,1773,1774],{"class":550},"$",[540,1776,1778],{"class":1777},"sHdIc","error",[540,1780,1781],{"class":1019},"'",[540,1783,648],{"class":574},[540,1785,1786,1789,1791,1794,1796,1799],{"class":542,"line":1176},[540,1787,1788],{"class":550},"            color",[540,1790,643],{"class":574},[540,1792,1793],{"class":546}," Colors",[540,1795,611],{"class":574},[540,1797,1798],{"class":550},"red",[540,1800,648],{"class":574},[540,1802,1803,1806],{"class":542,"line":1190},[540,1804,1805],{"class":550},"          )",[540,1807,648],{"class":574},[540,1809,1810,1813,1816,1818,1821,1823],{"class":542,"line":1210},[540,1811,1812],{"class":546},"        MutationSuccess",[540,1814,1815],{"class":550},"() ",[540,1817,683],{"class":574},[540,1819,1820],{"class":554}," const",[540,1822,1742],{"class":546},[540,1824,1641],{"class":550},[540,1826,1827,1829,1831,1833,1835,1838],{"class":542,"line":1222},[540,1828,1749],{"class":550},[540,1830,643],{"class":574},[540,1832,1754],{"class":546},[540,1834,611],{"class":574},[540,1836,1837],{"class":550},"check_circle_outline",[540,1839,648],{"class":574},[540,1841,1842,1844,1846,1849],{"class":542,"line":1228},[540,1843,1766],{"class":550},[540,1845,643],{"class":574},[540,1847,1848],{"class":1019}," 'Sauvegardé. Cache mis à jour depuis le serveur.'",[540,1850,648],{"class":574},[540,1852,1853,1855,1857,1859,1861,1864],{"class":542,"line":1284},[540,1854,1788],{"class":550},[540,1856,643],{"class":574},[540,1858,1793],{"class":546},[540,1860,611],{"class":574},[540,1862,1863],{"class":550},"green",[540,1865,648],{"class":574},[540,1867,1868,1870],{"class":542,"line":1297},[540,1869,1805],{"class":550},[540,1871,648],{"class":574},[540,1873,1874,1877,1879,1881,1884,1886,1889,1891,1893],{"class":542,"line":1304},[540,1875,1876],{"class":550},"        _ ",[540,1878,683],{"class":574},[540,1880,1820],{"class":554},[540,1882,1883],{"class":546}," SizedBox",[540,1885,611],{"class":574},[540,1887,1888],{"class":561},"shrink",[540,1890,1292],{"class":550},[540,1892,575],{"class":574},[540,1894,1895],{"class":587},"  // MutationIdle, MutationPending\n",[540,1897,1898,1901],{"class":542,"line":1309},[540,1899,1900],{"class":550},"      }",[540,1902,648],{"class":574},[540,1904,1905],{"class":542,"line":1314},[540,1906,1071],{"emptyLinePlaceholder":1070},[540,1908,1909,1912],{"class":542,"line":1320},[540,1910,1911],{"class":546},"      FilledButton",[540,1913,1641],{"class":550},[540,1915,1916,1919,1921,1923,1925,1928,1930,1932,1935,1938,1940,1943,1946,1948,1950,1952,1954,1956,1958,1960,1963],{"class":542,"line":1345},[540,1917,1918],{"class":550},"        onPressed",[540,1920,643],{"class":574},[540,1922,912],{"class":550},[540,1924,611],{"class":574},[540,1926,1927],{"class":550},"isPending ",[540,1929,1259],{"class":574},[540,1931,1090],{"class":574},[540,1933,1934],{"class":574}," :",[540,1936,1937],{"class":550}," () ",[540,1939,683],{"class":574},[540,1941,1942],{"class":561}," mutate",[540,1944,1945],{"class":550},"((id",[540,1947,643],{"class":574},[540,1949,694],{"class":550},[540,1951,611],{"class":574},[540,1953,813],{"class":550},[540,1955,575],{"class":574},[540,1957,818],{"class":550},[540,1959,643],{"class":574},[540,1961,1962],{"class":550}," name))",[540,1964,648],{"class":574},[540,1966,1968,1971,1973,1975,1977],{"class":542,"line":1967},27,[540,1969,1970],{"class":550},"        child",[540,1972,643],{"class":574},[540,1974,912],{"class":550},[540,1976,611],{"class":574},[540,1978,1979],{"class":550},"isPending\n",[540,1981,1983,1986,1988,1991,1994,1996,2000],{"class":542,"line":1982},28,[540,1984,1985],{"class":574},"            ?",[540,1987,1820],{"class":554},[540,1989,1990],{"class":546}," CircularProgressIndicator",[540,1992,1993],{"class":550},"(strokeWidth",[540,1995,643],{"class":574},[540,1997,1999],{"class":1998},"sbssI"," 2.5",[540,2001,930],{"class":550},[540,2003,2005,2008,2010,2013,2015,2018,2020],{"class":542,"line":2004},29,[540,2006,2007],{"class":574},"            :",[540,2009,1820],{"class":554},[540,2011,2012],{"class":546}," Text",[540,2014,565],{"class":550},[540,2016,2017],{"class":1019},"'Sauvegarder'",[540,2019,1572],{"class":550},[540,2021,648],{"class":574},[540,2023,2025,2027],{"class":542,"line":2024},30,[540,2026,1703],{"class":550},[540,2028,648],{"class":574},[540,2030,2032,2035],{"class":542,"line":2031},31,[540,2033,2034],{"class":550},"    ]",[540,2036,648],{"class":574},[540,2038,2040,2042],{"class":542,"line":2039},32,[540,2041,712],{"class":550},[540,2043,617],{"class":574},[540,2045,2047,2049],{"class":542,"line":2046},33,[540,2048,1348],{"class":550},[540,2050,648],{"class":574},[353,2052,2053,373,2055,2057,2058,2061,2062,1376,2065,373,2067,2069,2070,611],{},[367,2054,467],{},[367,2056,470],{}," partagent le bras ",[367,2059,2060],{},"_"," : la bannière de retour est absente et le bouton est activé ou désactivé par ",[367,2063,2064],{},"state.isPending",[367,2066,476],{},[367,2068,473],{}," affichent chacun une bannière inline distincte. Aucun flag booléen, aucun appel ",[367,2071,2072],{},"setState",[379,2074],{},[348,2076,2078],{"id":2077},"étapes-de-validation","Étapes de validation",[353,2080,2081],{},"L'exemple inclut un mode d'échec observable pour rendre le rollback visible :",[2083,2084,2085,2092,2095,2106,2117],"ol",{},[389,2086,2087,2088,2091],{},"Ouvrir ",[356,2089,2090],{},"Liste d'utilisateurs"," et attendre le chargement initial",[389,2093,2094],{},"Appuyer sur l'icône d'édition d'un utilisateur",[389,2096,2097,2098,2101,2102,2105],{},"Saisir un nouveau nom et appuyer sur ",[356,2099,2100],{},"Sauvegarder",": la liste se met à jour ",[356,2103,2104],{},"immédiatement"," en arrière-plan",[389,2107,2108,2109],{},"Attendre ~2 s pour la réponse du serveur :\n",[386,2110,2111,2114],{},[389,2112,2113],{},"En cas de succès (~70 %) : le nouveau nom persiste ; une confirmation verte apparaît",[389,2115,2116],{},"En cas d'échec (~30 %) : le nom revient à l'original ; une bannière rouge de rollback apparaît",[389,2118,2119],{},"Appuyer sur l'icône de rafraîchissement dans l'app bar pour forcer une invalidation complète et observer l'état confirmé par le serveur",[379,2121],{},[348,2123,2125],{"id":2124},"code-source","Code source",[502,2127,2130],{"className":2128,"code":2129,"language":507,"meta":508},[505],"examples/ex_02_mutations_optimistic/\n├── lib/\n│   ├── main.dart                      ← configuration QoraClient + QoraScope\n│   ├── models/user.dart               ← classe de données pure avec copyWith\n│   ├── services/fake_api.dart         ← API simulée (délai 2 s, ~30 % d'échec sur updateUser)\n│   └── screens/\n│       ├── user_list_screen.dart      ← QoraBuilder + déclencheur d'édition\n│       └── rename_user_sheet.dart     ← QoraMutationBuilder + protocole optimiste complet\n└── pubspec.yaml\n",[367,2131,2129],{"__ignoreMap":508},[353,2133,2134],{},[2135,2136,2140],"a",{"href":2137,"rel":2138},"https://github.com/meragix/qora/tree/main/examples/ex_02_mutations_optimistic",[2139],"nofollow","Voir le code source complet",[379,2142],{},[348,2144,2146],{"id":2145},"voir-aussi","Voir aussi",[386,2148,2149,2156,2167,2173],{},[389,2150,2151,2155],{},[2135,2152,2154],{"href":2153},"../recipes/optimistic-updates","Recette de mises à jour optimistes"," : présentation conceptuelle pas à pas du même pattern",[389,2157,2158,2162,2163,373,2165],{},[2135,2159,2161],{"href":2160},"../recipes/mutations","Recette de mutations"," : référence API ",[367,2164,113],{},[367,2166,737],{},[389,2168,2169,2172],{},[2135,2170,113],{"href":2171},"../flutter-integration/qora-mutation-builder"," : API widget complète",[389,2174,2175,2178,2179,2181,2182],{},[2135,2176,297],{"href":2177},"./basic-query"," : ",[367,2180,108],{},", cache SWR et ",[367,2183,2184],{},"FetchStatus",[2186,2187,2188],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":508,"searchDepth":584,"depth":584,"links":2190},[2191,2192,2193,2194,2202,2203,2204],{"id":350,"depth":584,"text":351},{"id":383,"depth":584,"text":384},{"id":493,"depth":584,"text":494},{"id":725,"depth":584,"text":726,"children":2195},[2196,2197,2198,2199,2200],{"id":730,"depth":591,"text":731},{"id":959,"depth":591,"text":960},{"id":1364,"depth":591,"text":1365},{"id":1498,"depth":591,"text":1499},{"id":1595,"depth":591,"text":2201},"Pattern matching sur MutationState",{"id":2077,"depth":584,"text":2078},{"id":2124,"depth":584,"text":2125},{"id":2145,"depth":584,"text":2146},"Une implémentation complète de mise à jour optimiste : setQueryData pour un retour UI immédiat, restoreQueryData pour le rollback automatique en cas d'échec, et invalidateWhere pour la réconciliation serveur.","md",null,{},{"icon":151},{"title":2211,"description":2212},"Étude de cas : Mutations optimistes et rollback - Qora","Mutations optimistes de niveau production avec QoraMutationBuilder. Couvre setQueryData, restoreQueryData, les snapshots cross-cache, le pattern matching sur MutationState et la réconciliation serveur.","5d3XhMPY2Lph_V_btJYtZpdg2GkVqkRfWWQdvU_wDmU",[2215,2217],{"title":297,"path":298,"stem":299,"description":2216,"icon":300,"children":-1},"Une fonctionnalité liste-détail complète : cache SWR, état UI à deux axes avec FetchStatus, clés de requête par élément, pull-to-refresh et dégradation gracieuse. Zéro boilerplate de gestion d'état.",{"title":306,"path":307,"stem":308,"description":2218,"icon":309,"children":-1},"Construire un feed de type réseau social haute performance avec pagination par curseur, gestion mémoire fenêtrée et création de post optimiste instantanée.",1794040411357]