@zap-js/client 0.0.7 → 0.0.9

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.
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
2
2
  import { join, resolve } from 'path';
3
3
  import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, rmSync, writeFileSync } from 'fs';
4
4
  import { cliLogger } from '../utils/logger.js';
5
+ import { resolveBinary } from '../utils/binary-resolver.js';
5
6
  /**
6
7
  * Build for production
7
8
  */
@@ -188,22 +189,24 @@ async function typeCheck() {
188
189
  }
189
190
  async function runCodegen() {
190
191
  const projectDir = process.cwd();
191
- // Find codegen binary using same logic as codegen command
192
- const possiblePaths = [
193
- join(projectDir, 'bin', 'zap-codegen'),
194
- join(projectDir, '../../target/release/zap-codegen'),
195
- join(projectDir, '../../target/aarch64-apple-darwin/release/zap-codegen'),
196
- join(projectDir, '../../target/x86_64-unknown-linux-gnu/release/zap-codegen'),
197
- join(projectDir, 'target/release/zap-codegen'),
198
- ];
199
- let codegenBinary = null;
200
- for (const path of possiblePaths) {
201
- if (existsSync(path)) {
202
- codegenBinary = path;
203
- break;
192
+ // Try to resolve codegen binary using binary resolver
193
+ let codegenBinary = resolveBinary('zap-codegen', projectDir);
194
+ // If not found, try workspace target locations (for development)
195
+ if (!codegenBinary) {
196
+ const possiblePaths = [
197
+ join(projectDir, '../../target/release/zap-codegen'),
198
+ join(projectDir, '../../target/aarch64-apple-darwin/release/zap-codegen'),
199
+ join(projectDir, '../../target/x86_64-unknown-linux-gnu/release/zap-codegen'),
200
+ join(projectDir, 'target/release/zap-codegen'),
201
+ ];
202
+ for (const path of possiblePaths) {
203
+ if (existsSync(path)) {
204
+ codegenBinary = path;
205
+ break;
206
+ }
204
207
  }
205
208
  }
206
- // Try global zap-codegen as fallback
209
+ // Try global zap-codegen as final fallback
207
210
  if (!codegenBinary) {
208
211
  try {
209
212
  execSync('which zap-codegen', { stdio: 'pipe' });
@@ -1,33 +1,6 @@
1
- import path from 'path';
2
- import { existsSync } from 'fs';
3
1
  import { DevServer } from '../../dev-server/index.js';
4
2
  import { cliLogger } from '../utils/logger.js';
5
- /**
6
- * Auto-detect pre-built binaries in bin/ directory
7
- */
8
- function detectBinaries(projectDir) {
9
- const binDir = path.join(projectDir, 'bin');
10
- const result = {};
11
- // Check for zap binary
12
- const zapBinary = path.join(binDir, 'zap');
13
- const zapBinaryExe = path.join(binDir, 'zap.exe');
14
- if (existsSync(zapBinary)) {
15
- result.binaryPath = zapBinary;
16
- }
17
- else if (existsSync(zapBinaryExe)) {
18
- result.binaryPath = zapBinaryExe;
19
- }
20
- // Check for zap-codegen binary
21
- const codegenBinary = path.join(binDir, 'zap-codegen');
22
- const codegenBinaryExe = path.join(binDir, 'zap-codegen.exe');
23
- if (existsSync(codegenBinary)) {
24
- result.codegenBinaryPath = codegenBinary;
25
- }
26
- else if (existsSync(codegenBinaryExe)) {
27
- result.codegenBinaryPath = codegenBinaryExe;
28
- }
29
- return result;
30
- }
3
+ import { detectBinaries, getPlatformIdentifier } from '../utils/binary-resolver.js';
31
4
  /**
32
5
  * Start development server with hot reload
33
6
  *
@@ -54,7 +27,8 @@ export async function devCommand(options) {
54
27
  };
55
28
  // Log if using pre-built binaries
56
29
  if (config.binaryPath) {
57
- cliLogger.info('Using pre-built binary', config.binaryPath);
30
+ const platformId = getPlatformIdentifier();
31
+ cliLogger.info(`Using pre-built binary for ${platformId}`, config.binaryPath);
58
32
  }
59
33
  const server = new DevServer(config);
60
34
  // Handle graceful shutdown
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Resolves the path to a ZapJS binary using multiple strategies:
3
+ * 1. Platform-specific npm package (@zap-js/darwin-arm64, etc.)
4
+ * 2. Local bin/ directory in user's project (for development/custom builds)
5
+ * 3. Returns null to trigger cargo build fallback
6
+ */
7
+ export declare function resolveBinary(binaryName: 'zap' | 'zap-codegen', projectDir?: string): string | null;
8
+ /**
9
+ * Detects both zap and zap-codegen binaries
10
+ */
11
+ export declare function detectBinaries(projectDir: string): {
12
+ binaryPath?: string;
13
+ codegenBinaryPath?: string;
14
+ };
15
+ /**
16
+ * Gets the platform identifier (e.g., "darwin-arm64")
17
+ */
18
+ export declare function getPlatformIdentifier(): string;
19
+ /**
20
+ * Checks if a platform-specific package is installed
21
+ */
22
+ export declare function isPlatformPackageInstalled(): boolean;
@@ -0,0 +1,71 @@
1
+ import path from 'path';
2
+ import { existsSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ /**
7
+ * Resolves the path to a ZapJS binary using multiple strategies:
8
+ * 1. Platform-specific npm package (@zap-js/darwin-arm64, etc.)
9
+ * 2. Local bin/ directory in user's project (for development/custom builds)
10
+ * 3. Returns null to trigger cargo build fallback
11
+ */
12
+ export function resolveBinary(binaryName, projectDir) {
13
+ const platform = process.platform;
14
+ const arch = process.arch;
15
+ // Strategy 1: Try platform-specific npm package
16
+ const platformPkg = `@zap-js/${platform}-${arch}`;
17
+ try {
18
+ // Try to resolve the platform package
19
+ const pkgPath = require.resolve(`${platformPkg}/package.json`);
20
+ const binPath = path.join(path.dirname(pkgPath), 'bin', binaryName);
21
+ if (existsSync(binPath)) {
22
+ return binPath;
23
+ }
24
+ }
25
+ catch (err) {
26
+ // Platform package not installed, continue to next strategy
27
+ }
28
+ // Strategy 2: Check local bin/ directory in user's project
29
+ if (projectDir) {
30
+ const localBin = path.join(projectDir, 'bin', binaryName);
31
+ const localBinExe = path.join(projectDir, 'bin', `${binaryName}.exe`);
32
+ if (existsSync(localBin)) {
33
+ return localBin;
34
+ }
35
+ if (existsSync(localBinExe)) {
36
+ return localBinExe;
37
+ }
38
+ }
39
+ // Strategy 3: Return null (will trigger cargo build)
40
+ return null;
41
+ }
42
+ /**
43
+ * Detects both zap and zap-codegen binaries
44
+ */
45
+ export function detectBinaries(projectDir) {
46
+ const binaryPath = resolveBinary('zap', projectDir);
47
+ const codegenBinaryPath = resolveBinary('zap-codegen', projectDir);
48
+ return {
49
+ binaryPath: binaryPath || undefined,
50
+ codegenBinaryPath: codegenBinaryPath || undefined,
51
+ };
52
+ }
53
+ /**
54
+ * Gets the platform identifier (e.g., "darwin-arm64")
55
+ */
56
+ export function getPlatformIdentifier() {
57
+ return `${process.platform}-${process.arch}`;
58
+ }
59
+ /**
60
+ * Checks if a platform-specific package is installed
61
+ */
62
+ export function isPlatformPackageInstalled() {
63
+ const platformPkg = `@zap-js/${getPlatformIdentifier()}`;
64
+ try {
65
+ require.resolve(`${platformPkg}/package.json`);
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
@@ -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.9",
4
4
  "description": "High-performance fullstack React framework - Client package",
5
5
  "homepage": "https://github.com/saint0x/zapjs",
6
6
  "repository": {
@@ -24,8 +24,7 @@
24
24
  "zap": "./dist/cli/index.js"
25
25
  },
26
26
  "files": [
27
- "dist",
28
- "bin"
27
+ "dist"
29
28
  ],
30
29
  "scripts": {
31
30
  "build": "tsc",
@@ -49,10 +48,16 @@
49
48
  "ora": "^6.0.0",
50
49
  "react": "^18.0.0",
51
50
  "react-dom": "^18.0.0",
51
+ "strip-ansi": "^7.0.0",
52
52
  "tsx": "^4.21.0",
53
53
  "vite": "^5.0.0",
54
54
  "ws": "^8.16.0"
55
55
  },
56
+ "optionalDependencies": {
57
+ "@zap-js/darwin-arm64": "0.0.9",
58
+ "@zap-js/darwin-x64": "0.0.9",
59
+ "@zap-js/linux-x64": "0.0.9"
60
+ },
56
61
  "peerDependencies": {
57
62
  "react": "^18.0.0",
58
63
  "react-dom": "^18.0.0"
package/bin/zap DELETED
Binary file
package/bin/zap-codegen DELETED
Binary file