Complete client-only shared counter

Create podda.json using the manifest topic, then add index.html and app.js. Import the SDK exactly as documented; do not probe or feature-detect method names.

<!doctype html><html lang="en"><meta name="viewport" content="width=device-width"><title>Shared counter</title><body><main><h1>Shared counter</h1><output id="count">0</output><button id="add">+1</button><p id="status"></p></main><script type="module" src="./app.js"></script></body></html>
import { init, PoddaSdkError } from "/__podda/v2/sdk.js";
const app = await init();
const count = document.querySelector("#count");
const status = document.querySelector("#status");
async function read() {
  try { return await app.kv.get("counter"); }
  catch (error) {
    if (error instanceof PoddaSdkError && error.code === "KV_NOT_FOUND") return { value: 0, version: undefined };
    throw error;
  }
}
async function render() { count.textContent = String((await read()).value); }
document.querySelector("#add").addEventListener("click", async () => {
  for (let attempt = 0; attempt < 6; attempt += 1) {
    const current = await read();
    try {
      const next = await app.kv.set("counter", Number(current.value) + 1, { ifVersion: current.version, idempotencyKey: crypto.randomUUID() });
      count.textContent = String(next.value); status.textContent = "Saved"; return;
    } catch (error) {
      if (!(error instanceof PoddaSdkError) || error.code !== "VERSION_CONFLICT") throw error;
    }
  }
  status.textContent = "Busy—please try again.";
});
render().catch((error) => { status.textContent = error.message; app.errors.capture(error); });

For a leaderboard, store one CAS-protected JSON object such as { total, members: { [app.user.id]: { name, count } } }, obtain display names from app.members.list(), and recompute the entire object after every conflict.

For a generated-server variant, declare the server capability and server.mjs entrypoint, then use this complete asset-fallback module:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (url.pathname !== "/api/health")
      return url.pathname.startsWith("/api/")
        ? Response.json({ error: { code: "ROUTE_NOT_FOUND" } }, { status: 404 })
        : env.ASSETS.fetch(request);
    if (request.method !== "GET")
      return new Response("Method not allowed", { status: 405, headers: { Allow: "GET" } });
    const session = await fetch(new URL("/__podda/v2/session", request.url));
    if (!session.ok) return Response.json({ ready: false }, { status: session.status });
    const { user } = await session.json();
    return Response.json({ ready: true, user_id: user.id });
  }
};

Last updated 22 August 2026 · Documentation version 7