| 1 | # @fixtures/waku-durable-objects |
| 2 | |
| 3 | E2e fixture for `@alchemy.run/frontend-frameworks/waku` exercising a **framework site plus the |
| 4 | user's own Durable Objects on the same worker** — the pattern alchemy's |
| 5 | `Website.Vite` supports via its custom `main` entry, carried generically by |
| 6 | framework-core's `DeployTarget.entry` (the user-entry seam; see |
| 7 | `packages/frontend-frameworks/src/core/DeployTarget.ts` and the "user-entry seam" |
| 8 | section of `packages/frontend-frameworks/src/core/README.md`). |
| 9 | |
| 10 | The app is a waku site (a dynamic page + a `/counter` API route) whose |
| 11 | `src/worker-entry.ts` is the user's own worker entry: it wraps waku's emitted |
| 12 | fetch handler via `virtual:waku/server-entry` and exports `class Counter`, a |
| 13 | SQLite-backed Durable Object. `e2e.config.ts` declares the namespace |
| 14 | (`durableObjectNamespaces` on the dev worker config, `durableObjects` on the |
| 15 | miniflare preview config) and binds it with |
| 16 | `DurableObjectNamespace.local({ binding: "COUNTER", className: "Counter" })`. |
| 17 | |
| 18 | ## How the seam works |
| 19 | |
| 20 | 1. **User `main` precedence** — `target.cloudflare.worker.main` |
| 21 | (`./src/worker-entry.ts`, resolved against the project root) takes |
| 22 | precedence over waku's pinned rsc server entry |
| 23 | (`makeWakuPluginOptions` in `packages/waku/src/cloudflare.ts`). The |
| 24 | wrapped entry is bundled in the **rsc** environment (waku's entry |
| 25 | topology `{ entry: "rsc", children: ["ssr"] }` is unchanged). |
| 26 | 2. **`virtual:waku/server-entry`** — the stable importable specifier for |
| 27 | waku's server handler (precedent: React Router's |
| 28 | `virtual:react-router/server-build`). Resolved by |
| 29 | `makeWakuServerEntryPlugin` to the installed |
| 30 | `<wakuDirectory>/dist/lib/vite-entries/entry.server.js`, whose default |
| 31 | export is the adapter's `ExportedHandler`, in dev and build alike. |
| 32 | 3. **Entry-chunk selection** — the target surfaces the config's `main` as |
| 33 | the generic `DeployTarget.entry` carriage; the platform-neutral framework |
| 34 | half pins the built user-entry chunk as `serverModules[0]` |
| 35 | (framework-core's `selectEntryByFacade`), while waku's own |
| 36 | `server/index.js` remains an ordinary chunk the user entry imports. |
| 37 | 4. **Dev** — the cloudflare vite plugin's module runner serves the wrapped |
| 38 | user entry, so the `Counter` class exists in dev too. |
| 39 | |
| 40 | ## What the app exercises |
| 41 | |
| 42 | - SSR page (`/`) reading the Counter DO at request time (`data-testid=do-count`) |
| 43 | - `/counter` API route: `POST` increments, `GET` reads — asserted to |
| 44 | increment **across requests**, proving DO instance identity on the same |
| 45 | worker (live + dev) |
| 46 | - static asset (`/hello.txt`) still served alongside the custom entry |
| 47 | - the `MESSAGE` Text binding through the wrapped handler (framework routes |
| 48 | unaffected by wrapping) |
| 49 | |
| 50 | ## Commands |
| 51 | |
| 52 | ```sh |
| 53 | bun run dev # waku dev with the rsc environment in workerd (port 3110) |
| 54 | bun run build # waku build -> dist/ + dist/build.json |
| 55 | bun run preview # miniflare over dist/build.json |
| 56 | bun run test # playwright e2e (live + dev) |
| 57 | ``` |