[{"data":1,"prerenderedAt":3580},["ShallowReactive",2],{"page-\u002Fjs\u002F21-web-storage-and-apis":3},{"id":4,"title":5,"body":6,"description":3573,"extension":3574,"meta":3575,"navigation":52,"path":3576,"seo":3577,"stem":3578,"__hash__":3579},"content\u002Fjs\u002F21-web-storage-and-apis.md","JavaScript 21 — Storage Architecture: localStorage, IndexedDB & Fetch API Internals",{"type":7,"value":8,"toc":3564},"minimark",[9,14,19,507,511,1432,1436,2014,2018,2415,2419,2987,2991,3278,3282,3290,3370,3560],[10,11,13],"h1",{"id":12},"_21-storage-architecture-localstorage-indexeddb-fetch-api-internals","21 — Storage Architecture: localStorage, IndexedDB & Fetch API Internals",[15,16,18],"h2",{"id":17},"localstorage-sessionstorage-synchronous-key-value","localStorage \u002F sessionStorage: Synchronous Key-Value",[20,21,23],"code-wrapper",{"language":22},"javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F ── localStorage: persistent, synchronous, ~5-10MB, strings only ──\n\u002F\u002F ── sessionStorage: per-tab, cleared on tab close ──\n\n\u002F\u002F ── API (synchronous — blocks the main thread) ──\nlocalStorage.setItem(\"key\", \"value\");    \u002F\u002F store (must be a string)\nlocalStorage.getItem(\"key\");              \u002F\u002F retrieve (returns string or null)\nlocalStorage.removeItem(\"key\");          \u002F\u002F delete one\nlocalStorage.clear();                     \u002F\u002F delete all\nlocalStorage.length;                      \u002F\u002F number of items\nlocalStorage.key(0);                     \u002F\u002F key at index (iteration)\n\n\u002F\u002F ── Storing objects (must serialize — only strings) ──\nconst config = { host: \"localhost\", port: 3000 };\nlocalStorage.setItem(\"config\", JSON.stringify(config));  \u002F\u002F serialize\nconst loaded = JSON.parse(localStorage.getItem(\"config\"));  \u002F\u002F deserialize\n\n\u002F\u002F ── ⚠️ localStorage is synchronous (blocks the main thread) ──\n\u002F\u002F ⚠️ Quota exceeded throws (can't catch with async — it's synchronous)\ntry {\n    localStorage.setItem(\"large\", \"x\".repeat(10_000_000));\n} catch (e) {\n    if (e.name === \"QuotaExceededError\") {\n        console.log(\"storage full\");  \u002F\u002F ~5-10MB limit\n    }\n}\n\n\u002F\u002F ── Storage event (cross-tab synchronization) ──\nwindow.addEventListener(\"storage\", (e) => {\n    \u002F\u002F Fires in OTHER tabs when localStorage changes (not the tab that made the change)\n    console.log(\"key:\", e.key);       \u002F\u002F changed key (null if clear())\n    console.log(\"old:\", e.oldValue);  \u002F\u002F old value (null if new key)\n    console.log(\"new:\", e.newValue);  \u002F\u002F new value (null if removed)\n});\n\u002F\u002F Use case: sync state across tabs (e.g., logout in one tab → all tabs update)\n","",[30,31,32,41,47,54,60,90,108,126,140,155,174,179,185,213,242,275,280,286,292,301,332,344,362,382,388,394,399,405,434,440,459,477,495,501],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F ── localStorage: persistent, synchronous, ~5-10MB, strings only ──\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"\u002F\u002F ── sessionStorage: per-tab, cleared on tab close ──\n",[33,48,50],{"class":35,"line":49},3,[33,51,53],{"emptyLinePlaceholder":52},true,"\n",[33,55,57],{"class":35,"line":56},4,[33,58,59],{"class":39},"\u002F\u002F ── API (synchronous — blocks the main thread) ──\n",[33,61,63,67,71,74,78,81,84,87],{"class":35,"line":62},5,[33,64,66],{"class":65},"ssxIu","localStorage.",[33,68,70],{"class":69},"sIsaT","setItem",[33,72,73],{"class":65},"(",[33,75,77],{"class":76},"sJ6F3","\"key\"",[33,79,80],{"class":65},", ",[33,82,83],{"class":76},"\"value\"",[33,85,86],{"class":65},");    ",[33,88,89],{"class":39},"\u002F\u002F store (must be a string)\n",[33,91,93,95,98,100,102,105],{"class":35,"line":92},6,[33,94,66],{"class":65},[33,96,97],{"class":69},"getItem",[33,99,73],{"class":65},[33,101,77],{"class":76},[33,103,104],{"class":65},");              ",[33,106,107],{"class":39},"\u002F\u002F retrieve (returns string or null)\n",[33,109,111,113,116,118,120,123],{"class":35,"line":110},7,[33,112,66],{"class":65},[33,114,115],{"class":69},"removeItem",[33,117,73],{"class":65},[33,119,77],{"class":76},[33,121,122],{"class":65},");          ",[33,124,125],{"class":39},"\u002F\u002F delete one\n",[33,127,129,131,134,137],{"class":35,"line":128},8,[33,130,66],{"class":65},[33,132,133],{"class":69},"clear",[33,135,136],{"class":65},"();                     ",[33,138,139],{"class":39},"\u002F\u002F delete all\n",[33,141,143,145,149,152],{"class":35,"line":142},9,[33,144,66],{"class":65},[33,146,148],{"class":147},"snvgF","length",[33,150,151],{"class":65},";                      ",[33,153,154],{"class":39},"\u002F\u002F number of items\n",[33,156,158,160,163,165,168,171],{"class":35,"line":157},10,[33,159,66],{"class":65},[33,161,162],{"class":69},"key",[33,164,73],{"class":65},[33,166,167],{"class":147},"0",[33,169,170],{"class":65},");                     ",[33,172,173],{"class":39},"\u002F\u002F key at index (iteration)\n",[33,175,177],{"class":35,"line":176},11,[33,178,53],{"emptyLinePlaceholder":52},[33,180,182],{"class":35,"line":181},12,[33,183,184],{"class":39},"\u002F\u002F ── Storing objects (must serialize — only strings) ──\n",[33,186,188,192,195,198,201,204,207,210],{"class":35,"line":187},13,[33,189,191],{"class":190},"svdQ7","const",[33,193,194],{"class":147}," config",[33,196,197],{"class":190}," =",[33,199,200],{"class":65}," { host: ",[33,202,203],{"class":76},"\"localhost\"",[33,205,206],{"class":65},", port: ",[33,208,209],{"class":147},"3000",[33,211,212],{"class":65}," };\n",[33,214,216,218,220,222,225,227,230,233,236,239],{"class":35,"line":215},14,[33,217,66],{"class":65},[33,219,70],{"class":69},[33,221,73],{"class":65},[33,223,224],{"class":76},"\"config\"",[33,226,80],{"class":65},[33,228,229],{"class":147},"JSON",[33,231,232],{"class":65},".",[33,234,235],{"class":69},"stringify",[33,237,238],{"class":65},"(config));  ",[33,240,241],{"class":39},"\u002F\u002F serialize\n",[33,243,245,247,250,252,255,257,260,263,265,267,269,272],{"class":35,"line":244},15,[33,246,191],{"class":190},[33,248,249],{"class":147}," loaded",[33,251,197],{"class":190},[33,253,254],{"class":147}," JSON",[33,256,232],{"class":65},[33,258,259],{"class":69},"parse",[33,261,262],{"class":65},"(localStorage.",[33,264,97],{"class":69},[33,266,73],{"class":65},[33,268,224],{"class":76},[33,270,271],{"class":65},"));  ",[33,273,274],{"class":39},"\u002F\u002F deserialize\n",[33,276,278],{"class":35,"line":277},16,[33,279,53],{"emptyLinePlaceholder":52},[33,281,283],{"class":35,"line":282},17,[33,284,285],{"class":39},"\u002F\u002F ── ⚠️ localStorage is synchronous (blocks the main thread) ──\n",[33,287,289],{"class":35,"line":288},18,[33,290,291],{"class":39},"\u002F\u002F ⚠️ Quota exceeded throws (can't catch with async — it's synchronous)\n",[33,293,295,298],{"class":35,"line":294},19,[33,296,297],{"class":190},"try",[33,299,300],{"class":65}," {\n",[33,302,304,307,309,311,314,316,319,321,324,326,329],{"class":35,"line":303},20,[33,305,306],{"class":65},"    localStorage.",[33,308,70],{"class":69},[33,310,73],{"class":65},[33,312,313],{"class":76},"\"large\"",[33,315,80],{"class":65},[33,317,318],{"class":76},"\"x\"",[33,320,232],{"class":65},[33,322,323],{"class":69},"repeat",[33,325,73],{"class":65},[33,327,328],{"class":147},"10_000_000",[33,330,331],{"class":65},"));\n",[33,333,335,338,341],{"class":35,"line":334},21,[33,336,337],{"class":65},"} ",[33,339,340],{"class":190},"catch",[33,342,343],{"class":65}," (e) {\n",[33,345,347,350,353,356,359],{"class":35,"line":346},22,[33,348,349],{"class":190},"    if",[33,351,352],{"class":65}," (e.name ",[33,354,355],{"class":190},"===",[33,357,358],{"class":76}," \"QuotaExceededError\"",[33,360,361],{"class":65},") {\n",[33,363,365,368,371,373,376,379],{"class":35,"line":364},23,[33,366,367],{"class":65},"        console.",[33,369,370],{"class":69},"log",[33,372,73],{"class":65},[33,374,375],{"class":76},"\"storage full\"",[33,377,378],{"class":65},");  ",[33,380,381],{"class":39},"\u002F\u002F ~5-10MB limit\n",[33,383,385],{"class":35,"line":384},24,[33,386,387],{"class":65},"    }\n",[33,389,391],{"class":35,"line":390},25,[33,392,393],{"class":65},"}\n",[33,395,397],{"class":35,"line":396},26,[33,398,53],{"emptyLinePlaceholder":52},[33,400,402],{"class":35,"line":401},27,[33,403,404],{"class":39},"\u002F\u002F ── Storage event (cross-tab synchronization) ──\n",[33,406,408,411,414,416,419,422,426,429,432],{"class":35,"line":407},28,[33,409,410],{"class":65},"window.",[33,412,413],{"class":69},"addEventListener",[33,415,73],{"class":65},[33,417,418],{"class":76},"\"storage\"",[33,420,421],{"class":65},", (",[33,423,425],{"class":424},"sCrzJ","e",[33,427,428],{"class":65},") ",[33,430,431],{"class":190},"=>",[33,433,300],{"class":65},[33,435,437],{"class":35,"line":436},29,[33,438,439],{"class":39},"    \u002F\u002F Fires in OTHER tabs when localStorage changes (not the tab that made the change)\n",[33,441,443,446,448,450,453,456],{"class":35,"line":442},30,[33,444,445],{"class":65},"    console.",[33,447,370],{"class":69},[33,449,73],{"class":65},[33,451,452],{"class":76},"\"key:\"",[33,454,455],{"class":65},", e.key);       ",[33,457,458],{"class":39},"\u002F\u002F changed key (null if clear())\n",[33,460,462,464,466,468,471,474],{"class":35,"line":461},31,[33,463,445],{"class":65},[33,465,370],{"class":69},[33,467,73],{"class":65},[33,469,470],{"class":76},"\"old:\"",[33,472,473],{"class":65},", e.oldValue);  ",[33,475,476],{"class":39},"\u002F\u002F old value (null if new key)\n",[33,478,480,482,484,486,489,492],{"class":35,"line":479},32,[33,481,445],{"class":65},[33,483,370],{"class":69},[33,485,73],{"class":65},[33,487,488],{"class":76},"\"new:\"",[33,490,491],{"class":65},", e.newValue);  ",[33,493,494],{"class":39},"\u002F\u002F new value (null if removed)\n",[33,496,498],{"class":35,"line":497},33,[33,499,500],{"class":65},"});\n",[33,502,504],{"class":35,"line":503},34,[33,505,506],{"class":39},"\u002F\u002F Use case: sync state across tabs (e.g., logout in one tab → all tabs update)\n",[15,508,510],{"id":509},"indexeddb-asynchronous-transactional-store","IndexedDB: Asynchronous Transactional Store",[20,512,513],{"language":22},[24,514,516],{"className":26,"code":515,"language":22,"meta":28,"style":28},"\u002F\u002F ── IndexedDB: async, transactional, ~50MB-unlimited, stores objects ──\n\u002F\u002F Much more powerful than localStorage, but complex API.\n\n\u002F\u002F ── Open a database ──\nconst request = indexedDB.open(\"MyApp\", 1);  \u002F\u002F name, version\nrequest.onupgradeneeded = (e) => {\n    \u002F\u002F Called when the DB is created or version changes — create stores here\n    const db = e.target.result;\n    if (!db.objectStoreNames.contains(\"users\")) {\n        const store = db.createObjectStore(\"users\", { keyPath: \"id\" });\n        store.createIndex(\"email\", \"email\", { unique: true });  \u002F\u002F index for queries\n    }\n};\nrequest.onsuccess = (e) => {\n    const db = e.target.result;\n    \u002F\u002F Use the database...\n};\n\n\u002F\u002F ── Promisified IndexedDB wrapper ──\nfunction idbGetAll(db, storeName) {\n    return new Promise((resolve, reject) => {\n        const tx = db.transaction(storeName, \"readonly\");\n        const store = tx.objectStore(storeName);\n        const req = store.getAll();\n        req.onsuccess = () => resolve(req.result);\n        req.onerror = () => reject(req.error);\n    });\n}\n\n\u002F\u002F ── Full promisified wrapper (modern approach) ──\nconst openDB = (name, version, onUpgrade) =>\n    new Promise((resolve, reject) => {\n        const req = indexedDB.open(name, version);\n        req.onupgradeneeded = (e) => onUpgrade(e.target.result);\n        req.onsuccess = () => resolve(req.result);\n        req.onerror = () => reject(req.error);\n    });\n\nasync function dbExample() {\n    const db = await openDB(\"MyApp\", 1, (db) => {\n        const store = db.createObjectStore(\"users\", { keyPath: \"id\" });\n        store.createIndex(\"email\", \"email\", { unique: true });\n    });\n\n    \u002F\u002F Add (transaction → store → add)\n    const tx = db.transaction(\"users\", \"readwrite\");\n    await new Promise((res, rej) => {\n        const req = tx.objectStore(\"users\").add({ id: 1, name: \"Alice\", email: \"a@b.com\" });\n        req.onsuccess = res;\n        req.onerror = () => rej(req.error);\n    });\n\n    \u002F\u002F Query by index\n    const emailIndex = db.transaction(\"users\", \"readonly\").objectStore(\"users\").index(\"email\");\n    const user = await new Promise((res, rej) => {\n        const req = emailIndex.get(\"a@b.com\");\n        req.onsuccess = () => res(req.result);\n        req.onerror = () => rej(req.error);\n    });\n    console.log(user);  \u002F\u002F { id: 1, name: \"Alice\", email: \"a@b.com\" }\n}\n",[30,517,518,523,528,532,537,567,588,593,606,629,658,687,691,696,715,725,730,734,738,743,763,791,814,831,849,869,888,893,897,901,906,935,956,971,993,1010,1027,1032,1037,1052,1084,1107,1128,1133,1138,1144,1168,1194,1236,1248,1266,1271,1276,1282,1323,1353,1374,1392,1409,1414,1427],{"__ignoreMap":28},[33,519,520],{"class":35,"line":36},[33,521,522],{"class":39},"\u002F\u002F ── IndexedDB: async, transactional, ~50MB-unlimited, stores objects ──\n",[33,524,525],{"class":35,"line":43},[33,526,527],{"class":39},"\u002F\u002F Much more powerful than localStorage, but complex API.\n",[33,529,530],{"class":35,"line":49},[33,531,53],{"emptyLinePlaceholder":52},[33,533,534],{"class":35,"line":56},[33,535,536],{"class":39},"\u002F\u002F ── Open a database ──\n",[33,538,539,541,544,546,549,552,554,557,559,562,564],{"class":35,"line":62},[33,540,191],{"class":190},[33,542,543],{"class":147}," request",[33,545,197],{"class":190},[33,547,548],{"class":65}," indexedDB.",[33,550,551],{"class":69},"open",[33,553,73],{"class":65},[33,555,556],{"class":76},"\"MyApp\"",[33,558,80],{"class":65},[33,560,561],{"class":147},"1",[33,563,378],{"class":65},[33,565,566],{"class":39},"\u002F\u002F name, version\n",[33,568,569,572,575,577,580,582,584,586],{"class":35,"line":92},[33,570,571],{"class":65},"request.",[33,573,574],{"class":69},"onupgradeneeded",[33,576,197],{"class":190},[33,578,579],{"class":65}," (",[33,581,425],{"class":424},[33,583,428],{"class":65},[33,585,431],{"class":190},[33,587,300],{"class":65},[33,589,590],{"class":35,"line":110},[33,591,592],{"class":39},"    \u002F\u002F Called when the DB is created or version changes — create stores here\n",[33,594,595,598,601,603],{"class":35,"line":128},[33,596,597],{"class":190},"    const",[33,599,600],{"class":147}," db",[33,602,197],{"class":190},[33,604,605],{"class":65}," e.target.result;\n",[33,607,608,610,612,615,618,621,623,626],{"class":35,"line":142},[33,609,349],{"class":190},[33,611,579],{"class":65},[33,613,614],{"class":190},"!",[33,616,617],{"class":65},"db.objectStoreNames.",[33,619,620],{"class":69},"contains",[33,622,73],{"class":65},[33,624,625],{"class":76},"\"users\"",[33,627,628],{"class":65},")) {\n",[33,630,631,634,637,639,642,645,647,649,652,655],{"class":35,"line":157},[33,632,633],{"class":190},"        const",[33,635,636],{"class":147}," store",[33,638,197],{"class":190},[33,640,641],{"class":65}," db.",[33,643,644],{"class":69},"createObjectStore",[33,646,73],{"class":65},[33,648,625],{"class":76},[33,650,651],{"class":65},", { keyPath: ",[33,653,654],{"class":76},"\"id\"",[33,656,657],{"class":65}," });\n",[33,659,660,663,666,668,671,673,675,678,681,684],{"class":35,"line":176},[33,661,662],{"class":65},"        store.",[33,664,665],{"class":69},"createIndex",[33,667,73],{"class":65},[33,669,670],{"class":76},"\"email\"",[33,672,80],{"class":65},[33,674,670],{"class":76},[33,676,677],{"class":65},", { unique: ",[33,679,680],{"class":147},"true",[33,682,683],{"class":65}," });  ",[33,685,686],{"class":39},"\u002F\u002F index for queries\n",[33,688,689],{"class":35,"line":181},[33,690,387],{"class":65},[33,692,693],{"class":35,"line":187},[33,694,695],{"class":65},"};\n",[33,697,698,700,703,705,707,709,711,713],{"class":35,"line":215},[33,699,571],{"class":65},[33,701,702],{"class":69},"onsuccess",[33,704,197],{"class":190},[33,706,579],{"class":65},[33,708,425],{"class":424},[33,710,428],{"class":65},[33,712,431],{"class":190},[33,714,300],{"class":65},[33,716,717,719,721,723],{"class":35,"line":244},[33,718,597],{"class":190},[33,720,600],{"class":147},[33,722,197],{"class":190},[33,724,605],{"class":65},[33,726,727],{"class":35,"line":277},[33,728,729],{"class":39},"    \u002F\u002F Use the database...\n",[33,731,732],{"class":35,"line":282},[33,733,695],{"class":65},[33,735,736],{"class":35,"line":288},[33,737,53],{"emptyLinePlaceholder":52},[33,739,740],{"class":35,"line":294},[33,741,742],{"class":39},"\u002F\u002F ── Promisified IndexedDB wrapper ──\n",[33,744,745,748,751,753,756,758,761],{"class":35,"line":303},[33,746,747],{"class":190},"function",[33,749,750],{"class":69}," idbGetAll",[33,752,73],{"class":65},[33,754,755],{"class":424},"db",[33,757,80],{"class":65},[33,759,760],{"class":424},"storeName",[33,762,361],{"class":65},[33,764,765,768,771,774,777,780,782,785,787,789],{"class":35,"line":334},[33,766,767],{"class":190},"    return",[33,769,770],{"class":190}," new",[33,772,773],{"class":147}," Promise",[33,775,776],{"class":65},"((",[33,778,779],{"class":424},"resolve",[33,781,80],{"class":65},[33,783,784],{"class":424},"reject",[33,786,428],{"class":65},[33,788,431],{"class":190},[33,790,300],{"class":65},[33,792,793,795,798,800,802,805,808,811],{"class":35,"line":346},[33,794,633],{"class":190},[33,796,797],{"class":147}," tx",[33,799,197],{"class":190},[33,801,641],{"class":65},[33,803,804],{"class":69},"transaction",[33,806,807],{"class":65},"(storeName, ",[33,809,810],{"class":76},"\"readonly\"",[33,812,813],{"class":65},");\n",[33,815,816,818,820,822,825,828],{"class":35,"line":364},[33,817,633],{"class":190},[33,819,636],{"class":147},[33,821,197],{"class":190},[33,823,824],{"class":65}," tx.",[33,826,827],{"class":69},"objectStore",[33,829,830],{"class":65},"(storeName);\n",[33,832,833,835,838,840,843,846],{"class":35,"line":384},[33,834,633],{"class":190},[33,836,837],{"class":147}," req",[33,839,197],{"class":190},[33,841,842],{"class":65}," store.",[33,844,845],{"class":69},"getAll",[33,847,848],{"class":65},"();\n",[33,850,851,854,856,858,861,863,866],{"class":35,"line":390},[33,852,853],{"class":65},"        req.",[33,855,702],{"class":69},[33,857,197],{"class":190},[33,859,860],{"class":65}," () ",[33,862,431],{"class":190},[33,864,865],{"class":69}," resolve",[33,867,868],{"class":65},"(req.result);\n",[33,870,871,873,876,878,880,882,885],{"class":35,"line":396},[33,872,853],{"class":65},[33,874,875],{"class":69},"onerror",[33,877,197],{"class":190},[33,879,860],{"class":65},[33,881,431],{"class":190},[33,883,884],{"class":69}," reject",[33,886,887],{"class":65},"(req.error);\n",[33,889,890],{"class":35,"line":401},[33,891,892],{"class":65},"    });\n",[33,894,895],{"class":35,"line":407},[33,896,393],{"class":65},[33,898,899],{"class":35,"line":436},[33,900,53],{"emptyLinePlaceholder":52},[33,902,903],{"class":35,"line":442},[33,904,905],{"class":39},"\u002F\u002F ── Full promisified wrapper (modern approach) ──\n",[33,907,908,910,913,915,917,920,922,925,927,930,932],{"class":35,"line":461},[33,909,191],{"class":190},[33,911,912],{"class":69}," openDB",[33,914,197],{"class":190},[33,916,579],{"class":65},[33,918,919],{"class":424},"name",[33,921,80],{"class":65},[33,923,924],{"class":424},"version",[33,926,80],{"class":65},[33,928,929],{"class":424},"onUpgrade",[33,931,428],{"class":65},[33,933,934],{"class":190},"=>\n",[33,936,937,940,942,944,946,948,950,952,954],{"class":35,"line":479},[33,938,939],{"class":190},"    new",[33,941,773],{"class":147},[33,943,776],{"class":65},[33,945,779],{"class":424},[33,947,80],{"class":65},[33,949,784],{"class":424},[33,951,428],{"class":65},[33,953,431],{"class":190},[33,955,300],{"class":65},[33,957,958,960,962,964,966,968],{"class":35,"line":497},[33,959,633],{"class":190},[33,961,837],{"class":147},[33,963,197],{"class":190},[33,965,548],{"class":65},[33,967,551],{"class":69},[33,969,970],{"class":65},"(name, version);\n",[33,972,973,975,977,979,981,983,985,987,990],{"class":35,"line":503},[33,974,853],{"class":65},[33,976,574],{"class":69},[33,978,197],{"class":190},[33,980,579],{"class":65},[33,982,425],{"class":424},[33,984,428],{"class":65},[33,986,431],{"class":190},[33,988,989],{"class":69}," onUpgrade",[33,991,992],{"class":65},"(e.target.result);\n",[33,994,996,998,1000,1002,1004,1006,1008],{"class":35,"line":995},35,[33,997,853],{"class":65},[33,999,702],{"class":69},[33,1001,197],{"class":190},[33,1003,860],{"class":65},[33,1005,431],{"class":190},[33,1007,865],{"class":69},[33,1009,868],{"class":65},[33,1011,1013,1015,1017,1019,1021,1023,1025],{"class":35,"line":1012},36,[33,1014,853],{"class":65},[33,1016,875],{"class":69},[33,1018,197],{"class":190},[33,1020,860],{"class":65},[33,1022,431],{"class":190},[33,1024,884],{"class":69},[33,1026,887],{"class":65},[33,1028,1030],{"class":35,"line":1029},37,[33,1031,892],{"class":65},[33,1033,1035],{"class":35,"line":1034},38,[33,1036,53],{"emptyLinePlaceholder":52},[33,1038,1040,1043,1046,1049],{"class":35,"line":1039},39,[33,1041,1042],{"class":190},"async",[33,1044,1045],{"class":190}," function",[33,1047,1048],{"class":69}," dbExample",[33,1050,1051],{"class":65},"() {\n",[33,1053,1055,1057,1059,1061,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082],{"class":35,"line":1054},40,[33,1056,597],{"class":190},[33,1058,600],{"class":147},[33,1060,197],{"class":190},[33,1062,1063],{"class":190}," await",[33,1065,912],{"class":69},[33,1067,73],{"class":65},[33,1069,556],{"class":76},[33,1071,80],{"class":65},[33,1073,561],{"class":147},[33,1075,421],{"class":65},[33,1077,755],{"class":424},[33,1079,428],{"class":65},[33,1081,431],{"class":190},[33,1083,300],{"class":65},[33,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105],{"class":35,"line":1086},41,[33,1088,633],{"class":190},[33,1090,636],{"class":147},[33,1092,197],{"class":190},[33,1094,641],{"class":65},[33,1096,644],{"class":69},[33,1098,73],{"class":65},[33,1100,625],{"class":76},[33,1102,651],{"class":65},[33,1104,654],{"class":76},[33,1106,657],{"class":65},[33,1108,1110,1112,1114,1116,1118,1120,1122,1124,1126],{"class":35,"line":1109},42,[33,1111,662],{"class":65},[33,1113,665],{"class":69},[33,1115,73],{"class":65},[33,1117,670],{"class":76},[33,1119,80],{"class":65},[33,1121,670],{"class":76},[33,1123,677],{"class":65},[33,1125,680],{"class":147},[33,1127,657],{"class":65},[33,1129,1131],{"class":35,"line":1130},43,[33,1132,892],{"class":65},[33,1134,1136],{"class":35,"line":1135},44,[33,1137,53],{"emptyLinePlaceholder":52},[33,1139,1141],{"class":35,"line":1140},45,[33,1142,1143],{"class":39},"    \u002F\u002F Add (transaction → store → add)\n",[33,1145,1147,1149,1151,1153,1155,1157,1159,1161,1163,1166],{"class":35,"line":1146},46,[33,1148,597],{"class":190},[33,1150,797],{"class":147},[33,1152,197],{"class":190},[33,1154,641],{"class":65},[33,1156,804],{"class":69},[33,1158,73],{"class":65},[33,1160,625],{"class":76},[33,1162,80],{"class":65},[33,1164,1165],{"class":76},"\"readwrite\"",[33,1167,813],{"class":65},[33,1169,1171,1174,1176,1178,1180,1183,1185,1188,1190,1192],{"class":35,"line":1170},47,[33,1172,1173],{"class":190},"    await",[33,1175,770],{"class":190},[33,1177,773],{"class":147},[33,1179,776],{"class":65},[33,1181,1182],{"class":424},"res",[33,1184,80],{"class":65},[33,1186,1187],{"class":424},"rej",[33,1189,428],{"class":65},[33,1191,431],{"class":190},[33,1193,300],{"class":65},[33,1195,1197,1199,1201,1203,1205,1207,1209,1211,1214,1217,1220,1222,1225,1228,1231,1234],{"class":35,"line":1196},48,[33,1198,633],{"class":190},[33,1200,837],{"class":147},[33,1202,197],{"class":190},[33,1204,824],{"class":65},[33,1206,827],{"class":69},[33,1208,73],{"class":65},[33,1210,625],{"class":76},[33,1212,1213],{"class":65},").",[33,1215,1216],{"class":69},"add",[33,1218,1219],{"class":65},"({ id: ",[33,1221,561],{"class":147},[33,1223,1224],{"class":65},", name: ",[33,1226,1227],{"class":76},"\"Alice\"",[33,1229,1230],{"class":65},", email: ",[33,1232,1233],{"class":76},"\"a@b.com\"",[33,1235,657],{"class":65},[33,1237,1239,1242,1245],{"class":35,"line":1238},49,[33,1240,1241],{"class":65},"        req.onsuccess ",[33,1243,1244],{"class":190},"=",[33,1246,1247],{"class":65}," res;\n",[33,1249,1251,1253,1255,1257,1259,1261,1264],{"class":35,"line":1250},50,[33,1252,853],{"class":65},[33,1254,875],{"class":69},[33,1256,197],{"class":190},[33,1258,860],{"class":65},[33,1260,431],{"class":190},[33,1262,1263],{"class":69}," rej",[33,1265,887],{"class":65},[33,1267,1269],{"class":35,"line":1268},51,[33,1270,892],{"class":65},[33,1272,1274],{"class":35,"line":1273},52,[33,1275,53],{"emptyLinePlaceholder":52},[33,1277,1279],{"class":35,"line":1278},53,[33,1280,1281],{"class":39},"    \u002F\u002F Query by index\n",[33,1283,1285,1287,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1317,1319,1321],{"class":35,"line":1284},54,[33,1286,597],{"class":190},[33,1288,1289],{"class":147}," emailIndex",[33,1291,197],{"class":190},[33,1293,641],{"class":65},[33,1295,804],{"class":69},[33,1297,73],{"class":65},[33,1299,625],{"class":76},[33,1301,80],{"class":65},[33,1303,810],{"class":76},[33,1305,1213],{"class":65},[33,1307,827],{"class":69},[33,1309,73],{"class":65},[33,1311,625],{"class":76},[33,1313,1213],{"class":65},[33,1315,1316],{"class":69},"index",[33,1318,73],{"class":65},[33,1320,670],{"class":76},[33,1322,813],{"class":65},[33,1324,1326,1328,1331,1333,1335,1337,1339,1341,1343,1345,1347,1349,1351],{"class":35,"line":1325},55,[33,1327,597],{"class":190},[33,1329,1330],{"class":147}," user",[33,1332,197],{"class":190},[33,1334,1063],{"class":190},[33,1336,770],{"class":190},[33,1338,773],{"class":147},[33,1340,776],{"class":65},[33,1342,1182],{"class":424},[33,1344,80],{"class":65},[33,1346,1187],{"class":424},[33,1348,428],{"class":65},[33,1350,431],{"class":190},[33,1352,300],{"class":65},[33,1354,1356,1358,1360,1362,1365,1368,1370,1372],{"class":35,"line":1355},56,[33,1357,633],{"class":190},[33,1359,837],{"class":147},[33,1361,197],{"class":190},[33,1363,1364],{"class":65}," emailIndex.",[33,1366,1367],{"class":69},"get",[33,1369,73],{"class":65},[33,1371,1233],{"class":76},[33,1373,813],{"class":65},[33,1375,1377,1379,1381,1383,1385,1387,1390],{"class":35,"line":1376},57,[33,1378,853],{"class":65},[33,1380,702],{"class":69},[33,1382,197],{"class":190},[33,1384,860],{"class":65},[33,1386,431],{"class":190},[33,1388,1389],{"class":69}," res",[33,1391,868],{"class":65},[33,1393,1395,1397,1399,1401,1403,1405,1407],{"class":35,"line":1394},58,[33,1396,853],{"class":65},[33,1398,875],{"class":69},[33,1400,197],{"class":190},[33,1402,860],{"class":65},[33,1404,431],{"class":190},[33,1406,1263],{"class":69},[33,1408,887],{"class":65},[33,1410,1412],{"class":35,"line":1411},59,[33,1413,892],{"class":65},[33,1415,1417,1419,1421,1424],{"class":35,"line":1416},60,[33,1418,445],{"class":65},[33,1420,370],{"class":69},[33,1422,1423],{"class":65},"(user);  ",[33,1425,1426],{"class":39},"\u002F\u002F { id: 1, name: \"Alice\", email: \"a@b.com\" }\n",[33,1428,1430],{"class":35,"line":1429},61,[33,1431,393],{"class":65},[15,1433,1435],{"id":1434},"fetch-api-requestresponse-and-streaming","Fetch API: Request\u002FResponse and Streaming",[20,1437,1438],{"language":22},[24,1439,1441],{"className":26,"code":1440,"language":22,"meta":28,"style":28},"\u002F\u002F ── Basic fetch (returns a Promise\u003CResponse>) ──\nconst response = await fetch(\"\u002Fapi\u002Fdata\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application\u002Fjson\" },\n    body: JSON.stringify({ name: \"Alice\" }),\n});\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst data = await response.json();  \u002F\u002F parse JSON (returns a Promise)\n\n\u002F\u002F ── Response properties ──\nresponse.ok;           \u002F\u002F true if status is 200-299\nresponse.status;       \u002F\u002F HTTP status code (200, 404, 500, etc.)\nresponse.headers.get(\"Content-Type\");  \u002F\u002F read response headers\nresponse.redirected;   \u002F\u002F true if redirected\nresponse.type;         \u002F\u002F \"basic\", \"cors\", \"opaque\", \"error\"\n\n\u002F\u002F ── Body consumption methods (each can only be called ONCE — body is a stream) ──\nawait response.json();     \u002F\u002F parse as JSON\nawait response.text();     \u002F\u002F read as text\nawait response.blob();     \u002F\u002F read as Blob (binary data)\nawait response.arrayBuffer(); \u002F\u002F read as ArrayBuffer (raw bytes)\nawait response.formData(); \u002F\u002F parse as FormData\n\n\u002F\u002F ── Streaming response (for large data — process chunks) ──\nconst response = await fetch(\"\u002Fapi\u002Fstream\");\nconst reader = response.body.getReader();\nconst decoder = new TextDecoder();\nwhile (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n    console.log(decoder.decode(value, { stream: true }));  \u002F\u002F process each chunk\n}\n\n\u002F\u002F ── AbortController for cancellation ──\nconst controller = new AbortController();\nconst timer = setTimeout(() => controller.abort(), 5000);\ntry {\n    const response = await fetch(\"\u002Fapi\u002Fslow\", { signal: controller.signal });\n    clearTimeout(timer);\n    const data = await response.json();\n} catch (e) {\n    if (e.name === \"AbortError\") console.log(\"cancelled\");\n    else throw e;\n}\n",[30,1442,1443,1448,1470,1481,1498,1517,1521,1559,1582,1586,1591,1599,1607,1623,1631,1639,1643,1648,1663,1677,1691,1706,1720,1724,1729,1748,1765,1781,1792,1822,1835,1858,1862,1866,1871,1887,1918,1924,1944,1952,1968,1976,1999,2010],{"__ignoreMap":28},[33,1444,1445],{"class":35,"line":36},[33,1446,1447],{"class":39},"\u002F\u002F ── Basic fetch (returns a Promise\u003CResponse>) ──\n",[33,1449,1450,1452,1455,1457,1459,1462,1464,1467],{"class":35,"line":43},[33,1451,191],{"class":190},[33,1453,1454],{"class":147}," response",[33,1456,197],{"class":190},[33,1458,1063],{"class":190},[33,1460,1461],{"class":69}," fetch",[33,1463,73],{"class":65},[33,1465,1466],{"class":76},"\"\u002Fapi\u002Fdata\"",[33,1468,1469],{"class":65},", {\n",[33,1471,1472,1475,1478],{"class":35,"line":49},[33,1473,1474],{"class":65},"    method: ",[33,1476,1477],{"class":76},"\"POST\"",[33,1479,1480],{"class":65},",\n",[33,1482,1483,1486,1489,1492,1495],{"class":35,"line":56},[33,1484,1485],{"class":65},"    headers: { ",[33,1487,1488],{"class":76},"\"Content-Type\"",[33,1490,1491],{"class":65},": ",[33,1493,1494],{"class":76},"\"application\u002Fjson\"",[33,1496,1497],{"class":65}," },\n",[33,1499,1500,1503,1505,1507,1509,1512,1514],{"class":35,"line":62},[33,1501,1502],{"class":65},"    body: ",[33,1504,229],{"class":147},[33,1506,232],{"class":65},[33,1508,235],{"class":69},[33,1510,1511],{"class":65},"({ name: ",[33,1513,1227],{"class":76},[33,1515,1516],{"class":65}," }),\n",[33,1518,1519],{"class":35,"line":92},[33,1520,500],{"class":65},[33,1522,1523,1526,1528,1530,1533,1536,1538,1541,1543,1546,1549,1551,1554,1557],{"class":35,"line":110},[33,1524,1525],{"class":190},"if",[33,1527,579],{"class":65},[33,1529,614],{"class":190},[33,1531,1532],{"class":65},"response.ok) ",[33,1534,1535],{"class":190},"throw",[33,1537,770],{"class":190},[33,1539,1540],{"class":69}," Error",[33,1542,73],{"class":65},[33,1544,1545],{"class":76},"`HTTP ${",[33,1547,1548],{"class":65},"response",[33,1550,232],{"class":76},[33,1552,1553],{"class":65},"status",[33,1555,1556],{"class":76},"}`",[33,1558,813],{"class":65},[33,1560,1561,1563,1566,1568,1570,1573,1576,1579],{"class":35,"line":128},[33,1562,191],{"class":190},[33,1564,1565],{"class":147}," data",[33,1567,197],{"class":190},[33,1569,1063],{"class":190},[33,1571,1572],{"class":65}," response.",[33,1574,1575],{"class":69},"json",[33,1577,1578],{"class":65},"();  ",[33,1580,1581],{"class":39},"\u002F\u002F parse JSON (returns a Promise)\n",[33,1583,1584],{"class":35,"line":142},[33,1585,53],{"emptyLinePlaceholder":52},[33,1587,1588],{"class":35,"line":157},[33,1589,1590],{"class":39},"\u002F\u002F ── Response properties ──\n",[33,1592,1593,1596],{"class":35,"line":176},[33,1594,1595],{"class":65},"response.ok;           ",[33,1597,1598],{"class":39},"\u002F\u002F true if status is 200-299\n",[33,1600,1601,1604],{"class":35,"line":181},[33,1602,1603],{"class":65},"response.status;       ",[33,1605,1606],{"class":39},"\u002F\u002F HTTP status code (200, 404, 500, etc.)\n",[33,1608,1609,1612,1614,1616,1618,1620],{"class":35,"line":187},[33,1610,1611],{"class":65},"response.headers.",[33,1613,1367],{"class":69},[33,1615,73],{"class":65},[33,1617,1488],{"class":76},[33,1619,378],{"class":65},[33,1621,1622],{"class":39},"\u002F\u002F read response headers\n",[33,1624,1625,1628],{"class":35,"line":215},[33,1626,1627],{"class":65},"response.redirected;   ",[33,1629,1630],{"class":39},"\u002F\u002F true if redirected\n",[33,1632,1633,1636],{"class":35,"line":244},[33,1634,1635],{"class":65},"response.type;         ",[33,1637,1638],{"class":39},"\u002F\u002F \"basic\", \"cors\", \"opaque\", \"error\"\n",[33,1640,1641],{"class":35,"line":277},[33,1642,53],{"emptyLinePlaceholder":52},[33,1644,1645],{"class":35,"line":282},[33,1646,1647],{"class":39},"\u002F\u002F ── Body consumption methods (each can only be called ONCE — body is a stream) ──\n",[33,1649,1650,1653,1655,1657,1660],{"class":35,"line":288},[33,1651,1652],{"class":190},"await",[33,1654,1572],{"class":65},[33,1656,1575],{"class":69},[33,1658,1659],{"class":65},"();     ",[33,1661,1662],{"class":39},"\u002F\u002F parse as JSON\n",[33,1664,1665,1667,1669,1672,1674],{"class":35,"line":294},[33,1666,1652],{"class":190},[33,1668,1572],{"class":65},[33,1670,1671],{"class":69},"text",[33,1673,1659],{"class":65},[33,1675,1676],{"class":39},"\u002F\u002F read as text\n",[33,1678,1679,1681,1683,1686,1688],{"class":35,"line":303},[33,1680,1652],{"class":190},[33,1682,1572],{"class":65},[33,1684,1685],{"class":69},"blob",[33,1687,1659],{"class":65},[33,1689,1690],{"class":39},"\u002F\u002F read as Blob (binary data)\n",[33,1692,1693,1695,1697,1700,1703],{"class":35,"line":334},[33,1694,1652],{"class":190},[33,1696,1572],{"class":65},[33,1698,1699],{"class":69},"arrayBuffer",[33,1701,1702],{"class":65},"(); ",[33,1704,1705],{"class":39},"\u002F\u002F read as ArrayBuffer (raw bytes)\n",[33,1707,1708,1710,1712,1715,1717],{"class":35,"line":346},[33,1709,1652],{"class":190},[33,1711,1572],{"class":65},[33,1713,1714],{"class":69},"formData",[33,1716,1702],{"class":65},[33,1718,1719],{"class":39},"\u002F\u002F parse as FormData\n",[33,1721,1722],{"class":35,"line":364},[33,1723,53],{"emptyLinePlaceholder":52},[33,1725,1726],{"class":35,"line":384},[33,1727,1728],{"class":39},"\u002F\u002F ── Streaming response (for large data — process chunks) ──\n",[33,1730,1731,1733,1735,1737,1739,1741,1743,1746],{"class":35,"line":390},[33,1732,191],{"class":190},[33,1734,1454],{"class":147},[33,1736,197],{"class":190},[33,1738,1063],{"class":190},[33,1740,1461],{"class":69},[33,1742,73],{"class":65},[33,1744,1745],{"class":76},"\"\u002Fapi\u002Fstream\"",[33,1747,813],{"class":65},[33,1749,1750,1752,1755,1757,1760,1763],{"class":35,"line":396},[33,1751,191],{"class":190},[33,1753,1754],{"class":147}," reader",[33,1756,197],{"class":190},[33,1758,1759],{"class":65}," response.body.",[33,1761,1762],{"class":69},"getReader",[33,1764,848],{"class":65},[33,1766,1767,1769,1772,1774,1776,1779],{"class":35,"line":401},[33,1768,191],{"class":190},[33,1770,1771],{"class":147}," decoder",[33,1773,197],{"class":190},[33,1775,770],{"class":190},[33,1777,1778],{"class":69}," TextDecoder",[33,1780,848],{"class":65},[33,1782,1783,1786,1788,1790],{"class":35,"line":407},[33,1784,1785],{"class":190},"while",[33,1787,579],{"class":65},[33,1789,680],{"class":147},[33,1791,361],{"class":65},[33,1793,1794,1796,1799,1802,1804,1807,1810,1812,1814,1817,1820],{"class":35,"line":436},[33,1795,597],{"class":190},[33,1797,1798],{"class":65}," { ",[33,1800,1801],{"class":147},"done",[33,1803,80],{"class":65},[33,1805,1806],{"class":147},"value",[33,1808,1809],{"class":65}," } ",[33,1811,1244],{"class":190},[33,1813,1063],{"class":190},[33,1815,1816],{"class":65}," reader.",[33,1818,1819],{"class":69},"read",[33,1821,848],{"class":65},[33,1823,1824,1826,1829,1832],{"class":35,"line":442},[33,1825,349],{"class":190},[33,1827,1828],{"class":65}," (done) ",[33,1830,1831],{"class":190},"break",[33,1833,1834],{"class":65},";\n",[33,1836,1837,1839,1841,1844,1847,1850,1852,1855],{"class":35,"line":461},[33,1838,445],{"class":65},[33,1840,370],{"class":69},[33,1842,1843],{"class":65},"(decoder.",[33,1845,1846],{"class":69},"decode",[33,1848,1849],{"class":65},"(value, { stream: ",[33,1851,680],{"class":147},[33,1853,1854],{"class":65}," }));  ",[33,1856,1857],{"class":39},"\u002F\u002F process each chunk\n",[33,1859,1860],{"class":35,"line":479},[33,1861,393],{"class":65},[33,1863,1864],{"class":35,"line":497},[33,1865,53],{"emptyLinePlaceholder":52},[33,1867,1868],{"class":35,"line":503},[33,1869,1870],{"class":39},"\u002F\u002F ── AbortController for cancellation ──\n",[33,1872,1873,1875,1878,1880,1882,1885],{"class":35,"line":995},[33,1874,191],{"class":190},[33,1876,1877],{"class":147}," controller",[33,1879,197],{"class":190},[33,1881,770],{"class":190},[33,1883,1884],{"class":69}," AbortController",[33,1886,848],{"class":65},[33,1888,1889,1891,1894,1896,1899,1902,1904,1907,1910,1913,1916],{"class":35,"line":1012},[33,1890,191],{"class":190},[33,1892,1893],{"class":147}," timer",[33,1895,197],{"class":190},[33,1897,1898],{"class":69}," setTimeout",[33,1900,1901],{"class":65},"(() ",[33,1903,431],{"class":190},[33,1905,1906],{"class":65}," controller.",[33,1908,1909],{"class":69},"abort",[33,1911,1912],{"class":65},"(), ",[33,1914,1915],{"class":147},"5000",[33,1917,813],{"class":65},[33,1919,1920,1922],{"class":35,"line":1029},[33,1921,297],{"class":190},[33,1923,300],{"class":65},[33,1925,1926,1928,1930,1932,1934,1936,1938,1941],{"class":35,"line":1034},[33,1927,597],{"class":190},[33,1929,1454],{"class":147},[33,1931,197],{"class":190},[33,1933,1063],{"class":190},[33,1935,1461],{"class":69},[33,1937,73],{"class":65},[33,1939,1940],{"class":76},"\"\u002Fapi\u002Fslow\"",[33,1942,1943],{"class":65},", { signal: controller.signal });\n",[33,1945,1946,1949],{"class":35,"line":1039},[33,1947,1948],{"class":69},"    clearTimeout",[33,1950,1951],{"class":65},"(timer);\n",[33,1953,1954,1956,1958,1960,1962,1964,1966],{"class":35,"line":1054},[33,1955,597],{"class":190},[33,1957,1565],{"class":147},[33,1959,197],{"class":190},[33,1961,1063],{"class":190},[33,1963,1572],{"class":65},[33,1965,1575],{"class":69},[33,1967,848],{"class":65},[33,1969,1970,1972,1974],{"class":35,"line":1086},[33,1971,337],{"class":65},[33,1973,340],{"class":190},[33,1975,343],{"class":65},[33,1977,1978,1980,1982,1984,1987,1990,1992,1994,1997],{"class":35,"line":1109},[33,1979,349],{"class":190},[33,1981,352],{"class":65},[33,1983,355],{"class":190},[33,1985,1986],{"class":76}," \"AbortError\"",[33,1988,1989],{"class":65},") console.",[33,1991,370],{"class":69},[33,1993,73],{"class":65},[33,1995,1996],{"class":76},"\"cancelled\"",[33,1998,813],{"class":65},[33,2000,2001,2004,2007],{"class":35,"line":1130},[33,2002,2003],{"class":190},"    else",[33,2005,2006],{"class":190}," throw",[33,2008,2009],{"class":65}," e;\n",[33,2011,2012],{"class":35,"line":1135},[33,2013,393],{"class":65},[15,2015,2017],{"id":2016},"production-pattern-offline-first-with-cache-api","Production Pattern: Offline-First with Cache API",[20,2019,2020],{"language":22},[24,2021,2023],{"className":26,"code":2022,"language":22,"meta":28,"style":28},"\u002F\u002F ── Cache API: store fetch responses for offline access (PWA pattern) ──\nconst CACHE_NAME = \"app-v1\";\nconst urlsToCache = [\"\u002F\", \"\u002Findex.html\", \"\u002Fstyles.css\", \"\u002Fapp.js\"];\n\n\u002F\u002F Install: pre-cache critical resources\nasync function cacheResources() {\n    const cache = await caches.open(CACHE_NAME);\n    await cache.addAll(urlsToCache);  \u002F\u002F fetch and store all\n}\n\n\u002F\u002F Fetch: serve from cache, fall back to network\nasync function fetchWithCache(request) {\n    const cached = await caches.match(request);\n    if (cached) return cached;  \u002F\u002F cache hit (offline-first)\n    \u002F\u002F Cache miss → fetch from network\n    const response = await fetch(request);\n    if (response.ok) {\n        const cache = await caches.open(CACHE_NAME);\n        cache.put(request, response.clone());  \u002F\u002F cache for next time (clone — body can only be consumed once)\n    }\n    return response;\n}\n\n\u002F\u002F Stale-while-revalidate (serve cache immediately, update in background)\nasync function staleWhileRevalidate(request) {\n    const cache = await caches.open(CACHE_NAME);\n    const cached = await cache.match(request);\n    const fetchPromise = fetch(request).then(response => {\n        if (response.ok) cache.put(request, response.clone());\n        return response;\n    });\n    return cached || fetchPromise;  \u002F\u002F serve cache immediately, update in background\n}\n",[30,2024,2025,2030,2044,2077,2081,2086,2097,2120,2136,2140,2144,2149,2165,2184,2200,2205,2219,2226,2246,2266,2270,2277,2281,2285,2290,2305,2325,2341,2367,2384,2391,2395,2411],{"__ignoreMap":28},[33,2026,2027],{"class":35,"line":36},[33,2028,2029],{"class":39},"\u002F\u002F ── Cache API: store fetch responses for offline access (PWA pattern) ──\n",[33,2031,2032,2034,2037,2039,2042],{"class":35,"line":43},[33,2033,191],{"class":190},[33,2035,2036],{"class":147}," CACHE_NAME",[33,2038,197],{"class":190},[33,2040,2041],{"class":76}," \"app-v1\"",[33,2043,1834],{"class":65},[33,2045,2046,2048,2051,2053,2056,2059,2061,2064,2066,2069,2071,2074],{"class":35,"line":49},[33,2047,191],{"class":190},[33,2049,2050],{"class":147}," urlsToCache",[33,2052,197],{"class":190},[33,2054,2055],{"class":65}," [",[33,2057,2058],{"class":76},"\"\u002F\"",[33,2060,80],{"class":65},[33,2062,2063],{"class":76},"\"\u002Findex.html\"",[33,2065,80],{"class":65},[33,2067,2068],{"class":76},"\"\u002Fstyles.css\"",[33,2070,80],{"class":65},[33,2072,2073],{"class":76},"\"\u002Fapp.js\"",[33,2075,2076],{"class":65},"];\n",[33,2078,2079],{"class":35,"line":56},[33,2080,53],{"emptyLinePlaceholder":52},[33,2082,2083],{"class":35,"line":62},[33,2084,2085],{"class":39},"\u002F\u002F Install: pre-cache critical resources\n",[33,2087,2088,2090,2092,2095],{"class":35,"line":92},[33,2089,1042],{"class":190},[33,2091,1045],{"class":190},[33,2093,2094],{"class":69}," cacheResources",[33,2096,1051],{"class":65},[33,2098,2099,2101,2104,2106,2108,2111,2113,2115,2118],{"class":35,"line":110},[33,2100,597],{"class":190},[33,2102,2103],{"class":147}," cache",[33,2105,197],{"class":190},[33,2107,1063],{"class":190},[33,2109,2110],{"class":65}," caches.",[33,2112,551],{"class":69},[33,2114,73],{"class":65},[33,2116,2117],{"class":147},"CACHE_NAME",[33,2119,813],{"class":65},[33,2121,2122,2124,2127,2130,2133],{"class":35,"line":128},[33,2123,1173],{"class":190},[33,2125,2126],{"class":65}," cache.",[33,2128,2129],{"class":69},"addAll",[33,2131,2132],{"class":65},"(urlsToCache);  ",[33,2134,2135],{"class":39},"\u002F\u002F fetch and store all\n",[33,2137,2138],{"class":35,"line":142},[33,2139,393],{"class":65},[33,2141,2142],{"class":35,"line":157},[33,2143,53],{"emptyLinePlaceholder":52},[33,2145,2146],{"class":35,"line":176},[33,2147,2148],{"class":39},"\u002F\u002F Fetch: serve from cache, fall back to network\n",[33,2150,2151,2153,2155,2158,2160,2163],{"class":35,"line":181},[33,2152,1042],{"class":190},[33,2154,1045],{"class":190},[33,2156,2157],{"class":69}," fetchWithCache",[33,2159,73],{"class":65},[33,2161,2162],{"class":424},"request",[33,2164,361],{"class":65},[33,2166,2167,2169,2172,2174,2176,2178,2181],{"class":35,"line":187},[33,2168,597],{"class":190},[33,2170,2171],{"class":147}," cached",[33,2173,197],{"class":190},[33,2175,1063],{"class":190},[33,2177,2110],{"class":65},[33,2179,2180],{"class":69},"match",[33,2182,2183],{"class":65},"(request);\n",[33,2185,2186,2188,2191,2194,2197],{"class":35,"line":215},[33,2187,349],{"class":190},[33,2189,2190],{"class":65}," (cached) ",[33,2192,2193],{"class":190},"return",[33,2195,2196],{"class":65}," cached;  ",[33,2198,2199],{"class":39},"\u002F\u002F cache hit (offline-first)\n",[33,2201,2202],{"class":35,"line":244},[33,2203,2204],{"class":39},"    \u002F\u002F Cache miss → fetch from network\n",[33,2206,2207,2209,2211,2213,2215,2217],{"class":35,"line":277},[33,2208,597],{"class":190},[33,2210,1454],{"class":147},[33,2212,197],{"class":190},[33,2214,1063],{"class":190},[33,2216,1461],{"class":69},[33,2218,2183],{"class":65},[33,2220,2221,2223],{"class":35,"line":282},[33,2222,349],{"class":190},[33,2224,2225],{"class":65}," (response.ok) {\n",[33,2227,2228,2230,2232,2234,2236,2238,2240,2242,2244],{"class":35,"line":288},[33,2229,633],{"class":190},[33,2231,2103],{"class":147},[33,2233,197],{"class":190},[33,2235,1063],{"class":190},[33,2237,2110],{"class":65},[33,2239,551],{"class":69},[33,2241,73],{"class":65},[33,2243,2117],{"class":147},[33,2245,813],{"class":65},[33,2247,2248,2251,2254,2257,2260,2263],{"class":35,"line":294},[33,2249,2250],{"class":65},"        cache.",[33,2252,2253],{"class":69},"put",[33,2255,2256],{"class":65},"(request, response.",[33,2258,2259],{"class":69},"clone",[33,2261,2262],{"class":65},"());  ",[33,2264,2265],{"class":39},"\u002F\u002F cache for next time (clone — body can only be consumed once)\n",[33,2267,2268],{"class":35,"line":303},[33,2269,387],{"class":65},[33,2271,2272,2274],{"class":35,"line":334},[33,2273,767],{"class":190},[33,2275,2276],{"class":65}," response;\n",[33,2278,2279],{"class":35,"line":346},[33,2280,393],{"class":65},[33,2282,2283],{"class":35,"line":364},[33,2284,53],{"emptyLinePlaceholder":52},[33,2286,2287],{"class":35,"line":384},[33,2288,2289],{"class":39},"\u002F\u002F Stale-while-revalidate (serve cache immediately, update in background)\n",[33,2291,2292,2294,2296,2299,2301,2303],{"class":35,"line":390},[33,2293,1042],{"class":190},[33,2295,1045],{"class":190},[33,2297,2298],{"class":69}," staleWhileRevalidate",[33,2300,73],{"class":65},[33,2302,2162],{"class":424},[33,2304,361],{"class":65},[33,2306,2307,2309,2311,2313,2315,2317,2319,2321,2323],{"class":35,"line":396},[33,2308,597],{"class":190},[33,2310,2103],{"class":147},[33,2312,197],{"class":190},[33,2314,1063],{"class":190},[33,2316,2110],{"class":65},[33,2318,551],{"class":69},[33,2320,73],{"class":65},[33,2322,2117],{"class":147},[33,2324,813],{"class":65},[33,2326,2327,2329,2331,2333,2335,2337,2339],{"class":35,"line":401},[33,2328,597],{"class":190},[33,2330,2171],{"class":147},[33,2332,197],{"class":190},[33,2334,1063],{"class":190},[33,2336,2126],{"class":65},[33,2338,2180],{"class":69},[33,2340,2183],{"class":65},[33,2342,2343,2345,2348,2350,2352,2355,2358,2360,2362,2365],{"class":35,"line":407},[33,2344,597],{"class":190},[33,2346,2347],{"class":147}," fetchPromise",[33,2349,197],{"class":190},[33,2351,1461],{"class":69},[33,2353,2354],{"class":65},"(request).",[33,2356,2357],{"class":69},"then",[33,2359,73],{"class":65},[33,2361,1548],{"class":424},[33,2363,2364],{"class":190}," =>",[33,2366,300],{"class":65},[33,2368,2369,2372,2375,2377,2379,2381],{"class":35,"line":436},[33,2370,2371],{"class":190},"        if",[33,2373,2374],{"class":65}," (response.ok) cache.",[33,2376,2253],{"class":69},[33,2378,2256],{"class":65},[33,2380,2259],{"class":69},[33,2382,2383],{"class":65},"());\n",[33,2385,2386,2389],{"class":35,"line":442},[33,2387,2388],{"class":190},"        return",[33,2390,2276],{"class":65},[33,2392,2393],{"class":35,"line":461},[33,2394,892],{"class":65},[33,2396,2397,2399,2402,2405,2408],{"class":35,"line":479},[33,2398,767],{"class":190},[33,2400,2401],{"class":65}," cached ",[33,2403,2404],{"class":190},"||",[33,2406,2407],{"class":65}," fetchPromise;  ",[33,2409,2410],{"class":39},"\u002F\u002F serve cache immediately, update in background\n",[33,2412,2413],{"class":35,"line":497},[33,2414,393],{"class":65},[15,2416,2418],{"id":2417},"tips-tricks","💡 Tips & Tricks",[20,2420,2421],{"language":22},[24,2422,2424],{"className":26,"code":2423,"language":22,"meta":28,"style":28},"\u002F\u002F ── Typed-safe localStorage wrapper ──\nconst storage = {\n    get(key, defaultValue = null) {\n        const value = localStorage.getItem(key);\n        if (value === null) return defaultValue;\n        try { return JSON.parse(value); } catch { return value; }\n    },\n    set(key, value) {\n        localStorage.setItem(key, JSON.stringify(value));\n    },\n    remove(key) { localStorage.removeItem(key); },\n};\n\n\u002F\u002F ── Debounced localStorage writes (avoid excessive writes on input) ──\nconst debouncedSave = debounce((key, value) => storage.set(key, value), 500);\ninput.addEventListener(\"input\", () => debouncedSave(\"draft\", input.value));\n\n\u002F\u002F ── Fetch with retry and timeout ──\nasync function fetchWithRetry(url, { retries = 3, timeout = 5000 } = {}) {\n    for (let i = 0; i \u003C retries; i++) {\n        const controller = new AbortController();\n        const timer = setTimeout(() => controller.abort(), timeout);\n        try {\n            const response = await fetch(url, { signal: controller.signal });\n            clearTimeout(timer);\n            if (!response.ok) throw new Error(`HTTP ${response.status}`);\n            return await response.json();\n        } catch (e) {\n            clearTimeout(timer);\n            if (i === retries - 1) throw e;\n            await new Promise(r => setTimeout(r, 1000 * 2 ** i));  \u002F\u002F exponential backoff\n        }\n    }\n}\n\n\u002F\u002F ── Cookie-based auth (not localStorage for security) ──\n\u002F\u002F Store auth tokens in httpOnly cookies (not accessible via JS — XSS-resistant)\n\u002F\u002F fetch includes cookies by default in same-origin requests\n\u002F\u002F For cross-origin: credentials: \"include\"\nfetch(\"\u002Fapi\u002Fuser\", { credentials: \"include\" });\n",[30,2425,2426,2431,2442,2463,2480,2498,2525,2530,2545,2564,2568,2585,2589,2593,2598,2636,2663,2667,2672,2714,2746,2760,2781,2787,2803,2810,2841,2854,2863,2869,2893,2932,2937,2941,2945,2949,2954,2959,2964,2969],{"__ignoreMap":28},[33,2427,2428],{"class":35,"line":36},[33,2429,2430],{"class":39},"\u002F\u002F ── Typed-safe localStorage wrapper ──\n",[33,2432,2433,2435,2438,2440],{"class":35,"line":43},[33,2434,191],{"class":190},[33,2436,2437],{"class":147}," storage",[33,2439,197],{"class":190},[33,2441,300],{"class":65},[33,2443,2444,2447,2449,2451,2453,2456,2458,2461],{"class":35,"line":49},[33,2445,2446],{"class":69},"    get",[33,2448,73],{"class":65},[33,2450,162],{"class":424},[33,2452,80],{"class":65},[33,2454,2455],{"class":424},"defaultValue",[33,2457,197],{"class":190},[33,2459,2460],{"class":147}," null",[33,2462,361],{"class":65},[33,2464,2465,2467,2470,2472,2475,2477],{"class":35,"line":56},[33,2466,633],{"class":190},[33,2468,2469],{"class":147}," value",[33,2471,197],{"class":190},[33,2473,2474],{"class":65}," localStorage.",[33,2476,97],{"class":69},[33,2478,2479],{"class":65},"(key);\n",[33,2481,2482,2484,2487,2489,2491,2493,2495],{"class":35,"line":62},[33,2483,2371],{"class":190},[33,2485,2486],{"class":65}," (value ",[33,2488,355],{"class":190},[33,2490,2460],{"class":147},[33,2492,428],{"class":65},[33,2494,2193],{"class":190},[33,2496,2497],{"class":65}," defaultValue;\n",[33,2499,2500,2503,2505,2507,2509,2511,2513,2516,2518,2520,2522],{"class":35,"line":92},[33,2501,2502],{"class":190},"        try",[33,2504,1798],{"class":65},[33,2506,2193],{"class":190},[33,2508,254],{"class":147},[33,2510,232],{"class":65},[33,2512,259],{"class":69},[33,2514,2515],{"class":65},"(value); } ",[33,2517,340],{"class":190},[33,2519,1798],{"class":65},[33,2521,2193],{"class":190},[33,2523,2524],{"class":65}," value; }\n",[33,2526,2527],{"class":35,"line":110},[33,2528,2529],{"class":65},"    },\n",[33,2531,2532,2535,2537,2539,2541,2543],{"class":35,"line":128},[33,2533,2534],{"class":69},"    set",[33,2536,73],{"class":65},[33,2538,162],{"class":424},[33,2540,80],{"class":65},[33,2542,1806],{"class":424},[33,2544,361],{"class":65},[33,2546,2547,2550,2552,2555,2557,2559,2561],{"class":35,"line":142},[33,2548,2549],{"class":65},"        localStorage.",[33,2551,70],{"class":69},[33,2553,2554],{"class":65},"(key, ",[33,2556,229],{"class":147},[33,2558,232],{"class":65},[33,2560,235],{"class":69},[33,2562,2563],{"class":65},"(value));\n",[33,2565,2566],{"class":35,"line":157},[33,2567,2529],{"class":65},[33,2569,2570,2573,2575,2577,2580,2582],{"class":35,"line":176},[33,2571,2572],{"class":69},"    remove",[33,2574,73],{"class":65},[33,2576,162],{"class":424},[33,2578,2579],{"class":65},") { localStorage.",[33,2581,115],{"class":69},[33,2583,2584],{"class":65},"(key); },\n",[33,2586,2587],{"class":35,"line":181},[33,2588,695],{"class":65},[33,2590,2591],{"class":35,"line":187},[33,2592,53],{"emptyLinePlaceholder":52},[33,2594,2595],{"class":35,"line":215},[33,2596,2597],{"class":39},"\u002F\u002F ── Debounced localStorage writes (avoid excessive writes on input) ──\n",[33,2599,2600,2602,2605,2607,2610,2612,2614,2616,2618,2620,2622,2625,2628,2631,2634],{"class":35,"line":244},[33,2601,191],{"class":190},[33,2603,2604],{"class":147}," debouncedSave",[33,2606,197],{"class":190},[33,2608,2609],{"class":69}," debounce",[33,2611,776],{"class":65},[33,2613,162],{"class":424},[33,2615,80],{"class":65},[33,2617,1806],{"class":424},[33,2619,428],{"class":65},[33,2621,431],{"class":190},[33,2623,2624],{"class":65}," storage.",[33,2626,2627],{"class":69},"set",[33,2629,2630],{"class":65},"(key, value), ",[33,2632,2633],{"class":147},"500",[33,2635,813],{"class":65},[33,2637,2638,2641,2643,2645,2648,2651,2653,2655,2657,2660],{"class":35,"line":277},[33,2639,2640],{"class":65},"input.",[33,2642,413],{"class":69},[33,2644,73],{"class":65},[33,2646,2647],{"class":76},"\"input\"",[33,2649,2650],{"class":65},", () ",[33,2652,431],{"class":190},[33,2654,2604],{"class":69},[33,2656,73],{"class":65},[33,2658,2659],{"class":76},"\"draft\"",[33,2661,2662],{"class":65},", input.value));\n",[33,2664,2665],{"class":35,"line":282},[33,2666,53],{"emptyLinePlaceholder":52},[33,2668,2669],{"class":35,"line":288},[33,2670,2671],{"class":39},"\u002F\u002F ── Fetch with retry and timeout ──\n",[33,2673,2674,2676,2678,2681,2683,2686,2689,2692,2694,2697,2699,2702,2704,2707,2709,2711],{"class":35,"line":294},[33,2675,1042],{"class":190},[33,2677,1045],{"class":190},[33,2679,2680],{"class":69}," fetchWithRetry",[33,2682,73],{"class":65},[33,2684,2685],{"class":424},"url",[33,2687,2688],{"class":65},", { ",[33,2690,2691],{"class":424},"retries",[33,2693,197],{"class":190},[33,2695,2696],{"class":147}," 3",[33,2698,80],{"class":65},[33,2700,2701],{"class":424},"timeout",[33,2703,197],{"class":190},[33,2705,2706],{"class":147}," 5000",[33,2708,1809],{"class":65},[33,2710,1244],{"class":190},[33,2712,2713],{"class":65}," {}) {\n",[33,2715,2716,2719,2721,2724,2727,2729,2732,2735,2738,2741,2744],{"class":35,"line":303},[33,2717,2718],{"class":190},"    for",[33,2720,579],{"class":65},[33,2722,2723],{"class":190},"let",[33,2725,2726],{"class":65}," i ",[33,2728,1244],{"class":190},[33,2730,2731],{"class":147}," 0",[33,2733,2734],{"class":65},"; i ",[33,2736,2737],{"class":190},"\u003C",[33,2739,2740],{"class":65}," retries; i",[33,2742,2743],{"class":190},"++",[33,2745,361],{"class":65},[33,2747,2748,2750,2752,2754,2756,2758],{"class":35,"line":334},[33,2749,633],{"class":190},[33,2751,1877],{"class":147},[33,2753,197],{"class":190},[33,2755,770],{"class":190},[33,2757,1884],{"class":69},[33,2759,848],{"class":65},[33,2761,2762,2764,2766,2768,2770,2772,2774,2776,2778],{"class":35,"line":346},[33,2763,633],{"class":190},[33,2765,1893],{"class":147},[33,2767,197],{"class":190},[33,2769,1898],{"class":69},[33,2771,1901],{"class":65},[33,2773,431],{"class":190},[33,2775,1906],{"class":65},[33,2777,1909],{"class":69},[33,2779,2780],{"class":65},"(), timeout);\n",[33,2782,2783,2785],{"class":35,"line":364},[33,2784,2502],{"class":190},[33,2786,300],{"class":65},[33,2788,2789,2792,2794,2796,2798,2800],{"class":35,"line":384},[33,2790,2791],{"class":190},"            const",[33,2793,1454],{"class":147},[33,2795,197],{"class":190},[33,2797,1063],{"class":190},[33,2799,1461],{"class":69},[33,2801,2802],{"class":65},"(url, { signal: controller.signal });\n",[33,2804,2805,2808],{"class":35,"line":390},[33,2806,2807],{"class":69},"            clearTimeout",[33,2809,1951],{"class":65},[33,2811,2812,2815,2817,2819,2821,2823,2825,2827,2829,2831,2833,2835,2837,2839],{"class":35,"line":396},[33,2813,2814],{"class":190},"            if",[33,2816,579],{"class":65},[33,2818,614],{"class":190},[33,2820,1532],{"class":65},[33,2822,1535],{"class":190},[33,2824,770],{"class":190},[33,2826,1540],{"class":69},[33,2828,73],{"class":65},[33,2830,1545],{"class":76},[33,2832,1548],{"class":65},[33,2834,232],{"class":76},[33,2836,1553],{"class":65},[33,2838,1556],{"class":76},[33,2840,813],{"class":65},[33,2842,2843,2846,2848,2850,2852],{"class":35,"line":401},[33,2844,2845],{"class":190},"            return",[33,2847,1063],{"class":190},[33,2849,1572],{"class":65},[33,2851,1575],{"class":69},[33,2853,848],{"class":65},[33,2855,2856,2859,2861],{"class":35,"line":407},[33,2857,2858],{"class":65},"        } ",[33,2860,340],{"class":190},[33,2862,343],{"class":65},[33,2864,2865,2867],{"class":35,"line":436},[33,2866,2807],{"class":69},[33,2868,1951],{"class":65},[33,2870,2871,2873,2876,2878,2881,2884,2887,2889,2891],{"class":35,"line":442},[33,2872,2814],{"class":190},[33,2874,2875],{"class":65}," (i ",[33,2877,355],{"class":190},[33,2879,2880],{"class":65}," retries ",[33,2882,2883],{"class":190},"-",[33,2885,2886],{"class":147}," 1",[33,2888,428],{"class":65},[33,2890,1535],{"class":190},[33,2892,2009],{"class":65},[33,2894,2895,2898,2900,2902,2904,2907,2909,2911,2914,2917,2920,2923,2926,2929],{"class":35,"line":461},[33,2896,2897],{"class":190},"            await",[33,2899,770],{"class":190},[33,2901,773],{"class":147},[33,2903,73],{"class":65},[33,2905,2906],{"class":424},"r",[33,2908,2364],{"class":190},[33,2910,1898],{"class":69},[33,2912,2913],{"class":65},"(r, ",[33,2915,2916],{"class":147},"1000",[33,2918,2919],{"class":190}," *",[33,2921,2922],{"class":147}," 2",[33,2924,2925],{"class":190}," **",[33,2927,2928],{"class":65}," i));  ",[33,2930,2931],{"class":39},"\u002F\u002F exponential backoff\n",[33,2933,2934],{"class":35,"line":479},[33,2935,2936],{"class":65},"        }\n",[33,2938,2939],{"class":35,"line":497},[33,2940,387],{"class":65},[33,2942,2943],{"class":35,"line":503},[33,2944,393],{"class":65},[33,2946,2947],{"class":35,"line":995},[33,2948,53],{"emptyLinePlaceholder":52},[33,2950,2951],{"class":35,"line":1012},[33,2952,2953],{"class":39},"\u002F\u002F ── Cookie-based auth (not localStorage for security) ──\n",[33,2955,2956],{"class":35,"line":1029},[33,2957,2958],{"class":39},"\u002F\u002F Store auth tokens in httpOnly cookies (not accessible via JS — XSS-resistant)\n",[33,2960,2961],{"class":35,"line":1034},[33,2962,2963],{"class":39},"\u002F\u002F fetch includes cookies by default in same-origin requests\n",[33,2965,2966],{"class":35,"line":1039},[33,2967,2968],{"class":39},"\u002F\u002F For cross-origin: credentials: \"include\"\n",[33,2970,2971,2974,2976,2979,2982,2985],{"class":35,"line":1054},[33,2972,2973],{"class":69},"fetch",[33,2975,73],{"class":65},[33,2977,2978],{"class":76},"\"\u002Fapi\u002Fuser\"",[33,2980,2981],{"class":65},", { credentials: ",[33,2983,2984],{"class":76},"\"include\"",[33,2986,657],{"class":65},[15,2988,2990],{"id":2989},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,2992,2993],{"language":22},[24,2994,2996],{"className":26,"code":2995,"language":22,"meta":28,"style":28},"\u002F\u002F ── localStorage is synchronous (blocks the main thread) ──\n\u002F\u002F ⚠️ Don't store large data in localStorage — it blocks the UI.\n\u002F\u002F Use IndexedDB for large data (async, non-blocking).\n\n\u002F\u002F ── localStorage stores strings only ──\nlocalStorage.setItem(\"count\", 42);  \u002F\u002F stored as \"42\" (string)\nconst count = localStorage.getItem(\"count\");  \u002F\u002F \"42\" (string — must parse)\n\u002F\u002F Use JSON.stringify\u002Fparse for non-string values.\n\n\u002F\u002F ── localStorage quota (~5-10MB) — throws on overflow ──\ntry { localStorage.setItem(\"big\", \"x\".repeat(20_000_000)); }\ncatch (e) { \u002F* QuotaExceededError *\u002F }\n\n\u002F\u002F ── localStorage is per-origin (protocol + domain + port) ──\n\u002F\u002F http:\u002F\u002Fexample.com and https:\u002F\u002Fexample.com have DIFFERENT localStorage\n\n\u002F\u002F ── fetch body can only be consumed ONCE ──\nconst response = await fetch(\"\u002Fapi\");\nawait response.text();   \u002F\u002F consume body\n\u002F\u002F await response.json();  \u002F\u002F ✗ TypeError: body already consumed\n\u002F\u002F Clone if you need to read twice: const clone = response.clone();\n\n\u002F\u002F ── fetch doesn't reject on HTTP errors (only on network errors) ──\nconst response = await fetch(\"\u002Fapi\u002F404\");\n\u002F\u002F response.ok === false, but the Promise is RESOLVED (not rejected)\n\u002F\u002F Must check response.ok manually:\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\n\u002F\u002F ── IndexedDB transactions auto-commit if no operations are pending ──\n\u002F\u002F Don't mix async (await) between transaction creation and operation — the transaction\n\u002F\u002F may auto-commit before the operation runs.\n",[30,2997,2998,3003,3008,3013,3017,3022,3043,3065,3070,3074,3079,3109,3122,3126,3131,3136,3140,3145,3164,3178,3186,3191,3195,3200,3219,3224,3229,3259,3263,3268,3273],{"__ignoreMap":28},[33,2999,3000],{"class":35,"line":36},[33,3001,3002],{"class":39},"\u002F\u002F ── localStorage is synchronous (blocks the main thread) ──\n",[33,3004,3005],{"class":35,"line":43},[33,3006,3007],{"class":39},"\u002F\u002F ⚠️ Don't store large data in localStorage — it blocks the UI.\n",[33,3009,3010],{"class":35,"line":49},[33,3011,3012],{"class":39},"\u002F\u002F Use IndexedDB for large data (async, non-blocking).\n",[33,3014,3015],{"class":35,"line":56},[33,3016,53],{"emptyLinePlaceholder":52},[33,3018,3019],{"class":35,"line":62},[33,3020,3021],{"class":39},"\u002F\u002F ── localStorage stores strings only ──\n",[33,3023,3024,3026,3028,3030,3033,3035,3038,3040],{"class":35,"line":92},[33,3025,66],{"class":65},[33,3027,70],{"class":69},[33,3029,73],{"class":65},[33,3031,3032],{"class":76},"\"count\"",[33,3034,80],{"class":65},[33,3036,3037],{"class":147},"42",[33,3039,378],{"class":65},[33,3041,3042],{"class":39},"\u002F\u002F stored as \"42\" (string)\n",[33,3044,3045,3047,3050,3052,3054,3056,3058,3060,3062],{"class":35,"line":110},[33,3046,191],{"class":190},[33,3048,3049],{"class":147}," count",[33,3051,197],{"class":190},[33,3053,2474],{"class":65},[33,3055,97],{"class":69},[33,3057,73],{"class":65},[33,3059,3032],{"class":76},[33,3061,378],{"class":65},[33,3063,3064],{"class":39},"\u002F\u002F \"42\" (string — must parse)\n",[33,3066,3067],{"class":35,"line":128},[33,3068,3069],{"class":39},"\u002F\u002F Use JSON.stringify\u002Fparse for non-string values.\n",[33,3071,3072],{"class":35,"line":142},[33,3073,53],{"emptyLinePlaceholder":52},[33,3075,3076],{"class":35,"line":157},[33,3077,3078],{"class":39},"\u002F\u002F ── localStorage quota (~5-10MB) — throws on overflow ──\n",[33,3080,3081,3083,3086,3088,3090,3093,3095,3097,3099,3101,3103,3106],{"class":35,"line":176},[33,3082,297],{"class":190},[33,3084,3085],{"class":65}," { localStorage.",[33,3087,70],{"class":69},[33,3089,73],{"class":65},[33,3091,3092],{"class":76},"\"big\"",[33,3094,80],{"class":65},[33,3096,318],{"class":76},[33,3098,232],{"class":65},[33,3100,323],{"class":69},[33,3102,73],{"class":65},[33,3104,3105],{"class":147},"20_000_000",[33,3107,3108],{"class":65},")); }\n",[33,3110,3111,3113,3116,3119],{"class":35,"line":181},[33,3112,340],{"class":190},[33,3114,3115],{"class":65}," (e) { ",[33,3117,3118],{"class":39},"\u002F* QuotaExceededError *\u002F",[33,3120,3121],{"class":65}," }\n",[33,3123,3124],{"class":35,"line":187},[33,3125,53],{"emptyLinePlaceholder":52},[33,3127,3128],{"class":35,"line":215},[33,3129,3130],{"class":39},"\u002F\u002F ── localStorage is per-origin (protocol + domain + port) ──\n",[33,3132,3133],{"class":35,"line":244},[33,3134,3135],{"class":39},"\u002F\u002F http:\u002F\u002Fexample.com and https:\u002F\u002Fexample.com have DIFFERENT localStorage\n",[33,3137,3138],{"class":35,"line":277},[33,3139,53],{"emptyLinePlaceholder":52},[33,3141,3142],{"class":35,"line":282},[33,3143,3144],{"class":39},"\u002F\u002F ── fetch body can only be consumed ONCE ──\n",[33,3146,3147,3149,3151,3153,3155,3157,3159,3162],{"class":35,"line":288},[33,3148,191],{"class":190},[33,3150,1454],{"class":147},[33,3152,197],{"class":190},[33,3154,1063],{"class":190},[33,3156,1461],{"class":69},[33,3158,73],{"class":65},[33,3160,3161],{"class":76},"\"\u002Fapi\"",[33,3163,813],{"class":65},[33,3165,3166,3168,3170,3172,3175],{"class":35,"line":294},[33,3167,1652],{"class":190},[33,3169,1572],{"class":65},[33,3171,1671],{"class":69},[33,3173,3174],{"class":65},"();   ",[33,3176,3177],{"class":39},"\u002F\u002F consume body\n",[33,3179,3180,3183],{"class":35,"line":303},[33,3181,3182],{"class":39},"\u002F\u002F await response.json();",[33,3184,3185],{"class":39},"  \u002F\u002F ✗ TypeError: body already consumed\n",[33,3187,3188],{"class":35,"line":334},[33,3189,3190],{"class":39},"\u002F\u002F Clone if you need to read twice: const clone = response.clone();\n",[33,3192,3193],{"class":35,"line":346},[33,3194,53],{"emptyLinePlaceholder":52},[33,3196,3197],{"class":35,"line":364},[33,3198,3199],{"class":39},"\u002F\u002F ── fetch doesn't reject on HTTP errors (only on network errors) ──\n",[33,3201,3202,3204,3206,3208,3210,3212,3214,3217],{"class":35,"line":384},[33,3203,191],{"class":190},[33,3205,1454],{"class":147},[33,3207,197],{"class":190},[33,3209,1063],{"class":190},[33,3211,1461],{"class":69},[33,3213,73],{"class":65},[33,3215,3216],{"class":76},"\"\u002Fapi\u002F404\"",[33,3218,813],{"class":65},[33,3220,3221],{"class":35,"line":390},[33,3222,3223],{"class":39},"\u002F\u002F response.ok === false, but the Promise is RESOLVED (not rejected)\n",[33,3225,3226],{"class":35,"line":396},[33,3227,3228],{"class":39},"\u002F\u002F Must check response.ok manually:\n",[33,3230,3231,3233,3235,3237,3239,3241,3243,3245,3247,3249,3251,3253,3255,3257],{"class":35,"line":401},[33,3232,1525],{"class":190},[33,3234,579],{"class":65},[33,3236,614],{"class":190},[33,3238,1532],{"class":65},[33,3240,1535],{"class":190},[33,3242,770],{"class":190},[33,3244,1540],{"class":69},[33,3246,73],{"class":65},[33,3248,1545],{"class":76},[33,3250,1548],{"class":65},[33,3252,232],{"class":76},[33,3254,1553],{"class":65},[33,3256,1556],{"class":76},[33,3258,813],{"class":65},[33,3260,3261],{"class":35,"line":407},[33,3262,53],{"emptyLinePlaceholder":52},[33,3264,3265],{"class":35,"line":436},[33,3266,3267],{"class":39},"\u002F\u002F ── IndexedDB transactions auto-commit if no operations are pending ──\n",[33,3269,3270],{"class":35,"line":442},[33,3271,3272],{"class":39},"\u002F\u002F Don't mix async (await) between transaction creation and operation — the transaction\n",[33,3274,3275],{"class":35,"line":461},[33,3276,3277],{"class":39},"\u002F\u002F may auto-commit before the operation runs.\n",[15,3279,3281],{"id":3280},"quick-quiz","🧠 Quick Quiz",[3283,3284,3285,3286,3289],"p",{},"Why does this throw a ",[30,3287,3288],{},"TypeError",", and how do you fix it?",[20,3291,3292],{"language":22},[24,3293,3295],{"className":26,"code":3294,"language":22,"meta":28,"style":28},"const response = await fetch(\"\u002Fapi\u002Fdata\");\nconst text = await response.text();\nconst json = JSON.parse(text);\nconst blob = await response.blob();  \u002F\u002F TypeError!\n",[30,3296,3297,3315,3332,3350],{"__ignoreMap":28},[33,3298,3299,3301,3303,3305,3307,3309,3311,3313],{"class":35,"line":36},[33,3300,191],{"class":190},[33,3302,1454],{"class":147},[33,3304,197],{"class":190},[33,3306,1063],{"class":190},[33,3308,1461],{"class":69},[33,3310,73],{"class":65},[33,3312,1466],{"class":76},[33,3314,813],{"class":65},[33,3316,3317,3319,3322,3324,3326,3328,3330],{"class":35,"line":43},[33,3318,191],{"class":190},[33,3320,3321],{"class":147}," text",[33,3323,197],{"class":190},[33,3325,1063],{"class":190},[33,3327,1572],{"class":65},[33,3329,1671],{"class":69},[33,3331,848],{"class":65},[33,3333,3334,3336,3339,3341,3343,3345,3347],{"class":35,"line":49},[33,3335,191],{"class":190},[33,3337,3338],{"class":147}," json",[33,3340,197],{"class":190},[33,3342,254],{"class":147},[33,3344,232],{"class":65},[33,3346,259],{"class":69},[33,3348,3349],{"class":65},"(text);\n",[33,3351,3352,3354,3357,3359,3361,3363,3365,3367],{"class":35,"line":56},[33,3353,191],{"class":190},[33,3355,3356],{"class":147}," blob",[33,3358,197],{"class":190},[33,3360,1063],{"class":190},[33,3362,1572],{"class":65},[33,3364,1685],{"class":69},[33,3366,1578],{"class":65},[33,3368,3369],{"class":39},"\u002F\u002F TypeError!\n",[3371,3372,3373,3377,3404,3410,3487,3490,3532],"details",{},[3374,3375,3376],"summary",{},"Answer",[3283,3378,3379,3380,3383,3384,3388,3389,3392,3393,3396,3397,3400,3401,232],{},"The ",[30,3381,3382],{},"response.body"," is a ",[3385,3386,3387],"strong",{},"stream"," — it can only be consumed ",[3385,3390,3391],{},"once",". After ",[30,3394,3395],{},"await response.text()",", the stream is exhausted. Calling ",[30,3398,3399],{},"await response.blob()"," throws ",[30,3402,3403],{},"TypeError: Body has already been consumed",[3283,3405,3406,3409],{},[3385,3407,3408],{},"Fix",": clone the response before consuming (each clone has its own body stream):",[24,3411,3413],{"className":26,"code":3412,"language":22,"meta":28,"style":28},"const response = await fetch(\"\u002Fapi\u002Fdata\");\nconst clone = response.clone();  \u002F\u002F create a copy with an independent body stream\nconst text = await response.text();\nconst blob = await clone.blob();  \u002F\u002F ✓ works (clone has its own body)\n",[30,3414,3415,3433,3451,3467],{"__ignoreMap":28},[33,3416,3417,3419,3421,3423,3425,3427,3429,3431],{"class":35,"line":36},[33,3418,191],{"class":190},[33,3420,1454],{"class":147},[33,3422,197],{"class":190},[33,3424,1063],{"class":190},[33,3426,1461],{"class":69},[33,3428,73],{"class":65},[33,3430,1466],{"class":76},[33,3432,813],{"class":65},[33,3434,3435,3437,3440,3442,3444,3446,3448],{"class":35,"line":43},[33,3436,191],{"class":190},[33,3438,3439],{"class":147}," clone",[33,3441,197],{"class":190},[33,3443,1572],{"class":65},[33,3445,2259],{"class":69},[33,3447,1578],{"class":65},[33,3449,3450],{"class":39},"\u002F\u002F create a copy with an independent body stream\n",[33,3452,3453,3455,3457,3459,3461,3463,3465],{"class":35,"line":49},[33,3454,191],{"class":190},[33,3456,3321],{"class":147},[33,3458,197],{"class":190},[33,3460,1063],{"class":190},[33,3462,1572],{"class":65},[33,3464,1671],{"class":69},[33,3466,848],{"class":65},[33,3468,3469,3471,3473,3475,3477,3480,3482,3484],{"class":35,"line":56},[33,3470,191],{"class":190},[33,3472,3356],{"class":147},[33,3474,197],{"class":190},[33,3476,1063],{"class":190},[33,3478,3479],{"class":65}," clone.",[33,3481,1685],{"class":69},[33,3483,1578],{"class":65},[33,3485,3486],{"class":39},"\u002F\u002F ✓ works (clone has its own body)\n",[3283,3488,3489],{},"Or, decide which format you need and consume it once:",[24,3491,3493],{"className":26,"code":3492,"language":22,"meta":28,"style":28},"const response = await fetch(\"\u002Fapi\u002Fdata\");\nconst json = await response.json();  \u002F\u002F consume once as JSON\n",[30,3494,3495,3513],{"__ignoreMap":28},[33,3496,3497,3499,3501,3503,3505,3507,3509,3511],{"class":35,"line":36},[33,3498,191],{"class":190},[33,3500,1454],{"class":147},[33,3502,197],{"class":190},[33,3504,1063],{"class":190},[33,3506,1461],{"class":69},[33,3508,73],{"class":65},[33,3510,1466],{"class":76},[33,3512,813],{"class":65},[33,3514,3515,3517,3519,3521,3523,3525,3527,3529],{"class":35,"line":43},[33,3516,191],{"class":190},[33,3518,3338],{"class":147},[33,3520,197],{"class":190},[33,3522,1063],{"class":190},[33,3524,1572],{"class":65},[33,3526,1575],{"class":69},[33,3528,1578],{"class":65},[33,3530,3531],{"class":39},"\u002F\u002F consume once as JSON\n",[3283,3533,3534,3537,3538,3541,3542,80,3545,80,3548,3551,3552,3555,3556,3559],{},[3385,3535,3536],{},"The lesson",": a ",[30,3539,3540],{},"Response"," body is a one-time stream. Reading it with ",[30,3543,3544],{},".text()",[30,3546,3547],{},".json()",[30,3549,3550],{},".blob()",", or ",[30,3553,3554],{},".arrayBuffer()"," consumes it permanently. Use ",[30,3557,3558],{},".clone()"," if you need to read the body in multiple formats.",[3561,3562,3563],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":28,"searchDepth":43,"depth":43,"links":3565},[3566,3567,3568,3569,3570,3571,3572],{"id":17,"depth":43,"text":18},{"id":509,"depth":43,"text":510},{"id":1434,"depth":43,"text":1435},{"id":2016,"depth":43,"text":2017},{"id":2417,"depth":43,"text":2418},{"id":2989,"depth":43,"text":2990},{"id":3280,"depth":43,"text":3281},"Deep-dive into browser storage: localStorage\u002FsessionStorage serialization, IndexedDB transactions and async stores, Fetch API streaming, and the Cache API for offline-first patterns. Code-first reference for senior engineers.","md",{},"\u002Fjs\u002F21-web-storage-and-apis",{"title":5,"description":3573},"js\u002F21-web-storage-and-apis","OwEq-q-34oRVYiSOzJ-ks5kNCNPyab1IB9XvVKfl73M",1789924649429]