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