@zap-js/client 0.2.1 → 0.2.3
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 +33 -1
- package/dist/cli/commands/serve.js +44 -6
- package/dist/cli/utils/binary-resolver.d.ts +10 -1
- package/dist/cli/utils/binary-resolver.js +11 -0
- package/dist/cli/utils/user-server.d.ts +13 -0
- package/dist/cli/utils/user-server.js +79 -0
- package/dist/dev-server/server.d.ts +25 -0
- package/dist/dev-server/server.js +192 -2
- package/dist/dev-server/splice-manager.d.ts +52 -0
- package/dist/dev-server/splice-manager.js +146 -0
- package/dist/dev-server/watcher.d.ts +2 -2
- package/dist/dev-server/watcher.js +9 -2
- package/dist/runtime/types.d.ts +2 -0
- package/package.json +4 -4
|
@@ -2,8 +2,9 @@ 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, getPlatformIdentifier } from '../utils/binary-resolver.js';
|
|
5
|
+
import { resolveBinary, getPlatformIdentifier, resolveSpliceBinary } from '../utils/binary-resolver.js';
|
|
6
6
|
import { validateBuildStructure } from '../utils/build-validator.js';
|
|
7
|
+
import { buildUserServerRelease } from '../utils/user-server.js';
|
|
7
8
|
/**
|
|
8
9
|
* Build for production
|
|
9
10
|
*/
|
|
@@ -63,6 +64,8 @@ export async function buildCommand(options) {
|
|
|
63
64
|
// This happens AFTER frontend build so Vite doesn't overwrite it
|
|
64
65
|
mkdirSync(join(outputDir, 'bin'), { recursive: true });
|
|
65
66
|
await buildRust(outputDir, options);
|
|
67
|
+
// Step 4.5: Build user server and copy Splice binary (if available)
|
|
68
|
+
await buildUserServerAndSplice(outputDir);
|
|
66
69
|
// Step 5: Create production config
|
|
67
70
|
await createProductionConfig(outputDir, staticDir);
|
|
68
71
|
// Step 6: Create build manifest
|
|
@@ -421,3 +424,32 @@ function getBinarySize(path) {
|
|
|
421
424
|
return 'unknown';
|
|
422
425
|
}
|
|
423
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Build user's Rust server and copy Splice binary to dist/bin/
|
|
429
|
+
*/
|
|
430
|
+
async function buildUserServerAndSplice(outputDir) {
|
|
431
|
+
const projectDir = process.cwd();
|
|
432
|
+
// 1. Try to resolve pre-built Splice binary
|
|
433
|
+
const spliceBinary = resolveSpliceBinary(projectDir);
|
|
434
|
+
if (spliceBinary && existsSync(spliceBinary)) {
|
|
435
|
+
cliLogger.spinner('splice', 'Copying Splice binary...');
|
|
436
|
+
try {
|
|
437
|
+
const destBinary = join(outputDir, 'bin', 'splice');
|
|
438
|
+
copyFileSync(spliceBinary, destBinary);
|
|
439
|
+
execSync(`chmod +x "${destBinary}"`, { stdio: 'pipe' });
|
|
440
|
+
cliLogger.succeedSpinner('splice', 'Splice binary copied');
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
cliLogger.warn('Failed to copy Splice binary');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
cliLogger.info('Splice binary not found (skipping)');
|
|
448
|
+
}
|
|
449
|
+
// 2. Build user server if it exists
|
|
450
|
+
const success = await buildUserServerRelease(projectDir, outputDir);
|
|
451
|
+
if (!success) {
|
|
452
|
+
// Warning already logged by buildUserServerRelease
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -6,6 +6,7 @@ import { pathToFileURL } from 'url';
|
|
|
6
6
|
import { findAvailablePort } from '../utils/port-finder.js';
|
|
7
7
|
import { IpcServer } from '../../runtime/index.js';
|
|
8
8
|
import { cliLogger } from '../utils/logger.js';
|
|
9
|
+
import { SpliceManager } from '../../dev-server/splice-manager.js';
|
|
9
10
|
// Register tsx loader for TypeScript imports
|
|
10
11
|
let tsxRegistered = false;
|
|
11
12
|
async function ensureTsxRegistered() {
|
|
@@ -107,6 +108,31 @@ async function runProductionServer(binPath, options, workDir, prodConfig) {
|
|
|
107
108
|
}
|
|
108
109
|
// Generate unique socket path
|
|
109
110
|
const socketPath = join(tmpdir(), `zap-prod-${Date.now()}-${Math.random().toString(36).substring(7)}.sock`);
|
|
111
|
+
// Check for Splice and user server binaries
|
|
112
|
+
let spliceManager = null;
|
|
113
|
+
const spliceBinPath = join(workDir, 'bin', 'splice');
|
|
114
|
+
const userServerBinPath = join(workDir, 'bin', 'server');
|
|
115
|
+
if (existsSync(spliceBinPath) && existsSync(userServerBinPath)) {
|
|
116
|
+
cliLogger.spinner('splice-prod', 'Starting Splice...');
|
|
117
|
+
const spliceSocketPath = join(tmpdir(), `splice-prod-${Date.now()}.sock`);
|
|
118
|
+
spliceManager = new SpliceManager({
|
|
119
|
+
spliceBinaryPath: spliceBinPath,
|
|
120
|
+
workerBinaryPath: userServerBinPath,
|
|
121
|
+
socketPath: spliceSocketPath,
|
|
122
|
+
maxConcurrency: 1024,
|
|
123
|
+
timeout: 30,
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
await spliceManager.start();
|
|
127
|
+
cliLogger.succeedSpinner('splice-prod', 'Splice started');
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
cliLogger.failSpinner('splice-prod', 'Splice failed to start');
|
|
131
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
132
|
+
cliLogger.warn(`Continuing without Splice: ${message}`);
|
|
133
|
+
spliceManager = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
110
136
|
// Start IPC server for TypeScript handlers
|
|
111
137
|
cliLogger.spinner('ipc', 'Starting IPC server...');
|
|
112
138
|
const ipcServer = new IpcServer(socketPath);
|
|
@@ -131,6 +157,10 @@ async function runProductionServer(binPath, options, workDir, prodConfig) {
|
|
|
131
157
|
},
|
|
132
158
|
health_check_path: '/health',
|
|
133
159
|
};
|
|
160
|
+
// Add Splice socket if available
|
|
161
|
+
if (spliceManager && spliceManager.isRunning()) {
|
|
162
|
+
zapConfig.splice_socket_path = spliceManager.getSocketPath();
|
|
163
|
+
}
|
|
134
164
|
// Also check for static directory in workDir
|
|
135
165
|
const staticDir = join(workDir, 'static');
|
|
136
166
|
if (existsSync(staticDir) && zapConfig.static_files.length === 0) {
|
|
@@ -173,7 +203,7 @@ async function runProductionServer(binPath, options, workDir, prodConfig) {
|
|
|
173
203
|
cliLogger.failSpinner('rpc', 'Failed to connect RPC client');
|
|
174
204
|
const message = err instanceof Error ? err.message : String(err);
|
|
175
205
|
cliLogger.error(message);
|
|
176
|
-
cleanup(ipcServer, tempConfigPath);
|
|
206
|
+
cleanup(ipcServer, tempConfigPath, spliceManager);
|
|
177
207
|
if (!rustProcess.killed) {
|
|
178
208
|
rustProcess.kill();
|
|
179
209
|
}
|
|
@@ -198,7 +228,7 @@ async function runProductionServer(binPath, options, workDir, prodConfig) {
|
|
|
198
228
|
if (output.includes('error') || output.includes('Error')) {
|
|
199
229
|
cliLogger.failSpinner('rust', 'Server failed to start');
|
|
200
230
|
cliLogger.error(output);
|
|
201
|
-
cleanup(ipcServer, tempConfigPath);
|
|
231
|
+
cleanup(ipcServer, tempConfigPath, spliceManager);
|
|
202
232
|
process.exit(1);
|
|
203
233
|
}
|
|
204
234
|
}
|
|
@@ -206,13 +236,13 @@ async function runProductionServer(binPath, options, workDir, prodConfig) {
|
|
|
206
236
|
});
|
|
207
237
|
rustProcess.on('error', (err) => {
|
|
208
238
|
cliLogger.failSpinner('rust', `Failed to start: ${err.message}`);
|
|
209
|
-
cleanup(ipcServer, tempConfigPath);
|
|
239
|
+
cleanup(ipcServer, tempConfigPath, spliceManager);
|
|
210
240
|
process.exit(1);
|
|
211
241
|
});
|
|
212
242
|
rustProcess.on('exit', (code) => {
|
|
213
243
|
if (code !== 0 && code !== null) {
|
|
214
244
|
cliLogger.error(`Server exited with code ${code}`);
|
|
215
|
-
cleanup(ipcServer, tempConfigPath);
|
|
245
|
+
cleanup(ipcServer, tempConfigPath, spliceManager);
|
|
216
246
|
process.exit(code);
|
|
217
247
|
}
|
|
218
248
|
});
|
|
@@ -234,7 +264,7 @@ async function runProductionServer(binPath, options, workDir, prodConfig) {
|
|
|
234
264
|
// Stop IPC server
|
|
235
265
|
await ipcServer.stop();
|
|
236
266
|
// Cleanup temp config
|
|
237
|
-
cleanup(null, tempConfigPath);
|
|
267
|
+
cleanup(null, tempConfigPath, spliceManager);
|
|
238
268
|
// Force kill after timeout
|
|
239
269
|
setTimeout(() => {
|
|
240
270
|
if (!rustProcess.killed) {
|
|
@@ -379,7 +409,15 @@ function formatHandlerResponse(result) {
|
|
|
379
409
|
/**
|
|
380
410
|
* Cleanup temp files
|
|
381
411
|
*/
|
|
382
|
-
function cleanup(ipcServer, configPath) {
|
|
412
|
+
function cleanup(ipcServer, configPath, spliceManager) {
|
|
413
|
+
if (spliceManager) {
|
|
414
|
+
try {
|
|
415
|
+
spliceManager.stop();
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// Ignore cleanup errors
|
|
419
|
+
}
|
|
420
|
+
}
|
|
383
421
|
if (ipcServer) {
|
|
384
422
|
try {
|
|
385
423
|
ipcServer.stop();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 2. Local bin/ directory in user's project (for development/custom builds)
|
|
5
5
|
* 3. Returns null to trigger cargo build fallback
|
|
6
6
|
*/
|
|
7
|
-
export declare function resolveBinary(binaryName: 'zap' | 'zap-codegen', projectDir?: string): string | null;
|
|
7
|
+
export declare function resolveBinary(binaryName: 'zap' | 'zap-codegen' | 'splice', projectDir?: string): string | null;
|
|
8
8
|
/**
|
|
9
9
|
* Detects both zap and zap-codegen binaries
|
|
10
10
|
*/
|
|
@@ -20,3 +20,12 @@ export declare function getPlatformIdentifier(): string;
|
|
|
20
20
|
* Checks if a platform-specific package is installed
|
|
21
21
|
*/
|
|
22
22
|
export declare function isPlatformPackageInstalled(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the Splice binary using the same resolution strategy
|
|
25
|
+
*
|
|
26
|
+
* Resolution order:
|
|
27
|
+
* 1. Platform-specific npm package (@zap-js/darwin-arm64)
|
|
28
|
+
* 2. Local bin/ directory (user's project)
|
|
29
|
+
* 3. null (triggers cargo build fallback)
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveSpliceBinary(projectDir?: string): string | null;
|
|
@@ -69,3 +69,14 @@ export function isPlatformPackageInstalled() {
|
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolves the Splice binary using the same resolution strategy
|
|
74
|
+
*
|
|
75
|
+
* Resolution order:
|
|
76
|
+
* 1. Platform-specific npm package (@zap-js/darwin-arm64)
|
|
77
|
+
* 2. Local bin/ directory (user's project)
|
|
78
|
+
* 3. null (triggers cargo build fallback)
|
|
79
|
+
*/
|
|
80
|
+
export function resolveSpliceBinary(projectDir) {
|
|
81
|
+
return resolveBinary('splice', projectDir);
|
|
82
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if project has a server/Cargo.toml (user's Rust server)
|
|
3
|
+
*/
|
|
4
|
+
export declare function hasUserServer(projectDir: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Build user's Rust server in development mode
|
|
7
|
+
* Returns path to built binary or null on failure
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildUserServer(projectDir: string, binaryName?: string): Promise<string | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Build user's Rust server in release mode for production
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildUserServerRelease(projectDir: string, outputDir: string, binaryName?: string): Promise<boolean>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync, copyFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { cliLogger } from './logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check if project has a server/Cargo.toml (user's Rust server)
|
|
7
|
+
*/
|
|
8
|
+
export function hasUserServer(projectDir) {
|
|
9
|
+
const serverCargoToml = join(projectDir, 'server', 'Cargo.toml');
|
|
10
|
+
return existsSync(serverCargoToml);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build user's Rust server in development mode
|
|
14
|
+
* Returns path to built binary or null on failure
|
|
15
|
+
*/
|
|
16
|
+
export async function buildUserServer(projectDir, binaryName = 'server') {
|
|
17
|
+
const serverDir = join(projectDir, 'server');
|
|
18
|
+
if (!existsSync(join(serverDir, 'Cargo.toml'))) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
cliLogger.spinner('user-server', 'Building Rust server...');
|
|
23
|
+
execSync('cargo build --bin ' + binaryName, {
|
|
24
|
+
cwd: serverDir,
|
|
25
|
+
stdio: 'pipe',
|
|
26
|
+
});
|
|
27
|
+
// Find the built binary
|
|
28
|
+
const targetDir = join(serverDir, 'target', 'debug');
|
|
29
|
+
const binaryPath = join(targetDir, binaryName);
|
|
30
|
+
if (existsSync(binaryPath)) {
|
|
31
|
+
cliLogger.succeedSpinner('user-server', 'User server built');
|
|
32
|
+
return binaryPath;
|
|
33
|
+
}
|
|
34
|
+
cliLogger.failSpinner('user-server', 'Binary not found after build');
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
cliLogger.failSpinner('user-server', 'User server build failed');
|
|
39
|
+
if (error instanceof Error) {
|
|
40
|
+
cliLogger.error(error.message);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build user's Rust server in release mode for production
|
|
47
|
+
*/
|
|
48
|
+
export async function buildUserServerRelease(projectDir, outputDir, binaryName = 'server') {
|
|
49
|
+
const serverDir = join(projectDir, 'server');
|
|
50
|
+
if (!existsSync(join(serverDir, 'Cargo.toml'))) {
|
|
51
|
+
cliLogger.info('No user server found (skipping)');
|
|
52
|
+
return true; // Not an error
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
cliLogger.spinner('user-server-release', 'Building user server (release mode)...');
|
|
56
|
+
execSync('cargo build --release --bin ' + binaryName, {
|
|
57
|
+
cwd: serverDir,
|
|
58
|
+
stdio: 'pipe',
|
|
59
|
+
});
|
|
60
|
+
// Copy binary to dist/bin/
|
|
61
|
+
const srcBinary = join(serverDir, 'target', 'release', binaryName);
|
|
62
|
+
const destBinary = join(outputDir, 'bin', binaryName);
|
|
63
|
+
if (existsSync(srcBinary)) {
|
|
64
|
+
copyFileSync(srcBinary, destBinary);
|
|
65
|
+
execSync(`chmod +x "${destBinary}"`, { stdio: 'pipe' });
|
|
66
|
+
cliLogger.succeedSpinner('user-server-release', 'User server built (release)');
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
cliLogger.failSpinner('user-server-release', 'Binary not found');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
cliLogger.failSpinner('user-server-release', 'Build failed');
|
|
74
|
+
if (error instanceof Error) {
|
|
75
|
+
cliLogger.error(error.message);
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -12,6 +12,8 @@ export interface DevServerConfig {
|
|
|
12
12
|
binaryPath?: string;
|
|
13
13
|
/** Path to pre-built zap-codegen binary */
|
|
14
14
|
codegenBinaryPath?: string;
|
|
15
|
+
/** Path to pre-built splice binary */
|
|
16
|
+
spliceBinaryPath?: string;
|
|
15
17
|
}
|
|
16
18
|
interface ServerState {
|
|
17
19
|
phase: 'starting' | 'building' | 'ready' | 'rebuilding' | 'error' | 'stopped';
|
|
@@ -49,6 +51,9 @@ export declare class DevServer extends EventEmitter {
|
|
|
49
51
|
private socketPath;
|
|
50
52
|
private currentRouteTree;
|
|
51
53
|
private registeredHandlers;
|
|
54
|
+
private spliceManager;
|
|
55
|
+
private splicePath;
|
|
56
|
+
private userServerBinaryPath;
|
|
52
57
|
private startTime;
|
|
53
58
|
constructor(config: DevServerConfig);
|
|
54
59
|
/**
|
|
@@ -99,6 +104,10 @@ export declare class DevServer extends EventEmitter {
|
|
|
99
104
|
* Build Rust server configuration from routes
|
|
100
105
|
*/
|
|
101
106
|
private buildRustConfig;
|
|
107
|
+
/**
|
|
108
|
+
* Start Splice supervisor for distributed Rust functions
|
|
109
|
+
*/
|
|
110
|
+
private startSplice;
|
|
102
111
|
/**
|
|
103
112
|
* Restart Rust server (called when routes change)
|
|
104
113
|
*/
|
|
@@ -131,6 +140,22 @@ export declare class DevServer extends EventEmitter {
|
|
|
131
140
|
* Handle config file changes
|
|
132
141
|
*/
|
|
133
142
|
private handleConfigChange;
|
|
143
|
+
/**
|
|
144
|
+
* Handle user server (server/ Rust files) file changes
|
|
145
|
+
*/
|
|
146
|
+
private handleUserServerChange;
|
|
147
|
+
/**
|
|
148
|
+
* Wait for Splice to detect binary change and reload
|
|
149
|
+
*/
|
|
150
|
+
private waitForSpliceReload;
|
|
151
|
+
/**
|
|
152
|
+
* Run Splice codegen to generate TypeScript bindings
|
|
153
|
+
*/
|
|
154
|
+
private runSpliceCodegen;
|
|
155
|
+
/**
|
|
156
|
+
* Find codegen binary in various locations
|
|
157
|
+
*/
|
|
158
|
+
private findCodegenBinary;
|
|
134
159
|
/**
|
|
135
160
|
* Print the ready message
|
|
136
161
|
*/
|
|
@@ -3,6 +3,8 @@ import path from 'path';
|
|
|
3
3
|
import { tmpdir } from 'os';
|
|
4
4
|
import { pathToFileURL } from 'url';
|
|
5
5
|
import { promises as fs } from 'fs';
|
|
6
|
+
import { spawn, execSync } from 'child_process';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
6
8
|
import { FileWatcher } from './watcher.js';
|
|
7
9
|
import { RustBuilder } from './rust-builder.js';
|
|
8
10
|
import { ViteProxy } from './vite-proxy.js';
|
|
@@ -12,6 +14,9 @@ import { RouteScannerRunner } from './route-scanner.js';
|
|
|
12
14
|
import { ProcessManager, IpcServer } from '../runtime/index.js';
|
|
13
15
|
import { initRpcClient } from '../runtime/rpc-client.js';
|
|
14
16
|
import { cliLogger } from '../cli/utils/logger.js';
|
|
17
|
+
import { SpliceManager } from './splice-manager.js';
|
|
18
|
+
import { hasUserServer, buildUserServer } from '../cli/utils/user-server.js';
|
|
19
|
+
import { resolveSpliceBinary } from '../cli/utils/binary-resolver.js';
|
|
15
20
|
// Register tsx loader for TypeScript imports
|
|
16
21
|
// This must be called before any dynamic imports of .ts files
|
|
17
22
|
let tsxRegistered = false;
|
|
@@ -52,6 +57,10 @@ export class DevServer extends EventEmitter {
|
|
|
52
57
|
this.socketPath = '';
|
|
53
58
|
this.currentRouteTree = null;
|
|
54
59
|
this.registeredHandlers = new Map(); // handlerId -> filePath
|
|
60
|
+
// Splice components for distributed Rust functions
|
|
61
|
+
this.spliceManager = null;
|
|
62
|
+
this.splicePath = '';
|
|
63
|
+
this.userServerBinaryPath = null;
|
|
55
64
|
// Timing
|
|
56
65
|
this.startTime = 0;
|
|
57
66
|
this.config = {
|
|
@@ -106,6 +115,7 @@ export class DevServer extends EventEmitter {
|
|
|
106
115
|
setupEventHandlers() {
|
|
107
116
|
// File watcher events
|
|
108
117
|
this.watcher.on('rust', (event) => this.handleRustChange(event));
|
|
118
|
+
this.watcher.on('user-server', (event) => this.handleUserServerChange(event));
|
|
109
119
|
this.watcher.on('typescript', (event) => this.handleTypeScriptChange(event));
|
|
110
120
|
this.watcher.on('config', (event) => this.handleConfigChange(event));
|
|
111
121
|
this.watcher.on('error', (err) => this.log('error', `Watcher error: ${err.message}`));
|
|
@@ -177,6 +187,11 @@ export class DevServer extends EventEmitter {
|
|
|
177
187
|
// Phase 2.5: Scan routes
|
|
178
188
|
const routeTree = await this.scanRoutes();
|
|
179
189
|
this.currentRouteTree = routeTree;
|
|
190
|
+
// Phase 2.75: Check for user server and start Splice if available
|
|
191
|
+
const hasServer = hasUserServer(this.config.projectDir);
|
|
192
|
+
if (hasServer) {
|
|
193
|
+
await this.startSplice();
|
|
194
|
+
}
|
|
180
195
|
// Phase 3: Start Rust HTTP server with IPC
|
|
181
196
|
await this.startRustServer(routeTree);
|
|
182
197
|
// Phase 4: Start other servers in parallel
|
|
@@ -210,7 +225,12 @@ export class DevServer extends EventEmitter {
|
|
|
210
225
|
this.state.phase = 'stopped';
|
|
211
226
|
cliLogger.newline();
|
|
212
227
|
cliLogger.warn('Shutting down...');
|
|
213
|
-
//
|
|
228
|
+
// Stop Splice first
|
|
229
|
+
if (this.spliceManager) {
|
|
230
|
+
await this.spliceManager.stop();
|
|
231
|
+
this.spliceManager = null;
|
|
232
|
+
}
|
|
233
|
+
// Kill Rust server (most important)
|
|
214
234
|
if (this.processManager) {
|
|
215
235
|
this.processManager.stop();
|
|
216
236
|
this.processManager = null;
|
|
@@ -473,7 +493,7 @@ export class DevServer extends EventEmitter {
|
|
|
473
493
|
* Build Rust server configuration from routes
|
|
474
494
|
*/
|
|
475
495
|
buildRustConfig(routes) {
|
|
476
|
-
|
|
496
|
+
const config = {
|
|
477
497
|
port: this.config.rustPort,
|
|
478
498
|
hostname: '127.0.0.1',
|
|
479
499
|
ipc_socket_path: this.socketPath,
|
|
@@ -487,6 +507,56 @@ export class DevServer extends EventEmitter {
|
|
|
487
507
|
health_check_path: '/health',
|
|
488
508
|
metrics_path: '/metrics',
|
|
489
509
|
};
|
|
510
|
+
// Add Splice socket if available
|
|
511
|
+
if (this.splicePath && this.spliceManager?.isRunning()) {
|
|
512
|
+
config.splice_socket_path = this.splicePath;
|
|
513
|
+
}
|
|
514
|
+
return config;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Start Splice supervisor for distributed Rust functions
|
|
518
|
+
*/
|
|
519
|
+
async startSplice() {
|
|
520
|
+
cliLogger.spinner('splice', 'Starting Splice supervisor...');
|
|
521
|
+
try {
|
|
522
|
+
// 1. Resolve Splice binary
|
|
523
|
+
const spliceBinaryPath = this.config.spliceBinaryPath ||
|
|
524
|
+
resolveSpliceBinary(this.config.projectDir);
|
|
525
|
+
if (!spliceBinaryPath) {
|
|
526
|
+
cliLogger.warn('Splice binary not found (skipping distributed functions)');
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
// 2. Build user server
|
|
530
|
+
const userServerPath = await buildUserServer(this.config.projectDir);
|
|
531
|
+
if (!userServerPath) {
|
|
532
|
+
cliLogger.warn('User server build failed (skipping Splice)');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
this.userServerBinaryPath = userServerPath;
|
|
536
|
+
// 3. Generate socket path for Splice
|
|
537
|
+
this.splicePath = path.join(tmpdir(), `splice-dev-${Date.now()}-${Math.random().toString(36).substring(7)}.sock`);
|
|
538
|
+
// 4. Create and start Splice manager
|
|
539
|
+
this.spliceManager = new SpliceManager({
|
|
540
|
+
spliceBinaryPath,
|
|
541
|
+
workerBinaryPath: userServerPath,
|
|
542
|
+
socketPath: this.splicePath,
|
|
543
|
+
maxConcurrency: 1024,
|
|
544
|
+
timeout: 30,
|
|
545
|
+
watchPaths: [path.join(this.config.projectDir, 'server')],
|
|
546
|
+
});
|
|
547
|
+
await this.spliceManager.start();
|
|
548
|
+
cliLogger.succeedSpinner('splice', `Splice ready on ${this.splicePath}`);
|
|
549
|
+
// Generate initial TypeScript bindings from Splice exports
|
|
550
|
+
cliLogger.spinner('splice-codegen', 'Generating TypeScript bindings...');
|
|
551
|
+
await this.runSpliceCodegen();
|
|
552
|
+
cliLogger.succeedSpinner('splice-codegen', 'Splice bindings generated');
|
|
553
|
+
}
|
|
554
|
+
catch (err) {
|
|
555
|
+
cliLogger.failSpinner('splice', 'Failed to start Splice');
|
|
556
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
557
|
+
this.log('error', `Splice error: ${message}`);
|
|
558
|
+
// Don't throw - continue without Splice
|
|
559
|
+
}
|
|
490
560
|
}
|
|
491
561
|
/**
|
|
492
562
|
* Restart Rust server (called when routes change)
|
|
@@ -600,6 +670,126 @@ export class DevServer extends EventEmitter {
|
|
|
600
670
|
await this.viteProxy.restart();
|
|
601
671
|
}
|
|
602
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Handle user server (server/ Rust files) file changes
|
|
675
|
+
*/
|
|
676
|
+
async handleUserServerChange(event) {
|
|
677
|
+
const relativePath = path.relative(this.config.projectDir, event.path);
|
|
678
|
+
cliLogger.newline();
|
|
679
|
+
cliLogger.info(`[user-server:${event.type}] ${relativePath}`);
|
|
680
|
+
// Only proceed if Splice is running
|
|
681
|
+
if (!this.spliceManager?.isRunning()) {
|
|
682
|
+
this.log('debug', 'Splice not running, skipping user server rebuild');
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
cliLogger.spinner('user-server-rebuild', 'Rebuilding user server...');
|
|
686
|
+
const rebuildStart = Date.now();
|
|
687
|
+
try {
|
|
688
|
+
// Step 1: Rebuild user server binary
|
|
689
|
+
const newBinaryPath = await buildUserServer(this.config.projectDir);
|
|
690
|
+
if (!newBinaryPath) {
|
|
691
|
+
cliLogger.failSpinner('user-server-rebuild', 'User server build failed');
|
|
692
|
+
this.hotReloadServer.notifyError('User server compilation failed');
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
this.userServerBinaryPath = newBinaryPath;
|
|
696
|
+
const duration = ((Date.now() - rebuildStart) / 1000).toFixed(2);
|
|
697
|
+
cliLogger.succeedSpinner('user-server-rebuild', `User server rebuilt (${duration}s)`);
|
|
698
|
+
// Step 2: Wait for Splice to detect binary change and reload worker
|
|
699
|
+
this.log('info', 'Waiting for Splice to reload worker...');
|
|
700
|
+
await this.waitForSpliceReload();
|
|
701
|
+
// Step 3: Regenerate TypeScript bindings from updated exports
|
|
702
|
+
if (this.splicePath) {
|
|
703
|
+
cliLogger.spinner('splice-codegen', 'Regenerating Splice bindings...');
|
|
704
|
+
await this.runSpliceCodegen();
|
|
705
|
+
cliLogger.succeedSpinner('splice-codegen', 'TypeScript bindings updated');
|
|
706
|
+
}
|
|
707
|
+
// Step 4: Signal browser to reload
|
|
708
|
+
this.hotReloadServer.reload('rust', [relativePath]);
|
|
709
|
+
this.state.phase = 'ready';
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
cliLogger.failSpinner('user-server-rebuild', 'Rebuild failed');
|
|
713
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
714
|
+
this.log('error', `User server rebuild error: ${message}`);
|
|
715
|
+
this.hotReloadServer.notifyError(`User server error: ${message}`);
|
|
716
|
+
this.state.phase = 'error';
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Wait for Splice to detect binary change and reload
|
|
721
|
+
*/
|
|
722
|
+
async waitForSpliceReload() {
|
|
723
|
+
// Splice ReloadManager detects binary hash change and auto-reloads
|
|
724
|
+
// Give it time to complete the reload cycle
|
|
725
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Run Splice codegen to generate TypeScript bindings
|
|
729
|
+
*/
|
|
730
|
+
async runSpliceCodegen() {
|
|
731
|
+
if (!this.splicePath || !this.spliceManager?.isRunning()) {
|
|
732
|
+
throw new Error('Splice not running');
|
|
733
|
+
}
|
|
734
|
+
const codegenBinary = await this.findCodegenBinary();
|
|
735
|
+
if (!codegenBinary) {
|
|
736
|
+
throw new Error('Codegen binary not found');
|
|
737
|
+
}
|
|
738
|
+
return new Promise((resolve, reject) => {
|
|
739
|
+
const args = ['--splice-socket', this.splicePath, '--output-dir', './src/api'];
|
|
740
|
+
this.log('debug', `Running codegen: ${codegenBinary} ${args.join(' ')}`);
|
|
741
|
+
const proc = spawn(codegenBinary, args, {
|
|
742
|
+
cwd: this.config.projectDir,
|
|
743
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
744
|
+
});
|
|
745
|
+
let stderr = '';
|
|
746
|
+
proc.stdout?.on('data', (data) => {
|
|
747
|
+
this.log('debug', `[codegen] ${data.toString().trim()}`);
|
|
748
|
+
});
|
|
749
|
+
proc.stderr?.on('data', (data) => {
|
|
750
|
+
stderr += data.toString();
|
|
751
|
+
this.log('warn', `[codegen] ${data.toString().trim()}`);
|
|
752
|
+
});
|
|
753
|
+
proc.on('close', (code) => {
|
|
754
|
+
if (code === 0) {
|
|
755
|
+
this.log('info', 'Splice codegen completed successfully');
|
|
756
|
+
resolve();
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
reject(new Error(`Codegen exited with code ${code}: ${stderr}`));
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
proc.on('error', (err) => {
|
|
763
|
+
reject(new Error(`Codegen process error: ${err.message}`));
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Find codegen binary in various locations
|
|
769
|
+
*/
|
|
770
|
+
async findCodegenBinary() {
|
|
771
|
+
const candidates = [
|
|
772
|
+
this.config.codegenBinaryPath,
|
|
773
|
+
path.join(this.config.projectDir, 'bin/zap-codegen'),
|
|
774
|
+
path.join(__dirname, '../../bin/zap-codegen'),
|
|
775
|
+
path.join(this.config.projectDir, 'node_modules/@zap-js/client/bin/zap-codegen'),
|
|
776
|
+
path.join(this.config.projectDir, 'target/release/zap-codegen'),
|
|
777
|
+
path.join(this.config.projectDir, 'target/debug/zap-codegen'),
|
|
778
|
+
].filter(Boolean);
|
|
779
|
+
for (const candidate of candidates) {
|
|
780
|
+
if (existsSync(candidate)) {
|
|
781
|
+
return candidate;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// Check PATH
|
|
785
|
+
try {
|
|
786
|
+
execSync('which zap-codegen', { stdio: 'ignore' });
|
|
787
|
+
return 'zap-codegen';
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
603
793
|
/**
|
|
604
794
|
* Print the ready message
|
|
605
795
|
*/
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface SpliceManagerConfig {
|
|
3
|
+
/** Path to Splice binary */
|
|
4
|
+
spliceBinaryPath: string;
|
|
5
|
+
/** Path to user's server binary */
|
|
6
|
+
workerBinaryPath: string;
|
|
7
|
+
/** Socket path for host connection */
|
|
8
|
+
socketPath: string;
|
|
9
|
+
/** Max concurrent requests */
|
|
10
|
+
maxConcurrency?: number;
|
|
11
|
+
/** Timeout in seconds */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** Watch paths for hot reload */
|
|
14
|
+
watchPaths?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SpliceManager - DevServer component for Splice supervisor
|
|
18
|
+
*
|
|
19
|
+
* Manages the Splice process lifecycle:
|
|
20
|
+
* - Spawns splice binary with proper args
|
|
21
|
+
* - Forwards logs
|
|
22
|
+
* - Monitors health
|
|
23
|
+
* - Graceful shutdown
|
|
24
|
+
*
|
|
25
|
+
* Follows same pattern as ProcessManager but specialized for Splice
|
|
26
|
+
*/
|
|
27
|
+
export declare class SpliceManager extends EventEmitter {
|
|
28
|
+
private config;
|
|
29
|
+
private process;
|
|
30
|
+
private running;
|
|
31
|
+
constructor(config: SpliceManagerConfig);
|
|
32
|
+
/**
|
|
33
|
+
* Start the Splice supervisor
|
|
34
|
+
*/
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Wait for Splice to create its socket file
|
|
38
|
+
*/
|
|
39
|
+
private waitForSocket;
|
|
40
|
+
/**
|
|
41
|
+
* Stop the Splice process
|
|
42
|
+
*/
|
|
43
|
+
stop(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Check if Splice is running
|
|
46
|
+
*/
|
|
47
|
+
isRunning(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Get the socket path
|
|
50
|
+
*/
|
|
51
|
+
getSocketPath(): string;
|
|
52
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { existsSync, unlinkSync } from 'fs';
|
|
4
|
+
/**
|
|
5
|
+
* SpliceManager - DevServer component for Splice supervisor
|
|
6
|
+
*
|
|
7
|
+
* Manages the Splice process lifecycle:
|
|
8
|
+
* - Spawns splice binary with proper args
|
|
9
|
+
* - Forwards logs
|
|
10
|
+
* - Monitors health
|
|
11
|
+
* - Graceful shutdown
|
|
12
|
+
*
|
|
13
|
+
* Follows same pattern as ProcessManager but specialized for Splice
|
|
14
|
+
*/
|
|
15
|
+
export class SpliceManager extends EventEmitter {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
super();
|
|
18
|
+
this.process = null;
|
|
19
|
+
this.running = false;
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Start the Splice supervisor
|
|
24
|
+
*/
|
|
25
|
+
async start() {
|
|
26
|
+
if (this.running) {
|
|
27
|
+
throw new Error('Splice already running');
|
|
28
|
+
}
|
|
29
|
+
const args = [
|
|
30
|
+
'--socket',
|
|
31
|
+
this.config.socketPath,
|
|
32
|
+
'--worker',
|
|
33
|
+
this.config.workerBinaryPath,
|
|
34
|
+
'--max-concurrency',
|
|
35
|
+
(this.config.maxConcurrency || 1024).toString(),
|
|
36
|
+
'--timeout',
|
|
37
|
+
(this.config.timeout || 30).toString(),
|
|
38
|
+
];
|
|
39
|
+
if (this.config.watchPaths && this.config.watchPaths.length > 0) {
|
|
40
|
+
args.push('--watch', this.config.watchPaths.join(','));
|
|
41
|
+
}
|
|
42
|
+
console.log('[Splice] Starting supervisor...');
|
|
43
|
+
console.log('[Splice] Worker:', this.config.workerBinaryPath);
|
|
44
|
+
console.log('[Splice] Socket:', this.config.socketPath);
|
|
45
|
+
this.process = spawn(this.config.spliceBinaryPath, args, {
|
|
46
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
47
|
+
env: {
|
|
48
|
+
...process.env,
|
|
49
|
+
RUST_LOG: process.env.RUST_LOG || 'info',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (!this.process.stdout || !this.process.stderr) {
|
|
53
|
+
throw new Error('Failed to create Splice process streams');
|
|
54
|
+
}
|
|
55
|
+
// Forward stdout
|
|
56
|
+
this.process.stdout.on('data', (data) => {
|
|
57
|
+
const output = data.toString().trim();
|
|
58
|
+
if (output) {
|
|
59
|
+
console.log(`[Splice] ${output}`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// Forward stderr
|
|
63
|
+
this.process.stderr.on('data', (data) => {
|
|
64
|
+
const output = data.toString().trim();
|
|
65
|
+
if (output) {
|
|
66
|
+
console.error(`[Splice] ${output}`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// Handle exit
|
|
70
|
+
this.process.on('exit', (code, signal) => {
|
|
71
|
+
this.running = false;
|
|
72
|
+
if (code !== 0 && code !== null) {
|
|
73
|
+
console.error(`[Splice] Exited: code=${code}, signal=${signal}`);
|
|
74
|
+
this.emit('error', new Error(`Splice exited with code ${code}`));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// Handle errors
|
|
78
|
+
this.process.on('error', (err) => {
|
|
79
|
+
this.running = false;
|
|
80
|
+
console.error('[Splice] Process error:', err);
|
|
81
|
+
this.emit('error', err);
|
|
82
|
+
});
|
|
83
|
+
this.running = true;
|
|
84
|
+
this.emit('started');
|
|
85
|
+
// Wait for socket to be ready
|
|
86
|
+
await this.waitForSocket();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Wait for Splice to create its socket file
|
|
90
|
+
*/
|
|
91
|
+
async waitForSocket() {
|
|
92
|
+
const maxWait = 5000; // 5 seconds
|
|
93
|
+
const checkInterval = 100;
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
while (Date.now() - startTime < maxWait) {
|
|
96
|
+
if (existsSync(this.config.socketPath)) {
|
|
97
|
+
console.log('[Splice] Socket ready');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
101
|
+
}
|
|
102
|
+
throw new Error('Splice socket not ready within timeout');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Stop the Splice process
|
|
106
|
+
*/
|
|
107
|
+
async stop() {
|
|
108
|
+
if (!this.process || !this.running) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.log('[Splice] Stopping...');
|
|
112
|
+
if (!this.process.killed) {
|
|
113
|
+
this.process.kill('SIGTERM');
|
|
114
|
+
}
|
|
115
|
+
// Wait briefly for graceful shutdown
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
117
|
+
// Force kill if still running
|
|
118
|
+
if (!this.process.killed) {
|
|
119
|
+
this.process.kill('SIGKILL');
|
|
120
|
+
}
|
|
121
|
+
this.process = null;
|
|
122
|
+
this.running = false;
|
|
123
|
+
// Cleanup socket file
|
|
124
|
+
if (existsSync(this.config.socketPath)) {
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(this.config.socketPath);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Ignore cleanup errors
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
this.emit('stopped');
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if Splice is running
|
|
136
|
+
*/
|
|
137
|
+
isRunning() {
|
|
138
|
+
return this.running && this.process !== null && !this.process.killed;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get the socket path
|
|
142
|
+
*/
|
|
143
|
+
getSocketPath() {
|
|
144
|
+
return this.config.socketPath;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -3,7 +3,7 @@ export type WatchEventType = 'add' | 'change' | 'unlink';
|
|
|
3
3
|
export interface WatchEvent {
|
|
4
4
|
type: WatchEventType;
|
|
5
5
|
path: string;
|
|
6
|
-
category: 'rust' | 'typescript' | 'config' | 'unknown';
|
|
6
|
+
category: 'rust' | 'user-server' | 'typescript' | 'config' | 'unknown';
|
|
7
7
|
}
|
|
8
8
|
export interface WatcherConfig {
|
|
9
9
|
rootDir: string;
|
|
@@ -38,7 +38,7 @@ export declare class FileWatcher extends EventEmitter {
|
|
|
38
38
|
*/
|
|
39
39
|
private handleEvent;
|
|
40
40
|
/**
|
|
41
|
-
* Categorize a file based on its extension
|
|
41
|
+
* Categorize a file based on its extension and location
|
|
42
42
|
*/
|
|
43
43
|
private categorizeFile;
|
|
44
44
|
/**
|
|
@@ -98,12 +98,19 @@ export class FileWatcher extends EventEmitter {
|
|
|
98
98
|
this.debounceTimers.set(filePath, timer);
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
|
-
* Categorize a file based on its extension
|
|
101
|
+
* Categorize a file based on its extension and location
|
|
102
102
|
*/
|
|
103
103
|
categorizeFile(filePath) {
|
|
104
104
|
const ext = path.extname(filePath).toLowerCase();
|
|
105
105
|
const basename = path.basename(filePath);
|
|
106
|
-
// Rust
|
|
106
|
+
// Check if file is in server/ directory (user's Rust server)
|
|
107
|
+
const relativePath = path.relative(this.config.rootDir, filePath);
|
|
108
|
+
if (relativePath.startsWith('server' + path.sep) || relativePath === 'server') {
|
|
109
|
+
if (ext === '.rs' || basename === 'Cargo.toml') {
|
|
110
|
+
return 'user-server';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Rust files in packages/server (Zap runtime)
|
|
107
114
|
if (ext === '.rs' || basename === 'Cargo.toml') {
|
|
108
115
|
return 'rust';
|
|
109
116
|
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -360,6 +360,8 @@ export interface ZapConfig {
|
|
|
360
360
|
security?: SecurityConfig;
|
|
361
361
|
/** Observability configuration */
|
|
362
362
|
observability?: ObservabilityConfig;
|
|
363
|
+
/** Splice socket path for distributed Rust functions (optional) */
|
|
364
|
+
splice_socket_path?: string;
|
|
363
365
|
}
|
|
364
366
|
/**
|
|
365
367
|
* Type guard for InvokeHandlerMessage
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-js/client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "High-performance fullstack React framework - Client package",
|
|
5
5
|
"homepage": "https://github.com/saint0x/zapjs",
|
|
6
6
|
"repository": {
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"ws": "^8.16.0"
|
|
75
75
|
},
|
|
76
76
|
"optionalDependencies": {
|
|
77
|
-
"@zap-js/darwin-arm64": "0.2.
|
|
78
|
-
"@zap-js/darwin-x64": "0.2.
|
|
79
|
-
"@zap-js/linux-x64": "0.2.
|
|
77
|
+
"@zap-js/darwin-arm64": "0.2.3",
|
|
78
|
+
"@zap-js/darwin-x64": "0.2.3",
|
|
79
|
+
"@zap-js/linux-x64": "0.2.3"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"react": "^18.0.0",
|