Copy This To Markdown So My Agent Can Read This Because I Don't Have The Time to Read It Myself ATM

5-minute guide · Browser

Make one browser tab add responsibly.

A browser can increment a number in one line, which is obviously inadequate. We will add SQLite WASM, OPFS, Web Locks, BroadcastChannel, a module worker, and a durable ten-minute alarm so the eventual + 1 can survive every tab dramatically closing.

01

Give one integer an origin strategy

The module worker owns the actor runtime. sharedSqliteWasm gives every tab one OPFS database and elects a new holder when the current tab makes its escape.

Scope confirmation

Yes, this is an SQLite database, a Web Lock, a BroadcastChannel, and a durable alarm so that one integer can become one larger. We are doing excellent work.

worker.mjs
import {
  Actor,
  configure,
  sharedSqliteWasm,
} from "https://esm.sh/solid-objects@0.14.3/browser/host"

const HOLD_MILLISECONDS = 10 * 60 * 1000

class TicketCounter extends Actor {
  static actorType = "TicketCounter"
  available = 1
  holds = {}

  hold({ buyer }) {
    if (this.available === 0 || buyer in this.holds) {
      return { held: false, available: this.available }
    }

    this.available -= 1
    this.holds = { ...this.holds, [buyer]: Date.now() }
    this.schedule({
      at: new Date(Date.now() + HOLD_MILLISECONDS),
      key: buyer,
    }).expire({ buyer })

    return { held: true, available: this.available }
  }

  expire({ buyer }) {
    if (!(buyer in this.holds)) return this.available

    const remainingHolds = { ...this.holds }
    delete remainingHolds[buyer]
    this.holds = remainingHolds
    this.available += 1
    return this.available
  }
}

const runtime = configure({
  database: sharedSqliteWasm({ path: "tickets.db" }),
  authorizeMessage: () => true,
  authorizeQuery: () => true,
})

await runtime.install()
const controller = new AbortController()
void runtime.run(controller.signal)

const counter = TicketCounter.ref("show-42")

self.addEventListener("message", async (event) => {
  if (event.data.type === "hold") {
    self.postMessage(await counter.hold({ buyer: event.data.buyer }))
    return
  }

  if (event.data.type === "status") {
    self.postMessage({ available: await counter.available })
  }
})

For this local example only: the authorization policies allow every call. Apply real authorization before accepting untrusted input.

02

Add two buttons for the one number

One button starts the hold. The other asks whether our daring integer has completed its ten-minute character arc.

index.html
<button data-action="hold">Hold the last ticket</button>
<button data-action="status">Check the sacred integer</button>
<output id="status">Available: 1</output>

<script type="module">
  const worker = new Worker("./worker.mjs", { type: "module" })
  const status = document.querySelector("#status")

  worker.addEventListener("message", (event) => {
    status.textContent = `Available: ${event.data.available}`
  })

  document.querySelector('[data-action="hold"]').addEventListener("click", () => {
    worker.postMessage({ type: "hold", buyer: "ada" })
  })

  document.querySelector('[data-action="status"]').addEventListener("click", () => {
    worker.postMessage({ type: "status" })
  })
</script>
03

Serve the two files

OPFS needs an origin, so open the page through localhost instead of a file URL.

Terminal
npx --yes serve . --listen 3000

Open http://localhost:3000 and hold the ticket. Availability becomes 0. A second tab cannot also claim the last one, despite its confidence.

04

Close everything at the exciting part

Close every tab before the deadline. Reopen the page after ten minutes, wait for the runtime to claim the due reminder, then check the counter. OPFS remembers what the vanished workers promised.

hold → available: 0 ten minutes and several closed tabs later → available: 1

In an application with a Content Security Policy, allow https://esm.sh and 'wasm-unsafe-eval'. Pin the package version as shown above.

A triumph for addition

The browser was never short on arithmetic.

The useful part is one event identity across tabs, plus a delayed release that survives losing every JavaScript heap. Without delayed work, a Web Lock and a transaction are delightfully less theatrical.

Seriously, for a moment: This same shape appears in chat rooms, collaborative documents, offline carts, shared sessions, timed reservations, games, and device control. Keeping state, delayed work, retries, and cross-tab ownership correct through reloads and closed browsers is genuinely non-trivial. That is the problem Solid Objects is meant to solve.

Read the complete Browser guide →