Dev Orchestrator
bun run dev starts the entire development stack in a coordinated sequence with colored terminal output, including code generation from your Laravel routes.
Startup Sequence
bun run dev
|
1. Load .env + resonance.config.ts
|
2. Start Laravel server (php artisan serve --port=8880)
3. Poll /up until 200 <- server must be up for generation
|
4. Run code generation (bunx kubb generate)
5. Wait 2s <- let filesystem writes settle
| before Vite starts watching
6. Start in parallel:
+---> Vite dev server
+---> Pail (log streaming)
+---> Queue worker (if enabled)
+---> Custom commands (from config)
Code Generation Pipeline
This is how your Laravel routes become type-safe React Query hooks.
Scramble reads your Laravel routes and generates an OpenAPI 3.x spec. It uses the route name as the operationId. The spec is served at /docs/api.json while the dev server runs.
Kubb fetches that spec and runs four plugins in sequence:
Laravel Routes (named routes)
|
v
Scramble -> OpenAPI JSON spec (/docs/api.json)
|
v
Kubb (4 plugins: OAS -> TS -> Zod -> React Query)
|
v
.resonance/ output directory
|
+--> types/ (TypeScript interfaces)
+--> zod/ (Zod validation schemas)
+--> hooks/ (React Query hooks)
|
v
Vite HMR (hot reload in browser)
Route Name Convention
Hook names derive from Laravel route names. The conversion:
Route::post('/demo/tasks', 'store')->name('demo.tasks.store')
-> operationId: 'demo.tasks.store'
-> hook: useDemoTasksStore()
Rules: split by dots, PascalCase each segment, prepend use.
| Route Name | HTTP Method | Generated Hook |
|---|---|---|
login | POST | useLogin() |
users.index | GET | useUsersIndex() |
demo.tasks.index | GET | useDemoTasksIndex() |
demo.tasks.store | POST | useDemoTasksStore() |
demo.tasks.destroy | DELETE | useDemoTasksDestroy() |
demo.profile.show | GET | useDemoProfileShow() |
demo.profile.update | POST | useDemoProfileUpdate() |
demo.posts.index | GET | useDemoPostsIndex() |
demo.posts.like | POST | useDemoPostsLike() |
GET routes produce useQuery hooks. POST/PUT/PATCH/DELETE routes produce useMutation hooks.
Generated Output
resources/js/.resonance/
├── types/
│ ├── DemoTasksIndex.ts
│ ├── DemoTasksStore.ts
│ └── ...
├── zod/
│ ├── demoTasksStoreRequest.ts
│ └── ...
└── hooks/
├── useDemoTasksIndex.ts
├── useDemoTasksStore.ts
└── ...
The directory is .gitignored. Generated types match your API's request and response shapes. Generated hooks wire to the kubb-client, which handles envelope parsing and signal processing.
The queryKey in each hook contains a { url: string } object -- this is how the invalidation system matches signals to cached queries.
Regeneration
After changing your API (adding routes, modifying return types, updating Form Requests), regenerate:
bun run generate
The server must be running -- Kubb fetches the spec from the live OpenAPI endpoint. Vite detects the updated files and triggers HMR.
clean: false in the Kubb config prevents deleting the output directory on each run (TanStack Router's routeTree.gen.ts lives nearby). The dev orchestrator adds a 2-second delay after generation to prevent partial-write HMR flicker.
Terminal Output
Each process gets a colored prefix:
[server] INFO Server running on [http://127.0.0.1:8880].
[vite] VITE v7.0.7 ready in 234 ms
[pail] ┌ 2024-01-15 14:23:01 INFO ─────────┐
[pail] │ Task created: Buy groceries │
[queue] [2024-01-15 14:23:01] Processing: App\Jobs\SendNotification
Process Management
Child processes run with piped stdio and FORCE_COLOR: '1' so tools like Vite and Artisan emit colored output through pipes.
If any service crashes, the entire stack shuts down -- running with a dead queue worker or broken Vite server leads to confusing partial failures. On Ctrl+C:
SIGTERMto all children- Wait 3 seconds for graceful exit
SIGKILLanything still running
Configuration
// resonance.config.ts
export default {
server: {
host: '127.0.0.1',
port: 8880,
},
queue: true,
customCommands: [
{
name: 'horizon',
cmd: ['php', 'artisan', 'horizon'],
color: 'magenta',
},
],
kubb: {
openApiUrl: 'http://localhost:8880/docs/api.json',
outputPath: './resources/js/.resonance',
clientImportPath: '@jhavenz/resonance/client/kubb-client',
},
};
Config can also be a function receiving { mode, command }. The loader searches for resonance.config.ts, .js, or .mjs. If none exists, defaults apply.
Services
| Service | Command | Purpose |
|---|---|---|
| Laravel Server | php artisan serve | Hosts app + OpenAPI spec endpoint |
| Code Generation | bunx kubb generate | One-shot type/hook generation from spec |
| Vite | bunx vite | Frontend dev server with HMR |
| Pail | php artisan pail | Real-time log streaming |
| Queue Worker | php artisan queue:work | Job processing (optional) |
| Custom | Configurable | Horizon, Reverb, project-specific tools |