@bakit/service 2.0.0-alpha.36
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/LICENSE +21 -0
- package/dist/index.d.ts +239 -0
- package/dist/index.js +382 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Louis Johnson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Socket } from 'node:net';
|
|
2
|
+
import { EventBus, Awaitable, FunctionLike, Promisify } from '@bakit/utils';
|
|
3
|
+
import { ValueOf, MergeExclusive } from 'type-fest';
|
|
4
|
+
|
|
5
|
+
type Serializable =
|
|
6
|
+
| null
|
|
7
|
+
| undefined
|
|
8
|
+
| string
|
|
9
|
+
| number
|
|
10
|
+
| boolean
|
|
11
|
+
| bigint
|
|
12
|
+
| Date
|
|
13
|
+
| Buffer
|
|
14
|
+
| Uint8Array
|
|
15
|
+
| Serializable[]
|
|
16
|
+
| { [key: string | number]: Serializable }
|
|
17
|
+
| Map<string | number, Serializable>
|
|
18
|
+
| Set<Serializable>
|
|
19
|
+
| void;
|
|
20
|
+
|
|
21
|
+
interface BaseClientDriver extends EventBus<BaseClientDriverEvents> {
|
|
22
|
+
send(message: Serializable): void;
|
|
23
|
+
connect(): void;
|
|
24
|
+
disconnect(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface BaseServerDriver extends EventBus<BaseServerDriverEvents> {
|
|
28
|
+
broadcast(message: Serializable): void;
|
|
29
|
+
send(connection: unknown, message: Serializable): void;
|
|
30
|
+
listen(): void;
|
|
31
|
+
close(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface BaseClientDriverEvents {
|
|
35
|
+
message: [message: Serializable];
|
|
36
|
+
connect: [];
|
|
37
|
+
disconnect: [];
|
|
38
|
+
error: [error: Error];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface BaseServerDriverEvents {
|
|
42
|
+
message: [connection: unknown, message: Serializable];
|
|
43
|
+
clientConnect: [connection: unknown];
|
|
44
|
+
clientDisconnect: [connection: unknown];
|
|
45
|
+
clientError: [connection: unknown, error: Error];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare const SocketState: {
|
|
49
|
+
readonly Idle: 0;
|
|
50
|
+
readonly Connecting: 1;
|
|
51
|
+
readonly Connected: 2;
|
|
52
|
+
readonly Disconnected: 3;
|
|
53
|
+
readonly Reconnecting: 4;
|
|
54
|
+
readonly Destroyed: 5;
|
|
55
|
+
};
|
|
56
|
+
type SocketState = ValueOf<typeof SocketState>;
|
|
57
|
+
declare const IPCServerState: {
|
|
58
|
+
readonly Idle: 0;
|
|
59
|
+
readonly Listening: 1;
|
|
60
|
+
readonly Closed: 2;
|
|
61
|
+
};
|
|
62
|
+
type IPCServerState = ValueOf<typeof IPCServerState>;
|
|
63
|
+
interface IPCServerEvents extends BaseServerDriverEvents {
|
|
64
|
+
message: [socket: Socket, message: Serializable];
|
|
65
|
+
clientConnect: [socket: Socket];
|
|
66
|
+
clientDisconnect: [socket: Socket];
|
|
67
|
+
clientError: [socket: Socket, error: Error];
|
|
68
|
+
drain: [socket: Socket];
|
|
69
|
+
listen: [];
|
|
70
|
+
close: [];
|
|
71
|
+
}
|
|
72
|
+
interface IPCClientEvents extends BaseClientDriverEvents {
|
|
73
|
+
}
|
|
74
|
+
interface IPCServer extends EventBus<IPCServerEvents> {
|
|
75
|
+
listen(): void;
|
|
76
|
+
close(): void;
|
|
77
|
+
broadcast(message: Serializable): void;
|
|
78
|
+
send(socket: Socket, message: Serializable): void;
|
|
79
|
+
readonly state: IPCServerState;
|
|
80
|
+
}
|
|
81
|
+
interface IPCSocketConnection extends EventBus<IPCClientEvents>, IPCSocketMessageHandler {
|
|
82
|
+
connect: () => void;
|
|
83
|
+
disconnect: () => void;
|
|
84
|
+
destroy: () => void;
|
|
85
|
+
reconnect: () => void;
|
|
86
|
+
write: (chunk: Buffer) => void;
|
|
87
|
+
readonly state: SocketState;
|
|
88
|
+
}
|
|
89
|
+
interface IPCSocketMessageHandler extends BaseClientDriver {
|
|
90
|
+
handleData: (chunk: Buffer) => void;
|
|
91
|
+
}
|
|
92
|
+
interface IPCServerOptions {
|
|
93
|
+
id: string;
|
|
94
|
+
platform?: NodeJS.Platform;
|
|
95
|
+
}
|
|
96
|
+
interface IPCClientOptions {
|
|
97
|
+
id: string;
|
|
98
|
+
platform?: NodeJS.Platform;
|
|
99
|
+
connection?: IPCSocketConnectionOptions;
|
|
100
|
+
}
|
|
101
|
+
interface IPCSocketConnectionOptions {
|
|
102
|
+
autoReconnect?: boolean;
|
|
103
|
+
maxReconnectAttempts?: number;
|
|
104
|
+
reconnectDelay?: number;
|
|
105
|
+
requestConcurrency?: number;
|
|
106
|
+
}
|
|
107
|
+
interface IPCSocketMessageHandlerOptions {
|
|
108
|
+
onWrite(chunk: Buffer): void;
|
|
109
|
+
onMessage(message: Serializable): void;
|
|
110
|
+
}
|
|
111
|
+
declare const DEFAULT_IPC_SOCKET_CONNECTION_OPTIONS: {
|
|
112
|
+
readonly autoReconnect: true;
|
|
113
|
+
readonly maxReconnectAttempts: 10;
|
|
114
|
+
readonly reconnectDelay: 5000;
|
|
115
|
+
readonly requestConcurrency: 10;
|
|
116
|
+
};
|
|
117
|
+
declare function getIPCPath(id: string, platform?: NodeJS.Platform): string;
|
|
118
|
+
declare function createIPCClient(options: IPCClientOptions): IPCSocketConnection;
|
|
119
|
+
declare function createIPCServer(options: IPCServerOptions): IPCServer;
|
|
120
|
+
declare function createIPCSocketConnection(socketPath: string, options?: IPCSocketConnectionOptions): IPCSocketConnection;
|
|
121
|
+
declare function createIPCSocketMessageHandler(options: IPCSocketMessageHandlerOptions): {
|
|
122
|
+
handleData: (chunk: Buffer) => void;
|
|
123
|
+
send: (message: Serializable) => void;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
interface RPCErrorPayload {
|
|
127
|
+
message: string;
|
|
128
|
+
code?: number | string;
|
|
129
|
+
data?: Serializable;
|
|
130
|
+
}
|
|
131
|
+
declare class RPCError extends Error implements RPCErrorPayload {
|
|
132
|
+
readonly code?: number | string;
|
|
133
|
+
readonly data?: Serializable;
|
|
134
|
+
constructor(message: string, options?: Omit<RPCErrorPayload, "message">);
|
|
135
|
+
}
|
|
136
|
+
declare function serializeRPCError(error: unknown): RPCErrorPayload;
|
|
137
|
+
declare function deserializeRPCError(payload: RPCErrorPayload): RPCError;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Transport driver type identifiers.
|
|
141
|
+
*/
|
|
142
|
+
declare const TransportDriver: {
|
|
143
|
+
/**
|
|
144
|
+
* Uses Unix Domain Sockets / Named Pipes for communications between processes.
|
|
145
|
+
*/
|
|
146
|
+
readonly IPC: "ipc";
|
|
147
|
+
};
|
|
148
|
+
type TransportDriver = ValueOf<typeof TransportDriver>;
|
|
149
|
+
interface TransportClientDriverSpecificOptions {
|
|
150
|
+
[TransportDriver.IPC]: IPCClientOptions;
|
|
151
|
+
}
|
|
152
|
+
interface TransportServerDriverSpecificOptions {
|
|
153
|
+
[TransportDriver.IPC]: IPCServerOptions;
|
|
154
|
+
}
|
|
155
|
+
type TransportClientOptions = {
|
|
156
|
+
[D in TransportDriver]: {
|
|
157
|
+
driver: D;
|
|
158
|
+
} & TransportClientDriverSpecificOptions[D];
|
|
159
|
+
}[TransportDriver];
|
|
160
|
+
type TransportServerOptions = {
|
|
161
|
+
[D in TransportDriver]: {
|
|
162
|
+
driver: D;
|
|
163
|
+
} & TransportServerDriverSpecificOptions[D];
|
|
164
|
+
}[TransportDriver];
|
|
165
|
+
interface TransportClient extends TransportClientProtocol, EventBus<TransportEvents> {
|
|
166
|
+
send: BaseServerDriver["send"];
|
|
167
|
+
connect: BaseClientDriver["connect"];
|
|
168
|
+
disconnect: BaseClientDriver["disconnect"];
|
|
169
|
+
}
|
|
170
|
+
interface TransportServer extends TransportServerProtocol, EventBus<TransportEvents> {
|
|
171
|
+
broadcast: BaseServerDriver["broadcast"];
|
|
172
|
+
listen: BaseServerDriver["listen"];
|
|
173
|
+
close: BaseServerDriver["close"];
|
|
174
|
+
}
|
|
175
|
+
interface TransportClientProtocol {
|
|
176
|
+
request<T>(method: string, ...args: Serializable[]): Promise<T>;
|
|
177
|
+
}
|
|
178
|
+
type RPCHandler<Args extends Serializable[], Result extends Serializable> = (...args: Args) => Awaitable<Result>;
|
|
179
|
+
interface TransportServerProtocol {
|
|
180
|
+
handle<Args extends Serializable[], Result extends Serializable>(method: string, handler: RPCHandler<Args, Result>): void;
|
|
181
|
+
}
|
|
182
|
+
interface RPCRequestMessage {
|
|
183
|
+
type: "request";
|
|
184
|
+
id: string;
|
|
185
|
+
method: string;
|
|
186
|
+
args: Serializable[];
|
|
187
|
+
}
|
|
188
|
+
type RPCResponseMessage = {
|
|
189
|
+
type: "response";
|
|
190
|
+
id: string;
|
|
191
|
+
} & MergeExclusive<{
|
|
192
|
+
result: Serializable;
|
|
193
|
+
}, {
|
|
194
|
+
error: RPCErrorPayload;
|
|
195
|
+
}>;
|
|
196
|
+
interface TransportEvents {
|
|
197
|
+
connect: [];
|
|
198
|
+
disconnect: [];
|
|
199
|
+
error: [error: Error];
|
|
200
|
+
request: [id: string, method: string];
|
|
201
|
+
response: [id: string];
|
|
202
|
+
timeout: [id: string];
|
|
203
|
+
clientConnect: [connection: unknown];
|
|
204
|
+
clientDisconnect: [connection: unknown];
|
|
205
|
+
}
|
|
206
|
+
declare function createTransportClient(options: TransportClientOptions): TransportClient;
|
|
207
|
+
declare function createTransportServer(options: TransportServerOptions): TransportServer;
|
|
208
|
+
declare function createTransportClientProtocol(driver: BaseClientDriver): TransportClientProtocol;
|
|
209
|
+
declare function createTransportServerProtocol(driver: BaseServerDriver): TransportServerProtocol;
|
|
210
|
+
|
|
211
|
+
interface ServiceOptions {
|
|
212
|
+
name: string;
|
|
213
|
+
transport: TransportClientOptions & TransportServerOptions;
|
|
214
|
+
}
|
|
215
|
+
interface ServiceServer {
|
|
216
|
+
define<F extends ServiceFunction>(method: string, handler: F): Promisify<F>;
|
|
217
|
+
transport: TransportServer;
|
|
218
|
+
}
|
|
219
|
+
interface ServiceClient {
|
|
220
|
+
define<F extends ServiceFunction>(method: string, handler: F): Promisify<F>;
|
|
221
|
+
transport: TransportClient;
|
|
222
|
+
}
|
|
223
|
+
interface Service {
|
|
224
|
+
define<F extends ServiceFunction>(method: string, handler: F): Promisify<F>;
|
|
225
|
+
start(): void;
|
|
226
|
+
stop(): void;
|
|
227
|
+
}
|
|
228
|
+
type ServiceFunction = FunctionLike<any[], Awaitable<any>>;
|
|
229
|
+
declare function createService(options: ServiceOptions): Service;
|
|
230
|
+
/**
|
|
231
|
+
* Create a service client instance.
|
|
232
|
+
*
|
|
233
|
+
* @param {options} options.
|
|
234
|
+
* @return {ServiceClient} Service client instance.
|
|
235
|
+
*/
|
|
236
|
+
declare function createServiceClient(options: ServiceOptions): ServiceClient;
|
|
237
|
+
declare function createServiceServer(options: ServiceOptions): ServiceServer;
|
|
238
|
+
|
|
239
|
+
export { type BaseClientDriver, type BaseClientDriverEvents, type BaseServerDriver, type BaseServerDriverEvents, DEFAULT_IPC_SOCKET_CONNECTION_OPTIONS, type IPCClientEvents, type IPCClientOptions, type IPCServer, type IPCServerEvents, type IPCServerOptions, IPCServerState, type IPCSocketConnection, type IPCSocketConnectionOptions, type IPCSocketMessageHandler, type IPCSocketMessageHandlerOptions, RPCError, type RPCErrorPayload, type RPCHandler, type RPCRequestMessage, type RPCResponseMessage, type Serializable, type Service, type ServiceClient, type ServiceFunction, type ServiceOptions, type ServiceServer, SocketState, type TransportClient, type TransportClientDriverSpecificOptions, type TransportClientOptions, type TransportClientProtocol, TransportDriver, type TransportEvents, type TransportServer, type TransportServerDriverSpecificOptions, type TransportServerOptions, type TransportServerProtocol, createIPCClient, createIPCServer, createIPCSocketConnection, createIPCSocketMessageHandler, createService, createServiceClient, createServiceServer, createTransportClient, createTransportClientProtocol, createTransportServer, createTransportServerProtocol, deserializeRPCError, getIPCPath, serializeRPCError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { createServer, createConnection } from 'net';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, rmSync } from 'fs';
|
|
4
|
+
import { pack, unpack } from 'msgpackr';
|
|
5
|
+
import PQueue from 'p-queue';
|
|
6
|
+
import { attachEventBus, Collection, isPlainObject, promisify } from '@bakit/utils';
|
|
7
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
|
|
9
|
+
// src/lib/driver/ipc.ts
|
|
10
|
+
var UNIX_SOCKET_DIR = "/tmp", WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\", SocketState = {
|
|
11
|
+
Idle: 0,
|
|
12
|
+
Connecting: 1,
|
|
13
|
+
Connected: 2,
|
|
14
|
+
Disconnected: 3,
|
|
15
|
+
Reconnecting: 4,
|
|
16
|
+
Destroyed: 5
|
|
17
|
+
}, IPCServerState = {
|
|
18
|
+
Idle: 0,
|
|
19
|
+
Listening: 1,
|
|
20
|
+
Closed: 2
|
|
21
|
+
}, DEFAULT_IPC_SOCKET_CONNECTION_OPTIONS = {
|
|
22
|
+
autoReconnect: true,
|
|
23
|
+
maxReconnectAttempts: 10,
|
|
24
|
+
reconnectDelay: 5e3,
|
|
25
|
+
requestConcurrency: 10
|
|
26
|
+
};
|
|
27
|
+
function getIPCPath(id, platform = process.platform) {
|
|
28
|
+
switch (platform) {
|
|
29
|
+
case "win32":
|
|
30
|
+
return `${WINDOWS_PIPE_PREFIX}${id}`;
|
|
31
|
+
default:
|
|
32
|
+
return join(UNIX_SOCKET_DIR, `${id}.sock`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function createIPCClient(options) {
|
|
36
|
+
let ipcPath = getIPCPath(options.id, options.platform);
|
|
37
|
+
return createIPCSocketConnection(ipcPath, options.connection);
|
|
38
|
+
}
|
|
39
|
+
function createIPCServer(options) {
|
|
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;
|
|
47
|
+
}
|
|
48
|
+
}), server = createServer((socket) => {
|
|
49
|
+
clients.add(socket);
|
|
50
|
+
let handler = createIPCSocketMessageHandler({
|
|
51
|
+
onMessage: (msg) => self.emit("message", socket, msg),
|
|
52
|
+
onWrite: (chunk) => writeSocket(socket, chunk)
|
|
53
|
+
});
|
|
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
|
+
});
|
|
58
|
+
server.on("listening", () => {
|
|
59
|
+
state = IPCServerState.Listening, self.emit("listen");
|
|
60
|
+
}), server.on("close", () => {
|
|
61
|
+
state = IPCServerState.Closed, self.emit("close");
|
|
62
|
+
});
|
|
63
|
+
function listen() {
|
|
64
|
+
existsSync(ipcPath) && rmSync(ipcPath), server.listen(ipcPath);
|
|
65
|
+
}
|
|
66
|
+
function close() {
|
|
67
|
+
server.close();
|
|
68
|
+
}
|
|
69
|
+
function writeSocket(socket, chunk) {
|
|
70
|
+
if (!socket.writable)
|
|
71
|
+
return;
|
|
72
|
+
socket.write(chunk) || socket.once("drain", () => self.emit("drain", socket));
|
|
73
|
+
}
|
|
74
|
+
function broadcast(message) {
|
|
75
|
+
let payload = pack(message), header = Buffer.alloc(4);
|
|
76
|
+
header.writeUInt32LE(payload.length);
|
|
77
|
+
let packet = Buffer.concat([header, payload]);
|
|
78
|
+
for (let socket of clients)
|
|
79
|
+
writeSocket(socket, packet);
|
|
80
|
+
}
|
|
81
|
+
function send(socket, message) {
|
|
82
|
+
let payload = pack(message), header = Buffer.alloc(4);
|
|
83
|
+
header.writeUInt32LE(payload.length);
|
|
84
|
+
let packet = Buffer.concat([header, payload]);
|
|
85
|
+
writeSocket(socket, packet);
|
|
86
|
+
}
|
|
87
|
+
return self;
|
|
88
|
+
}
|
|
89
|
+
function createIPCSocketConnection(socketPath, options = {}) {
|
|
90
|
+
let resolvedOptions = { ...DEFAULT_IPC_SOCKET_CONNECTION_OPTIONS, ...options }, handler = createIPCSocketMessageHandler({
|
|
91
|
+
onMessage: (message) => connection.emit("message", message),
|
|
92
|
+
onWrite: write
|
|
93
|
+
}), queue = new PQueue({
|
|
94
|
+
concurrency: resolvedOptions.requestConcurrency,
|
|
95
|
+
autoStart: true
|
|
96
|
+
});
|
|
97
|
+
queue.pause();
|
|
98
|
+
let socket, state = SocketState.Idle, isConnecting = false, shouldReconnect = resolvedOptions.autoReconnect, reconnectAttempts = 0, reconnectTimeout, connection = attachEventBus({
|
|
99
|
+
...handler,
|
|
100
|
+
connect,
|
|
101
|
+
disconnect,
|
|
102
|
+
destroy,
|
|
103
|
+
reconnect,
|
|
104
|
+
write,
|
|
105
|
+
get state() {
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
function connect() {
|
|
110
|
+
if (state === SocketState.Destroyed) {
|
|
111
|
+
connection.emit("error", new Error("Cannot start a new socket after destroyed."));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (state === SocketState.Connected || state === SocketState.Connecting) {
|
|
115
|
+
connection.emit("error", new Error("The current socket is still running, use reconnect() instead."));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (isConnecting) {
|
|
119
|
+
connection.emit("error", new Error("connect() shouldn't be called more than once."));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
state !== SocketState.Reconnecting && (state = SocketState.Connecting), isConnecting = true, socket = createConnection(socketPath), initSocket();
|
|
123
|
+
}
|
|
124
|
+
function initSocket() {
|
|
125
|
+
socket && (socket.on("connect", handleConnect), socket.on("data", handler.handleData), socket.on("error", handleError), socket.on("close", handleClose));
|
|
126
|
+
}
|
|
127
|
+
function handleConnect() {
|
|
128
|
+
state = SocketState.Connected, isConnecting = false, reconnectAttempts = 0, shouldReconnect = resolvedOptions.autoReconnect, queue.start(), connection.emit("connect");
|
|
129
|
+
}
|
|
130
|
+
function handleError(error) {
|
|
131
|
+
error.code === "ECONNREFUSED" || error.code === "ENOENT" || connection.emit("error", error);
|
|
132
|
+
}
|
|
133
|
+
function handleClose() {
|
|
134
|
+
state = SocketState.Disconnected, queue.pause(), isConnecting = false, connection.emit("disconnect"), scheduleReconnect();
|
|
135
|
+
}
|
|
136
|
+
function scheduleReconnect() {
|
|
137
|
+
shouldReconnect && (resolvedOptions.maxReconnectAttempts > 0 && reconnectAttempts >= resolvedOptions.maxReconnectAttempts && (connection.emit("error", new Error(`Max reconnect attempts (${resolvedOptions.maxReconnectAttempts}) exceeded`)), state = SocketState.Disconnected), reconnectTimeout && (clearTimeout(reconnectTimeout), reconnectTimeout = void 0), reconnectTimeout = setTimeout(() => {
|
|
138
|
+
reconnectTimeout = void 0, reconnect();
|
|
139
|
+
}, resolvedOptions.reconnectDelay));
|
|
140
|
+
}
|
|
141
|
+
function reconnect() {
|
|
142
|
+
state === SocketState.Destroyed || state === SocketState.Reconnecting || (reconnectAttempts++, state = SocketState.Reconnecting, cleanupSocket(), connect());
|
|
143
|
+
}
|
|
144
|
+
function disconnect() {
|
|
145
|
+
state = SocketState.Disconnected, shouldReconnect = false, cleanupSocket();
|
|
146
|
+
}
|
|
147
|
+
function destroy() {
|
|
148
|
+
state = SocketState.Destroyed, shouldReconnect = false, queue.clear(), connection.removeAllListeners(), cleanupSocket();
|
|
149
|
+
}
|
|
150
|
+
function cleanupSocket() {
|
|
151
|
+
queue.pause(), reconnectTimeout && (clearTimeout(reconnectTimeout), reconnectTimeout = void 0), socket && (socket.removeAllListeners(), socket.destroyed || socket.destroy(), socket = void 0);
|
|
152
|
+
}
|
|
153
|
+
function write(chunk) {
|
|
154
|
+
queue.add(() => new Promise((resolve, reject) => {
|
|
155
|
+
if (!socket || !socket.writable)
|
|
156
|
+
return resolve();
|
|
157
|
+
let done = false, safeResolve = () => {
|
|
158
|
+
done || (done = true, resolve());
|
|
159
|
+
}, safeReject = (err) => {
|
|
160
|
+
done || (done = true, reject(err));
|
|
161
|
+
}, ok = socket.write(chunk, (err) => {
|
|
162
|
+
err ? safeReject(err) : ok && safeResolve();
|
|
163
|
+
});
|
|
164
|
+
ok || socket.once("drain", safeResolve);
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
return connection;
|
|
168
|
+
}
|
|
169
|
+
function createIPCSocketMessageHandler(options) {
|
|
170
|
+
let buffer = Buffer.alloc(0);
|
|
171
|
+
function handleData(chunk) {
|
|
172
|
+
for (buffer = Buffer.concat([buffer, chunk]); !(buffer.length < 4); ) {
|
|
173
|
+
let messageLength = buffer.readUInt32LE(0);
|
|
174
|
+
if (buffer.length < 4 + messageLength)
|
|
175
|
+
break;
|
|
176
|
+
let payload = buffer.subarray(4, 4 + messageLength);
|
|
177
|
+
try {
|
|
178
|
+
let message = unpack(payload);
|
|
179
|
+
options.onMessage(message);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error("Failed to unpack message:", error);
|
|
182
|
+
}
|
|
183
|
+
buffer = buffer.subarray(4 + messageLength);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function send(message) {
|
|
187
|
+
let payload = pack(message), header = Buffer.alloc(4);
|
|
188
|
+
header.writeUInt32LE(payload.length);
|
|
189
|
+
let packet = Buffer.concat([header, payload]);
|
|
190
|
+
options.onWrite(packet);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
handleData,
|
|
194
|
+
send
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/errors/RPCError.ts
|
|
199
|
+
var RPCError = class extends Error {
|
|
200
|
+
code;
|
|
201
|
+
data;
|
|
202
|
+
constructor(message, options = {}) {
|
|
203
|
+
super(message), this.name = "RPCError", this.code = options.code, this.data = options.data;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
function serializeRPCError(error) {
|
|
207
|
+
return error instanceof RPCError ? {
|
|
208
|
+
message: error.message,
|
|
209
|
+
code: error.code,
|
|
210
|
+
data: error.data
|
|
211
|
+
} : error instanceof Error ? {
|
|
212
|
+
message: error.message,
|
|
213
|
+
code: error.name
|
|
214
|
+
} : {
|
|
215
|
+
message: String(error)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function deserializeRPCError(payload) {
|
|
219
|
+
return new RPCError(payload.message, {
|
|
220
|
+
code: payload.code,
|
|
221
|
+
data: payload.data
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/lib/transport.ts
|
|
226
|
+
var TransportDriver = {
|
|
227
|
+
/**
|
|
228
|
+
* Uses Unix Domain Sockets / Named Pipes for communications between processes.
|
|
229
|
+
*/
|
|
230
|
+
IPC: "ipc"
|
|
231
|
+
};
|
|
232
|
+
function createTransportClient(options) {
|
|
233
|
+
let driver;
|
|
234
|
+
switch (options.driver) {
|
|
235
|
+
case TransportDriver.IPC:
|
|
236
|
+
driver = createIPCClient(options);
|
|
237
|
+
}
|
|
238
|
+
let base = {
|
|
239
|
+
...createTransportClientProtocol(driver),
|
|
240
|
+
send: driver.send,
|
|
241
|
+
connect: driver.connect,
|
|
242
|
+
disconnect: driver.disconnect
|
|
243
|
+
}, self = attachEventBus(base);
|
|
244
|
+
return driver.on("connect", () => self.emit("connect")), driver.on("disconnect", () => self.emit("disconnect")), driver.on("error", (error) => self.emit("error", error)), self;
|
|
245
|
+
}
|
|
246
|
+
function createTransportServer(options) {
|
|
247
|
+
let driver;
|
|
248
|
+
switch (options.driver) {
|
|
249
|
+
case TransportDriver.IPC:
|
|
250
|
+
driver = createIPCServer(options);
|
|
251
|
+
}
|
|
252
|
+
let base = {
|
|
253
|
+
...createTransportServerProtocol(driver),
|
|
254
|
+
broadcast: driver.broadcast,
|
|
255
|
+
listen: driver.listen,
|
|
256
|
+
close: driver.close
|
|
257
|
+
}, self = attachEventBus(base);
|
|
258
|
+
return 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;
|
|
259
|
+
}
|
|
260
|
+
function createTransportClientProtocol(driver) {
|
|
261
|
+
let requests = new Collection();
|
|
262
|
+
driver.on("message", (message) => {
|
|
263
|
+
if (!isResponseMessage(message))
|
|
264
|
+
return;
|
|
265
|
+
let entry = requests.get(message.id);
|
|
266
|
+
entry && (requests.delete(message.id), message.error !== void 0 ? entry.reject(deserializeRPCError(message.error)) : entry.resolve(message.result));
|
|
267
|
+
});
|
|
268
|
+
function isResponseMessage(message) {
|
|
269
|
+
if (!isPlainObject(message))
|
|
270
|
+
return false;
|
|
271
|
+
let hasType = "type" in message && message.type === "response", hasId = "id" in message && typeof message.id == "string", hasResult = "result" in message && message.result !== void 0, hasError = "error" in message && message.error !== void 0;
|
|
272
|
+
return hasType && hasId && hasResult !== hasError;
|
|
273
|
+
}
|
|
274
|
+
function request(method, ...args) {
|
|
275
|
+
let requestId = randomUUID();
|
|
276
|
+
return driver.send({
|
|
277
|
+
type: "request",
|
|
278
|
+
id: requestId,
|
|
279
|
+
method,
|
|
280
|
+
args
|
|
281
|
+
}), new Promise((resolve, reject) => {
|
|
282
|
+
let timeout = setTimeout(() => {
|
|
283
|
+
let request2 = requests.get(requestId);
|
|
284
|
+
request2 && (request2.reject(new Error("RPC timeout")), requests.delete(requestId));
|
|
285
|
+
}, 3e4);
|
|
286
|
+
requests.set(requestId, {
|
|
287
|
+
resolve: (v) => {
|
|
288
|
+
clearTimeout(timeout), resolve(v);
|
|
289
|
+
},
|
|
290
|
+
reject: (e) => {
|
|
291
|
+
clearTimeout(timeout), reject(e);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
request
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function createTransportServerProtocol(driver) {
|
|
301
|
+
let handlers = new Collection();
|
|
302
|
+
driver.on("message", async (connection, message) => {
|
|
303
|
+
if (!isRequestMessage(message))
|
|
304
|
+
return;
|
|
305
|
+
let handler = handlers.get(message.method), sendError = (error) => {
|
|
306
|
+
let err = error instanceof Error ? serializeRPCError(error) : error, payload = {
|
|
307
|
+
type: "response",
|
|
308
|
+
id: message.id,
|
|
309
|
+
error: err
|
|
310
|
+
};
|
|
311
|
+
driver.send(connection, payload);
|
|
312
|
+
}, sendResult = (result) => {
|
|
313
|
+
driver.send(connection, {
|
|
314
|
+
type: "response",
|
|
315
|
+
id: message.id,
|
|
316
|
+
result
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
if (!handler) {
|
|
320
|
+
sendError({ message: `Unknown method: ${message.method}` });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
let result = await handler(...message.args);
|
|
325
|
+
sendResult(result);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
sendError(error);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
function isRequestMessage(message) {
|
|
331
|
+
if (!isPlainObject(message))
|
|
332
|
+
return false;
|
|
333
|
+
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);
|
|
334
|
+
return hasType && hasId && hasMethod && hasArgs;
|
|
335
|
+
}
|
|
336
|
+
function handle(method, handler) {
|
|
337
|
+
let wrapped = (...args) => handler(...args);
|
|
338
|
+
handlers.set(method, wrapped);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
handle
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function createService(options) {
|
|
345
|
+
if (process.env.BAKIT_SERVICE_NAME === options.name) {
|
|
346
|
+
let server = createServiceServer(options);
|
|
347
|
+
return {
|
|
348
|
+
define: server.define,
|
|
349
|
+
start: server.transport.listen,
|
|
350
|
+
stop: server.transport.close
|
|
351
|
+
};
|
|
352
|
+
} else {
|
|
353
|
+
let client = createServiceClient(options);
|
|
354
|
+
return {
|
|
355
|
+
define: client.define,
|
|
356
|
+
start: client.transport.connect,
|
|
357
|
+
stop: client.transport.disconnect
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function createServiceClient(options) {
|
|
362
|
+
let transport = createTransportClient(options.transport);
|
|
363
|
+
function define(method, _handler) {
|
|
364
|
+
return (...args) => transport.request(method, ...args);
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
define,
|
|
368
|
+
transport
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function createServiceServer(options) {
|
|
372
|
+
let transport = createTransportServer(options.transport);
|
|
373
|
+
function define(method, handler) {
|
|
374
|
+
return transport.handle(method, handler), promisify(handler);
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
define,
|
|
378
|
+
transport
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export { DEFAULT_IPC_SOCKET_CONNECTION_OPTIONS, IPCServerState, RPCError, SocketState, TransportDriver, createIPCClient, createIPCServer, createIPCSocketConnection, createIPCSocketMessageHandler, createService, createServiceClient, createServiceServer, createTransportClient, createTransportClientProtocol, createTransportServer, createTransportServerProtocol, deserializeRPCError, getIPCPath, serializeRPCError };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bakit/service",
|
|
3
|
+
"version": "2.0.0-alpha.36",
|
|
4
|
+
"description": "Service manager for bakit framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/louiszn/bakit#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/louiszn/bakit.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/louiszn/bakit/issues"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"discord-bot",
|
|
25
|
+
"bakit",
|
|
26
|
+
"framework"
|
|
27
|
+
],
|
|
28
|
+
"author": "louiszn",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"msgpackr": "^1.11.8",
|
|
32
|
+
"p-queue": "^9.0.1",
|
|
33
|
+
"type-fest": "^4.41.0",
|
|
34
|
+
"@bakit/utils": "2.0.0-alpha.36"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/ws": "^8.18.1",
|
|
38
|
+
"ws": "^8.18.3"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"type-check": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run --pass-with-no-tests"
|
|
44
|
+
}
|
|
45
|
+
}
|