Skip to content

examples/chat: typegres + Cap'n Web on a Durable Object#94

Merged
ryanrasti merged 1 commit into
mainfrom
ryan_5_example_chat
Jul 24, 2026
Merged

examples/chat: typegres + Cap'n Web on a Durable Object#94
ryanrasti merged 1 commit into
mainfrom
ryan_5_example_chat

Conversation

@ryanrasti

@ryanrasti ryanrasti commented Jul 23, 2026

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a complete, runnable “typegres chat” example that demonstrates capability-scoped queries authored in the browser and replayed inside a Cloudflare Durable Object over Cap’n Web, with a Vite/React SPA, DO-backed SQLite schema, migrations/seed, and an end-to-end test suite.

Changes:

  • Introduces a Durable Object Worker implementation (schema-as-API via facets), plus migration/seed and PBKDF2-based demo auth.
  • Adds a React SPA with a wire log + console-style query prompt for client-authored queries and live subscriptions.
  • Adds Wrangler/Vite/Vitest configuration and multiple DO + WebSocket-driven tests to validate confinement claims.

Reviewed changes

Copilot reviewed 22 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
examples/chat/wrangler.jsonc Wrangler configuration for SPA assets + /ws DO routing and DO binding/migration.
examples/chat/worker/migrate.ts SQLite DDL plus seed data for an initial lobby experience.
examples/chat/worker/index.ts Worker entrypoint that forwards /ws to the Chat durable object.
examples/chat/worker/chat.ts Core “schema is the API” table/facet capability graph and exposed RPC surface.
examples/chat/worker/chat-do.ts Durable Object wrapper that attaches DO SQLite storage and serves Cap’n Web RPC.
examples/chat/worker/auth.ts Demo PBKDF2 password hashing/verification using WebCrypto.
examples/chat/vitest.config.ts Vitest configuration to run in workerd via Cloudflare’s workers pool + SWC decorators.
examples/chat/vite.config.ts Vite configuration with SWC decorator lowering and Cloudflare plugin integration.
examples/chat/tsconfig.json TypeScript configuration for SPA + worker + tests.
examples/chat/tests/facet-spike.test.ts In-DO spike test proving facet classes and RPC/hydration behavior end-to-end.
examples/chat/tests/env.d.ts Test env typing to expose Wrangler bindings to cloudflare:test.
examples/chat/tests/do-roundtrip.test.ts DO-local tests for migration/seed and typed insert/select round-trips.
examples/chat/tests/capabilities.test.ts End-to-end confinement + live subscription behavior over real WebSocket route.
examples/chat/src/wire-log.ts Client-side wire log store and prompt injection helpers.
examples/chat/src/styles.css Tailwind import and stat flash animation for live-updating counts.
examples/chat/src/rpc.ts Client RPC setup + logging wrappers + window.* exposure helpers.
examples/chat/src/main.tsx SPA bootstrap without StrictMode to avoid duplicating network effects in the wire log.
examples/chat/src/components/WireLog.tsx UI component for displaying RPC traffic and evaluating local prompt expressions.
examples/chat/src/App.tsx Main app UI: login, directory, room view, live feed, and stats aggregation entrypoint.
examples/chat/README.md Documentation explaining the model, run commands, and capability graph rationale.
examples/chat/package.json Example app dependencies and scripts (dev/test/deploy/typecheck).
examples/chat/index.html Minimal SPA HTML shell.
examples/chat/.gitignore Ignores build artifacts, Wrangler output, and generated worker types.
Comments suppressed due to low confidence (1)

examples/chat/src/App.tsx:483

  • runStats() currently starts a live subscription but never stores the returned subscription handle (statsSub.current) and never updates stats (the old setStats(rows) is commented out). As a result the “top posters” button doesn’t render anything and the subscription can’t be unsubscribed via stopStats() / unmount cleanup.
        .select(({ users, messages }) => ({ author: users.name, posts: messages.id.count() }))
        .orderBy(({ messages }) => [messages.id.count(), "desc"])
        .live()
        .observe({ onNext: setStatsByRef })
    );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread examples/chat/src/App.tsx
Comment on lines +444 to +447
return () => {
cancelled = true;
void sub?.unsubscribe();
};
Comment on lines +138 to +139
await sub.unsubscribe();
const pushesAfterStop = got.length;

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 26 changed files in this pull request and generated 5 comments.

Comment thread examples/chat/src/App.tsx
Comment on lines +526 to +546
const runStats = async () => {
// The server has no "top posters" endpoint. This aggregation is
// authored here, client-side, and replayed against the room's granted
// builder — it cannot reach past this room.
//
// One-shot: computes the standings once. To make them update live, swap
// `.execute()` for a subscription that pushes on every change:
// statsSub.current = await loggedDoRpc(current.stub, (r) =>
// r.messages().groupBy(...).select(...).orderBy(...)
// .live().observe({ onNext: setStatsByRef }));
const rows = await loggedDoRpc(current.stub, (r) =>
r
.messages()
.groupBy(({ users }) => [users.name])
.select(({ users, messages }) => ({ author: users.name, posts: messages.id.count() }))
.orderBy(({ messages }) => [messages.id.count(), "desc"])
.live()
.observe({ onNext: setStatsByRef })
);
//setStats(rows);
};
Comment thread examples/chat/src/App.tsx
Comment on lines +65 to +70
const onLogout = () => {
localStorage.removeItem(AUTH_KEY);
localStorage.removeItem(ROOM_KEY);
setUser(null);
setUserName("");
};
// Durable Object. Static assets (the SPA) are served by the assets binding;
// wrangler.jsonc routes /ws here first.
export default {
async fetch(request, env): Promise<Response> {
Comment on lines +28 to +43
export const verifyPassword = async (password: string, stored: string): Promise<boolean> => {
const [scheme, iterStr, saltB64, hashB64] = stored.split(":");
if (scheme !== "pbkdf2" || !iterStr || !saltB64 || !hashB64) {
return false;
}
const expected = unb64(hashB64);
const actual = await derive(password, unb64(saltB64), Number(iterStr));
if (expected.length !== actual.length) {
return false;
}
let diff = 0;
for (let i = 0; i < expected.length; i++) {
diff |= expected[i]! ^ actual[i]!;
}
return diff === 0;
};
Comment on lines +19 to +20
this.conn = db.attach(new DoSqliteDriver(ctx.storage));
ctx.blockConcurrencyWhile(() => migrate(this.conn));
@ryanrasti
ryanrasti force-pushed the ryan_5_example_chat branch from fb1c94a to 58e45a1 Compare July 24, 2026 02:43
@ryanrasti
ryanrasti merged commit 810352c into main Jul 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants