@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,621 @@
1
+ import { createServer, createConnection } from "net";
2
+ import { unlinkSync, existsSync } from "fs";
3
+ import { EventEmitter } from "events";
4
+ import { encode, decode } from "@msgpack/msgpack";
5
+ import { isAsyncIterable } from "./types.js";
6
+ /**
7
+ * Serialize an IPC message to bytes
8
+ */
9
+ function serializeMessage(msg, encoding) {
10
+ if (encoding === "msgpack") {
11
+ return Buffer.from(encode(msg));
12
+ }
13
+ return Buffer.from(JSON.stringify(msg));
14
+ }
15
+ /**
16
+ * Deserialize an IPC message from bytes (auto-detects encoding)
17
+ */
18
+ function deserializeMessage(data) {
19
+ if (data.length === 0) {
20
+ throw new Error("Empty message");
21
+ }
22
+ // Auto-detect: JSON starts with '{' (0x7B), MessagePack maps start with 0x80-0xBF
23
+ const firstByte = data[0];
24
+ if (firstByte === 0x7b) {
25
+ // '{' character = JSON
26
+ return JSON.parse(data.toString("utf-8"));
27
+ }
28
+ // MessagePack
29
+ return decode(data);
30
+ }
31
+ /**
32
+ * Write a length-prefixed message to a socket
33
+ */
34
+ function writeFramedMessage(socket, msg, encoding) {
35
+ const payload = serializeMessage(msg, encoding);
36
+ const length = payload.length;
37
+ // 4-byte big-endian length prefix
38
+ const lengthBuf = Buffer.alloc(4);
39
+ lengthBuf.writeUInt32BE(length, 0);
40
+ // ATOMIC: Single write with combined buffer to prevent frame corruption
41
+ const frame = Buffer.concat([lengthBuf, payload]);
42
+ socket.write(frame);
43
+ }
44
+ /**
45
+ * FrameReader - reads length-prefixed frames from a socket
46
+ */
47
+ class FrameReader {
48
+ constructor(onFrame) {
49
+ this.buffer = Buffer.alloc(0);
50
+ this.onFrame = onFrame;
51
+ }
52
+ /**
53
+ * Process incoming data chunks
54
+ */
55
+ push(chunk) {
56
+ this.buffer = Buffer.concat([this.buffer, chunk]);
57
+ // Process complete frames
58
+ while (this.buffer.length >= 4) {
59
+ const length = this.buffer.readUInt32BE(0);
60
+ // Check for unreasonably large messages (100MB limit)
61
+ if (length > 100 * 1024 * 1024) {
62
+ throw new Error(`Message too large: ${length} bytes`);
63
+ }
64
+ // Wait for complete frame
65
+ if (this.buffer.length < 4 + length) {
66
+ break;
67
+ }
68
+ // Extract frame
69
+ const frame = this.buffer.subarray(4, 4 + length);
70
+ this.buffer = this.buffer.subarray(4 + length);
71
+ // Emit frame
72
+ this.onFrame(frame);
73
+ }
74
+ }
75
+ /**
76
+ * Reset the buffer
77
+ */
78
+ reset() {
79
+ this.buffer = Buffer.alloc(0);
80
+ }
81
+ }
82
+ /**
83
+ * WebSocket connection implementation for TypeScript handlers
84
+ */
85
+ class WsConnectionImpl {
86
+ constructor(id, path, headers, handlerId, server) {
87
+ this.id = id;
88
+ this.path = path;
89
+ this.headers = headers;
90
+ this.handlerId = handlerId;
91
+ this.server = server;
92
+ }
93
+ /**
94
+ * Send a text message to the client
95
+ */
96
+ send(data) {
97
+ this.server.sendWsMessage(this.id, data, false);
98
+ }
99
+ /**
100
+ * Send binary data to the client
101
+ */
102
+ sendBinary(data) {
103
+ // Base64 encode binary data for IPC transport
104
+ const encoded = Buffer.from(data).toString("base64");
105
+ this.server.sendWsMessage(this.id, encoded, true);
106
+ }
107
+ /**
108
+ * Close the connection
109
+ */
110
+ close(code, reason) {
111
+ this.server.closeWsConnection(this.id, code, reason);
112
+ }
113
+ }
114
+ /**
115
+ * IpcServer
116
+ *
117
+ * Listens on a Unix socket for IPC messages from the Rust backend.
118
+ * The Rust server sends handler invocation requests, which we dispatch
119
+ * to the registered TypeScript handlers and send responses back.
120
+ *
121
+ * Protocol: Length-prefixed MessagePack (default) with JSON fallback
122
+ * Frame format: [4-byte big-endian length][payload]
123
+ */
124
+ export class IpcServer {
125
+ constructor(socketPath, encoding = "msgpack") {
126
+ this.server = null;
127
+ this.handlers = new Map();
128
+ this.wsHandlers = new Map();
129
+ this.wsConnections = new Map();
130
+ this.currentSocket = null;
131
+ this.socketPath = socketPath;
132
+ this.encoding = encoding;
133
+ }
134
+ /**
135
+ * Register a handler function for a specific handler ID
136
+ */
137
+ registerHandler(handlerId, handler) {
138
+ this.handlers.set(handlerId, handler);
139
+ }
140
+ /**
141
+ * Register a WebSocket handler for a specific handler ID
142
+ */
143
+ registerWsHandler(handlerId, handler) {
144
+ this.wsHandlers.set(handlerId, handler);
145
+ }
146
+ /**
147
+ * Start the IPC server listening on the Unix socket
148
+ */
149
+ async start() {
150
+ return new Promise((resolve, reject) => {
151
+ try {
152
+ // Clean up old socket file if it exists
153
+ if (existsSync(this.socketPath)) {
154
+ try {
155
+ unlinkSync(this.socketPath);
156
+ }
157
+ catch {
158
+ // Ignore if we can't delete it
159
+ }
160
+ }
161
+ // Create Unix domain socket server
162
+ this.server = createServer((socket) => {
163
+ this.handleConnection(socket);
164
+ });
165
+ this.server.on("error", (err) => {
166
+ console.error(`[IPC] Server error:`, err);
167
+ reject(err);
168
+ });
169
+ this.server.listen(this.socketPath, () => {
170
+ console.log(`[IPC] IPC server listening on ${this.socketPath} (${this.encoding})`);
171
+ resolve();
172
+ });
173
+ }
174
+ catch (error) {
175
+ reject(error);
176
+ }
177
+ });
178
+ }
179
+ /**
180
+ * Handle a new IPC connection from the Rust server
181
+ */
182
+ handleConnection(socket) {
183
+ console.log(`[IPC] Client connected`);
184
+ this.currentSocket = socket;
185
+ const frameReader = new FrameReader((frame) => {
186
+ void this.handleFrame(frame, socket);
187
+ });
188
+ socket.on("data", (chunk) => {
189
+ try {
190
+ frameReader.push(chunk);
191
+ }
192
+ catch (error) {
193
+ console.error(`[IPC] Frame error:`, error);
194
+ socket.destroy();
195
+ }
196
+ });
197
+ socket.on("close", () => {
198
+ console.log(`[IPC] Client disconnected`);
199
+ if (this.currentSocket === socket) {
200
+ this.currentSocket = null;
201
+ }
202
+ // Clean up any WebSocket connections for this socket
203
+ this.wsConnections.clear();
204
+ });
205
+ socket.on("error", (error) => {
206
+ console.error(`[IPC] Connection error:`, error);
207
+ });
208
+ }
209
+ /**
210
+ * Handle a complete frame
211
+ */
212
+ async handleFrame(frame, socket) {
213
+ try {
214
+ const message = deserializeMessage(frame);
215
+ await this.processMessage(message, socket);
216
+ }
217
+ catch (error) {
218
+ const errorMessage = error instanceof Error ? error.message : String(error);
219
+ console.error(`[IPC] Error processing message:`, errorMessage);
220
+ const errorResponse = {
221
+ type: "error",
222
+ code: "HANDLER_ERROR",
223
+ message: errorMessage,
224
+ status: 500,
225
+ digest: crypto.randomUUID(),
226
+ };
227
+ writeFramedMessage(socket, errorResponse, this.encoding);
228
+ }
229
+ }
230
+ /**
231
+ * Process an incoming IPC message
232
+ */
233
+ async processMessage(message, socket) {
234
+ console.log(`[IPC] Received message type: ${message.type}`);
235
+ if (message.type === "invoke_handler") {
236
+ const invokeMsg = message;
237
+ const { handler_id, request } = invokeMsg;
238
+ console.log(`[IPC] Looking for handler: ${handler_id}`);
239
+ console.log(`[IPC] Available handlers: ${Array.from(this.handlers.keys()).join(', ')}`);
240
+ const handler = this.handlers.get(handler_id);
241
+ if (!handler) {
242
+ console.error(`[IPC] Handler NOT FOUND: ${handler_id}`);
243
+ writeFramedMessage(socket, {
244
+ type: "error",
245
+ code: "HANDLER_NOT_FOUND",
246
+ message: `Handler ${handler_id} not found`,
247
+ status: 404,
248
+ digest: crypto.randomUUID(),
249
+ }, this.encoding);
250
+ return;
251
+ }
252
+ try {
253
+ console.log(`[IPC] Invoking handler: ${handler_id} for ${request.method} ${request.path}`);
254
+ const result = handler(request);
255
+ // Check if this is a streaming response (async iterable)
256
+ if (isAsyncIterable(result)) {
257
+ await this.handleStreamingResponse(result, handler_id, socket);
258
+ }
259
+ else {
260
+ // Regular response - await the promise
261
+ const response = await result;
262
+ console.log(`[IPC] Handler result:`, JSON.stringify(response, null, 2));
263
+ writeFramedMessage(socket, {
264
+ type: "handler_response",
265
+ handler_id,
266
+ status: response.status || 200,
267
+ headers: response.headers || { "content-type": "application/json" },
268
+ body: response.body || "{}",
269
+ }, this.encoding);
270
+ }
271
+ }
272
+ catch (error) {
273
+ const errorMessage = error instanceof Error ? error.message : String(error);
274
+ console.error(`[IPC] Error executing handler ${handler_id}:`, errorMessage);
275
+ writeFramedMessage(socket, {
276
+ type: "error",
277
+ code: "HANDLER_EXECUTION_ERROR",
278
+ message: errorMessage,
279
+ status: 500,
280
+ digest: crypto.randomUUID(),
281
+ }, this.encoding);
282
+ }
283
+ return;
284
+ }
285
+ // Health check message
286
+ if (message.type === "health_check") {
287
+ console.log(`[IPC] Health check received`);
288
+ writeFramedMessage(socket, { type: "health_check_response" }, this.encoding);
289
+ return;
290
+ }
291
+ // WebSocket connect message - new client connected
292
+ if (message.type === "ws_connect") {
293
+ const { connection_id, handler_id, path, headers } = message;
294
+ console.log(`[IPC] WebSocket connect: ${connection_id} for handler ${handler_id}`);
295
+ const wsHandler = this.wsHandlers.get(handler_id);
296
+ if (!wsHandler) {
297
+ console.error(`[IPC] WebSocket handler NOT FOUND: ${handler_id}`);
298
+ writeFramedMessage(socket, {
299
+ type: "error",
300
+ code: "WS_HANDLER_NOT_FOUND",
301
+ message: `WebSocket handler ${handler_id} not found`,
302
+ status: 404,
303
+ digest: crypto.randomUUID(),
304
+ }, this.encoding);
305
+ return;
306
+ }
307
+ // Create connection object and store it
308
+ const connection = new WsConnectionImpl(connection_id, path, headers, handler_id, this);
309
+ this.wsConnections.set(connection_id, connection);
310
+ // Call onConnect if defined
311
+ try {
312
+ if (wsHandler.onConnect) {
313
+ await wsHandler.onConnect(connection);
314
+ }
315
+ }
316
+ catch (error) {
317
+ const errorMessage = error instanceof Error ? error.message : String(error);
318
+ console.error(`[IPC] WebSocket onConnect error:`, errorMessage);
319
+ // Close the connection on error
320
+ this.closeWsConnection(connection_id, 1011, errorMessage);
321
+ }
322
+ return;
323
+ }
324
+ // WebSocket message - message from client
325
+ if (message.type === "ws_message") {
326
+ const { connection_id, data, binary } = message;
327
+ console.log(`[IPC] WebSocket message from ${connection_id}: ${data.length} bytes (binary: ${binary})`);
328
+ const connection = this.wsConnections.get(connection_id);
329
+ if (!connection) {
330
+ console.error(`[IPC] WebSocket connection NOT FOUND: ${connection_id}`);
331
+ return;
332
+ }
333
+ // Use handler_id from the connection (set during connect)
334
+ const wsHandler = this.wsHandlers.get(connection.handlerId);
335
+ if (!wsHandler || !wsHandler.onMessage) {
336
+ return;
337
+ }
338
+ try {
339
+ let messageData;
340
+ if (binary) {
341
+ // Decode base64 binary data
342
+ messageData = Buffer.from(data, "base64");
343
+ }
344
+ else {
345
+ messageData = data;
346
+ }
347
+ await wsHandler.onMessage(connection, messageData);
348
+ }
349
+ catch (error) {
350
+ const errorMessage = error instanceof Error ? error.message : String(error);
351
+ console.error(`[IPC] WebSocket onMessage error:`, errorMessage);
352
+ if (wsHandler.onError) {
353
+ try {
354
+ await wsHandler.onError(connection, error instanceof Error ? error : new Error(errorMessage));
355
+ }
356
+ catch {
357
+ // Ignore errors in error handler
358
+ }
359
+ }
360
+ }
361
+ return;
362
+ }
363
+ // WebSocket close - client disconnected
364
+ if (message.type === "ws_close") {
365
+ const { connection_id, code, reason } = message;
366
+ console.log(`[IPC] WebSocket close: ${connection_id} (code: ${code}, reason: ${reason})`);
367
+ const connection = this.wsConnections.get(connection_id);
368
+ if (!connection) {
369
+ return;
370
+ }
371
+ // Use handler_id from the connection (set during connect)
372
+ const wsHandler = this.wsHandlers.get(connection.handlerId);
373
+ if (wsHandler && wsHandler.onClose) {
374
+ try {
375
+ await wsHandler.onClose(connection, code, reason);
376
+ }
377
+ catch (error) {
378
+ const errorMessage = error instanceof Error ? error.message : String(error);
379
+ console.error(`[IPC] WebSocket onClose error:`, errorMessage);
380
+ }
381
+ }
382
+ // Remove from connections map
383
+ this.wsConnections.delete(connection_id);
384
+ return;
385
+ }
386
+ console.error(`[IPC] Unknown message type: ${message.type}`);
387
+ writeFramedMessage(socket, {
388
+ type: "error",
389
+ code: "UNKNOWN_MESSAGE_TYPE",
390
+ message: `Unknown message type: ${message.type}`,
391
+ status: 400,
392
+ digest: crypto.randomUUID(),
393
+ }, this.encoding);
394
+ }
395
+ /**
396
+ * Handle a streaming response from a handler
397
+ */
398
+ async handleStreamingResponse(stream, handlerId, socket) {
399
+ const streamId = `stream_${Date.now()}_${Math.random().toString(36).substring(7)}`;
400
+ console.log(`[IPC] Starting streaming response: ${streamId}`);
401
+ // Send stream start message
402
+ writeFramedMessage(socket, {
403
+ type: "stream_start",
404
+ stream_id: streamId,
405
+ status: 200,
406
+ headers: { "content-type": "text/event-stream" },
407
+ }, this.encoding);
408
+ try {
409
+ // Stream chunks
410
+ for await (const chunk of stream) {
411
+ let data;
412
+ if (chunk.bytes) {
413
+ // Binary data - base64 encode
414
+ data = Buffer.from(chunk.bytes).toString("base64");
415
+ }
416
+ else if (chunk.data) {
417
+ // String data - base64 encode for consistency
418
+ data = Buffer.from(chunk.data, "utf-8").toString("base64");
419
+ }
420
+ else {
421
+ continue; // Skip empty chunks
422
+ }
423
+ writeFramedMessage(socket, {
424
+ type: "stream_chunk",
425
+ stream_id: streamId,
426
+ data,
427
+ }, this.encoding);
428
+ }
429
+ // Send stream end message
430
+ writeFramedMessage(socket, {
431
+ type: "stream_end",
432
+ stream_id: streamId,
433
+ }, this.encoding);
434
+ console.log(`[IPC] Streaming response completed: ${streamId}`);
435
+ }
436
+ catch (error) {
437
+ const errorMessage = error instanceof Error ? error.message : String(error);
438
+ console.error(`[IPC] Streaming error for ${handlerId}:`, errorMessage);
439
+ // Send error message
440
+ writeFramedMessage(socket, {
441
+ type: "error",
442
+ code: "STREAM_ERROR",
443
+ message: errorMessage,
444
+ status: 500,
445
+ digest: crypto.randomUUID(),
446
+ }, this.encoding);
447
+ }
448
+ }
449
+ /**
450
+ * Send a message to a WebSocket client via the Rust server
451
+ */
452
+ sendWsMessage(connectionId, data, binary) {
453
+ if (!this.currentSocket) {
454
+ console.error(`[IPC] Cannot send WebSocket message: no active socket`);
455
+ return;
456
+ }
457
+ writeFramedMessage(this.currentSocket, {
458
+ type: "ws_send",
459
+ connection_id: connectionId,
460
+ data,
461
+ binary,
462
+ }, this.encoding);
463
+ }
464
+ /**
465
+ * Close a WebSocket connection via the Rust server
466
+ */
467
+ closeWsConnection(connectionId, code, reason) {
468
+ // Remove from connections map
469
+ const connection = this.wsConnections.get(connectionId);
470
+ const handlerId = connection?.handlerId || "";
471
+ if (connection) {
472
+ this.wsConnections.delete(connectionId);
473
+ }
474
+ if (!this.currentSocket) {
475
+ console.error(`[IPC] Cannot close WebSocket connection: no active socket`);
476
+ return;
477
+ }
478
+ writeFramedMessage(this.currentSocket, {
479
+ type: "ws_close",
480
+ connection_id: connectionId,
481
+ handler_id: handlerId,
482
+ code,
483
+ reason,
484
+ }, this.encoding);
485
+ }
486
+ /**
487
+ * Stop the IPC server
488
+ */
489
+ async stop() {
490
+ // Close all WebSocket connections
491
+ for (const [connectionId] of this.wsConnections) {
492
+ this.closeWsConnection(connectionId, 1001, "Server shutting down");
493
+ }
494
+ this.wsConnections.clear();
495
+ if (this.server) {
496
+ return new Promise((resolve) => {
497
+ this.server.close(() => {
498
+ // Clean up socket file
499
+ if (existsSync(this.socketPath)) {
500
+ try {
501
+ unlinkSync(this.socketPath);
502
+ }
503
+ catch {
504
+ // Ignore
505
+ }
506
+ }
507
+ resolve();
508
+ });
509
+ });
510
+ }
511
+ }
512
+ }
513
+ /**
514
+ * IpcClient
515
+ *
516
+ * Connects to a Unix socket to communicate with the Rust server.
517
+ * Used for RPC calls from TypeScript to Rust.
518
+ *
519
+ * Protocol: Length-prefixed MessagePack (default) with JSON fallback
520
+ */
521
+ export class IpcClient extends EventEmitter {
522
+ constructor(socketPath, encoding = "msgpack") {
523
+ super();
524
+ this.socket = null;
525
+ this.connected = false;
526
+ this.frameReader = null;
527
+ this.socketPath = socketPath;
528
+ this.encoding = encoding;
529
+ this.connect();
530
+ }
531
+ /**
532
+ * Connect to the Unix socket
533
+ */
534
+ connect() {
535
+ this.socket = createConnection(this.socketPath);
536
+ this.socket.on("connect", () => {
537
+ this.connected = true;
538
+ this.emit("connect");
539
+ // Set up frame reader for length-prefixed messages
540
+ this.frameReader = new FrameReader((frame) => {
541
+ try {
542
+ const message = deserializeMessage(frame);
543
+ this.emit("message", message);
544
+ }
545
+ catch (error) {
546
+ this.emit("error", new Error(`Failed to deserialize message: ${error}`));
547
+ }
548
+ });
549
+ });
550
+ this.socket.on("data", (chunk) => {
551
+ if (this.frameReader) {
552
+ try {
553
+ this.frameReader.push(chunk);
554
+ }
555
+ catch (error) {
556
+ this.emit("error", error);
557
+ }
558
+ }
559
+ });
560
+ this.socket.on("error", (err) => {
561
+ this.connected = false;
562
+ this.emit("error", err);
563
+ });
564
+ this.socket.on("close", () => {
565
+ this.connected = false;
566
+ this.emit("close");
567
+ });
568
+ }
569
+ /**
570
+ * Send a message to the server
571
+ */
572
+ send(message) {
573
+ if (!this.socket || !this.connected) {
574
+ throw new Error("IPC client not connected");
575
+ }
576
+ writeFramedMessage(this.socket, message, this.encoding);
577
+ }
578
+ /**
579
+ * Send a message and wait for response
580
+ */
581
+ async sendRecv(message) {
582
+ return new Promise((resolve, reject) => {
583
+ const timeout = setTimeout(() => {
584
+ reject(new Error("IPC request timeout"));
585
+ }, 30000);
586
+ const handler = (response) => {
587
+ clearTimeout(timeout);
588
+ this.removeListener("message", handler);
589
+ resolve(response);
590
+ };
591
+ this.on("message", handler);
592
+ this.send(message);
593
+ });
594
+ }
595
+ /**
596
+ * Close the connection
597
+ */
598
+ async close() {
599
+ this.frameReader = null;
600
+ if (this.socket) {
601
+ return new Promise((resolve) => {
602
+ this.socket.once("close", resolve);
603
+ this.socket.end();
604
+ });
605
+ }
606
+ }
607
+ /**
608
+ * Check if connected
609
+ */
610
+ isConnected() {
611
+ return this.connected;
612
+ }
613
+ /**
614
+ * Get the encoding being used
615
+ */
616
+ getEncoding() {
617
+ return this.encoding;
618
+ }
619
+ }
620
+ // Export serialization utilities for testing
621
+ export { serializeMessage, deserializeMessage, FrameReader };
@@ -0,0 +1,71 @@
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
+ export interface LogContext {
8
+ request_id?: string;
9
+ handler_id?: string;
10
+ method?: string;
11
+ path?: string;
12
+ duration_ms?: number;
13
+ status?: number;
14
+ [key: string]: unknown;
15
+ }
16
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
17
+ declare class Logger {
18
+ private jsonFormat;
19
+ private minLevel;
20
+ constructor();
21
+ private shouldLog;
22
+ private formatEntry;
23
+ private log;
24
+ trace(message: string, context?: LogContext): void;
25
+ debug(message: string, context?: LogContext): void;
26
+ info(message: string, context?: LogContext): void;
27
+ warn(message: string, context?: LogContext): void;
28
+ error(message: string, context?: LogContext, error?: Error): void;
29
+ /**
30
+ * Create a child logger with pre-set context
31
+ */
32
+ child(baseContext: LogContext): ChildLogger;
33
+ /**
34
+ * Set JSON format mode
35
+ */
36
+ setJsonFormat(enabled: boolean): void;
37
+ /**
38
+ * Set minimum log level
39
+ */
40
+ setMinLevel(level: LogLevel): void;
41
+ /**
42
+ * Get current configuration
43
+ */
44
+ getConfig(): {
45
+ jsonFormat: boolean;
46
+ minLevel: LogLevel;
47
+ };
48
+ }
49
+ /**
50
+ * Child logger with inherited base context
51
+ */
52
+ declare class ChildLogger {
53
+ private parent;
54
+ private baseContext;
55
+ constructor(parent: Logger, baseContext: LogContext);
56
+ private mergeContext;
57
+ trace(message: string, context?: LogContext): void;
58
+ debug(message: string, context?: LogContext): void;
59
+ info(message: string, context?: LogContext): void;
60
+ warn(message: string, context?: LogContext): void;
61
+ error(message: string, context?: LogContext, error?: Error): void;
62
+ /**
63
+ * Create a further nested child logger
64
+ */
65
+ child(additionalContext: LogContext): ChildLogger;
66
+ }
67
+ /**
68
+ * Global logger instance
69
+ */
70
+ export declare const logger: Logger;
71
+ export { Logger, ChildLogger };