@replit/river 0.23.12 → 0.23.14
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/{chunk-3AW3IXVD.js → chunk-4PVU7J25.js} +1 -21
- package/dist/chunk-4PVU7J25.js.map +1 -0
- package/dist/{chunk-HDBVL7EF.js → chunk-BEALFLCB.js} +2 -2
- package/dist/chunk-D2DHRRBN.js +476 -0
- package/dist/chunk-D2DHRRBN.js.map +1 -0
- package/dist/{chunk-7RUKEUKE.js → chunk-GCCRVSMR.js} +33 -4
- package/dist/chunk-GCCRVSMR.js.map +1 -0
- package/dist/{chunk-XZ6IOBM5.js → chunk-GN4YEXT7.js} +2 -2
- package/dist/chunk-GN4YEXT7.js.map +1 -0
- package/dist/chunk-O2AVDJCQ.js +335 -0
- package/dist/chunk-O2AVDJCQ.js.map +1 -0
- package/dist/chunk-OTVTKAN6.js +451 -0
- package/dist/chunk-OTVTKAN6.js.map +1 -0
- package/dist/chunk-WUL63FR6.js +335 -0
- package/dist/chunk-WUL63FR6.js.map +1 -0
- package/dist/{chunk-H6KTH6W6.js → chunk-YCLZWES2.js} +2 -2
- package/dist/client-e13979ac.d.ts +52 -0
- package/dist/codec/index.js +20 -2
- package/dist/codec/index.js.map +1 -1
- package/dist/{connection-8debd45f.d.ts → connection-5d0978ce.d.ts} +1 -1
- package/dist/{connection-581558f8.d.ts → connection-e57e98ea.d.ts} +1 -1
- package/dist/{transport-47af1c81.d.ts → handshake-5665ffd3.d.ts} +101 -153
- package/dist/{index-60f03cb7.d.ts → index-ea74cdbb.d.ts} +1 -1
- package/dist/logging/index.d.cts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/router/index.cjs +16 -1
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +8 -6
- package/dist/router/index.d.ts +8 -6
- package/dist/router/index.js +2 -2
- package/dist/server-1cfc88d1.d.ts +24 -0
- package/dist/{services-ca72c9f8.d.ts → services-86c4d10d.d.ts} +3 -2
- package/dist/transport/impls/uds/client.cjs +303 -180
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +6 -5
- package/dist/transport/impls/uds/client.d.ts +6 -5
- package/dist/transport/impls/uds/client.js +6 -4
- package/dist/transport/impls/uds/client.js.map +1 -1
- package/dist/transport/impls/uds/server.cjs +396 -234
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +6 -5
- package/dist/transport/impls/uds/server.d.ts +6 -5
- package/dist/transport/impls/uds/server.js +8 -6
- package/dist/transport/impls/uds/server.js.map +1 -1
- package/dist/transport/impls/ws/client.cjs +305 -182
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +6 -5
- package/dist/transport/impls/ws/client.d.ts +6 -5
- package/dist/transport/impls/ws/client.js +6 -4
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +350 -188
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +4 -3
- package/dist/transport/impls/ws/server.d.ts +4 -3
- package/dist/transport/impls/ws/server.js +8 -6
- package/dist/transport/impls/ws/server.js.map +1 -1
- package/dist/transport/index.cjs +338 -142
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +4 -2
- package/dist/transport/index.d.ts +4 -2
- package/dist/transport/index.js +14 -8
- package/dist/util/testHelpers.cjs +10 -6
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +5 -4
- package/dist/util/testHelpers.d.ts +5 -4
- package/dist/util/testHelpers.js +4 -5
- package/dist/util/testHelpers.js.map +1 -1
- package/package.json +13 -14
- package/dist/chunk-3AW3IXVD.js.map +0 -1
- package/dist/chunk-7RUKEUKE.js.map +0 -1
- package/dist/chunk-VRU4IKRT.js +0 -1392
- package/dist/chunk-VRU4IKRT.js.map +0 -1
- package/dist/chunk-XZ6IOBM5.js.map +0 -1
- /package/dist/{chunk-HDBVL7EF.js.map → chunk-BEALFLCB.js.map} +0 -0
- /package/dist/{chunk-H6KTH6W6.js.map → chunk-YCLZWES2.js.map} +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseLogger,
|
|
3
|
+
createLogProxy
|
|
4
|
+
} from "./chunk-6LCL2ZZF.js";
|
|
5
|
+
import {
|
|
6
|
+
Session,
|
|
7
|
+
defaultTransportOptions
|
|
8
|
+
} from "./chunk-O2AVDJCQ.js";
|
|
9
|
+
import {
|
|
10
|
+
OpaqueTransportMessageSchema,
|
|
11
|
+
createConnectionTelemetryInfo,
|
|
12
|
+
isAck
|
|
13
|
+
} from "./chunk-GCCRVSMR.js";
|
|
14
|
+
|
|
15
|
+
// transport/events.ts
|
|
16
|
+
var ProtocolError = {
|
|
17
|
+
RetriesExceeded: "conn_retry_exceeded",
|
|
18
|
+
HandshakeFailed: "handshake_failed",
|
|
19
|
+
MessageOrderingViolated: "message_ordering_violated"
|
|
20
|
+
};
|
|
21
|
+
var EventDispatcher = class {
|
|
22
|
+
eventListeners = {};
|
|
23
|
+
removeAllListeners() {
|
|
24
|
+
this.eventListeners = {};
|
|
25
|
+
}
|
|
26
|
+
numberOfListeners(eventType) {
|
|
27
|
+
return this.eventListeners[eventType]?.size ?? 0;
|
|
28
|
+
}
|
|
29
|
+
addEventListener(eventType, handler) {
|
|
30
|
+
if (!this.eventListeners[eventType]) {
|
|
31
|
+
this.eventListeners[eventType] = /* @__PURE__ */ new Set();
|
|
32
|
+
}
|
|
33
|
+
this.eventListeners[eventType]?.add(handler);
|
|
34
|
+
}
|
|
35
|
+
removeEventListener(eventType, handler) {
|
|
36
|
+
const handlers = this.eventListeners[eventType];
|
|
37
|
+
if (handlers) {
|
|
38
|
+
this.eventListeners[eventType]?.delete(handler);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
dispatchEvent(eventType, event) {
|
|
42
|
+
const handlers = this.eventListeners[eventType];
|
|
43
|
+
if (handlers) {
|
|
44
|
+
const copy = [...handlers];
|
|
45
|
+
for (const handler of copy) {
|
|
46
|
+
handler(event);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// transport/transport.ts
|
|
53
|
+
import { Value } from "@sinclair/typebox/value";
|
|
54
|
+
import { SpanStatusCode } from "@opentelemetry/api";
|
|
55
|
+
var Transport = class {
|
|
56
|
+
/**
|
|
57
|
+
* The status of the transport.
|
|
58
|
+
*/
|
|
59
|
+
status;
|
|
60
|
+
/**
|
|
61
|
+
* The {@link Codec} used to encode and decode messages.
|
|
62
|
+
*/
|
|
63
|
+
codec;
|
|
64
|
+
/**
|
|
65
|
+
* The client ID of this transport.
|
|
66
|
+
*/
|
|
67
|
+
clientId;
|
|
68
|
+
/**
|
|
69
|
+
* The map of {@link Session}s managed by this transport.
|
|
70
|
+
*/
|
|
71
|
+
sessions;
|
|
72
|
+
/**
|
|
73
|
+
* The map of {@link Connection}s managed by this transport.
|
|
74
|
+
*/
|
|
75
|
+
get connections() {
|
|
76
|
+
return new Map(
|
|
77
|
+
[...this.sessions].map(([client, session]) => [client, session.connection]).filter((entry) => entry[1] !== void 0)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The event dispatcher for handling events of type EventTypes.
|
|
82
|
+
*/
|
|
83
|
+
eventDispatcher;
|
|
84
|
+
/**
|
|
85
|
+
* The options for this transport.
|
|
86
|
+
*/
|
|
87
|
+
options;
|
|
88
|
+
log;
|
|
89
|
+
/**
|
|
90
|
+
* Creates a new Transport instance.
|
|
91
|
+
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
92
|
+
* @param codec The codec used to encode and decode messages.
|
|
93
|
+
* @param clientId The client ID of this transport.
|
|
94
|
+
*/
|
|
95
|
+
constructor(clientId, providedOptions) {
|
|
96
|
+
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
97
|
+
this.eventDispatcher = new EventDispatcher();
|
|
98
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
99
|
+
this.codec = this.options.codec;
|
|
100
|
+
this.clientId = clientId;
|
|
101
|
+
this.status = "open";
|
|
102
|
+
}
|
|
103
|
+
bindLogger(fn, level) {
|
|
104
|
+
if (typeof fn === "function") {
|
|
105
|
+
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.log = createLogProxy(fn);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Called when a new connection is established
|
|
112
|
+
* and we know the identity of the connected client.
|
|
113
|
+
* @param conn The connection object.
|
|
114
|
+
*/
|
|
115
|
+
onConnect(conn, session, isTransparentReconnect) {
|
|
116
|
+
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
117
|
+
status: "connect",
|
|
118
|
+
conn
|
|
119
|
+
});
|
|
120
|
+
conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);
|
|
121
|
+
session.replaceWithNewConnection(conn, isTransparentReconnect);
|
|
122
|
+
this.log?.info(`connected to ${session.to}`, {
|
|
123
|
+
...conn.loggingMetadata,
|
|
124
|
+
...session.loggingMetadata
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
createSession(to, conn, propagationCtx) {
|
|
128
|
+
const session = new Session(
|
|
129
|
+
conn,
|
|
130
|
+
this.clientId,
|
|
131
|
+
to,
|
|
132
|
+
this.options,
|
|
133
|
+
propagationCtx
|
|
134
|
+
);
|
|
135
|
+
if (this.log) {
|
|
136
|
+
session.bindLogger(this.log);
|
|
137
|
+
}
|
|
138
|
+
this.sessions.set(session.to, session);
|
|
139
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
140
|
+
status: "connect",
|
|
141
|
+
session
|
|
142
|
+
});
|
|
143
|
+
return session;
|
|
144
|
+
}
|
|
145
|
+
createNewSession({
|
|
146
|
+
to,
|
|
147
|
+
conn,
|
|
148
|
+
sessionId,
|
|
149
|
+
propagationCtx
|
|
150
|
+
}) {
|
|
151
|
+
let session = this.sessions.get(to);
|
|
152
|
+
if (session !== void 0) {
|
|
153
|
+
this.log?.info(
|
|
154
|
+
`session for ${to} already exists, replacing it with a new session as requested`,
|
|
155
|
+
session.loggingMetadata
|
|
156
|
+
);
|
|
157
|
+
this.deleteSession({
|
|
158
|
+
session,
|
|
159
|
+
closeHandshakingConnection: false
|
|
160
|
+
});
|
|
161
|
+
session = void 0;
|
|
162
|
+
}
|
|
163
|
+
session = this.createSession(to, conn, propagationCtx);
|
|
164
|
+
session.advertisedSessionId = sessionId;
|
|
165
|
+
this.log?.info(`created new session for ${to}`, session.loggingMetadata);
|
|
166
|
+
return session;
|
|
167
|
+
}
|
|
168
|
+
getExistingSession({
|
|
169
|
+
to,
|
|
170
|
+
sessionId,
|
|
171
|
+
nextExpectedSeq
|
|
172
|
+
}) {
|
|
173
|
+
const session = this.sessions.get(to);
|
|
174
|
+
if (
|
|
175
|
+
// reject this request if there was no previous session to replace
|
|
176
|
+
session === void 0 || // or if both parties do not agree about the next expected sequence number
|
|
177
|
+
session.nextExpectedAck < nextExpectedSeq || // or if both parties do not agree on the advertised session id
|
|
178
|
+
session.advertisedSessionId !== sessionId
|
|
179
|
+
) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
this.log?.info(
|
|
183
|
+
`reused existing session for ${to}`,
|
|
184
|
+
session.loggingMetadata
|
|
185
|
+
);
|
|
186
|
+
return session;
|
|
187
|
+
}
|
|
188
|
+
getOrCreateSession({
|
|
189
|
+
to,
|
|
190
|
+
conn,
|
|
191
|
+
handshakingConn,
|
|
192
|
+
sessionId,
|
|
193
|
+
propagationCtx
|
|
194
|
+
}) {
|
|
195
|
+
let session = this.sessions.get(to);
|
|
196
|
+
const isReconnect = session !== void 0;
|
|
197
|
+
let isTransparentReconnect = isReconnect;
|
|
198
|
+
if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
|
|
199
|
+
this.log?.info(
|
|
200
|
+
`session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
|
|
201
|
+
session.loggingMetadata
|
|
202
|
+
);
|
|
203
|
+
this.deleteSession({
|
|
204
|
+
session,
|
|
205
|
+
closeHandshakingConnection: handshakingConn !== void 0,
|
|
206
|
+
handshakingConn
|
|
207
|
+
});
|
|
208
|
+
isTransparentReconnect = false;
|
|
209
|
+
session = void 0;
|
|
210
|
+
}
|
|
211
|
+
if (!session) {
|
|
212
|
+
session = this.createSession(to, conn, propagationCtx);
|
|
213
|
+
this.log?.info(
|
|
214
|
+
`no session for ${to}, created a new one`,
|
|
215
|
+
session.loggingMetadata
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (sessionId !== void 0) {
|
|
219
|
+
session.advertisedSessionId = sessionId;
|
|
220
|
+
}
|
|
221
|
+
if (handshakingConn !== void 0) {
|
|
222
|
+
session.replaceWithNewHandshakingConnection(handshakingConn);
|
|
223
|
+
}
|
|
224
|
+
return { session, isReconnect, isTransparentReconnect };
|
|
225
|
+
}
|
|
226
|
+
deleteSession({
|
|
227
|
+
session,
|
|
228
|
+
closeHandshakingConnection,
|
|
229
|
+
handshakingConn
|
|
230
|
+
}) {
|
|
231
|
+
if (closeHandshakingConnection) {
|
|
232
|
+
session.closeHandshakingConnection(handshakingConn);
|
|
233
|
+
}
|
|
234
|
+
session.close();
|
|
235
|
+
session.telemetry.span.end();
|
|
236
|
+
this.sessions.delete(session.to);
|
|
237
|
+
this.log?.info(
|
|
238
|
+
`session ${session.id} disconnect from ${session.to}`,
|
|
239
|
+
session.loggingMetadata
|
|
240
|
+
);
|
|
241
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
242
|
+
status: "disconnect",
|
|
243
|
+
session
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The downstream implementation needs to call this when a connection is closed.
|
|
248
|
+
* @param conn The connection object.
|
|
249
|
+
* @param connectedTo The peer we are connected to.
|
|
250
|
+
*/
|
|
251
|
+
onDisconnect(conn, session) {
|
|
252
|
+
if (session.connection !== void 0 && session.connection.id !== conn.id) {
|
|
253
|
+
session.telemetry.span.addEvent("onDisconnect race");
|
|
254
|
+
this.log?.warn("onDisconnect race", {
|
|
255
|
+
clientId: this.clientId,
|
|
256
|
+
...session.loggingMetadata,
|
|
257
|
+
...conn.loggingMetadata,
|
|
258
|
+
tags: ["invariant-violation"]
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
conn.telemetry?.span.end();
|
|
263
|
+
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
264
|
+
status: "disconnect",
|
|
265
|
+
conn
|
|
266
|
+
});
|
|
267
|
+
session.connection = void 0;
|
|
268
|
+
session.beginGrace(() => {
|
|
269
|
+
if (session.connection !== void 0) {
|
|
270
|
+
session.telemetry.span.addEvent("session grace period race");
|
|
271
|
+
this.log?.warn("session grace period race", {
|
|
272
|
+
clientId: this.clientId,
|
|
273
|
+
...session.loggingMetadata,
|
|
274
|
+
...conn.loggingMetadata,
|
|
275
|
+
tags: ["invariant-violation"]
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
session.telemetry.span.addEvent("session grace period expired");
|
|
280
|
+
this.deleteSession({
|
|
281
|
+
session,
|
|
282
|
+
closeHandshakingConnection: true,
|
|
283
|
+
handshakingConn: conn
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
|
|
289
|
+
* @param msg The message to parse.
|
|
290
|
+
* @returns The parsed message, or null if the message is malformed or invalid.
|
|
291
|
+
*/
|
|
292
|
+
parseMsg(msg, conn) {
|
|
293
|
+
const parsedMsg = this.codec.fromBuffer(msg);
|
|
294
|
+
if (parsedMsg === null) {
|
|
295
|
+
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
296
|
+
this.log?.error(
|
|
297
|
+
`received malformed msg, killing conn: ${decodedBuffer}`,
|
|
298
|
+
{
|
|
299
|
+
clientId: this.clientId,
|
|
300
|
+
...conn.loggingMetadata
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
306
|
+
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
307
|
+
clientId: this.clientId,
|
|
308
|
+
...conn.loggingMetadata,
|
|
309
|
+
validationErrors: [
|
|
310
|
+
...Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
311
|
+
]
|
|
312
|
+
});
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
return parsedMsg;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Called when a message is received by this transport.
|
|
319
|
+
* You generally shouldn't need to override this in downstream transport implementations.
|
|
320
|
+
* @param msg The received message.
|
|
321
|
+
*/
|
|
322
|
+
handleMsg(msg, conn) {
|
|
323
|
+
if (this.getStatus() !== "open")
|
|
324
|
+
return;
|
|
325
|
+
const session = this.sessions.get(msg.from);
|
|
326
|
+
if (!session) {
|
|
327
|
+
this.log?.error(`received message for unknown session from ${msg.from}`, {
|
|
328
|
+
clientId: this.clientId,
|
|
329
|
+
transportMessage: msg,
|
|
330
|
+
...conn.loggingMetadata,
|
|
331
|
+
tags: ["invariant-violation"]
|
|
332
|
+
});
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
session.cancelGrace();
|
|
336
|
+
this.log?.debug(`received msg`, {
|
|
337
|
+
clientId: this.clientId,
|
|
338
|
+
transportMessage: msg,
|
|
339
|
+
...conn.loggingMetadata
|
|
340
|
+
});
|
|
341
|
+
if (msg.seq !== session.nextExpectedSeq) {
|
|
342
|
+
if (msg.seq < session.nextExpectedSeq) {
|
|
343
|
+
this.log?.debug(
|
|
344
|
+
`received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,
|
|
345
|
+
{
|
|
346
|
+
clientId: this.clientId,
|
|
347
|
+
transportMessage: msg,
|
|
348
|
+
...conn.loggingMetadata
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
|
|
353
|
+
this.log?.error(`${errMsg}, marking connection as dead`, {
|
|
354
|
+
clientId: this.clientId,
|
|
355
|
+
transportMessage: msg,
|
|
356
|
+
...conn.loggingMetadata,
|
|
357
|
+
tags: ["invariant-violation"]
|
|
358
|
+
});
|
|
359
|
+
this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
|
|
360
|
+
session.telemetry.span.setStatus({
|
|
361
|
+
code: SpanStatusCode.ERROR,
|
|
362
|
+
message: "message order violated"
|
|
363
|
+
});
|
|
364
|
+
this.deleteSession({ session, closeHandshakingConnection: true });
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
session.updateBookkeeping(msg.ack, msg.seq);
|
|
369
|
+
if (!isAck(msg.controlFlags)) {
|
|
370
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
371
|
+
} else {
|
|
372
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
373
|
+
clientId: this.clientId,
|
|
374
|
+
transportMessage: msg,
|
|
375
|
+
...conn.loggingMetadata
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Adds a listener to this transport.
|
|
381
|
+
* @param the type of event to listen for
|
|
382
|
+
* @param handler The message handler to add.
|
|
383
|
+
*/
|
|
384
|
+
addEventListener(type, handler) {
|
|
385
|
+
this.eventDispatcher.addEventListener(type, handler);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Removes a listener from this transport.
|
|
389
|
+
* @param the type of event to un-listen on
|
|
390
|
+
* @param handler The message handler to remove.
|
|
391
|
+
*/
|
|
392
|
+
removeEventListener(type, handler) {
|
|
393
|
+
this.eventDispatcher.removeEventListener(type, handler);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Sends a message over this transport, delegating to the appropriate connection to actually
|
|
397
|
+
* send the message.
|
|
398
|
+
* @param msg The message to send.
|
|
399
|
+
* @returns The ID of the sent message or undefined if it wasn't sent
|
|
400
|
+
*/
|
|
401
|
+
send(to, msg) {
|
|
402
|
+
if (this.getStatus() === "closed") {
|
|
403
|
+
const err = "transport is closed, cant send";
|
|
404
|
+
this.log?.error(err, {
|
|
405
|
+
clientId: this.clientId,
|
|
406
|
+
transportMessage: msg,
|
|
407
|
+
tags: ["invariant-violation"]
|
|
408
|
+
});
|
|
409
|
+
throw new Error(err);
|
|
410
|
+
}
|
|
411
|
+
return this.getOrCreateSession({ to }).session.send(msg);
|
|
412
|
+
}
|
|
413
|
+
// control helpers
|
|
414
|
+
sendCloseStream(to, streamId) {
|
|
415
|
+
return this.send(to, {
|
|
416
|
+
streamId,
|
|
417
|
+
controlFlags: 4 /* StreamClosedBit */,
|
|
418
|
+
payload: {
|
|
419
|
+
type: "CLOSE"
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
protocolError(type, message) {
|
|
424
|
+
this.eventDispatcher.dispatchEvent("protocolError", { type, message });
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Default close implementation for transports. You should override this in the downstream
|
|
428
|
+
* implementation if you need to do any additional cleanup and call super.close() at the end.
|
|
429
|
+
* Closes the transport. Any messages sent while the transport is closed will be silently discarded.
|
|
430
|
+
*/
|
|
431
|
+
close() {
|
|
432
|
+
this.status = "closed";
|
|
433
|
+
for (const session of this.sessions.values()) {
|
|
434
|
+
this.deleteSession({ session, closeHandshakingConnection: true });
|
|
435
|
+
}
|
|
436
|
+
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
437
|
+
status: this.status
|
|
438
|
+
});
|
|
439
|
+
this.eventDispatcher.removeAllListeners();
|
|
440
|
+
this.log?.info(`manually closed transport`, { clientId: this.clientId });
|
|
441
|
+
}
|
|
442
|
+
getStatus() {
|
|
443
|
+
return this.status;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
export {
|
|
448
|
+
ProtocolError,
|
|
449
|
+
Transport
|
|
450
|
+
};
|
|
451
|
+
//# sourceMappingURL=chunk-OTVTKAN6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../transport/events.ts","../transport/transport.ts"],"sourcesContent":["import { TransportStatus } from '.';\nimport { OpaqueTransportMessage } from './message';\nimport { Connection, Session } from './session';\n\ntype ConnectionStatus = 'connect' | 'disconnect';\nexport const ProtocolError = {\n RetriesExceeded: 'conn_retry_exceeded',\n HandshakeFailed: 'handshake_failed',\n MessageOrderingViolated: 'message_ordering_violated',\n} as const;\n\nexport type ProtocolErrorType =\n (typeof ProtocolError)[keyof typeof ProtocolError];\n\nexport interface EventMap {\n message: OpaqueTransportMessage;\n connectionStatus: {\n status: ConnectionStatus;\n conn: Connection;\n };\n sessionStatus: {\n status: ConnectionStatus;\n session: Session<Connection>;\n };\n protocolError: {\n type: ProtocolErrorType;\n message: string;\n };\n transportStatus: {\n status: TransportStatus;\n };\n}\n\nexport type EventTypes = keyof EventMap;\nexport type EventHandler<K extends EventTypes> = (\n event: EventMap[K],\n) => unknown;\n\nexport class EventDispatcher<T extends EventTypes> {\n private eventListeners: { [K in T]?: Set<EventHandler<K>> } = {};\n\n removeAllListeners() {\n this.eventListeners = {};\n }\n\n numberOfListeners<K extends T>(eventType: K) {\n return this.eventListeners[eventType]?.size ?? 0;\n }\n\n addEventListener<K extends T>(eventType: K, handler: EventHandler<K>) {\n if (!this.eventListeners[eventType]) {\n this.eventListeners[eventType] = new Set();\n }\n\n this.eventListeners[eventType]?.add(handler);\n }\n\n removeEventListener<K extends T>(eventType: K, handler: EventHandler<K>) {\n const handlers = this.eventListeners[eventType];\n if (handlers) {\n this.eventListeners[eventType]?.delete(handler);\n }\n }\n\n dispatchEvent<K extends T>(eventType: K, event: EventMap[K]) {\n const handlers = this.eventListeners[eventType];\n if (handlers) {\n // copying ensures that adding more listeners in a handler doesn't\n // affect the current dispatch.\n const copy = [...handlers];\n for (const handler of copy) {\n handler(event);\n }\n }\n }\n}\n","import { Codec } from '../codec/types';\nimport { Value } from '@sinclair/typebox/value';\nimport {\n OpaqueTransportMessage,\n OpaqueTransportMessageSchema,\n TransportClientId,\n PartialTransportMessage,\n ControlFlags,\n ControlMessagePayloadSchema,\n isAck,\n} from './message';\nimport {\n BaseLogger,\n LogFn,\n Logger,\n LoggingLevel,\n createLogProxy,\n} from '../logging/log';\nimport {\n EventDispatcher,\n EventHandler,\n EventTypes,\n ProtocolError,\n ProtocolErrorType,\n} from './events';\nimport { Connection, Session } from './session';\nimport { Static } from '@sinclair/typebox';\nimport { PropagationContext, createConnectionTelemetryInfo } from '../tracing';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport {\n ProvidedTransportOptions,\n TransportOptions,\n defaultTransportOptions,\n} from './options';\n\n/**\n * Represents the possible states of a transport.\n * @property {'open'} open - The transport is open and operational (note that this doesn't mean it is actively connected)\n * @property {'closed'} closed - The transport is permanently closed and cannot be reopened.\n */\nexport type TransportStatus = 'open' | 'closed';\n\n/**\n * Transports manage the lifecycle (creation/deletion) of sessions and connections. Its responsibilities include:\n *\n * 1) Constructing a new {@link Session} and {@link Connection} on {@link TransportMessage}s from new clients.\n * After constructing the {@link Connection}, {@link onConnect} is called which adds it to the connection map.\n * 2) Delegating message listening of the connection to the newly created {@link Connection}.\n * From this point on, the {@link Connection} is responsible for *reading* and *writing*\n * messages from the connection.\n * 3) When a connection is closed, the {@link Transport} calls {@link onDisconnect} which closes the\n * connection via {@link Connection.close} and removes it from the {@link connections} map.\n\n *\n * ```plaintext\n * ▲\n * incoming │\n * messages │\n * ▼\n * ┌─────────────┐ 1:N ┌───────────┐ 1:1* ┌────────────┐\n * │ Transport │ ◄─────► │ Session │ ◄─────► │ Connection │\n * └─────────────┘ └───────────┘ └────────────┘\n * ▲ * (may or may not be initialized yet)\n * │\n * ▼\n * ┌───────────┐\n * │ Message │\n * │ Listeners │\n * └───────────┘\n * ```\n * @abstract\n */\nexport abstract class Transport<ConnType extends Connection> {\n /**\n * The status of the transport.\n */\n private status: TransportStatus;\n\n /**\n * The {@link Codec} used to encode and decode messages.\n */\n codec: Codec;\n\n /**\n * The client ID of this transport.\n */\n clientId: TransportClientId;\n\n /**\n * The map of {@link Session}s managed by this transport.\n */\n sessions: Map<TransportClientId, Session<ConnType>>;\n\n /**\n * The map of {@link Connection}s managed by this transport.\n */\n get connections() {\n return new Map(\n [...this.sessions]\n .map(([client, session]) => [client, session.connection])\n .filter((entry): entry is [string, ConnType] => entry[1] !== undefined),\n );\n }\n\n /**\n * The event dispatcher for handling events of type EventTypes.\n */\n eventDispatcher: EventDispatcher<EventTypes>;\n\n /**\n * The options for this transport.\n */\n protected options: TransportOptions;\n log?: Logger;\n\n /**\n * Creates a new Transport instance.\n * This should also set up {@link onConnect}, and {@link onDisconnect} listeners.\n * @param codec The codec used to encode and decode messages.\n * @param clientId The client ID of this transport.\n */\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedTransportOptions,\n ) {\n this.options = { ...defaultTransportOptions, ...providedOptions };\n this.eventDispatcher = new EventDispatcher();\n this.sessions = new Map();\n this.codec = this.options.codec;\n this.clientId = clientId;\n this.status = 'open';\n }\n\n bindLogger(fn: LogFn | Logger, level?: LoggingLevel) {\n // construct logger from fn\n if (typeof fn === 'function') {\n this.log = createLogProxy(new BaseLogger(fn, level));\n return;\n }\n\n // object case, just assign\n this.log = createLogProxy(fn);\n }\n\n /**\n * This is called immediately after a new connection is established and we\n * may or may not know the identity of the connected client.\n * It should attach all the necessary listeners to the connection for lifecycle\n * events (i.e. data, close, error)\n *\n * This method is implemented by {@link ClientTransport} and {@link ServerTransport}.\n */\n protected abstract handleConnection(\n conn: ConnType,\n to: TransportClientId,\n ): void;\n\n /**\n * Called when a new connection is established\n * and we know the identity of the connected client.\n * @param conn The connection object.\n */\n protected onConnect(\n conn: ConnType,\n session: Session<ConnType>,\n isTransparentReconnect: boolean,\n ) {\n this.eventDispatcher.dispatchEvent('connectionStatus', {\n status: 'connect',\n conn,\n });\n\n conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);\n\n session.replaceWithNewConnection(conn, isTransparentReconnect);\n\n this.log?.info(`connected to ${session.to}`, {\n ...conn.loggingMetadata,\n ...session.loggingMetadata,\n });\n }\n\n protected createSession(\n to: TransportClientId,\n conn?: ConnType,\n propagationCtx?: PropagationContext,\n ) {\n const session = new Session<ConnType>(\n conn,\n this.clientId,\n to,\n this.options,\n propagationCtx,\n );\n\n if (this.log) {\n session.bindLogger(this.log);\n }\n\n this.sessions.set(session.to, session);\n this.eventDispatcher.dispatchEvent('sessionStatus', {\n status: 'connect',\n session,\n });\n return session;\n }\n\n protected createNewSession({\n to,\n conn,\n sessionId,\n propagationCtx,\n }: {\n to: TransportClientId;\n conn: ConnType;\n sessionId: string;\n propagationCtx?: PropagationContext;\n }) {\n let session = this.sessions.get(to);\n if (session !== undefined) {\n // this is a new session. if the client already had one, delete it before continuing\n this.log?.info(\n `session for ${to} already exists, replacing it with a new session as requested`,\n session.loggingMetadata,\n );\n this.deleteSession({\n session,\n closeHandshakingConnection: false,\n });\n session = undefined;\n }\n\n session = this.createSession(to, conn, propagationCtx);\n session.advertisedSessionId = sessionId;\n this.log?.info(`created new session for ${to}`, session.loggingMetadata);\n\n return session;\n }\n\n protected getExistingSession({\n to,\n sessionId,\n nextExpectedSeq,\n }: {\n to: TransportClientId;\n sessionId: string;\n nextExpectedSeq: number;\n }) {\n const session = this.sessions.get(to);\n if (\n // reject this request if there was no previous session to replace\n session === undefined ||\n // or if both parties do not agree about the next expected sequence number\n session.nextExpectedAck < nextExpectedSeq ||\n // or if both parties do not agree on the advertised session id\n session.advertisedSessionId !== sessionId\n ) {\n return false;\n }\n\n this.log?.info(\n `reused existing session for ${to}`,\n session.loggingMetadata,\n );\n\n return session;\n }\n\n protected getOrCreateSession({\n to,\n conn,\n handshakingConn,\n sessionId,\n propagationCtx,\n }: {\n to: TransportClientId;\n conn?: ConnType;\n handshakingConn?: ConnType;\n sessionId?: string;\n propagationCtx?: PropagationContext;\n }) {\n let session = this.sessions.get(to);\n const isReconnect = session !== undefined;\n let isTransparentReconnect = isReconnect;\n\n if (\n session?.advertisedSessionId !== undefined &&\n sessionId !== undefined &&\n session.advertisedSessionId !== sessionId\n ) {\n this.log?.info(\n `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,\n session.loggingMetadata,\n );\n // note that here we are only interested in closing the handshaking connection if it _does\n // not_ match the current handshaking connection. otherwise we can be in a situation where we\n // can accidentally close the current connection and are never able to establish a full\n // handshake.\n this.deleteSession({\n session,\n closeHandshakingConnection: handshakingConn !== undefined,\n handshakingConn,\n });\n isTransparentReconnect = false;\n session = undefined;\n }\n\n if (!session) {\n session = this.createSession(to, conn, propagationCtx);\n this.log?.info(\n `no session for ${to}, created a new one`,\n session.loggingMetadata,\n );\n }\n\n if (sessionId !== undefined) {\n session.advertisedSessionId = sessionId;\n }\n\n if (handshakingConn !== undefined) {\n session.replaceWithNewHandshakingConnection(handshakingConn);\n }\n return { session, isReconnect, isTransparentReconnect };\n }\n\n protected deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n }: {\n session: Session<ConnType>;\n closeHandshakingConnection: boolean;\n handshakingConn?: ConnType;\n }) {\n if (closeHandshakingConnection) {\n session.closeHandshakingConnection(handshakingConn);\n }\n session.close();\n session.telemetry.span.end();\n this.sessions.delete(session.to);\n this.log?.info(\n `session ${session.id} disconnect from ${session.to}`,\n session.loggingMetadata,\n );\n this.eventDispatcher.dispatchEvent('sessionStatus', {\n status: 'disconnect',\n session,\n });\n }\n\n /**\n * The downstream implementation needs to call this when a connection is closed.\n * @param conn The connection object.\n * @param connectedTo The peer we are connected to.\n */\n protected onDisconnect(conn: ConnType, session: Session<ConnType>) {\n if (session.connection !== undefined && session.connection.id !== conn.id) {\n // it is completely legal for us to receive the onDisconnect notification later down the line\n // and accidentally try to install the grace notification into an already-reconnected session.\n session.telemetry.span.addEvent('onDisconnect race');\n this.log?.warn('onDisconnect race', {\n clientId: this.clientId,\n ...session.loggingMetadata,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n conn.telemetry?.span.end();\n this.eventDispatcher.dispatchEvent('connectionStatus', {\n status: 'disconnect',\n conn,\n });\n\n session.connection = undefined;\n session.beginGrace(() => {\n if (session.connection !== undefined) {\n // if for whatever reason the session has a connection, it means that we accidentally\n // installed a grace period in a session that already had reconnected. oops.\n session.telemetry.span.addEvent('session grace period race');\n this.log?.warn('session grace period race', {\n clientId: this.clientId,\n ...session.loggingMetadata,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n session.telemetry.span.addEvent('session grace period expired');\n this.deleteSession({\n session,\n closeHandshakingConnection: true,\n handshakingConn: conn,\n });\n });\n }\n\n /**\n * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.\n * @param msg The message to parse.\n * @returns The parsed message, or null if the message is malformed or invalid.\n */\n protected parseMsg(\n msg: Uint8Array,\n conn: ConnType,\n ): OpaqueTransportMessage | null {\n const parsedMsg = this.codec.fromBuffer(msg);\n\n if (parsedMsg === null) {\n const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));\n this.log?.error(\n `received malformed msg, killing conn: ${decodedBuffer}`,\n {\n clientId: this.clientId,\n ...conn.loggingMetadata,\n },\n );\n return null;\n }\n\n if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {\n this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {\n clientId: this.clientId,\n ...conn.loggingMetadata,\n validationErrors: [\n ...Value.Errors(OpaqueTransportMessageSchema, parsedMsg),\n ],\n });\n return null;\n }\n\n return parsedMsg;\n }\n\n /**\n * Called when a message is received by this transport.\n * You generally shouldn't need to override this in downstream transport implementations.\n * @param msg The received message.\n */\n protected handleMsg(msg: OpaqueTransportMessage, conn: ConnType) {\n if (this.getStatus() !== 'open') return;\n const session = this.sessions.get(msg.from);\n if (!session) {\n this.log?.error(`received message for unknown session from ${msg.from}`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n\n // got a msg so we know the other end is alive, reset the grace period\n session.cancelGrace();\n\n this.log?.debug(`received msg`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n });\n if (msg.seq !== session.nextExpectedSeq) {\n if (msg.seq < session.nextExpectedSeq) {\n this.log?.debug(\n `received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,\n {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n },\n );\n } else {\n const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;\n this.log?.error(`${errMsg}, marking connection as dead`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);\n session.telemetry.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'message order violated',\n });\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n return;\n }\n\n session.updateBookkeeping(msg.ack, msg.seq);\n\n // don't dispatch explicit acks\n if (!isAck(msg.controlFlags)) {\n this.eventDispatcher.dispatchEvent('message', msg);\n } else {\n this.log?.debug(`discarding msg (ack bit set)`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n });\n }\n }\n\n /**\n * Adds a listener to this transport.\n * @param the type of event to listen for\n * @param handler The message handler to add.\n */\n addEventListener<K extends EventTypes, T extends EventHandler<K>>(\n type: K,\n handler: T,\n ): void {\n this.eventDispatcher.addEventListener(type, handler);\n }\n\n /**\n * Removes a listener from this transport.\n * @param the type of event to un-listen on\n * @param handler The message handler to remove.\n */\n removeEventListener<K extends EventTypes, T extends EventHandler<K>>(\n type: K,\n handler: T,\n ): void {\n this.eventDispatcher.removeEventListener(type, handler);\n }\n\n /**\n * Sends a message over this transport, delegating to the appropriate connection to actually\n * send the message.\n * @param msg The message to send.\n * @returns The ID of the sent message or undefined if it wasn't sent\n */\n\n send(to: TransportClientId, msg: PartialTransportMessage): string {\n if (this.getStatus() === 'closed') {\n const err = 'transport is closed, cant send';\n this.log?.error(err, {\n clientId: this.clientId,\n transportMessage: msg,\n tags: ['invariant-violation'],\n });\n\n throw new Error(err);\n }\n\n return this.getOrCreateSession({ to }).session.send(msg);\n }\n\n // control helpers\n sendCloseStream(to: TransportClientId, streamId: string) {\n return this.send(to, {\n streamId: streamId,\n controlFlags: ControlFlags.StreamClosedBit,\n payload: {\n type: 'CLOSE' as const,\n } satisfies Static<typeof ControlMessagePayloadSchema>,\n });\n }\n\n protected protocolError(type: ProtocolErrorType, message: string) {\n this.eventDispatcher.dispatchEvent('protocolError', { type, message });\n }\n\n /**\n * Default close implementation for transports. You should override this in the downstream\n * implementation if you need to do any additional cleanup and call super.close() at the end.\n * Closes the transport. Any messages sent while the transport is closed will be silently discarded.\n */\n close() {\n this.status = 'closed';\n\n for (const session of this.sessions.values()) {\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n this.eventDispatcher.dispatchEvent('transportStatus', {\n status: this.status,\n });\n\n this.eventDispatcher.removeAllListeners();\n\n this.log?.info(`manually closed transport`, { clientId: this.clientId });\n }\n\n getStatus(): TransportStatus {\n return this.status;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAKO,IAAM,gBAAgB;AAAA,EAC3B,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,yBAAyB;AAC3B;AA6BO,IAAM,kBAAN,MAA4C;AAAA,EACzC,iBAAsD,CAAC;AAAA,EAE/D,qBAAqB;AACnB,SAAK,iBAAiB,CAAC;AAAA,EACzB;AAAA,EAEA,kBAA+B,WAAc;AAC3C,WAAO,KAAK,eAAe,SAAS,GAAG,QAAQ;AAAA,EACjD;AAAA,EAEA,iBAA8B,WAAc,SAA0B;AACpE,QAAI,CAAC,KAAK,eAAe,SAAS,GAAG;AACnC,WAAK,eAAe,SAAS,IAAI,oBAAI,IAAI;AAAA,IAC3C;AAEA,SAAK,eAAe,SAAS,GAAG,IAAI,OAAO;AAAA,EAC7C;AAAA,EAEA,oBAAiC,WAAc,SAA0B;AACvE,UAAM,WAAW,KAAK,eAAe,SAAS;AAC9C,QAAI,UAAU;AACZ,WAAK,eAAe,SAAS,GAAG,OAAO,OAAO;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,cAA2B,WAAc,OAAoB;AAC3D,UAAM,WAAW,KAAK,eAAe,SAAS;AAC9C,QAAI,UAAU;AAGZ,YAAM,OAAO,CAAC,GAAG,QAAQ;AACzB,iBAAW,WAAW,MAAM;AAC1B,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;;;AC1EA,SAAS,aAAa;AA2BtB,SAAS,sBAAsB;AA4CxB,IAAe,YAAf,MAAsD;AAAA;AAAA;AAAA;AAAA,EAInD;AAAA;AAAA;AAAA;AAAA,EAKR;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,cAAc;AAChB,WAAO,IAAI;AAAA,MACT,CAAC,GAAG,KAAK,QAAQ,EACd,IAAI,CAAC,CAAC,QAAQ,OAAO,MAAM,CAAC,QAAQ,QAAQ,UAAU,CAAC,EACvD,OAAO,CAAC,UAAuC,MAAM,CAAC,MAAM,MAAS;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKU;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YACE,UACA,iBACA;AACA,SAAK,UAAU,EAAE,GAAG,yBAAyB,GAAG,gBAAgB;AAChE,SAAK,kBAAkB,IAAI,gBAAgB;AAC3C,SAAK,WAAW,oBAAI,IAAI;AACxB,SAAK,QAAQ,KAAK,QAAQ;AAC1B,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAW,IAAoB,OAAsB;AAEnD,QAAI,OAAO,OAAO,YAAY;AAC5B,WAAK,MAAM,eAAe,IAAI,WAAW,IAAI,KAAK,CAAC;AACnD;AAAA,IACF;AAGA,SAAK,MAAM,eAAe,EAAE;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBU,UACR,MACA,SACA,wBACA;AACA,SAAK,gBAAgB,cAAc,oBAAoB;AAAA,MACrD,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAED,SAAK,YAAY,8BAA8B,MAAM,QAAQ,SAAS;AAEtE,YAAQ,yBAAyB,MAAM,sBAAsB;AAE7D,SAAK,KAAK,KAAK,gBAAgB,QAAQ,EAAE,IAAI;AAAA,MAC3C,GAAG,KAAK;AAAA,MACR,GAAG,QAAQ;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEU,cACR,IACA,MACA,gBACA;AACA,UAAM,UAAU,IAAI;AAAA,MAClB;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,MACL;AAAA,IACF;AAEA,QAAI,KAAK,KAAK;AACZ,cAAQ,WAAW,KAAK,GAAG;AAAA,IAC7B;AAEA,SAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;AACrC,SAAK,gBAAgB,cAAc,iBAAiB;AAAA,MAClD,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEU,iBAAiB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKG;AACD,QAAI,UAAU,KAAK,SAAS,IAAI,EAAE;AAClC,QAAI,YAAY,QAAW;AAEzB,WAAK,KAAK;AAAA,QACR,eAAe,EAAE;AAAA,QACjB,QAAQ;AAAA,MACV;AACA,WAAK,cAAc;AAAA,QACjB;AAAA,QACA,4BAA4B;AAAA,MAC9B,CAAC;AACD,gBAAU;AAAA,IACZ;AAEA,cAAU,KAAK,cAAc,IAAI,MAAM,cAAc;AACrD,YAAQ,sBAAsB;AAC9B,SAAK,KAAK,KAAK,2BAA2B,EAAE,IAAI,QAAQ,eAAe;AAEvE,WAAO;AAAA,EACT;AAAA,EAEU,mBAAmB;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIG;AACD,UAAM,UAAU,KAAK,SAAS,IAAI,EAAE;AACpC;AAAA;AAAA,MAEE,YAAY;AAAA,MAEZ,QAAQ,kBAAkB;AAAA,MAE1B,QAAQ,wBAAwB;AAAA,MAChC;AACA,aAAO;AAAA,IACT;AAEA,SAAK,KAAK;AAAA,MACR,+BAA+B,EAAE;AAAA,MACjC,QAAQ;AAAA,IACV;AAEA,WAAO;AAAA,EACT;AAAA,EAEU,mBAAmB;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMG;AACD,QAAI,UAAU,KAAK,SAAS,IAAI,EAAE;AAClC,UAAM,cAAc,YAAY;AAChC,QAAI,yBAAyB;AAE7B,QACE,SAAS,wBAAwB,UACjC,cAAc,UACd,QAAQ,wBAAwB,WAChC;AACA,WAAK,KAAK;AAAA,QACR,eAAe,EAAE,6DAA6D,QAAQ,mBAAmB,UAAU,SAAS;AAAA,QAC5H,QAAQ;AAAA,MACV;AAKA,WAAK,cAAc;AAAA,QACjB;AAAA,QACA,4BAA4B,oBAAoB;AAAA,QAChD;AAAA,MACF,CAAC;AACD,+BAAyB;AACzB,gBAAU;AAAA,IACZ;AAEA,QAAI,CAAC,SAAS;AACZ,gBAAU,KAAK,cAAc,IAAI,MAAM,cAAc;AACrD,WAAK,KAAK;AAAA,QACR,kBAAkB,EAAE;AAAA,QACpB,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,QAAI,cAAc,QAAW;AAC3B,cAAQ,sBAAsB;AAAA,IAChC;AAEA,QAAI,oBAAoB,QAAW;AACjC,cAAQ,oCAAoC,eAAe;AAAA,IAC7D;AACA,WAAO,EAAE,SAAS,aAAa,uBAAuB;AAAA,EACxD;AAAA,EAEU,cAAc;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIG;AACD,QAAI,4BAA4B;AAC9B,cAAQ,2BAA2B,eAAe;AAAA,IACpD;AACA,YAAQ,MAAM;AACd,YAAQ,UAAU,KAAK,IAAI;AAC3B,SAAK,SAAS,OAAO,QAAQ,EAAE;AAC/B,SAAK,KAAK;AAAA,MACR,WAAW,QAAQ,EAAE,oBAAoB,QAAQ,EAAE;AAAA,MACnD,QAAQ;AAAA,IACV;AACA,SAAK,gBAAgB,cAAc,iBAAiB;AAAA,MAClD,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,aAAa,MAAgB,SAA4B;AACjE,QAAI,QAAQ,eAAe,UAAa,QAAQ,WAAW,OAAO,KAAK,IAAI;AAGzE,cAAQ,UAAU,KAAK,SAAS,mBAAmB;AACnD,WAAK,KAAK,KAAK,qBAAqB;AAAA,QAClC,UAAU,KAAK;AAAA,QACf,GAAG,QAAQ;AAAA,QACX,GAAG,KAAK;AAAA,QACR,MAAM,CAAC,qBAAqB;AAAA,MAC9B,CAAC;AACD;AAAA,IACF;AACA,SAAK,WAAW,KAAK,IAAI;AACzB,SAAK,gBAAgB,cAAc,oBAAoB;AAAA,MACrD,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAED,YAAQ,aAAa;AACrB,YAAQ,WAAW,MAAM;AACvB,UAAI,QAAQ,eAAe,QAAW;AAGpC,gBAAQ,UAAU,KAAK,SAAS,2BAA2B;AAC3D,aAAK,KAAK,KAAK,6BAA6B;AAAA,UAC1C,UAAU,KAAK;AAAA,UACf,GAAG,QAAQ;AAAA,UACX,GAAG,KAAK;AAAA,UACR,MAAM,CAAC,qBAAqB;AAAA,QAC9B,CAAC;AACD;AAAA,MACF;AACA,cAAQ,UAAU,KAAK,SAAS,8BAA8B;AAC9D,WAAK,cAAc;AAAA,QACjB;AAAA,QACA,4BAA4B;AAAA,QAC5B,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,SACR,KACA,MAC+B;AAC/B,UAAM,YAAY,KAAK,MAAM,WAAW,GAAG;AAE3C,QAAI,cAAc,MAAM;AACtB,YAAM,gBAAgB,IAAI,YAAY,EAAE,OAAO,OAAO,KAAK,GAAG,CAAC;AAC/D,WAAK,KAAK;AAAA,QACR,yCAAyC,aAAa;AAAA,QACtD;AAAA,UACE,UAAU,KAAK;AAAA,UACf,GAAG,KAAK;AAAA,QACV;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,MAAM,MAAM,8BAA8B,SAAS,GAAG;AACzD,WAAK,KAAK,MAAM,yBAAyB,KAAK,UAAU,SAAS,CAAC,IAAI;AAAA,QACpE,UAAU,KAAK;AAAA,QACf,GAAG,KAAK;AAAA,QACR,kBAAkB;AAAA,UAChB,GAAG,MAAM,OAAO,8BAA8B,SAAS;AAAA,QACzD;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,UAAU,KAA6B,MAAgB;AAC/D,QAAI,KAAK,UAAU,MAAM;AAAQ;AACjC,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI,IAAI;AAC1C,QAAI,CAAC,SAAS;AACZ,WAAK,KAAK,MAAM,6CAA6C,IAAI,IAAI,IAAI;AAAA,QACvE,UAAU,KAAK;AAAA,QACf,kBAAkB;AAAA,QAClB,GAAG,KAAK;AAAA,QACR,MAAM,CAAC,qBAAqB;AAAA,MAC9B,CAAC;AACD;AAAA,IACF;AAGA,YAAQ,YAAY;AAEpB,SAAK,KAAK,MAAM,gBAAgB;AAAA,MAC9B,UAAU,KAAK;AAAA,MACf,kBAAkB;AAAA,MAClB,GAAG,KAAK;AAAA,IACV,CAAC;AACD,QAAI,IAAI,QAAQ,QAAQ,iBAAiB;AACvC,UAAI,IAAI,MAAM,QAAQ,iBAAiB;AACrC,aAAK,KAAK;AAAA,UACR,oCAAoC,IAAI,GAAG,iBAAiB,QAAQ,eAAe;AAAA,UACnF;AAAA,YACE,UAAU,KAAK;AAAA,YACf,kBAAkB;AAAA,YAClB,GAAG,KAAK;AAAA,UACV;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM,SAAS,uCAAuC,IAAI,GAAG,iBAAiB,QAAQ,eAAe;AACrG,aAAK,KAAK,MAAM,GAAG,MAAM,gCAAgC;AAAA,UACvD,UAAU,KAAK;AAAA,UACf,kBAAkB;AAAA,UAClB,GAAG,KAAK;AAAA,UACR,MAAM,CAAC,qBAAqB;AAAA,QAC9B,CAAC;AACD,aAAK,cAAc,cAAc,yBAAyB,MAAM;AAChE,gBAAQ,UAAU,KAAK,UAAU;AAAA,UAC/B,MAAM,eAAe;AAAA,UACrB,SAAS;AAAA,QACX,CAAC;AACD,aAAK,cAAc,EAAE,SAAS,4BAA4B,KAAK,CAAC;AAAA,MAClE;AAEA;AAAA,IACF;AAEA,YAAQ,kBAAkB,IAAI,KAAK,IAAI,GAAG;AAG1C,QAAI,CAAC,MAAM,IAAI,YAAY,GAAG;AAC5B,WAAK,gBAAgB,cAAc,WAAW,GAAG;AAAA,IACnD,OAAO;AACL,WAAK,KAAK,MAAM,gCAAgC;AAAA,QAC9C,UAAU,KAAK;AAAA,QACf,kBAAkB;AAAA,QAClB,GAAG,KAAK;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBACE,MACA,SACM;AACN,SAAK,gBAAgB,iBAAiB,MAAM,OAAO;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBACE,MACA,SACM;AACN,SAAK,gBAAgB,oBAAoB,MAAM,OAAO;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,IAAuB,KAAsC;AAChE,QAAI,KAAK,UAAU,MAAM,UAAU;AACjC,YAAM,MAAM;AACZ,WAAK,KAAK,MAAM,KAAK;AAAA,QACnB,UAAU,KAAK;AAAA,QACf,kBAAkB;AAAA,QAClB,MAAM,CAAC,qBAAqB;AAAA,MAC9B,CAAC;AAED,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB;AAEA,WAAO,KAAK,mBAAmB,EAAE,GAAG,CAAC,EAAE,QAAQ,KAAK,GAAG;AAAA,EACzD;AAAA;AAAA,EAGA,gBAAgB,IAAuB,UAAkB;AACvD,WAAO,KAAK,KAAK,IAAI;AAAA,MACnB;AAAA,MACA;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEU,cAAc,MAAyB,SAAiB;AAChE,SAAK,gBAAgB,cAAc,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ;AACN,SAAK,SAAS;AAEd,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,WAAK,cAAc,EAAE,SAAS,4BAA4B,KAAK,CAAC;AAAA,IAClE;AAEA,SAAK,gBAAgB,cAAc,mBAAmB;AAAA,MACpD,QAAQ,KAAK;AAAA,IACf,CAAC;AAED,SAAK,gBAAgB,mBAAmB;AAExC,SAAK,KAAK,KAAK,6BAA6B,EAAE,UAAU,KAAK,SAAS,CAAC;AAAA,EACzE;AAAA,EAEA,YAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AACF;","names":[]}
|