Naalya Handbook
Using Chowbea

Exporting a type on the bus

Put a TypeScript type, interface, or enum on the Chowbea type bus so the Education Hub can import it without a Swagger detour.

You have a type the Hub needs that does not belong on a REST DTO: a websocket payload map, a domain enum, a union that OpenAPI would flatten into mush. You export it from a *.chowbea.ts barrel, extract, and the Hub generates it under _generated/bus.

This page is the ticket "share RealtimeEventMap" or "stop mirroring Action by hand on the Hub."

If the type already appears on an HTTP body, put @ApiProperty on the DTO instead and use the REST client. Do not dual-publish the same DTO on the bus.

What you will touch

  1. libs/shared/src/<area>/<area>.chowbea.ts (create or extend).
  2. pnpm bus:extract — writes chowbea.bus.json.
  3. Nothing on the Hub until they regenerate — Using a bus type in the Hub.

Existing barrels:

BarrelWhat it exports
permission/permission.chowbea.tsAction, Resource
user/user.chowbea.tsUserType
realtime/realtime.chowbea.tsRealtimeEventMap, RealtimeEventName, InvalidationAction, InvalidationPayloadMap, InvalidateChange

Step 1: Create or extend a barrel

Every exported type, interface, or enum in a *.chowbea.ts file rides the bus. Re-export from the real definition file — keep the barrel thin.

libs/shared/src/realtime/realtime.chowbea.ts
// Realtime types shared with Education-Hub over the chowbea type bus.
// These never appear in a REST response, so the OpenAPI spec cannot carry
// them. Closed-world: anything RealtimeEventMap references must also ride
// the bus, which is why InvalidationAction is here.
export type { RealtimeEventMap, RealtimeEventName } from './realtime.types';
export { InvalidationAction } from './invalidation-action';
export type {
  InvalidationPayloadMap,
  InvalidateChange,
} from './invalidation-payload';
libs/shared/src/permission/permission.chowbea.ts
export { Action, Resource } from './permission.enum';

Alternatively, tag a single declaration in any file:

/** @chowbea-export */
export interface Plan {
  name: string;
  seats: number;
}

Prefer a barrel next to the domain. One file per area matches the generated Hub path (libs.shared.src.realtime.realtime.ts).


Step 2: Satisfy the extractor

chowbea-axios extract fails with file:line when you break a rule:

Types only. type, interface, enum. Classes, consts, and functions are rejected — runtime values other than enums do not belong on a type bus. Share logic via a package, not the bus.

Closed world. A bus type may reference primitives, TypeScript built-ins (Map, Record, generics, mapped/conditional/template-literal types), and other bus types. If RealtimeEventMap names InvalidateChange, InvalidateChange must be exported on the bus too. Referencing a random project type errors and tells you to add it. node_modules types are rejected.

No duplicate names anywhere on the bus. Two barrels cannot both export Action.

When you add a field to RealtimeEventMap that uses a new helper type, export that helper in the same barrel in the same PR. That is why InvalidationAction sits next to the event map.


Step 3: Extract and serve

terminal (API repo root)
pnpm bus:extract
# or during work:
pnpm bus:watch

Confirm the symbol is in the manifest (chowbea.bus.json at the API repo root — look under barrels for your file path and name). Do not hand-edit that file. With the API server running:

curl -s http://localhost:8000/.well-known/chowbea.json | head

File-mode busHandler() picks up the new mtime. No restart.

prebuild already runs extract, so a production image ships a fresh manifest. bus:check in CI should stay green.


Step 4: Breaking changes

Renaming or removing a bus export breaks the Hub at compile time after they fetch — that is the point. Coordinate the PRs: API extract + Hub regen in a follow-up (or a same-day pair).

pnpm bus:diff   # --check --diff staging --fail-on-removed

Use that when you need a tripwire against accidental removals.


Worked example

You are not inventing a new barrel; you are extending one.

  1. Add a member to InvalidationAction and a row to InvalidationPayloadMap.
  2. Both already export from realtime.chowbea.ts — extract picks them up.
  3. Hub regenerates; InvalidationAction.YOUR_THING exists.
  4. Register the Hub resolver — Invalidating Hub queries.

No Swagger change. No fake DTO.


Gotchas

Do not put DTOs on the bus "for convenience." Hub features should keep using api.contracts for HTTP bodies. Dual sources drift.

Enums are the exception to "no runtime." String enums extract as real enum objects on the Hub, which is why Action.READ works in app code.

extract needs the real tsconfig. It builds a TypeScript program. If it is not the nearest config, pass --project.

A barrel that exports nothing useful still has a cost — skip empty files. Sarah's empty skill folder is fine; an empty .chowbea.ts is noise.

Where to go next

On this page