With client side first apps, like Notion and Linear, the available storage on the client is crucial. So say you’re building the next Linear, how much space do you have to work with?

If you do some searching, there’s a good post from 2015 discussing the limits of IndexedDB and I used that code as the basis for testing.

browser max localStorage max IndexedDB
iOS Safari 5MB 1.2GB (before prompting user for more)
Mac Safari 5MB 1.2GB (before prompting user for more)
Mac Chrome 5MB a lot! Gave up after it reached 5GB
Mac Firefox 5MB a lot! Gave up after it reached 5GB

conclusion

localStorage is sufficent for small amounts of data, but if you have anything serious, you’ll probably have to use IndexedDB.

code

index.html
<!DOCTYPE html>
<script src="storage.js"></script>
<select id="storage-option">
  <option value="localstorage">localstorage</option>
  <option value="idb">idb</option>
  <option value="memory">memory</option>
</select>
<button id="addButton">start!</button>
storage.js
// based on: https://www.raymondcamden.com/2015/04/17/indexeddb-and-limits/
const data500KB = "a".repeat(500_000)

document.addEventListener(
  "DOMContentLoaded",
  () => {
    //Listen for add clicks
    document.getElementById("addButton").addEventListener("click", main, false)
  },
  false
)

let idx = 0
let error = null

function addIDBData(db) {
  if (error != null) {
    return
  }
  idx += 1

  const transaction = db.transaction(["crap"], "readwrite")
  const store = transaction.objectStore("crap")

  log(`adding id:${idx} w/ size ${data500KB.length}...`)
  const request = store.add({
    data: data500KB
  })
  request.onerror = function (e) {
    log(`failed id:${idx}! ${e.target.error}`)
    error = e
  }
  request.onsuccess = function (e) {
    log(`added id:${idx}!`)
    addIDBData(db)
  }
}

function log(text) {
  const div = document.createElement("div")
  div.innerHTML = text
  document.body.appendChild(div)
}

function foo() {
  for (let x = 0; x++; x < 2) {
    debugger
    console.log("xxx")
  }
}

function main() {
  const selection = document.getElementById("storage-option").value
  if (selection === "localstorage") {
    while (error == null) {
      idx += 1
      log(`adding ${idx} w/ size ${data500KB.length}...`)
      try {
        localStorage.setItem(idx, data500KB)
        log(`added ${idx}!`)
      } catch (e) {
        log(`failed ${idx}! ${e}`)
        error = e
      }
    }
  } else if (selection === "idb") {
    log("connecting...")
    const openRequest = indexedDB.open("bighonkingtest", 1)
    openRequest.onupgradeneeded = function (e) {
      const thisDB = e.target.result
      console.log("running onupgradeneeded")
      if (!thisDB.objectStoreNames.contains("crap")) {
        thisDB.createObjectStore("crap", {
          keyPath: "id",
          autoIncrement: true
        })
      }
    }
    openRequest.onsuccess = function (e) {
      log("connected!")

      const db = e.target.result
      addIDBData(db)
    }
    openRequest.onerror = function (e) {
      log("error!")
    }
  } else if (selection === "memory") {
    log("memory starting...")
    let acc = []
    // 50
    for (let step = 0; step < 500; step++) {
      if (error != null) {
        break
      }
      idx += 1
      log(`adding ${idx} w/ size ${data500KB.length}...`)
      try {
        // 50 MB
        let data = new Uint8Array(1024 * 1024 * 50)
        data.fill(idx)
        acc.push(data)
        log(`added ${idx}!`)
      } catch (e) {
        log(`failed ${idx}! ${e}`)
        error = e
      }
    }
  } else {
    log("unknown...")
  }
}