@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.
- package/dist/cli/commands/build.js +17 -14
- package/dist/cli/commands/dev.js +3 -29
- package/dist/cli/utils/binary-resolver.d.ts +22 -0
- package/dist/cli/utils/binary-resolver.js +71 -0
- package/dist/dev-server/server.d.ts +8 -0
- package/dist/dev-server/server.js +39 -5
- package/dist/runtime/ipc-client.d.ts +5 -0
- package/dist/runtime/ipc-client.js +28 -1
- package/dist/runtime/rpc-client.d.ts +1 -1
- package/dist/runtime/rpc-client.js +20 -1
- package/package.json +8 -3
- package/bin/zap +0 -0
- package/bin/zap-codegen +0 -0
|
@@ -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
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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' });
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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.
|
|
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
|