1/**
2 * The user's own worker entry — the whole point of this fixture.
3 *
4 * It wraps waku's emitted fetch handler (so every framework route still
5 * works) and ADDITIONALLY exports the `Counter` Durable Object class, which
6 * must live on the same worker for the `COUNTER` namespace binding to
7 * resolve. This mirrors alchemy's Website.Vite "Custom Worker Entry"
8 * pattern (`main: "worker/index.ts"` re-exporting DO classes).
9 *
10 * `virtual:waku/server-entry` is the import seam for waku's server handler
11 * (waku's real entry, `dist/lib/vite-entries/entry.server.js`, is not on
12 * waku's exports map, so the integration exposes it — the same way React
13 * Router exposes `virtual:react-router/server-build`).
14 */
15import { DurableObject } from "cloudflare:workers";
16import wakuHandler from "virtual:waku/server-entry";
17
18/** A SQLite-backed Durable Object owned by the user app, not the framework. */
19export class Counter extends DurableObject {
20 #ensureTable() {
21 this.ctx.storage.sql.exec(
22 "CREATE TABLE IF NOT EXISTS counter (id INTEGER PRIMARY KEY, value INTEGER NOT NULL)",
23 );
24 }
25
26 async get(): Promise<number> {
27 this.#ensureTable();
28 const rows = this.ctx.storage.sql
29 .exec("SELECT value FROM counter WHERE id = 0")
30 .toArray();
31 return Number(rows[0]?.value ?? 0);
32 }
33
34 async increment(): Promise<number> {
35 this.#ensureTable();
36 const rows = this.ctx.storage.sql
37 .exec(
38 "INSERT INTO counter (id, value) VALUES (0, 1) ON CONFLICT (id) DO UPDATE SET value = value + 1 RETURNING value",
39 )
40 .toArray();
41 return Number(rows[0]!.value);
42 }
43}
44
45export default {
46 fetch: (request: Request, env: unknown, ctx: unknown) =>
47 wakuHandler.fetch(request, env, ctx),
48};