@zap-js/client 0.0.2 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +310 -24
  2. package/bin/zap +0 -0
  3. package/bin/zap-codegen +0 -0
  4. package/dist/cli/commands/build.d.ts +11 -0
  5. package/dist/cli/commands/build.js +282 -0
  6. package/dist/cli/commands/codegen.d.ts +8 -0
  7. package/dist/cli/commands/codegen.js +95 -0
  8. package/dist/cli/commands/dev.d.ts +20 -0
  9. package/dist/cli/commands/dev.js +78 -0
  10. package/dist/cli/commands/new.d.ts +9 -0
  11. package/dist/cli/commands/new.js +307 -0
  12. package/dist/cli/commands/routes-old.d.ts +9 -0
  13. package/dist/cli/commands/routes-old.js +106 -0
  14. package/dist/cli/commands/routes.d.ts +11 -0
  15. package/dist/cli/commands/routes.js +280 -0
  16. package/dist/cli/commands/serve.d.ts +17 -0
  17. package/dist/cli/commands/serve.js +386 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +76 -0
  20. package/dist/cli/utils/index.d.ts +2 -0
  21. package/dist/cli/utils/index.js +2 -0
  22. package/dist/cli/utils/logger.d.ts +84 -0
  23. package/dist/cli/utils/logger.js +181 -0
  24. package/dist/cli/utils/port-finder.d.ts +8 -0
  25. package/dist/cli/utils/port-finder.js +48 -0
  26. package/dist/dev-server/codegen-runner.d.ts +41 -0
  27. package/dist/dev-server/codegen-runner.js +172 -0
  28. package/dist/dev-server/hot-reload.d.ts +72 -0
  29. package/dist/dev-server/hot-reload.js +280 -0
  30. package/dist/dev-server/index.d.ts +8 -0
  31. package/dist/dev-server/index.js +8 -0
  32. package/dist/dev-server/route-scanner.d.ts +84 -0
  33. package/dist/dev-server/route-scanner.js +113 -0
  34. package/dist/dev-server/rust-builder.d.ts +66 -0
  35. package/dist/dev-server/rust-builder.js +286 -0
  36. package/dist/dev-server/server.d.ts +147 -0
  37. package/dist/dev-server/server.js +660 -0
  38. package/dist/dev-server/vite-proxy.d.ts +56 -0
  39. package/dist/dev-server/vite-proxy.js +212 -0
  40. package/dist/dev-server/watcher.d.ts +48 -0
  41. package/dist/dev-server/watcher.js +127 -0
  42. package/dist/router/codegen-enhanced.d.ts +5 -0
  43. package/dist/router/codegen-enhanced.js +275 -0
  44. package/dist/router/codegen.d.ts +17 -0
  45. package/dist/router/codegen.js +654 -0
  46. package/dist/router/index.d.ts +16 -0
  47. package/dist/router/index.js +19 -0
  48. package/dist/router/scanner.d.ts +86 -0
  49. package/dist/router/scanner.js +689 -0
  50. package/dist/router/ssg.d.ts +115 -0
  51. package/dist/router/ssg.js +202 -0
  52. package/dist/router/types.d.ts +124 -0
  53. package/dist/router/types.js +9 -0
  54. package/dist/router/watch.d.ts +38 -0
  55. package/dist/router/watch.js +135 -0
  56. package/dist/runtime/csrf.d.ts +146 -0
  57. package/dist/runtime/csrf.js +166 -0
  58. package/dist/runtime/error-boundary.d.ts +129 -0
  59. package/dist/runtime/error-boundary.js +287 -0
  60. package/dist/runtime/hooks.d.ts +83 -0
  61. package/dist/runtime/hooks.js +96 -0
  62. package/dist/runtime/index.d.ts +229 -0
  63. package/dist/runtime/index.js +449 -0
  64. package/dist/runtime/ipc-client.d.ts +144 -0
  65. package/dist/runtime/ipc-client.js +621 -0
  66. package/dist/runtime/logger.d.ts +71 -0
  67. package/dist/runtime/logger.js +164 -0
  68. package/dist/runtime/middleware.d.ts +66 -0
  69. package/dist/runtime/middleware.js +114 -0
  70. package/dist/runtime/process-manager.d.ts +51 -0
  71. package/dist/runtime/process-manager.js +207 -0
  72. package/dist/runtime/router-simple.d.ts +98 -0
  73. package/dist/runtime/router-simple.js +330 -0
  74. package/dist/runtime/router.d.ts +103 -0
  75. package/dist/runtime/router.js +435 -0
  76. package/dist/runtime/rpc-client.d.ts +35 -0
  77. package/dist/runtime/rpc-client.js +140 -0
  78. package/dist/runtime/streaming-utils.d.ts +86 -0
  79. package/dist/runtime/streaming-utils.js +150 -0
  80. package/dist/runtime/types.d.ts +465 -0
  81. package/dist/runtime/types.js +60 -0
  82. package/dist/runtime/websockets-utils.d.ts +50 -0
  83. package/dist/runtime/websockets-utils.js +92 -0
  84. package/package.json +30 -20
  85. package/index.js +0 -29
  86. package/internal/cli/package.json +0 -46
  87. package/internal/cli/tsconfig.tsbuildinfo +0 -1
  88. package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
  89. package/internal/dev-server/node_modules/ora/index.js +0 -416
  90. package/internal/dev-server/node_modules/ora/license +0 -9
  91. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
  92. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
  93. package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
  94. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  95. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
  96. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
  97. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
  98. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
  99. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
  100. package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
  101. package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
  102. package/internal/dev-server/node_modules/ora/package.json +0 -66
  103. package/internal/dev-server/node_modules/ora/readme.md +0 -325
  104. package/internal/dev-server/package.json +0 -41
  105. package/internal/router/package.json +0 -28
  106. package/internal/runtime/package.json +0 -41
  107. package/internal/runtime/src/error-boundary.tsx +0 -476
  108. package/internal/runtime/src/router-simple.tsx +0 -640
  109. package/internal/runtime/src/router.tsx +0 -771
  110. package/internal/runtime/tsconfig.tsbuildinfo +0 -1
  111. package/src/errors.js +0 -33
  112. package/src/logger.js +0 -10
  113. package/src/middleware.js +0 -32
  114. package/src/router.js +0 -41
  115. package/src/types.js +0 -39
@@ -0,0 +1,660 @@
1
+ import { EventEmitter } from 'events';
2
+ import path from 'path';
3
+ import { tmpdir } from 'os';
4
+ import { pathToFileURL } from 'url';
5
+ import { FileWatcher } from './watcher.js';
6
+ import { RustBuilder } from './rust-builder.js';
7
+ import { ViteProxy } from './vite-proxy.js';
8
+ import { CodegenRunner } from './codegen-runner.js';
9
+ import { HotReloadServer } from './hot-reload.js';
10
+ import { RouteScannerRunner } from './route-scanner.js';
11
+ import { ProcessManager, IpcServer } from '../runtime/index.js';
12
+ import { cliLogger } from '../cli/utils/logger.js';
13
+ // Register tsx loader for TypeScript imports
14
+ // This must be called before any dynamic imports of .ts files
15
+ let tsxRegistered = false;
16
+ async function ensureTsxRegistered() {
17
+ if (tsxRegistered)
18
+ return;
19
+ try {
20
+ const tsx = await import('tsx/esm/api');
21
+ tsx.register();
22
+ tsxRegistered = true;
23
+ }
24
+ catch {
25
+ // tsx not available, TypeScript imports won't work
26
+ }
27
+ }
28
+ /**
29
+ * DevServer - Unified development server orchestrator
30
+ *
31
+ * Coordinates all development components:
32
+ * - Rust backend compilation with file watching
33
+ * - Vite frontend dev server
34
+ * - Automatic TypeScript binding generation
35
+ * - Hot reload signaling
36
+ *
37
+ * Workflow:
38
+ * 1. Initial build of Rust backend
39
+ * 2. Generate TypeScript bindings
40
+ * 3. Start Vite dev server
41
+ * 4. Start hot reload WebSocket server
42
+ * 5. Watch for file changes and orchestrate rebuilds
43
+ */
44
+ export class DevServer extends EventEmitter {
45
+ constructor(config) {
46
+ super();
47
+ // Runtime components for Rust server + IPC
48
+ this.processManager = null;
49
+ this.ipcServer = null;
50
+ this.socketPath = '';
51
+ this.currentRouteTree = null;
52
+ this.registeredHandlers = new Map(); // handlerId -> filePath
53
+ // Timing
54
+ this.startTime = 0;
55
+ this.config = {
56
+ rustPort: 3000,
57
+ vitePort: 5173,
58
+ hotReloadPort: 3001,
59
+ logLevel: 'info',
60
+ release: false,
61
+ skipInitialBuild: false,
62
+ openBrowser: false,
63
+ ...config,
64
+ };
65
+ // If binaryPath is provided, skip initial build
66
+ if (this.config.binaryPath) {
67
+ this.config.skipInitialBuild = true;
68
+ }
69
+ this.state = {
70
+ phase: 'starting',
71
+ rustReady: false,
72
+ viteReady: false,
73
+ lastError: null,
74
+ };
75
+ // Initialize components
76
+ this.watcher = new FileWatcher({
77
+ rootDir: this.config.projectDir,
78
+ });
79
+ this.rustBuilder = new RustBuilder({
80
+ projectDir: this.config.projectDir,
81
+ release: this.config.release,
82
+ bin: 'zap',
83
+ binaryPath: this.config.binaryPath,
84
+ });
85
+ this.viteProxy = new ViteProxy({
86
+ projectDir: this.config.projectDir,
87
+ port: this.config.vitePort,
88
+ });
89
+ this.codegenRunner = new CodegenRunner({
90
+ projectDir: this.config.projectDir,
91
+ codegenBinary: this.config.codegenBinaryPath,
92
+ });
93
+ this.hotReloadServer = new HotReloadServer({
94
+ port: this.config.hotReloadPort,
95
+ });
96
+ this.routeScanner = new RouteScannerRunner({
97
+ projectDir: this.config.projectDir,
98
+ });
99
+ this.setupEventHandlers();
100
+ }
101
+ /**
102
+ * Set up event handlers for all components
103
+ */
104
+ setupEventHandlers() {
105
+ // File watcher events
106
+ this.watcher.on('rust', (event) => this.handleRustChange(event));
107
+ this.watcher.on('typescript', (event) => this.handleTypeScriptChange(event));
108
+ this.watcher.on('config', (event) => this.handleConfigChange(event));
109
+ this.watcher.on('error', (err) => this.log('error', `Watcher error: ${err.message}`));
110
+ // Rust builder events
111
+ this.rustBuilder.on('build-start', () => {
112
+ this.state.phase = 'rebuilding';
113
+ this.emit('rust-build-start');
114
+ });
115
+ this.rustBuilder.on('build-complete', (result) => {
116
+ this.emit('rust-build-complete', result);
117
+ });
118
+ this.rustBuilder.on('error', (msg) => {
119
+ this.log('error', msg);
120
+ });
121
+ this.rustBuilder.on('warning', (msg) => {
122
+ this.log('warn', msg);
123
+ });
124
+ // Vite events
125
+ this.viteProxy.on('ready', (port) => {
126
+ this.state.viteReady = true;
127
+ this.emit('vite-ready', port);
128
+ });
129
+ this.viteProxy.on('error', (err) => {
130
+ this.log('error', `Vite error: ${err.message}`);
131
+ });
132
+ this.viteProxy.on('hmr-update', () => {
133
+ this.emit('hmr-update');
134
+ });
135
+ // Hot reload events
136
+ this.hotReloadServer.on('client-connected', () => {
137
+ this.log('debug', 'Hot reload client connected');
138
+ });
139
+ // Codegen events
140
+ this.codegenRunner.on('complete', (result) => {
141
+ if (result.success) {
142
+ this.log('info', 'TypeScript bindings regenerated');
143
+ }
144
+ });
145
+ // Route scanner events
146
+ this.routeScanner.on('routes-changed', async (tree) => {
147
+ this.log('info', `Routes updated (${tree.routes.length} pages, ${tree.apiRoutes.length} API)`);
148
+ this.hotReloadServer.reload('routes', []);
149
+ // Restart Rust server to pick up new routes
150
+ try {
151
+ await this.restartRustServer();
152
+ }
153
+ catch (err) {
154
+ const message = err instanceof Error ? err.message : String(err);
155
+ this.log('error', `Failed to restart Rust server: ${message}`);
156
+ }
157
+ });
158
+ this.routeScanner.on('error', (err) => {
159
+ this.log('warn', `Route scanner error: ${err.message}`);
160
+ });
161
+ }
162
+ /**
163
+ * Start the development server
164
+ */
165
+ async start() {
166
+ this.startTime = Date.now();
167
+ cliLogger.header('ZapJS Dev Server');
168
+ try {
169
+ // Phase 1: Initial Rust build
170
+ if (!this.config.skipInitialBuild) {
171
+ await this.buildRust();
172
+ }
173
+ // Phase 2: Generate TypeScript bindings
174
+ await this.runCodegen();
175
+ // Phase 2.5: Scan routes
176
+ const routeTree = await this.scanRoutes();
177
+ this.currentRouteTree = routeTree;
178
+ // Phase 3: Start Rust HTTP server with IPC
179
+ await this.startRustServer(routeTree);
180
+ // Phase 4: Start other servers in parallel
181
+ await Promise.all([
182
+ this.startHotReloadServer(),
183
+ this.startViteServer(),
184
+ ]);
185
+ // Phase 5: Start file watcher and route watcher
186
+ this.startWatcher();
187
+ await this.startRouteWatcher();
188
+ // Ready!
189
+ this.state.phase = 'ready';
190
+ this.printReadyMessage();
191
+ // Open browser if requested
192
+ if (this.config.openBrowser) {
193
+ this.openBrowser();
194
+ }
195
+ }
196
+ catch (err) {
197
+ this.state.phase = 'error';
198
+ this.state.lastError = err instanceof Error ? err.message : String(err);
199
+ throw err;
200
+ }
201
+ }
202
+ /**
203
+ * Stop all servers immediately
204
+ */
205
+ async stop() {
206
+ if (this.state.phase === 'stopped')
207
+ return;
208
+ this.state.phase = 'stopped';
209
+ cliLogger.newline();
210
+ cliLogger.warn('Shutting down...');
211
+ // Kill Rust server first (most important)
212
+ if (this.processManager) {
213
+ this.processManager.stop();
214
+ this.processManager = null;
215
+ }
216
+ // Stop IPC
217
+ if (this.ipcServer) {
218
+ this.ipcServer.stop();
219
+ this.ipcServer = null;
220
+ }
221
+ // Stop other components
222
+ try {
223
+ this.watcher.stop();
224
+ this.viteProxy.stop();
225
+ this.hotReloadServer.stop();
226
+ this.routeScanner.stopWatching();
227
+ this.rustBuilder.cancel();
228
+ }
229
+ catch {
230
+ // Ignore errors during cleanup
231
+ }
232
+ cliLogger.success('Goodbye!');
233
+ cliLogger.newline();
234
+ process.exit(0);
235
+ }
236
+ /**
237
+ * Build Rust backend
238
+ */
239
+ async buildRust() {
240
+ cliLogger.spinner('rust-build', 'Building Rust backend...');
241
+ const result = await this.rustBuilder.build();
242
+ if (result.success) {
243
+ const duration = (result.duration / 1000).toFixed(2);
244
+ cliLogger.succeedSpinner('rust-build', `Rust build complete (${duration}s)`);
245
+ this.state.rustReady = true;
246
+ if (result.warnings.length > 0) {
247
+ cliLogger.warn(`${result.warnings.length} warnings`);
248
+ }
249
+ }
250
+ else {
251
+ cliLogger.failSpinner('rust-build', 'Rust build failed');
252
+ for (const error of result.errors) {
253
+ cliLogger.error(error);
254
+ }
255
+ throw new Error('Rust build failed');
256
+ }
257
+ }
258
+ /**
259
+ * Run codegen to generate TypeScript bindings
260
+ */
261
+ async runCodegen() {
262
+ cliLogger.spinner('codegen', 'Generating TypeScript bindings...');
263
+ const success = await this.codegenRunner.run();
264
+ if (success) {
265
+ cliLogger.succeedSpinner('codegen', 'TypeScript bindings generated');
266
+ }
267
+ else {
268
+ cliLogger.warn('Codegen skipped (binary not found)');
269
+ }
270
+ }
271
+ /**
272
+ * Scan routes directory and generate route tree
273
+ */
274
+ async scanRoutes() {
275
+ if (!this.routeScanner.hasRoutesDir()) {
276
+ this.log('debug', 'No routes directory found');
277
+ return null;
278
+ }
279
+ cliLogger.spinner('routes', 'Scanning routes...');
280
+ const tree = await this.routeScanner.scan();
281
+ if (tree) {
282
+ cliLogger.succeedSpinner('routes', `Found ${tree.routes.length} pages, ${tree.apiRoutes.length} API routes`);
283
+ return tree;
284
+ }
285
+ else {
286
+ cliLogger.warn('Route scanning skipped');
287
+ return null;
288
+ }
289
+ }
290
+ /**
291
+ * Start the Rust HTTP server with IPC for TypeScript handlers
292
+ */
293
+ async startRustServer(routeTree) {
294
+ cliLogger.spinner('rust-server', 'Starting Rust HTTP server...');
295
+ try {
296
+ // Generate unique socket path for this dev session
297
+ this.socketPath = path.join(tmpdir(), `zap-dev-${Date.now()}-${Math.random().toString(36).substring(7)}.sock`);
298
+ // Create and start IPC server first
299
+ this.ipcServer = new IpcServer(this.socketPath);
300
+ await this.ipcServer.start();
301
+ this.log('debug', `IPC server listening on ${this.socketPath}`);
302
+ // Load and register route handlers
303
+ const routes = await this.loadRouteHandlers(routeTree);
304
+ console.log(`[dev-server] Loaded ${routes.length} route configurations`);
305
+ // Build Rust server configuration
306
+ const config = this.buildRustConfig(routes);
307
+ console.log(`[dev-server] Built Rust config with ${config.routes.length} routes`);
308
+ // Get binary path
309
+ const binaryPath = this.rustBuilder.getBinaryPath();
310
+ this.log('debug', `Using Rust binary: ${binaryPath}`);
311
+ // Create process manager and start Rust server
312
+ this.processManager = new ProcessManager(binaryPath, this.socketPath);
313
+ await this.processManager.start(config, this.config.logLevel || 'info');
314
+ this.state.rustReady = true;
315
+ cliLogger.succeedSpinner('rust-server', `Rust server ready on port ${this.config.rustPort}`);
316
+ }
317
+ catch (err) {
318
+ cliLogger.failSpinner('rust-server', 'Failed to start Rust server');
319
+ const message = err instanceof Error ? err.message : String(err);
320
+ this.log('error', `Rust server error: ${message}`);
321
+ throw err;
322
+ }
323
+ }
324
+ /**
325
+ * Load TypeScript route handlers and register them with IPC server
326
+ */
327
+ async loadRouteHandlers(routeTree) {
328
+ const routes = [];
329
+ if (!routeTree || !routeTree.apiRoutes || routeTree.apiRoutes.length === 0) {
330
+ console.log('[routes] No API routes to register');
331
+ return routes;
332
+ }
333
+ // Register tsx loader for TypeScript imports
334
+ await ensureTsxRegistered();
335
+ console.log(`[routes] Loading ${routeTree.apiRoutes.length} API route handlers...`);
336
+ for (const apiRoute of routeTree.apiRoutes) {
337
+ try {
338
+ // Construct the full file path
339
+ const routeFilePath = path.join(this.config.projectDir, 'routes', apiRoute.relativePath);
340
+ console.log(`[routes] Loading: ${routeFilePath}`);
341
+ // Dynamic import the route module using file URL for ESM compatibility
342
+ const fileUrl = pathToFileURL(routeFilePath).href;
343
+ const routeModule = await import(fileUrl);
344
+ console.log(`[routes] Loaded module, exports: ${Object.keys(routeModule).join(', ')}`);
345
+ // Check for each HTTP method handler
346
+ const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
347
+ for (const method of methods) {
348
+ if (routeModule[method]) {
349
+ const handlerId = `handler_${method}_${apiRoute.urlPath.replace(/\//g, '_').replace(/:/g, '')}`;
350
+ console.log(`[routes] Registering handler: ${handlerId} for ${method} ${apiRoute.urlPath}`);
351
+ // Register handler with IPC server
352
+ this.ipcServer.registerHandler(handlerId, async (req) => {
353
+ console.log(`[handler] Received request: ${method} ${apiRoute.urlPath}`);
354
+ console.log(`[handler] Request data:`, JSON.stringify(req, null, 2));
355
+ try {
356
+ // Call the route handler
357
+ console.log(`[handler] Calling handler function...`);
358
+ const result = await routeModule[method](req);
359
+ console.log(`[handler] Handler returned:`, JSON.stringify(result, null, 2));
360
+ // Convert result to IPC response format
361
+ const response = this.formatHandlerResponse(result);
362
+ console.log(`[handler] Formatted response:`, JSON.stringify(response, null, 2));
363
+ return response;
364
+ }
365
+ catch (err) {
366
+ const message = err instanceof Error ? err.message : String(err);
367
+ const stack = err instanceof Error ? err.stack : '';
368
+ console.error(`[handler] ERROR in ${method} ${apiRoute.urlPath}: ${message}`);
369
+ console.error(`[handler] Stack: ${stack}`);
370
+ return {
371
+ status: 500,
372
+ headers: { 'content-type': 'application/json' },
373
+ body: JSON.stringify({ error: 'Internal Server Error', message }),
374
+ };
375
+ }
376
+ });
377
+ // Track handler mapping
378
+ this.registeredHandlers.set(handlerId, routeFilePath);
379
+ // Add route to config
380
+ routes.push({
381
+ method,
382
+ path: apiRoute.urlPath,
383
+ handler_id: handlerId,
384
+ is_typescript: true,
385
+ });
386
+ console.log(`[routes] ✓ Registered: ${method} ${apiRoute.urlPath} -> ${handlerId}`);
387
+ }
388
+ }
389
+ }
390
+ catch (err) {
391
+ const message = err instanceof Error ? err.message : String(err);
392
+ console.error(`[routes] Failed to load ${apiRoute.relativePath}: ${message}`);
393
+ }
394
+ }
395
+ console.log(`[routes] Total registered: ${routes.length} API handlers`);
396
+ return routes;
397
+ }
398
+ /**
399
+ * Format handler result into IPC response
400
+ */
401
+ formatHandlerResponse(result) {
402
+ // Handle Response object
403
+ if (result instanceof Response) {
404
+ return {
405
+ status: result.status,
406
+ headers: Object.fromEntries(result.headers.entries()),
407
+ body: '', // Will need to await text() but keeping sync for now
408
+ };
409
+ }
410
+ // Handle string
411
+ if (typeof result === 'string') {
412
+ return {
413
+ status: 200,
414
+ headers: { 'content-type': 'text/plain' },
415
+ body: result,
416
+ };
417
+ }
418
+ // Handle object (JSON)
419
+ if (typeof result === 'object' && result !== null) {
420
+ return {
421
+ status: 200,
422
+ headers: { 'content-type': 'application/json' },
423
+ body: JSON.stringify(result),
424
+ };
425
+ }
426
+ // Default
427
+ return {
428
+ status: 200,
429
+ headers: { 'content-type': 'text/plain' },
430
+ body: String(result),
431
+ };
432
+ }
433
+ /**
434
+ * Build Rust server configuration from routes
435
+ */
436
+ buildRustConfig(routes) {
437
+ return {
438
+ port: this.config.rustPort,
439
+ hostname: '127.0.0.1',
440
+ ipc_socket_path: this.socketPath,
441
+ routes,
442
+ static_files: [],
443
+ middleware: {
444
+ enable_cors: true,
445
+ enable_logging: true,
446
+ enable_compression: false,
447
+ },
448
+ health_check_path: '/health',
449
+ metrics_path: '/metrics',
450
+ };
451
+ }
452
+ /**
453
+ * Restart Rust server (called when routes change)
454
+ */
455
+ async restartRustServer() {
456
+ this.log('info', 'Restarting Rust server...');
457
+ // Stop existing server
458
+ if (this.processManager) {
459
+ await this.processManager.stop();
460
+ }
461
+ if (this.ipcServer) {
462
+ await this.ipcServer.stop();
463
+ }
464
+ // Clear registered handlers
465
+ this.registeredHandlers.clear();
466
+ // Re-scan routes and restart
467
+ const routeTree = await this.scanRoutes();
468
+ this.currentRouteTree = routeTree;
469
+ await this.startRustServer(routeTree);
470
+ }
471
+ /**
472
+ * Start the route file watcher
473
+ */
474
+ async startRouteWatcher() {
475
+ if (!this.routeScanner.hasRoutesDir()) {
476
+ return;
477
+ }
478
+ await this.routeScanner.startWatching();
479
+ this.log('debug', 'Route watcher started');
480
+ }
481
+ /**
482
+ * Start the hot reload WebSocket server
483
+ */
484
+ async startHotReloadServer() {
485
+ await this.hotReloadServer.start();
486
+ this.log('debug', `Hot reload server on port ${this.config.hotReloadPort}`);
487
+ }
488
+ /**
489
+ * Start the Vite dev server
490
+ */
491
+ async startViteServer() {
492
+ cliLogger.spinner('vite', 'Starting Vite dev server...');
493
+ try {
494
+ await this.viteProxy.start();
495
+ cliLogger.succeedSpinner('vite', `Vite ready on port ${this.viteProxy.getPort()}`);
496
+ }
497
+ catch (err) {
498
+ cliLogger.warn('Vite not available (frontend only)');
499
+ this.log('debug', `Vite error: ${err}`);
500
+ }
501
+ }
502
+ /**
503
+ * Start the file watcher
504
+ */
505
+ startWatcher() {
506
+ this.watcher.start();
507
+ this.log('debug', 'File watcher started');
508
+ }
509
+ /**
510
+ * Handle Rust file changes
511
+ */
512
+ async handleRustChange(event) {
513
+ const relativePath = path.relative(this.config.projectDir, event.path);
514
+ cliLogger.newline();
515
+ cliLogger.info(`[${event.type}] ${relativePath}`);
516
+ cliLogger.spinner('rust-rebuild', 'Rebuilding Rust...');
517
+ const result = await this.rustBuilder.build();
518
+ if (result.success) {
519
+ const duration = (result.duration / 1000).toFixed(2);
520
+ cliLogger.succeedSpinner('rust-rebuild', `Rust rebuild complete (${duration}s)`);
521
+ // Regenerate bindings
522
+ await this.codegenRunner.run();
523
+ // Signal hot reload
524
+ this.hotReloadServer.reload('rust', [relativePath]);
525
+ this.state.phase = 'ready';
526
+ }
527
+ else {
528
+ cliLogger.failSpinner('rust-rebuild', 'Rust build failed');
529
+ for (const error of result.errors.slice(0, 3)) {
530
+ cliLogger.error(error);
531
+ }
532
+ // Notify clients of error
533
+ this.hotReloadServer.notifyError(result.errors.join('\n'));
534
+ this.state.phase = 'error';
535
+ }
536
+ }
537
+ /**
538
+ * Handle TypeScript file changes
539
+ */
540
+ async handleTypeScriptChange(event) {
541
+ const relativePath = path.relative(this.config.projectDir, event.path);
542
+ this.log('debug', `[${event.type}] ${relativePath}`);
543
+ // Vite HMR handles TypeScript changes automatically
544
+ // Just emit the event for logging
545
+ this.emit('typescript-change', event);
546
+ }
547
+ /**
548
+ * Handle config file changes
549
+ */
550
+ async handleConfigChange(event) {
551
+ const relativePath = path.relative(this.config.projectDir, event.path);
552
+ cliLogger.newline();
553
+ cliLogger.warn(`[config] ${relativePath}`);
554
+ // For significant config changes, restart might be needed
555
+ if (relativePath.includes('Cargo.toml')) {
556
+ cliLogger.warn('Cargo.toml changed - rebuilding...');
557
+ await this.handleRustChange(event);
558
+ }
559
+ else if (relativePath.includes('vite.config')) {
560
+ cliLogger.warn('Vite config changed - restarting Vite...');
561
+ await this.viteProxy.restart();
562
+ }
563
+ }
564
+ /**
565
+ * Print the ready message
566
+ */
567
+ printReadyMessage() {
568
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(2);
569
+ cliLogger.newline();
570
+ cliLogger.success(`Dev server ready in ${elapsed}s`);
571
+ cliLogger.newline();
572
+ cliLogger.info('Servers:');
573
+ cliLogger.listItem(`API: http://127.0.0.1:${this.config.rustPort}`, '➜');
574
+ if (this.viteProxy.getPort()) {
575
+ cliLogger.listItem(`Frontend: http://127.0.0.1:${this.viteProxy.getPort()}`, '➜');
576
+ }
577
+ cliLogger.listItem(`Hot Reload: ws://127.0.0.1:${this.config.hotReloadPort}`, '➜');
578
+ cliLogger.newline();
579
+ cliLogger.info('Press Ctrl+C to stop');
580
+ cliLogger.newline();
581
+ // Show keyboard shortcuts
582
+ cliLogger.info('Shortcuts:');
583
+ console.log(' r - Rebuild Rust');
584
+ console.log(' c - Regenerate codegen');
585
+ console.log(' q - Quit');
586
+ cliLogger.newline();
587
+ // Setup keyboard input
588
+ this.setupKeyboardInput();
589
+ }
590
+ /**
591
+ * Set up keyboard input handling
592
+ */
593
+ setupKeyboardInput() {
594
+ if (process.stdin.isTTY) {
595
+ process.stdin.setRawMode(true);
596
+ process.stdin.resume();
597
+ process.stdin.setEncoding('utf8');
598
+ process.stdin.on('data', async (key) => {
599
+ // Ctrl+C
600
+ if (key === '\u0003') {
601
+ await this.stop();
602
+ process.exit(0);
603
+ }
604
+ // 'r' - rebuild
605
+ if (key === 'r') {
606
+ cliLogger.newline();
607
+ cliLogger.info('Manual rebuild triggered...');
608
+ await this.rustBuilder.build();
609
+ }
610
+ // 'c' - codegen
611
+ if (key === 'c') {
612
+ cliLogger.newline();
613
+ cliLogger.info('Regenerating bindings...');
614
+ await this.codegenRunner.run();
615
+ }
616
+ // 'q' - quit
617
+ if (key === 'q') {
618
+ await this.stop();
619
+ process.exit(0);
620
+ }
621
+ });
622
+ }
623
+ }
624
+ /**
625
+ * Open the browser
626
+ */
627
+ openBrowser() {
628
+ const url = this.viteProxy.getPort()
629
+ ? `http://127.0.0.1:${this.viteProxy.getPort()}`
630
+ : `http://127.0.0.1:${this.config.rustPort}`;
631
+ import('child_process').then(({ exec }) => {
632
+ const command = process.platform === 'darwin' ? 'open' :
633
+ process.platform === 'win32' ? 'start' : 'xdg-open';
634
+ exec(`${command} ${url}`);
635
+ });
636
+ }
637
+ /**
638
+ * Log a message with the appropriate level
639
+ */
640
+ log(level, message) {
641
+ const levels = { debug: 0, info: 1, warn: 2, error: 3 };
642
+ const configLevel = levels[this.config.logLevel || 'info'];
643
+ const msgLevel = levels[level];
644
+ if (msgLevel >= configLevel) {
645
+ const prefix = {
646
+ debug: '[debug]',
647
+ info: '[info]',
648
+ warn: '[warn]',
649
+ error: '[error]',
650
+ }[level];
651
+ console.log(`${prefix} ${message}`);
652
+ }
653
+ }
654
+ /**
655
+ * Get current server state
656
+ */
657
+ getState() {
658
+ return { ...this.state };
659
+ }
660
+ }