@zap-js/server 0.0.1

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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @zap-js/server
2
+
3
+ Server-side package for the ZapJS fullstack React framework with Rust-powered backend.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @zap-js/server
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ import { rpc, ipc } from '@zap-js/server'
15
+
16
+ // Call Rust backend functions
17
+ const result = await rpc.call('getUser', { id: 123 })
18
+
19
+ // Direct IPC communication
20
+ const client = new ipc.Client('/tmp/zap.sock')
21
+ ```
22
+
23
+ ## Features
24
+
25
+ - **Rust-powered backend** with 9ns route matching
26
+ - **Automatic TypeScript bindings** for Rust functions
27
+ - **Zero-overhead RPC** communication
28
+ - **Type-safe** end-to-end
29
+
30
+ ## API Routes
31
+
32
+ Use server exports in your API route handlers:
33
+
34
+ ```javascript
35
+ // routes/api/users.ts
36
+ import { rpc } from '@zap-js/server'
37
+
38
+ export const GET = async (req) => {
39
+ return await rpc.call('list_users', {
40
+ limit: req.query.limit || 10
41
+ })
42
+ }
43
+ ```
44
+
45
+ ## Documentation
46
+
47
+ Full documentation available at [https://github.com/saint0x/zapjs](https://github.com/saint0x/zapjs)
48
+
49
+ ## License
50
+
51
+ MIT
package/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @zap-js/server
3
+ *
4
+ * ZapJS server communication utilities
5
+ */
6
+
7
+ // RPC client
8
+ import rpc from './src/rpc.js';
9
+
10
+ // IPC client
11
+ import ipc from './src/ipc.js';
12
+
13
+ // Types
14
+ import * as types from './src/types.js';
15
+
16
+ // Export everything
17
+ export {
18
+ rpc,
19
+ ipc,
20
+ types
21
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@zap-js/server",
3
+ "version": "0.0.1",
4
+ "description": "High-performance fullstack React framework - Server package with Rust-powered backend",
5
+ "homepage": "https://github.com/saint0x/zapjs",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/saint0x/zapjs.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/saint0x/zapjs/issues"
12
+ },
13
+ "type": "module",
14
+ "main": "./index.js",
15
+ "exports": {
16
+ ".": "./index.js"
17
+ },
18
+ "files": [
19
+ "index.js",
20
+ "src",
21
+ "internal",
22
+ "!internal/*/src/**/*.rs",
23
+ "!internal/*/Cargo.toml"
24
+ ],
25
+ "scripts": {
26
+ "build": "echo 'No build needed for server package'",
27
+ "clean": "rm -rf dist"
28
+ },
29
+ "keywords": [
30
+ "zap",
31
+ "zapjs",
32
+ "zap-js",
33
+ "server",
34
+ "rust",
35
+ "rpc",
36
+ "ipc",
37
+ "typescript",
38
+ "fullstack",
39
+ "framework",
40
+ "http",
41
+ "websocket",
42
+ "performance",
43
+ "backend"
44
+ ],
45
+ "author": "saint0x",
46
+ "license": "MIT",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }
package/src/bin/zap.rs ADDED
@@ -0,0 +1,154 @@
1
+ //! Zap HTTP Server - High-Performance Rust-Based Server
2
+ //!
3
+ //! Ultra-fast HTTP server with SIMD optimizations and Unix socket IPC
4
+ //! for TypeScript handler invocation.
5
+
6
+ use clap::Parser;
7
+ use std::path::PathBuf;
8
+ use tokio::signal;
9
+ use tracing::{error, info};
10
+ use tracing_subscriber::EnvFilter;
11
+
12
+ use zap_server::config::ZapConfig;
13
+ use zap_server::error::ZapResult;
14
+ use zap_server::Zap;
15
+
16
+ #[derive(Parser, Debug)]
17
+ #[command(name = "Zap")]
18
+ #[command(version = "1.0.0")]
19
+ #[command(about = "Ultra-fast HTTP server for Node.js/Bun", long_about = None)]
20
+ struct Args {
21
+ /// Path to JSON configuration file
22
+ #[arg(short, long)]
23
+ config: PathBuf,
24
+
25
+ /// Override HTTP server port
26
+ #[arg(short, long)]
27
+ port: Option<u16>,
28
+
29
+ /// Override HTTP server hostname
30
+ #[arg(long)]
31
+ hostname: Option<String>,
32
+
33
+ /// Unix socket path for IPC with TypeScript wrapper
34
+ #[arg(short, long)]
35
+ socket: Option<String>,
36
+
37
+ /// Log level (trace, debug, info, warn, error)
38
+ #[arg(long, default_value = "info")]
39
+ log_level: String,
40
+ }
41
+
42
+ #[tokio::main]
43
+ async fn main() -> ZapResult<()> {
44
+ let args = Args::parse();
45
+
46
+ // Initialize logging
47
+ init_logging(&args.log_level)?;
48
+
49
+ info!("🚀 Starting Zap HTTP server v1.0.0");
50
+
51
+ // Load configuration from JSON file
52
+ let mut config = ZapConfig::from_file(args.config.to_str().unwrap())?;
53
+
54
+ info!(
55
+ "📋 Configuration loaded from {}",
56
+ args.config.display()
57
+ );
58
+
59
+ // Apply CLI argument overrides
60
+ if let Some(port) = args.port {
61
+ info!("⚙️ Overriding port: {}", port);
62
+ config.port = port;
63
+ }
64
+ if let Some(hostname) = args.hostname {
65
+ info!("⚙️ Overriding hostname: {}", hostname);
66
+ config.hostname = hostname;
67
+ }
68
+ if let Some(socket) = args.socket {
69
+ info!("⚙️ Overriding IPC socket: {}", socket);
70
+ config.ipc_socket_path = socket;
71
+ }
72
+
73
+ // Validate configuration
74
+ config.validate().await?;
75
+
76
+ info!(
77
+ "📡 Server will listen on http://{}:{}",
78
+ config.hostname, config.port
79
+ );
80
+ info!("🔌 IPC socket: {}", config.ipc_socket_path);
81
+ info!("📊 Routes: {}", config.routes.len());
82
+ info!("📁 Static files: {}", config.static_files.len());
83
+
84
+ // Create and start the server
85
+ let app = Zap::from_config(config).await?;
86
+
87
+ info!("✅ Zap server initialized successfully");
88
+
89
+ // Run the server (blocks until signal)
90
+ // Note: listen() takes ownership and runs indefinitely
91
+ tokio::select! {
92
+ result = app.listen() => {
93
+ if let Err(e) = result {
94
+ error!("Server error: {}", e);
95
+ return Err(e);
96
+ }
97
+ }
98
+ _ = setup_signal_handlers() => {
99
+ info!("📛 Received shutdown signal");
100
+ }
101
+ }
102
+
103
+ info!("👋 Zap server shut down successfully");
104
+ Ok(())
105
+ }
106
+
107
+ /// Initialize structured logging with configurable level
108
+ fn init_logging(level: &str) -> ZapResult<()> {
109
+ let env_filter = level.parse::<EnvFilter>().map_err(|e| {
110
+ zap_server::error::ZapError::config(format!(
111
+ "Invalid log level '{}': {}",
112
+ level, e
113
+ ))
114
+ })?;
115
+
116
+ tracing_subscriber::fmt()
117
+ .with_env_filter(env_filter)
118
+ .with_target(true)
119
+ .with_file(true)
120
+ .with_line_number(true)
121
+ .with_ansi(true)
122
+ .init();
123
+
124
+ Ok(())
125
+ }
126
+
127
+ /// Setup Unix signal handlers for graceful shutdown
128
+ async fn setup_signal_handlers() {
129
+ #[cfg(unix)]
130
+ {
131
+ let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate())
132
+ .expect("Failed to setup SIGTERM handler");
133
+ let mut sigint = signal::unix::signal(signal::unix::SignalKind::interrupt())
134
+ .expect("Failed to setup SIGINT handler");
135
+
136
+ tokio::select! {
137
+ _ = sigterm.recv() => {
138
+ info!("Received SIGTERM signal");
139
+ }
140
+ _ = sigint.recv() => {
141
+ info!("Received SIGINT signal");
142
+ }
143
+ _ = signal::ctrl_c() => {
144
+ info!("Received Ctrl+C");
145
+ }
146
+ }
147
+ }
148
+
149
+ #[cfg(not(unix))]
150
+ {
151
+ let _ = signal::ctrl_c().await;
152
+ info!("Received Ctrl+C");
153
+ }
154
+ }
package/src/config.rs ADDED
@@ -0,0 +1,253 @@
1
+ //! Server configuration
2
+ //!
3
+ //! Comprehensive configuration system supporting:
4
+ //! - JSON config files
5
+ //! - Environment variables
6
+ //! - CLI argument overrides
7
+
8
+ use serde::{Deserialize, Serialize};
9
+ use std::time::Duration;
10
+ use crate::error::{ZapError, ZapResult};
11
+
12
+ /// Complete Zap server configuration
13
+ #[derive(Debug, Clone, Serialize, Deserialize)]
14
+ pub struct ZapConfig {
15
+ /// HTTP server port
16
+ pub port: u16,
17
+
18
+ /// HTTP server hostname
19
+ pub hostname: String,
20
+
21
+ /// Unix domain socket path for IPC with TypeScript
22
+ pub ipc_socket_path: String,
23
+
24
+ /// Maximum request body size in bytes (default: 16MB)
25
+ #[serde(default = "default_max_body_size")]
26
+ pub max_request_body_size: usize,
27
+
28
+ /// Request timeout in seconds
29
+ #[serde(default = "default_request_timeout")]
30
+ pub request_timeout_secs: u64,
31
+
32
+ /// Keep-alive timeout in seconds
33
+ #[serde(default = "default_keepalive_timeout")]
34
+ pub keepalive_timeout_secs: u64,
35
+
36
+ /// Route configurations
37
+ #[serde(default)]
38
+ pub routes: Vec<RouteConfig>,
39
+
40
+ /// Static file configurations
41
+ #[serde(default)]
42
+ pub static_files: Vec<StaticFileConfig>,
43
+
44
+ /// Middleware settings
45
+ #[serde(default)]
46
+ pub middleware: MiddlewareConfig,
47
+
48
+ /// Health check endpoint path
49
+ #[serde(default = "default_health_path")]
50
+ pub health_check_path: String,
51
+
52
+ /// Metrics endpoint path
53
+ #[serde(default)]
54
+ pub metrics_path: Option<String>,
55
+ }
56
+
57
+ /// Route configuration
58
+ #[derive(Debug, Clone, Serialize, Deserialize)]
59
+ pub struct RouteConfig {
60
+ /// HTTP method: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
61
+ pub method: String,
62
+
63
+ /// URL path pattern: /api/users/:id
64
+ pub path: String,
65
+
66
+ /// Handler ID: handler_0, handler_1, etc.
67
+ pub handler_id: String,
68
+
69
+ /// Is this a TypeScript handler (needs IPC), or Rust native?
70
+ #[serde(default = "default_is_typescript")]
71
+ pub is_typescript: bool,
72
+ }
73
+
74
+ /// Static file serving configuration
75
+ #[derive(Debug, Clone, Serialize, Deserialize)]
76
+ pub struct StaticFileConfig {
77
+ /// URL prefix: /static
78
+ pub prefix: String,
79
+
80
+ /// Directory path: ./public
81
+ pub directory: String,
82
+
83
+ /// Additional options
84
+ #[serde(default)]
85
+ pub options: StaticFileOptions,
86
+ }
87
+
88
+ /// Static file serving options
89
+ #[derive(Debug, Clone, Serialize, Deserialize, Default)]
90
+ pub struct StaticFileOptions {
91
+ /// Cache control header value
92
+ #[serde(default)]
93
+ pub cache_control: Option<String>,
94
+
95
+ /// Enable gzip compression
96
+ #[serde(default)]
97
+ pub enable_gzip: bool,
98
+ }
99
+
100
+ /// Middleware configuration
101
+ #[derive(Debug, Clone, Serialize, Deserialize, Default)]
102
+ pub struct MiddlewareConfig {
103
+ /// Enable CORS middleware
104
+ #[serde(default)]
105
+ pub enable_cors: bool,
106
+
107
+ /// Enable request logging middleware
108
+ #[serde(default)]
109
+ pub enable_logging: bool,
110
+
111
+ /// Enable response compression
112
+ #[serde(default)]
113
+ pub enable_compression: bool,
114
+ }
115
+
116
+ impl Default for ZapConfig {
117
+ fn default() -> Self {
118
+ Self {
119
+ port: 3000,
120
+ hostname: "127.0.0.1".to_string(),
121
+ ipc_socket_path: "/tmp/zap.sock".to_string(),
122
+ max_request_body_size: 16 * 1024 * 1024, // 16MB
123
+ request_timeout_secs: 30,
124
+ keepalive_timeout_secs: 75,
125
+ routes: Vec::new(),
126
+ static_files: Vec::new(),
127
+ middleware: MiddlewareConfig::default(),
128
+ health_check_path: "/health".to_string(),
129
+ metrics_path: None,
130
+ }
131
+ }
132
+ }
133
+
134
+ impl ZapConfig {
135
+ /// Create a new config with defaults
136
+ pub fn new() -> Self {
137
+ Self::default()
138
+ }
139
+
140
+ /// Load configuration from a JSON file
141
+ pub fn from_file(path: &str) -> ZapResult<Self> {
142
+ let content = std::fs::read_to_string(path)
143
+ .map_err(|e| ZapError::config(format!("Failed to read config file: {}", e)))?;
144
+
145
+ let config = serde_json::from_str(&content)
146
+ .map_err(|e| ZapError::config(format!("Failed to parse config JSON: {}", e)))?;
147
+
148
+ Ok(config)
149
+ }
150
+
151
+ /// Validate configuration
152
+ pub async fn validate(&self) -> ZapResult<()> {
153
+ if self.port == 0 {
154
+ return Err(ZapError::config("Port must be > 0"));
155
+ }
156
+ if self.hostname.is_empty() {
157
+ return Err(ZapError::config("Hostname cannot be empty"));
158
+ }
159
+ if self.ipc_socket_path.is_empty() {
160
+ return Err(ZapError::config("IPC socket path cannot be empty"));
161
+ }
162
+ if self.request_timeout_secs == 0 {
163
+ return Err(ZapError::config("Request timeout must be > 0"));
164
+ }
165
+ Ok(())
166
+ }
167
+
168
+ /// Get socket address as string
169
+ pub fn socket_addr(&self) -> String {
170
+ format!("{}:{}", self.hostname, self.port)
171
+ }
172
+
173
+ /// Get request timeout as Duration
174
+ pub fn request_timeout(&self) -> Duration {
175
+ Duration::from_secs(self.request_timeout_secs)
176
+ }
177
+
178
+ /// Get keep-alive timeout as Duration
179
+ pub fn keepalive_timeout(&self) -> Duration {
180
+ Duration::from_secs(self.keepalive_timeout_secs)
181
+ }
182
+ }
183
+
184
+ // Default function values for serde
185
+ fn default_max_body_size() -> usize { 16 * 1024 * 1024 }
186
+ fn default_request_timeout() -> u64 { 30 }
187
+ fn default_keepalive_timeout() -> u64 { 75 }
188
+ fn default_health_path() -> String { "/health".to_string() }
189
+ fn default_is_typescript() -> bool { true }
190
+
191
+ /// Legacy ServerConfig for compatibility
192
+ #[derive(Debug, Clone)]
193
+ pub struct ServerConfig {
194
+ pub port: u16,
195
+ pub hostname: String,
196
+ pub keep_alive_timeout: Duration,
197
+ pub max_request_body_size: usize,
198
+ pub max_headers: usize,
199
+ pub request_timeout: Duration,
200
+ }
201
+
202
+ impl Default for ServerConfig {
203
+ fn default() -> Self {
204
+ Self {
205
+ port: 3000,
206
+ hostname: "127.0.0.1".to_string(),
207
+ keep_alive_timeout: Duration::from_secs(75),
208
+ max_request_body_size: 16 * 1024 * 1024,
209
+ max_headers: 100,
210
+ request_timeout: Duration::from_secs(30),
211
+ }
212
+ }
213
+ }
214
+
215
+ impl ServerConfig {
216
+ pub fn new() -> Self {
217
+ Self::default()
218
+ }
219
+
220
+ pub fn port(mut self, port: u16) -> Self {
221
+ self.port = port;
222
+ self
223
+ }
224
+
225
+ pub fn hostname<S: Into<String>>(mut self, hostname: S) -> Self {
226
+ self.hostname = hostname.into();
227
+ self
228
+ }
229
+
230
+ pub fn keep_alive_timeout(mut self, timeout: Duration) -> Self {
231
+ self.keep_alive_timeout = timeout;
232
+ self
233
+ }
234
+
235
+ pub fn max_request_body_size(mut self, size: usize) -> Self {
236
+ self.max_request_body_size = size;
237
+ self
238
+ }
239
+
240
+ pub fn max_headers(mut self, count: usize) -> Self {
241
+ self.max_headers = count;
242
+ self
243
+ }
244
+
245
+ pub fn request_timeout(mut self, timeout: Duration) -> Self {
246
+ self.request_timeout = timeout;
247
+ self
248
+ }
249
+
250
+ pub fn socket_addr(&self) -> String {
251
+ format!("{}:{}", self.hostname, self.port)
252
+ }
253
+ }