@zap-js/client 0.0.2 → 0.0.5

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.
Files changed (115) hide show
  1. package/README.md +310 -24
  2. package/bin/zap +0 -0
  3. package/bin/zap-codegen +0 -0
  4. package/dist/cli/commands/build.d.ts +11 -0
  5. package/dist/cli/commands/build.js +282 -0
  6. package/dist/cli/commands/codegen.d.ts +8 -0
  7. package/dist/cli/commands/codegen.js +95 -0
  8. package/dist/cli/commands/dev.d.ts +20 -0
  9. package/dist/cli/commands/dev.js +78 -0
  10. package/dist/cli/commands/new.d.ts +9 -0
  11. package/dist/cli/commands/new.js +307 -0
  12. package/dist/cli/commands/routes-old.d.ts +9 -0
  13. package/dist/cli/commands/routes-old.js +106 -0
  14. package/dist/cli/commands/routes.d.ts +11 -0
  15. package/dist/cli/commands/routes.js +280 -0
  16. package/dist/cli/commands/serve.d.ts +17 -0
  17. package/dist/cli/commands/serve.js +386 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +76 -0
  20. package/dist/cli/utils/index.d.ts +2 -0
  21. package/dist/cli/utils/index.js +2 -0
  22. package/dist/cli/utils/logger.d.ts +84 -0
  23. package/dist/cli/utils/logger.js +181 -0
  24. package/dist/cli/utils/port-finder.d.ts +8 -0
  25. package/dist/cli/utils/port-finder.js +48 -0
  26. package/dist/dev-server/codegen-runner.d.ts +41 -0
  27. package/dist/dev-server/codegen-runner.js +172 -0
  28. package/dist/dev-server/hot-reload.d.ts +72 -0
  29. package/dist/dev-server/hot-reload.js +280 -0
  30. package/dist/dev-server/index.d.ts +8 -0
  31. package/dist/dev-server/index.js +8 -0
  32. package/dist/dev-server/route-scanner.d.ts +84 -0
  33. package/dist/dev-server/route-scanner.js +113 -0
  34. package/dist/dev-server/rust-builder.d.ts +66 -0
  35. package/dist/dev-server/rust-builder.js +286 -0
  36. package/dist/dev-server/server.d.ts +147 -0
  37. package/dist/dev-server/server.js +660 -0
  38. package/dist/dev-server/vite-proxy.d.ts +56 -0
  39. package/dist/dev-server/vite-proxy.js +212 -0
  40. package/dist/dev-server/watcher.d.ts +48 -0
  41. package/dist/dev-server/watcher.js +127 -0
  42. package/dist/router/codegen-enhanced.d.ts +5 -0
  43. package/dist/router/codegen-enhanced.js +275 -0
  44. package/dist/router/codegen.d.ts +17 -0
  45. package/dist/router/codegen.js +654 -0
  46. package/dist/router/index.d.ts +16 -0
  47. package/dist/router/index.js +19 -0
  48. package/dist/router/scanner.d.ts +86 -0
  49. package/dist/router/scanner.js +689 -0
  50. package/dist/router/ssg.d.ts +115 -0
  51. package/dist/router/ssg.js +202 -0
  52. package/dist/router/types.d.ts +124 -0
  53. package/dist/router/types.js +9 -0
  54. package/dist/router/watch.d.ts +38 -0
  55. package/dist/router/watch.js +135 -0
  56. package/dist/runtime/csrf.d.ts +146 -0
  57. package/dist/runtime/csrf.js +166 -0
  58. package/dist/runtime/error-boundary.d.ts +129 -0
  59. package/dist/runtime/error-boundary.js +287 -0
  60. package/dist/runtime/hooks.d.ts +83 -0
  61. package/dist/runtime/hooks.js +96 -0
  62. package/dist/runtime/index.d.ts +229 -0
  63. package/dist/runtime/index.js +449 -0
  64. package/dist/runtime/ipc-client.d.ts +144 -0
  65. package/dist/runtime/ipc-client.js +621 -0
  66. package/dist/runtime/logger.d.ts +71 -0
  67. package/dist/runtime/logger.js +164 -0
  68. package/dist/runtime/middleware.d.ts +66 -0
  69. package/dist/runtime/middleware.js +114 -0
  70. package/dist/runtime/process-manager.d.ts +51 -0
  71. package/dist/runtime/process-manager.js +207 -0
  72. package/dist/runtime/router-simple.d.ts +98 -0
  73. package/dist/runtime/router-simple.js +330 -0
  74. package/dist/runtime/router.d.ts +103 -0
  75. package/dist/runtime/router.js +435 -0
  76. package/dist/runtime/rpc-client.d.ts +35 -0
  77. package/dist/runtime/rpc-client.js +140 -0
  78. package/dist/runtime/streaming-utils.d.ts +86 -0
  79. package/dist/runtime/streaming-utils.js +150 -0
  80. package/dist/runtime/types.d.ts +465 -0
  81. package/dist/runtime/types.js +60 -0
  82. package/dist/runtime/websockets-utils.d.ts +50 -0
  83. package/dist/runtime/websockets-utils.js +92 -0
  84. package/package.json +30 -20
  85. package/index.js +0 -29
  86. package/internal/cli/package.json +0 -46
  87. package/internal/cli/tsconfig.tsbuildinfo +0 -1
  88. package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
  89. package/internal/dev-server/node_modules/ora/index.js +0 -416
  90. package/internal/dev-server/node_modules/ora/license +0 -9
  91. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
  92. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
  93. package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
  94. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  95. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
  96. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
  97. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
  98. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
  99. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
  100. package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
  101. package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
  102. package/internal/dev-server/node_modules/ora/package.json +0 -66
  103. package/internal/dev-server/node_modules/ora/readme.md +0 -325
  104. package/internal/dev-server/package.json +0 -41
  105. package/internal/router/package.json +0 -28
  106. package/internal/runtime/package.json +0 -41
  107. package/internal/runtime/src/error-boundary.tsx +0 -476
  108. package/internal/runtime/src/router-simple.tsx +0 -640
  109. package/internal/runtime/src/router.tsx +0 -771
  110. package/internal/runtime/tsconfig.tsbuildinfo +0 -1
  111. package/src/errors.js +0 -33
  112. package/src/logger.js +0 -10
  113. package/src/middleware.js +0 -32
  114. package/src/router.js +0 -41
  115. package/src/types.js +0 -39
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Structured JSON Logger for ZapJS
3
+ *
4
+ * Provides consistent JSON logging with request context.
5
+ * Used across the TypeScript runtime for observability.
6
+ */
7
+ const LEVEL_PRIORITY = {
8
+ trace: 0,
9
+ debug: 1,
10
+ info: 2,
11
+ warn: 3,
12
+ error: 4,
13
+ };
14
+ class Logger {
15
+ constructor() {
16
+ this.jsonFormat = process.env.ZAP_JSON_LOGS === 'true';
17
+ this.minLevel = process.env.ZAP_LOG_LEVEL || 'info';
18
+ }
19
+ shouldLog(level) {
20
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.minLevel];
21
+ }
22
+ formatEntry(entry) {
23
+ if (this.jsonFormat) {
24
+ return JSON.stringify(entry);
25
+ }
26
+ // Human-readable format for development
27
+ const ctx = entry.context;
28
+ const timestamp = entry.timestamp.split('T')[1].split('.')[0]; // HH:MM:SS
29
+ const level = entry.level.toUpperCase().padEnd(5);
30
+ const requestIdStr = ctx?.request_id ? `[${ctx.request_id.slice(0, 8)}]` : '';
31
+ const contextParts = [];
32
+ if (ctx) {
33
+ if (ctx.method)
34
+ contextParts.push(`${ctx.method}`);
35
+ if (ctx.path)
36
+ contextParts.push(`${ctx.path}`);
37
+ if (ctx.handler_id)
38
+ contextParts.push(`handler=${ctx.handler_id}`);
39
+ if (ctx.duration_ms !== undefined)
40
+ contextParts.push(`${ctx.duration_ms.toFixed(2)}ms`);
41
+ if (ctx.status)
42
+ contextParts.push(`status=${ctx.status}`);
43
+ }
44
+ const contextStr = contextParts.length > 0 ? ` ${contextParts.join(' ')}` : '';
45
+ let result = `${timestamp} ${level} ${requestIdStr} ${entry.message}${contextStr}`;
46
+ if (entry.error) {
47
+ result += `\n Error: ${entry.error.name}: ${entry.error.message}`;
48
+ if (entry.error.stack && process.env.NODE_ENV !== 'production') {
49
+ result += `\n${entry.error.stack}`;
50
+ }
51
+ }
52
+ return result.trim();
53
+ }
54
+ log(level, message, context, error) {
55
+ if (!this.shouldLog(level))
56
+ return;
57
+ const entry = {
58
+ timestamp: new Date().toISOString(),
59
+ level,
60
+ message,
61
+ context,
62
+ };
63
+ if (error) {
64
+ entry.error = {
65
+ name: error.name,
66
+ message: error.message,
67
+ stack: error.stack,
68
+ };
69
+ }
70
+ const output = this.formatEntry(entry);
71
+ if (level === 'error') {
72
+ console.error(output);
73
+ }
74
+ else if (level === 'warn') {
75
+ console.warn(output);
76
+ }
77
+ else {
78
+ console.log(output);
79
+ }
80
+ }
81
+ trace(message, context) {
82
+ this.log('trace', message, context);
83
+ }
84
+ debug(message, context) {
85
+ this.log('debug', message, context);
86
+ }
87
+ info(message, context) {
88
+ this.log('info', message, context);
89
+ }
90
+ warn(message, context) {
91
+ this.log('warn', message, context);
92
+ }
93
+ error(message, context, error) {
94
+ this.log('error', message, context, error);
95
+ }
96
+ /**
97
+ * Create a child logger with pre-set context
98
+ */
99
+ child(baseContext) {
100
+ return new ChildLogger(this, baseContext);
101
+ }
102
+ /**
103
+ * Set JSON format mode
104
+ */
105
+ setJsonFormat(enabled) {
106
+ this.jsonFormat = enabled;
107
+ }
108
+ /**
109
+ * Set minimum log level
110
+ */
111
+ setMinLevel(level) {
112
+ this.minLevel = level;
113
+ }
114
+ /**
115
+ * Get current configuration
116
+ */
117
+ getConfig() {
118
+ return {
119
+ jsonFormat: this.jsonFormat,
120
+ minLevel: this.minLevel,
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * Child logger with inherited base context
126
+ */
127
+ class ChildLogger {
128
+ constructor(parent, baseContext) {
129
+ this.parent = parent;
130
+ this.baseContext = baseContext;
131
+ }
132
+ mergeContext(context) {
133
+ return { ...this.baseContext, ...context };
134
+ }
135
+ trace(message, context) {
136
+ this.parent.trace(message, this.mergeContext(context));
137
+ }
138
+ debug(message, context) {
139
+ this.parent.debug(message, this.mergeContext(context));
140
+ }
141
+ info(message, context) {
142
+ this.parent.info(message, this.mergeContext(context));
143
+ }
144
+ warn(message, context) {
145
+ this.parent.warn(message, this.mergeContext(context));
146
+ }
147
+ error(message, context, error) {
148
+ this.parent.error(message, this.mergeContext(context), error);
149
+ }
150
+ /**
151
+ * Create a further nested child logger
152
+ */
153
+ child(additionalContext) {
154
+ return new ChildLogger(this.parent, {
155
+ ...this.baseContext,
156
+ ...additionalContext,
157
+ });
158
+ }
159
+ }
160
+ /**
161
+ * Global logger instance
162
+ */
163
+ export const logger = new Logger();
164
+ export { Logger, ChildLogger };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Route-level middleware for ZapJS
3
+ *
4
+ * Middleware can:
5
+ * - Protect routes (authentication/authorization)
6
+ * - Transform data before rendering
7
+ * - Handle redirects
8
+ * - Log/monitor route access
9
+ */
10
+ import type { RouteMatch } from './router.js';
11
+ export interface MiddlewareContext {
12
+ /** Current route match */
13
+ match: RouteMatch;
14
+ /** URL pathname */
15
+ pathname: string;
16
+ /** Search params */
17
+ search: string;
18
+ /** Hash */
19
+ hash: string;
20
+ /** Navigation state */
21
+ state?: unknown;
22
+ }
23
+ export interface MiddlewareResult {
24
+ /** Continue to route */
25
+ type: 'continue' | 'redirect' | 'block';
26
+ /** Redirect path if type is 'redirect' */
27
+ redirectTo?: string;
28
+ /** Error to throw if type is 'block' */
29
+ error?: Error;
30
+ /** Data to pass to route */
31
+ data?: Record<string, any>;
32
+ }
33
+ export type MiddlewareFunction = (context: MiddlewareContext) => Promise<MiddlewareResult> | MiddlewareResult;
34
+ export interface RouteMiddleware {
35
+ /** Middleware name for debugging */
36
+ name?: string;
37
+ /** Middleware function */
38
+ handler: MiddlewareFunction;
39
+ }
40
+ /**
41
+ * Compose multiple middleware functions into one
42
+ */
43
+ export declare function composeMiddleware(middlewares: RouteMiddleware[]): MiddlewareFunction;
44
+ /**
45
+ * Common middleware factories
46
+ */
47
+ /**
48
+ * Authentication middleware
49
+ */
50
+ export declare function requireAuth(checkAuth: () => boolean | Promise<boolean>, loginPath?: string): RouteMiddleware;
51
+ /**
52
+ * Role-based access control
53
+ */
54
+ export declare function requireRole(roles: string[], getUserRoles: () => string[] | Promise<string[]>, forbiddenPath?: string): RouteMiddleware;
55
+ /**
56
+ * Logging middleware
57
+ */
58
+ export declare function routeLogger(log: (info: {
59
+ pathname: string;
60
+ params: Record<string, string>;
61
+ timestamp: number;
62
+ }) => void): RouteMiddleware;
63
+ /**
64
+ * Data preloading middleware
65
+ */
66
+ export declare function preloadData<T>(loader: (params: Record<string, string>) => Promise<T>, key?: string): RouteMiddleware;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Route-level middleware for ZapJS
3
+ *
4
+ * Middleware can:
5
+ * - Protect routes (authentication/authorization)
6
+ * - Transform data before rendering
7
+ * - Handle redirects
8
+ * - Log/monitor route access
9
+ */
10
+ /**
11
+ * Compose multiple middleware functions into one
12
+ */
13
+ export function composeMiddleware(middlewares) {
14
+ return async (context) => {
15
+ for (const middleware of middlewares) {
16
+ try {
17
+ const result = await middleware.handler(context);
18
+ if (result.type !== 'continue') {
19
+ return result;
20
+ }
21
+ // Pass data to next middleware
22
+ if (result.data) {
23
+ const existingState = typeof context.state === 'object' && context.state !== null ? context.state : {};
24
+ context = { ...context, state: { ...existingState, ...result.data } };
25
+ }
26
+ }
27
+ catch (error) {
28
+ console.error(`Middleware ${middleware.name || 'unknown'} error:`, error);
29
+ return {
30
+ type: 'block',
31
+ error: error instanceof Error ? error : new Error('Middleware error'),
32
+ };
33
+ }
34
+ }
35
+ return { type: 'continue' };
36
+ };
37
+ }
38
+ /**
39
+ * Common middleware factories
40
+ */
41
+ /**
42
+ * Authentication middleware
43
+ */
44
+ export function requireAuth(checkAuth, loginPath = '/login') {
45
+ return {
46
+ name: 'requireAuth',
47
+ handler: async (context) => {
48
+ const isAuthenticated = await checkAuth();
49
+ if (!isAuthenticated) {
50
+ return {
51
+ type: 'redirect',
52
+ redirectTo: `${loginPath}?redirect=${encodeURIComponent(context.pathname)}`,
53
+ };
54
+ }
55
+ return { type: 'continue' };
56
+ },
57
+ };
58
+ }
59
+ /**
60
+ * Role-based access control
61
+ */
62
+ export function requireRole(roles, getUserRoles, forbiddenPath = '/403') {
63
+ return {
64
+ name: 'requireRole',
65
+ handler: async (context) => {
66
+ const userRoles = await getUserRoles();
67
+ const hasRole = roles.some(role => userRoles.includes(role));
68
+ if (!hasRole) {
69
+ return {
70
+ type: 'redirect',
71
+ redirectTo: forbiddenPath,
72
+ };
73
+ }
74
+ return { type: 'continue' };
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * Logging middleware
80
+ */
81
+ export function routeLogger(log) {
82
+ return {
83
+ name: 'routeLogger',
84
+ handler: (context) => {
85
+ log({
86
+ pathname: context.pathname,
87
+ params: context.match.params,
88
+ timestamp: Date.now(),
89
+ });
90
+ return { type: 'continue' };
91
+ },
92
+ };
93
+ }
94
+ /**
95
+ * Data preloading middleware
96
+ */
97
+ export function preloadData(loader, key = 'preloadedData') {
98
+ return {
99
+ name: 'preloadData',
100
+ handler: async (context) => {
101
+ try {
102
+ const data = await loader(context.match.params);
103
+ return {
104
+ type: 'continue',
105
+ data: { [key]: data },
106
+ };
107
+ }
108
+ catch (error) {
109
+ console.error('Data preload failed:', error);
110
+ return { type: 'continue' };
111
+ }
112
+ },
113
+ };
114
+ }
@@ -0,0 +1,51 @@
1
+ import type { ZapConfig } from "./types.js";
2
+ export type { ZapConfig, RouteConfig, MiddlewareConfig, StaticFileConfig, StaticFileOptions, } from "./types.js";
3
+ /**
4
+ * ProcessManager
5
+ *
6
+ * Manages the lifecycle of the Rust binary process:
7
+ * - Spawning the process with proper configuration
8
+ * - Forwarding logs to console
9
+ * - Monitoring for crashes
10
+ * - Graceful shutdown with timeout
11
+ * - Health check polling
12
+ */
13
+ export declare class ProcessManager {
14
+ private process;
15
+ private configPath;
16
+ private binaryPath;
17
+ private socketPath;
18
+ constructor(binaryPath?: string, socketPath?: string);
19
+ /**
20
+ * Find the Zap binary in common locations
21
+ */
22
+ private getDefaultBinaryPath;
23
+ /**
24
+ * Check if a binary file exists and is executable
25
+ */
26
+ private binaryExists;
27
+ /**
28
+ * Start the Rust server process
29
+ */
30
+ start(config: ZapConfig, logLevel?: string): Promise<void>;
31
+ /**
32
+ * Poll the health check endpoint until the server is ready
33
+ */
34
+ private waitForHealthy;
35
+ /**
36
+ * Stop the server process immediately
37
+ */
38
+ stop(): Promise<void>;
39
+ /**
40
+ * Restart the server
41
+ */
42
+ restart(config: ZapConfig, logLevel?: string): Promise<void>;
43
+ /**
44
+ * Get the IPC socket path
45
+ */
46
+ getSocketPath(): string;
47
+ /**
48
+ * Check if the process is still running
49
+ */
50
+ isRunning(): boolean;
51
+ }
@@ -0,0 +1,207 @@
1
+ import { spawn, execSync } from "child_process";
2
+ import { writeFileSync, unlinkSync, existsSync } from "fs";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+ /**
6
+ * ProcessManager
7
+ *
8
+ * Manages the lifecycle of the Rust binary process:
9
+ * - Spawning the process with proper configuration
10
+ * - Forwarding logs to console
11
+ * - Monitoring for crashes
12
+ * - Graceful shutdown with timeout
13
+ * - Health check polling
14
+ */
15
+ export class ProcessManager {
16
+ constructor(binaryPath, socketPath) {
17
+ this.process = null;
18
+ this.configPath = null;
19
+ this.binaryPath = binaryPath || this.getDefaultBinaryPath();
20
+ this.socketPath = socketPath || join(tmpdir(), `zap-${Date.now()}.sock`);
21
+ }
22
+ /**
23
+ * Find the Zap binary in common locations
24
+ */
25
+ getDefaultBinaryPath() {
26
+ // Try multiple locations
27
+ const arch = process.arch === "arm64" ? "aarch64-apple-darwin" : `${process.arch}-${process.platform}`;
28
+ const candidates = [
29
+ // Project local bin directory
30
+ join(process.cwd(), "bin/zap"),
31
+ // Installed package location (when @zap-js/client is a dependency)
32
+ join(__dirname, "../../bin/zap"),
33
+ // node_modules location
34
+ join(process.cwd(), "node_modules/@zap-js/client/bin/zap"),
35
+ // Legacy locations
36
+ join(__dirname, `../target/${arch}/release/zap`),
37
+ join(__dirname, "../target/release/zap"),
38
+ join(__dirname, "../server/target/release/zap"),
39
+ join(process.cwd(), "target/release/zap"),
40
+ join(process.cwd(), `target/${arch}/release/zap`),
41
+ "zap", // System PATH
42
+ ];
43
+ for (const candidate of candidates) {
44
+ if (this.binaryExists(candidate)) {
45
+ return candidate;
46
+ }
47
+ }
48
+ throw new Error("Zap binary not found. Build with: npm run build:rust or cargo build --release --bin zap");
49
+ }
50
+ /**
51
+ * Check if a binary file exists and is executable
52
+ */
53
+ binaryExists(path) {
54
+ if (!existsSync(path)) {
55
+ return false;
56
+ }
57
+ // For system PATH, just check existence
58
+ if (!path.includes("/")) {
59
+ return true;
60
+ }
61
+ // For local paths, more thorough check
62
+ try {
63
+ execSync(`test -x "${path}"`, { stdio: "ignore" });
64
+ return true;
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ /**
71
+ * Start the Rust server process
72
+ */
73
+ async start(config, logLevel = "info") {
74
+ try {
75
+ // Write configuration to temporary JSON file
76
+ this.configPath = join(tmpdir(), `zap-config-${Date.now()}.json`);
77
+ writeFileSync(this.configPath, JSON.stringify(config, null, 2));
78
+ console.log(`[Zap] Starting server on ${config.hostname}:${config.port}`);
79
+ console.log(`[Zap] IPC socket: ${this.socketPath}`);
80
+ // Spawn the Rust binary
81
+ this.process = spawn(this.binaryPath, [
82
+ "--config",
83
+ this.configPath,
84
+ "--socket",
85
+ this.socketPath,
86
+ "--log-level",
87
+ logLevel,
88
+ ], {
89
+ stdio: ["ignore", "pipe", "pipe"],
90
+ env: {
91
+ ...process.env,
92
+ RUST_LOG: logLevel,
93
+ },
94
+ });
95
+ if (!this.process.stdout || !this.process.stderr) {
96
+ throw new Error("Failed to create process streams");
97
+ }
98
+ // Forward stdout
99
+ this.process.stdout.on("data", (data) => {
100
+ const output = data.toString().trim();
101
+ if (output) {
102
+ console.log(`[Zap] ${output}`);
103
+ }
104
+ });
105
+ // Forward stderr
106
+ this.process.stderr.on("data", (data) => {
107
+ const output = data.toString().trim();
108
+ if (output) {
109
+ console.error(`[Zap] ${output}`);
110
+ }
111
+ });
112
+ // Handle process exit
113
+ this.process.on("exit", (code, signal) => {
114
+ if (code !== 0 || signal) {
115
+ console.error(`[Zap] Process exited: code=${code ?? 'null'}, signal=${signal ?? 'null'}`);
116
+ }
117
+ });
118
+ // Handle process errors
119
+ this.process.on("error", (err) => {
120
+ console.error(`[Zap] Process error:`, err);
121
+ });
122
+ console.log(`[Zap] Server started on http://${config.hostname}:${config.port}`);
123
+ }
124
+ catch (error) {
125
+ // Clean up on error
126
+ await this.stop();
127
+ throw error;
128
+ }
129
+ }
130
+ /**
131
+ * Poll the health check endpoint until the server is ready
132
+ */
133
+ async waitForHealthy(hostname, port, healthPath, maxAttempts = 50, delayMs = 100) {
134
+ for (let i = 0; i < maxAttempts; i++) {
135
+ try {
136
+ const controller = new AbortController();
137
+ const timeout = setTimeout(() => controller.abort(), 1000);
138
+ const response = await fetch(`http://${hostname}:${port}${healthPath}`, {
139
+ signal: controller.signal,
140
+ });
141
+ clearTimeout(timeout);
142
+ if (response.ok) {
143
+ return;
144
+ }
145
+ }
146
+ catch {
147
+ // Server not ready yet, continue polling
148
+ }
149
+ // Wait before retrying
150
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
151
+ }
152
+ throw new Error(`Server failed to start within ${maxAttempts * delayMs}ms`);
153
+ }
154
+ /**
155
+ * Stop the server process immediately
156
+ */
157
+ async stop() {
158
+ if (!this.process) {
159
+ // Clean up config file if it exists
160
+ if (this.configPath && existsSync(this.configPath)) {
161
+ try {
162
+ unlinkSync(this.configPath);
163
+ }
164
+ catch {
165
+ // Ignore cleanup errors
166
+ }
167
+ }
168
+ return;
169
+ }
170
+ // Kill immediately
171
+ if (!this.process.killed) {
172
+ this.process.kill("SIGKILL");
173
+ }
174
+ this.process = null;
175
+ // Clean up config file
176
+ if (this.configPath && existsSync(this.configPath)) {
177
+ try {
178
+ unlinkSync(this.configPath);
179
+ }
180
+ catch {
181
+ // Ignore cleanup errors
182
+ }
183
+ }
184
+ }
185
+ /**
186
+ * Restart the server
187
+ */
188
+ async restart(config, logLevel = "info") {
189
+ console.log("[Zap] Restarting server...");
190
+ await this.stop();
191
+ // Small delay to ensure clean shutdown
192
+ await new Promise((resolve) => setTimeout(resolve, 100));
193
+ await this.start(config, logLevel);
194
+ }
195
+ /**
196
+ * Get the IPC socket path
197
+ */
198
+ getSocketPath() {
199
+ return this.socketPath;
200
+ }
201
+ /**
202
+ * Check if the process is still running
203
+ */
204
+ isRunning() {
205
+ return this.process !== null && !this.process.killed;
206
+ }
207
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * ZapJS Production Router with Nested Layouts
3
+ *
4
+ * Features:
5
+ * - Nested layout support
6
+ * - Route-level code splitting
7
+ * - Error boundaries per route
8
+ * - Suspense boundaries
9
+ * - Type-safe navigation
10
+ */
11
+ import React, { type ReactNode, type ComponentType } from 'react';
12
+ export interface LayoutDefinition {
13
+ path: string;
14
+ component: React.LazyExoticComponent<ComponentType<any>>;
15
+ parentLayout?: string;
16
+ }
17
+ export interface RouteDefinition {
18
+ path: string;
19
+ pattern: RegExp;
20
+ paramNames: string[];
21
+ component: React.LazyExoticComponent<ComponentType<any>>;
22
+ layoutPath?: string;
23
+ errorComponent?: React.LazyExoticComponent<ComponentType<any>>;
24
+ pendingComponent?: React.LazyExoticComponent<ComponentType<any>>;
25
+ meta?: () => Promise<RouteMeta>;
26
+ }
27
+ export interface RouteMeta {
28
+ title?: string;
29
+ description?: string;
30
+ keywords?: string[];
31
+ [key: string]: any;
32
+ }
33
+ export interface RouteMatch {
34
+ route: RouteDefinition;
35
+ params: Record<string, string>;
36
+ pathname: string;
37
+ }
38
+ export interface RouterState {
39
+ pathname: string;
40
+ search: string;
41
+ hash: string;
42
+ match: RouteMatch | null;
43
+ }
44
+ export interface NavigateOptions {
45
+ replace?: boolean;
46
+ scroll?: boolean;
47
+ state?: unknown;
48
+ }
49
+ export interface Router {
50
+ push(path: string, options?: NavigateOptions): void;
51
+ replace(path: string, options?: NavigateOptions): void;
52
+ back(): void;
53
+ forward(): void;
54
+ refresh(): void;
55
+ prefetch(path: string): void;
56
+ }
57
+ interface RouterProviderProps {
58
+ routes: RouteDefinition[];
59
+ layouts?: LayoutDefinition[];
60
+ children: ReactNode;
61
+ notFound?: ComponentType;
62
+ fallback?: ReactNode;
63
+ }
64
+ export declare function RouterProvider({ routes, layouts, children, notFound: NotFound, fallback, }: RouterProviderProps): JSX.Element;
65
+ export declare function useRouter(): Router;
66
+ export declare function useParams<T extends Record<string, string> = Record<string, string>>(): T;
67
+ export declare function usePathname(): string;
68
+ export declare function useSearchParams(): [URLSearchParams, (params: Record<string, string>) => void];
69
+ export declare function useRouteMatch(): RouteMatch | null;
70
+ export declare function useIsPending(): boolean;
71
+ export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
72
+ to: string;
73
+ replace?: boolean;
74
+ prefetch?: boolean;
75
+ scroll?: boolean;
76
+ children: ReactNode;
77
+ }
78
+ export declare function Link({ to, replace, prefetch, scroll, children, onClick, onMouseEnter, ...props }: LinkProps): JSX.Element;
79
+ interface OutletProps {
80
+ notFound?: ComponentType;
81
+ fallback?: ReactNode;
82
+ }
83
+ export declare function Outlet({ notFound: NotFound, fallback }: OutletProps): JSX.Element | null;
84
+ interface NavLinkProps extends LinkProps {
85
+ activeClassName?: string;
86
+ activeStyle?: React.CSSProperties;
87
+ exact?: boolean;
88
+ pending?: boolean;
89
+ pendingClassName?: string;
90
+ pendingStyle?: React.CSSProperties;
91
+ }
92
+ export declare function NavLink({ to, activeClassName, activeStyle, exact, pending, pendingClassName, pendingStyle, className, style, ...props }: NavLinkProps): JSX.Element;
93
+ interface RedirectProps {
94
+ to: string;
95
+ replace?: boolean;
96
+ }
97
+ export declare function Redirect({ to, replace }: RedirectProps): null;
98
+ export {};