@zap-js/client 0.0.7 → 0.0.8

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.
@@ -71,6 +71,14 @@ export declare class DevServer extends EventEmitter {
71
71
  * Run codegen to generate TypeScript bindings
72
72
  */
73
73
  private runCodegen;
74
+ /**
75
+ * Wait for Rust server to be ready by checking for .rpc socket file
76
+ */
77
+ private waitForRustServer;
78
+ /**
79
+ * Check if a file exists
80
+ */
81
+ private fileExists;
74
82
  /**
75
83
  * Scan routes directory and generate route tree
76
84
  */
@@ -2,6 +2,7 @@ import { EventEmitter } from 'events';
2
2
  import path from 'path';
3
3
  import { tmpdir } from 'os';
4
4
  import { pathToFileURL } from 'url';
5
+ import { promises as fs } from 'fs';
5
6
  import { FileWatcher } from './watcher.js';
6
7
  import { RustBuilder } from './rust-builder.js';
7
8
  import { ViteProxy } from './vite-proxy.js';
@@ -269,6 +270,36 @@ export class DevServer extends EventEmitter {
269
270
  cliLogger.warn('Codegen skipped (binary not found)');
270
271
  }
271
272
  }
273
+ /**
274
+ * Wait for Rust server to be ready by checking for .rpc socket file
275
+ */
276
+ async waitForRustServer() {
277
+ const rpcSocketPath = this.socketPath + '.rpc';
278
+ const maxWait = 10000; // 10 seconds
279
+ const checkInterval = 100; // 100ms
280
+ const startTime = Date.now();
281
+ while (Date.now() - startTime < maxWait) {
282
+ // Check if .rpc socket file exists
283
+ if (await this.fileExists(rpcSocketPath)) {
284
+ this.log('debug', `RPC socket ready: ${rpcSocketPath}`);
285
+ return;
286
+ }
287
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
288
+ }
289
+ throw new Error(`RPC server failed to start within ${maxWait}ms`);
290
+ }
291
+ /**
292
+ * Check if a file exists
293
+ */
294
+ async fileExists(filePath) {
295
+ try {
296
+ await fs.access(filePath);
297
+ return true;
298
+ }
299
+ catch {
300
+ return false;
301
+ }
302
+ }
272
303
  /**
273
304
  * Scan routes directory and generate route tree
274
305
  */
@@ -300,11 +331,7 @@ export class DevServer extends EventEmitter {
300
331
  this.ipcServer = new IpcServer(this.socketPath);
301
332
  await this.ipcServer.start();
302
333
  this.log('debug', `IPC server listening on ${this.socketPath}`);
303
- // Initialize RPC client for bidirectional IPC communication
304
- // This allows TypeScript route handlers to call Rust functions via rpc.call()
305
- initRpcClient(this.socketPath + '.rpc');
306
- this.log('debug', `RPC client initialized on ${this.socketPath}.rpc`);
307
- // Load and register route handlers
334
+ // Load and register route handlers (before starting Rust)
308
335
  const routes = await this.loadRouteHandlers(routeTree);
309
336
  console.log(`[dev-server] Loaded ${routes.length} route configurations`);
310
337
  // Build Rust server configuration
@@ -318,6 +345,13 @@ export class DevServer extends EventEmitter {
318
345
  await this.processManager.start(config, this.config.logLevel || 'info');
319
346
  this.state.rustReady = true;
320
347
  cliLogger.succeedSpinner('rust-server', `Rust server ready on port ${this.config.rustPort}`);
348
+ // Wait for Rust RPC server to be ready
349
+ await this.waitForRustServer();
350
+ // Initialize RPC client for bidirectional IPC communication
351
+ // This allows TypeScript route handlers to call Rust functions via rpc.call()
352
+ this.log('debug', `Initializing RPC client on ${this.socketPath}.rpc`);
353
+ await initRpcClient(this.socketPath + '.rpc');
354
+ this.log('debug', `RPC client connected to ${this.socketPath}.rpc`);
321
355
  }
322
356
  catch (err) {
323
357
  cliLogger.failSpinner('rust-server', 'Failed to start Rust server');
@@ -116,6 +116,11 @@ export declare class IpcClient extends EventEmitter {
116
116
  private frameReader;
117
117
  private encoding;
118
118
  constructor(socketPath: string, encoding?: IpcEncoding);
119
+ /**
120
+ * Ensure the client is connected. If not connected, attempt to connect.
121
+ * Returns a promise that resolves when connected.
122
+ */
123
+ ensureConnected(): Promise<void>;
119
124
  /**
120
125
  * Connect to the Unix socket
121
126
  */
@@ -526,7 +526,34 @@ export class IpcClient extends EventEmitter {
526
526
  this.frameReader = null;
527
527
  this.socketPath = socketPath;
528
528
  this.encoding = encoding;
529
- this.connect();
529
+ // Don't connect immediately - wait for explicit ensureConnected() call
530
+ }
531
+ /**
532
+ * Ensure the client is connected. If not connected, attempt to connect.
533
+ * Returns a promise that resolves when connected.
534
+ */
535
+ async ensureConnected() {
536
+ if (this.connected) {
537
+ return Promise.resolve();
538
+ }
539
+ return new Promise((resolve, reject) => {
540
+ const timeout = setTimeout(() => {
541
+ reject(new Error(`Failed to connect to ${this.socketPath} after 5s`));
542
+ }, 5000);
543
+ this.connect();
544
+ const onConnect = () => {
545
+ clearTimeout(timeout);
546
+ this.removeListener('error', onError);
547
+ resolve();
548
+ };
549
+ const onError = (err) => {
550
+ clearTimeout(timeout);
551
+ this.removeListener('connect', onConnect);
552
+ reject(err);
553
+ };
554
+ this.once('connect', onConnect);
555
+ this.once('error', onError);
556
+ });
530
557
  }
531
558
  /**
532
559
  * Connect to the Unix socket
@@ -12,7 +12,7 @@ export declare class RpcError extends Error {
12
12
  /**
13
13
  * Initialize the RPC client with a socket path
14
14
  */
15
- export declare function initRpcClient(socketPath: string): void;
15
+ export declare function initRpcClient(socketPath: string): Promise<void>;
16
16
  /**
17
17
  * Call a Rust server function via RPC
18
18
  */
@@ -21,7 +21,7 @@ export class RpcError extends Error {
21
21
  /**
22
22
  * Initialize the RPC client with a socket path
23
23
  */
24
- export function initRpcClient(socketPath) {
24
+ export async function initRpcClient(socketPath) {
25
25
  if (ipcClient) {
26
26
  throw new Error('RPC client already initialized');
27
27
  }
@@ -59,6 +59,23 @@ export function initRpcClient(socketPath) {
59
59
  }
60
60
  pendingRequests.clear();
61
61
  });
62
+ // Wait for connection with retry logic
63
+ const maxRetries = 10;
64
+ const retryDelay = 500; // ms
65
+ for (let i = 0; i < maxRetries; i++) {
66
+ try {
67
+ await ipcClient.ensureConnected();
68
+ console.log(`[RPC] Client connected to ${socketPath}`);
69
+ return;
70
+ }
71
+ catch (err) {
72
+ if (i === maxRetries - 1) {
73
+ throw new Error(`Failed to connect RPC client after ${maxRetries} attempts: ${err}`);
74
+ }
75
+ console.log(`[RPC] Waiting for server... (attempt ${i + 1}/${maxRetries})`);
76
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
77
+ }
78
+ }
62
79
  }
63
80
  /**
64
81
  * Call a Rust server function via RPC
@@ -67,6 +84,8 @@ export async function rpcCall(functionName, params = {}, timeoutMs = 30000) {
67
84
  if (!ipcClient) {
68
85
  throw new Error('RPC client not initialized. Call initRpcClient() first.');
69
86
  }
87
+ // Ensure connection before sending
88
+ await ipcClient.ensureConnected();
70
89
  const requestId = `req_${Date.now()}_${requestCounter++}`;
71
90
  const message = {
72
91
  type: 'rpc_call',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zap-js/client",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "High-performance fullstack React framework - Client package",
5
5
  "homepage": "https://github.com/saint0x/zapjs",
6
6
  "repository": {