@bakit/service 3.2.1 → 4.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/dist/index.cjs ADDED
@@ -0,0 +1,757 @@
1
+ 'use strict';
2
+
3
+ var EventEmitter = require('events');
4
+ var net = require('net');
5
+ var utils = require('@bakit/utils');
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+ var crypto = require('crypto');
9
+ var child_process = require('child_process');
10
+ var url = require('url');
11
+
12
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
+
15
+ var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
16
+
17
+ // src/lib/FrameCodec.ts
18
+ var SUPPORTED_LENGTH_BYTES = [1, 2, 4, 8], DEFAULT_OPTIONS = {
19
+ maxBufferSize: 16 * 1024 * 1024,
20
+ maxFrameSize: 16 * 1024 * 1024,
21
+ lengthBytes: 4,
22
+ endian: "big",
23
+ lengthIncludesHeader: false
24
+ }, FrameCodec = class _FrameCodec {
25
+ buffer = Buffer.alloc(0);
26
+ options;
27
+ stats = {
28
+ bytesReceived: 0,
29
+ bytesDecoded: 0,
30
+ framesDecoded: 0,
31
+ bufferCopies: 0
32
+ };
33
+ constructor(options = {}) {
34
+ if (this.options = { ...DEFAULT_OPTIONS, ...options }, !SUPPORTED_LENGTH_BYTES.includes(this.options.lengthBytes))
35
+ throw new Error("lengthBytes must be 1, 2, 4, or 8");
36
+ }
37
+ push(chunk) {
38
+ if (chunk.length === 0) return [];
39
+ if (this.stats.bytesReceived += chunk.length, this.buffer.length + chunk.length > this.options.maxBufferSize)
40
+ throw this.reset(), new Error(`Buffer overflow: ${this.buffer.length + chunk.length} > ${this.options.maxBufferSize}`);
41
+ this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
42
+ let frames = [], headerSize = this.options.lengthBytes;
43
+ for (; this.buffer.length >= headerSize; ) {
44
+ let lengthField = this.readLength(this.buffer), [payloadLength, frameLength] = this.options.lengthIncludesHeader ? [lengthField - headerSize, lengthField] : [lengthField, headerSize + lengthField];
45
+ if (payloadLength < 0 || payloadLength > this.options.maxFrameSize)
46
+ throw this.reset(), new Error(`Invalid frame size: ${payloadLength}`);
47
+ if (this.buffer.length < frameLength)
48
+ break;
49
+ frames.push(this.buffer.subarray(headerSize, frameLength)), this.stats.framesDecoded++, this.stats.bytesDecoded += payloadLength, this.buffer = frameLength === this.buffer.length ? Buffer.alloc(0) : this.buffer.subarray(frameLength);
50
+ }
51
+ return this.buffer.byteOffset > 1024 * 1024 && (this.buffer = Buffer.from(this.buffer), this.stats.bufferCopies++), frames;
52
+ }
53
+ get bufferedBytes() {
54
+ return this.buffer.length;
55
+ }
56
+ reset() {
57
+ this.buffer = Buffer.alloc(0), this.stats.bytesReceived = 0, this.stats.bytesDecoded = 0, this.stats.framesDecoded = 0, this.stats.bufferCopies = 0;
58
+ }
59
+ readLength(buf) {
60
+ let big = this.options.endian === "big";
61
+ switch (this.options.lengthBytes) {
62
+ case 1:
63
+ return buf.readUInt8(0);
64
+ case 2:
65
+ return big ? buf.readUInt16BE(0) : buf.readUInt16LE(0);
66
+ case 4:
67
+ return big ? buf.readUInt32BE(0) : buf.readUInt32LE(0);
68
+ case 8: {
69
+ let val = big ? buf.readBigUInt64BE(0) : buf.readBigUInt64LE(0);
70
+ if (val > BigInt(Number.MAX_SAFE_INTEGER))
71
+ throw new Error("Frame size exceeds safe integer range");
72
+ return Number(val);
73
+ }
74
+ }
75
+ }
76
+ encode(payload) {
77
+ return _FrameCodec.encode(payload, this.options);
78
+ }
79
+ static encode(payload, options) {
80
+ let opts = { ...DEFAULT_OPTIONS, ...options }, { lengthBytes, endian, lengthIncludesHeader } = opts, headerSize = lengthBytes, payloadSize = payload.length, totalFrameSize = lengthIncludesHeader ? headerSize + payloadSize : payloadSize, maxLength = 2 ** (lengthBytes * 8) - 1;
81
+ if (totalFrameSize > maxLength)
82
+ throw new Error(`Frame size ${totalFrameSize} exceeds maximum ${maxLength} for ${lengthBytes}-byte length field`);
83
+ let header = Buffer.allocUnsafe(headerSize), big = endian === "big";
84
+ switch (lengthBytes) {
85
+ case 1:
86
+ header.writeUInt8(totalFrameSize, 0);
87
+ break;
88
+ case 2:
89
+ header[big ? "writeUInt16BE" : "writeUInt16LE"](totalFrameSize, 0);
90
+ break;
91
+ case 4:
92
+ header[big ? "writeUInt32BE" : "writeUInt32LE"](totalFrameSize, 0);
93
+ break;
94
+ case 8: {
95
+ header[big ? "writeBigUInt64BE" : "writeBigUInt64LE"](BigInt(totalFrameSize), 0);
96
+ break;
97
+ }
98
+ default:
99
+ throw new Error(`Unsupported lengthBytes: ${lengthBytes}`);
100
+ }
101
+ return Buffer.concat([header, payload]);
102
+ }
103
+ static serialize(obj) {
104
+ return Buffer.from(JSON.stringify(obj));
105
+ }
106
+ static deserialize(buf) {
107
+ return JSON.parse(buf.toString());
108
+ }
109
+ };
110
+ var BaseDriver = class extends EventEmitter__default.default {
111
+ constructor(options) {
112
+ super();
113
+ this.options = options;
114
+ }
115
+ }, BaseClientDriver = class extends BaseDriver {
116
+ constructor(options) {
117
+ super(options);
118
+ }
119
+ }, BaseServerDriver = class extends BaseDriver {
120
+ constructor(options) {
121
+ super(options);
122
+ }
123
+ };
124
+ var WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\", UNIX_SOCKET_DIR = "/tmp";
125
+ function getIPCPath(id, platform = process.platform) {
126
+ switch (platform) {
127
+ case "win32":
128
+ return `${WINDOWS_PIPE_PREFIX}${id}`;
129
+ default:
130
+ return path.join(UNIX_SOCKET_DIR, `${id}.sock`);
131
+ }
132
+ }
133
+ function isServerRunning(path) {
134
+ return new Promise((resolve2) => {
135
+ if (!fs.existsSync(path)) {
136
+ resolve2(false);
137
+ return;
138
+ }
139
+ let socket = net.createConnection(path), timer = setTimeout(() => {
140
+ socket.destroy(), resolve2(false);
141
+ }, 500);
142
+ socket.once("connect", () => {
143
+ clearTimeout(timer), socket.destroy(), resolve2(true);
144
+ }), socket.once("error", () => {
145
+ clearTimeout(timer), socket.destroy(), resolve2(false);
146
+ });
147
+ });
148
+ }
149
+
150
+ // src/lib/drivers/ipc/IPCClient.ts
151
+ var IPCClientState = /* @__PURE__ */ ((IPCClientState2) => (IPCClientState2[IPCClientState2.Idle = 0] = "Idle", IPCClientState2[IPCClientState2.Connecting = 1] = "Connecting", IPCClientState2[IPCClientState2.Connected = 2] = "Connected", IPCClientState2[IPCClientState2.Disconnected = 3] = "Disconnected", IPCClientState2))(IPCClientState || {}), DEFAULT_IPC_CLIENT_RECONNECT_OPTIONS = {
152
+ enabled: true,
153
+ maxRetries: 5,
154
+ initialDelay: 1e3,
155
+ backoff: 1.5,
156
+ maxDelay: 3e4
157
+ }, IPCClient = class extends BaseClientDriver {
158
+ socket;
159
+ codec;
160
+ state = 0 /* Idle */;
161
+ _ready = false;
162
+ reconnectTimer;
163
+ reconnectOptions;
164
+ reconnectAttempt = 0;
165
+ isIntentionalClose = false;
166
+ queue = new utils.Queue({
167
+ concurrency: 1,
168
+ autoStart: false
169
+ });
170
+ constructor(options) {
171
+ super(options), this.codec = new FrameCodec(options.codec), this.reconnectOptions = { ...DEFAULT_IPC_CLIENT_RECONNECT_OPTIONS, ...options.reconnect };
172
+ }
173
+ get path() {
174
+ return getIPCPath(this.options.id);
175
+ }
176
+ get ready() {
177
+ return this._ready;
178
+ }
179
+ connect() {
180
+ if (this.socket)
181
+ throw new Error(`Socket is already connected to '${this.options.id}'.`);
182
+ return new Promise((resolve2, reject) => {
183
+ this.once("connect", resolve2), this.init().catch(reject);
184
+ });
185
+ }
186
+ send(message) {
187
+ return this.queue.add(() => this.makeMessage(message));
188
+ }
189
+ disconnect() {
190
+ return new Promise((resolve2) => {
191
+ if (!this.socket || this.state === 0 /* Idle */) {
192
+ resolve2();
193
+ return;
194
+ }
195
+ this.once("disconnect", resolve2), this.isIntentionalClose = true, this.cleanup(true);
196
+ });
197
+ }
198
+ async init() {
199
+ if (this.socket)
200
+ throw new Error(`Socket is already connected to '${this.options.id}'.`);
201
+ this.state = 1 /* Connecting */, this.socket = net.createConnection(this.path), this.socket.on("connect", () => this.onSocketConnect()), this.socket.on("data", (data) => this.onSocketData(data)), this.socket.on("close", () => this.onSocketClose()), this.socket.on("error", (err) => this.onSocketError(err));
202
+ }
203
+ cleanup(hard = false) {
204
+ this.reconnectAttempt = 0, this.clearReconnectTimer(), this.queue.pause(), hard && this.queue.clear(), this.socket && (this.socket.destroyed || this.socket.destroy(), this.socket = void 0), this.state = 3 /* Disconnected */;
205
+ }
206
+ onSocketConnect() {
207
+ this.state = 2 /* Connected */;
208
+ let isReconnected = this.reconnectAttempt > 0 && this.ready;
209
+ this.reconnectAttempt = 0, this._ready = true, this.isIntentionalClose = false, this.queue.start(), isReconnected ? this.emit("reconnect") : this.emit("connect");
210
+ }
211
+ onSocketClose() {
212
+ if (this.isIntentionalClose) {
213
+ this.emit("disconnect");
214
+ return;
215
+ }
216
+ let shouldReconnect = this.reconnectOptions.enabled && (this.state === 2 /* Connected */ || this.reconnectAttempt < this.reconnectOptions.maxRetries);
217
+ this.cleanup(), shouldReconnect ? this.scheduleReconnect() : this.emit("disconnect");
218
+ }
219
+ onSocketError(err) {
220
+ "code" in err && (err.code === "ECONNREFUSED" || err.code === "ENOENT") || this.emit("error", err);
221
+ }
222
+ onSocketData(chunk) {
223
+ let packets = this.codec.push(chunk);
224
+ for (let packet of packets) {
225
+ let message = this.deserialize(packet);
226
+ this.emit("message", message);
227
+ }
228
+ }
229
+ scheduleReconnect() {
230
+ if (this.reconnectAttempt >= this.reconnectOptions.maxRetries) {
231
+ this.emit("error", new Error(`Reconnect failed after ${this.reconnectOptions.maxRetries} attempts`)), this.disconnect();
232
+ return;
233
+ }
234
+ let delay = Math.min(
235
+ this.reconnectOptions.initialDelay * Math.pow(this.reconnectOptions.backoff, this.reconnectAttempt),
236
+ this.reconnectOptions.maxDelay
237
+ );
238
+ this.reconnectAttempt++, this.emit("reconnecting", this.reconnectAttempt, delay), this.reconnectTimer = setTimeout(() => {
239
+ this.init().catch((err) => {
240
+ this.emit("error", err);
241
+ });
242
+ }, delay);
243
+ }
244
+ clearReconnectTimer() {
245
+ this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.reconnectTimer = void 0);
246
+ }
247
+ makeMessage(message) {
248
+ let { socket } = this;
249
+ if (!socket || socket.destroyed)
250
+ return Promise.reject(new Error("Socket not available"));
251
+ let payload = this.serialize(message), frame = this.codec.encode(payload);
252
+ return new Promise((resolve2, reject) => {
253
+ socket.write(frame, (err) => {
254
+ err ? reject(err) : resolve2();
255
+ });
256
+ });
257
+ }
258
+ serialize(obj) {
259
+ return Buffer.from(JSON.stringify(obj));
260
+ }
261
+ deserialize(buf) {
262
+ return JSON.parse(buf.toString());
263
+ }
264
+ };
265
+ function createIPCClient(options) {
266
+ return new IPCClient(options);
267
+ }
268
+ var IPCConnection = class extends EventEmitter__default.default {
269
+ constructor(server, socket) {
270
+ super();
271
+ this.server = server;
272
+ this.socket = socket;
273
+ this.codec = new FrameCodec(server.codecOptions), this.setupListeners();
274
+ }
275
+ codec;
276
+ send(message) {
277
+ return this.server.sendSocket(this.socket, message);
278
+ }
279
+ destroy() {
280
+ this.socket.destroy();
281
+ }
282
+ setupListeners() {
283
+ this.socket.on("data", (chunk) => this.handleData(chunk)), this.socket.on("close", () => this.emit("close")), this.socket.on("error", (err) => this.emit("error", err));
284
+ }
285
+ handleData(chunk) {
286
+ try {
287
+ let packets = this.codec.push(chunk);
288
+ for (let packet of packets) {
289
+ let message = FrameCodec.deserialize(packet);
290
+ this.emit("message", message);
291
+ }
292
+ } catch (err) {
293
+ this.emit("error", err);
294
+ }
295
+ }
296
+ };
297
+ var IPCServer = class extends BaseServerDriver {
298
+ server;
299
+ codecOptions;
300
+ connections = new utils.Collection();
301
+ codec;
302
+ constructor(options) {
303
+ super(options), this.codecOptions = options.codec ?? {}, this.codec = new FrameCodec(this.codecOptions);
304
+ }
305
+ get path() {
306
+ return getIPCPath(this.options.id);
307
+ }
308
+ async listen() {
309
+ if (this.server)
310
+ throw new Error(`Server '${this.options.id}' is already listening`);
311
+ let { path } = this;
312
+ if (await isServerRunning(path))
313
+ throw new Error(`Server '${this.options.id}' is already running at ${path}`);
314
+ if (process.platform !== "win32")
315
+ try {
316
+ fs.unlinkSync(path);
317
+ } catch {
318
+ }
319
+ return new Promise((resolve2, reject) => {
320
+ this.server = net.createServer((socket) => this.handleConnection(socket)), this.server.on("error", (err) => {
321
+ err.code === "EADDRINUSE" ? reject(new Error(`Address already in use: ${path}. Is another server running?`)) : reject(err);
322
+ }), this.server.listen(path, () => {
323
+ this.emit("listen"), resolve2();
324
+ });
325
+ });
326
+ }
327
+ handleConnection(socket) {
328
+ let connection = new IPCConnection(this, socket);
329
+ this.connections.set(socket, connection), this.emit("connectionAdd", connection), connection.on("message", (message) => this.emit("message", connection, message)), connection.on("error", (err) => this.emit("connectionError", connection, err)), connection.on("close", () => {
330
+ this.connections.delete(socket), this.emit("connectionRemove", connection);
331
+ });
332
+ }
333
+ send(connection, message) {
334
+ return connection.send(message);
335
+ }
336
+ /**
337
+ * Send message to a specific client
338
+ */
339
+ sendSocket(socket, message) {
340
+ let payload = FrameCodec.serialize(message), frame = this.codec.encode(payload);
341
+ return this.sendSocketFrame(socket, frame);
342
+ }
343
+ /**
344
+ * Broadcast to all connected clients
345
+ * Returns count of successful sends (fire-and-forget, errors emitted via 'clientError')
346
+ */
347
+ async broadcast(message) {
348
+ let payload = FrameCodec.serialize(message), frame = this.codec.encode(payload);
349
+ await Promise.all(this.connections.map((_, socket) => this.sendSocketFrame(socket, frame)));
350
+ }
351
+ /**
352
+ * Close server and disconnect all clients
353
+ */
354
+ close() {
355
+ return new Promise((resolve2) => {
356
+ for (let connection of this.connections.values())
357
+ connection.destroy();
358
+ if (this.connections.clear(), !this.server) {
359
+ resolve2();
360
+ return;
361
+ }
362
+ this.server.close(() => {
363
+ if (this.server = void 0, process.platform !== "win32")
364
+ try {
365
+ fs.unlinkSync(this.path);
366
+ } catch {
367
+ }
368
+ this.emit("close"), resolve2();
369
+ });
370
+ });
371
+ }
372
+ sendSocketFrame(socket, frame) {
373
+ return new Promise((resolve2, reject) => {
374
+ if (socket.destroyed) {
375
+ reject(new Error("Socket destroyed"));
376
+ return;
377
+ }
378
+ socket.write(frame, (err) => {
379
+ err ? reject(err) : resolve2();
380
+ });
381
+ });
382
+ }
383
+ };
384
+ function createIPCServer(options) {
385
+ return new IPCServer(options);
386
+ }
387
+ var STANDARD_ERRORS = {
388
+ Error,
389
+ TypeError,
390
+ RangeError,
391
+ URIError,
392
+ SyntaxError,
393
+ ReferenceError,
394
+ EvalError
395
+ };
396
+ function serializeRPCError(err) {
397
+ let serialized = {
398
+ ...utils.instanceToObject(err),
399
+ message: err.message,
400
+ constructorName: err.constructor.name,
401
+ name: err.constructor.name,
402
+ stack: err.stack
403
+ };
404
+ return err.cause instanceof Error && (serialized.cause = serializeRPCError(err.cause)), err instanceof AggregateError && Array.isArray(err.errors) && (serialized.errors = err.errors.map((e) => serializeRPCError(e))), serialized;
405
+ }
406
+ function createDynamicRPCError(data) {
407
+ let { constructorName, name, message, stack, cause, errors, ...customProps } = data;
408
+ validateConstructorName(constructorName);
409
+ let error = instantiateError(constructorName, message, errors);
410
+ return Object.assign(error, customProps), name && setProperty(error, "name", name), stack && setProperty(error, "stack", stack), cause && typeof cause == "object" && setProperty(error, "cause", createDynamicRPCError(cause)), error;
411
+ }
412
+ function validateConstructorName(name) {
413
+ if (!name || typeof name != "string")
414
+ throw new TypeError("Invalid constructorName in serialized error");
415
+ if (name.length > 100 || /[<>\\{}]/.test(name))
416
+ throw new TypeError(`Suspicious constructorName: ${name}`);
417
+ if (["__proto__", "constructor", "prototype"].includes(name))
418
+ throw new Error(`Forbidden constructor name: ${name}`);
419
+ }
420
+ function instantiateError(constructorName, message, errors) {
421
+ if (constructorName === "AggregateError") {
422
+ let childErrors = Array.isArray(errors) ? errors.map((e) => isSerializedError(e) ? createDynamicRPCError(e) : new Error(String(e))) : [];
423
+ return new AggregateError(childErrors, message);
424
+ }
425
+ if (constructorName in STANDARD_ERRORS) {
426
+ let Constructor = STANDARD_ERRORS[constructorName];
427
+ return new Constructor(message);
428
+ }
429
+ let CustomError = { [constructorName]: class extends Error {
430
+ } }[constructorName];
431
+ return new CustomError(message);
432
+ }
433
+ function setProperty(error, key, value) {
434
+ Object.defineProperty(error, key, { value, configurable: true, writable: true });
435
+ }
436
+ function isSerializedError(value) {
437
+ return typeof value == "object" && value !== null && "constructorName" in value && "message" in value;
438
+ }
439
+
440
+ // src/lib/transport/TransportClient.ts
441
+ var TransportClient = class extends EventEmitter__default.default {
442
+ constructor(driver) {
443
+ super();
444
+ this.driver = driver;
445
+ this.setupDriverListeners();
446
+ }
447
+ pending = new utils.Collection();
448
+ connect() {
449
+ return this.driver.connect();
450
+ }
451
+ disconnect() {
452
+ for (let req of this.pending.values())
453
+ clearTimeout(req.timeout), req.reject(new Error(`Disconnected while waiting for response to "${req.method}"`));
454
+ return this.pending.clear(), this.driver.disconnect();
455
+ }
456
+ request(method, ...args) {
457
+ if (!this.driver.ready)
458
+ throw new Error("Transport driver is not ready");
459
+ let id = crypto.randomUUID(), clientStack = this.captureCallStack();
460
+ return new Promise((resolve2, reject) => {
461
+ let timeout = setTimeout(() => {
462
+ this.pending.delete(id), reject(new Error(`Request to "${method}" timed out`));
463
+ }, 5e3);
464
+ this.pending.set(id, {
465
+ resolve: resolve2,
466
+ reject,
467
+ timeout,
468
+ method,
469
+ clientStack
470
+ }), Promise.resolve(
471
+ this.driver.send({
472
+ type: "request",
473
+ id,
474
+ method,
475
+ args
476
+ })
477
+ ).catch((err) => {
478
+ clearTimeout(timeout), this.pending.delete(id), reject(err);
479
+ });
480
+ });
481
+ }
482
+ send(message) {
483
+ return this.driver.send(message);
484
+ }
485
+ setupDriverListeners() {
486
+ this.driver.on("connect", () => this.emit("connect")), this.driver.on("disconnect", () => this.emit("disconnect")), this.driver.on("error", (err) => this.emit("error", err)), this.driver.on("message", (msg) => this.handleMessage(msg));
487
+ }
488
+ handleMessage(message) {
489
+ if (this.emit("message", message), !this.isResponseMessage(message))
490
+ return;
491
+ let pending = this.pending.get(message.id);
492
+ pending && (clearTimeout(pending.timeout), this.pending.delete(message.id), message.error ? pending.reject(this.hydrateError(message.error, pending.clientStack)) : pending.resolve(message.result));
493
+ }
494
+ hydrateError(errorData, callerStack) {
495
+ let err = createDynamicRPCError(errorData), serverStack = err.stack?.split(`
496
+ `).slice(1).join(`
497
+ `) ?? "", separator = `
498
+ --- Error originated on RPC server ---
499
+ `;
500
+ return err.stack = `${err}
501
+ ${callerStack}${separator}${serverStack}`, err;
502
+ }
503
+ captureCallStack() {
504
+ let dummy = new Error();
505
+ return dummy.stack ? dummy.stack.split(`
506
+ `).slice(2).join(`
507
+ `) : "";
508
+ }
509
+ isResponseMessage(message) {
510
+ if (!utils.isPlainObject(message))
511
+ return false;
512
+ let hasType = "type" in message && message.type === "response", hasId = "id" in message && typeof message.id == "string", hasResult = "result" in message, hasError = "error" in message && message.error !== void 0;
513
+ return hasType && hasId && hasResult !== hasError;
514
+ }
515
+ };
516
+ function createTransportClient(driver) {
517
+ return new TransportClient(driver);
518
+ }
519
+ var TransportServer = class extends EventEmitter__default.default {
520
+ constructor(driver) {
521
+ super();
522
+ this.driver = driver;
523
+ this.setupDriverListeners();
524
+ }
525
+ handlers = /* @__PURE__ */ new Map();
526
+ get connections() {
527
+ return this.driver.connections;
528
+ }
529
+ handle(method, handler) {
530
+ if (this.handlers.has(method))
531
+ throw new Error(`Method ${method} is already registered`);
532
+ this.handlers.set(method, handler);
533
+ }
534
+ listen() {
535
+ return this.driver.listen();
536
+ }
537
+ close() {
538
+ return this.driver.close();
539
+ }
540
+ broadcast(message) {
541
+ return this.driver.broadcast(message);
542
+ }
543
+ setupDriverListeners() {
544
+ this.driver.on("listen", () => this.emit("listen")), this.driver.on("close", () => this.emit("close")), this.driver.on("error", (err) => this.emit("error", err)), this.driver.on("connectionAdd", (conn) => this.emit("connectionAdd", conn)), this.driver.on("connectionRemove", (conn) => this.emit("connectionRemove", conn)), this.driver.on("connectionError", (conn, err) => this.emit("connectionError", conn, err)), this.driver.on("message", (conn, msg) => this.handleMessage(conn, msg));
545
+ }
546
+ async handleMessage(connection, message) {
547
+ if (this.emit("message", connection, message), !this.isRequestMessage(message))
548
+ return;
549
+ let handler = this.handlers.get(message.method), sendResponse = (result2, error) => {
550
+ let response = {
551
+ type: "response",
552
+ id: message.id,
553
+ result: error ? void 0 : result2,
554
+ error: error instanceof Error ? serializeRPCError(error) : void 0
555
+ };
556
+ Promise.resolve(this.driver.send(connection, response)).catch((err) => {
557
+ this.emit("error", new Error(`Failed to send response to ${message.method}: ${err.message}`));
558
+ });
559
+ };
560
+ if (!handler || typeof handler != "function") {
561
+ sendResponse(void 0, new Error(`Unknown or invalid method: ${message.method}`));
562
+ return;
563
+ }
564
+ let result = await handler(...message.args);
565
+ result instanceof Error ? sendResponse(void 0, result) : sendResponse(result);
566
+ }
567
+ isRequestMessage(message) {
568
+ if (!utils.isPlainObject(message))
569
+ return false;
570
+ let hasType = "type" in message && message.type === "request", hasId = "id" in message && typeof message.id == "string", hasMethod = "method" in message && typeof message.method == "string", hasArgs = "args" in message && Array.isArray(message.args);
571
+ return hasType && hasId && hasMethod && hasArgs;
572
+ }
573
+ };
574
+ function createTransportServer(driver) {
575
+ return new TransportServer(driver);
576
+ }
577
+ var ServiceDriver = /* @__PURE__ */ ((ServiceDriver2) => (ServiceDriver2.IPC = "ipc", ServiceDriver2))(ServiceDriver || {}), Service = class {
578
+ constructor(options) {
579
+ this.options = options;
580
+ }
581
+ runtime;
582
+ handlers = /* @__PURE__ */ new Map();
583
+ binding;
584
+ get role() {
585
+ if (!this.runtime)
586
+ throw new Error("Service is not bound");
587
+ return this.runtime.role;
588
+ }
589
+ define(method, handler) {
590
+ if (this.handlers.has(method))
591
+ throw new Error(`Service method "${method}" already defined`);
592
+ return this.handlers.set(method, handler), (async (...args) => {
593
+ if (await this.ensureClientBound(), !this.runtime)
594
+ throw new Error(`Service is not bound (method "${method}")`);
595
+ return this.runtime.role === "server" ? handler(...args) : this.runtime.transport.request(method, ...args);
596
+ });
597
+ }
598
+ ensureServerBound() {
599
+ if (!this.runtime)
600
+ return this.binding ? this.binding : (this.binding = (async () => {
601
+ let transport;
602
+ switch (this.options.driver) {
603
+ case "ipc" /* IPC */: {
604
+ let driver = createIPCServer({
605
+ id: this.options.id,
606
+ ...this.options.server
607
+ });
608
+ transport = createTransportServer(driver);
609
+ break;
610
+ }
611
+ }
612
+ this.runtime = {
613
+ role: "server",
614
+ transport
615
+ };
616
+ for (let [name, handler] of this.handlers)
617
+ transport.handle(name, handler);
618
+ await new Promise((resolve2, reject) => {
619
+ let onResolve = () => {
620
+ cleanup(), resolve2();
621
+ }, onReject = (err) => {
622
+ cleanup(), reject(err);
623
+ }, cleanup = () => {
624
+ transport.off("listen", onResolve), transport.off("error", onReject);
625
+ };
626
+ transport.once("listen", onResolve), transport.once("error", onReject), transport.listen();
627
+ }), this.binding = void 0;
628
+ })(), this.binding);
629
+ }
630
+ ensureClientBound() {
631
+ if (!this.runtime)
632
+ return this.binding ? this.binding : (this.binding = (async () => {
633
+ let transport;
634
+ switch (this.options.driver) {
635
+ case "ipc" /* IPC */: {
636
+ let driver = createIPCClient({
637
+ id: this.options.id,
638
+ ...this.options.client
639
+ });
640
+ transport = createTransportClient(driver);
641
+ break;
642
+ }
643
+ }
644
+ this.runtime = {
645
+ role: "client",
646
+ transport
647
+ }, await new Promise((resolve2, reject) => {
648
+ let onResolve = () => {
649
+ cleanup(), resolve2();
650
+ }, onReject = (err) => {
651
+ cleanup(), reject(err);
652
+ }, cleanup = () => {
653
+ transport.off("connect", onResolve), transport.off("error", onReject);
654
+ };
655
+ transport.once("connect", onResolve), transport.once("error", onReject), transport.connect();
656
+ }), this.binding = void 0;
657
+ })(), this.binding);
658
+ }
659
+ };
660
+ function createService(options) {
661
+ return new Service(options);
662
+ }
663
+ async function getServices(entryDir = "./services", cwd = process.cwd()) {
664
+ let path$1 = path.resolve(entryDir, "**", "*.service.{ts,js}");
665
+ return await utils.glob(path$1, { cwd });
666
+ }
667
+ function getServiceName(servicePath, cwd = process.cwd()) {
668
+ let rel = path.relative(cwd, servicePath);
669
+ return rel = path.normalize(rel), rel = rel.replace(/^services[\\/]/, ""), rel = rel.replace(/\.service\.(ts|js)$/, ""), rel.split(path.sep).join("/");
670
+ }
671
+ var __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))), __dirname$1 = path.dirname(__filename$1), ServiceState = /* @__PURE__ */ ((ServiceState2) => (ServiceState2[ServiceState2.Idle = 0] = "Idle", ServiceState2[ServiceState2.Starting = 1] = "Starting", ServiceState2[ServiceState2.Running = 2] = "Running", ServiceState2[ServiceState2.Restarting = 3] = "Restarting", ServiceState2[ServiceState2.Stopping = 4] = "Stopping", ServiceState2[ServiceState2.Stopped = 5] = "Stopped", ServiceState2))(ServiceState || {}), ServiceProcess = class extends EventEmitter__default.default {
672
+ constructor(path) {
673
+ super();
674
+ this.path = path;
675
+ }
676
+ child;
677
+ spawnTimestamp = -1;
678
+ _state = 0 /* Idle */;
679
+ get name() {
680
+ return getServiceName(this.path);
681
+ }
682
+ get state() {
683
+ return this._state;
684
+ }
685
+ get ready() {
686
+ return this.state === 2 /* Running */;
687
+ }
688
+ get uptime() {
689
+ return this.spawnTimestamp === -1 ? -1 : Date.now() - this.spawnTimestamp;
690
+ }
691
+ start() {
692
+ this.state !== 2 /* Running */ && this.init();
693
+ }
694
+ async stop() {
695
+ this.state === 5 /* Stopped */ || this.state === 4 /* Stopping */ || (this._state = 4 /* Stopping */, await new Promise((resolve2) => {
696
+ let timeout = setTimeout(() => {
697
+ this.child?.killed || this.child?.kill("SIGKILL");
698
+ }, 5e3);
699
+ this.once("exit", () => {
700
+ clearTimeout(timeout), resolve2();
701
+ }), this.child?.kill("SIGINT");
702
+ }), this._state = 5 /* Stopped */, this.cleanup());
703
+ }
704
+ async restart() {
705
+ this.state !== 3 /* Restarting */ && (await this.stop(), await utils.sleep(1e3), this.start());
706
+ }
707
+ init() {
708
+ if (this.child && this.child.connected)
709
+ return;
710
+ this._state = 1 /* Starting */;
711
+ let file = path.join(__dirname$1, "service.js");
712
+ this.child = child_process.fork(file, [], {
713
+ env: {
714
+ BAKIT_SERVICE_NAME: this.name,
715
+ BAKIT_SERVICE_PATH: this.path,
716
+ FORCE_COLOR: "1"
717
+ },
718
+ stdio: ["inherit", "pipe", "pipe", "ipc"]
719
+ }), this.child.on("exit", (code, signal) => this.onChildExit(code, signal)), this.child.on("error", (err) => this.emit("error", err)), this.child.on("spawn", () => this.onChildSpawn()), this.child.stdout?.on("data", (chunk) => this.emit("stdout", chunk)), this.child.stderr?.on("data", (chunk) => this.emit("stderr", chunk));
720
+ }
721
+ cleanup() {
722
+ this.child && (this.child.killed || this.child.kill(), this.child.removeAllListeners(), this.child = void 0), this.spawnTimestamp = -1;
723
+ }
724
+ onChildSpawn() {
725
+ this._state = 2 /* Running */, this.spawnTimestamp = Date.now(), this.emit("spawn");
726
+ }
727
+ onChildExit(code, signal) {
728
+ let oldState = this._state;
729
+ this._state = 5 /* Stopped */, this.cleanup(), this.emit("exit", code, signal), oldState !== 4 /* Stopping */ && oldState !== 3 /* Restarting */ && code !== 0 && this.restart();
730
+ }
731
+ };
732
+
733
+ exports.BaseClientDriver = BaseClientDriver;
734
+ exports.BaseServerDriver = BaseServerDriver;
735
+ exports.DEFAULT_IPC_CLIENT_RECONNECT_OPTIONS = DEFAULT_IPC_CLIENT_RECONNECT_OPTIONS;
736
+ exports.FrameCodec = FrameCodec;
737
+ exports.IPCClient = IPCClient;
738
+ exports.IPCClientState = IPCClientState;
739
+ exports.IPCServer = IPCServer;
740
+ exports.Service = Service;
741
+ exports.ServiceDriver = ServiceDriver;
742
+ exports.ServiceProcess = ServiceProcess;
743
+ exports.ServiceState = ServiceState;
744
+ exports.TransportClient = TransportClient;
745
+ exports.TransportServer = TransportServer;
746
+ exports.createDynamicRPCError = createDynamicRPCError;
747
+ exports.createIPCClient = createIPCClient;
748
+ exports.createIPCServer = createIPCServer;
749
+ exports.createService = createService;
750
+ exports.createTransportClient = createTransportClient;
751
+ exports.createTransportServer = createTransportServer;
752
+ exports.getIPCPath = getIPCPath;
753
+ exports.getServiceName = getServiceName;
754
+ exports.getServices = getServices;
755
+ exports.isSerializedError = isSerializedError;
756
+ exports.isServerRunning = isServerRunning;
757
+ exports.serializeRPCError = serializeRPCError;