@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.
- package/README.md +310 -24
- package/bin/zap +0 -0
- package/bin/zap-codegen +0 -0
- package/dist/cli/commands/build.d.ts +11 -0
- package/dist/cli/commands/build.js +282 -0
- package/dist/cli/commands/codegen.d.ts +8 -0
- package/dist/cli/commands/codegen.js +95 -0
- package/dist/cli/commands/dev.d.ts +20 -0
- package/dist/cli/commands/dev.js +78 -0
- package/dist/cli/commands/new.d.ts +9 -0
- package/dist/cli/commands/new.js +307 -0
- package/dist/cli/commands/routes-old.d.ts +9 -0
- package/dist/cli/commands/routes-old.js +106 -0
- package/dist/cli/commands/routes.d.ts +11 -0
- package/dist/cli/commands/routes.js +280 -0
- package/dist/cli/commands/serve.d.ts +17 -0
- package/dist/cli/commands/serve.js +386 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +76 -0
- package/dist/cli/utils/index.d.ts +2 -0
- package/dist/cli/utils/index.js +2 -0
- package/dist/cli/utils/logger.d.ts +84 -0
- package/dist/cli/utils/logger.js +181 -0
- package/dist/cli/utils/port-finder.d.ts +8 -0
- package/dist/cli/utils/port-finder.js +48 -0
- package/dist/dev-server/codegen-runner.d.ts +41 -0
- package/dist/dev-server/codegen-runner.js +172 -0
- package/dist/dev-server/hot-reload.d.ts +72 -0
- package/dist/dev-server/hot-reload.js +280 -0
- package/dist/dev-server/index.d.ts +8 -0
- package/dist/dev-server/index.js +8 -0
- package/dist/dev-server/route-scanner.d.ts +84 -0
- package/dist/dev-server/route-scanner.js +113 -0
- package/dist/dev-server/rust-builder.d.ts +66 -0
- package/dist/dev-server/rust-builder.js +286 -0
- package/dist/dev-server/server.d.ts +147 -0
- package/dist/dev-server/server.js +660 -0
- package/dist/dev-server/vite-proxy.d.ts +56 -0
- package/dist/dev-server/vite-proxy.js +212 -0
- package/dist/dev-server/watcher.d.ts +48 -0
- package/dist/dev-server/watcher.js +127 -0
- package/dist/router/codegen-enhanced.d.ts +5 -0
- package/dist/router/codegen-enhanced.js +275 -0
- package/dist/router/codegen.d.ts +17 -0
- package/dist/router/codegen.js +654 -0
- package/dist/router/index.d.ts +16 -0
- package/dist/router/index.js +19 -0
- package/dist/router/scanner.d.ts +86 -0
- package/dist/router/scanner.js +689 -0
- package/dist/router/ssg.d.ts +115 -0
- package/dist/router/ssg.js +202 -0
- package/dist/router/types.d.ts +124 -0
- package/dist/router/types.js +9 -0
- package/dist/router/watch.d.ts +38 -0
- package/dist/router/watch.js +135 -0
- package/dist/runtime/csrf.d.ts +146 -0
- package/dist/runtime/csrf.js +166 -0
- package/dist/runtime/error-boundary.d.ts +129 -0
- package/dist/runtime/error-boundary.js +287 -0
- package/dist/runtime/hooks.d.ts +83 -0
- package/dist/runtime/hooks.js +96 -0
- package/dist/runtime/index.d.ts +229 -0
- package/dist/runtime/index.js +449 -0
- package/dist/runtime/ipc-client.d.ts +144 -0
- package/dist/runtime/ipc-client.js +621 -0
- package/dist/runtime/logger.d.ts +71 -0
- package/dist/runtime/logger.js +164 -0
- package/dist/runtime/middleware.d.ts +66 -0
- package/dist/runtime/middleware.js +114 -0
- package/dist/runtime/process-manager.d.ts +51 -0
- package/dist/runtime/process-manager.js +207 -0
- package/dist/runtime/router-simple.d.ts +98 -0
- package/dist/runtime/router-simple.js +330 -0
- package/dist/runtime/router.d.ts +103 -0
- package/dist/runtime/router.js +435 -0
- package/dist/runtime/rpc-client.d.ts +35 -0
- package/dist/runtime/rpc-client.js +140 -0
- package/dist/runtime/streaming-utils.d.ts +86 -0
- package/dist/runtime/streaming-utils.js +150 -0
- package/dist/runtime/types.d.ts +465 -0
- package/dist/runtime/types.js +60 -0
- package/dist/runtime/websockets-utils.d.ts +50 -0
- package/dist/runtime/websockets-utils.js +92 -0
- package/package.json +30 -20
- package/index.js +0 -29
- package/internal/cli/package.json +0 -46
- package/internal/cli/tsconfig.tsbuildinfo +0 -1
- package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
- package/internal/dev-server/node_modules/ora/index.js +0 -416
- package/internal/dev-server/node_modules/ora/license +0 -9
- package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
- package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
- package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
- package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
- package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
- package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
- package/internal/dev-server/node_modules/ora/package.json +0 -66
- package/internal/dev-server/node_modules/ora/readme.md +0 -325
- package/internal/dev-server/package.json +0 -41
- package/internal/router/package.json +0 -28
- package/internal/runtime/package.json +0 -41
- package/internal/runtime/src/error-boundary.tsx +0 -476
- package/internal/runtime/src/router-simple.tsx +0 -640
- package/internal/runtime/src/router.tsx +0 -771
- package/internal/runtime/tsconfig.tsbuildinfo +0 -1
- package/src/errors.js +0 -33
- package/src/logger.js +0 -10
- package/src/middleware.js +0 -32
- package/src/router.js +0 -41
- 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 {};
|