apps/api/src/resources/<name>/handlers/*.ts. Each file calls eventBus.on(...) at module scope — importing the file registers the subscription.
Anatomy of a handler
A handler file importseventBus, subscribes to one `${table}.${type}` key, iterates docs, and wraps the work in a try/catch so a side-effect failure never takes down the request that triggered it:
data is the typed MutationEvent for the users table, so user is a fully-typed User row — user.fullName, user.email and user.id are all known to the compiler.
Reacting to updates
users.update events carry both the new rows (docs) and the rows as they were (prevDocs). Here Ship pushes every updated user to their own browser over Socket.IO:
prevDocs (same order as docs):
Opt a table into events
A table only emits events when itsDbService is constructed with the event-bus hook. This is wired in the generated src/db.ts:
eventBus.hook('users') returns the onMutation callback DbService calls after every write — it re-emits the mutation as users.insert / users.update / users.delete. A table created without the fourth argument (e.g. sessions, verifications) writes silently and fires no events. Add the hook when you want a table to be observable.
src/db.ts is generated by codegen-db.ts. Re-run pnpm --filter api codegen after adding a schema; then add eventBus.hook('<table>') to that table’s DbService if it should emit events.Registration is automatic
You never wire handlers up by hand.codegen-router.ts scans every resources/<name>/handlers/ folder and emits a side-effect import at the top of the generated src/router.ts:
eventBus.on(...), registering the subscription. So the workflow is:
Add the file
Create
resources/<name>/handlers/<what-it-does>.ts and call eventBus.on('<table>.<type>', ...).Keep handlers safe
- Always
try/catch. A handler runs in the same process as the write; an unguarded throw shouldn’t surface as a request error. Log and move on. - Iterate
docs. Bulk mutations (insertMany,updateMany,deleteMany) deliver one event with many rows. - Stay idempotent where you can. Treat handlers as best-effort reactions, not part of the transaction.
Next steps
- The event shape and types: Mutation events.
- Cross-resource patterns: Using events.
