@delali/narsil 0.2.2 → 0.2.3-next.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/adapters/filesystem.d.ts +1 -1
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/adapters/memory.d.ts +1 -1
- package/dist/{adapters-CVqI5NBt.d.ts → adapters-79OIa4xm.d.ts} +1 -1
- package/dist/brute-force-DbHYCTc3.d.ts +8 -0
- package/dist/chunk-BFIH7OV5.mjs +185 -0
- package/dist/chunk-C6VVLMRX.mjs +63 -0
- package/dist/{chunk-UNJZ7ZDB.mjs → chunk-EMI63EXP.mjs} +33 -2
- package/dist/chunk-MFFAJ2NR.mjs +135 -0
- package/dist/chunk-OBXS7OZM.mjs +376 -0
- package/dist/{chunk-ASI6AOW2.mjs → chunk-RO4EFWE6.mjs} +7833 -3524
- package/dist/{chunk-WLR5BFL3.mjs → chunk-TEAJO7CU.mjs} +3067 -2087
- package/dist/{chunk-S7RJJMHI.mjs → chunk-WDL632CN.mjs} +699 -222
- package/dist/{chunk-BKAI2WXI.mjs → chunk-WTONLDU3.mjs} +5 -0
- package/dist/client-DQHB_swQ.d.ts +793 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.mjs +843 -0
- package/dist/{config-DXypXAfN.d.ts → config-ChZ7lfqc.d.ts} +3 -3
- package/dist/direct-executor-YARCDMJF.mjs +8 -0
- package/dist/distribution/coordinator/etcd.d.ts +3 -3
- package/dist/distribution/coordinator/etcd.mjs +471 -2
- package/dist/distribution/coordinator/in-memory.d.ts +3 -3
- package/dist/distribution/coordinator/in-memory.mjs +286 -1
- package/dist/distribution/transport/grpc.d.ts +45 -0
- package/dist/distribution/transport/grpc.mjs +586 -0
- package/dist/distribution/transport/in-memory.d.ts +118 -0
- package/dist/distribution/transport/in-memory.mjs +214 -0
- package/dist/distribution/transport/tcp.d.ts +27 -0
- package/dist/distribution/transport/tcp.mjs +595 -0
- package/dist/distribution.d.ts +241 -192
- package/dist/distribution.mjs +6620 -4795
- package/dist/embeddings/openai.d.ts +1 -1
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-CO9Wnfie.d.ts → engine-DScGlT2Z.d.ts} +30 -3
- package/dist/index.browser.mjs +10035 -3914
- package/dist/index.d.ts +8 -8
- package/dist/index.mjs +8 -5
- package/dist/invalidation/broadcast-channel.d.ts +1 -1
- package/dist/invalidation/filesystem.d.ts +1 -1
- package/dist/invalidation/filesystem.mjs +3 -1
- package/dist/invalidation/noop.d.ts +1 -1
- package/dist/options-BORJg5XB.d.ts +234 -0
- package/dist/persistence/durability/checkpoint-worker.d.ts +4 -2
- package/dist/persistence/durability/checkpoint-worker.mjs +8 -5
- package/dist/react.d.ts +340 -0
- package/dist/react.mjs +687 -0
- package/dist/{schema-CE6S_aAr.d.ts → schema-emTQfEiX.d.ts} +21 -7
- package/dist/{search-DLBtiPdQ.d.ts → search-CQ-9bKxo.d.ts} +226 -14
- package/dist/server.d.ts +42 -356
- package/dist/server.mjs +493 -106
- package/dist/tasks-CnMLpfk-.d.ts +274 -0
- package/dist/types-BQDlIvfS.d.ts +34 -0
- package/dist/{types-BV3nAKTL.d.ts → types-Bfhhnh3G.d.ts} +23 -1
- package/dist/types-D5OwhtsQ.d.ts +97 -0
- package/dist/vector/hnsw-build-worker.d.ts +2 -7
- package/dist/vector/hnsw-build-worker.mjs +2 -1
- package/dist/vector/search-worker.d.ts +373 -0
- package/dist/vector/search-worker.mjs +279 -0
- package/dist/workers/entry.mjs +43 -25
- package/package.json +42 -2
- package/dist/chunk-FKK76OR7.mjs +0 -456
- package/dist/chunk-RQLXC2YS.mjs +0 -269
- package/dist/direct-executor-3W4QRA4E.mjs +0 -5
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { DEFAULT_TCP_CONFIG, encodeTransportMessage, encodeFrame, FRAME_TYPE_REQUEST, FrameParser, FRAME_TYPE_RESPONSE, decodeTransportMessage, FRAME_TYPE_STREAM_CHUNK, FRAME_TYPE_STREAM_END } from '../../chunk-MFFAJ2NR.mjs';
|
|
2
|
+
import { MAX_MESSAGE_SIZE_BYTES, TransportError, TransportErrorCodes } from '../../chunk-C6VVLMRX.mjs';
|
|
3
|
+
import { connect as connect$1, createServer as createServer$1 } from 'net';
|
|
4
|
+
import { connect, createServer } from 'tls';
|
|
5
|
+
|
|
6
|
+
// src/distribution/transport/tcp/parse-address.ts
|
|
7
|
+
function parseAddress(address) {
|
|
8
|
+
const lastColon = address.lastIndexOf(":");
|
|
9
|
+
if (lastColon === -1) {
|
|
10
|
+
throw new TransportError(
|
|
11
|
+
TransportErrorCodes.CONNECT_FAILED,
|
|
12
|
+
`Invalid address format '${address}', expected host:port`,
|
|
13
|
+
{
|
|
14
|
+
address
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const host = address.slice(0, lastColon);
|
|
19
|
+
const port = address.slice(lastColon + 1);
|
|
20
|
+
if (host.length === 0 || host.includes("\0")) {
|
|
21
|
+
throw new TransportError(TransportErrorCodes.CONNECT_FAILED, `Invalid host in address '${address}'`, { address });
|
|
22
|
+
}
|
|
23
|
+
return [host, port];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/distribution/transport/tcp/connection.ts
|
|
27
|
+
var TcpConnectionPool = class {
|
|
28
|
+
connections = /* @__PURE__ */ new Map();
|
|
29
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
30
|
+
pendingStreams = /* @__PURE__ */ new Map();
|
|
31
|
+
parsers = /* @__PURE__ */ new Map();
|
|
32
|
+
config;
|
|
33
|
+
closed = false;
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = config;
|
|
36
|
+
}
|
|
37
|
+
updateTlsConfig(nextTlsConfig) {
|
|
38
|
+
this.config = {
|
|
39
|
+
...this.config,
|
|
40
|
+
tls: nextTlsConfig
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async send(target, message) {
|
|
44
|
+
if (this.closed) {
|
|
45
|
+
throw new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, "Connection pool has been shut down");
|
|
46
|
+
}
|
|
47
|
+
const messageBytes = encodeTransportMessage(message);
|
|
48
|
+
if (messageBytes.byteLength > MAX_MESSAGE_SIZE_BYTES) {
|
|
49
|
+
throw new TransportError(
|
|
50
|
+
TransportErrorCodes.MESSAGE_TOO_LARGE,
|
|
51
|
+
`Message payload (${messageBytes.byteLength} bytes) exceeds the ${MAX_MESSAGE_SIZE_BYTES} byte limit`,
|
|
52
|
+
{ target, requestId: message.requestId, payloadSize: messageBytes.byteLength }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const socket = await this.getOrCreateConnection(target);
|
|
56
|
+
const frame = encodeFrame(FRAME_TYPE_REQUEST, message.requestId, messageBytes);
|
|
57
|
+
const pendingKey = `${target}:${message.requestId}`;
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const timer = setTimeout(() => {
|
|
60
|
+
this.pendingRequests.delete(pendingKey);
|
|
61
|
+
reject(
|
|
62
|
+
new TransportError(
|
|
63
|
+
TransportErrorCodes.TIMEOUT,
|
|
64
|
+
`Request to '${target}' timed out after ${this.config.requestTimeout}ms`,
|
|
65
|
+
{
|
|
66
|
+
target,
|
|
67
|
+
requestId: message.requestId,
|
|
68
|
+
timeoutMs: this.config.requestTimeout
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
}, this.config.requestTimeout);
|
|
73
|
+
this.pendingRequests.set(pendingKey, { target, resolve, reject, timer });
|
|
74
|
+
socket.write(frame, (err) => {
|
|
75
|
+
if (err) {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
this.pendingRequests.delete(pendingKey);
|
|
78
|
+
reject(
|
|
79
|
+
new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, `Failed to write to '${target}': ${err.message}`, {
|
|
80
|
+
target,
|
|
81
|
+
requestId: message.requestId
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async stream(target, message, handler) {
|
|
89
|
+
if (this.closed) {
|
|
90
|
+
throw new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, "Connection pool has been shut down");
|
|
91
|
+
}
|
|
92
|
+
const messageBytes = encodeTransportMessage(message);
|
|
93
|
+
const socket = await this.getOrCreateConnection(target);
|
|
94
|
+
const frame = encodeFrame(FRAME_TYPE_REQUEST, message.requestId, messageBytes);
|
|
95
|
+
const pendingKey = `${target}:${message.requestId}`;
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
this.pendingStreams.delete(pendingKey);
|
|
99
|
+
reject(
|
|
100
|
+
new TransportError(
|
|
101
|
+
TransportErrorCodes.TIMEOUT,
|
|
102
|
+
`Stream to '${target}' timed out after ${this.config.snapshotTimeout}ms`,
|
|
103
|
+
{
|
|
104
|
+
target,
|
|
105
|
+
requestId: message.requestId,
|
|
106
|
+
timeoutMs: this.config.snapshotTimeout
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}, this.config.snapshotTimeout);
|
|
111
|
+
this.pendingStreams.set(pendingKey, { target, handler, resolve, reject, timer });
|
|
112
|
+
socket.write(frame, (err) => {
|
|
113
|
+
if (err) {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
this.pendingStreams.delete(pendingKey);
|
|
116
|
+
reject(
|
|
117
|
+
new TransportError(
|
|
118
|
+
TransportErrorCodes.PEER_UNAVAILABLE,
|
|
119
|
+
`Failed to write stream to '${target}': ${err.message}`,
|
|
120
|
+
{
|
|
121
|
+
target,
|
|
122
|
+
requestId: message.requestId
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async getOrCreateConnection(target) {
|
|
131
|
+
const existing = this.connections.get(target);
|
|
132
|
+
if (existing !== void 0 && !existing.destroyed) {
|
|
133
|
+
return existing;
|
|
134
|
+
}
|
|
135
|
+
const [host, portStr] = parseAddress(target);
|
|
136
|
+
const port = Number.parseInt(portStr, 10);
|
|
137
|
+
if (Number.isNaN(port) || port < 0 || port > 65535) {
|
|
138
|
+
throw new TransportError(TransportErrorCodes.CONNECT_FAILED, `Invalid port in target address '${target}'`, {
|
|
139
|
+
target
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
let settled = false;
|
|
144
|
+
const tlsConfig = this.config.tls;
|
|
145
|
+
const socket = tlsConfig !== void 0 ? connect({
|
|
146
|
+
host,
|
|
147
|
+
port,
|
|
148
|
+
cert: tlsConfig.cert,
|
|
149
|
+
key: tlsConfig.key,
|
|
150
|
+
ca: tlsConfig.ca,
|
|
151
|
+
rejectUnauthorized: tlsConfig.rejectUnauthorized ?? true
|
|
152
|
+
}) : connect$1({ host, port });
|
|
153
|
+
const connectEvent = tlsConfig !== void 0 ? "secureConnect" : "connect";
|
|
154
|
+
const connectTimer = setTimeout(() => {
|
|
155
|
+
if (!settled) {
|
|
156
|
+
settled = true;
|
|
157
|
+
socket.destroy();
|
|
158
|
+
reject(
|
|
159
|
+
new TransportError(
|
|
160
|
+
TransportErrorCodes.TIMEOUT,
|
|
161
|
+
`Connection to '${target}' timed out after ${this.config.connectTimeout}ms`,
|
|
162
|
+
{ target, timeoutMs: this.config.connectTimeout }
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}, this.config.connectTimeout);
|
|
167
|
+
socket.once(connectEvent, () => {
|
|
168
|
+
if (settled) {
|
|
169
|
+
socket.destroy();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
settled = true;
|
|
173
|
+
clearTimeout(connectTimer);
|
|
174
|
+
this.setupConnection(target, socket);
|
|
175
|
+
resolve(socket);
|
|
176
|
+
});
|
|
177
|
+
socket.once("error", (err) => {
|
|
178
|
+
if (!settled) {
|
|
179
|
+
settled = true;
|
|
180
|
+
clearTimeout(connectTimer);
|
|
181
|
+
reject(
|
|
182
|
+
new TransportError(TransportErrorCodes.CONNECT_FAILED, `Failed to connect to '${target}': ${err.message}`, {
|
|
183
|
+
target
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
setupConnection(target, socket) {
|
|
191
|
+
this.connections.set(target, socket);
|
|
192
|
+
const parser = new FrameParser((frame) => {
|
|
193
|
+
this.handleIncomingFrame(target, frame);
|
|
194
|
+
});
|
|
195
|
+
this.parsers.set(target, parser);
|
|
196
|
+
socket.on("data", (data) => {
|
|
197
|
+
try {
|
|
198
|
+
parser.feed(new Uint8Array(data));
|
|
199
|
+
} catch (err) {
|
|
200
|
+
this.handleConnectionError(target, err instanceof Error ? err : new Error(String(err)));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
socket.on("error", (err) => {
|
|
204
|
+
this.handleConnectionError(target, err);
|
|
205
|
+
});
|
|
206
|
+
socket.on("close", () => {
|
|
207
|
+
this.connections.delete(target);
|
|
208
|
+
this.parsers.delete(target);
|
|
209
|
+
this.rejectPendingForTarget(
|
|
210
|
+
target,
|
|
211
|
+
new TransportError(
|
|
212
|
+
TransportErrorCodes.PEER_UNAVAILABLE,
|
|
213
|
+
`Connection to '${target}' closed before a response was received`,
|
|
214
|
+
{ target }
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
handleIncomingFrame(target, frame) {
|
|
220
|
+
const pendingKey = `${target}:${frame.requestId}`;
|
|
221
|
+
if (frame.frameType === FRAME_TYPE_RESPONSE) {
|
|
222
|
+
const pendingReq = this.pendingRequests.get(pendingKey);
|
|
223
|
+
if (pendingReq !== void 0) {
|
|
224
|
+
this.pendingRequests.delete(pendingKey);
|
|
225
|
+
clearTimeout(pendingReq.timer);
|
|
226
|
+
try {
|
|
227
|
+
const message = decodeTransportMessage(frame.data);
|
|
228
|
+
pendingReq.resolve(message);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
pendingReq.reject(
|
|
231
|
+
new TransportError(
|
|
232
|
+
TransportErrorCodes.DECODE_FAILED,
|
|
233
|
+
`Failed to decode response: ${err instanceof Error ? err.message : String(err)}`
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const pendingStream = this.pendingStreams.get(pendingKey);
|
|
240
|
+
if (pendingStream !== void 0) {
|
|
241
|
+
try {
|
|
242
|
+
const message = decodeTransportMessage(frame.data);
|
|
243
|
+
pendingStream.handler(message.payload);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
this.pendingStreams.delete(pendingKey);
|
|
246
|
+
clearTimeout(pendingStream.timer);
|
|
247
|
+
pendingStream.reject(
|
|
248
|
+
new TransportError(
|
|
249
|
+
TransportErrorCodes.DECODE_FAILED,
|
|
250
|
+
`Failed to decode stream frame: ${err instanceof Error ? err.message : String(err)}`,
|
|
251
|
+
{ target, requestId: frame.requestId }
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (frame.frameType === FRAME_TYPE_STREAM_CHUNK) {
|
|
259
|
+
const pending = this.pendingStreams.get(pendingKey);
|
|
260
|
+
if (pending !== void 0) {
|
|
261
|
+
pending.handler(frame.data);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (frame.frameType === FRAME_TYPE_STREAM_END) {
|
|
266
|
+
const pending = this.pendingStreams.get(pendingKey);
|
|
267
|
+
if (pending !== void 0) {
|
|
268
|
+
this.pendingStreams.delete(pendingKey);
|
|
269
|
+
clearTimeout(pending.timer);
|
|
270
|
+
pending.resolve();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
handleConnectionError(target, err) {
|
|
275
|
+
const socket = this.connections.get(target);
|
|
276
|
+
this.connections.delete(target);
|
|
277
|
+
this.parsers.delete(target);
|
|
278
|
+
if (socket !== void 0 && !socket.destroyed) {
|
|
279
|
+
socket.destroy();
|
|
280
|
+
}
|
|
281
|
+
this.rejectPendingForTarget(
|
|
282
|
+
target,
|
|
283
|
+
new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, `Connection to '${target}' lost: ${err.message}`, {
|
|
284
|
+
target
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
rejectPendingForTarget(target, error) {
|
|
289
|
+
for (const [pendingKey, pending] of this.pendingRequests) {
|
|
290
|
+
if (pending.target !== target) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
clearTimeout(pending.timer);
|
|
294
|
+
this.pendingRequests.delete(pendingKey);
|
|
295
|
+
pending.reject(error);
|
|
296
|
+
}
|
|
297
|
+
for (const [pendingKey, pending] of this.pendingStreams) {
|
|
298
|
+
if (pending.target !== target) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
clearTimeout(pending.timer);
|
|
302
|
+
this.pendingStreams.delete(pendingKey);
|
|
303
|
+
pending.reject(error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
getConnection(target) {
|
|
307
|
+
const conn = this.connections.get(target);
|
|
308
|
+
if (conn?.destroyed) {
|
|
309
|
+
this.connections.delete(target);
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
return conn;
|
|
313
|
+
}
|
|
314
|
+
async shutdown() {
|
|
315
|
+
if (this.closed) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this.closed = true;
|
|
319
|
+
for (const [, pending] of this.pendingRequests) {
|
|
320
|
+
clearTimeout(pending.timer);
|
|
321
|
+
pending.reject(new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, "Connection pool shutting down"));
|
|
322
|
+
}
|
|
323
|
+
this.pendingRequests.clear();
|
|
324
|
+
for (const [, pending] of this.pendingStreams) {
|
|
325
|
+
clearTimeout(pending.timer);
|
|
326
|
+
pending.reject(new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, "Connection pool shutting down"));
|
|
327
|
+
}
|
|
328
|
+
this.pendingStreams.clear();
|
|
329
|
+
const closePromises = [];
|
|
330
|
+
for (const [target, socket] of this.connections) {
|
|
331
|
+
closePromises.push(
|
|
332
|
+
new Promise((resolve) => {
|
|
333
|
+
socket.once("close", () => resolve());
|
|
334
|
+
socket.destroy();
|
|
335
|
+
})
|
|
336
|
+
);
|
|
337
|
+
this.connections.delete(target);
|
|
338
|
+
}
|
|
339
|
+
this.parsers.clear();
|
|
340
|
+
await Promise.all(closePromises);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
function writeAwaitingDrain(socket, frame) {
|
|
344
|
+
if (socket.write(frame)) {
|
|
345
|
+
return Promise.resolve();
|
|
346
|
+
}
|
|
347
|
+
return new Promise((resolve) => {
|
|
348
|
+
socket.once("drain", resolve);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
var TcpServer = class {
|
|
352
|
+
server = null;
|
|
353
|
+
clients = /* @__PURE__ */ new Set();
|
|
354
|
+
config;
|
|
355
|
+
handler = null;
|
|
356
|
+
closed = false;
|
|
357
|
+
boundPort = 0;
|
|
358
|
+
tlsServer = null;
|
|
359
|
+
constructor(config) {
|
|
360
|
+
this.config = config;
|
|
361
|
+
}
|
|
362
|
+
getPort() {
|
|
363
|
+
return this.boundPort;
|
|
364
|
+
}
|
|
365
|
+
updateTlsConfig(nextTlsConfig) {
|
|
366
|
+
this.config = {
|
|
367
|
+
...this.config,
|
|
368
|
+
tls: nextTlsConfig
|
|
369
|
+
};
|
|
370
|
+
if (this.server === null) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (this.tlsServer === null) {
|
|
374
|
+
throw new TransportError(
|
|
375
|
+
TransportErrorCodes.PEER_UNAVAILABLE,
|
|
376
|
+
"Cannot rotate TLS context on a server that was started without TLS"
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
this.tlsServer.setSecureContext(toSecureContextOptions(nextTlsConfig));
|
|
380
|
+
}
|
|
381
|
+
async start(handler) {
|
|
382
|
+
if (this.closed) {
|
|
383
|
+
throw new TransportError(TransportErrorCodes.PEER_UNAVAILABLE, "TCP server has been shut down");
|
|
384
|
+
}
|
|
385
|
+
const previousHandler = this.handler;
|
|
386
|
+
this.handler = handler;
|
|
387
|
+
if (this.server !== null) {
|
|
388
|
+
return () => {
|
|
389
|
+
if (this.handler === handler) {
|
|
390
|
+
this.handler = previousHandler ?? null;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return new Promise((resolve, reject) => {
|
|
395
|
+
const tlsConfig = this.config.tls;
|
|
396
|
+
const server = tlsConfig !== void 0 ? createServer(
|
|
397
|
+
{
|
|
398
|
+
cert: tlsConfig.cert,
|
|
399
|
+
key: tlsConfig.key,
|
|
400
|
+
ca: tlsConfig.ca,
|
|
401
|
+
requestCert: true,
|
|
402
|
+
rejectUnauthorized: tlsConfig.rejectUnauthorized ?? true
|
|
403
|
+
},
|
|
404
|
+
(clientSocket) => {
|
|
405
|
+
this.handleClient(clientSocket);
|
|
406
|
+
}
|
|
407
|
+
) : createServer$1((clientSocket) => {
|
|
408
|
+
this.handleClient(clientSocket);
|
|
409
|
+
});
|
|
410
|
+
server.on("error", (err) => {
|
|
411
|
+
if (this.server === null) {
|
|
412
|
+
reject(new TransportError(TransportErrorCodes.CONNECT_FAILED, `TCP server failed to start: ${err.message}`));
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
server.listen(this.config.port, this.config.host, () => {
|
|
416
|
+
this.server = server;
|
|
417
|
+
this.tlsServer = tlsConfig !== void 0 ? server : null;
|
|
418
|
+
const addr = server.address();
|
|
419
|
+
if (addr !== null && typeof addr === "object") {
|
|
420
|
+
this.boundPort = addr.port;
|
|
421
|
+
}
|
|
422
|
+
resolve(() => {
|
|
423
|
+
if (this.handler === handler) {
|
|
424
|
+
this.handler = previousHandler ?? null;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
handleClient(socket) {
|
|
431
|
+
if (this.clients.size >= this.config.maxConnections) {
|
|
432
|
+
socket.destroy();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
this.clients.add(socket);
|
|
436
|
+
const parser = new FrameParser((frame) => {
|
|
437
|
+
if (frame.frameType !== FRAME_TYPE_REQUEST) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (this.handler === null) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
this.processRequest(socket, frame, this.handler);
|
|
444
|
+
});
|
|
445
|
+
socket.on("data", (data) => {
|
|
446
|
+
try {
|
|
447
|
+
parser.feed(new Uint8Array(data));
|
|
448
|
+
} catch (_) {
|
|
449
|
+
socket.destroy();
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
socket.on("error", () => {
|
|
453
|
+
socket.destroy();
|
|
454
|
+
});
|
|
455
|
+
socket.on("close", () => {
|
|
456
|
+
this.clients.delete(socket);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
processRequest(socket, frame, handler) {
|
|
460
|
+
try {
|
|
461
|
+
const message = decodeTransportMessage(frame.data);
|
|
462
|
+
let respondCount = 0;
|
|
463
|
+
const respond = (response) => {
|
|
464
|
+
if (socket.destroyed) {
|
|
465
|
+
return Promise.resolve();
|
|
466
|
+
}
|
|
467
|
+
respondCount++;
|
|
468
|
+
if (respondCount === 1) {
|
|
469
|
+
const responseBytes = encodeTransportMessage(response);
|
|
470
|
+
return writeAwaitingDrain(socket, encodeFrame(FRAME_TYPE_RESPONSE, frame.requestId, responseBytes));
|
|
471
|
+
}
|
|
472
|
+
return writeAwaitingDrain(socket, encodeFrame(FRAME_TYPE_STREAM_CHUNK, frame.requestId, response.payload));
|
|
473
|
+
};
|
|
474
|
+
const result = handler(message, respond);
|
|
475
|
+
const finalize = () => {
|
|
476
|
+
if (respondCount > 0 && !socket.destroyed) {
|
|
477
|
+
const endFrame = encodeFrame(FRAME_TYPE_STREAM_END, frame.requestId, new Uint8Array(0));
|
|
478
|
+
socket.write(endFrame);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
if (result instanceof Promise) {
|
|
482
|
+
result.then(finalize, (err) => {
|
|
483
|
+
this.sendErrorResponse(socket, frame.requestId, err);
|
|
484
|
+
});
|
|
485
|
+
} else {
|
|
486
|
+
finalize();
|
|
487
|
+
}
|
|
488
|
+
} catch (err) {
|
|
489
|
+
this.sendErrorResponse(socket, frame.requestId, err);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
sendErrorResponse(socket, requestId, err) {
|
|
493
|
+
if (socket.destroyed) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
497
|
+
const errorResponse = {
|
|
498
|
+
type: "error",
|
|
499
|
+
sourceId: "",
|
|
500
|
+
requestId,
|
|
501
|
+
payload: new TextEncoder().encode(errorMsg)
|
|
502
|
+
};
|
|
503
|
+
const responseBytes = encodeTransportMessage(errorResponse);
|
|
504
|
+
const responseFrame = encodeFrame(FRAME_TYPE_RESPONSE, requestId, responseBytes);
|
|
505
|
+
socket.write(responseFrame);
|
|
506
|
+
}
|
|
507
|
+
async shutdown() {
|
|
508
|
+
if (this.closed) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
this.closed = true;
|
|
512
|
+
this.handler = null;
|
|
513
|
+
for (const client of this.clients) {
|
|
514
|
+
client.destroy();
|
|
515
|
+
}
|
|
516
|
+
this.clients.clear();
|
|
517
|
+
if (this.server !== null) {
|
|
518
|
+
await new Promise((resolve) => {
|
|
519
|
+
const server = this.server;
|
|
520
|
+
if (server === null) {
|
|
521
|
+
resolve();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
server.close(() => resolve());
|
|
525
|
+
});
|
|
526
|
+
this.server = null;
|
|
527
|
+
this.tlsServer = null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
function toSecureContextOptions(tlsConfig) {
|
|
532
|
+
return {
|
|
533
|
+
cert: tlsConfig.cert,
|
|
534
|
+
key: tlsConfig.key,
|
|
535
|
+
ca: tlsConfig.ca
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/distribution/transport/tcp/index.ts
|
|
540
|
+
function createTcpTransport(nodeId, config) {
|
|
541
|
+
const resolvedConfig = {
|
|
542
|
+
...DEFAULT_TCP_CONFIG,
|
|
543
|
+
...config
|
|
544
|
+
};
|
|
545
|
+
const pool = new TcpConnectionPool(resolvedConfig);
|
|
546
|
+
const server = new TcpServer(resolvedConfig);
|
|
547
|
+
let isShutdown = false;
|
|
548
|
+
function assertNotShutdown() {
|
|
549
|
+
if (isShutdown) {
|
|
550
|
+
throw new TransportError(
|
|
551
|
+
TransportErrorCodes.PEER_UNAVAILABLE,
|
|
552
|
+
`TCP transport for node '${nodeId}' has been shut down`,
|
|
553
|
+
{ nodeId }
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
getPort() {
|
|
559
|
+
return server.getPort();
|
|
560
|
+
},
|
|
561
|
+
rotateTlsContext(nextTlsConfig) {
|
|
562
|
+
assertNotShutdown();
|
|
563
|
+
pool.updateTlsConfig(nextTlsConfig);
|
|
564
|
+
server.updateTlsConfig(nextTlsConfig);
|
|
565
|
+
},
|
|
566
|
+
async send(target, message) {
|
|
567
|
+
assertNotShutdown();
|
|
568
|
+
if (message.payload.byteLength > MAX_MESSAGE_SIZE_BYTES) {
|
|
569
|
+
throw new TransportError(
|
|
570
|
+
TransportErrorCodes.MESSAGE_TOO_LARGE,
|
|
571
|
+
`Message payload (${message.payload.byteLength} bytes) exceeds the ${MAX_MESSAGE_SIZE_BYTES} byte limit`,
|
|
572
|
+
{ target, requestId: message.requestId, payloadSize: message.payload.byteLength }
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
return pool.send(target, message);
|
|
576
|
+
},
|
|
577
|
+
async stream(target, message, handler) {
|
|
578
|
+
assertNotShutdown();
|
|
579
|
+
return pool.stream(target, message, handler);
|
|
580
|
+
},
|
|
581
|
+
async listen(handler) {
|
|
582
|
+
assertNotShutdown();
|
|
583
|
+
return server.start(handler);
|
|
584
|
+
},
|
|
585
|
+
async shutdown() {
|
|
586
|
+
if (isShutdown) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
isShutdown = true;
|
|
590
|
+
await Promise.all([pool.shutdown(), server.shutdown()]);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export { createTcpTransport };
|