aaw start events errors push auth sessions reset store

aaw

async/await over WebSockets. Your backend is a folder of functions, and every file in it is callable from the browser as if it were local. Bun Zero dependencies Rooms Optional auth

Your first server

Point aaw at a folder. Every .js file in it becomes an event the client can await.

BACKEND index.js BACKEND events/greet.js FRONTEND app.js

Events are files

The path is the name. A folder is a namespace, and adding a capability is adding a file — there is no router to register with and no client to regenerate.

events/ ├── greet.js → ws.sendAsync('greet') ├── teamplay/ │ ├── list.js → ws.sendAsync('teamplay/list') │ └── chat.js → ws.sendAsync('teamplay/chat') └── user/ └── statistics.js → ws.sendAsync('user/statistics')

An async handler replies to the caller. Drop the keyword and the event is fire‑and‑forget — nothing is sent back.

BACKEND events/teamplay/list.js BACKEND events/track.js

Third‑party services are handed to every handler. Pass them once when the server starts and they arrive in the second argument, alongside ws and room.

BACKEND index.js

Errors and timeouts

A handler that throws rejects the caller's promise with { error }. There is no status code to map and no envelope to unwrap.

BACKEND events/user/login.js FRONTEND app.js sendAsync gives up after 3 seconds. The socket stays open and the handler keeps running — only the caller stopped listening. Size the third argument to the slowest call you expect. FRONTEND app.js

Push and multicast

Handlers can talk back without being asked. ws.broadcast reaches every connection; a room reaches a named subset, and membership clears itself when a client disconnects.

BACKEND events/teamplay/join.js FRONTEND app.js

The fourth argument to room.emit is a connection to skip — pass ws so the sender does not hear its own message.

Authentication, if you want it

Off by default. Turn it on with a fifth argument for server‑minted sessions, a folder convention for who may call what, and a SQLite store you never configure.

BACKEND index.js

Events under auth/ need a session. Everything else is open. Where the file sits is the rule — no per‑file flag, no policy list to keep in sync. A protected handler never checks a token: it reads identity, because the runtime refused the call otherwise.

BACKEND events/ ├── health.js anyone ├── teamplay/ │ └── list.js anyone └── auth/ ├── chat.js needs a session └── admin/ └── seed.js needs a session BACKEND events/auth/chat.js Leaving auth off does not open those events — it makes them unreachable, and aaw lists them at boot.

Sessions on the client

Connecting is free. A token is what buys the right to call anything under auth/. aaw's own events live under aaw/ and are called like any other — there is no separate login API.

FRONTEND app.js

The session binds to the connection, so nothing carries a token in its body. Hand the token back on the next page load and aaw restores it before open fires.

FRONTEND app.js
EventDoes
aaw/registerCreate an account and bind a session
aaw/loginBind a session to this connection
aaw/resumeRe‑bind an existing token, which reconnects do for you
aaw/logoutEnd the session everywhere

Password reset

aaw mints the token and verifies it. Delivering it is yours — a transport library has no business holding your mail credentials, so it hands you the token through onPasswordReset. Here it is with Resend; Postmark, SES, SMTP or your own queue plug in the same place.

BACKEND index.js FRONTEND forgot-password.js FRONTEND reset-password.js

The token is crypto.randomUUID(), single use, and expires — checked where the password changes, not only where the form renders. An unknown address answers exactly like a known one, so the endpoint cannot discover who has an account. On success the caller is logged straight in.

Without an onPasswordReset handler there is no way to deliver a token, so the request answers with an error rather than a success nobody can act on.

When your users live elsewhere

The SQLite store is a default, not a requirement. Pass store and aaw never opens a database — for when accounts already live in Mongo, or a "user" is an API key in a committed file.

BACKEND index.js

For credentials aaw knows nothing about — a licence key, a magic link, SSO — write an ordinary event and bind the session yourself.

BACKEND events/redeem-licence.js