@restatedev/restate-sdk-tunnel 1.15.1 → 1.16.0
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 +1 -1
- package/dist/index.cjs +13 -3
- package/dist/index.d.cts +29 -10
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +29 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ Key options (see `ConnectTunnelOptions` for the full surface and defaults):
|
|
|
211
211
|
| `services` | Same shape `restate.serve` accepts |
|
|
212
212
|
| `tls` | Default on (system trust, ALPN `h2`); object form for CA/mTLS |
|
|
213
213
|
| `connectTimeoutMs` | TCP+TLS dial deadline (5s, mirrors the standalone client) |
|
|
214
|
-
| `
|
|
214
|
+
| `reconnectRetryPolicy` | Jittered exponential backoff `{ initialInterval, maxInterval, exponentiationFactor }` (10ms → 120s, reset after a stable connection) |
|
|
215
215
|
| `supportsDrain` / `drainGraceMs` | Graceful-drain handover on cloud rollovers (on, 120s grace) |
|
|
216
216
|
| `supportsClientDrain` / `gracefulShutdown` | Client shutdown drain with h2 GOAWAY and default `SIGTERM` handling |
|
|
217
217
|
| `pingIntervalMs/TimeoutMs/MaxMissed` | Liveness watchdog (75s cadence) |
|
package/dist/index.cjs
CHANGED
|
@@ -216,6 +216,16 @@ function positive(value, fallback, name) {
|
|
|
216
216
|
return value;
|
|
217
217
|
}
|
|
218
218
|
/**
|
|
219
|
+
* Normalize a `Duration | number` into milliseconds (a number is already ms).
|
|
220
|
+
* Inlined rather than importing the SDK's `millisOrDurationToMillis`, which is
|
|
221
|
+
* not part of its public API — this keeps the tunnel package dependency-free.
|
|
222
|
+
*/
|
|
223
|
+
function toMillis(value) {
|
|
224
|
+
if (value === void 0) return void 0;
|
|
225
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
226
|
+
return Math.trunc((value.milliseconds ?? 0) + 1e3 * (value.seconds ?? 0) + 1e3 * 60 * (value.minutes ?? 0) + 1e3 * 60 * 60 * (value.hours ?? 0) + 1e3 * 60 * 60 * 24 * (value.days ?? 0));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
219
229
|
* Validate user options and apply defaults. Throws on misconfiguration.
|
|
220
230
|
* Each identity/discovery option falls back to its RESTATE_INPROC_* env var
|
|
221
231
|
* (option > environment > throw), so a pod the restate-operator configured
|
|
@@ -263,9 +273,9 @@ function resolveOptions(options) {
|
|
|
263
273
|
gracefulShutdown: resolveGracefulShutdown(options.gracefulShutdown, drainGraceMs),
|
|
264
274
|
connectTimeoutMs: positive(options.connectTimeoutMs, 5e3, "connectTimeoutMs"),
|
|
265
275
|
handshakeTimeoutMs: positive(options.handshakeTimeoutMs, 5e3, "handshakeTimeoutMs"),
|
|
266
|
-
reconnectInitialMs: positive(options.
|
|
267
|
-
reconnectMaxMs: positive(options.
|
|
268
|
-
reconnectFactor: positive(options.
|
|
276
|
+
reconnectInitialMs: positive(toMillis(options.reconnectRetryPolicy?.initialInterval), 10, "reconnectRetryPolicy.initialInterval"),
|
|
277
|
+
reconnectMaxMs: positive(toMillis(options.reconnectRetryPolicy?.maxInterval), 12e4, "reconnectRetryPolicy.maxInterval"),
|
|
278
|
+
reconnectFactor: positive(options.reconnectRetryPolicy?.exponentiationFactor, 2, "reconnectRetryPolicy.exponentiationFactor"),
|
|
269
279
|
pingIntervalMs,
|
|
270
280
|
pingTimeoutMs,
|
|
271
281
|
pingMaxMissed: positive(options.pingMaxMissed, 2, "pingMaxMissed"),
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EndpointOptions } from "@restatedev/restate-sdk";
|
|
1
|
+
import { Duration, EndpointOptions } from "@restatedev/restate-sdk";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
|
|
@@ -28,6 +28,29 @@ interface TunnelTlsOptions {
|
|
|
28
28
|
/** Verify the server certificate. Default true. */
|
|
29
29
|
rejectUnauthorized?: boolean;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Reconnect backoff policy. Between reconnect attempts the engine waits a
|
|
33
|
+
* jittered exponential delay: it starts at {@link initialInterval}, grows by
|
|
34
|
+
* {@link exponentiationFactor} per failed attempt, is capped at
|
|
35
|
+
* {@link maxInterval}, and resets after a connection stays up long enough to be
|
|
36
|
+
* considered stable. Intervals accept a {@link Duration} or a number of
|
|
37
|
+
* milliseconds.
|
|
38
|
+
*
|
|
39
|
+
* The field names mirror the invocation and ingress retry policies. Note the
|
|
40
|
+
* tunnel reconnect loop is unbounded — there is no `maxAttempts`; it redials
|
|
41
|
+
* forever until a connection succeeds or the tunnel is shut down.
|
|
42
|
+
*/
|
|
43
|
+
interface ReconnectRetryPolicy {
|
|
44
|
+
/** Initial delay. A number is interpreted as milliseconds. Default 10 milliseconds. */
|
|
45
|
+
initialInterval?: Duration | number;
|
|
46
|
+
/** Maximum delay. A number is interpreted as milliseconds. Default 120_000 milliseconds. */
|
|
47
|
+
maxInterval?: Duration | number;
|
|
48
|
+
/**
|
|
49
|
+
* Exponentiation factor applied to the delay after each failed attempt.
|
|
50
|
+
* Default 2.
|
|
51
|
+
*/
|
|
52
|
+
exponentiationFactor?: number;
|
|
53
|
+
}
|
|
31
54
|
/**
|
|
32
55
|
* Options for {@link connectTunnel}.
|
|
33
56
|
*
|
|
@@ -211,15 +234,11 @@ interface ConnectTunnelOptions extends Omit<EndpointOptions, "identityKeys"> {
|
|
|
211
234
|
graceMs?: number;
|
|
212
235
|
};
|
|
213
236
|
/**
|
|
214
|
-
* Reconnect backoff:
|
|
215
|
-
*
|
|
216
|
-
*
|
|
237
|
+
* Reconnect backoff policy: jittered exponential backoff applied between
|
|
238
|
+
* reconnect attempts (10ms → 120s by default), reset after a stable
|
|
239
|
+
* connection. See {@link ReconnectRetryPolicy}.
|
|
217
240
|
*/
|
|
218
|
-
|
|
219
|
-
/** Reconnect backoff: maximum delay in milliseconds. Default 120_000. */
|
|
220
|
-
reconnectMaxMs?: number;
|
|
221
|
-
/** Reconnect backoff: growth factor. Default 2. */
|
|
222
|
-
reconnectFactor?: number;
|
|
241
|
+
reconnectRetryPolicy?: ReconnectRetryPolicy;
|
|
223
242
|
/**
|
|
224
243
|
* Deadline for establishing the TCP connection and completing the TLS
|
|
225
244
|
* handshake. Default 5_000 (mirrors the standalone tunnel client's
|
|
@@ -350,5 +369,5 @@ interface TunnelConnection {
|
|
|
350
369
|
*/
|
|
351
370
|
declare function connectTunnel(options: ConnectTunnelOptions): TunnelConnection;
|
|
352
371
|
//#endregion
|
|
353
|
-
export { type ConnectTunnelOptions, type TunnelConnection, type TunnelTlsOptions, connectTunnel };
|
|
372
|
+
export { type ConnectTunnelOptions, type ReconnectRetryPolicy, type TunnelConnection, type TunnelTlsOptions, connectTunnel };
|
|
354
373
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/connect.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;AASuB,UATN,gBAAA,CASM;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/connect.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;AASuB,UATN,gBAAA,CASM;EAsBN;AA4BjB;;;EAkHmD,EAAA,CAAA,EAAA,MAAA,GAxKnC,MAwKmC,GAxK1B,KAwK0B,CAAA,MAAA,GAxKX,MAwKW,CAAA;EAmDR;EAMlB,IAAA,CAAA,EAAA,MAAA,GA/NP,MA+NO;EA+CP;EAEP,GAAA,CAAA,EAAA,MAAA,GA9QM,MA8QN;EA5NmC;;AAsO9C;;EAcyC,UAAA,CAAA,EAAA,MAAA;EAyCvB;EAMA,kBAAA,CAAA,EAAA,OAAA;;;;;ACnLlB;;;;;;;;;UD5IiB,oBAAA;;oBAEG;;gBAEJ;;;;;;;;;;;;;;;;;;;;;;;UAwBC,oBAAA,SAA6B,KAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiHe,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmDR,MAAA,CAAO;;;;;;;;yBAMzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA+CP;;WAEP;;;;;;;UAUM,gBAAA;;WAEN;;;;;;;;;;;;;;MAY8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAyCvB;;;;;;kBAMA;;;;;;AA9VlB;;;;AAOkB,iBCoKF,aAAA,CDpKE,OAAA,ECoKqB,oBDpKrB,CAAA,ECoK4C,gBDpK5C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EndpointOptions } from "@restatedev/restate-sdk";
|
|
1
|
+
import { Duration, EndpointOptions } from "@restatedev/restate-sdk";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
|
|
@@ -28,6 +28,29 @@ interface TunnelTlsOptions {
|
|
|
28
28
|
/** Verify the server certificate. Default true. */
|
|
29
29
|
rejectUnauthorized?: boolean;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Reconnect backoff policy. Between reconnect attempts the engine waits a
|
|
33
|
+
* jittered exponential delay: it starts at {@link initialInterval}, grows by
|
|
34
|
+
* {@link exponentiationFactor} per failed attempt, is capped at
|
|
35
|
+
* {@link maxInterval}, and resets after a connection stays up long enough to be
|
|
36
|
+
* considered stable. Intervals accept a {@link Duration} or a number of
|
|
37
|
+
* milliseconds.
|
|
38
|
+
*
|
|
39
|
+
* The field names mirror the invocation and ingress retry policies. Note the
|
|
40
|
+
* tunnel reconnect loop is unbounded — there is no `maxAttempts`; it redials
|
|
41
|
+
* forever until a connection succeeds or the tunnel is shut down.
|
|
42
|
+
*/
|
|
43
|
+
interface ReconnectRetryPolicy {
|
|
44
|
+
/** Initial delay. A number is interpreted as milliseconds. Default 10 milliseconds. */
|
|
45
|
+
initialInterval?: Duration | number;
|
|
46
|
+
/** Maximum delay. A number is interpreted as milliseconds. Default 120_000 milliseconds. */
|
|
47
|
+
maxInterval?: Duration | number;
|
|
48
|
+
/**
|
|
49
|
+
* Exponentiation factor applied to the delay after each failed attempt.
|
|
50
|
+
* Default 2.
|
|
51
|
+
*/
|
|
52
|
+
exponentiationFactor?: number;
|
|
53
|
+
}
|
|
31
54
|
/**
|
|
32
55
|
* Options for {@link connectTunnel}.
|
|
33
56
|
*
|
|
@@ -211,15 +234,11 @@ interface ConnectTunnelOptions extends Omit<EndpointOptions, "identityKeys"> {
|
|
|
211
234
|
graceMs?: number;
|
|
212
235
|
};
|
|
213
236
|
/**
|
|
214
|
-
* Reconnect backoff:
|
|
215
|
-
*
|
|
216
|
-
*
|
|
237
|
+
* Reconnect backoff policy: jittered exponential backoff applied between
|
|
238
|
+
* reconnect attempts (10ms → 120s by default), reset after a stable
|
|
239
|
+
* connection. See {@link ReconnectRetryPolicy}.
|
|
217
240
|
*/
|
|
218
|
-
|
|
219
|
-
/** Reconnect backoff: maximum delay in milliseconds. Default 120_000. */
|
|
220
|
-
reconnectMaxMs?: number;
|
|
221
|
-
/** Reconnect backoff: growth factor. Default 2. */
|
|
222
|
-
reconnectFactor?: number;
|
|
241
|
+
reconnectRetryPolicy?: ReconnectRetryPolicy;
|
|
223
242
|
/**
|
|
224
243
|
* Deadline for establishing the TCP connection and completing the TLS
|
|
225
244
|
* handshake. Default 5_000 (mirrors the standalone tunnel client's
|
|
@@ -350,5 +369,5 @@ interface TunnelConnection {
|
|
|
350
369
|
*/
|
|
351
370
|
declare function connectTunnel(options: ConnectTunnelOptions): TunnelConnection;
|
|
352
371
|
//#endregion
|
|
353
|
-
export { type ConnectTunnelOptions, type TunnelConnection, type TunnelTlsOptions, connectTunnel };
|
|
372
|
+
export { type ConnectTunnelOptions, type ReconnectRetryPolicy, type TunnelConnection, type TunnelTlsOptions, connectTunnel };
|
|
354
373
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/connect.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;AASuB,UATN,gBAAA,CASM;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/connect.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;AASuB,UATN,gBAAA,CASM;EAsBN;AA4BjB;;;EAkHmD,EAAA,CAAA,EAAA,MAAA,GAxKnC,MAwKmC,GAxK1B,KAwK0B,CAAA,MAAA,GAxKX,MAwKW,CAAA;EAmDR;EAMlB,IAAA,CAAA,EAAA,MAAA,GA/NP,MA+NO;EA+CP;EAEP,GAAA,CAAA,EAAA,MAAA,GA9QM,MA8QN;EA5NmC;;AAsO9C;;EAcyC,UAAA,CAAA,EAAA,MAAA;EAyCvB;EAMA,kBAAA,CAAA,EAAA,OAAA;;;;;ACnLlB;;;;;;;;;UD5IiB,oBAAA;;oBAEG;;gBAEJ;;;;;;;;;;;;;;;;;;;;;;;UAwBC,oBAAA,SAA6B,KAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiHe,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmDR,MAAA,CAAO;;;;;;;;yBAMzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA+CP;;WAEP;;;;;;;UAUM,gBAAA;;WAEN;;;;;;;;;;;;;;MAY8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAyCvB;;;;;;kBAMA;;;;;;AA9VlB;;;;AAOkB,iBCoKF,aAAA,CDpKE,OAAA,ECoKqB,oBDpKrB,CAAA,ECoK4C,gBDpK5C"}
|
package/dist/index.js
CHANGED
|
@@ -185,6 +185,16 @@ function positive(value, fallback, name) {
|
|
|
185
185
|
return value;
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
|
+
* Normalize a `Duration | number` into milliseconds (a number is already ms).
|
|
189
|
+
* Inlined rather than importing the SDK's `millisOrDurationToMillis`, which is
|
|
190
|
+
* not part of its public API — this keeps the tunnel package dependency-free.
|
|
191
|
+
*/
|
|
192
|
+
function toMillis(value) {
|
|
193
|
+
if (value === void 0) return void 0;
|
|
194
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
195
|
+
return Math.trunc((value.milliseconds ?? 0) + 1e3 * (value.seconds ?? 0) + 1e3 * 60 * (value.minutes ?? 0) + 1e3 * 60 * 60 * (value.hours ?? 0) + 1e3 * 60 * 60 * 24 * (value.days ?? 0));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
188
198
|
* Validate user options and apply defaults. Throws on misconfiguration.
|
|
189
199
|
* Each identity/discovery option falls back to its RESTATE_INPROC_* env var
|
|
190
200
|
* (option > environment > throw), so a pod the restate-operator configured
|
|
@@ -232,9 +242,9 @@ function resolveOptions(options) {
|
|
|
232
242
|
gracefulShutdown: resolveGracefulShutdown(options.gracefulShutdown, drainGraceMs),
|
|
233
243
|
connectTimeoutMs: positive(options.connectTimeoutMs, 5e3, "connectTimeoutMs"),
|
|
234
244
|
handshakeTimeoutMs: positive(options.handshakeTimeoutMs, 5e3, "handshakeTimeoutMs"),
|
|
235
|
-
reconnectInitialMs: positive(options.
|
|
236
|
-
reconnectMaxMs: positive(options.
|
|
237
|
-
reconnectFactor: positive(options.
|
|
245
|
+
reconnectInitialMs: positive(toMillis(options.reconnectRetryPolicy?.initialInterval), 10, "reconnectRetryPolicy.initialInterval"),
|
|
246
|
+
reconnectMaxMs: positive(toMillis(options.reconnectRetryPolicy?.maxInterval), 12e4, "reconnectRetryPolicy.maxInterval"),
|
|
247
|
+
reconnectFactor: positive(options.reconnectRetryPolicy?.exponentiationFactor, 2, "reconnectRetryPolicy.exponentiationFactor"),
|
|
238
248
|
pingIntervalMs,
|
|
239
249
|
pingTimeoutMs,
|
|
240
250
|
pingMaxMissed: positive(options.pingMaxMissed, 2, "pingMaxMissed"),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["url: URL","port","log: DiagnosticLogger","targets","targets: Target[]","base: tls.ConnectionOptions","entry: DrainingConnection","session: http2.Http2Session","opts: ResolvedOptions","onDead: () => void","authToken: string","target: Target","slotSignal: AbortSignal","deps: ConnectionDeps","initialMs: number","factor: number","maxMs: number","onAbort!: () => void","opts: ResolvedOptions","deps: ConnectionDeps","hooks: SupervisorHooks","log: (message: string) => void","slot: Slot","timeout: ReturnType<typeof setTimeout> | undefined","targets: Target[]","output: Output","state: EngineState","signalUnregisters: Array<() => void>"],"sources":["../src/targets.ts","../src/options.ts","../src/draining.ts","../src/handshake.ts","../src/forwarded.ts","../src/connection.ts","../src/backoff.ts","../src/util.ts","../src/supervisor.ts","../src/connect.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Tunnel-server discovery: explicit addresses or region-based DNS SRV.\n\nimport * as dns from \"node:dns\";\n\n/** A dialable tunnel server. */\nexport interface Target {\n host: string;\n port: number;\n /**\n * TLS SNI / verification name. For SRV-discovered targets this is the SRV\n * QUERY name (`tunnel.<region>.restate.cloud` — what the cloud's cert\n * covers), regardless of which per-record host is dialed; for explicit\n * addresses it is the configured host.\n */\n servername: string;\n /**\n * Per-target plaintext override: set when an explicit `http://` URL was\n * given. `undefined` means \"follow the global `tls` option\".\n */\n plaintext?: boolean;\n}\n\ntype DiagnosticLogger = (message: string) => void;\n\nfunction formatError(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Parse one explicit tunnel-server address: `\"host:port\"`, or a URL whose\n * scheme picks TLS (`https`) / plaintext (`http`) for that server.\n * Throws on a malformed address.\n */\nexport function parseServerAddress(address: string): Target {\n if (address.includes(\"://\")) {\n let url: URL;\n try {\n url = new URL(address);\n } catch {\n throw new Error(\n `tunnel: invalid tunnel server URL ${JSON.stringify(address)}`\n );\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(\n `tunnel: unsupported tunnel server scheme ${JSON.stringify(url.protocol)} (use http or https)`\n );\n }\n if (url.pathname !== \"/\" || url.search !== \"\") {\n throw new Error(\n `tunnel: tunnel server URL must not have a path or query: ${JSON.stringify(address)}`\n );\n }\n const port =\n url.port !== \"\" ? Number(url.port) : url.protocol === \"https:\" ? 443 : 80;\n return {\n host: url.hostname,\n port,\n servername: url.hostname,\n plaintext: url.protocol === \"http:\",\n };\n }\n // \"host:port\" — split on the LAST colon so IPv6-ish hosts survive.\n const idx = address.lastIndexOf(\":\");\n if (idx <= 0 || idx === address.length - 1) {\n throw new Error(\n `tunnel: invalid tunnel server address ${JSON.stringify(address)} (expected \"host:port\" or a URL)`\n );\n }\n const host = address.slice(0, idx);\n const port = Number(address.slice(idx + 1));\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(\n `tunnel: invalid port in tunnel server address ${JSON.stringify(address)}`\n );\n }\n return { host, port, servername: host };\n}\n\n/**\n * Resolve the current set of tunnel servers. Called fresh per connection\n * attempt, so DNS changes are picked up across redials.\n *\n * - Explicit `tunnelServers`: parsed as-is (no DNS here — the dial resolves\n * the hostname).\n * - `srvName` (region-derived or given directly): a DNS SRV lookup, each\n * record expanded to ALL of its addresses (priority asc, weight desc).\n *\n * Error taxonomy (mirrors the Rust resolver): a NEGATIVE answer for an SRV\n * target (the name genuinely has no address — ENOTFOUND/ENODATA) removes\n * that target, and an all-negative answer yields an EMPTY list (the\n * supervisor then reconciles everything away, like Rust's empty set). A\n * TRANSPORT error (EAI_AGAIN, timeouts, SERVFAIL) THROWS instead — the\n * supervisor must keep the existing connections serving and retry, not\n * tear down healthy slots over a resolver blip.\n */\nexport async function resolveTargets(spec: {\n srvName?: string;\n tunnelServers?: string[];\n logger?: DiagnosticLogger;\n}): Promise<Target[]> {\n const log: DiagnosticLogger = spec.logger ?? (() => {});\n if (spec.tunnelServers !== undefined) {\n const targets = spec.tunnelServers.map(parseServerAddress);\n if (targets.length === 0) {\n throw new Error(\"tunnel: tunnelServers is empty\");\n }\n log(\n `tunnel: using configured tunnel target(s): ${targets.map(targetKey).join(\", \")}`\n );\n return targets;\n }\n const srvName = spec.srvName!;\n log(`tunnel: resolving tunnel targets from SRV ${srvName}`);\n const records = await dns.promises.resolveSrv(srvName);\n records.sort((a, b) => a.priority - b.priority || b.weight - a.weight);\n log(\n `tunnel: SRV ${srvName} returned ${records.length} record(s): ${\n records.map((r) => `${r.name}:${r.port}`).join(\", \") || \"<none>\"\n }`\n );\n // Expand each SRV target to its addresses: the tunnel connects to EVERY\n // resolved address (one connection per IP), exactly like the Rust client,\n // which flat-maps SRV targets through A/AAAA lookups into per-IP URIs.\n // Lookups run concurrently (Rust uses FuturesUnordered) so one slow\n // resolver doesn't serialize the rest. SNI / certificate verification\n // uses the SRV QUERY name (the cloud's cert covers the SRV name, not\n // per-node hostnames) — mirroring the Rust FixedServerNameResolver.\n const lookups = await Promise.allSettled(\n records.map((r) => dns.promises.lookup(r.name, { all: true }))\n );\n const targets: Target[] = [];\n const seen = new Set<string>();\n for (let i = 0; i < records.length; i++) {\n const r = records[i]!;\n const result = lookups[i]!;\n if (result.status === \"rejected\") {\n const code = (result.reason as NodeJS.ErrnoException | undefined)?.code;\n if (code === \"ENOTFOUND\" || code === \"ENODATA\") {\n log(`tunnel: SRV target ${r.name}:${r.port} has no address (${code})`);\n continue; // negative answer: this SRV target genuinely has no address\n }\n // Transport error — fail the whole resolution so the supervisor\n // keeps existing slots and retries.\n log(\n `tunnel: address lookup for SRV target ${r.name}:${r.port} failed: ${formatError(result.reason)}`\n );\n throw result.reason;\n }\n for (const a of result.value) {\n const key = `${a.address}:${r.port}`;\n if (seen.has(key)) continue;\n seen.add(key);\n targets.push({ host: a.address, port: r.port, servername: srvName });\n }\n }\n log(\n `tunnel: SRV ${srvName} expanded to ${targets.length} target(s): ${\n targets.map(targetKey).join(\", \") || \"<none>\"\n }`\n );\n return targets;\n}\n\n/** Stable identity of a target — the unit of one tunnel connection. */\nexport function targetKey(t: Target): string {\n return `${t.host}:${t.port}`;\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Option validation and TLS construction.\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport type * as tls from \"node:tls\";\nimport type { ConnectTunnelOptions, TunnelTlsOptions } from \"./types.js\";\nimport { parseServerAddress } from \"./targets.js\";\n\n// The environment variables options fall back to when not given explicitly\n// (option > environment > throw). They form the contract with the\n// restate-operator, which injects the first four into the pods of a\n// `tunnelMode: in-process` RestateDeployment; AUTH_TOKEN_FILE is reserved\n// for the user's own Secret mount — credentials are never injected.\nexport const TUNNEL_NAME_ENV = \"RESTATE_INPROC_TUNNEL_NAME\";\nexport const ENVIRONMENT_ID_ENV = \"RESTATE_INPROC_ENVIRONMENT_ID\";\nexport const CLOUD_REGION_ENV = \"RESTATE_INPROC_CLOUD_REGION\";\nexport const SIGNING_PUBLIC_KEY_ENV = \"RESTATE_INPROC_SIGNING_PUBLIC_KEY\";\nexport const AUTH_TOKEN_FILE_ENV = \"RESTATE_INPROC_AUTH_TOKEN_FILE\";\nexport const TUNNEL_WORKER_ID_ENV = \"RESTATE_TUNNEL_WORKER_ID\";\n\nexport interface ResolvedOptions {\n /** The SRV name to discover tunnel servers from (region-derived or given). */\n srvName?: string;\n tunnelServers?: string[];\n environmentId: string;\n /**\n * Returns the bearer token for the handshake. Called once per connection\n * attempt: a file-sourced token (AUTH_TOKEN_FILE_ENV) is re-read on every\n * redial so rotations are picked up without a restart. May throw (e.g.\n * the file is briefly unreadable mid-rotation) — callers treat that as a\n * retryable connection failure.\n */\n authToken: () => string;\n signingPublicKey: string;\n tunnelName: string;\n tunnelWorkerId: string;\n bidirectional: boolean;\n startupReady?: () => Promise<void>;\n startupReadyTimeoutMs: number;\n resolveIntervalMs: number;\n supportsDrain: boolean;\n drainGraceMs: number;\n supportsClientDrain: boolean;\n /** Set when auto signal-handling is opted into; undefined leaves signals alone. */\n gracefulShutdown?: { signals: NodeJS.Signals[]; graceMs: number };\n connectTimeoutMs: number;\n handshakeTimeoutMs: number;\n reconnectInitialMs: number;\n reconnectMaxMs: number;\n reconnectFactor: number;\n pingIntervalMs: number;\n pingTimeoutMs: number;\n pingMaxMissed: number;\n maxConcurrentStreams: number;\n connectionWindowSize: number;\n maxSessionMemory: number;\n tls: boolean | TunnelTlsOptions;\n logger: (message: string) => void;\n}\n\n/** An env var set to the empty string is treated as unset. */\nfunction fromEnv(name: string): string | undefined {\n const value = process.env[name];\n return value === undefined || value === \"\" ? undefined : value;\n}\n\n/** Resolve option > environment > throw. */\nfunction requireConfigured(\n value: string | undefined,\n name: string,\n envName: string\n): string {\n const resolved =\n value !== undefined && value !== \"\" ? value : fromEnv(envName);\n if (resolved === undefined) {\n throw new Error(\n `tunnel: ${name} is required (pass the option or set ${envName})`\n );\n }\n return resolved;\n}\n\n/**\n * Both credentials travel as HTTP header values in the handshake. Node\n * silently strips header-illegal characters, which would surface as a\n * baffling `unauthorized` from the server — reject them loudly instead.\n */\nfunction requireHeaderSafe(value: string, what: string): string {\n if (!/^[\\x21-\\x7e]+$/.test(value)) {\n throw new Error(\n `tunnel: ${what} contains characters that cannot travel in an HTTP header (whitespace or non-printable)`\n );\n }\n return value;\n}\n\nfunction resolveAuthToken(option: string | undefined): () => string {\n if (option !== undefined && option !== \"\") {\n requireHeaderSafe(option, \"authToken\");\n return () => option;\n }\n const tokenFile = fromEnv(AUTH_TOKEN_FILE_ENV);\n if (tokenFile === undefined) {\n throw new Error(\n `tunnel: authToken is required (pass the option or set ${AUTH_TOKEN_FILE_ENV})`\n );\n }\n const readToken = () => {\n // Guard before reading: this runs synchronously on the redial path, so a\n // FIFO (blocks forever) or an unbounded device file (reads forever) would\n // freeze the event loop — and with it every other live connection.\n const stat = fs.statSync(tokenFile);\n if (!stat.isFile()) {\n throw new Error(\n `tunnel: auth token file ${tokenFile} is not a regular file`\n );\n }\n if (stat.size > 64 * 1024) {\n throw new Error(\n `tunnel: auth token file ${tokenFile} is implausibly large for a token (${stat.size} bytes)`\n );\n }\n // Trimmed because mounted secrets routinely carry a trailing newline.\n const token = fs.readFileSync(tokenFile, \"utf8\").trim();\n if (token === \"\") {\n throw new Error(`tunnel: auth token file ${tokenFile} is empty`);\n }\n return requireHeaderSafe(token, `auth token file ${tokenFile}`);\n };\n // A bad path or token must throw at configuration time like every other\n // misconfiguration, not look like a transient failure mid-redial.\n readToken();\n return readToken;\n}\n\nfunction sanitizeDefaultWorkerIdSegment(value: string): string {\n const sanitized = value\n .replace(/[^A-Za-z0-9._:-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return (sanitized === \"\" ? \"worker\" : sanitized).slice(0, 96);\n}\n\nfunction makeDefaultTunnelWorkerId(): string {\n const host = fromEnv(\"HOSTNAME\") ?? os.hostname() ?? \"worker\";\n const suffix = randomBytes(4).toString(\"hex\");\n return `${sanitizeDefaultWorkerIdSegment(host)}-${suffix}`;\n}\n\n// Stable for the process lifetime. Multiple connectTunnel() calls in the same\n// process get the same default worker id unless explicitly overridden.\nconst DEFAULT_TUNNEL_WORKER_ID = makeDefaultTunnelWorkerId();\n\nfunction resolveTunnelWorkerId(option: string | undefined): string {\n const value =\n option !== undefined && option !== \"\"\n ? option\n : fromEnv(TUNNEL_WORKER_ID_ENV);\n return requireHeaderSafe(value ?? DEFAULT_TUNNEL_WORKER_ID, \"tunnelWorkerId\");\n}\n\nfunction resolveStartupReady(\n option: ConnectTunnelOptions[\"startupReady\"]\n): (() => Promise<void>) | undefined {\n if (option === undefined) return undefined;\n if (typeof option === \"function\") {\n return async () => {\n await option();\n };\n }\n const ready = Promise.resolve(option);\n ready.catch(() => {});\n return async () => {\n await ready;\n };\n}\n\nfunction positive(\n value: number | undefined,\n fallback: number,\n name: string\n): number {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`tunnel: ${name} must be a positive number`);\n }\n return value;\n}\n\n/**\n * Validate user options and apply defaults. Throws on misconfiguration.\n * Each identity/discovery option falls back to its RESTATE_INPROC_* env var\n * (option > environment > throw), so a pod the restate-operator configured\n * for `tunnelMode: in-process` needs no explicit configuration beyond the\n * auth token.\n */\nexport function resolveOptions(options: ConnectTunnelOptions): ResolvedOptions {\n const hasSrv =\n options.tunnelServersSrv !== undefined && options.tunnelServersSrv !== \"\";\n const hasServers =\n options.tunnelServers !== undefined && options.tunnelServers.length > 0;\n // The env var only fills the gap when NO discovery option was given — an\n // explicit tunnelServersSrv/tunnelServers wins over an injected region, and\n // an explicitly-given-but-empty tunnelServers stays a loud config error\n // rather than silently yielding to the environment.\n let region = options.region;\n if (\n (region === undefined || region === \"\") &&\n !hasSrv &&\n options.tunnelServers === undefined\n ) {\n region = fromEnv(CLOUD_REGION_ENV);\n }\n const hasRegion = region !== undefined && region !== \"\";\n const discoveryCount =\n Number(hasRegion) + Number(hasSrv) + Number(hasServers);\n if (discoveryCount === 0) {\n throw new Error(\n `tunnel: specify one of \\`region\\`, \\`tunnelServersSrv\\` or \\`tunnelServers\\` (or set ${CLOUD_REGION_ENV})`\n );\n }\n if (discoveryCount > 1) {\n throw new Error(\n \"tunnel: specify exactly one of `region`, `tunnelServersSrv` or `tunnelServers`\"\n );\n }\n // A region becomes DNS labels in `tunnel.{region}.restate.cloud`, so it may be\n // multi-label (e.g. a BYOC region like \"inl4edhpbxasp9yuz1n0yvvkme.byoc\") —\n // each label lowercase [a-z0-9-], dot-separated, no empty labels.\n if (hasRegion && !/^[a-z0-9-]+(\\.[a-z0-9-]+)*$/.test(region!)) {\n throw new Error(`tunnel: invalid region ${JSON.stringify(region)}`);\n }\n if (hasSrv && !/^[A-Za-z0-9._-]+$/.test(options.tunnelServersSrv!)) {\n throw new Error(\n `tunnel: invalid tunnelServersSrv ${JSON.stringify(options.tunnelServersSrv)}`\n );\n }\n // Parse explicit servers eagerly: a config typo must throw here, like\n // every other misconfiguration (the Rust client parses URIs at startup).\n // Left to the supervisor it would look like a transient resolution\n // failure and retry forever without ever connecting.\n if (hasServers) {\n for (const address of options.tunnelServers!) parseServerAddress(address);\n }\n\n const environmentId = requireConfigured(\n options.environmentId,\n \"environmentId\",\n ENVIRONMENT_ID_ENV\n );\n if (!/^env_[A-Za-z0-9_-]+$/.test(environmentId)) {\n throw new Error(\n \"tunnel: environmentId must be `env_` followed by alphanumerics (e.g. env_201k0yd4...)\"\n );\n }\n const authToken = resolveAuthToken(options.authToken);\n const signingPublicKey = requireConfigured(\n options.signingPublicKey,\n \"signingPublicKey\",\n SIGNING_PUBLIC_KEY_ENV\n );\n if (!signingPublicKey.startsWith(\"publickeyv1_\")) {\n throw new Error(\n \"tunnel: signingPublicKey must be a request-identity public key (publickeyv1_...)\"\n );\n }\n const tunnelName = requireConfigured(\n options.tunnelName,\n \"tunnelName\",\n TUNNEL_NAME_ENV\n );\n if (!/^[A-Za-z0-9._-]+$/.test(tunnelName)) {\n throw new Error(\n `tunnel: invalid tunnelName ${JSON.stringify(tunnelName)} — use letters, digits, '.', '_' or '-'`\n );\n }\n const tunnelWorkerId = resolveTunnelWorkerId(options.tunnelWorkerId);\n const pingIntervalMs = positive(\n options.pingIntervalMs,\n 75_000,\n \"pingIntervalMs\"\n );\n const pingTimeoutMs = positive(\n options.pingTimeoutMs,\n 10_000,\n \"pingTimeoutMs\"\n );\n const drainGraceMs = positive(options.drainGraceMs, 120_000, \"drainGraceMs\");\n\n return {\n srvName: hasRegion\n ? srvNameForRegion(region!)\n : hasSrv\n ? options.tunnelServersSrv\n : undefined,\n tunnelServers: hasServers ? options.tunnelServers : undefined,\n environmentId,\n authToken,\n signingPublicKey,\n tunnelName,\n tunnelWorkerId,\n bidirectional: options.bidirectional ?? true,\n startupReady: resolveStartupReady(options.startupReady),\n startupReadyTimeoutMs: positive(\n options.startupReadyTimeoutMs,\n 120_000,\n \"startupReadyTimeoutMs\"\n ),\n resolveIntervalMs: positive(\n options.resolveIntervalMs,\n 30_000,\n \"resolveIntervalMs\"\n ),\n supportsDrain: options.supportsDrain ?? true,\n drainGraceMs,\n supportsClientDrain: options.supportsClientDrain ?? true,\n gracefulShutdown: resolveGracefulShutdown(\n options.gracefulShutdown,\n drainGraceMs\n ),\n connectTimeoutMs: positive(\n options.connectTimeoutMs,\n 5_000,\n \"connectTimeoutMs\"\n ),\n handshakeTimeoutMs: positive(\n options.handshakeTimeoutMs,\n 5_000,\n \"handshakeTimeoutMs\"\n ),\n reconnectInitialMs: positive(\n options.reconnectInitialMs,\n 10,\n \"reconnectInitialMs\"\n ),\n reconnectMaxMs: positive(options.reconnectMaxMs, 120_000, \"reconnectMaxMs\"),\n reconnectFactor: positive(options.reconnectFactor, 2, \"reconnectFactor\"),\n pingIntervalMs,\n pingTimeoutMs,\n pingMaxMissed: positive(options.pingMaxMissed, 2, \"pingMaxMissed\"),\n maxConcurrentStreams: positive(\n options.maxConcurrentStreams,\n 4096,\n \"maxConcurrentStreams\"\n ),\n connectionWindowSize: positive(\n options.connectionWindowSize,\n 16 * 1024 * 1024,\n \"connectionWindowSize\"\n ),\n maxSessionMemory: positive(\n options.maxSessionMemory,\n 256,\n \"maxSessionMemory\"\n ),\n tls: options.tls ?? true,\n logger: options.tunnelDiagnosticLogger ?? (() => {}),\n };\n}\n\n/** Resolve the opt-in auto signal-handling config (undefined = leave signals alone). */\nfunction resolveGracefulShutdown(\n option:\n | boolean\n | { signals?: NodeJS.Signals[]; graceMs?: number }\n | undefined,\n drainGraceMs: number\n): { signals: NodeJS.Signals[]; graceMs: number } | undefined {\n // On by default: only an explicit `false` opts out.\n if (option === false) return undefined;\n if (option === undefined || option === true) {\n return { signals: [\"SIGTERM\"], graceMs: drainGraceMs };\n }\n const signals = option.signals ?? [\"SIGTERM\"];\n if (signals.length === 0) {\n throw new Error(\"tunnel: gracefulShutdown.signals must not be empty\");\n }\n return {\n signals,\n graceMs: positive(option.graceMs, drainGraceMs, \"gracefulShutdown.graceMs\"),\n };\n}\n\n/**\n * Build the `tls.connect` options for a tunnel target, or `undefined` for a\n * plaintext connection.\n *\n * Always offers ALPN `[\"h2\"]` — the same offer every Rust tunnel client\n * makes — and the connection layer requires the negotiation to succeed:\n * Node's http2 will only run a server session over a TLS socket whose ALPN\n * negotiated `h2`. Tunnel servers advertise it since the standard-h2\n * control-traffic change; older servers (which cleared their ALPN list)\n * cannot serve this client.\n */\nexport function buildTlsConnectOptions(\n tlsOption: boolean | TunnelTlsOptions,\n servername: string\n): tls.ConnectionOptions | undefined {\n if (tlsOption === false) return undefined;\n const base: tls.ConnectionOptions = { servername, ALPNProtocols: [\"h2\"] };\n if (tlsOption === true) return base;\n return {\n ...base,\n ...(tlsOption.servername !== undefined && {\n servername: tlsOption.servername,\n }),\n ...(tlsOption.ca !== undefined && { ca: tlsOption.ca }),\n ...(tlsOption.cert !== undefined && { cert: tlsOption.cert }),\n ...(tlsOption.key !== undefined && { key: tlsOption.key }),\n ...(tlsOption.rejectUnauthorized !== undefined && {\n rejectUnauthorized: tlsOption.rejectUnauthorized,\n }),\n };\n}\n\n/** The DNS SRV name for region-based tunnel-server discovery. */\nexport function srvNameForRegion(region: string): string {\n return `tunnel.${region}.restate.cloud`;\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The draining registry — graceful-drain handover ownership.\n//\n// When the cloud asks a connection to drain (`/_/drain-tunnel`), the\n// connection is \"detached\": its attempt settles (so the slot dials a\n// replacement) WITHOUT destroying the session, which keeps serving its\n// in-flight invocations. This registry owns those detached sessions:\n// each is bounded by a grace timer, removes itself when the session ends\n// naturally, and is destroyed unconditionally on engine teardown — a\n// fatal or close() must never leave a detached session serving (and\n// pinning the process) for the rest of its grace window.\n\nimport type * as http2 from \"node:http2\";\nimport type * as net from \"node:net\";\n\ninterface DrainingConnection {\n session: http2.Http2Session;\n socket: net.Socket;\n timer: NodeJS.Timeout;\n}\n\nexport class DrainingRegistry {\n private readonly entries = new Set<DrainingConnection>();\n\n /**\n * Take ownership of a detached (draining) connection: let it serve its\n * in-flight streams for up to `graceMs`, then tear it down. The entry\n * removes itself if the session ends earlier on its own.\n */\n add(session: http2.Http2Session, socket: net.Socket, graceMs: number): void {\n const entry: DrainingConnection = {\n session,\n socket,\n timer: setTimeout(() => {\n this.entries.delete(entry);\n session.destroy();\n socket.destroy();\n }, graceMs),\n };\n // unref'd: a draining session must not keep the process alive past\n // engine teardown (destroyAll covers the explicit paths).\n entry.timer.unref();\n this.entries.add(entry);\n session.on(\"close\", () => {\n clearTimeout(entry.timer);\n this.entries.delete(entry);\n socket.destroy();\n });\n }\n\n /** Tear down every draining connection. Idempotent. */\n destroyAll(): void {\n for (const entry of this.entries) {\n clearTimeout(entry.timer);\n entry.session.destroy();\n entry.socket.destroy();\n }\n this.entries.clear();\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The /_/start-tunnel handshake.\n// =============================================================================\n//\n// The tunnel server (the HTTP/2 client on the role-flipped connection)\n// opens its FIRST stream as `GET /_/start-tunnel`, with a request body that\n// stays open and later delivers HTTP/2 TRAILERS. The exchange:\n//\n// 1. We answer immediately: `200` whose RESPONSE HEADERS carry our\n// credentials — `authorization: Bearer <token>`,\n// `environment-id: env_<id>`, `tunnel-name: <name>`, advisory diagnostic\n// ids (`tunnel-worker-id`, `tunnel-connection-id`), and `supports-drain:\n// true` when the drain handover is enabled (the default — see the\n// /_/drain-tunnel handling in connect.ts).\n// 2. The server validates the credentials, then completes the handshake\n// by sending TRAILERS on its still-open request body:\n// `tunnel-status: ok | unauthorized | bad-tunnel-name | too-many-tunnels`\n// plus, on ok: `proxy-url`, `tunnel-url`, `tunnel-name`.\n//\n// Node gotcha (PoC-verified): the high-level Http2ServerRequest \"trailers\"\n// event does NOT fire. Trailers must be read from the raw stream —\n// `req.stream.on(\"trailers\", ...)` — or from `req.trailers` after \"end\".\n// The body must be drained for either to fire.\n//\n// Outcome taxonomy (drives the reconnect policy in connect.ts):\n// - fatal: unauthorized, bad-tunnel-name, or a tunnel-name echo\n// mismatch. Configuration errors — redialing cannot fix\n// them, and hammering the auth path is harmful.\n// - retryable: too-many-tunnels (often a previous instance still\n// draining), timeout, malformed/missing trailers, stream\n// errors, and unknown statuses (forward compatibility).\n\nimport type * as http2 from \"node:http2\";\n\n/** What the server tells us about the established tunnel. */\nexport interface HandshakeInfo {\n tunnelName: string;\n proxyUrl: string;\n tunnelUrl: string;\n}\n\nexport type HandshakeOutcome =\n | { kind: \"ok\"; info: HandshakeInfo }\n | { kind: \"fatal\"; reason: string }\n | { kind: \"retryable\"; reason: string };\n\nexport interface HandshakeCredentials {\n authToken: string;\n environmentId: string;\n tunnelName: string;\n /** Stable-ish per SDK worker/process, for cross-side diagnostics. */\n tunnelWorkerId: string;\n /** Unique per h2 tunnel connection attempt, for cross-side diagnostics. */\n tunnelConnectionId: string;\n /**\n * Advertise `supports-drain: true`. Only set this when the engine\n * actually implements the `/_/drain-tunnel` handover — advertising it\n * obliges us to open a replacement connection on drain.\n */\n supportsDrain: boolean;\n /**\n * Advertise `supports-client-drain: true`. Tells the server that on\n * shutdown we proactively send GOAWAY and refuse any raced streams with the\n * `x-restate-tunnel-draining` sentinel (rather than dropping them); only\n * then does the server trust that sentinel to deselect this connection.\n */\n supportsClientDrain: boolean;\n}\n\nexport const START_TUNNEL_PATH = \"/_/start-tunnel\";\n\n/** Handshake deadline — mirrors the tunnel server's own 5s timeout. */\nexport const HANDSHAKE_TIMEOUT_MS = 5_000;\n\n/**\n * Run the receiver side of the /_/start-tunnel exchange on its stream.\n * Resolves with an outcome; never rejects.\n */\nexport function performHandshake(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse,\n creds: HandshakeCredentials,\n timeoutMs: number = HANDSHAKE_TIMEOUT_MS\n): Promise<HandshakeOutcome> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (outcome: HandshakeOutcome) => {\n if (settled) return;\n settled = true;\n clearTimeout(deadline);\n resolve(outcome);\n };\n\n const deadline = setTimeout(() => {\n finish({\n kind: \"retryable\",\n reason: `handshake trailers not received within ${timeoutMs}ms`,\n });\n req.stream.destroy();\n }, timeoutMs);\n deadline.unref();\n\n const onTrailers = (trailers: http2.IncomingHttpHeaders) => {\n const status = trailers[\"tunnel-status\"];\n if (status !== \"ok\") {\n if (status === \"unauthorized\" || status === \"bad-tunnel-name\") {\n finish({ kind: \"fatal\", reason: `tunnel-status: ${String(status)}` });\n } else {\n finish({\n kind: \"retryable\",\n reason: `tunnel-status: ${String(status ?? \"<missing>\")}`,\n });\n }\n return;\n }\n const tunnelName = trailers[\"tunnel-name\"];\n const proxyUrl = trailers[\"proxy-url\"];\n const tunnelUrl = trailers[\"tunnel-url\"];\n if (\n typeof tunnelName !== \"string\" ||\n typeof proxyUrl !== \"string\" ||\n typeof tunnelUrl !== \"string\"\n ) {\n finish({\n kind: \"retryable\",\n reason: \"handshake ok but proxy-url/tunnel-url/tunnel-name missing\",\n });\n return;\n }\n if (tunnelName !== creds.tunnelName) {\n // We requested a specific name; the server must echo it. A different\n // name means our registration URL would not route here.\n finish({\n kind: \"fatal\",\n reason: `tunnel-name mismatch: requested ${JSON.stringify(creds.tunnelName)}, got ${JSON.stringify(tunnelName)}`,\n });\n return;\n }\n finish({ kind: \"ok\", info: { tunnelName, proxyUrl, tunnelUrl } });\n };\n\n // PoC-verified: only the raw stream's \"trailers\" event fires; also read\n // req.trailers after \"end\" as a belt-and-braces fallback.\n req.stream.on(\"trailers\", onTrailers);\n req.on(\"end\", () => {\n if (!settled && req.trailers && Object.keys(req.trailers).length > 0) {\n onTrailers(req.trailers);\n }\n });\n req.on(\"error\", (err) => {\n finish({\n kind: \"retryable\",\n reason: `handshake stream error: ${err.message}`,\n });\n });\n req.stream.on(\"close\", () => {\n finish({\n kind: \"retryable\",\n reason: \"handshake stream closed before trailers\",\n });\n });\n // Drain the (empty) body so \"end\"/\"trailers\" can fire.\n req.resume();\n\n // Answer with our credentials. The request side stays open for trailers.\n res.writeHead(200, {\n authorization: `Bearer ${creds.authToken}`,\n \"environment-id\": creds.environmentId,\n \"tunnel-name\": creds.tunnelName,\n \"tunnel-worker-id\": creds.tunnelWorkerId,\n \"tunnel-connection-id\": creds.tunnelConnectionId,\n ...(creds.supportsDrain && { \"supports-drain\": \"true\" }),\n ...(creds.supportsClientDrain && { \"supports-client-drain\": \"true\" }),\n });\n res.end();\n });\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Forwarded-path handling. Pure — no I/O.\n\n/**\n * Strip the tunnel's forwarded prefix `/<scheme>/<host>/<port>` and return\n * the tail — the path the SDK should see.\n *\n * A forwarded invocation arrives down the tunnel with its destination\n * encoded in the path (`/http/my-service.ns.svc.cluster.local/9080/invoke/...`);\n * the cloud proxy has already stripped the `/<env>/<tunnel>` rendezvous\n * prefix. For an in-process SDK deployment the scheme/host/port are\n * vestigial (the receiver *is* the service), so we drop exactly those three\n * segments and keep the tail (`/discover`, `/invoke/<svc>/<handler>`, …).\n *\n * The tail is passed through without re-encoding: the SDK verifies each\n * request's identity JWT against the signed service-relative path (its\n * routing and verification tolerate extra path *prefixes*, but re-encoding,\n * normalization or case folding of the tail itself would break the match).\n * The query string is preserved (it is not part of `aud`).\n *\n * Returns `null` if the path isn't a forwarded `/<scheme>/<host>/<port>/...`\n * path.\n */\nexport function forwardedTail(rawUrl: string): string | null {\n const qIdx = rawUrl.indexOf(\"?\");\n const path = qIdx === -1 ? rawUrl : rawUrl.slice(0, qIdx);\n const query = qIdx === -1 ? \"\" : rawUrl.slice(qIdx);\n const seg = path.split(\"/\"); // [\"\", scheme, host, port, ...tail]\n // The port segment must be numeric — that's what distinguishes a real\n // forwarded prefix from an unprefixed SDK path that happens to have three\n // segments (e.g. `/invoke/Svc/handler` must NOT parse as scheme=invoke,\n // host=Svc, port=handler and dispatch `/` to the SDK).\n if (\n seg.length < 4 ||\n seg[1] === \"\" ||\n seg[2] === \"\" ||\n !/^\\d+$/.test(seg[3]!)\n ) {\n return null;\n }\n const tail = \"/\" + seg.slice(4).join(\"/\");\n return query ? tail + query : tail;\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// A single tunnel connection attempt.\n// =============================================================================\n//\n// One dial → serve → end cycle against one tunnel server, structured as an\n// explicit pipeline of stages driven by `ConnectionAttempt.drive()`:\n//\n// dial() — connect TCP + TLS, verify ALPN h2 (a self-contained stage\n// that owns its own connect-timeout / abort wiring).\n// establish() — role-flip: become the HTTP/2 *server* on the socket we\n// dialed; obtain the session.\n// handshake — the cloud (h2 client) opens `GET /_/start-tunnel`; we run\n// the credentials/trailers exchange (handshake.ts).\n// serve — each forwarded invocation is one h2 stream; strip\n// `/<scheme>/<host>/<port>` and hand it to the SDK handler.\n//\n// The lifecycle is one explicit `AttemptState` value, and every phase-owned\n// resource lives ON the phase that owns it — the handshake timer on\n// `handshaking`, the liveness `watchdog` on `serving`/`draining` — so each\n// method narrows the state and destructures what it needs (`const { session,\n// watchdog } = this.state`) rather than reaching for nullable instance fields.\n// Only the two genuinely lifetime-scoped things are fields: `socket` (used for\n// teardown in every phase, and handed to the registry on a server drain) and\n// `completion` (resolves `run()` exactly once).\n//\n// Two behaviours don't fit one linear phase:\n// * `run()`-resolution is decoupled from session teardown. A *server* drain\n// resolves `run()` immediately (so the slot redials) while the detached\n// session keeps serving its in-flight invocations from the\n// DrainingRegistry — the zero-drop property.\n// * New-invocation refusal during shutdown is engine-wide, so it is gated on\n// `deps.isShuttingDown()` in addition to this connection's own state.\n\nimport * as net from \"node:net\";\nimport * as tls from \"node:tls\";\nimport * as http2 from \"node:http2\";\nimport { randomBytes } from \"node:crypto\";\n\nimport type { ResolvedOptions } from \"./options.js\";\nimport { buildTlsConnectOptions } from \"./options.js\";\nimport type { Target } from \"./targets.js\";\nimport {\n performHandshake,\n START_TUNNEL_PATH,\n type HandshakeInfo,\n} from \"./handshake.js\";\nimport { forwardedTail } from \"./forwarded.js\";\nimport type { DrainingRegistry } from \"./draining.js\";\n\n/** Why a connection ended — drives the slot's reconnect policy. */\nexport type ConnectionOutcome =\n | { kind: \"served\"; uptimeMs: number } // handshake ok'd, served, then closed → redial\n | { kind: \"drained\"; uptimeMs: number } // server asked us to rotate → redial promptly\n | { kind: \"retryable\"; reason: string } // redial with backoff\n | { kind: \"fatal\"; reason: string }; // stop the tunnel, surface an error\n\n/**\n * Why a connection is draining.\n * - `server`: the cloud sent `/_/drain-tunnel` (it is rotating this tunnel\n * node). We detach + redial; the old session keeps serving in-flight.\n * - `client`: this process is shutting down (SIGTERM / `shutdown()`). We\n * send GOAWAY, refuse raced invocations, and finish in-flight in place,\n * with no redial.\n */\nexport type DrainTrigger = \"server\" | \"client\";\n\nconst CLIENT_DRAIN_SESSION_CLOSE_TIMEOUT_MS = 1_000;\nconst TUNNEL_DRAINING_HEADER = \"x-restate-tunnel-draining\";\nconst CROCKFORD_BASE32 = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nfunction encodeBase32(value: bigint, length: number): string {\n let out = \"\";\n for (let i = 0; i < length; i++) {\n out = CROCKFORD_BASE32.charAt(Number(value & 31n)) + out;\n value >>= 5n;\n }\n return out;\n}\n\nfunction newTunnelConnectionId(): string {\n let random = 0n;\n for (const byte of randomBytes(10)) {\n random = (random << 8n) | BigInt(byte);\n }\n return `${encodeBase32(BigInt(Date.now()), 10)}${encodeBase32(random, 16)}`;\n}\n\nfunction formatIdentity(workerId: string, connectionId: string): string {\n return `worker_id=${workerId} connection_id=${connectionId}`;\n}\n\nfunction targetLabel(target: Target): string {\n return `${target.host}:${target.port}`;\n}\n\nfunction formatConnectionOutcome(outcome: ConnectionOutcome): string {\n switch (outcome.kind) {\n case \"served\":\n return `served uptimeMs=${outcome.uptimeMs}`;\n case \"drained\":\n return `drained uptimeMs=${outcome.uptimeMs}`;\n case \"retryable\":\n return `retryable reason=${outcome.reason}`;\n case \"fatal\":\n return `fatal reason=${outcome.reason}`;\n }\n}\n\nfunction formatSettings(settings: http2.Settings): string {\n const entries = Object.entries(settings).filter(\n ([, value]) => value !== undefined\n );\n if (entries.length === 0) return \"{}\";\n return `{${entries\n .map(([key, value]) => `${key}=${String(value)}`)\n .join(\", \")}}`;\n}\n\nfunction pathWithoutQuery(url: string | undefined): string {\n if (url === undefined) return \"?\";\n const queryStart = url.indexOf(\"?\");\n return queryStart === -1 ? url : url.slice(0, queryStart);\n}\n\nfunction endInternalError(res: http2.Http2ServerResponse): void {\n try {\n if (!res.headersSent) res.writeHead(500);\n if (!res.writableEnded) res.end(\"tunnel: SDK handler error\");\n } catch {\n // The stream may already be closing; keep the session lifecycle contained.\n }\n}\n\n/** The Node request handler produced by the SDK's createEndpointHandler. */\ntype SdkHandler = ReturnType<\n typeof import(\"@restatedev/restate-sdk\").createEndpointHandler\n>;\n\n/**\n * The engine's handle on a live connection: lets `shutdown()` ask each one to\n * begin and finish a client-initiated drain.\n */\nexport interface DrainableConnection {\n beginClientDrain(): void;\n finishClientDrain(opts: { force: boolean }): Promise<void>;\n}\n\nexport interface ConnectionIdentity {\n workerId: string;\n connectionId: string;\n target: string;\n}\n\n/** What a connection attempt needs from the engine. */\nexport interface ConnectionDeps {\n opts: ResolvedOptions;\n /** Built once by the engine; stateless per call, shared across streams. */\n sdkHandler: SdkHandler;\n /** Takes ownership of a detached session on a server-drain handover. */\n draining: DrainingRegistry;\n /** Engine-level socket registry, so close() can destroy in-flight dials. */\n activeSockets: Set<net.Socket>;\n /** Live connections the engine can ask to drain on shutdown(). */\n activeConnections: Set<DrainableConnection>;\n /** Called once per successful handshake (count, learned info, ready). */\n onEstablished: (info: HandshakeInfo, identity: ConnectionIdentity) => void;\n /**\n * True once the engine is gracefully shutting down: new forwarded\n * invocations are refused with the drain sentinel instead of dispatched, so\n * the cloud deselects this connection while in-flight invocations finish.\n * Engine-wide (every connection refuses), so it is checked in addition to\n * this connection's own `draining{client}` state.\n */\n isShuttingDown: () => boolean;\n /** True once the startup readiness gate has passed. */\n isStartupReady: () => boolean;\n /** A forwarded invocation began executing (counts toward the drain wait). */\n inflightStarted: () => void;\n /** A forwarded invocation finished (its stream closed). */\n inflightEnded: () => void;\n}\n\n/**\n * The connection's lifecycle as one explicit value. Each phase carries exactly\n * the resources it owns, so a method cannot touch a resource that the current\n * phase has no business with:\n *\n * connecting — dialing the socket + TLS; no h2 session yet.\n * handshaking — session is up; `firstRequestTimer` bounds the wait for the\n * cloud to open /_/start-tunnel; `handshake` is set once it\n * does (gate streams park on it until it resolves).\n * serving — handshake ok'd; forwarding invocations to the SDK, with the\n * liveness `watchdog` running.\n * draining — winding down; `trigger` records who asked. A `client` drain\n * refuses new invocations; a `server` drain keeps serving its\n * detached session until the registry closes it.\n * closed — terminal; the session/socket are gone.\n */\ntype AttemptState =\n | { readonly kind: \"connecting\" }\n | {\n readonly kind: \"handshaking\";\n readonly session: http2.Http2Session;\n readonly firstRequestTimer: NodeJS.Timeout;\n handshake: Promise<{ ok: boolean }> | undefined;\n }\n | {\n readonly kind: \"serving\";\n readonly session: http2.Http2Session;\n readonly openedAt: number;\n readonly watchdog: Watchdog;\n }\n | {\n readonly kind: \"draining\";\n readonly session: http2.Http2Session;\n readonly openedAt: number;\n readonly trigger: DrainTrigger;\n readonly watchdog: Watchdog;\n }\n | { readonly kind: \"closed\" };\n\n/** A request classified by its (control or forwarded) intent — pure routing. */\ntype TunnelRequest =\n | { kind: \"start-tunnel\" } // the cloud opening the handshake stream\n | { kind: \"health\" } // GET /_/health liveness probe\n | { kind: \"drain\" } // /_/drain-tunnel: the cloud asks us to rotate\n | { kind: \"forwarded\" }; // anything else: a forwarded invocation\n\n/** Classify an incoming h2 request. Control paths are cloud-originated and\n * arrive UNPREFIXED (before any destination-prefix stripping). */\nfunction classifyRequest(req: http2.Http2ServerRequest): TunnelRequest {\n const rawPath = (req.url ?? \"\").split(\"?\")[0];\n if (req.method === \"GET\" && rawPath === START_TUNNEL_PATH) {\n return { kind: \"start-tunnel\" };\n }\n if (rawPath === \"/_/health\") return { kind: \"health\" };\n if (rawPath === \"/_/drain-tunnel\") return { kind: \"drain\" };\n return { kind: \"forwarded\" };\n}\n\n/** Resolves a connection attempt's outcome exactly once. */\nclass Completion {\n private done = false;\n private resolveFn!: (outcome: ConnectionOutcome) => void;\n readonly promise: Promise<ConnectionOutcome> = new Promise((resolve) => {\n this.resolveFn = resolve;\n });\n\n get settled(): boolean {\n return this.done;\n }\n\n /** Resolve once; returns false if it was already resolved. */\n resolve(outcome: ConnectionOutcome): boolean {\n if (this.done) return false;\n this.done = true;\n this.resolveFn(outcome);\n return true;\n }\n}\n\n/**\n * Liveness watchdog: periodic h2 PING; `pingMaxMissed` consecutive misses mean\n * the connection is half-open (the OS may never surface it), so `onDead` fires.\n * Owns its own timer/miss state so the connection doesn't have to.\n */\nclass Watchdog {\n private interval: NodeJS.Timeout | undefined;\n private missed = 0;\n\n constructor(\n private readonly session: http2.Http2Session,\n private readonly opts: ResolvedOptions,\n private readonly onDead: () => void\n ) {}\n\n start(): void {\n this.interval = setInterval(() => this.beat(), this.opts.pingIntervalMs);\n this.interval.unref();\n }\n\n stop(): void {\n if (this.interval !== undefined) clearInterval(this.interval);\n }\n\n private beat(): void {\n if (this.session.destroyed) return;\n let acked = false;\n try {\n this.session.ping((err) => {\n if (err === null) {\n acked = true;\n this.missed = 0;\n }\n });\n } catch {\n return;\n }\n const t = setTimeout(() => {\n if (acked || this.session.destroyed) return;\n this.missed++;\n if (this.missed >= this.opts.pingMaxMissed) this.onDead();\n }, this.opts.pingTimeoutMs);\n t.unref();\n }\n}\n\n/** Connect result: a connected, ALPN-verified socket, or a terminal outcome. */\ntype DialResult =\n | { ok: true; socket: net.Socket }\n | { ok: false; outcome: ConnectionOutcome };\n\n/**\n * The dial stage: connect TCP (+ TLS), bounded by `connectTimeoutMs` and the\n * slot abort, and require ALPN to have negotiated h2. Owns the socket until it\n * either hands it back connected or destroys it on failure — so all of the\n * connect-phase timer/listener state stays local here.\n */\nfunction dial(\n target: Target,\n deps: ConnectionDeps,\n plaintext: boolean,\n signal: AbortSignal,\n connectionId: string\n): Promise<DialResult> {\n const log = deps.opts.logger;\n const identity = formatIdentity(deps.opts.tunnelWorkerId, connectionId);\n const tlsOptions = plaintext\n ? undefined\n : buildTlsConnectOptions(deps.opts.tls, target.servername);\n const socket = plaintext\n ? net.connect({ host: target.host, port: target.port })\n : tls.connect({ host: target.host, port: target.port, ...tlsOptions });\n\n return new Promise<DialResult>((resolve) => {\n let done = false;\n const label = targetLabel(target);\n const onError = (err: Error) => fail(`socket error: ${err.message}`);\n const onAbort = () => fail(\"tunnel closed\");\n const timer = setTimeout(\n () => fail(`connect timeout after ${deps.opts.connectTimeoutMs}ms`),\n deps.opts.connectTimeoutMs\n );\n timer.unref();\n\n const cleanup = () => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", onAbort);\n socket.removeListener(\"error\", onError);\n };\n function fail(reason: string) {\n if (done) return;\n done = true;\n cleanup();\n log(`tunnel: failed to connect to ${label}: ${reason} (${identity})`);\n socket.destroy();\n resolve({ ok: false, outcome: { kind: \"retryable\", reason } });\n }\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n socket.on(\"error\", onError);\n socket.once(plaintext ? \"connect\" : \"secureConnect\", () => {\n if (done) return;\n socket.setNoDelay(true);\n const alpn = plaintext\n ? \"plaintext\"\n : `tls alpn=${JSON.stringify((socket as tls.TLSSocket).alpnProtocol)}`;\n // Node's http2 requires ALPN to have negotiated h2 before it will run a\n // server session over a TLS socket. A server that doesn't negotiate is\n // too old for this client (see the README's server-version note).\n if (!plaintext && (socket as tls.TLSSocket).alpnProtocol !== \"h2\") {\n fail(\n \"tunnel server did not negotiate h2 ALPN — it predates standard-h2 control traffic and cannot serve this client\"\n );\n return;\n }\n done = true;\n cleanup();\n log(`tunnel: connected socket to ${label} (${alpn}, ${identity})`);\n resolve({ ok: true, socket });\n });\n });\n}\n\n/**\n * Run one connection attempt. Resolves (never rejects) with the outcome\n * when the connection ends; `slotSignal` aborts the attempt at any phase.\n */\nexport function runConnection(\n target: Target,\n slotSignal: AbortSignal,\n deps: ConnectionDeps\n): Promise<ConnectionOutcome> {\n // Resolved once per attempt, before dialing: a file-sourced token is\n // re-read on every redial so rotations are picked up, and a read failure\n // (e.g. mid-rotation) is a retryable outcome rather than a crash.\n let authToken: string;\n try {\n authToken = deps.opts.authToken();\n } catch (err) {\n return Promise.resolve({\n kind: \"retryable\",\n reason: `auth token unavailable: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n return new ConnectionAttempt(target, slotSignal, deps, authToken).run();\n}\n\nclass ConnectionAttempt implements DrainableConnection {\n private state: AttemptState = { kind: \"connecting\" };\n private readonly completion = new Completion();\n /** Lifetime-scoped: destroyed on teardown in any phase, handed to the\n * registry on a server drain. */\n private socket: net.Socket | undefined;\n\n private readonly plaintext: boolean;\n private readonly log: (message: string) => void;\n private readonly connectionId = newTunnelConnectionId();\n\n constructor(\n private readonly target: Target,\n private readonly slotSignal: AbortSignal,\n private readonly deps: ConnectionDeps,\n private readonly authToken: string\n ) {\n this.log = deps.opts.logger;\n this.plaintext = target.plaintext ?? deps.opts.tls === false;\n }\n\n private get identity(): ConnectionIdentity {\n return {\n workerId: this.deps.opts.tunnelWorkerId,\n connectionId: this.connectionId,\n target: targetLabel(this.target),\n };\n }\n\n private identityLog(): string {\n return formatIdentity(this.deps.opts.tunnelWorkerId, this.connectionId);\n }\n\n private logWithIdentity(message: string): void {\n this.log(`${message} (${this.identityLog()})`);\n }\n\n run(): Promise<ConnectionOutcome> {\n this.deps.activeConnections.add(this);\n void this.drive();\n return this.completion.promise;\n }\n\n /** Stage pipeline: dial → establish (role-flip). The remaining stages\n * (handshake, serve) are event-driven from the h2 server's request handler. */\n private async drive(): Promise<void> {\n const dialed = await dial(\n this.target,\n this.deps,\n this.plaintext,\n this.slotSignal,\n this.connectionId\n );\n // A client-drain (shutdown) or abort may have settled us mid-dial.\n if (this.completion.settled) {\n if (dialed.ok) dialed.socket.destroy();\n this.deps.activeConnections.delete(this);\n return;\n }\n if (!dialed.ok) {\n this.settle(dialed.outcome);\n return;\n }\n this.socket = dialed.socket;\n this.deps.activeSockets.add(dialed.socket);\n this.slotSignal.addEventListener(\"abort\", this.onAbort, { once: true });\n dialed.socket.on(\"error\", (err: Error) =>\n this.settle(\n this.endOutcome(`socket error: ${err.message}`),\n `socket error: ${err.message}`\n )\n );\n dialed.socket.on(\"close\", () =>\n this.settle(\n this.endOutcome(\"connection closed before handshake completed\"),\n \"socket closed\"\n )\n );\n this.establish(dialed.socket);\n }\n\n // ---- state helpers ----\n\n private get closed(): boolean {\n return this.state.kind === \"closed\";\n }\n\n private session(): http2.Http2Session | undefined {\n const s = this.state;\n return s.kind === \"handshaking\" ||\n s.kind === \"serving\" ||\n s.kind === \"draining\"\n ? s.session\n : undefined;\n }\n\n private get openedAt(): number | undefined {\n const s = this.state;\n return s.kind === \"serving\" || s.kind === \"draining\"\n ? s.openedAt\n : undefined;\n }\n\n private uptimeMs(): number {\n return this.openedAt === undefined ? 0 : Date.now() - this.openedAt;\n }\n\n /** The end-of-connection outcome: \"served\" once established, else retryable. */\n private endOutcome(reason: string): ConnectionOutcome {\n return this.openedAt !== undefined\n ? { kind: \"served\", uptimeMs: this.uptimeMs() }\n : { kind: \"retryable\", reason };\n }\n\n // ---- teardown ----\n\n private readonly onAbort = () =>\n this.settle({ kind: \"retryable\", reason: \"tunnel closed\" });\n\n /** Stop the timers/monitors owned by the current phase. */\n private stopPhaseResources(): void {\n const s = this.state;\n if (s.kind === \"handshaking\") clearTimeout(s.firstRequestTimer);\n else if (s.kind === \"serving\" || s.kind === \"draining\") s.watchdog.stop();\n }\n\n /** Detach from the engine registries and the slot-abort listener. */\n private deregister(): void {\n this.slotSignal.removeEventListener(\"abort\", this.onAbort);\n this.deps.activeConnections.delete(this);\n if (this.socket !== undefined) this.deps.activeSockets.delete(this.socket);\n }\n\n /**\n * The single terminal path — resolve the outcome (once), destroy the\n * session/socket, and move to `closed`. Idempotent. A server drain does NOT\n * funnel through here for teardown: it detaches via {@link beginServerDrain}\n * and lets the DrainingRegistry destroy the session later.\n */\n private settle(outcome: ConnectionOutcome, detail?: string): void {\n if (this.closed) return;\n const phase = this.state.kind;\n const session = this.session();\n this.stopPhaseResources();\n this.deregister();\n this.state = { kind: \"closed\" };\n session?.destroy();\n this.socket?.destroy();\n const firstOutcome = this.completion.resolve(outcome);\n this.logWithIdentity(\n `tunnel: connection to ${targetLabel(this.target)} closed (phase=${phase}, outcome=${formatConnectionOutcome(outcome)}${\n detail === undefined ? \"\" : `, detail=${detail}`\n }${firstOutcome ? \"\" : \", already reported\"})`\n );\n }\n\n private closeSessionGracefully(session: http2.Http2Session): Promise<void> {\n return new Promise((resolve) => {\n if (session.closed || session.destroyed) {\n resolve();\n return;\n }\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n this.settle(\n { kind: \"served\", uptimeMs: this.uptimeMs() },\n \"session.close() timed out\"\n );\n finish();\n }, CLIENT_DRAIN_SESSION_CLOSE_TIMEOUT_MS);\n timer.unref();\n session.once(\"close\", finish);\n try {\n session.close();\n } catch {\n this.settle(\n this.endOutcome(\"session close failed\"),\n \"session close failed\"\n );\n finish();\n }\n });\n }\n\n private sendClientDrainGoaway(session: http2.Http2Session): void {\n if (session.closed || session.destroyed) return;\n try {\n session.goaway(http2.constants.NGHTTP2_NO_ERROR);\n this.logWithIdentity(\n `tunnel: sent client-drain GOAWAY to ${targetLabel(this.target)}`\n );\n } catch (err) {\n this.logWithIdentity(\n `tunnel: failed to send client-drain GOAWAY to ${targetLabel(this.target)}: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n // The session may be closing under us. dispatchForwarded still refuses\n // any raced streams once the state flips to client-draining.\n }\n }\n\n // ---- establish: role-flip ----\n\n private establish(socket: net.Socket): void {\n this.logWithIdentity(\n `tunnel: connected to ${this.target.host}:${this.target.port}, starting handshake`\n );\n\n const h2 = http2.createServer(\n {\n maxSessionMemory: this.deps.opts.maxSessionMemory,\n settings: {\n // TODO why not allow to configure the other h2 options?\n maxConcurrentStreams: this.deps.opts.maxConcurrentStreams,\n initialWindowSize: 1024 * 1024,\n maxFrameSize: 65536,\n },\n },\n (req, res) => this.handleRequest(req, res)\n );\n\n h2.on(\"session\", (s) => {\n this.logWithIdentity(\n `tunnel: h2 session established to ${targetLabel(this.target)} (localSettings=${formatSettings(\n s.localSettings\n )}, remoteSettings=${formatSettings(s.remoteSettings)})`\n );\n s.on(\"localSettings\", (settings: http2.Settings) =>\n this.logWithIdentity(\n `tunnel: h2 local settings acknowledged by ${targetLabel(this.target)}: ${formatSettings(settings)}`\n )\n );\n s.on(\"remoteSettings\", (settings: http2.Settings) =>\n this.logWithIdentity(\n `tunnel: h2 remote settings from ${targetLabel(this.target)}: ${formatSettings(settings)}`\n )\n );\n // Role-flip complete: the cloud is now our h2 client. connecting →\n // handshaking, arming the timer that fires if the server never opens\n // /_/start-tunnel.\n if (this.state.kind === \"connecting\") {\n const firstRequestTimer = setTimeout(() => {\n if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake === undefined\n ) {\n this.settle({\n kind: \"retryable\",\n reason: \"server never initiated /_/start-tunnel\",\n });\n }\n }, this.deps.opts.handshakeTimeoutMs);\n firstRequestTimer.unref();\n this.state = {\n kind: \"handshaking\",\n session: s,\n firstRequestTimer,\n handshake: undefined,\n };\n }\n try {\n // Raise the per-connection flow-control window (Node defaults to\n // 64 KiB, throttling aggregate throughput across streams).\n (\n s as unknown as { setLocalWindowSize?: (n: number) => void }\n ).setLocalWindowSize?.(this.deps.opts.connectionWindowSize);\n } catch {\n // Older Node — per-stream windows still apply.\n }\n s.on(\"close\", () =>\n this.settle(\n this.endOutcome(\"session closed before handshake completed\"),\n \"session closed\"\n )\n );\n s.on(\"error\", (err: Error) =>\n this.settle(\n this.endOutcome(`session error: ${err.message}`),\n `session error: ${err.message}`\n )\n );\n });\n h2.on(\"sessionError\", (err: Error) =>\n this.settle(\n this.endOutcome(`session error: ${err.message}`),\n `session error: ${err.message}`\n )\n );\n\n h2.emit(\"connection\", socket);\n }\n\n // ---- request routing ----\n\n private handleRequest(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n switch (classifyRequest(req).kind) {\n case \"health\":\n res.writeHead(200);\n res.end();\n return;\n case \"drain\":\n this.handleDrainRequest(res);\n return;\n case \"start-tunnel\":\n if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake === undefined\n ) {\n this.startHandshake(req, res);\n } else {\n this.notReady(res);\n }\n return;\n case \"forwarded\":\n this.handleForwarded(req, res);\n return;\n }\n }\n\n private handleForwarded(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n // Serving, or draining (a server-drained session keeps serving its\n // in-flight; dispatchForwarded refuses only a client drain / shutdown).\n if (this.state.kind === \"serving\" || this.state.kind === \"draining\") {\n this.dispatchForwarded(req, res);\n return;\n }\n // A stream that raced the handshake parks on its outcome (the cloud fires\n // work the instant the tunnel registers, coalescing it with the\n // ok-trailers) rather than being rejected.\n if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake !== undefined\n ) {\n const handshake = this.state.handshake;\n void handshake.then(({ ok }) => {\n if (this.closed || res.stream.destroyed) return;\n try {\n if (ok) this.dispatchForwarded(req, res);\n else {\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} before tunnel handshake completed`\n );\n this.notReady(res);\n }\n } catch {\n // The session may be tearing down under us.\n }\n });\n return;\n }\n // Before /_/start-tunnel was even opened (or already gone) — not a tunnel\n // server speaking the protocol.\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} before tunnel handshake completed`\n );\n this.notReady(res);\n }\n\n private notReady(res: http2.Http2ServerResponse): void {\n res.writeHead(503, { [TUNNEL_DRAINING_HEADER]: \"true\" });\n res.end(\"tunnel: not ready\");\n }\n\n /** First stream: run the handshake; its outcome opens the gate. */\n private startHandshake(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n if (this.state.kind !== \"handshaking\") return;\n clearTimeout(this.state.firstRequestTimer);\n this.state.handshake = performHandshake(\n req,\n res,\n {\n authToken: this.authToken,\n environmentId: this.deps.opts.environmentId,\n tunnelName: this.deps.opts.tunnelName,\n tunnelWorkerId: this.deps.opts.tunnelWorkerId,\n tunnelConnectionId: this.connectionId,\n supportsDrain: this.deps.opts.supportsDrain,\n supportsClientDrain: this.deps.opts.supportsClientDrain,\n },\n this.deps.opts.handshakeTimeoutMs\n ).then((outcome) => {\n // settle (session/socket error) may have raced us to `closed`.\n if (this.state.kind !== \"handshaking\") return { ok: false };\n if (outcome.kind === \"ok\") {\n const { session } = this.state;\n const openedAt = Date.now();\n const watchdog = this.startWatchdog(session);\n // If our own shutdown began while we were handshaking, open straight\n // into a client drain so this connection refuses work from the start.\n this.state = this.deps.isShuttingDown()\n ? { kind: \"draining\", session, openedAt, trigger: \"client\", watchdog }\n : { kind: \"serving\", session, openedAt, watchdog };\n this.logWithIdentity(\n `tunnel: established (name=${outcome.info.tunnelName}, proxy=${outcome.info.proxyUrl})`\n );\n this.deps.onEstablished(outcome.info, this.identity);\n return { ok: true };\n }\n this.settle(outcome);\n return { ok: false };\n });\n }\n\n /** Strip the destination prefix and hand the stream to the SDK. */\n private dispatchForwarded(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n // Client-initiated drain (this connection, or an engine-wide shutdown):\n // refuse new invocations WITHOUT running the handler. The sentinel tells\n // the server to stop routing here; failing the request (rather than\n // running it) lets the runtime retry it on a healthy connection with no\n // risk of double-execution. A *server* drain does not refuse — its\n // detached session keeps serving (the zero-drop property).\n const clientDraining =\n this.state.kind === \"draining\" && this.state.trigger === \"client\";\n if (!this.deps.isStartupReady()) {\n // Defensive fallback: the supervisor gates dialing until startupReady\n // passes, so normal protocol traffic should not reach this state.\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} before startup readiness gate completed`\n );\n this.notReady(res);\n return;\n }\n if (clientDraining || this.deps.isShuttingDown()) {\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} during client drain`\n );\n res.writeHead(503, { [TUNNEL_DRAINING_HEADER]: \"true\" });\n res.end();\n return;\n }\n const tail = forwardedTail(req.url ?? \"\");\n if (tail === null) {\n res.writeHead(400);\n res.end(\"tunnel: malformed forwarded path\");\n return;\n }\n req.url = tail;\n // Count this invocation as in-flight so shutdown() waits for it to finish.\n this.deps.inflightStarted();\n res.stream.once(\"close\", () => this.deps.inflightEnded());\n res.stream.once(\"error\", (err: Error) => {\n const streamId = res.stream.id ?? \"?\";\n const logPath = pathWithoutQuery(req.url);\n this.logWithIdentity(\n `tunnel: forwarded stream ${streamId} ${req.method ?? \"?\"} ${logPath} failed: ${\n err.message\n }`\n );\n });\n try {\n this.deps.sdkHandler(req, res);\n } catch (err) {\n const streamId = res.stream.id ?? \"?\";\n const logPath = pathWithoutQuery(req.url);\n this.logWithIdentity(\n `tunnel: SDK handler threw for forwarded stream ${streamId} ${\n req.method ?? \"?\"\n } ${logPath}: ${err instanceof Error ? err.message : String(err)}`\n );\n endInternalError(res);\n }\n }\n\n // ---- drain ----\n\n private handleDrainRequest(res: http2.Http2ServerResponse): void {\n res.writeHead(200);\n res.end();\n this.logWithIdentity(\n `tunnel: received server drain notification from ${targetLabel(this.target)}`\n );\n if (!this.deps.opts.supportsDrain) {\n // Not advertised, so unexpected — acknowledge and let the server\n // close on us; the slot's redial loop re-establishes.\n this.logWithIdentity(\n \"tunnel: received /_/drain-tunnel (drain not advertised) — acknowledging\"\n );\n return;\n }\n if (this.state.kind === \"serving\") {\n this.beginServerDrain();\n } else if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake !== undefined\n ) {\n // Drain coalesced with the ok-trailers (the server drains tunnels the\n // moment it shuts down, including ones it just registered): the same\n // gate race as forwarded streams — park the drain on the handshake\n // outcome instead of silently dropping it.\n void this.state.handshake.then(({ ok }) => {\n if (ok) this.beginServerDrain();\n });\n }\n // Before /_/start-tunnel was even opened: not a tunnel server\n // speaking the protocol — ack-and-ignore.\n }\n\n /**\n * Server-initiated drain: detach the still-serving session to the\n * DrainingRegistry and resolve `run()` so the slot dials a replacement. The\n * detached session keeps serving its in-flight invocations under the\n * registry's grace window — `run()` resolves now, the session closes later\n * (its `close` handler then runs `settle()` → `closed`, a no-op resolve).\n */\n private beginServerDrain(): void {\n if (this.state.kind !== \"serving\") return;\n const { session, openedAt, watchdog } = this.state;\n this.logWithIdentity(\n \"tunnel: server drain notification accepted — opening a replacement connection\"\n );\n watchdog.stop(); // the registry owns the session now; stop pinging it\n this.deregister();\n this.deps.draining.add(session, this.socket!, this.deps.opts.drainGraceMs);\n this.state = {\n kind: \"draining\",\n session,\n openedAt,\n trigger: \"server\",\n watchdog,\n };\n this.completion.resolve({ kind: \"drained\", uptimeMs: this.uptimeMs() });\n }\n\n /**\n * Client-initiated drain: the engine is shutting this process down. Refuse\n * new invocations and finish in-flight IN PLACE — no redial. The engine\n * waits for in-flight to drain, then tears the connection down.\n */\n beginClientDrain(): void {\n const s = this.state;\n switch (s.kind) {\n case \"serving\":\n this.state = {\n kind: \"draining\",\n session: s.session,\n openedAt: s.openedAt,\n trigger: \"client\",\n watchdog: s.watchdog,\n };\n this.sendClientDrainGoaway(s.session);\n return;\n case \"connecting\":\n case \"handshaking\":\n // No serving session yet — nothing in-flight to protect; abort.\n this.settle({ kind: \"retryable\", reason: \"shutting down\" });\n return;\n case \"draining\":\n case \"closed\":\n return; // already winding down\n }\n }\n\n async finishClientDrain(opts: { force: boolean }): Promise<void> {\n const s = this.state;\n if (s.kind !== \"draining\" || s.trigger !== \"client\") return;\n if (opts.force) {\n this.settle(\n { kind: \"served\", uptimeMs: this.uptimeMs() },\n \"client drain grace expired\"\n );\n return;\n }\n await this.closeSessionGracefully(s.session);\n }\n\n // ---- liveness ----\n\n private startWatchdog(session: http2.Http2Session): Watchdog {\n const watchdog = new Watchdog(session, this.deps.opts, () => {\n this.logWithIdentity(\"tunnel: pings missed — reconnecting\");\n this.settle(\n { kind: \"served\", uptimeMs: this.uptimeMs() },\n \"ping watchdog missed too many acknowledgements\"\n );\n });\n watchdog.start();\n return watchdog;\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Reconnect backoff policy.\n\n/**\n * Backoff resets only when a served connection stayed up at least this long\n * (mirrors the Rust client's 5s \"opened\" guard). Without it, a server that\n * authorizes the handshake but immediately drops the connection would be\n * redialed at the backoff floor forever — a full TLS+h2+auth round trip\n * every ~10ms.\n */\nexport const MIN_UPTIME_FOR_BACKOFF_RESET_MS = 5_000;\n\n/**\n * Jittered exponential backoff: each `next()` returns the current delay\n * with ±50% jitter and advances the schedule toward `maxMs`; `reset()`\n * returns to the floor. Jitter keeps multi-homed slots from redialing in\n * lockstep after a fleet-wide blip (thundering herd).\n */\nexport class Backoff {\n private currentMs: number;\n\n constructor(\n private readonly initialMs: number,\n private readonly factor: number,\n private readonly maxMs: number\n ) {\n this.currentMs = initialMs;\n }\n\n next(): number {\n const d = this.currentMs;\n this.currentMs = Math.min(this.currentMs * this.factor, this.maxMs);\n return d * (0.5 + Math.random());\n }\n\n reset(): void {\n this.currentMs = this.initialMs;\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Small abort-aware async utilities shared by the engine.\n\n/** Sleep that wakes early (resolving) when the signal aborts. */\nexport function delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const t = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n resolve();\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Race a promise against a signal: resolves `null` the moment the signal\n * aborts, otherwise passes the promise's result through (rejections\n * propagate). The abort listener is removed when the race settles, so\n * repeated calls against a long-lived signal don't accumulate listeners.\n */\nexport async function raceAbortable<T>(\n promise: Promise<T>,\n signal: AbortSignal\n): Promise<T | null> {\n if (signal.aborted) return null;\n let onAbort!: () => void;\n const aborted = new Promise<null>((resolve) => {\n onAbort = () => resolve(null);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n try {\n return await Promise.race([promise, aborted]);\n } finally {\n signal.removeEventListener(\"abort\", onAbort);\n }\n}\n\n// TODO replace with Promise.withResolvers\n/** A promise whose resolve/reject are exposed and fire at most once. */\nexport class Deferred<T> {\n private settled = false;\n readonly promise: Promise<T>;\n private resolveFn!: (value: T) => void;\n private rejectFn!: (err: Error) => void;\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this.resolveFn = resolve;\n this.rejectFn = reject;\n });\n }\n\n resolve(value: T): void {\n if (this.settled) return;\n this.settled = true;\n this.resolveFn(value);\n }\n\n reject(err: Error): void {\n if (this.settled) return;\n this.settled = true;\n this.rejectFn(err);\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The slot supervisor.\n// =============================================================================\n//\n// Multi-homing — one tunnel connection per resolved tunnel server (like the\n// Rust client; the slot set IS the resolved set, it is not configurable). The\n// supervisor resolves the server set, reconciles the slot map against it\n// (starting connections to servers that appear, tearing down ones that\n// vanish), and re-resolves every `resolveIntervalMs` for SRV discovery. Each\n// slot runs its own reconnect loop with fatal-vs-retryable classification.\n//\n// Invariants:\n// E1. A FATAL outcome (unauthorized / bad-tunnel-name / name mismatch) on\n// ANY slot stops the WHOLE tunnel — the credentials are shared, so every\n// other slot would hit the same wall. It aborts every slot and reports\n// via `hooks.onFatal`; the engine surfaces it on `error`/`ready`.\n// E2. Backoff resets only after a connection held for\n// MIN_UPTIME_FOR_BACKOFF_RESET_MS; a drain only skips the backoff sleep\n// under the same guard (drain-spam must compound).\n// E3. Teardown is prompt: `abortAll()` aborts in-flight dials via per-slot\n// signals and wakes the resolve loop out of any sleep; the (un-abortable)\n// DNS work is raced against the wake signal, never awaited.\n\nimport type { ResolvedOptions } from \"./options.js\";\nimport { resolveTargets, targetKey, type Target } from \"./targets.js\";\nimport { runConnection, type ConnectionDeps } from \"./connection.js\";\nimport { Backoff, MIN_UPTIME_FOR_BACKOFF_RESET_MS } from \"./backoff.js\";\nimport { delay, raceAbortable } from \"./util.js\";\n\n/** A running per-server connection loop. */\ninterface Slot {\n ctl: AbortController;\n done: Promise<void>;\n}\n\nfunction formatTargetList(keys: string[]): string {\n return keys.length === 0 ? \"<none>\" : keys.join(\", \");\n}\n\nexport interface SupervisorHooks {\n /** A slot hit a non-retryable failure; the whole tunnel must stop (E1). */\n onFatal: (err: Error) => void;\n}\n\nexport class Supervisor {\n private readonly slots = new Map<string, Slot>();\n /** Aborting this cascades to every slot (each slot chains its ctl to it). */\n private readonly stopSignal = new AbortController();\n /** Wakes the resolve loop out of a sleep / DNS race (E3). */\n private readonly wake = new AbortController();\n private stopping = false;\n private fatal: Error | undefined;\n private lastResolvedKeys: string[] | undefined;\n\n /** Resolves when the resolve loop has exited AND every slot has settled. */\n readonly done: Promise<void>;\n\n constructor(\n private readonly opts: ResolvedOptions,\n private readonly deps: ConnectionDeps,\n private readonly hooks: SupervisorHooks,\n private readonly log: (message: string) => void\n ) {\n this.stopSignal.signal.addEventListener(\"abort\", () => this.wake.abort(), {\n once: true,\n });\n this.done = this.supervise();\n }\n\n get fatalError(): Error | undefined {\n return this.fatal;\n }\n\n /**\n * Stop starting/resolving new connections; existing slots keep running so\n * their connections can finish draining in place (client-initiated drain).\n */\n stopResolving(): void {\n this.stopping = true;\n this.log(\"tunnel: supervisor stopping target resolution\");\n this.wake.abort();\n }\n\n /** Abort every slot and the resolve loop (engine teardown). */\n abortAll(): void {\n this.stopping = true;\n this.log(\"tunnel: supervisor aborting all connections\");\n this.stopSignal.abort();\n }\n\n private startSlot(key: string, target: Target): void {\n const ctl = new AbortController();\n // Chain to the global stop so abortAll() cascades; self-detaching.\n this.stopSignal.signal.addEventListener(\"abort\", () => ctl.abort(), {\n once: true,\n signal: ctl.signal,\n });\n const slot: Slot = { ctl, done: Promise.resolve() };\n slot.done = this.runSlot(target, ctl).finally(() => {\n // Guarded: this key may have vanished and re-appeared, in which case a\n // NEWER slot owns it — don't delete someone else's registration.\n if (this.slots.get(key) === slot) this.slots.delete(key);\n });\n this.slots.set(key, slot);\n }\n\n private async waitForStartupReady(): Promise<boolean> {\n if (this.opts.startupReady === undefined) return true;\n this.log(\n `tunnel: waiting for startup readiness gate (timeoutMs=${this.opts.startupReadyTimeoutMs})`\n );\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const ready = this.opts.startupReady();\n ready.catch(() => {}); // a late rejection after abort must not be unhandled\n const readyOrTimeout = Promise.race([\n ready,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n reject(\n new Error(\n `startup readiness gate timed out after ${this.opts.startupReadyTimeoutMs}ms`\n )\n );\n }, this.opts.startupReadyTimeoutMs);\n }),\n ]);\n readyOrTimeout.catch(() => {});\n const raced = await raceAbortable(readyOrTimeout, this.wake.signal);\n if (raced === null) return false;\n this.log(\"tunnel: startup readiness gate passed\");\n return true;\n } catch (err) {\n if (this.stopping || this.wake.signal.aborted) return false;\n const reason = err instanceof Error ? err.message : String(err);\n this.fatal = new Error(\n `tunnel: startup readiness gate failed: ${reason}`\n );\n this.log(\n `tunnel: FATAL — startup readiness gate failed: ${reason}; stopping all connections`\n );\n this.hooks.onFatal(this.fatal);\n this.stopSignal.abort();\n return false;\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n }\n\n /** The per-server loop: dial → serve → classify outcome → backoff → redial. */\n private async runSlot(target: Target, ctl: AbortController): Promise<void> {\n const backoff = new Backoff(\n this.opts.reconnectInitialMs,\n this.opts.reconnectFactor,\n this.opts.reconnectMaxMs\n );\n\n while (!this.stopping && !ctl.signal.aborted && this.fatal === undefined) {\n const outcome = await runConnection(target, ctl.signal, this.deps);\n if (this.stopping || ctl.signal.aborted) break;\n if (outcome.kind === \"fatal\") {\n // E1: shared credentials — stop everything.\n this.fatal = new Error(`tunnel: ${outcome.reason}`);\n this.log(`tunnel: FATAL — ${outcome.reason}; stopping all connections`);\n this.hooks.onFatal(this.fatal);\n this.stopSignal.abort();\n break;\n }\n if (outcome.kind === \"served\" || outcome.kind === \"drained\") {\n // E2: only a connection that actually held resets the backoff.\n const heldLongEnough =\n outcome.uptimeMs >= MIN_UPTIME_FOR_BACKOFF_RESET_MS;\n if (heldLongEnough) backoff.reset();\n if (outcome.kind === \"drained\" && heldLongEnough) {\n // A stable connection was asked to rotate and the server is holding\n // the old one open for us — replace it NOW.\n this.log(\"tunnel: draining — reconnecting immediately\");\n continue;\n }\n this.log(\n outcome.kind === \"drained\"\n ? \"tunnel: drained shortly after connecting — reconnecting with backoff\"\n : \"tunnel: connection ended — reconnecting\"\n );\n } else {\n this.log(`tunnel: ${outcome.reason} — reconnecting`);\n }\n await delay(backoff.next(), ctl.signal);\n }\n }\n\n /** Resolve the server set, reconcile slots, repeat. For SRV discovery the\n * set is re-resolved every resolveIntervalMs; an explicit set is fixed. */\n private async supervise(): Promise<void> {\n if (!(await this.waitForStartupReady())) return;\n while (!this.stopping && this.fatal === undefined) {\n let targets: Target[];\n try {\n // E3: race the (un-abortable) DNS work against the wake signal so\n // teardown/fatal don't block on a slow resolver — a late result is\n // discarded by the stopping/fatal check below.\n const resolution = resolveTargets({ ...this.opts, logger: this.log });\n resolution.catch(() => {}); // a late rejection must not be unhandled\n const raced = await raceAbortable(resolution, this.wake.signal);\n if (raced === null) break; // woken: stopping or fatal\n targets = raced;\n } catch (err) {\n // Keep whatever slots exist serving; retry the resolution later\n // (the Rust client does the same on SRV failures).\n this.log(\n `tunnel: target resolution failed: ${err instanceof Error ? err.message : String(err)} — retrying`\n );\n await delay(\n Math.min(5_000, this.opts.resolveIntervalMs),\n this.wake.signal\n );\n continue;\n }\n if (this.stopping || this.fatal !== undefined) break;\n\n const desired = new Map(targets.map((t) => [targetKey(t), t] as const));\n const desiredKeys = [...desired.keys()].sort();\n if (\n this.lastResolvedKeys === undefined ||\n desiredKeys.length !== this.lastResolvedKeys.length ||\n desiredKeys.some((key, i) => key !== this.lastResolvedKeys![i])\n ) {\n const source =\n this.opts.srvName === undefined\n ? \"configured tunnel targets\"\n : `SRV ${this.opts.srvName}`;\n this.log(\n `tunnel: target set from ${source}: ${formatTargetList(desiredKeys)}`\n );\n if (this.lastResolvedKeys !== undefined) {\n const previous = new Set(this.lastResolvedKeys);\n const current = new Set(desiredKeys);\n const added = desiredKeys.filter((key) => !previous.has(key));\n const removed = this.lastResolvedKeys.filter(\n (key) => !current.has(key)\n );\n if (added.length > 0) {\n this.log(\n `tunnel: discovered new tunnel target(s): ${formatTargetList(added)}`\n );\n }\n if (removed.length > 0) {\n this.log(\n `tunnel: tunnel target(s) disappeared: ${formatTargetList(removed)}`\n );\n }\n }\n this.lastResolvedKeys = desiredKeys;\n }\n for (const [key, target] of desired) {\n if (!this.slots.has(key)) {\n this.log(`tunnel: starting connection to ${key}`);\n this.startSlot(key, target);\n }\n }\n for (const [key, slot] of this.slots) {\n if (!desired.has(key)) {\n this.log(`tunnel: ${key} no longer resolves — tearing down`);\n slot.ctl.abort();\n }\n }\n\n if (this.opts.srvName === undefined) break; // explicit servers: fixed set\n await delay(this.opts.resolveIntervalMs, this.wake.signal);\n }\n // Slots still in the map are live; evicted ones have already settled. No\n // slot can start after the loop exits (stopping/fatal both gate startSlot).\n await Promise.all([...this.slots.values()].map((s) => s.done));\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The tunnel engine.\n// =============================================================================\n//\n// connectTunnel() serves a Restate SDK deployment over OUTBOUND connections to\n// Restate Cloud's tunnel servers — no inbound listener. The pieces:\n//\n// connection.ts — one dial → role-flip → handshake → serve cycle\n// supervisor.ts — the slot supervisor (one connection per resolved server)\n// handshake.ts — the /_/start-tunnel credentials/trailers exchange\n// forwarded.ts — the /<scheme>/<host>/<port> destination-prefix strip\n// targets.ts — server discovery (SRV per-IP expansion / explicit list)\n// draining.ts — server-drain handover ownership\n// backoff.ts — jittered exponential reconnect policy\n//\n// The engine has three kinds of state, kept deliberately separate:\n//\n// * Injected infrastructure — the registries (activeSockets/activeConnections/\n// draining/inflight) that ConnectionDeps hands to every connection. By\n// definition the per-connection layer reads these, so they are created once\n// and injected, not stored in a lifecycle phase.\n// * Observable output — connectionCount / lastInfo / fatalError, which the\n// handle exposes in every phase (including after close()), so they live in\n// one lifetime record rather than a phase.\n// * Lifecycle state — `EngineState`, a disjoint union whose live phases own\n// the supervisor + the event-loop anchor (and, while draining, the in-flight\n// drain promise). Each transition narrows the state and destructures what it\n// needs, so no function reaches for the live machinery in the wrong phase\n// (and `closed` provably has none).\n\nimport type * as net from \"node:net\";\nimport { createEndpointHandler } from \"@restatedev/restate-sdk\";\n\nimport type { ConnectTunnelOptions, TunnelConnection } from \"./types.js\";\nimport { resolveOptions } from \"./options.js\";\nimport type { HandshakeInfo } from \"./handshake.js\";\nimport {\n type ConnectionDeps,\n type ConnectionIdentity,\n type DrainableConnection,\n} from \"./connection.js\";\nimport { DrainingRegistry } from \"./draining.js\";\nimport { Supervisor } from \"./supervisor.js\";\nimport { Deferred } from \"./util.js\";\n\n/**\n * Counts forwarded invocations in flight and lets a graceful shutdown wait for\n * them to finish. Spans both actively-served and server-drained (detached)\n * sessions, since every dispatch increments and every stream close decrements\n * regardless of which session it belongs to.\n */\nclass InflightTracker {\n private count = 0;\n private notifyDrained: (() => void) | undefined;\n\n get inFlight(): number {\n return this.count;\n }\n\n started(): void {\n this.count++;\n }\n\n ended(): void {\n this.count = Math.max(0, this.count - 1);\n if (this.count === 0 && this.notifyDrained !== undefined) {\n const notify = this.notifyDrained;\n this.notifyDrained = undefined;\n notify();\n }\n }\n\n /** Resolve true once nothing is in flight, or false after `graceMs`.\n * Only one shutdown runs at a time, so a single waiter suffices. */\n whenDrained(graceMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n if (this.count === 0) {\n resolve(true);\n return;\n }\n const timer = setTimeout(() => {\n this.notifyDrained = undefined;\n resolve(false);\n }, graceMs);\n timer.unref();\n this.notifyDrained = () => {\n clearTimeout(timer);\n resolve(true);\n };\n });\n }\n}\n\n/** The engine's observable output — readable from the handle in every phase. */\ninterface Output {\n connectionCount: number;\n lastInfo: HandshakeInfo | undefined;\n fatalError: Error | undefined;\n}\n\n/** The live machinery, owned by the running/draining phases and gone in closed. */\ninterface Active {\n readonly supervisor: Supervisor;\n /** Anchors the event loop while live (a bare awaited promise won't keep Node\n * alive between a session closing and the next redial timer). */\n readonly keepAlive: NodeJS.Timeout;\n}\n\n/** The engine lifecycle as one disjoint state; live phases carry `Active`. */\ntype EngineState =\n | { readonly kind: \"running\"; readonly active: Active }\n | {\n readonly kind: \"draining\";\n readonly active: Active;\n readonly completed: Promise<void>;\n }\n | { readonly kind: \"closed\" };\n\ninterface SignalShutdownParticipant {\n shutdown(signal: NodeJS.Signals): Promise<void>;\n}\n\nconst signalShutdownRegistrations = new Map<\n NodeJS.Signals,\n Set<SignalShutdownParticipant>\n>();\nconst signalShutdownHandlers = new Map<NodeJS.Signals, () => void>();\nlet signalShutdownInProgress = false;\n\nasync function runGracefulShutdownSignal(\n signal: NodeJS.Signals\n): Promise<void> {\n if (signalShutdownInProgress) return;\n signalShutdownInProgress = true;\n const registrations = [...(signalShutdownRegistrations.get(signal) ?? [])];\n await Promise.allSettled(\n registrations.map((entry) => entry.shutdown(signal))\n );\n try {\n process.exit(0);\n } finally {\n // Tests stub process.exit(); real process.exit() does not return.\n signalShutdownInProgress = false;\n }\n}\n\nfunction registerGracefulShutdownSignals(\n signals: NodeJS.Signals[],\n participant: SignalShutdownParticipant\n): () => void {\n const unregisters = signals.map((signal) => {\n let registrations = signalShutdownRegistrations.get(signal);\n if (registrations === undefined) {\n registrations = new Set();\n signalShutdownRegistrations.set(signal, registrations);\n }\n registrations.add(participant);\n if (!signalShutdownHandlers.has(signal)) {\n const handler = () => void runGracefulShutdownSignal(signal);\n signalShutdownHandlers.set(signal, handler);\n process.once(signal, handler);\n }\n return () => {\n const current = signalShutdownRegistrations.get(signal);\n if (current === undefined) return;\n current.delete(participant);\n if (current.size > 0) return;\n const handler = signalShutdownHandlers.get(signal);\n if (handler !== undefined) process.removeListener(signal, handler);\n signalShutdownHandlers.delete(signal);\n signalShutdownRegistrations.delete(signal);\n };\n });\n return () => {\n for (const unregister of unregisters) unregister();\n };\n}\n\n/**\n * Connect this deployment to a Restate Cloud tunnel and serve `services`\n * over it. Returns immediately; connection management runs in the\n * background until `close()`/`shutdown()` (or the `signal`) stops it. See\n * {@link TunnelConnection.ready} to await the first successful handshake.\n */\nexport function connectTunnel(options: ConnectTunnelOptions): TunnelConnection {\n // Resolve options, eventually picking them up from env.\n const resolvedOptions = resolveOptions(options);\n\n // Built once, shared across connections and streams (it is stateless per\n // call). identityKeys delegates per-request JWT verification to the SDK —\n // it checks `aud` against the post-strip `req.url` pathname.\n const sdkHandler = createEndpointHandler({\n services: options.services,\n bidirectional: resolvedOptions.bidirectional,\n // TODO identityKeys and signingPublicKey are the very same field.\n // This can be aligned into the same field (and could be nice also for the regular SDK to read the key from env).\n identityKeys: [resolvedOptions.signingPublicKey],\n defaultServiceOptions: options.defaultServiceOptions,\n logger: options.logger,\n journalValueCodecProvider: options.journalValueCodecProvider,\n });\n\n // Logger used for debugging the tunnel\n const log = resolvedOptions.logger;\n const logWithWorker = (message: string) =>\n log(`${message} (worker_id=${resolvedOptions.tunnelWorkerId})`);\n let startupGatePassed = resolvedOptions.startupReady === undefined;\n const opts = {\n ...resolvedOptions,\n startupReady:\n resolvedOptions.startupReady === undefined\n ? undefined\n : async () => {\n await resolvedOptions.startupReady!();\n startupGatePassed = true;\n },\n };\n\n // Injected infrastructure: the per-connection layer reads these via deps.\n const activeSockets = new Set<net.Socket>();\n const activeConnections = new Set<DrainableConnection>();\n const draining = new DrainingRegistry();\n const inflight = new InflightTracker();\n\n // Observable output (valid in every phase, including after close).\n const output: Output = {\n connectionCount: 0,\n lastInfo: undefined,\n fatalError: undefined,\n };\n\n // Resolves on the first successful handshake; rejects on a fatal stop or if\n // the tunnel closes before connecting. The catch keeps a never-awaited\n // rejection from surfacing as unhandled.\n const ready = new Deferred<void>();\n void ready.promise.catch(() => {});\n\n // Assigned synchronously below once the live machinery exists; the deps\n // closures only read it at runtime, long after.\n let state: EngineState;\n\n // Opt-in process-signal registrations, removed on teardown so a closed\n // connection can never later intercept a signal and exit the host process.\n const signalUnregisters: Array<() => void> = [];\n\n const connectionDeps: ConnectionDeps = {\n opts,\n sdkHandler,\n draining,\n activeSockets,\n activeConnections,\n onEstablished: (info, identity: ConnectionIdentity) => {\n if (state.kind === \"closed\") return;\n const firstConnection = output.connectionCount === 0;\n output.connectionCount++;\n output.lastInfo = info;\n if (firstConnection) {\n log(\n `tunnel: service ready (name=${info.tunnelName}, proxy=${info.proxyUrl}, tunnel=${info.tunnelUrl}, worker_id=${identity.workerId}, connection_id=${identity.connectionId}, target=${identity.target})`\n );\n } else {\n log(\n `tunnel: additional connection ready (connections=${output.connectionCount}, name=${info.tunnelName}, worker_id=${identity.workerId}, connection_id=${identity.connectionId}, target=${identity.target})`\n );\n }\n ready.resolve();\n },\n isShuttingDown: () => state.kind === \"draining\",\n isStartupReady: () => startupGatePassed,\n inflightStarted: () => inflight.started(),\n inflightEnded: () => inflight.ended(),\n };\n\n const supervisor = new Supervisor(\n opts,\n connectionDeps,\n {\n onFatal: (err) => {\n output.fatalError = err; // E1 surfaces on ready/error\n ready.reject(err);\n },\n },\n logWithWorker\n );\n\n const keepAlive = setInterval(() => {}, 0x7fffffff);\n\n state = { kind: \"running\", active: { supervisor, keepAlive } };\n\n // ---- transitions ----\n\n /** Abrupt teardown; idempotent. Destructures the live machinery from the\n * state, so it can only run while running/draining. */\n const teardown = (): void => {\n if (state.kind === \"closed\") return;\n const { supervisor, keepAlive } = state.active;\n state = { kind: \"closed\" };\n for (const unregister of signalUnregisters) unregister();\n signalUnregisters.length = 0;\n supervisor.abortAll();\n for (const socket of activeSockets) socket.destroy();\n activeSockets.clear();\n draining.destroyAll();\n clearInterval(keepAlive);\n };\n\n // Resolves when the supervisor has fully wound down; then tear down (covers a\n // fatal that stopped us with no close()/shutdown() call) and settle `ready`.\n const done = supervisor.done.then(() => {\n teardown();\n ready.reject(\n output.fatalError ??\n new Error(\"tunnel: closed before the first handshake\")\n );\n });\n\n const drainGracefully = async (\n active: Active,\n graceMs: number\n ): Promise<void> => {\n const { supervisor } = active;\n // Stop dialing/resolving new connections (existing ones keep serving) and\n // move every live connection into a client-drain: serving ones refuse new\n // invocations and finish in-flight in place; not-yet-serving ones abort.\n // Snapshot — beginClientDrain may settle a connection, removing it.\n supervisor.stopResolving();\n for (const c of [...activeConnections]) c.beginClientDrain();\n logWithWorker(\n `tunnel: graceful shutdown — refusing new invocations, draining ${inflight.inFlight} in-flight`\n );\n const drained = await inflight.whenDrained(graceMs);\n // In-flight drained: ask h2 to close cleanly. Grace elapsed: force the\n // still-open sessions down, which tears down any stuck streams.\n await Promise.all(\n [...activeConnections].map((c) =>\n c.finishClientDrain({ force: !drained })\n )\n );\n // Now tear down the supervisor and any idle retry loops. For the graceful\n // case the live h2 sessions have already closed; for the forced case this\n // is idempotent cleanup.\n teardown();\n await done;\n };\n\n const close = async (): Promise<void> => {\n teardown();\n await done;\n };\n\n const shutdown = ({ graceMs }: { graceMs?: number } = {}): Promise<void> => {\n // Without the advertised capability the server ignores our drain sentinel,\n // so a graceful drain can't work (refused requests just keep getting\n // routed back) — fall back to an abrupt close, as documented.\n if (!resolvedOptions.supportsClientDrain) return close();\n // Coalesce: a drain already in progress, or already closed.\n if (state.kind === \"draining\") return state.completed;\n if (state.kind === \"closed\") return done;\n // Start the drain and record its promise on the state before the first\n // await, so every connection sees isShuttingDown() for the whole drain.\n // (drainGracefully runs synchronously up to inflight.whenDrained, so no new\n // invocation can interleave before `state` is set.)\n const { active } = state;\n const completed = drainGracefully(\n active,\n graceMs ?? resolvedOptions.drainGraceMs\n );\n state = { kind: \"draining\", active, completed };\n return completed;\n };\n\n // Install process-signal handlers (graceful shutdown is on by default; see\n // ConnectTunnelOptions.gracefulShutdown). Registered BEFORE the\n // already-aborted-signal handling below, so that path's synchronous close()\n // tears them down via teardown() rather than leaving a live handler on a\n // connection that is already closed.\n if (resolvedOptions.gracefulShutdown !== undefined) {\n const { signals, graceMs } = resolvedOptions.gracefulShutdown;\n signalUnregisters.push(\n registerGracefulShutdownSignals(signals, {\n async shutdown(signal) {\n logWithWorker(\n `tunnel: received ${signal} — shutting down gracefully`\n );\n await shutdown({ graceMs });\n },\n })\n );\n }\n\n if (options.signal?.aborted) {\n // An already-aborted signal means \"don't run\" — stop before dialing.\n void close();\n } else {\n options.signal?.addEventListener(\"abort\", () => void close(), {\n once: true,\n });\n }\n\n // ---- the public handle (reads the observable output) ----\n\n return {\n close,\n shutdown,\n get connectionCount() {\n return output.connectionCount;\n },\n get tunnelName() {\n return output.lastInfo?.tunnelName;\n },\n get proxyUrl() {\n return output.lastInfo?.proxyUrl;\n },\n get tunnelUrl() {\n return output.lastInfo?.tunnelUrl;\n },\n get deploymentUrl() {\n const lastInfo = output.lastInfo;\n if (lastInfo === undefined) return undefined;\n // Public clusters may advertise the proxy without a port; the proxy\n // listens on 9080. The destination (`/http/in-process/9080/`) is a\n // constant — an in-process tunnel is never dialed, so the server routes\n // purely by the tunnelName earlier in the path.\n try {\n const proxy = new URL(lastInfo.proxyUrl);\n if (proxy.port === \"\") proxy.port = \"9080\";\n const base = proxy.toString().replace(/\\/$/, \"\");\n return `${base}/http/in-process/9080/`;\n } catch {\n return `${lastInfo.proxyUrl}/http/in-process/9080/`;\n }\n },\n get error() {\n return output.fatalError;\n },\n ready: ready.promise,\n };\n}\n"],"mappings":";;;;;;;;;;AAmCA,SAAS,YAAY,KAAsB;AACzC,QAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;;;;;AAQzD,SAAgB,mBAAmB,SAAyB;AAC1D,KAAI,QAAQ,SAAS,MAAM,EAAE;EAC3B,IAAIA;AACJ,MAAI;AACF,SAAM,IAAI,IAAI,QAAQ;UAChB;AACN,SAAM,IAAI,MACR,qCAAqC,KAAK,UAAU,QAAQ,GAC7D;;AAEH,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAC/C,OAAM,IAAI,MACR,4CAA4C,KAAK,UAAU,IAAI,SAAS,CAAC,sBAC1E;AAEH,MAAI,IAAI,aAAa,OAAO,IAAI,WAAW,GACzC,OAAM,IAAI,MACR,4DAA4D,KAAK,UAAU,QAAQ,GACpF;EAEH,MAAMC,SACJ,IAAI,SAAS,KAAK,OAAO,IAAI,KAAK,GAAG,IAAI,aAAa,WAAW,MAAM;AACzE,SAAO;GACL,MAAM,IAAI;GACV;GACA,YAAY,IAAI;GAChB,WAAW,IAAI,aAAa;GAC7B;;CAGH,MAAM,MAAM,QAAQ,YAAY,IAAI;AACpC,KAAI,OAAO,KAAK,QAAQ,QAAQ,SAAS,EACvC,OAAM,IAAI,MACR,yCAAyC,KAAK,UAAU,QAAQ,CAAC,kCAClE;CAEH,MAAM,OAAO,QAAQ,MAAM,GAAG,IAAI;CAClC,MAAM,OAAO,OAAO,QAAQ,MAAM,MAAM,EAAE,CAAC;AAC3C,KAAI,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,OAAO,MAChD,OAAM,IAAI,MACR,iDAAiD,KAAK,UAAU,QAAQ,GACzE;AAEH,QAAO;EAAE;EAAM;EAAM,YAAY;EAAM;;;;;;;;;;;;;;;;;;;AAoBzC,eAAsB,eAAe,MAIf;CACpB,MAAMC,MAAwB,KAAK,iBAAiB;AACpD,KAAI,KAAK,kBAAkB,QAAW;EACpC,MAAMC,YAAU,KAAK,cAAc,IAAI,mBAAmB;AAC1D,MAAIA,UAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,iCAAiC;AAEnD,MACE,8CAA8CA,UAAQ,IAAI,UAAU,CAAC,KAAK,KAAK,GAChF;AACD,SAAOA;;CAET,MAAM,UAAU,KAAK;AACrB,KAAI,6CAA6C,UAAU;CAC3D,MAAM,UAAU,MAAM,IAAI,SAAS,WAAW,QAAQ;AACtD,SAAQ,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO;AACtE,KACE,eAAe,QAAQ,YAAY,QAAQ,OAAO,cAChD,QAAQ,KAAK,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI,WAE3D;CAQD,MAAM,UAAU,MAAM,QAAQ,WAC5B,QAAQ,KAAK,MAAM,IAAI,SAAS,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC,CAAC,CAC/D;CACD,MAAMC,UAAoB,EAAE;CAC5B,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,IAAI,QAAQ;EAClB,MAAM,SAAS,QAAQ;AACvB,MAAI,OAAO,WAAW,YAAY;GAChC,MAAM,OAAQ,OAAO,QAA8C;AACnE,OAAI,SAAS,eAAe,SAAS,WAAW;AAC9C,QAAI,sBAAsB,EAAE,KAAK,GAAG,EAAE,KAAK,mBAAmB,KAAK,GAAG;AACtE;;AAIF,OACE,yCAAyC,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,YAAY,OAAO,OAAO,GAChG;AACD,SAAM,OAAO;;AAEf,OAAK,MAAM,KAAK,OAAO,OAAO;GAC5B,MAAM,MAAM,GAAG,EAAE,QAAQ,GAAG,EAAE;AAC9B,OAAI,KAAK,IAAI,IAAI,CAAE;AACnB,QAAK,IAAI,IAAI;AACb,WAAQ,KAAK;IAAE,MAAM,EAAE;IAAS,MAAM,EAAE;IAAM,YAAY;IAAS,CAAC;;;AAGxE,KACE,eAAe,QAAQ,eAAe,QAAQ,OAAO,cACnD,QAAQ,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI,WAExC;AACD,QAAO;;;AAIT,SAAgB,UAAU,GAAmB;AAC3C,QAAO,GAAG,EAAE,KAAK,GAAG,EAAE;;;;;ACxJxB,MAAa,kBAAkB;AAC/B,MAAa,qBAAqB;AAClC,MAAa,mBAAmB;AAChC,MAAa,yBAAyB;AACtC,MAAa,sBAAsB;AACnC,MAAa,uBAAuB;;AA2CpC,SAAS,QAAQ,MAAkC;CACjD,MAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAO,UAAU,UAAa,UAAU,KAAK,SAAY;;;AAI3D,SAAS,kBACP,OACA,MACA,SACQ;CACR,MAAM,WACJ,UAAU,UAAa,UAAU,KAAK,QAAQ,QAAQ,QAAQ;AAChE,KAAI,aAAa,OACf,OAAM,IAAI,MACR,WAAW,KAAK,uCAAuC,QAAQ,GAChE;AAEH,QAAO;;;;;;;AAQT,SAAS,kBAAkB,OAAe,MAAsB;AAC9D,KAAI,CAAC,iBAAiB,KAAK,MAAM,CAC/B,OAAM,IAAI,MACR,WAAW,KAAK,yFACjB;AAEH,QAAO;;AAGT,SAAS,iBAAiB,QAA0C;AAClE,KAAI,WAAW,UAAa,WAAW,IAAI;AACzC,oBAAkB,QAAQ,YAAY;AACtC,eAAa;;CAEf,MAAM,YAAY,QAAQ,oBAAoB;AAC9C,KAAI,cAAc,OAChB,OAAM,IAAI,MACR,yDAAyD,oBAAoB,GAC9E;CAEH,MAAM,kBAAkB;EAItB,MAAM,OAAO,GAAG,SAAS,UAAU;AACnC,MAAI,CAAC,KAAK,QAAQ,CAChB,OAAM,IAAI,MACR,2BAA2B,UAAU,wBACtC;AAEH,MAAI,KAAK,OAAO,KAAK,KACnB,OAAM,IAAI,MACR,2BAA2B,UAAU,qCAAqC,KAAK,KAAK,SACrF;EAGH,MAAM,QAAQ,GAAG,aAAa,WAAW,OAAO,CAAC,MAAM;AACvD,MAAI,UAAU,GACZ,OAAM,IAAI,MAAM,2BAA2B,UAAU,WAAW;AAElE,SAAO,kBAAkB,OAAO,mBAAmB,YAAY;;AAIjE,YAAW;AACX,QAAO;;AAGT,SAAS,+BAA+B,OAAuB;CAC7D,MAAM,YAAY,MACf,QAAQ,sBAAsB,IAAI,CAClC,QAAQ,YAAY,GAAG;AAC1B,SAAQ,cAAc,KAAK,WAAW,WAAW,MAAM,GAAG,GAAG;;AAG/D,SAAS,4BAAoC;CAC3C,MAAM,OAAO,QAAQ,WAAW,IAAI,GAAG,UAAU,IAAI;CACrD,MAAM,SAAS,YAAY,EAAE,CAAC,SAAS,MAAM;AAC7C,QAAO,GAAG,+BAA+B,KAAK,CAAC,GAAG;;AAKpD,MAAM,2BAA2B,2BAA2B;AAE5D,SAAS,sBAAsB,QAAoC;AAKjE,QAAO,mBAHL,WAAW,UAAa,WAAW,KAC/B,SACA,QAAQ,qBAAqB,KACD,0BAA0B,iBAAiB;;AAG/E,SAAS,oBACP,QACmC;AACnC,KAAI,WAAW,OAAW,QAAO;AACjC,KAAI,OAAO,WAAW,WACpB,QAAO,YAAY;AACjB,QAAM,QAAQ;;CAGlB,MAAM,QAAQ,QAAQ,QAAQ,OAAO;AACrC,OAAM,YAAY,GAAG;AACrB,QAAO,YAAY;AACjB,QAAM;;;AAIV,SAAS,SACP,OACA,UACA,MACQ;AACR,KAAI,UAAU,OAAW,QAAO;AAChC,KAAI,CAAC,OAAO,SAAS,MAAM,IAAI,SAAS,EACtC,OAAM,IAAI,MAAM,WAAW,KAAK,4BAA4B;AAE9D,QAAO;;;;;;;;;AAUT,SAAgB,eAAe,SAAgD;CAC7E,MAAM,SACJ,QAAQ,qBAAqB,UAAa,QAAQ,qBAAqB;CACzE,MAAM,aACJ,QAAQ,kBAAkB,UAAa,QAAQ,cAAc,SAAS;CAKxE,IAAI,SAAS,QAAQ;AACrB,MACG,WAAW,UAAa,WAAW,OACpC,CAAC,UACD,QAAQ,kBAAkB,OAE1B,UAAS,QAAQ,iBAAiB;CAEpC,MAAM,YAAY,WAAW,UAAa,WAAW;CACrD,MAAM,iBACJ,OAAO,UAAU,GAAG,OAAO,OAAO,GAAG,OAAO,WAAW;AACzD,KAAI,mBAAmB,EACrB,OAAM,IAAI,MACR,wFAAwF,iBAAiB,GAC1G;AAEH,KAAI,iBAAiB,EACnB,OAAM,IAAI,MACR,iFACD;AAKH,KAAI,aAAa,CAAC,8BAA8B,KAAK,OAAQ,CAC3D,OAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,OAAO,GAAG;AAErE,KAAI,UAAU,CAAC,oBAAoB,KAAK,QAAQ,iBAAkB,CAChE,OAAM,IAAI,MACR,oCAAoC,KAAK,UAAU,QAAQ,iBAAiB,GAC7E;AAMH,KAAI,WACF,MAAK,MAAM,WAAW,QAAQ,cAAgB,oBAAmB,QAAQ;CAG3E,MAAM,gBAAgB,kBACpB,QAAQ,eACR,iBACA,mBACD;AACD,KAAI,CAAC,uBAAuB,KAAK,cAAc,CAC7C,OAAM,IAAI,MACR,wFACD;CAEH,MAAM,YAAY,iBAAiB,QAAQ,UAAU;CACrD,MAAM,mBAAmB,kBACvB,QAAQ,kBACR,oBACA,uBACD;AACD,KAAI,CAAC,iBAAiB,WAAW,eAAe,CAC9C,OAAM,IAAI,MACR,mFACD;CAEH,MAAM,aAAa,kBACjB,QAAQ,YACR,cACA,gBACD;AACD,KAAI,CAAC,oBAAoB,KAAK,WAAW,CACvC,OAAM,IAAI,MACR,8BAA8B,KAAK,UAAU,WAAW,CAAC,yCAC1D;CAEH,MAAM,iBAAiB,sBAAsB,QAAQ,eAAe;CACpE,MAAM,iBAAiB,SACrB,QAAQ,gBACR,MACA,iBACD;CACD,MAAM,gBAAgB,SACpB,QAAQ,eACR,KACA,gBACD;CACD,MAAM,eAAe,SAAS,QAAQ,cAAc,MAAS,eAAe;AAE5E,QAAO;EACL,SAAS,YACL,iBAAiB,OAAQ,GACzB,SACE,QAAQ,mBACR;EACN,eAAe,aAAa,QAAQ,gBAAgB;EACpD;EACA;EACA;EACA;EACA;EACA,eAAe,QAAQ,iBAAiB;EACxC,cAAc,oBAAoB,QAAQ,aAAa;EACvD,uBAAuB,SACrB,QAAQ,uBACR,MACA,wBACD;EACD,mBAAmB,SACjB,QAAQ,mBACR,KACA,oBACD;EACD,eAAe,QAAQ,iBAAiB;EACxC;EACA,qBAAqB,QAAQ,uBAAuB;EACpD,kBAAkB,wBAChB,QAAQ,kBACR,aACD;EACD,kBAAkB,SAChB,QAAQ,kBACR,KACA,mBACD;EACD,oBAAoB,SAClB,QAAQ,oBACR,KACA,qBACD;EACD,oBAAoB,SAClB,QAAQ,oBACR,IACA,qBACD;EACD,gBAAgB,SAAS,QAAQ,gBAAgB,MAAS,iBAAiB;EAC3E,iBAAiB,SAAS,QAAQ,iBAAiB,GAAG,kBAAkB;EACxE;EACA;EACA,eAAe,SAAS,QAAQ,eAAe,GAAG,gBAAgB;EAClE,sBAAsB,SACpB,QAAQ,sBACR,MACA,uBACD;EACD,sBAAsB,SACpB,QAAQ,sBACR,KAAK,OAAO,MACZ,uBACD;EACD,kBAAkB,SAChB,QAAQ,kBACR,KACA,mBACD;EACD,KAAK,QAAQ,OAAO;EACpB,QAAQ,QAAQ,iCAAiC;EAClD;;;AAIH,SAAS,wBACP,QAIA,cAC4D;AAE5D,KAAI,WAAW,MAAO,QAAO;AAC7B,KAAI,WAAW,UAAa,WAAW,KACrC,QAAO;EAAE,SAAS,CAAC,UAAU;EAAE,SAAS;EAAc;CAExD,MAAM,UAAU,OAAO,WAAW,CAAC,UAAU;AAC7C,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,qDAAqD;AAEvE,QAAO;EACL;EACA,SAAS,SAAS,OAAO,SAAS,cAAc,2BAA2B;EAC5E;;;;;;;;;;;;;AAcH,SAAgB,uBACd,WACA,YACmC;AACnC,KAAI,cAAc,MAAO,QAAO;CAChC,MAAMC,OAA8B;EAAE;EAAY,eAAe,CAAC,KAAK;EAAE;AACzE,KAAI,cAAc,KAAM,QAAO;AAC/B,QAAO;EACL,GAAG;EACH,GAAI,UAAU,eAAe,UAAa,EACxC,YAAY,UAAU,YACvB;EACD,GAAI,UAAU,OAAO,UAAa,EAAE,IAAI,UAAU,IAAI;EACtD,GAAI,UAAU,SAAS,UAAa,EAAE,MAAM,UAAU,MAAM;EAC5D,GAAI,UAAU,QAAQ,UAAa,EAAE,KAAK,UAAU,KAAK;EACzD,GAAI,UAAU,uBAAuB,UAAa,EAChD,oBAAoB,UAAU,oBAC/B;EACF;;;AAIH,SAAgB,iBAAiB,QAAwB;AACvD,QAAO,UAAU,OAAO;;;;;AC7Y1B,IAAa,mBAAb,MAA8B;CAC5B,AAAiB,0BAAU,IAAI,KAAyB;;;;;;CAOxD,IAAI,SAA6B,QAAoB,SAAuB;EAC1E,MAAMC,QAA4B;GAChC;GACA;GACA,OAAO,iBAAiB;AACtB,SAAK,QAAQ,OAAO,MAAM;AAC1B,YAAQ,SAAS;AACjB,WAAO,SAAS;MACf,QAAQ;GACZ;AAGD,QAAM,MAAM,OAAO;AACnB,OAAK,QAAQ,IAAI,MAAM;AACvB,UAAQ,GAAG,eAAe;AACxB,gBAAa,MAAM,MAAM;AACzB,QAAK,QAAQ,OAAO,MAAM;AAC1B,UAAO,SAAS;IAChB;;;CAIJ,aAAmB;AACjB,OAAK,MAAM,SAAS,KAAK,SAAS;AAChC,gBAAa,MAAM,MAAM;AACzB,SAAM,QAAQ,SAAS;AACvB,SAAM,OAAO,SAAS;;AAExB,OAAK,QAAQ,OAAO;;;;;;ACYxB,MAAa,oBAAoB;;AAGjC,MAAa,uBAAuB;;;;;AAMpC,SAAgB,iBACd,KACA,KACA,OACA,YAAoB,sBACO;AAC3B,QAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,UAAU;EACd,MAAM,UAAU,YAA8B;AAC5C,OAAI,QAAS;AACb,aAAU;AACV,gBAAa,SAAS;AACtB,WAAQ,QAAQ;;EAGlB,MAAM,WAAW,iBAAiB;AAChC,UAAO;IACL,MAAM;IACN,QAAQ,0CAA0C,UAAU;IAC7D,CAAC;AACF,OAAI,OAAO,SAAS;KACnB,UAAU;AACb,WAAS,OAAO;EAEhB,MAAM,cAAc,aAAwC;GAC1D,MAAM,SAAS,SAAS;AACxB,OAAI,WAAW,MAAM;AACnB,QAAI,WAAW,kBAAkB,WAAW,kBAC1C,QAAO;KAAE,MAAM;KAAS,QAAQ,kBAAkB,OAAO,OAAO;KAAI,CAAC;QAErE,QAAO;KACL,MAAM;KACN,QAAQ,kBAAkB,OAAO,UAAU,YAAY;KACxD,CAAC;AAEJ;;GAEF,MAAM,aAAa,SAAS;GAC5B,MAAM,WAAW,SAAS;GAC1B,MAAM,YAAY,SAAS;AAC3B,OACE,OAAO,eAAe,YACtB,OAAO,aAAa,YACpB,OAAO,cAAc,UACrB;AACA,WAAO;KACL,MAAM;KACN,QAAQ;KACT,CAAC;AACF;;AAEF,OAAI,eAAe,MAAM,YAAY;AAGnC,WAAO;KACL,MAAM;KACN,QAAQ,mCAAmC,KAAK,UAAU,MAAM,WAAW,CAAC,QAAQ,KAAK,UAAU,WAAW;KAC/G,CAAC;AACF;;AAEF,UAAO;IAAE,MAAM;IAAM,MAAM;KAAE;KAAY;KAAU;KAAW;IAAE,CAAC;;AAKnE,MAAI,OAAO,GAAG,YAAY,WAAW;AACrC,MAAI,GAAG,aAAa;AAClB,OAAI,CAAC,WAAW,IAAI,YAAY,OAAO,KAAK,IAAI,SAAS,CAAC,SAAS,EACjE,YAAW,IAAI,SAAS;IAE1B;AACF,MAAI,GAAG,UAAU,QAAQ;AACvB,UAAO;IACL,MAAM;IACN,QAAQ,2BAA2B,IAAI;IACxC,CAAC;IACF;AACF,MAAI,OAAO,GAAG,eAAe;AAC3B,UAAO;IACL,MAAM;IACN,QAAQ;IACT,CAAC;IACF;AAEF,MAAI,QAAQ;AAGZ,MAAI,UAAU,KAAK;GACjB,eAAe,UAAU,MAAM;GAC/B,kBAAkB,MAAM;GACxB,eAAe,MAAM;GACrB,oBAAoB,MAAM;GAC1B,wBAAwB,MAAM;GAC9B,GAAI,MAAM,iBAAiB,EAAE,kBAAkB,QAAQ;GACvD,GAAI,MAAM,uBAAuB,EAAE,yBAAyB,QAAQ;GACrE,CAAC;AACF,MAAI,KAAK;GACT;;;;;;;;;;;;;;;;;;;;;;;;;ACxJJ,SAAgB,cAAc,QAA+B;CAC3D,MAAM,OAAO,OAAO,QAAQ,IAAI;CAChC,MAAM,OAAO,SAAS,KAAK,SAAS,OAAO,MAAM,GAAG,KAAK;CACzD,MAAM,QAAQ,SAAS,KAAK,KAAK,OAAO,MAAM,KAAK;CACnD,MAAM,MAAM,KAAK,MAAM,IAAI;AAK3B,KACE,IAAI,SAAS,KACb,IAAI,OAAO,MACX,IAAI,OAAO,MACX,CAAC,QAAQ,KAAK,IAAI,GAAI,CAEtB,QAAO;CAET,MAAM,OAAO,MAAM,IAAI,MAAM,EAAE,CAAC,KAAK,IAAI;AACzC,QAAO,QAAQ,OAAO,QAAQ;;;;;ACyBhC,MAAM,wCAAwC;AAC9C,MAAM,yBAAyB;AAC/B,MAAM,mBAAmB;AAEzB,SAAS,aAAa,OAAe,QAAwB;CAC3D,IAAI,MAAM;AACV,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,QAAM,iBAAiB,OAAO,OAAO,QAAQ,IAAI,CAAC,GAAG;AACrD,YAAU;;AAEZ,QAAO;;AAGT,SAAS,wBAAgC;CACvC,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,YAAY,GAAG,CAChC,UAAU,UAAU,KAAM,OAAO,KAAK;AAExC,QAAO,GAAG,aAAa,OAAO,KAAK,KAAK,CAAC,EAAE,GAAG,GAAG,aAAa,QAAQ,GAAG;;AAG3E,SAAS,eAAe,UAAkB,cAA8B;AACtE,QAAO,aAAa,SAAS,iBAAiB;;AAGhD,SAAS,YAAY,QAAwB;AAC3C,QAAO,GAAG,OAAO,KAAK,GAAG,OAAO;;AAGlC,SAAS,wBAAwB,SAAoC;AACnE,SAAQ,QAAQ,MAAhB;EACE,KAAK,SACH,QAAO,mBAAmB,QAAQ;EACpC,KAAK,UACH,QAAO,oBAAoB,QAAQ;EACrC,KAAK,YACH,QAAO,oBAAoB,QAAQ;EACrC,KAAK,QACH,QAAO,gBAAgB,QAAQ;;;AAIrC,SAAS,eAAe,UAAkC;CACxD,MAAM,UAAU,OAAO,QAAQ,SAAS,CAAC,QACtC,GAAG,WAAW,UAAU,OAC1B;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAO,IAAI,QACR,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,MAAM,GAAG,CAChD,KAAK,KAAK,CAAC;;AAGhB,SAAS,iBAAiB,KAAiC;AACzD,KAAI,QAAQ,OAAW,QAAO;CAC9B,MAAM,aAAa,IAAI,QAAQ,IAAI;AACnC,QAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,WAAW;;AAG3D,SAAS,iBAAiB,KAAsC;AAC9D,KAAI;AACF,MAAI,CAAC,IAAI,YAAa,KAAI,UAAU,IAAI;AACxC,MAAI,CAAC,IAAI,cAAe,KAAI,IAAI,4BAA4B;SACtD;;;;AAsGV,SAAS,gBAAgB,KAA8C;CACrE,MAAM,WAAW,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC;AAC3C,KAAI,IAAI,WAAW,SAAS,YAAY,kBACtC,QAAO,EAAE,MAAM,gBAAgB;AAEjC,KAAI,YAAY,YAAa,QAAO,EAAE,MAAM,UAAU;AACtD,KAAI,YAAY,kBAAmB,QAAO,EAAE,MAAM,SAAS;AAC3D,QAAO,EAAE,MAAM,aAAa;;;AAI9B,IAAM,aAAN,MAAiB;CACf,AAAQ,OAAO;CACf,AAAQ;CACR,AAAS,UAAsC,IAAI,SAAS,YAAY;AACtE,OAAK,YAAY;GACjB;CAEF,IAAI,UAAmB;AACrB,SAAO,KAAK;;;CAId,QAAQ,SAAqC;AAC3C,MAAI,KAAK,KAAM,QAAO;AACtB,OAAK,OAAO;AACZ,OAAK,UAAU,QAAQ;AACvB,SAAO;;;;;;;;AASX,IAAM,WAAN,MAAe;CACb,AAAQ;CACR,AAAQ,SAAS;CAEjB,YACE,AAAiBC,SACjB,AAAiBC,MACjB,AAAiBC,QACjB;EAHiB;EACA;EACA;;CAGnB,QAAc;AACZ,OAAK,WAAW,kBAAkB,KAAK,MAAM,EAAE,KAAK,KAAK,eAAe;AACxE,OAAK,SAAS,OAAO;;CAGvB,OAAa;AACX,MAAI,KAAK,aAAa,OAAW,eAAc,KAAK,SAAS;;CAG/D,AAAQ,OAAa;AACnB,MAAI,KAAK,QAAQ,UAAW;EAC5B,IAAI,QAAQ;AACZ,MAAI;AACF,QAAK,QAAQ,MAAM,QAAQ;AACzB,QAAI,QAAQ,MAAM;AAChB,aAAQ;AACR,UAAK,SAAS;;KAEhB;UACI;AACN;;AAOF,EALU,iBAAiB;AACzB,OAAI,SAAS,KAAK,QAAQ,UAAW;AACrC,QAAK;AACL,OAAI,KAAK,UAAU,KAAK,KAAK,cAAe,MAAK,QAAQ;KACxD,KAAK,KAAK,cAAc,CACzB,OAAO;;;;;;;;;AAeb,SAAS,KACP,QACA,MACA,WACA,QACA,cACqB;CACrB,MAAM,MAAM,KAAK,KAAK;CACtB,MAAM,WAAW,eAAe,KAAK,KAAK,gBAAgB,aAAa;CACvE,MAAM,aAAa,YACf,SACA,uBAAuB,KAAK,KAAK,KAAK,OAAO,WAAW;CAC5D,MAAM,SAAS,YACX,IAAI,QAAQ;EAAE,MAAM,OAAO;EAAM,MAAM,OAAO;EAAM,CAAC,GACrD,IAAI,QAAQ;EAAE,MAAM,OAAO;EAAM,MAAM,OAAO;EAAM,GAAG;EAAY,CAAC;AAExE,QAAO,IAAI,SAAqB,YAAY;EAC1C,IAAI,OAAO;EACX,MAAM,QAAQ,YAAY,OAAO;EACjC,MAAM,WAAW,QAAe,KAAK,iBAAiB,IAAI,UAAU;EACpE,MAAM,gBAAgB,KAAK,gBAAgB;EAC3C,MAAM,QAAQ,iBACN,KAAK,yBAAyB,KAAK,KAAK,iBAAiB,IAAI,EACnE,KAAK,KAAK,iBACX;AACD,QAAM,OAAO;EAEb,MAAM,gBAAgB;AACpB,gBAAa,MAAM;AACnB,UAAO,oBAAoB,SAAS,QAAQ;AAC5C,UAAO,eAAe,SAAS,QAAQ;;EAEzC,SAAS,KAAK,QAAgB;AAC5B,OAAI,KAAM;AACV,UAAO;AACP,YAAS;AACT,OAAI,gCAAgC,MAAM,IAAI,OAAO,IAAI,SAAS,GAAG;AACrE,UAAO,SAAS;AAChB,WAAQ;IAAE,IAAI;IAAO,SAAS;KAAE,MAAM;KAAa;KAAQ;IAAE,CAAC;;AAGhE,SAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AACzD,SAAO,GAAG,SAAS,QAAQ;AAC3B,SAAO,KAAK,YAAY,YAAY,uBAAuB;AACzD,OAAI,KAAM;AACV,UAAO,WAAW,KAAK;GACvB,MAAM,OAAO,YACT,cACA,YAAY,KAAK,UAAW,OAAyB,aAAa;AAItE,OAAI,CAAC,aAAc,OAAyB,iBAAiB,MAAM;AACjE,SACE,iHACD;AACD;;AAEF,UAAO;AACP,YAAS;AACT,OAAI,+BAA+B,MAAM,IAAI,KAAK,IAAI,SAAS,GAAG;AAClE,WAAQ;IAAE,IAAI;IAAM;IAAQ,CAAC;IAC7B;GACF;;;;;;AAOJ,SAAgB,cACd,QACA,YACA,MAC4B;CAI5B,IAAIC;AACJ,KAAI;AACF,cAAY,KAAK,KAAK,WAAW;UAC1B,KAAK;AACZ,SAAO,QAAQ,QAAQ;GACrB,MAAM;GACN,QAAQ,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACpF,CAAC;;AAEJ,QAAO,IAAI,kBAAkB,QAAQ,YAAY,MAAM,UAAU,CAAC,KAAK;;AAGzE,IAAM,oBAAN,MAAuD;CACrD,AAAQ,QAAsB,EAAE,MAAM,cAAc;CACpD,AAAiB,aAAa,IAAI,YAAY;;;CAG9C,AAAQ;CAER,AAAiB;CACjB,AAAiB;CACjB,AAAiB,eAAe,uBAAuB;CAEvD,YACE,AAAiBC,QACjB,AAAiBC,YACjB,AAAiBC,MACjB,AAAiBH,WACjB;EAJiB;EACA;EACA;EACA;AAEjB,OAAK,MAAM,KAAK,KAAK;AACrB,OAAK,YAAY,OAAO,aAAa,KAAK,KAAK,QAAQ;;CAGzD,IAAY,WAA+B;AACzC,SAAO;GACL,UAAU,KAAK,KAAK,KAAK;GACzB,cAAc,KAAK;GACnB,QAAQ,YAAY,KAAK,OAAO;GACjC;;CAGH,AAAQ,cAAsB;AAC5B,SAAO,eAAe,KAAK,KAAK,KAAK,gBAAgB,KAAK,aAAa;;CAGzE,AAAQ,gBAAgB,SAAuB;AAC7C,OAAK,IAAI,GAAG,QAAQ,IAAI,KAAK,aAAa,CAAC,GAAG;;CAGhD,MAAkC;AAChC,OAAK,KAAK,kBAAkB,IAAI,KAAK;AACrC,EAAK,KAAK,OAAO;AACjB,SAAO,KAAK,WAAW;;;;CAKzB,MAAc,QAAuB;EACnC,MAAM,SAAS,MAAM,KACnB,KAAK,QACL,KAAK,MACL,KAAK,WACL,KAAK,YACL,KAAK,aACN;AAED,MAAI,KAAK,WAAW,SAAS;AAC3B,OAAI,OAAO,GAAI,QAAO,OAAO,SAAS;AACtC,QAAK,KAAK,kBAAkB,OAAO,KAAK;AACxC;;AAEF,MAAI,CAAC,OAAO,IAAI;AACd,QAAK,OAAO,OAAO,QAAQ;AAC3B;;AAEF,OAAK,SAAS,OAAO;AACrB,OAAK,KAAK,cAAc,IAAI,OAAO,OAAO;AAC1C,OAAK,WAAW,iBAAiB,SAAS,KAAK,SAAS,EAAE,MAAM,MAAM,CAAC;AACvE,SAAO,OAAO,GAAG,UAAU,QACzB,KAAK,OACH,KAAK,WAAW,iBAAiB,IAAI,UAAU,EAC/C,iBAAiB,IAAI,UACtB,CACF;AACD,SAAO,OAAO,GAAG,eACf,KAAK,OACH,KAAK,WAAW,+CAA+C,EAC/D,gBACD,CACF;AACD,OAAK,UAAU,OAAO,OAAO;;CAK/B,IAAY,SAAkB;AAC5B,SAAO,KAAK,MAAM,SAAS;;CAG7B,AAAQ,UAA0C;EAChD,MAAM,IAAI,KAAK;AACf,SAAO,EAAE,SAAS,iBAChB,EAAE,SAAS,aACX,EAAE,SAAS,aACT,EAAE,UACF;;CAGN,IAAY,WAA+B;EACzC,MAAM,IAAI,KAAK;AACf,SAAO,EAAE,SAAS,aAAa,EAAE,SAAS,aACtC,EAAE,WACF;;CAGN,AAAQ,WAAmB;AACzB,SAAO,KAAK,aAAa,SAAY,IAAI,KAAK,KAAK,GAAG,KAAK;;;CAI7D,AAAQ,WAAW,QAAmC;AACpD,SAAO,KAAK,aAAa,SACrB;GAAE,MAAM;GAAU,UAAU,KAAK,UAAU;GAAE,GAC7C;GAAE,MAAM;GAAa;GAAQ;;CAKnC,AAAiB,gBACf,KAAK,OAAO;EAAE,MAAM;EAAa,QAAQ;EAAiB,CAAC;;CAG7D,AAAQ,qBAA2B;EACjC,MAAM,IAAI,KAAK;AACf,MAAI,EAAE,SAAS,cAAe,cAAa,EAAE,kBAAkB;WACtD,EAAE,SAAS,aAAa,EAAE,SAAS,WAAY,GAAE,SAAS,MAAM;;;CAI3E,AAAQ,aAAmB;AACzB,OAAK,WAAW,oBAAoB,SAAS,KAAK,QAAQ;AAC1D,OAAK,KAAK,kBAAkB,OAAO,KAAK;AACxC,MAAI,KAAK,WAAW,OAAW,MAAK,KAAK,cAAc,OAAO,KAAK,OAAO;;;;;;;;CAS5E,AAAQ,OAAO,SAA4B,QAAuB;AAChE,MAAI,KAAK,OAAQ;EACjB,MAAM,QAAQ,KAAK,MAAM;EACzB,MAAM,UAAU,KAAK,SAAS;AAC9B,OAAK,oBAAoB;AACzB,OAAK,YAAY;AACjB,OAAK,QAAQ,EAAE,MAAM,UAAU;AAC/B,WAAS,SAAS;AAClB,OAAK,QAAQ,SAAS;EACtB,MAAM,eAAe,KAAK,WAAW,QAAQ,QAAQ;AACrD,OAAK,gBACH,yBAAyB,YAAY,KAAK,OAAO,CAAC,iBAAiB,MAAM,YAAY,wBAAwB,QAAQ,GACnH,WAAW,SAAY,KAAK,YAAY,WACvC,eAAe,KAAK,qBAAqB,GAC7C;;CAGH,AAAQ,uBAAuB,SAA4C;AACzE,SAAO,IAAI,SAAS,YAAY;AAC9B,OAAI,QAAQ,UAAU,QAAQ,WAAW;AACvC,aAAS;AACT;;GAEF,IAAI,OAAO;GACX,MAAM,eAAe;AACnB,QAAI,KAAM;AACV,WAAO;AACP,iBAAa,MAAM;AACnB,aAAS;;GAEX,MAAM,QAAQ,iBAAiB;AAC7B,SAAK,OACH;KAAE,MAAM;KAAU,UAAU,KAAK,UAAU;KAAE,EAC7C,4BACD;AACD,YAAQ;MACP,sCAAsC;AACzC,SAAM,OAAO;AACb,WAAQ,KAAK,SAAS,OAAO;AAC7B,OAAI;AACF,YAAQ,OAAO;WACT;AACN,SAAK,OACH,KAAK,WAAW,uBAAuB,EACvC,uBACD;AACD,YAAQ;;IAEV;;CAGJ,AAAQ,sBAAsB,SAAmC;AAC/D,MAAI,QAAQ,UAAU,QAAQ,UAAW;AACzC,MAAI;AACF,WAAQ,OAAO,MAAM,UAAU,iBAAiB;AAChD,QAAK,gBACH,uCAAuC,YAAY,KAAK,OAAO,GAChE;WACM,KAAK;AACZ,QAAK,gBACH,iDAAiD,YAAY,KAAK,OAAO,CAAC,IACxE,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAEnD;;;CAQL,AAAQ,UAAU,QAA0B;AAC1C,OAAK,gBACH,wBAAwB,KAAK,OAAO,KAAK,GAAG,KAAK,OAAO,KAAK,sBAC9D;EAED,MAAM,KAAK,MAAM,aACf;GACE,kBAAkB,KAAK,KAAK,KAAK;GACjC,UAAU;IAER,sBAAsB,KAAK,KAAK,KAAK;IACrC,mBAAmB,OAAO;IAC1B,cAAc;IACf;GACF,GACA,KAAK,QAAQ,KAAK,cAAc,KAAK,IAAI,CAC3C;AAED,KAAG,GAAG,YAAY,MAAM;AACtB,QAAK,gBACH,qCAAqC,YAAY,KAAK,OAAO,CAAC,kBAAkB,eAC9E,EAAE,cACH,CAAC,mBAAmB,eAAe,EAAE,eAAe,CAAC,GACvD;AACD,KAAE,GAAG,kBAAkB,aACrB,KAAK,gBACH,6CAA6C,YAAY,KAAK,OAAO,CAAC,IAAI,eAAe,SAAS,GACnG,CACF;AACD,KAAE,GAAG,mBAAmB,aACtB,KAAK,gBACH,mCAAmC,YAAY,KAAK,OAAO,CAAC,IAAI,eAAe,SAAS,GACzF,CACF;AAID,OAAI,KAAK,MAAM,SAAS,cAAc;IACpC,MAAM,oBAAoB,iBAAiB;AACzC,SACE,KAAK,MAAM,SAAS,iBACpB,KAAK,MAAM,cAAc,OAEzB,MAAK,OAAO;MACV,MAAM;MACN,QAAQ;MACT,CAAC;OAEH,KAAK,KAAK,KAAK,mBAAmB;AACrC,sBAAkB,OAAO;AACzB,SAAK,QAAQ;KACX,MAAM;KACN,SAAS;KACT;KACA,WAAW;KACZ;;AAEH,OAAI;AAGF,IACE,EACA,qBAAqB,KAAK,KAAK,KAAK,qBAAqB;WACrD;AAGR,KAAE,GAAG,eACH,KAAK,OACH,KAAK,WAAW,4CAA4C,EAC5D,iBACD,CACF;AACD,KAAE,GAAG,UAAU,QACb,KAAK,OACH,KAAK,WAAW,kBAAkB,IAAI,UAAU,EAChD,kBAAkB,IAAI,UACvB,CACF;IACD;AACF,KAAG,GAAG,iBAAiB,QACrB,KAAK,OACH,KAAK,WAAW,kBAAkB,IAAI,UAAU,EAChD,kBAAkB,IAAI,UACvB,CACF;AAED,KAAG,KAAK,cAAc,OAAO;;CAK/B,AAAQ,cACN,KACA,KACM;AACN,UAAQ,gBAAgB,IAAI,CAAC,MAA7B;GACE,KAAK;AACH,QAAI,UAAU,IAAI;AAClB,QAAI,KAAK;AACT;GACF,KAAK;AACH,SAAK,mBAAmB,IAAI;AAC5B;GACF,KAAK;AACH,QACE,KAAK,MAAM,SAAS,iBACpB,KAAK,MAAM,cAAc,OAEzB,MAAK,eAAe,KAAK,IAAI;QAE7B,MAAK,SAAS,IAAI;AAEpB;GACF,KAAK;AACH,SAAK,gBAAgB,KAAK,IAAI;AAC9B;;;CAIN,AAAQ,gBACN,KACA,KACM;AAGN,MAAI,KAAK,MAAM,SAAS,aAAa,KAAK,MAAM,SAAS,YAAY;AACnE,QAAK,kBAAkB,KAAK,IAAI;AAChC;;AAKF,MACE,KAAK,MAAM,SAAS,iBACpB,KAAK,MAAM,cAAc,QACzB;AAEA,GADkB,KAAK,MAAM,UACd,MAAM,EAAE,SAAS;AAC9B,QAAI,KAAK,UAAU,IAAI,OAAO,UAAW;AACzC,QAAI;AACF,SAAI,GAAI,MAAK,kBAAkB,KAAK,IAAI;UACnC;AACH,WAAK,gBACH,oCAAoC,IAAI,OAAO,MAAM,IAAI,oCAC1D;AACD,WAAK,SAAS,IAAI;;YAEd;KAGR;AACF;;AAIF,OAAK,gBACH,oCAAoC,IAAI,OAAO,MAAM,IAAI,oCAC1D;AACD,OAAK,SAAS,IAAI;;CAGpB,AAAQ,SAAS,KAAsC;AACrD,MAAI,UAAU,KAAK,GAAG,yBAAyB,QAAQ,CAAC;AACxD,MAAI,IAAI,oBAAoB;;;CAI9B,AAAQ,eACN,KACA,KACM;AACN,MAAI,KAAK,MAAM,SAAS,cAAe;AACvC,eAAa,KAAK,MAAM,kBAAkB;AAC1C,OAAK,MAAM,YAAY,iBACrB,KACA,KACA;GACE,WAAW,KAAK;GAChB,eAAe,KAAK,KAAK,KAAK;GAC9B,YAAY,KAAK,KAAK,KAAK;GAC3B,gBAAgB,KAAK,KAAK,KAAK;GAC/B,oBAAoB,KAAK;GACzB,eAAe,KAAK,KAAK,KAAK;GAC9B,qBAAqB,KAAK,KAAK,KAAK;GACrC,EACD,KAAK,KAAK,KAAK,mBAChB,CAAC,MAAM,YAAY;AAElB,OAAI,KAAK,MAAM,SAAS,cAAe,QAAO,EAAE,IAAI,OAAO;AAC3D,OAAI,QAAQ,SAAS,MAAM;IACzB,MAAM,EAAE,YAAY,KAAK;IACzB,MAAM,WAAW,KAAK,KAAK;IAC3B,MAAM,WAAW,KAAK,cAAc,QAAQ;AAG5C,SAAK,QAAQ,KAAK,KAAK,gBAAgB,GACnC;KAAE,MAAM;KAAY;KAAS;KAAU,SAAS;KAAU;KAAU,GACpE;KAAE,MAAM;KAAW;KAAS;KAAU;KAAU;AACpD,SAAK,gBACH,6BAA6B,QAAQ,KAAK,WAAW,UAAU,QAAQ,KAAK,SAAS,GACtF;AACD,SAAK,KAAK,cAAc,QAAQ,MAAM,KAAK,SAAS;AACpD,WAAO,EAAE,IAAI,MAAM;;AAErB,QAAK,OAAO,QAAQ;AACpB,UAAO,EAAE,IAAI,OAAO;IACpB;;;CAIJ,AAAQ,kBACN,KACA,KACM;EAON,MAAM,iBACJ,KAAK,MAAM,SAAS,cAAc,KAAK,MAAM,YAAY;AAC3D,MAAI,CAAC,KAAK,KAAK,gBAAgB,EAAE;AAG/B,QAAK,gBACH,oCAAoC,IAAI,OAAO,MAAM,IAAI,0CAC1D;AACD,QAAK,SAAS,IAAI;AAClB;;AAEF,MAAI,kBAAkB,KAAK,KAAK,gBAAgB,EAAE;AAChD,QAAK,gBACH,oCAAoC,IAAI,OAAO,MAAM,IAAI,sBAC1D;AACD,OAAI,UAAU,KAAK,GAAG,yBAAyB,QAAQ,CAAC;AACxD,OAAI,KAAK;AACT;;EAEF,MAAM,OAAO,cAAc,IAAI,OAAO,GAAG;AACzC,MAAI,SAAS,MAAM;AACjB,OAAI,UAAU,IAAI;AAClB,OAAI,IAAI,mCAAmC;AAC3C;;AAEF,MAAI,MAAM;AAEV,OAAK,KAAK,iBAAiB;AAC3B,MAAI,OAAO,KAAK,eAAe,KAAK,KAAK,eAAe,CAAC;AACzD,MAAI,OAAO,KAAK,UAAU,QAAe;GACvC,MAAM,WAAW,IAAI,OAAO,MAAM;GAClC,MAAM,UAAU,iBAAiB,IAAI,IAAI;AACzC,QAAK,gBACH,4BAA4B,SAAS,GAAG,IAAI,UAAU,IAAI,GAAG,QAAQ,WACnE,IAAI,UAEP;IACD;AACF,MAAI;AACF,QAAK,KAAK,WAAW,KAAK,IAAI;WACvB,KAAK;GACZ,MAAM,WAAW,IAAI,OAAO,MAAM;GAClC,MAAM,UAAU,iBAAiB,IAAI,IAAI;AACzC,QAAK,gBACH,kDAAkD,SAAS,GACzD,IAAI,UAAU,IACf,GAAG,QAAQ,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACjE;AACD,oBAAiB,IAAI;;;CAMzB,AAAQ,mBAAmB,KAAsC;AAC/D,MAAI,UAAU,IAAI;AAClB,MAAI,KAAK;AACT,OAAK,gBACH,mDAAmD,YAAY,KAAK,OAAO,GAC5E;AACD,MAAI,CAAC,KAAK,KAAK,KAAK,eAAe;AAGjC,QAAK,gBACH,0EACD;AACD;;AAEF,MAAI,KAAK,MAAM,SAAS,UACtB,MAAK,kBAAkB;WAEvB,KAAK,MAAM,SAAS,iBACpB,KAAK,MAAM,cAAc,OAMzB,CAAK,KAAK,MAAM,UAAU,MAAM,EAAE,SAAS;AACzC,OAAI,GAAI,MAAK,kBAAkB;IAC/B;;;;;;;;;CAaN,AAAQ,mBAAyB;AAC/B,MAAI,KAAK,MAAM,SAAS,UAAW;EACnC,MAAM,EAAE,SAAS,UAAU,aAAa,KAAK;AAC7C,OAAK,gBACH,gFACD;AACD,WAAS,MAAM;AACf,OAAK,YAAY;AACjB,OAAK,KAAK,SAAS,IAAI,SAAS,KAAK,QAAS,KAAK,KAAK,KAAK,aAAa;AAC1E,OAAK,QAAQ;GACX,MAAM;GACN;GACA;GACA,SAAS;GACT;GACD;AACD,OAAK,WAAW,QAAQ;GAAE,MAAM;GAAW,UAAU,KAAK,UAAU;GAAE,CAAC;;;;;;;CAQzE,mBAAyB;EACvB,MAAM,IAAI,KAAK;AACf,UAAQ,EAAE,MAAV;GACE,KAAK;AACH,SAAK,QAAQ;KACX,MAAM;KACN,SAAS,EAAE;KACX,UAAU,EAAE;KACZ,SAAS;KACT,UAAU,EAAE;KACb;AACD,SAAK,sBAAsB,EAAE,QAAQ;AACrC;GACF,KAAK;GACL,KAAK;AAEH,SAAK,OAAO;KAAE,MAAM;KAAa,QAAQ;KAAiB,CAAC;AAC3D;GACF,KAAK;GACL,KAAK,SACH;;;CAIN,MAAM,kBAAkB,MAAyC;EAC/D,MAAM,IAAI,KAAK;AACf,MAAI,EAAE,SAAS,cAAc,EAAE,YAAY,SAAU;AACrD,MAAI,KAAK,OAAO;AACd,QAAK,OACH;IAAE,MAAM;IAAU,UAAU,KAAK,UAAU;IAAE,EAC7C,6BACD;AACD;;AAEF,QAAM,KAAK,uBAAuB,EAAE,QAAQ;;CAK9C,AAAQ,cAAc,SAAuC;EAC3D,MAAM,WAAW,IAAI,SAAS,SAAS,KAAK,KAAK,YAAY;AAC3D,QAAK,gBAAgB,sCAAsC;AAC3D,QAAK,OACH;IAAE,MAAM;IAAU,UAAU,KAAK,UAAU;IAAE,EAC7C,iDACD;IACD;AACF,WAAS,OAAO;AAChB,SAAO;;;;;;;;;;;;;ACj+BX,MAAa,kCAAkC;;;;;;;AAQ/C,IAAa,UAAb,MAAqB;CACnB,AAAQ;CAER,YACE,AAAiBI,WACjB,AAAiBC,QACjB,AAAiBC,OACjB;EAHiB;EACA;EACA;AAEjB,OAAK,YAAY;;CAGnB,OAAe;EACb,MAAM,IAAI,KAAK;AACf,OAAK,YAAY,KAAK,IAAI,KAAK,YAAY,KAAK,QAAQ,KAAK,MAAM;AACnE,SAAO,KAAK,KAAM,KAAK,QAAQ;;CAGjC,QAAc;AACZ,OAAK,YAAY,KAAK;;;;;;;AChC1B,SAAgB,MAAM,IAAY,QAAoC;AACpE,QAAO,IAAI,SAAS,YAAY;AAC9B,MAAI,OAAO,SAAS;AAClB,YAAS;AACT;;EAEF,MAAM,IAAI,iBAAiB;AACzB,UAAO,oBAAoB,SAAS,QAAQ;AAC5C,YAAS;KACR,GAAG;EACN,MAAM,gBAAgB;AACpB,gBAAa,EAAE;AACf,YAAS;;AAEX,SAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;GACzD;;;;;;;;AASJ,eAAsB,cACpB,SACA,QACmB;AACnB,KAAI,OAAO,QAAS,QAAO;CAC3B,IAAIC;CACJ,MAAM,UAAU,IAAI,SAAe,YAAY;AAC7C,kBAAgB,QAAQ,KAAK;AAC7B,SAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;GACzD;AACF,KAAI;AACF,SAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,CAAC;WACrC;AACR,SAAO,oBAAoB,SAAS,QAAQ;;;;AAMhD,IAAa,WAAb,MAAyB;CACvB,AAAQ,UAAU;CAClB,AAAS;CACT,AAAQ;CACR,AAAQ;CAER,cAAc;AACZ,OAAK,UAAU,IAAI,SAAY,SAAS,WAAW;AACjD,QAAK,YAAY;AACjB,QAAK,WAAW;IAChB;;CAGJ,QAAQ,OAAgB;AACtB,MAAI,KAAK,QAAS;AAClB,OAAK,UAAU;AACf,OAAK,UAAU,MAAM;;CAGvB,OAAO,KAAkB;AACvB,MAAI,KAAK,QAAS;AAClB,OAAK,UAAU;AACf,OAAK,SAAS,IAAI;;;;;;AClCtB,SAAS,iBAAiB,MAAwB;AAChD,QAAO,KAAK,WAAW,IAAI,WAAW,KAAK,KAAK,KAAK;;AAQvD,IAAa,aAAb,MAAwB;CACtB,AAAiB,wBAAQ,IAAI,KAAmB;;CAEhD,AAAiB,aAAa,IAAI,iBAAiB;;CAEnD,AAAiB,OAAO,IAAI,iBAAiB;CAC7C,AAAQ,WAAW;CACnB,AAAQ;CACR,AAAQ;;CAGR,AAAS;CAET,YACE,AAAiBC,MACjB,AAAiBC,MACjB,AAAiBC,OACjB,AAAiBC,KACjB;EAJiB;EACA;EACA;EACA;AAEjB,OAAK,WAAW,OAAO,iBAAiB,eAAe,KAAK,KAAK,OAAO,EAAE,EACxE,MAAM,MACP,CAAC;AACF,OAAK,OAAO,KAAK,WAAW;;CAG9B,IAAI,aAAgC;AAClC,SAAO,KAAK;;;;;;CAOd,gBAAsB;AACpB,OAAK,WAAW;AAChB,OAAK,IAAI,gDAAgD;AACzD,OAAK,KAAK,OAAO;;;CAInB,WAAiB;AACf,OAAK,WAAW;AAChB,OAAK,IAAI,8CAA8C;AACvD,OAAK,WAAW,OAAO;;CAGzB,AAAQ,UAAU,KAAa,QAAsB;EACnD,MAAM,MAAM,IAAI,iBAAiB;AAEjC,OAAK,WAAW,OAAO,iBAAiB,eAAe,IAAI,OAAO,EAAE;GAClE,MAAM;GACN,QAAQ,IAAI;GACb,CAAC;EACF,MAAMC,OAAa;GAAE;GAAK,MAAM,QAAQ,SAAS;GAAE;AACnD,OAAK,OAAO,KAAK,QAAQ,QAAQ,IAAI,CAAC,cAAc;AAGlD,OAAI,KAAK,MAAM,IAAI,IAAI,KAAK,KAAM,MAAK,MAAM,OAAO,IAAI;IACxD;AACF,OAAK,MAAM,IAAI,KAAK,KAAK;;CAG3B,MAAc,sBAAwC;AACpD,MAAI,KAAK,KAAK,iBAAiB,OAAW,QAAO;AACjD,OAAK,IACH,yDAAyD,KAAK,KAAK,sBAAsB,GAC1F;EACD,IAAIC;AACJ,MAAI;GACF,MAAM,QAAQ,KAAK,KAAK,cAAc;AACtC,SAAM,YAAY,GAAG;GACrB,MAAM,iBAAiB,QAAQ,KAAK,CAClC,OACA,IAAI,SAAgB,GAAG,WAAW;AAChC,cAAU,iBAAiB;AACzB,4BACE,IAAI,MACF,0CAA0C,KAAK,KAAK,sBAAsB,IAC3E,CACF;OACA,KAAK,KAAK,sBAAsB;KACnC,CACH,CAAC;AACF,kBAAe,YAAY,GAAG;AAE9B,OADc,MAAM,cAAc,gBAAgB,KAAK,KAAK,OAAO,KACrD,KAAM,QAAO;AAC3B,QAAK,IAAI,wCAAwC;AACjD,UAAO;WACA,KAAK;AACZ,OAAI,KAAK,YAAY,KAAK,KAAK,OAAO,QAAS,QAAO;GACtD,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC/D,QAAK,wBAAQ,IAAI,MACf,0CAA0C,SAC3C;AACD,QAAK,IACH,kDAAkD,OAAO,4BAC1D;AACD,QAAK,MAAM,QAAQ,KAAK,MAAM;AAC9B,QAAK,WAAW,OAAO;AACvB,UAAO;YACC;AACR,OAAI,YAAY,OAAW,cAAa,QAAQ;;;;CAKpD,MAAc,QAAQ,QAAgB,KAAqC;EACzE,MAAM,UAAU,IAAI,QAClB,KAAK,KAAK,oBACV,KAAK,KAAK,iBACV,KAAK,KAAK,eACX;AAED,SAAO,CAAC,KAAK,YAAY,CAAC,IAAI,OAAO,WAAW,KAAK,UAAU,QAAW;GACxE,MAAM,UAAU,MAAM,cAAc,QAAQ,IAAI,QAAQ,KAAK,KAAK;AAClE,OAAI,KAAK,YAAY,IAAI,OAAO,QAAS;AACzC,OAAI,QAAQ,SAAS,SAAS;AAE5B,SAAK,wBAAQ,IAAI,MAAM,WAAW,QAAQ,SAAS;AACnD,SAAK,IAAI,mBAAmB,QAAQ,OAAO,4BAA4B;AACvE,SAAK,MAAM,QAAQ,KAAK,MAAM;AAC9B,SAAK,WAAW,OAAO;AACvB;;AAEF,OAAI,QAAQ,SAAS,YAAY,QAAQ,SAAS,WAAW;IAE3D,MAAM,iBACJ,QAAQ,YAAY;AACtB,QAAI,eAAgB,SAAQ,OAAO;AACnC,QAAI,QAAQ,SAAS,aAAa,gBAAgB;AAGhD,UAAK,IAAI,8CAA8C;AACvD;;AAEF,SAAK,IACH,QAAQ,SAAS,YACb,yEACA,0CACL;SAED,MAAK,IAAI,WAAW,QAAQ,OAAO,iBAAiB;AAEtD,SAAM,MAAM,QAAQ,MAAM,EAAE,IAAI,OAAO;;;;;CAM3C,MAAc,YAA2B;AACvC,MAAI,CAAE,MAAM,KAAK,qBAAqB,CAAG;AACzC,SAAO,CAAC,KAAK,YAAY,KAAK,UAAU,QAAW;GACjD,IAAIC;AACJ,OAAI;IAIF,MAAM,aAAa,eAAe;KAAE,GAAG,KAAK;KAAM,QAAQ,KAAK;KAAK,CAAC;AACrE,eAAW,YAAY,GAAG;IAC1B,MAAM,QAAQ,MAAM,cAAc,YAAY,KAAK,KAAK,OAAO;AAC/D,QAAI,UAAU,KAAM;AACpB,cAAU;YACH,KAAK;AAGZ,SAAK,IACH,qCAAqC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC,aACvF;AACD,UAAM,MACJ,KAAK,IAAI,KAAO,KAAK,KAAK,kBAAkB,EAC5C,KAAK,KAAK,OACX;AACD;;AAEF,OAAI,KAAK,YAAY,KAAK,UAAU,OAAW;GAE/C,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,CAAU,CAAC;GACvE,MAAM,cAAc,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC,MAAM;AAC9C,OACE,KAAK,qBAAqB,UAC1B,YAAY,WAAW,KAAK,iBAAiB,UAC7C,YAAY,MAAM,KAAK,MAAM,QAAQ,KAAK,iBAAkB,GAAG,EAC/D;IACA,MAAM,SACJ,KAAK,KAAK,YAAY,SAClB,8BACA,OAAO,KAAK,KAAK;AACvB,SAAK,IACH,2BAA2B,OAAO,IAAI,iBAAiB,YAAY,GACpE;AACD,QAAI,KAAK,qBAAqB,QAAW;KACvC,MAAM,WAAW,IAAI,IAAI,KAAK,iBAAiB;KAC/C,MAAM,UAAU,IAAI,IAAI,YAAY;KACpC,MAAM,QAAQ,YAAY,QAAQ,QAAQ,CAAC,SAAS,IAAI,IAAI,CAAC;KAC7D,MAAM,UAAU,KAAK,iBAAiB,QACnC,QAAQ,CAAC,QAAQ,IAAI,IAAI,CAC3B;AACD,SAAI,MAAM,SAAS,EACjB,MAAK,IACH,4CAA4C,iBAAiB,MAAM,GACpE;AAEH,SAAI,QAAQ,SAAS,EACnB,MAAK,IACH,yCAAyC,iBAAiB,QAAQ,GACnE;;AAGL,SAAK,mBAAmB;;AAE1B,QAAK,MAAM,CAAC,KAAK,WAAW,QAC1B,KAAI,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE;AACxB,SAAK,IAAI,kCAAkC,MAAM;AACjD,SAAK,UAAU,KAAK,OAAO;;AAG/B,QAAK,MAAM,CAAC,KAAK,SAAS,KAAK,MAC7B,KAAI,CAAC,QAAQ,IAAI,IAAI,EAAE;AACrB,SAAK,IAAI,WAAW,IAAI,oCAAoC;AAC5D,SAAK,IAAI,OAAO;;AAIpB,OAAI,KAAK,KAAK,YAAY,OAAW;AACrC,SAAM,MAAM,KAAK,KAAK,mBAAmB,KAAK,KAAK,OAAO;;AAI5D,QAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC;;;;;;;;;;;;AC7NlE,IAAM,kBAAN,MAAsB;CACpB,AAAQ,QAAQ;CAChB,AAAQ;CAER,IAAI,WAAmB;AACrB,SAAO,KAAK;;CAGd,UAAgB;AACd,OAAK;;CAGP,QAAc;AACZ,OAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,QAAQ,EAAE;AACxC,MAAI,KAAK,UAAU,KAAK,KAAK,kBAAkB,QAAW;GACxD,MAAM,SAAS,KAAK;AACpB,QAAK,gBAAgB;AACrB,WAAQ;;;;;CAMZ,YAAY,SAAmC;AAC7C,SAAO,IAAI,SAAS,YAAY;AAC9B,OAAI,KAAK,UAAU,GAAG;AACpB,YAAQ,KAAK;AACb;;GAEF,MAAM,QAAQ,iBAAiB;AAC7B,SAAK,gBAAgB;AACrB,YAAQ,MAAM;MACb,QAAQ;AACX,SAAM,OAAO;AACb,QAAK,sBAAsB;AACzB,iBAAa,MAAM;AACnB,YAAQ,KAAK;;IAEf;;;AAiCN,MAAM,8CAA8B,IAAI,KAGrC;AACH,MAAM,yCAAyB,IAAI,KAAiC;AACpE,IAAI,2BAA2B;AAE/B,eAAe,0BACb,QACe;AACf,KAAI,yBAA0B;AAC9B,4BAA2B;CAC3B,MAAM,gBAAgB,CAAC,GAAI,4BAA4B,IAAI,OAAO,IAAI,EAAE,CAAE;AAC1E,OAAM,QAAQ,WACZ,cAAc,KAAK,UAAU,MAAM,SAAS,OAAO,CAAC,CACrD;AACD,KAAI;AACF,UAAQ,KAAK,EAAE;WACP;AAER,6BAA2B;;;AAI/B,SAAS,gCACP,SACA,aACY;CACZ,MAAM,cAAc,QAAQ,KAAK,WAAW;EAC1C,IAAI,gBAAgB,4BAA4B,IAAI,OAAO;AAC3D,MAAI,kBAAkB,QAAW;AAC/B,mCAAgB,IAAI,KAAK;AACzB,+BAA4B,IAAI,QAAQ,cAAc;;AAExD,gBAAc,IAAI,YAAY;AAC9B,MAAI,CAAC,uBAAuB,IAAI,OAAO,EAAE;GACvC,MAAM,gBAAgB,KAAK,0BAA0B,OAAO;AAC5D,0BAAuB,IAAI,QAAQ,QAAQ;AAC3C,WAAQ,KAAK,QAAQ,QAAQ;;AAE/B,eAAa;GACX,MAAM,UAAU,4BAA4B,IAAI,OAAO;AACvD,OAAI,YAAY,OAAW;AAC3B,WAAQ,OAAO,YAAY;AAC3B,OAAI,QAAQ,OAAO,EAAG;GACtB,MAAM,UAAU,uBAAuB,IAAI,OAAO;AAClD,OAAI,YAAY,OAAW,SAAQ,eAAe,QAAQ,QAAQ;AAClE,0BAAuB,OAAO,OAAO;AACrC,+BAA4B,OAAO,OAAO;;GAE5C;AACF,cAAa;AACX,OAAK,MAAM,cAAc,YAAa,aAAY;;;;;;;;;AAUtD,SAAgB,cAAc,SAAiD;CAE7E,MAAM,kBAAkB,eAAe,QAAQ;CAK/C,MAAM,aAAa,sBAAsB;EACvC,UAAU,QAAQ;EAClB,eAAe,gBAAgB;EAG/B,cAAc,CAAC,gBAAgB,iBAAiB;EAChD,uBAAuB,QAAQ;EAC/B,QAAQ,QAAQ;EAChB,2BAA2B,QAAQ;EACpC,CAAC;CAGF,MAAM,MAAM,gBAAgB;CAC5B,MAAM,iBAAiB,YACrB,IAAI,GAAG,QAAQ,cAAc,gBAAgB,eAAe,GAAG;CACjE,IAAI,oBAAoB,gBAAgB,iBAAiB;CACzD,MAAM,OAAO;EACX,GAAG;EACH,cACE,gBAAgB,iBAAiB,SAC7B,SACA,YAAY;AACV,SAAM,gBAAgB,cAAe;AACrC,uBAAoB;;EAE7B;CAGD,MAAM,gCAAgB,IAAI,KAAiB;CAC3C,MAAM,oCAAoB,IAAI,KAA0B;CACxD,MAAM,WAAW,IAAI,kBAAkB;CACvC,MAAM,WAAW,IAAI,iBAAiB;CAGtC,MAAMC,SAAiB;EACrB,iBAAiB;EACjB,UAAU;EACV,YAAY;EACb;CAKD,MAAM,QAAQ,IAAI,UAAgB;AAClC,CAAK,MAAM,QAAQ,YAAY,GAAG;CAIlC,IAAIC;CAIJ,MAAMC,oBAAuC,EAAE;CA8B/C,MAAM,aAAa,IAAI,WACrB,MA7BqC;EACrC;EACA;EACA;EACA;EACA;EACA,gBAAgB,MAAM,aAAiC;AACrD,OAAI,MAAM,SAAS,SAAU;GAC7B,MAAM,kBAAkB,OAAO,oBAAoB;AACnD,UAAO;AACP,UAAO,WAAW;AAClB,OAAI,gBACF,KACE,+BAA+B,KAAK,WAAW,UAAU,KAAK,SAAS,WAAW,KAAK,UAAU,cAAc,SAAS,SAAS,kBAAkB,SAAS,aAAa,WAAW,SAAS,OAAO,GACrM;OAED,KACE,oDAAoD,OAAO,gBAAgB,SAAS,KAAK,WAAW,cAAc,SAAS,SAAS,kBAAkB,SAAS,aAAa,WAAW,SAAS,OAAO,GACxM;AAEH,SAAM,SAAS;;EAEjB,sBAAsB,MAAM,SAAS;EACrC,sBAAsB;EACtB,uBAAuB,SAAS,SAAS;EACzC,qBAAqB,SAAS,OAAO;EACtC,EAKC,EACE,UAAU,QAAQ;AAChB,SAAO,aAAa;AACpB,QAAM,OAAO,IAAI;IAEpB,EACD,cACD;AAID,SAAQ;EAAE,MAAM;EAAW,QAAQ;GAAE;GAAY,WAF/B,kBAAkB,IAAI,WAAW;GAES;EAAE;;;CAM9D,MAAM,iBAAuB;AAC3B,MAAI,MAAM,SAAS,SAAU;EAC7B,MAAM,EAAE,0BAAY,cAAc,MAAM;AACxC,UAAQ,EAAE,MAAM,UAAU;AAC1B,OAAK,MAAM,cAAc,kBAAmB,aAAY;AACxD,oBAAkB,SAAS;AAC3B,eAAW,UAAU;AACrB,OAAK,MAAM,UAAU,cAAe,QAAO,SAAS;AACpD,gBAAc,OAAO;AACrB,WAAS,YAAY;AACrB,gBAAc,UAAU;;CAK1B,MAAM,OAAO,WAAW,KAAK,WAAW;AACtC,YAAU;AACV,QAAM,OACJ,OAAO,8BACL,IAAI,MAAM,4CAA4C,CACzD;GACD;CAEF,MAAM,kBAAkB,OACtB,QACA,YACkB;EAClB,MAAM,EAAE,6BAAe;AAKvB,eAAW,eAAe;AAC1B,OAAK,MAAM,KAAK,CAAC,GAAG,kBAAkB,CAAE,GAAE,kBAAkB;AAC5D,gBACE,kEAAkE,SAAS,SAAS,YACrF;EACD,MAAM,UAAU,MAAM,SAAS,YAAY,QAAQ;AAGnD,QAAM,QAAQ,IACZ,CAAC,GAAG,kBAAkB,CAAC,KAAK,MAC1B,EAAE,kBAAkB,EAAE,OAAO,CAAC,SAAS,CAAC,CACzC,CACF;AAID,YAAU;AACV,QAAM;;CAGR,MAAM,QAAQ,YAA2B;AACvC,YAAU;AACV,QAAM;;CAGR,MAAM,YAAY,EAAE,YAAkC,EAAE,KAAoB;AAI1E,MAAI,CAAC,gBAAgB,oBAAqB,QAAO,OAAO;AAExD,MAAI,MAAM,SAAS,WAAY,QAAO,MAAM;AAC5C,MAAI,MAAM,SAAS,SAAU,QAAO;EAKpC,MAAM,EAAE,WAAW;EACnB,MAAM,YAAY,gBAChB,QACA,WAAW,gBAAgB,aAC5B;AACD,UAAQ;GAAE,MAAM;GAAY;GAAQ;GAAW;AAC/C,SAAO;;AAQT,KAAI,gBAAgB,qBAAqB,QAAW;EAClD,MAAM,EAAE,SAAS,YAAY,gBAAgB;AAC7C,oBAAkB,KAChB,gCAAgC,SAAS,EACvC,MAAM,SAAS,QAAQ;AACrB,iBACE,oBAAoB,OAAO,6BAC5B;AACD,SAAM,SAAS,EAAE,SAAS,CAAC;KAE9B,CAAC,CACH;;AAGH,KAAI,QAAQ,QAAQ,QAElB,CAAK,OAAO;KAEZ,SAAQ,QAAQ,iBAAiB,eAAe,KAAK,OAAO,EAAE,EAC5D,MAAM,MACP,CAAC;AAKJ,QAAO;EACL;EACA;EACA,IAAI,kBAAkB;AACpB,UAAO,OAAO;;EAEhB,IAAI,aAAa;AACf,UAAO,OAAO,UAAU;;EAE1B,IAAI,WAAW;AACb,UAAO,OAAO,UAAU;;EAE1B,IAAI,YAAY;AACd,UAAO,OAAO,UAAU;;EAE1B,IAAI,gBAAgB;GAClB,MAAM,WAAW,OAAO;AACxB,OAAI,aAAa,OAAW,QAAO;AAKnC,OAAI;IACF,MAAM,QAAQ,IAAI,IAAI,SAAS,SAAS;AACxC,QAAI,MAAM,SAAS,GAAI,OAAM,OAAO;AAEpC,WAAO,GADM,MAAM,UAAU,CAAC,QAAQ,OAAO,GAAG,CACjC;WACT;AACN,WAAO,GAAG,SAAS,SAAS;;;EAGhC,IAAI,QAAQ;AACV,UAAO,OAAO;;EAEhB,OAAO,MAAM;EACd"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["url: URL","port","log: DiagnosticLogger","targets","targets: Target[]","base: tls.ConnectionOptions","entry: DrainingConnection","session: http2.Http2Session","opts: ResolvedOptions","onDead: () => void","authToken: string","target: Target","slotSignal: AbortSignal","deps: ConnectionDeps","initialMs: number","factor: number","maxMs: number","onAbort!: () => void","opts: ResolvedOptions","deps: ConnectionDeps","hooks: SupervisorHooks","log: (message: string) => void","slot: Slot","timeout: ReturnType<typeof setTimeout> | undefined","targets: Target[]","output: Output","state: EngineState","signalUnregisters: Array<() => void>"],"sources":["../src/targets.ts","../src/options.ts","../src/draining.ts","../src/handshake.ts","../src/forwarded.ts","../src/connection.ts","../src/backoff.ts","../src/util.ts","../src/supervisor.ts","../src/connect.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Tunnel-server discovery: explicit addresses or region-based DNS SRV.\n\nimport * as dns from \"node:dns\";\n\n/** A dialable tunnel server. */\nexport interface Target {\n host: string;\n port: number;\n /**\n * TLS SNI / verification name. For SRV-discovered targets this is the SRV\n * QUERY name (`tunnel.<region>.restate.cloud` — what the cloud's cert\n * covers), regardless of which per-record host is dialed; for explicit\n * addresses it is the configured host.\n */\n servername: string;\n /**\n * Per-target plaintext override: set when an explicit `http://` URL was\n * given. `undefined` means \"follow the global `tls` option\".\n */\n plaintext?: boolean;\n}\n\ntype DiagnosticLogger = (message: string) => void;\n\nfunction formatError(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Parse one explicit tunnel-server address: `\"host:port\"`, or a URL whose\n * scheme picks TLS (`https`) / plaintext (`http`) for that server.\n * Throws on a malformed address.\n */\nexport function parseServerAddress(address: string): Target {\n if (address.includes(\"://\")) {\n let url: URL;\n try {\n url = new URL(address);\n } catch {\n throw new Error(\n `tunnel: invalid tunnel server URL ${JSON.stringify(address)}`\n );\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(\n `tunnel: unsupported tunnel server scheme ${JSON.stringify(url.protocol)} (use http or https)`\n );\n }\n if (url.pathname !== \"/\" || url.search !== \"\") {\n throw new Error(\n `tunnel: tunnel server URL must not have a path or query: ${JSON.stringify(address)}`\n );\n }\n const port =\n url.port !== \"\" ? Number(url.port) : url.protocol === \"https:\" ? 443 : 80;\n return {\n host: url.hostname,\n port,\n servername: url.hostname,\n plaintext: url.protocol === \"http:\",\n };\n }\n // \"host:port\" — split on the LAST colon so IPv6-ish hosts survive.\n const idx = address.lastIndexOf(\":\");\n if (idx <= 0 || idx === address.length - 1) {\n throw new Error(\n `tunnel: invalid tunnel server address ${JSON.stringify(address)} (expected \"host:port\" or a URL)`\n );\n }\n const host = address.slice(0, idx);\n const port = Number(address.slice(idx + 1));\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(\n `tunnel: invalid port in tunnel server address ${JSON.stringify(address)}`\n );\n }\n return { host, port, servername: host };\n}\n\n/**\n * Resolve the current set of tunnel servers. Called fresh per connection\n * attempt, so DNS changes are picked up across redials.\n *\n * - Explicit `tunnelServers`: parsed as-is (no DNS here — the dial resolves\n * the hostname).\n * - `srvName` (region-derived or given directly): a DNS SRV lookup, each\n * record expanded to ALL of its addresses (priority asc, weight desc).\n *\n * Error taxonomy (mirrors the Rust resolver): a NEGATIVE answer for an SRV\n * target (the name genuinely has no address — ENOTFOUND/ENODATA) removes\n * that target, and an all-negative answer yields an EMPTY list (the\n * supervisor then reconciles everything away, like Rust's empty set). A\n * TRANSPORT error (EAI_AGAIN, timeouts, SERVFAIL) THROWS instead — the\n * supervisor must keep the existing connections serving and retry, not\n * tear down healthy slots over a resolver blip.\n */\nexport async function resolveTargets(spec: {\n srvName?: string;\n tunnelServers?: string[];\n logger?: DiagnosticLogger;\n}): Promise<Target[]> {\n const log: DiagnosticLogger = spec.logger ?? (() => {});\n if (spec.tunnelServers !== undefined) {\n const targets = spec.tunnelServers.map(parseServerAddress);\n if (targets.length === 0) {\n throw new Error(\"tunnel: tunnelServers is empty\");\n }\n log(\n `tunnel: using configured tunnel target(s): ${targets.map(targetKey).join(\", \")}`\n );\n return targets;\n }\n const srvName = spec.srvName!;\n log(`tunnel: resolving tunnel targets from SRV ${srvName}`);\n const records = await dns.promises.resolveSrv(srvName);\n records.sort((a, b) => a.priority - b.priority || b.weight - a.weight);\n log(\n `tunnel: SRV ${srvName} returned ${records.length} record(s): ${\n records.map((r) => `${r.name}:${r.port}`).join(\", \") || \"<none>\"\n }`\n );\n // Expand each SRV target to its addresses: the tunnel connects to EVERY\n // resolved address (one connection per IP), exactly like the Rust client,\n // which flat-maps SRV targets through A/AAAA lookups into per-IP URIs.\n // Lookups run concurrently (Rust uses FuturesUnordered) so one slow\n // resolver doesn't serialize the rest. SNI / certificate verification\n // uses the SRV QUERY name (the cloud's cert covers the SRV name, not\n // per-node hostnames) — mirroring the Rust FixedServerNameResolver.\n const lookups = await Promise.allSettled(\n records.map((r) => dns.promises.lookup(r.name, { all: true }))\n );\n const targets: Target[] = [];\n const seen = new Set<string>();\n for (let i = 0; i < records.length; i++) {\n const r = records[i]!;\n const result = lookups[i]!;\n if (result.status === \"rejected\") {\n const code = (result.reason as NodeJS.ErrnoException | undefined)?.code;\n if (code === \"ENOTFOUND\" || code === \"ENODATA\") {\n log(`tunnel: SRV target ${r.name}:${r.port} has no address (${code})`);\n continue; // negative answer: this SRV target genuinely has no address\n }\n // Transport error — fail the whole resolution so the supervisor\n // keeps existing slots and retries.\n log(\n `tunnel: address lookup for SRV target ${r.name}:${r.port} failed: ${formatError(result.reason)}`\n );\n throw result.reason;\n }\n for (const a of result.value) {\n const key = `${a.address}:${r.port}`;\n if (seen.has(key)) continue;\n seen.add(key);\n targets.push({ host: a.address, port: r.port, servername: srvName });\n }\n }\n log(\n `tunnel: SRV ${srvName} expanded to ${targets.length} target(s): ${\n targets.map(targetKey).join(\", \") || \"<none>\"\n }`\n );\n return targets;\n}\n\n/** Stable identity of a target — the unit of one tunnel connection. */\nexport function targetKey(t: Target): string {\n return `${t.host}:${t.port}`;\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Option validation and TLS construction.\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport type * as tls from \"node:tls\";\nimport type { Duration } from \"@restatedev/restate-sdk\";\nimport type { ConnectTunnelOptions, TunnelTlsOptions } from \"./types.js\";\nimport { parseServerAddress } from \"./targets.js\";\n\n// The environment variables options fall back to when not given explicitly\n// (option > environment > throw). They form the contract with the\n// restate-operator, which injects the first four into the pods of a\n// `tunnelMode: in-process` RestateDeployment; AUTH_TOKEN_FILE is reserved\n// for the user's own Secret mount — credentials are never injected.\nexport const TUNNEL_NAME_ENV = \"RESTATE_INPROC_TUNNEL_NAME\";\nexport const ENVIRONMENT_ID_ENV = \"RESTATE_INPROC_ENVIRONMENT_ID\";\nexport const CLOUD_REGION_ENV = \"RESTATE_INPROC_CLOUD_REGION\";\nexport const SIGNING_PUBLIC_KEY_ENV = \"RESTATE_INPROC_SIGNING_PUBLIC_KEY\";\nexport const AUTH_TOKEN_FILE_ENV = \"RESTATE_INPROC_AUTH_TOKEN_FILE\";\nexport const TUNNEL_WORKER_ID_ENV = \"RESTATE_TUNNEL_WORKER_ID\";\n\nexport interface ResolvedOptions {\n /** The SRV name to discover tunnel servers from (region-derived or given). */\n srvName?: string;\n tunnelServers?: string[];\n environmentId: string;\n /**\n * Returns the bearer token for the handshake. Called once per connection\n * attempt: a file-sourced token (AUTH_TOKEN_FILE_ENV) is re-read on every\n * redial so rotations are picked up without a restart. May throw (e.g.\n * the file is briefly unreadable mid-rotation) — callers treat that as a\n * retryable connection failure.\n */\n authToken: () => string;\n signingPublicKey: string;\n tunnelName: string;\n tunnelWorkerId: string;\n bidirectional: boolean;\n startupReady?: () => Promise<void>;\n startupReadyTimeoutMs: number;\n resolveIntervalMs: number;\n supportsDrain: boolean;\n drainGraceMs: number;\n supportsClientDrain: boolean;\n /** Set when auto signal-handling is opted into; undefined leaves signals alone. */\n gracefulShutdown?: { signals: NodeJS.Signals[]; graceMs: number };\n connectTimeoutMs: number;\n handshakeTimeoutMs: number;\n reconnectInitialMs: number;\n reconnectMaxMs: number;\n reconnectFactor: number;\n pingIntervalMs: number;\n pingTimeoutMs: number;\n pingMaxMissed: number;\n maxConcurrentStreams: number;\n connectionWindowSize: number;\n maxSessionMemory: number;\n tls: boolean | TunnelTlsOptions;\n logger: (message: string) => void;\n}\n\n/** An env var set to the empty string is treated as unset. */\nfunction fromEnv(name: string): string | undefined {\n const value = process.env[name];\n return value === undefined || value === \"\" ? undefined : value;\n}\n\n/** Resolve option > environment > throw. */\nfunction requireConfigured(\n value: string | undefined,\n name: string,\n envName: string\n): string {\n const resolved =\n value !== undefined && value !== \"\" ? value : fromEnv(envName);\n if (resolved === undefined) {\n throw new Error(\n `tunnel: ${name} is required (pass the option or set ${envName})`\n );\n }\n return resolved;\n}\n\n/**\n * Both credentials travel as HTTP header values in the handshake. Node\n * silently strips header-illegal characters, which would surface as a\n * baffling `unauthorized` from the server — reject them loudly instead.\n */\nfunction requireHeaderSafe(value: string, what: string): string {\n if (!/^[\\x21-\\x7e]+$/.test(value)) {\n throw new Error(\n `tunnel: ${what} contains characters that cannot travel in an HTTP header (whitespace or non-printable)`\n );\n }\n return value;\n}\n\nfunction resolveAuthToken(option: string | undefined): () => string {\n if (option !== undefined && option !== \"\") {\n requireHeaderSafe(option, \"authToken\");\n return () => option;\n }\n const tokenFile = fromEnv(AUTH_TOKEN_FILE_ENV);\n if (tokenFile === undefined) {\n throw new Error(\n `tunnel: authToken is required (pass the option or set ${AUTH_TOKEN_FILE_ENV})`\n );\n }\n const readToken = () => {\n // Guard before reading: this runs synchronously on the redial path, so a\n // FIFO (blocks forever) or an unbounded device file (reads forever) would\n // freeze the event loop — and with it every other live connection.\n const stat = fs.statSync(tokenFile);\n if (!stat.isFile()) {\n throw new Error(\n `tunnel: auth token file ${tokenFile} is not a regular file`\n );\n }\n if (stat.size > 64 * 1024) {\n throw new Error(\n `tunnel: auth token file ${tokenFile} is implausibly large for a token (${stat.size} bytes)`\n );\n }\n // Trimmed because mounted secrets routinely carry a trailing newline.\n const token = fs.readFileSync(tokenFile, \"utf8\").trim();\n if (token === \"\") {\n throw new Error(`tunnel: auth token file ${tokenFile} is empty`);\n }\n return requireHeaderSafe(token, `auth token file ${tokenFile}`);\n };\n // A bad path or token must throw at configuration time like every other\n // misconfiguration, not look like a transient failure mid-redial.\n readToken();\n return readToken;\n}\n\nfunction sanitizeDefaultWorkerIdSegment(value: string): string {\n const sanitized = value\n .replace(/[^A-Za-z0-9._:-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return (sanitized === \"\" ? \"worker\" : sanitized).slice(0, 96);\n}\n\nfunction makeDefaultTunnelWorkerId(): string {\n const host = fromEnv(\"HOSTNAME\") ?? os.hostname() ?? \"worker\";\n const suffix = randomBytes(4).toString(\"hex\");\n return `${sanitizeDefaultWorkerIdSegment(host)}-${suffix}`;\n}\n\n// Stable for the process lifetime. Multiple connectTunnel() calls in the same\n// process get the same default worker id unless explicitly overridden.\nconst DEFAULT_TUNNEL_WORKER_ID = makeDefaultTunnelWorkerId();\n\nfunction resolveTunnelWorkerId(option: string | undefined): string {\n const value =\n option !== undefined && option !== \"\"\n ? option\n : fromEnv(TUNNEL_WORKER_ID_ENV);\n return requireHeaderSafe(value ?? DEFAULT_TUNNEL_WORKER_ID, \"tunnelWorkerId\");\n}\n\nfunction resolveStartupReady(\n option: ConnectTunnelOptions[\"startupReady\"]\n): (() => Promise<void>) | undefined {\n if (option === undefined) return undefined;\n if (typeof option === \"function\") {\n return async () => {\n await option();\n };\n }\n const ready = Promise.resolve(option);\n ready.catch(() => {});\n return async () => {\n await ready;\n };\n}\n\nfunction positive(\n value: number | undefined,\n fallback: number,\n name: string\n): number {\n if (value === undefined) return fallback;\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`tunnel: ${name} must be a positive number`);\n }\n return value;\n}\n\n/**\n * Normalize a `Duration | number` into milliseconds (a number is already ms).\n * Inlined rather than importing the SDK's `millisOrDurationToMillis`, which is\n * not part of its public API — this keeps the tunnel package dependency-free.\n */\nfunction toMillis(value: Duration | number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value === \"number\") return Math.trunc(value);\n return Math.trunc(\n (value.milliseconds ?? 0) +\n 1000 * (value.seconds ?? 0) +\n 1000 * 60 * (value.minutes ?? 0) +\n 1000 * 60 * 60 * (value.hours ?? 0) +\n 1000 * 60 * 60 * 24 * (value.days ?? 0)\n );\n}\n\n/**\n * Validate user options and apply defaults. Throws on misconfiguration.\n * Each identity/discovery option falls back to its RESTATE_INPROC_* env var\n * (option > environment > throw), so a pod the restate-operator configured\n * for `tunnelMode: in-process` needs no explicit configuration beyond the\n * auth token.\n */\nexport function resolveOptions(options: ConnectTunnelOptions): ResolvedOptions {\n const hasSrv =\n options.tunnelServersSrv !== undefined && options.tunnelServersSrv !== \"\";\n const hasServers =\n options.tunnelServers !== undefined && options.tunnelServers.length > 0;\n // The env var only fills the gap when NO discovery option was given — an\n // explicit tunnelServersSrv/tunnelServers wins over an injected region, and\n // an explicitly-given-but-empty tunnelServers stays a loud config error\n // rather than silently yielding to the environment.\n let region = options.region;\n if (\n (region === undefined || region === \"\") &&\n !hasSrv &&\n options.tunnelServers === undefined\n ) {\n region = fromEnv(CLOUD_REGION_ENV);\n }\n const hasRegion = region !== undefined && region !== \"\";\n const discoveryCount =\n Number(hasRegion) + Number(hasSrv) + Number(hasServers);\n if (discoveryCount === 0) {\n throw new Error(\n `tunnel: specify one of \\`region\\`, \\`tunnelServersSrv\\` or \\`tunnelServers\\` (or set ${CLOUD_REGION_ENV})`\n );\n }\n if (discoveryCount > 1) {\n throw new Error(\n \"tunnel: specify exactly one of `region`, `tunnelServersSrv` or `tunnelServers`\"\n );\n }\n // A region becomes DNS labels in `tunnel.{region}.restate.cloud`, so it may be\n // multi-label (e.g. a BYOC region like \"inl4edhpbxasp9yuz1n0yvvkme.byoc\") —\n // each label lowercase [a-z0-9-], dot-separated, no empty labels.\n if (hasRegion && !/^[a-z0-9-]+(\\.[a-z0-9-]+)*$/.test(region!)) {\n throw new Error(`tunnel: invalid region ${JSON.stringify(region)}`);\n }\n if (hasSrv && !/^[A-Za-z0-9._-]+$/.test(options.tunnelServersSrv!)) {\n throw new Error(\n `tunnel: invalid tunnelServersSrv ${JSON.stringify(options.tunnelServersSrv)}`\n );\n }\n // Parse explicit servers eagerly: a config typo must throw here, like\n // every other misconfiguration (the Rust client parses URIs at startup).\n // Left to the supervisor it would look like a transient resolution\n // failure and retry forever without ever connecting.\n if (hasServers) {\n for (const address of options.tunnelServers!) parseServerAddress(address);\n }\n\n const environmentId = requireConfigured(\n options.environmentId,\n \"environmentId\",\n ENVIRONMENT_ID_ENV\n );\n if (!/^env_[A-Za-z0-9_-]+$/.test(environmentId)) {\n throw new Error(\n \"tunnel: environmentId must be `env_` followed by alphanumerics (e.g. env_201k0yd4...)\"\n );\n }\n const authToken = resolveAuthToken(options.authToken);\n const signingPublicKey = requireConfigured(\n options.signingPublicKey,\n \"signingPublicKey\",\n SIGNING_PUBLIC_KEY_ENV\n );\n if (!signingPublicKey.startsWith(\"publickeyv1_\")) {\n throw new Error(\n \"tunnel: signingPublicKey must be a request-identity public key (publickeyv1_...)\"\n );\n }\n const tunnelName = requireConfigured(\n options.tunnelName,\n \"tunnelName\",\n TUNNEL_NAME_ENV\n );\n if (!/^[A-Za-z0-9._-]+$/.test(tunnelName)) {\n throw new Error(\n `tunnel: invalid tunnelName ${JSON.stringify(tunnelName)} — use letters, digits, '.', '_' or '-'`\n );\n }\n const tunnelWorkerId = resolveTunnelWorkerId(options.tunnelWorkerId);\n const pingIntervalMs = positive(\n options.pingIntervalMs,\n 75_000,\n \"pingIntervalMs\"\n );\n const pingTimeoutMs = positive(\n options.pingTimeoutMs,\n 10_000,\n \"pingTimeoutMs\"\n );\n const drainGraceMs = positive(options.drainGraceMs, 120_000, \"drainGraceMs\");\n\n return {\n srvName: hasRegion\n ? srvNameForRegion(region!)\n : hasSrv\n ? options.tunnelServersSrv\n : undefined,\n tunnelServers: hasServers ? options.tunnelServers : undefined,\n environmentId,\n authToken,\n signingPublicKey,\n tunnelName,\n tunnelWorkerId,\n bidirectional: options.bidirectional ?? true,\n startupReady: resolveStartupReady(options.startupReady),\n startupReadyTimeoutMs: positive(\n options.startupReadyTimeoutMs,\n 120_000,\n \"startupReadyTimeoutMs\"\n ),\n resolveIntervalMs: positive(\n options.resolveIntervalMs,\n 30_000,\n \"resolveIntervalMs\"\n ),\n supportsDrain: options.supportsDrain ?? true,\n drainGraceMs,\n supportsClientDrain: options.supportsClientDrain ?? true,\n gracefulShutdown: resolveGracefulShutdown(\n options.gracefulShutdown,\n drainGraceMs\n ),\n connectTimeoutMs: positive(\n options.connectTimeoutMs,\n 5_000,\n \"connectTimeoutMs\"\n ),\n handshakeTimeoutMs: positive(\n options.handshakeTimeoutMs,\n 5_000,\n \"handshakeTimeoutMs\"\n ),\n reconnectInitialMs: positive(\n toMillis(options.reconnectRetryPolicy?.initialInterval),\n 10,\n \"reconnectRetryPolicy.initialInterval\"\n ),\n reconnectMaxMs: positive(\n toMillis(options.reconnectRetryPolicy?.maxInterval),\n 120_000,\n \"reconnectRetryPolicy.maxInterval\"\n ),\n reconnectFactor: positive(\n options.reconnectRetryPolicy?.exponentiationFactor,\n 2,\n \"reconnectRetryPolicy.exponentiationFactor\"\n ),\n pingIntervalMs,\n pingTimeoutMs,\n pingMaxMissed: positive(options.pingMaxMissed, 2, \"pingMaxMissed\"),\n maxConcurrentStreams: positive(\n options.maxConcurrentStreams,\n 4096,\n \"maxConcurrentStreams\"\n ),\n connectionWindowSize: positive(\n options.connectionWindowSize,\n 16 * 1024 * 1024,\n \"connectionWindowSize\"\n ),\n maxSessionMemory: positive(\n options.maxSessionMemory,\n 256,\n \"maxSessionMemory\"\n ),\n tls: options.tls ?? true,\n logger: options.tunnelDiagnosticLogger ?? (() => {}),\n };\n}\n\n/** Resolve the opt-in auto signal-handling config (undefined = leave signals alone). */\nfunction resolveGracefulShutdown(\n option:\n | boolean\n | { signals?: NodeJS.Signals[]; graceMs?: number }\n | undefined,\n drainGraceMs: number\n): { signals: NodeJS.Signals[]; graceMs: number } | undefined {\n // On by default: only an explicit `false` opts out.\n if (option === false) return undefined;\n if (option === undefined || option === true) {\n return { signals: [\"SIGTERM\"], graceMs: drainGraceMs };\n }\n const signals = option.signals ?? [\"SIGTERM\"];\n if (signals.length === 0) {\n throw new Error(\"tunnel: gracefulShutdown.signals must not be empty\");\n }\n return {\n signals,\n graceMs: positive(option.graceMs, drainGraceMs, \"gracefulShutdown.graceMs\"),\n };\n}\n\n/**\n * Build the `tls.connect` options for a tunnel target, or `undefined` for a\n * plaintext connection.\n *\n * Always offers ALPN `[\"h2\"]` — the same offer every Rust tunnel client\n * makes — and the connection layer requires the negotiation to succeed:\n * Node's http2 will only run a server session over a TLS socket whose ALPN\n * negotiated `h2`. Tunnel servers advertise it since the standard-h2\n * control-traffic change; older servers (which cleared their ALPN list)\n * cannot serve this client.\n */\nexport function buildTlsConnectOptions(\n tlsOption: boolean | TunnelTlsOptions,\n servername: string\n): tls.ConnectionOptions | undefined {\n if (tlsOption === false) return undefined;\n const base: tls.ConnectionOptions = { servername, ALPNProtocols: [\"h2\"] };\n if (tlsOption === true) return base;\n return {\n ...base,\n ...(tlsOption.servername !== undefined && {\n servername: tlsOption.servername,\n }),\n ...(tlsOption.ca !== undefined && { ca: tlsOption.ca }),\n ...(tlsOption.cert !== undefined && { cert: tlsOption.cert }),\n ...(tlsOption.key !== undefined && { key: tlsOption.key }),\n ...(tlsOption.rejectUnauthorized !== undefined && {\n rejectUnauthorized: tlsOption.rejectUnauthorized,\n }),\n };\n}\n\n/** The DNS SRV name for region-based tunnel-server discovery. */\nexport function srvNameForRegion(region: string): string {\n return `tunnel.${region}.restate.cloud`;\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The draining registry — graceful-drain handover ownership.\n//\n// When the cloud asks a connection to drain (`/_/drain-tunnel`), the\n// connection is \"detached\": its attempt settles (so the slot dials a\n// replacement) WITHOUT destroying the session, which keeps serving its\n// in-flight invocations. This registry owns those detached sessions:\n// each is bounded by a grace timer, removes itself when the session ends\n// naturally, and is destroyed unconditionally on engine teardown — a\n// fatal or close() must never leave a detached session serving (and\n// pinning the process) for the rest of its grace window.\n\nimport type * as http2 from \"node:http2\";\nimport type * as net from \"node:net\";\n\ninterface DrainingConnection {\n session: http2.Http2Session;\n socket: net.Socket;\n timer: NodeJS.Timeout;\n}\n\nexport class DrainingRegistry {\n private readonly entries = new Set<DrainingConnection>();\n\n /**\n * Take ownership of a detached (draining) connection: let it serve its\n * in-flight streams for up to `graceMs`, then tear it down. The entry\n * removes itself if the session ends earlier on its own.\n */\n add(session: http2.Http2Session, socket: net.Socket, graceMs: number): void {\n const entry: DrainingConnection = {\n session,\n socket,\n timer: setTimeout(() => {\n this.entries.delete(entry);\n session.destroy();\n socket.destroy();\n }, graceMs),\n };\n // unref'd: a draining session must not keep the process alive past\n // engine teardown (destroyAll covers the explicit paths).\n entry.timer.unref();\n this.entries.add(entry);\n session.on(\"close\", () => {\n clearTimeout(entry.timer);\n this.entries.delete(entry);\n socket.destroy();\n });\n }\n\n /** Tear down every draining connection. Idempotent. */\n destroyAll(): void {\n for (const entry of this.entries) {\n clearTimeout(entry.timer);\n entry.session.destroy();\n entry.socket.destroy();\n }\n this.entries.clear();\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The /_/start-tunnel handshake.\n// =============================================================================\n//\n// The tunnel server (the HTTP/2 client on the role-flipped connection)\n// opens its FIRST stream as `GET /_/start-tunnel`, with a request body that\n// stays open and later delivers HTTP/2 TRAILERS. The exchange:\n//\n// 1. We answer immediately: `200` whose RESPONSE HEADERS carry our\n// credentials — `authorization: Bearer <token>`,\n// `environment-id: env_<id>`, `tunnel-name: <name>`, advisory diagnostic\n// ids (`tunnel-worker-id`, `tunnel-connection-id`), and `supports-drain:\n// true` when the drain handover is enabled (the default — see the\n// /_/drain-tunnel handling in connect.ts).\n// 2. The server validates the credentials, then completes the handshake\n// by sending TRAILERS on its still-open request body:\n// `tunnel-status: ok | unauthorized | bad-tunnel-name | too-many-tunnels`\n// plus, on ok: `proxy-url`, `tunnel-url`, `tunnel-name`.\n//\n// Node gotcha (PoC-verified): the high-level Http2ServerRequest \"trailers\"\n// event does NOT fire. Trailers must be read from the raw stream —\n// `req.stream.on(\"trailers\", ...)` — or from `req.trailers` after \"end\".\n// The body must be drained for either to fire.\n//\n// Outcome taxonomy (drives the reconnect policy in connect.ts):\n// - fatal: unauthorized, bad-tunnel-name, or a tunnel-name echo\n// mismatch. Configuration errors — redialing cannot fix\n// them, and hammering the auth path is harmful.\n// - retryable: too-many-tunnels (often a previous instance still\n// draining), timeout, malformed/missing trailers, stream\n// errors, and unknown statuses (forward compatibility).\n\nimport type * as http2 from \"node:http2\";\n\n/** What the server tells us about the established tunnel. */\nexport interface HandshakeInfo {\n tunnelName: string;\n proxyUrl: string;\n tunnelUrl: string;\n}\n\nexport type HandshakeOutcome =\n | { kind: \"ok\"; info: HandshakeInfo }\n | { kind: \"fatal\"; reason: string }\n | { kind: \"retryable\"; reason: string };\n\nexport interface HandshakeCredentials {\n authToken: string;\n environmentId: string;\n tunnelName: string;\n /** Stable-ish per SDK worker/process, for cross-side diagnostics. */\n tunnelWorkerId: string;\n /** Unique per h2 tunnel connection attempt, for cross-side diagnostics. */\n tunnelConnectionId: string;\n /**\n * Advertise `supports-drain: true`. Only set this when the engine\n * actually implements the `/_/drain-tunnel` handover — advertising it\n * obliges us to open a replacement connection on drain.\n */\n supportsDrain: boolean;\n /**\n * Advertise `supports-client-drain: true`. Tells the server that on\n * shutdown we proactively send GOAWAY and refuse any raced streams with the\n * `x-restate-tunnel-draining` sentinel (rather than dropping them); only\n * then does the server trust that sentinel to deselect this connection.\n */\n supportsClientDrain: boolean;\n}\n\nexport const START_TUNNEL_PATH = \"/_/start-tunnel\";\n\n/** Handshake deadline — mirrors the tunnel server's own 5s timeout. */\nexport const HANDSHAKE_TIMEOUT_MS = 5_000;\n\n/**\n * Run the receiver side of the /_/start-tunnel exchange on its stream.\n * Resolves with an outcome; never rejects.\n */\nexport function performHandshake(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse,\n creds: HandshakeCredentials,\n timeoutMs: number = HANDSHAKE_TIMEOUT_MS\n): Promise<HandshakeOutcome> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (outcome: HandshakeOutcome) => {\n if (settled) return;\n settled = true;\n clearTimeout(deadline);\n resolve(outcome);\n };\n\n const deadline = setTimeout(() => {\n finish({\n kind: \"retryable\",\n reason: `handshake trailers not received within ${timeoutMs}ms`,\n });\n req.stream.destroy();\n }, timeoutMs);\n deadline.unref();\n\n const onTrailers = (trailers: http2.IncomingHttpHeaders) => {\n const status = trailers[\"tunnel-status\"];\n if (status !== \"ok\") {\n if (status === \"unauthorized\" || status === \"bad-tunnel-name\") {\n finish({ kind: \"fatal\", reason: `tunnel-status: ${String(status)}` });\n } else {\n finish({\n kind: \"retryable\",\n reason: `tunnel-status: ${String(status ?? \"<missing>\")}`,\n });\n }\n return;\n }\n const tunnelName = trailers[\"tunnel-name\"];\n const proxyUrl = trailers[\"proxy-url\"];\n const tunnelUrl = trailers[\"tunnel-url\"];\n if (\n typeof tunnelName !== \"string\" ||\n typeof proxyUrl !== \"string\" ||\n typeof tunnelUrl !== \"string\"\n ) {\n finish({\n kind: \"retryable\",\n reason: \"handshake ok but proxy-url/tunnel-url/tunnel-name missing\",\n });\n return;\n }\n if (tunnelName !== creds.tunnelName) {\n // We requested a specific name; the server must echo it. A different\n // name means our registration URL would not route here.\n finish({\n kind: \"fatal\",\n reason: `tunnel-name mismatch: requested ${JSON.stringify(creds.tunnelName)}, got ${JSON.stringify(tunnelName)}`,\n });\n return;\n }\n finish({ kind: \"ok\", info: { tunnelName, proxyUrl, tunnelUrl } });\n };\n\n // PoC-verified: only the raw stream's \"trailers\" event fires; also read\n // req.trailers after \"end\" as a belt-and-braces fallback.\n req.stream.on(\"trailers\", onTrailers);\n req.on(\"end\", () => {\n if (!settled && req.trailers && Object.keys(req.trailers).length > 0) {\n onTrailers(req.trailers);\n }\n });\n req.on(\"error\", (err) => {\n finish({\n kind: \"retryable\",\n reason: `handshake stream error: ${err.message}`,\n });\n });\n req.stream.on(\"close\", () => {\n finish({\n kind: \"retryable\",\n reason: \"handshake stream closed before trailers\",\n });\n });\n // Drain the (empty) body so \"end\"/\"trailers\" can fire.\n req.resume();\n\n // Answer with our credentials. The request side stays open for trailers.\n res.writeHead(200, {\n authorization: `Bearer ${creds.authToken}`,\n \"environment-id\": creds.environmentId,\n \"tunnel-name\": creds.tunnelName,\n \"tunnel-worker-id\": creds.tunnelWorkerId,\n \"tunnel-connection-id\": creds.tunnelConnectionId,\n ...(creds.supportsDrain && { \"supports-drain\": \"true\" }),\n ...(creds.supportsClientDrain && { \"supports-client-drain\": \"true\" }),\n });\n res.end();\n });\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Forwarded-path handling. Pure — no I/O.\n\n/**\n * Strip the tunnel's forwarded prefix `/<scheme>/<host>/<port>` and return\n * the tail — the path the SDK should see.\n *\n * A forwarded invocation arrives down the tunnel with its destination\n * encoded in the path (`/http/my-service.ns.svc.cluster.local/9080/invoke/...`);\n * the cloud proxy has already stripped the `/<env>/<tunnel>` rendezvous\n * prefix. For an in-process SDK deployment the scheme/host/port are\n * vestigial (the receiver *is* the service), so we drop exactly those three\n * segments and keep the tail (`/discover`, `/invoke/<svc>/<handler>`, …).\n *\n * The tail is passed through without re-encoding: the SDK verifies each\n * request's identity JWT against the signed service-relative path (its\n * routing and verification tolerate extra path *prefixes*, but re-encoding,\n * normalization or case folding of the tail itself would break the match).\n * The query string is preserved (it is not part of `aud`).\n *\n * Returns `null` if the path isn't a forwarded `/<scheme>/<host>/<port>/...`\n * path.\n */\nexport function forwardedTail(rawUrl: string): string | null {\n const qIdx = rawUrl.indexOf(\"?\");\n const path = qIdx === -1 ? rawUrl : rawUrl.slice(0, qIdx);\n const query = qIdx === -1 ? \"\" : rawUrl.slice(qIdx);\n const seg = path.split(\"/\"); // [\"\", scheme, host, port, ...tail]\n // The port segment must be numeric — that's what distinguishes a real\n // forwarded prefix from an unprefixed SDK path that happens to have three\n // segments (e.g. `/invoke/Svc/handler` must NOT parse as scheme=invoke,\n // host=Svc, port=handler and dispatch `/` to the SDK).\n if (\n seg.length < 4 ||\n seg[1] === \"\" ||\n seg[2] === \"\" ||\n !/^\\d+$/.test(seg[3]!)\n ) {\n return null;\n }\n const tail = \"/\" + seg.slice(4).join(\"/\");\n return query ? tail + query : tail;\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// A single tunnel connection attempt.\n// =============================================================================\n//\n// One dial → serve → end cycle against one tunnel server, structured as an\n// explicit pipeline of stages driven by `ConnectionAttempt.drive()`:\n//\n// dial() — connect TCP + TLS, verify ALPN h2 (a self-contained stage\n// that owns its own connect-timeout / abort wiring).\n// establish() — role-flip: become the HTTP/2 *server* on the socket we\n// dialed; obtain the session.\n// handshake — the cloud (h2 client) opens `GET /_/start-tunnel`; we run\n// the credentials/trailers exchange (handshake.ts).\n// serve — each forwarded invocation is one h2 stream; strip\n// `/<scheme>/<host>/<port>` and hand it to the SDK handler.\n//\n// The lifecycle is one explicit `AttemptState` value, and every phase-owned\n// resource lives ON the phase that owns it — the handshake timer on\n// `handshaking`, the liveness `watchdog` on `serving`/`draining` — so each\n// method narrows the state and destructures what it needs (`const { session,\n// watchdog } = this.state`) rather than reaching for nullable instance fields.\n// Only the two genuinely lifetime-scoped things are fields: `socket` (used for\n// teardown in every phase, and handed to the registry on a server drain) and\n// `completion` (resolves `run()` exactly once).\n//\n// Two behaviours don't fit one linear phase:\n// * `run()`-resolution is decoupled from session teardown. A *server* drain\n// resolves `run()` immediately (so the slot redials) while the detached\n// session keeps serving its in-flight invocations from the\n// DrainingRegistry — the zero-drop property.\n// * New-invocation refusal during shutdown is engine-wide, so it is gated on\n// `deps.isShuttingDown()` in addition to this connection's own state.\n\nimport * as net from \"node:net\";\nimport * as tls from \"node:tls\";\nimport * as http2 from \"node:http2\";\nimport { randomBytes } from \"node:crypto\";\n\nimport type { ResolvedOptions } from \"./options.js\";\nimport { buildTlsConnectOptions } from \"./options.js\";\nimport type { Target } from \"./targets.js\";\nimport {\n performHandshake,\n START_TUNNEL_PATH,\n type HandshakeInfo,\n} from \"./handshake.js\";\nimport { forwardedTail } from \"./forwarded.js\";\nimport type { DrainingRegistry } from \"./draining.js\";\n\n/** Why a connection ended — drives the slot's reconnect policy. */\nexport type ConnectionOutcome =\n | { kind: \"served\"; uptimeMs: number } // handshake ok'd, served, then closed → redial\n | { kind: \"drained\"; uptimeMs: number } // server asked us to rotate → redial promptly\n | { kind: \"retryable\"; reason: string } // redial with backoff\n | { kind: \"fatal\"; reason: string }; // stop the tunnel, surface an error\n\n/**\n * Why a connection is draining.\n * - `server`: the cloud sent `/_/drain-tunnel` (it is rotating this tunnel\n * node). We detach + redial; the old session keeps serving in-flight.\n * - `client`: this process is shutting down (SIGTERM / `shutdown()`). We\n * send GOAWAY, refuse raced invocations, and finish in-flight in place,\n * with no redial.\n */\nexport type DrainTrigger = \"server\" | \"client\";\n\nconst CLIENT_DRAIN_SESSION_CLOSE_TIMEOUT_MS = 1_000;\nconst TUNNEL_DRAINING_HEADER = \"x-restate-tunnel-draining\";\nconst CROCKFORD_BASE32 = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nfunction encodeBase32(value: bigint, length: number): string {\n let out = \"\";\n for (let i = 0; i < length; i++) {\n out = CROCKFORD_BASE32.charAt(Number(value & 31n)) + out;\n value >>= 5n;\n }\n return out;\n}\n\nfunction newTunnelConnectionId(): string {\n let random = 0n;\n for (const byte of randomBytes(10)) {\n random = (random << 8n) | BigInt(byte);\n }\n return `${encodeBase32(BigInt(Date.now()), 10)}${encodeBase32(random, 16)}`;\n}\n\nfunction formatIdentity(workerId: string, connectionId: string): string {\n return `worker_id=${workerId} connection_id=${connectionId}`;\n}\n\nfunction targetLabel(target: Target): string {\n return `${target.host}:${target.port}`;\n}\n\nfunction formatConnectionOutcome(outcome: ConnectionOutcome): string {\n switch (outcome.kind) {\n case \"served\":\n return `served uptimeMs=${outcome.uptimeMs}`;\n case \"drained\":\n return `drained uptimeMs=${outcome.uptimeMs}`;\n case \"retryable\":\n return `retryable reason=${outcome.reason}`;\n case \"fatal\":\n return `fatal reason=${outcome.reason}`;\n }\n}\n\nfunction formatSettings(settings: http2.Settings): string {\n const entries = Object.entries(settings).filter(\n ([, value]) => value !== undefined\n );\n if (entries.length === 0) return \"{}\";\n return `{${entries\n .map(([key, value]) => `${key}=${String(value)}`)\n .join(\", \")}}`;\n}\n\nfunction pathWithoutQuery(url: string | undefined): string {\n if (url === undefined) return \"?\";\n const queryStart = url.indexOf(\"?\");\n return queryStart === -1 ? url : url.slice(0, queryStart);\n}\n\nfunction endInternalError(res: http2.Http2ServerResponse): void {\n try {\n if (!res.headersSent) res.writeHead(500);\n if (!res.writableEnded) res.end(\"tunnel: SDK handler error\");\n } catch {\n // The stream may already be closing; keep the session lifecycle contained.\n }\n}\n\n/** The Node request handler produced by the SDK's createEndpointHandler. */\ntype SdkHandler = ReturnType<\n typeof import(\"@restatedev/restate-sdk\").createEndpointHandler\n>;\n\n/**\n * The engine's handle on a live connection: lets `shutdown()` ask each one to\n * begin and finish a client-initiated drain.\n */\nexport interface DrainableConnection {\n beginClientDrain(): void;\n finishClientDrain(opts: { force: boolean }): Promise<void>;\n}\n\nexport interface ConnectionIdentity {\n workerId: string;\n connectionId: string;\n target: string;\n}\n\n/** What a connection attempt needs from the engine. */\nexport interface ConnectionDeps {\n opts: ResolvedOptions;\n /** Built once by the engine; stateless per call, shared across streams. */\n sdkHandler: SdkHandler;\n /** Takes ownership of a detached session on a server-drain handover. */\n draining: DrainingRegistry;\n /** Engine-level socket registry, so close() can destroy in-flight dials. */\n activeSockets: Set<net.Socket>;\n /** Live connections the engine can ask to drain on shutdown(). */\n activeConnections: Set<DrainableConnection>;\n /** Called once per successful handshake (count, learned info, ready). */\n onEstablished: (info: HandshakeInfo, identity: ConnectionIdentity) => void;\n /**\n * True once the engine is gracefully shutting down: new forwarded\n * invocations are refused with the drain sentinel instead of dispatched, so\n * the cloud deselects this connection while in-flight invocations finish.\n * Engine-wide (every connection refuses), so it is checked in addition to\n * this connection's own `draining{client}` state.\n */\n isShuttingDown: () => boolean;\n /** True once the startup readiness gate has passed. */\n isStartupReady: () => boolean;\n /** A forwarded invocation began executing (counts toward the drain wait). */\n inflightStarted: () => void;\n /** A forwarded invocation finished (its stream closed). */\n inflightEnded: () => void;\n}\n\n/**\n * The connection's lifecycle as one explicit value. Each phase carries exactly\n * the resources it owns, so a method cannot touch a resource that the current\n * phase has no business with:\n *\n * connecting — dialing the socket + TLS; no h2 session yet.\n * handshaking — session is up; `firstRequestTimer` bounds the wait for the\n * cloud to open /_/start-tunnel; `handshake` is set once it\n * does (gate streams park on it until it resolves).\n * serving — handshake ok'd; forwarding invocations to the SDK, with the\n * liveness `watchdog` running.\n * draining — winding down; `trigger` records who asked. A `client` drain\n * refuses new invocations; a `server` drain keeps serving its\n * detached session until the registry closes it.\n * closed — terminal; the session/socket are gone.\n */\ntype AttemptState =\n | { readonly kind: \"connecting\" }\n | {\n readonly kind: \"handshaking\";\n readonly session: http2.Http2Session;\n readonly firstRequestTimer: NodeJS.Timeout;\n handshake: Promise<{ ok: boolean }> | undefined;\n }\n | {\n readonly kind: \"serving\";\n readonly session: http2.Http2Session;\n readonly openedAt: number;\n readonly watchdog: Watchdog;\n }\n | {\n readonly kind: \"draining\";\n readonly session: http2.Http2Session;\n readonly openedAt: number;\n readonly trigger: DrainTrigger;\n readonly watchdog: Watchdog;\n }\n | { readonly kind: \"closed\" };\n\n/** A request classified by its (control or forwarded) intent — pure routing. */\ntype TunnelRequest =\n | { kind: \"start-tunnel\" } // the cloud opening the handshake stream\n | { kind: \"health\" } // GET /_/health liveness probe\n | { kind: \"drain\" } // /_/drain-tunnel: the cloud asks us to rotate\n | { kind: \"forwarded\" }; // anything else: a forwarded invocation\n\n/** Classify an incoming h2 request. Control paths are cloud-originated and\n * arrive UNPREFIXED (before any destination-prefix stripping). */\nfunction classifyRequest(req: http2.Http2ServerRequest): TunnelRequest {\n const rawPath = (req.url ?? \"\").split(\"?\")[0];\n if (req.method === \"GET\" && rawPath === START_TUNNEL_PATH) {\n return { kind: \"start-tunnel\" };\n }\n if (rawPath === \"/_/health\") return { kind: \"health\" };\n if (rawPath === \"/_/drain-tunnel\") return { kind: \"drain\" };\n return { kind: \"forwarded\" };\n}\n\n/** Resolves a connection attempt's outcome exactly once. */\nclass Completion {\n private done = false;\n private resolveFn!: (outcome: ConnectionOutcome) => void;\n readonly promise: Promise<ConnectionOutcome> = new Promise((resolve) => {\n this.resolveFn = resolve;\n });\n\n get settled(): boolean {\n return this.done;\n }\n\n /** Resolve once; returns false if it was already resolved. */\n resolve(outcome: ConnectionOutcome): boolean {\n if (this.done) return false;\n this.done = true;\n this.resolveFn(outcome);\n return true;\n }\n}\n\n/**\n * Liveness watchdog: periodic h2 PING; `pingMaxMissed` consecutive misses mean\n * the connection is half-open (the OS may never surface it), so `onDead` fires.\n * Owns its own timer/miss state so the connection doesn't have to.\n */\nclass Watchdog {\n private interval: NodeJS.Timeout | undefined;\n private missed = 0;\n\n constructor(\n private readonly session: http2.Http2Session,\n private readonly opts: ResolvedOptions,\n private readonly onDead: () => void\n ) {}\n\n start(): void {\n this.interval = setInterval(() => this.beat(), this.opts.pingIntervalMs);\n this.interval.unref();\n }\n\n stop(): void {\n if (this.interval !== undefined) clearInterval(this.interval);\n }\n\n private beat(): void {\n if (this.session.destroyed) return;\n let acked = false;\n try {\n this.session.ping((err) => {\n if (err === null) {\n acked = true;\n this.missed = 0;\n }\n });\n } catch {\n return;\n }\n const t = setTimeout(() => {\n if (acked || this.session.destroyed) return;\n this.missed++;\n if (this.missed >= this.opts.pingMaxMissed) this.onDead();\n }, this.opts.pingTimeoutMs);\n t.unref();\n }\n}\n\n/** Connect result: a connected, ALPN-verified socket, or a terminal outcome. */\ntype DialResult =\n | { ok: true; socket: net.Socket }\n | { ok: false; outcome: ConnectionOutcome };\n\n/**\n * The dial stage: connect TCP (+ TLS), bounded by `connectTimeoutMs` and the\n * slot abort, and require ALPN to have negotiated h2. Owns the socket until it\n * either hands it back connected or destroys it on failure — so all of the\n * connect-phase timer/listener state stays local here.\n */\nfunction dial(\n target: Target,\n deps: ConnectionDeps,\n plaintext: boolean,\n signal: AbortSignal,\n connectionId: string\n): Promise<DialResult> {\n const log = deps.opts.logger;\n const identity = formatIdentity(deps.opts.tunnelWorkerId, connectionId);\n const tlsOptions = plaintext\n ? undefined\n : buildTlsConnectOptions(deps.opts.tls, target.servername);\n const socket = plaintext\n ? net.connect({ host: target.host, port: target.port })\n : tls.connect({ host: target.host, port: target.port, ...tlsOptions });\n\n return new Promise<DialResult>((resolve) => {\n let done = false;\n const label = targetLabel(target);\n const onError = (err: Error) => fail(`socket error: ${err.message}`);\n const onAbort = () => fail(\"tunnel closed\");\n const timer = setTimeout(\n () => fail(`connect timeout after ${deps.opts.connectTimeoutMs}ms`),\n deps.opts.connectTimeoutMs\n );\n timer.unref();\n\n const cleanup = () => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", onAbort);\n socket.removeListener(\"error\", onError);\n };\n function fail(reason: string) {\n if (done) return;\n done = true;\n cleanup();\n log(`tunnel: failed to connect to ${label}: ${reason} (${identity})`);\n socket.destroy();\n resolve({ ok: false, outcome: { kind: \"retryable\", reason } });\n }\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n socket.on(\"error\", onError);\n socket.once(plaintext ? \"connect\" : \"secureConnect\", () => {\n if (done) return;\n socket.setNoDelay(true);\n const alpn = plaintext\n ? \"plaintext\"\n : `tls alpn=${JSON.stringify((socket as tls.TLSSocket).alpnProtocol)}`;\n // Node's http2 requires ALPN to have negotiated h2 before it will run a\n // server session over a TLS socket. A server that doesn't negotiate is\n // too old for this client (see the README's server-version note).\n if (!plaintext && (socket as tls.TLSSocket).alpnProtocol !== \"h2\") {\n fail(\n \"tunnel server did not negotiate h2 ALPN — it predates standard-h2 control traffic and cannot serve this client\"\n );\n return;\n }\n done = true;\n cleanup();\n log(`tunnel: connected socket to ${label} (${alpn}, ${identity})`);\n resolve({ ok: true, socket });\n });\n });\n}\n\n/**\n * Run one connection attempt. Resolves (never rejects) with the outcome\n * when the connection ends; `slotSignal` aborts the attempt at any phase.\n */\nexport function runConnection(\n target: Target,\n slotSignal: AbortSignal,\n deps: ConnectionDeps\n): Promise<ConnectionOutcome> {\n // Resolved once per attempt, before dialing: a file-sourced token is\n // re-read on every redial so rotations are picked up, and a read failure\n // (e.g. mid-rotation) is a retryable outcome rather than a crash.\n let authToken: string;\n try {\n authToken = deps.opts.authToken();\n } catch (err) {\n return Promise.resolve({\n kind: \"retryable\",\n reason: `auth token unavailable: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n return new ConnectionAttempt(target, slotSignal, deps, authToken).run();\n}\n\nclass ConnectionAttempt implements DrainableConnection {\n private state: AttemptState = { kind: \"connecting\" };\n private readonly completion = new Completion();\n /** Lifetime-scoped: destroyed on teardown in any phase, handed to the\n * registry on a server drain. */\n private socket: net.Socket | undefined;\n\n private readonly plaintext: boolean;\n private readonly log: (message: string) => void;\n private readonly connectionId = newTunnelConnectionId();\n\n constructor(\n private readonly target: Target,\n private readonly slotSignal: AbortSignal,\n private readonly deps: ConnectionDeps,\n private readonly authToken: string\n ) {\n this.log = deps.opts.logger;\n this.plaintext = target.plaintext ?? deps.opts.tls === false;\n }\n\n private get identity(): ConnectionIdentity {\n return {\n workerId: this.deps.opts.tunnelWorkerId,\n connectionId: this.connectionId,\n target: targetLabel(this.target),\n };\n }\n\n private identityLog(): string {\n return formatIdentity(this.deps.opts.tunnelWorkerId, this.connectionId);\n }\n\n private logWithIdentity(message: string): void {\n this.log(`${message} (${this.identityLog()})`);\n }\n\n run(): Promise<ConnectionOutcome> {\n this.deps.activeConnections.add(this);\n void this.drive();\n return this.completion.promise;\n }\n\n /** Stage pipeline: dial → establish (role-flip). The remaining stages\n * (handshake, serve) are event-driven from the h2 server's request handler. */\n private async drive(): Promise<void> {\n const dialed = await dial(\n this.target,\n this.deps,\n this.plaintext,\n this.slotSignal,\n this.connectionId\n );\n // A client-drain (shutdown) or abort may have settled us mid-dial.\n if (this.completion.settled) {\n if (dialed.ok) dialed.socket.destroy();\n this.deps.activeConnections.delete(this);\n return;\n }\n if (!dialed.ok) {\n this.settle(dialed.outcome);\n return;\n }\n this.socket = dialed.socket;\n this.deps.activeSockets.add(dialed.socket);\n this.slotSignal.addEventListener(\"abort\", this.onAbort, { once: true });\n dialed.socket.on(\"error\", (err: Error) =>\n this.settle(\n this.endOutcome(`socket error: ${err.message}`),\n `socket error: ${err.message}`\n )\n );\n dialed.socket.on(\"close\", () =>\n this.settle(\n this.endOutcome(\"connection closed before handshake completed\"),\n \"socket closed\"\n )\n );\n this.establish(dialed.socket);\n }\n\n // ---- state helpers ----\n\n private get closed(): boolean {\n return this.state.kind === \"closed\";\n }\n\n private session(): http2.Http2Session | undefined {\n const s = this.state;\n return s.kind === \"handshaking\" ||\n s.kind === \"serving\" ||\n s.kind === \"draining\"\n ? s.session\n : undefined;\n }\n\n private get openedAt(): number | undefined {\n const s = this.state;\n return s.kind === \"serving\" || s.kind === \"draining\"\n ? s.openedAt\n : undefined;\n }\n\n private uptimeMs(): number {\n return this.openedAt === undefined ? 0 : Date.now() - this.openedAt;\n }\n\n /** The end-of-connection outcome: \"served\" once established, else retryable. */\n private endOutcome(reason: string): ConnectionOutcome {\n return this.openedAt !== undefined\n ? { kind: \"served\", uptimeMs: this.uptimeMs() }\n : { kind: \"retryable\", reason };\n }\n\n // ---- teardown ----\n\n private readonly onAbort = () =>\n this.settle({ kind: \"retryable\", reason: \"tunnel closed\" });\n\n /** Stop the timers/monitors owned by the current phase. */\n private stopPhaseResources(): void {\n const s = this.state;\n if (s.kind === \"handshaking\") clearTimeout(s.firstRequestTimer);\n else if (s.kind === \"serving\" || s.kind === \"draining\") s.watchdog.stop();\n }\n\n /** Detach from the engine registries and the slot-abort listener. */\n private deregister(): void {\n this.slotSignal.removeEventListener(\"abort\", this.onAbort);\n this.deps.activeConnections.delete(this);\n if (this.socket !== undefined) this.deps.activeSockets.delete(this.socket);\n }\n\n /**\n * The single terminal path — resolve the outcome (once), destroy the\n * session/socket, and move to `closed`. Idempotent. A server drain does NOT\n * funnel through here for teardown: it detaches via {@link beginServerDrain}\n * and lets the DrainingRegistry destroy the session later.\n */\n private settle(outcome: ConnectionOutcome, detail?: string): void {\n if (this.closed) return;\n const phase = this.state.kind;\n const session = this.session();\n this.stopPhaseResources();\n this.deregister();\n this.state = { kind: \"closed\" };\n session?.destroy();\n this.socket?.destroy();\n const firstOutcome = this.completion.resolve(outcome);\n this.logWithIdentity(\n `tunnel: connection to ${targetLabel(this.target)} closed (phase=${phase}, outcome=${formatConnectionOutcome(outcome)}${\n detail === undefined ? \"\" : `, detail=${detail}`\n }${firstOutcome ? \"\" : \", already reported\"})`\n );\n }\n\n private closeSessionGracefully(session: http2.Http2Session): Promise<void> {\n return new Promise((resolve) => {\n if (session.closed || session.destroyed) {\n resolve();\n return;\n }\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n this.settle(\n { kind: \"served\", uptimeMs: this.uptimeMs() },\n \"session.close() timed out\"\n );\n finish();\n }, CLIENT_DRAIN_SESSION_CLOSE_TIMEOUT_MS);\n timer.unref();\n session.once(\"close\", finish);\n try {\n session.close();\n } catch {\n this.settle(\n this.endOutcome(\"session close failed\"),\n \"session close failed\"\n );\n finish();\n }\n });\n }\n\n private sendClientDrainGoaway(session: http2.Http2Session): void {\n if (session.closed || session.destroyed) return;\n try {\n session.goaway(http2.constants.NGHTTP2_NO_ERROR);\n this.logWithIdentity(\n `tunnel: sent client-drain GOAWAY to ${targetLabel(this.target)}`\n );\n } catch (err) {\n this.logWithIdentity(\n `tunnel: failed to send client-drain GOAWAY to ${targetLabel(this.target)}: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n // The session may be closing under us. dispatchForwarded still refuses\n // any raced streams once the state flips to client-draining.\n }\n }\n\n // ---- establish: role-flip ----\n\n private establish(socket: net.Socket): void {\n this.logWithIdentity(\n `tunnel: connected to ${this.target.host}:${this.target.port}, starting handshake`\n );\n\n const h2 = http2.createServer(\n {\n maxSessionMemory: this.deps.opts.maxSessionMemory,\n settings: {\n // TODO why not allow to configure the other h2 options?\n maxConcurrentStreams: this.deps.opts.maxConcurrentStreams,\n initialWindowSize: 1024 * 1024,\n maxFrameSize: 65536,\n },\n },\n (req, res) => this.handleRequest(req, res)\n );\n\n h2.on(\"session\", (s) => {\n this.logWithIdentity(\n `tunnel: h2 session established to ${targetLabel(this.target)} (localSettings=${formatSettings(\n s.localSettings\n )}, remoteSettings=${formatSettings(s.remoteSettings)})`\n );\n s.on(\"localSettings\", (settings: http2.Settings) =>\n this.logWithIdentity(\n `tunnel: h2 local settings acknowledged by ${targetLabel(this.target)}: ${formatSettings(settings)}`\n )\n );\n s.on(\"remoteSettings\", (settings: http2.Settings) =>\n this.logWithIdentity(\n `tunnel: h2 remote settings from ${targetLabel(this.target)}: ${formatSettings(settings)}`\n )\n );\n // Role-flip complete: the cloud is now our h2 client. connecting →\n // handshaking, arming the timer that fires if the server never opens\n // /_/start-tunnel.\n if (this.state.kind === \"connecting\") {\n const firstRequestTimer = setTimeout(() => {\n if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake === undefined\n ) {\n this.settle({\n kind: \"retryable\",\n reason: \"server never initiated /_/start-tunnel\",\n });\n }\n }, this.deps.opts.handshakeTimeoutMs);\n firstRequestTimer.unref();\n this.state = {\n kind: \"handshaking\",\n session: s,\n firstRequestTimer,\n handshake: undefined,\n };\n }\n try {\n // Raise the per-connection flow-control window (Node defaults to\n // 64 KiB, throttling aggregate throughput across streams).\n (\n s as unknown as { setLocalWindowSize?: (n: number) => void }\n ).setLocalWindowSize?.(this.deps.opts.connectionWindowSize);\n } catch {\n // Older Node — per-stream windows still apply.\n }\n s.on(\"close\", () =>\n this.settle(\n this.endOutcome(\"session closed before handshake completed\"),\n \"session closed\"\n )\n );\n s.on(\"error\", (err: Error) =>\n this.settle(\n this.endOutcome(`session error: ${err.message}`),\n `session error: ${err.message}`\n )\n );\n });\n h2.on(\"sessionError\", (err: Error) =>\n this.settle(\n this.endOutcome(`session error: ${err.message}`),\n `session error: ${err.message}`\n )\n );\n\n h2.emit(\"connection\", socket);\n }\n\n // ---- request routing ----\n\n private handleRequest(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n switch (classifyRequest(req).kind) {\n case \"health\":\n res.writeHead(200);\n res.end();\n return;\n case \"drain\":\n this.handleDrainRequest(res);\n return;\n case \"start-tunnel\":\n if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake === undefined\n ) {\n this.startHandshake(req, res);\n } else {\n this.notReady(res);\n }\n return;\n case \"forwarded\":\n this.handleForwarded(req, res);\n return;\n }\n }\n\n private handleForwarded(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n // Serving, or draining (a server-drained session keeps serving its\n // in-flight; dispatchForwarded refuses only a client drain / shutdown).\n if (this.state.kind === \"serving\" || this.state.kind === \"draining\") {\n this.dispatchForwarded(req, res);\n return;\n }\n // A stream that raced the handshake parks on its outcome (the cloud fires\n // work the instant the tunnel registers, coalescing it with the\n // ok-trailers) rather than being rejected.\n if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake !== undefined\n ) {\n const handshake = this.state.handshake;\n void handshake.then(({ ok }) => {\n if (this.closed || res.stream.destroyed) return;\n try {\n if (ok) this.dispatchForwarded(req, res);\n else {\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} before tunnel handshake completed`\n );\n this.notReady(res);\n }\n } catch {\n // The session may be tearing down under us.\n }\n });\n return;\n }\n // Before /_/start-tunnel was even opened (or already gone) — not a tunnel\n // server speaking the protocol.\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} before tunnel handshake completed`\n );\n this.notReady(res);\n }\n\n private notReady(res: http2.Http2ServerResponse): void {\n res.writeHead(503, { [TUNNEL_DRAINING_HEADER]: \"true\" });\n res.end(\"tunnel: not ready\");\n }\n\n /** First stream: run the handshake; its outcome opens the gate. */\n private startHandshake(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n if (this.state.kind !== \"handshaking\") return;\n clearTimeout(this.state.firstRequestTimer);\n this.state.handshake = performHandshake(\n req,\n res,\n {\n authToken: this.authToken,\n environmentId: this.deps.opts.environmentId,\n tunnelName: this.deps.opts.tunnelName,\n tunnelWorkerId: this.deps.opts.tunnelWorkerId,\n tunnelConnectionId: this.connectionId,\n supportsDrain: this.deps.opts.supportsDrain,\n supportsClientDrain: this.deps.opts.supportsClientDrain,\n },\n this.deps.opts.handshakeTimeoutMs\n ).then((outcome) => {\n // settle (session/socket error) may have raced us to `closed`.\n if (this.state.kind !== \"handshaking\") return { ok: false };\n if (outcome.kind === \"ok\") {\n const { session } = this.state;\n const openedAt = Date.now();\n const watchdog = this.startWatchdog(session);\n // If our own shutdown began while we were handshaking, open straight\n // into a client drain so this connection refuses work from the start.\n this.state = this.deps.isShuttingDown()\n ? { kind: \"draining\", session, openedAt, trigger: \"client\", watchdog }\n : { kind: \"serving\", session, openedAt, watchdog };\n this.logWithIdentity(\n `tunnel: established (name=${outcome.info.tunnelName}, proxy=${outcome.info.proxyUrl})`\n );\n this.deps.onEstablished(outcome.info, this.identity);\n return { ok: true };\n }\n this.settle(outcome);\n return { ok: false };\n });\n }\n\n /** Strip the destination prefix and hand the stream to the SDK. */\n private dispatchForwarded(\n req: http2.Http2ServerRequest,\n res: http2.Http2ServerResponse\n ): void {\n // Client-initiated drain (this connection, or an engine-wide shutdown):\n // refuse new invocations WITHOUT running the handler. The sentinel tells\n // the server to stop routing here; failing the request (rather than\n // running it) lets the runtime retry it on a healthy connection with no\n // risk of double-execution. A *server* drain does not refuse — its\n // detached session keeps serving (the zero-drop property).\n const clientDraining =\n this.state.kind === \"draining\" && this.state.trigger === \"client\";\n if (!this.deps.isStartupReady()) {\n // Defensive fallback: the supervisor gates dialing until startupReady\n // passes, so normal protocol traffic should not reach this state.\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} before startup readiness gate completed`\n );\n this.notReady(res);\n return;\n }\n if (clientDraining || this.deps.isShuttingDown()) {\n this.logWithIdentity(\n `tunnel: refused forwarded stream ${res.stream.id ?? \"?\"} during client drain`\n );\n res.writeHead(503, { [TUNNEL_DRAINING_HEADER]: \"true\" });\n res.end();\n return;\n }\n const tail = forwardedTail(req.url ?? \"\");\n if (tail === null) {\n res.writeHead(400);\n res.end(\"tunnel: malformed forwarded path\");\n return;\n }\n req.url = tail;\n // Count this invocation as in-flight so shutdown() waits for it to finish.\n this.deps.inflightStarted();\n res.stream.once(\"close\", () => this.deps.inflightEnded());\n res.stream.once(\"error\", (err: Error) => {\n const streamId = res.stream.id ?? \"?\";\n const logPath = pathWithoutQuery(req.url);\n this.logWithIdentity(\n `tunnel: forwarded stream ${streamId} ${req.method ?? \"?\"} ${logPath} failed: ${\n err.message\n }`\n );\n });\n try {\n this.deps.sdkHandler(req, res);\n } catch (err) {\n const streamId = res.stream.id ?? \"?\";\n const logPath = pathWithoutQuery(req.url);\n this.logWithIdentity(\n `tunnel: SDK handler threw for forwarded stream ${streamId} ${\n req.method ?? \"?\"\n } ${logPath}: ${err instanceof Error ? err.message : String(err)}`\n );\n endInternalError(res);\n }\n }\n\n // ---- drain ----\n\n private handleDrainRequest(res: http2.Http2ServerResponse): void {\n res.writeHead(200);\n res.end();\n this.logWithIdentity(\n `tunnel: received server drain notification from ${targetLabel(this.target)}`\n );\n if (!this.deps.opts.supportsDrain) {\n // Not advertised, so unexpected — acknowledge and let the server\n // close on us; the slot's redial loop re-establishes.\n this.logWithIdentity(\n \"tunnel: received /_/drain-tunnel (drain not advertised) — acknowledging\"\n );\n return;\n }\n if (this.state.kind === \"serving\") {\n this.beginServerDrain();\n } else if (\n this.state.kind === \"handshaking\" &&\n this.state.handshake !== undefined\n ) {\n // Drain coalesced with the ok-trailers (the server drains tunnels the\n // moment it shuts down, including ones it just registered): the same\n // gate race as forwarded streams — park the drain on the handshake\n // outcome instead of silently dropping it.\n void this.state.handshake.then(({ ok }) => {\n if (ok) this.beginServerDrain();\n });\n }\n // Before /_/start-tunnel was even opened: not a tunnel server\n // speaking the protocol — ack-and-ignore.\n }\n\n /**\n * Server-initiated drain: detach the still-serving session to the\n * DrainingRegistry and resolve `run()` so the slot dials a replacement. The\n * detached session keeps serving its in-flight invocations under the\n * registry's grace window — `run()` resolves now, the session closes later\n * (its `close` handler then runs `settle()` → `closed`, a no-op resolve).\n */\n private beginServerDrain(): void {\n if (this.state.kind !== \"serving\") return;\n const { session, openedAt, watchdog } = this.state;\n this.logWithIdentity(\n \"tunnel: server drain notification accepted — opening a replacement connection\"\n );\n watchdog.stop(); // the registry owns the session now; stop pinging it\n this.deregister();\n this.deps.draining.add(session, this.socket!, this.deps.opts.drainGraceMs);\n this.state = {\n kind: \"draining\",\n session,\n openedAt,\n trigger: \"server\",\n watchdog,\n };\n this.completion.resolve({ kind: \"drained\", uptimeMs: this.uptimeMs() });\n }\n\n /**\n * Client-initiated drain: the engine is shutting this process down. Refuse\n * new invocations and finish in-flight IN PLACE — no redial. The engine\n * waits for in-flight to drain, then tears the connection down.\n */\n beginClientDrain(): void {\n const s = this.state;\n switch (s.kind) {\n case \"serving\":\n this.state = {\n kind: \"draining\",\n session: s.session,\n openedAt: s.openedAt,\n trigger: \"client\",\n watchdog: s.watchdog,\n };\n this.sendClientDrainGoaway(s.session);\n return;\n case \"connecting\":\n case \"handshaking\":\n // No serving session yet — nothing in-flight to protect; abort.\n this.settle({ kind: \"retryable\", reason: \"shutting down\" });\n return;\n case \"draining\":\n case \"closed\":\n return; // already winding down\n }\n }\n\n async finishClientDrain(opts: { force: boolean }): Promise<void> {\n const s = this.state;\n if (s.kind !== \"draining\" || s.trigger !== \"client\") return;\n if (opts.force) {\n this.settle(\n { kind: \"served\", uptimeMs: this.uptimeMs() },\n \"client drain grace expired\"\n );\n return;\n }\n await this.closeSessionGracefully(s.session);\n }\n\n // ---- liveness ----\n\n private startWatchdog(session: http2.Http2Session): Watchdog {\n const watchdog = new Watchdog(session, this.deps.opts, () => {\n this.logWithIdentity(\"tunnel: pings missed — reconnecting\");\n this.settle(\n { kind: \"served\", uptimeMs: this.uptimeMs() },\n \"ping watchdog missed too many acknowledgements\"\n );\n });\n watchdog.start();\n return watchdog;\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Reconnect backoff policy.\n\n/**\n * Backoff resets only when a served connection stayed up at least this long\n * (mirrors the Rust client's 5s \"opened\" guard). Without it, a server that\n * authorizes the handshake but immediately drops the connection would be\n * redialed at the backoff floor forever — a full TLS+h2+auth round trip\n * every ~10ms.\n */\nexport const MIN_UPTIME_FOR_BACKOFF_RESET_MS = 5_000;\n\n/**\n * Jittered exponential backoff: each `next()` returns the current delay\n * with ±50% jitter and advances the schedule toward `maxMs`; `reset()`\n * returns to the floor. Jitter keeps multi-homed slots from redialing in\n * lockstep after a fleet-wide blip (thundering herd).\n */\nexport class Backoff {\n private currentMs: number;\n\n constructor(\n private readonly initialMs: number,\n private readonly factor: number,\n private readonly maxMs: number\n ) {\n this.currentMs = initialMs;\n }\n\n next(): number {\n const d = this.currentMs;\n this.currentMs = Math.min(this.currentMs * this.factor, this.maxMs);\n return d * (0.5 + Math.random());\n }\n\n reset(): void {\n this.currentMs = this.initialMs;\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// Small abort-aware async utilities shared by the engine.\n\n/** Sleep that wakes early (resolving) when the signal aborts. */\nexport function delay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const t = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n resolve();\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Race a promise against a signal: resolves `null` the moment the signal\n * aborts, otherwise passes the promise's result through (rejections\n * propagate). The abort listener is removed when the race settles, so\n * repeated calls against a long-lived signal don't accumulate listeners.\n */\nexport async function raceAbortable<T>(\n promise: Promise<T>,\n signal: AbortSignal\n): Promise<T | null> {\n if (signal.aborted) return null;\n let onAbort!: () => void;\n const aborted = new Promise<null>((resolve) => {\n onAbort = () => resolve(null);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n try {\n return await Promise.race([promise, aborted]);\n } finally {\n signal.removeEventListener(\"abort\", onAbort);\n }\n}\n\n// TODO replace with Promise.withResolvers\n/** A promise whose resolve/reject are exposed and fire at most once. */\nexport class Deferred<T> {\n private settled = false;\n readonly promise: Promise<T>;\n private resolveFn!: (value: T) => void;\n private rejectFn!: (err: Error) => void;\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this.resolveFn = resolve;\n this.rejectFn = reject;\n });\n }\n\n resolve(value: T): void {\n if (this.settled) return;\n this.settled = true;\n this.resolveFn(value);\n }\n\n reject(err: Error): void {\n if (this.settled) return;\n this.settled = true;\n this.rejectFn(err);\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The slot supervisor.\n// =============================================================================\n//\n// Multi-homing — one tunnel connection per resolved tunnel server (like the\n// Rust client; the slot set IS the resolved set, it is not configurable). The\n// supervisor resolves the server set, reconciles the slot map against it\n// (starting connections to servers that appear, tearing down ones that\n// vanish), and re-resolves every `resolveIntervalMs` for SRV discovery. Each\n// slot runs its own reconnect loop with fatal-vs-retryable classification.\n//\n// Invariants:\n// E1. A FATAL outcome (unauthorized / bad-tunnel-name / name mismatch) on\n// ANY slot stops the WHOLE tunnel — the credentials are shared, so every\n// other slot would hit the same wall. It aborts every slot and reports\n// via `hooks.onFatal`; the engine surfaces it on `error`/`ready`.\n// E2. Backoff resets only after a connection held for\n// MIN_UPTIME_FOR_BACKOFF_RESET_MS; a drain only skips the backoff sleep\n// under the same guard (drain-spam must compound).\n// E3. Teardown is prompt: `abortAll()` aborts in-flight dials via per-slot\n// signals and wakes the resolve loop out of any sleep; the (un-abortable)\n// DNS work is raced against the wake signal, never awaited.\n\nimport type { ResolvedOptions } from \"./options.js\";\nimport { resolveTargets, targetKey, type Target } from \"./targets.js\";\nimport { runConnection, type ConnectionDeps } from \"./connection.js\";\nimport { Backoff, MIN_UPTIME_FOR_BACKOFF_RESET_MS } from \"./backoff.js\";\nimport { delay, raceAbortable } from \"./util.js\";\n\n/** A running per-server connection loop. */\ninterface Slot {\n ctl: AbortController;\n done: Promise<void>;\n}\n\nfunction formatTargetList(keys: string[]): string {\n return keys.length === 0 ? \"<none>\" : keys.join(\", \");\n}\n\nexport interface SupervisorHooks {\n /** A slot hit a non-retryable failure; the whole tunnel must stop (E1). */\n onFatal: (err: Error) => void;\n}\n\nexport class Supervisor {\n private readonly slots = new Map<string, Slot>();\n /** Aborting this cascades to every slot (each slot chains its ctl to it). */\n private readonly stopSignal = new AbortController();\n /** Wakes the resolve loop out of a sleep / DNS race (E3). */\n private readonly wake = new AbortController();\n private stopping = false;\n private fatal: Error | undefined;\n private lastResolvedKeys: string[] | undefined;\n\n /** Resolves when the resolve loop has exited AND every slot has settled. */\n readonly done: Promise<void>;\n\n constructor(\n private readonly opts: ResolvedOptions,\n private readonly deps: ConnectionDeps,\n private readonly hooks: SupervisorHooks,\n private readonly log: (message: string) => void\n ) {\n this.stopSignal.signal.addEventListener(\"abort\", () => this.wake.abort(), {\n once: true,\n });\n this.done = this.supervise();\n }\n\n get fatalError(): Error | undefined {\n return this.fatal;\n }\n\n /**\n * Stop starting/resolving new connections; existing slots keep running so\n * their connections can finish draining in place (client-initiated drain).\n */\n stopResolving(): void {\n this.stopping = true;\n this.log(\"tunnel: supervisor stopping target resolution\");\n this.wake.abort();\n }\n\n /** Abort every slot and the resolve loop (engine teardown). */\n abortAll(): void {\n this.stopping = true;\n this.log(\"tunnel: supervisor aborting all connections\");\n this.stopSignal.abort();\n }\n\n private startSlot(key: string, target: Target): void {\n const ctl = new AbortController();\n // Chain to the global stop so abortAll() cascades; self-detaching.\n this.stopSignal.signal.addEventListener(\"abort\", () => ctl.abort(), {\n once: true,\n signal: ctl.signal,\n });\n const slot: Slot = { ctl, done: Promise.resolve() };\n slot.done = this.runSlot(target, ctl).finally(() => {\n // Guarded: this key may have vanished and re-appeared, in which case a\n // NEWER slot owns it — don't delete someone else's registration.\n if (this.slots.get(key) === slot) this.slots.delete(key);\n });\n this.slots.set(key, slot);\n }\n\n private async waitForStartupReady(): Promise<boolean> {\n if (this.opts.startupReady === undefined) return true;\n this.log(\n `tunnel: waiting for startup readiness gate (timeoutMs=${this.opts.startupReadyTimeoutMs})`\n );\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const ready = this.opts.startupReady();\n ready.catch(() => {}); // a late rejection after abort must not be unhandled\n const readyOrTimeout = Promise.race([\n ready,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n reject(\n new Error(\n `startup readiness gate timed out after ${this.opts.startupReadyTimeoutMs}ms`\n )\n );\n }, this.opts.startupReadyTimeoutMs);\n }),\n ]);\n readyOrTimeout.catch(() => {});\n const raced = await raceAbortable(readyOrTimeout, this.wake.signal);\n if (raced === null) return false;\n this.log(\"tunnel: startup readiness gate passed\");\n return true;\n } catch (err) {\n if (this.stopping || this.wake.signal.aborted) return false;\n const reason = err instanceof Error ? err.message : String(err);\n this.fatal = new Error(\n `tunnel: startup readiness gate failed: ${reason}`\n );\n this.log(\n `tunnel: FATAL — startup readiness gate failed: ${reason}; stopping all connections`\n );\n this.hooks.onFatal(this.fatal);\n this.stopSignal.abort();\n return false;\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n }\n }\n\n /** The per-server loop: dial → serve → classify outcome → backoff → redial. */\n private async runSlot(target: Target, ctl: AbortController): Promise<void> {\n const backoff = new Backoff(\n this.opts.reconnectInitialMs,\n this.opts.reconnectFactor,\n this.opts.reconnectMaxMs\n );\n\n while (!this.stopping && !ctl.signal.aborted && this.fatal === undefined) {\n const outcome = await runConnection(target, ctl.signal, this.deps);\n if (this.stopping || ctl.signal.aborted) break;\n if (outcome.kind === \"fatal\") {\n // E1: shared credentials — stop everything.\n this.fatal = new Error(`tunnel: ${outcome.reason}`);\n this.log(`tunnel: FATAL — ${outcome.reason}; stopping all connections`);\n this.hooks.onFatal(this.fatal);\n this.stopSignal.abort();\n break;\n }\n if (outcome.kind === \"served\" || outcome.kind === \"drained\") {\n // E2: only a connection that actually held resets the backoff.\n const heldLongEnough =\n outcome.uptimeMs >= MIN_UPTIME_FOR_BACKOFF_RESET_MS;\n if (heldLongEnough) backoff.reset();\n if (outcome.kind === \"drained\" && heldLongEnough) {\n // A stable connection was asked to rotate and the server is holding\n // the old one open for us — replace it NOW.\n this.log(\"tunnel: draining — reconnecting immediately\");\n continue;\n }\n this.log(\n outcome.kind === \"drained\"\n ? \"tunnel: drained shortly after connecting — reconnecting with backoff\"\n : \"tunnel: connection ended — reconnecting\"\n );\n } else {\n this.log(`tunnel: ${outcome.reason} — reconnecting`);\n }\n await delay(backoff.next(), ctl.signal);\n }\n }\n\n /** Resolve the server set, reconcile slots, repeat. For SRV discovery the\n * set is re-resolved every resolveIntervalMs; an explicit set is fixed. */\n private async supervise(): Promise<void> {\n if (!(await this.waitForStartupReady())) return;\n while (!this.stopping && this.fatal === undefined) {\n let targets: Target[];\n try {\n // E3: race the (un-abortable) DNS work against the wake signal so\n // teardown/fatal don't block on a slow resolver — a late result is\n // discarded by the stopping/fatal check below.\n const resolution = resolveTargets({ ...this.opts, logger: this.log });\n resolution.catch(() => {}); // a late rejection must not be unhandled\n const raced = await raceAbortable(resolution, this.wake.signal);\n if (raced === null) break; // woken: stopping or fatal\n targets = raced;\n } catch (err) {\n // Keep whatever slots exist serving; retry the resolution later\n // (the Rust client does the same on SRV failures).\n this.log(\n `tunnel: target resolution failed: ${err instanceof Error ? err.message : String(err)} — retrying`\n );\n await delay(\n Math.min(5_000, this.opts.resolveIntervalMs),\n this.wake.signal\n );\n continue;\n }\n if (this.stopping || this.fatal !== undefined) break;\n\n const desired = new Map(targets.map((t) => [targetKey(t), t] as const));\n const desiredKeys = [...desired.keys()].sort();\n if (\n this.lastResolvedKeys === undefined ||\n desiredKeys.length !== this.lastResolvedKeys.length ||\n desiredKeys.some((key, i) => key !== this.lastResolvedKeys![i])\n ) {\n const source =\n this.opts.srvName === undefined\n ? \"configured tunnel targets\"\n : `SRV ${this.opts.srvName}`;\n this.log(\n `tunnel: target set from ${source}: ${formatTargetList(desiredKeys)}`\n );\n if (this.lastResolvedKeys !== undefined) {\n const previous = new Set(this.lastResolvedKeys);\n const current = new Set(desiredKeys);\n const added = desiredKeys.filter((key) => !previous.has(key));\n const removed = this.lastResolvedKeys.filter(\n (key) => !current.has(key)\n );\n if (added.length > 0) {\n this.log(\n `tunnel: discovered new tunnel target(s): ${formatTargetList(added)}`\n );\n }\n if (removed.length > 0) {\n this.log(\n `tunnel: tunnel target(s) disappeared: ${formatTargetList(removed)}`\n );\n }\n }\n this.lastResolvedKeys = desiredKeys;\n }\n for (const [key, target] of desired) {\n if (!this.slots.has(key)) {\n this.log(`tunnel: starting connection to ${key}`);\n this.startSlot(key, target);\n }\n }\n for (const [key, slot] of this.slots) {\n if (!desired.has(key)) {\n this.log(`tunnel: ${key} no longer resolves — tearing down`);\n slot.ctl.abort();\n }\n }\n\n if (this.opts.srvName === undefined) break; // explicit servers: fixed set\n await delay(this.opts.resolveIntervalMs, this.wake.signal);\n }\n // Slots still in the map are live; evicted ones have already settled. No\n // slot can start after the loop exits (stopping/fatal both gate startSlot).\n await Promise.all([...this.slots.values()].map((s) => s.done));\n }\n}\n","/*\n * Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\n// The tunnel engine.\n// =============================================================================\n//\n// connectTunnel() serves a Restate SDK deployment over OUTBOUND connections to\n// Restate Cloud's tunnel servers — no inbound listener. The pieces:\n//\n// connection.ts — one dial → role-flip → handshake → serve cycle\n// supervisor.ts — the slot supervisor (one connection per resolved server)\n// handshake.ts — the /_/start-tunnel credentials/trailers exchange\n// forwarded.ts — the /<scheme>/<host>/<port> destination-prefix strip\n// targets.ts — server discovery (SRV per-IP expansion / explicit list)\n// draining.ts — server-drain handover ownership\n// backoff.ts — jittered exponential reconnect policy\n//\n// The engine has three kinds of state, kept deliberately separate:\n//\n// * Injected infrastructure — the registries (activeSockets/activeConnections/\n// draining/inflight) that ConnectionDeps hands to every connection. By\n// definition the per-connection layer reads these, so they are created once\n// and injected, not stored in a lifecycle phase.\n// * Observable output — connectionCount / lastInfo / fatalError, which the\n// handle exposes in every phase (including after close()), so they live in\n// one lifetime record rather than a phase.\n// * Lifecycle state — `EngineState`, a disjoint union whose live phases own\n// the supervisor + the event-loop anchor (and, while draining, the in-flight\n// drain promise). Each transition narrows the state and destructures what it\n// needs, so no function reaches for the live machinery in the wrong phase\n// (and `closed` provably has none).\n\nimport type * as net from \"node:net\";\nimport { createEndpointHandler } from \"@restatedev/restate-sdk\";\n\nimport type { ConnectTunnelOptions, TunnelConnection } from \"./types.js\";\nimport { resolveOptions } from \"./options.js\";\nimport type { HandshakeInfo } from \"./handshake.js\";\nimport {\n type ConnectionDeps,\n type ConnectionIdentity,\n type DrainableConnection,\n} from \"./connection.js\";\nimport { DrainingRegistry } from \"./draining.js\";\nimport { Supervisor } from \"./supervisor.js\";\nimport { Deferred } from \"./util.js\";\n\n/**\n * Counts forwarded invocations in flight and lets a graceful shutdown wait for\n * them to finish. Spans both actively-served and server-drained (detached)\n * sessions, since every dispatch increments and every stream close decrements\n * regardless of which session it belongs to.\n */\nclass InflightTracker {\n private count = 0;\n private notifyDrained: (() => void) | undefined;\n\n get inFlight(): number {\n return this.count;\n }\n\n started(): void {\n this.count++;\n }\n\n ended(): void {\n this.count = Math.max(0, this.count - 1);\n if (this.count === 0 && this.notifyDrained !== undefined) {\n const notify = this.notifyDrained;\n this.notifyDrained = undefined;\n notify();\n }\n }\n\n /** Resolve true once nothing is in flight, or false after `graceMs`.\n * Only one shutdown runs at a time, so a single waiter suffices. */\n whenDrained(graceMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n if (this.count === 0) {\n resolve(true);\n return;\n }\n const timer = setTimeout(() => {\n this.notifyDrained = undefined;\n resolve(false);\n }, graceMs);\n timer.unref();\n this.notifyDrained = () => {\n clearTimeout(timer);\n resolve(true);\n };\n });\n }\n}\n\n/** The engine's observable output — readable from the handle in every phase. */\ninterface Output {\n connectionCount: number;\n lastInfo: HandshakeInfo | undefined;\n fatalError: Error | undefined;\n}\n\n/** The live machinery, owned by the running/draining phases and gone in closed. */\ninterface Active {\n readonly supervisor: Supervisor;\n /** Anchors the event loop while live (a bare awaited promise won't keep Node\n * alive between a session closing and the next redial timer). */\n readonly keepAlive: NodeJS.Timeout;\n}\n\n/** The engine lifecycle as one disjoint state; live phases carry `Active`. */\ntype EngineState =\n | { readonly kind: \"running\"; readonly active: Active }\n | {\n readonly kind: \"draining\";\n readonly active: Active;\n readonly completed: Promise<void>;\n }\n | { readonly kind: \"closed\" };\n\ninterface SignalShutdownParticipant {\n shutdown(signal: NodeJS.Signals): Promise<void>;\n}\n\nconst signalShutdownRegistrations = new Map<\n NodeJS.Signals,\n Set<SignalShutdownParticipant>\n>();\nconst signalShutdownHandlers = new Map<NodeJS.Signals, () => void>();\nlet signalShutdownInProgress = false;\n\nasync function runGracefulShutdownSignal(\n signal: NodeJS.Signals\n): Promise<void> {\n if (signalShutdownInProgress) return;\n signalShutdownInProgress = true;\n const registrations = [...(signalShutdownRegistrations.get(signal) ?? [])];\n await Promise.allSettled(\n registrations.map((entry) => entry.shutdown(signal))\n );\n try {\n process.exit(0);\n } finally {\n // Tests stub process.exit(); real process.exit() does not return.\n signalShutdownInProgress = false;\n }\n}\n\nfunction registerGracefulShutdownSignals(\n signals: NodeJS.Signals[],\n participant: SignalShutdownParticipant\n): () => void {\n const unregisters = signals.map((signal) => {\n let registrations = signalShutdownRegistrations.get(signal);\n if (registrations === undefined) {\n registrations = new Set();\n signalShutdownRegistrations.set(signal, registrations);\n }\n registrations.add(participant);\n if (!signalShutdownHandlers.has(signal)) {\n const handler = () => void runGracefulShutdownSignal(signal);\n signalShutdownHandlers.set(signal, handler);\n process.once(signal, handler);\n }\n return () => {\n const current = signalShutdownRegistrations.get(signal);\n if (current === undefined) return;\n current.delete(participant);\n if (current.size > 0) return;\n const handler = signalShutdownHandlers.get(signal);\n if (handler !== undefined) process.removeListener(signal, handler);\n signalShutdownHandlers.delete(signal);\n signalShutdownRegistrations.delete(signal);\n };\n });\n return () => {\n for (const unregister of unregisters) unregister();\n };\n}\n\n/**\n * Connect this deployment to a Restate Cloud tunnel and serve `services`\n * over it. Returns immediately; connection management runs in the\n * background until `close()`/`shutdown()` (or the `signal`) stops it. See\n * {@link TunnelConnection.ready} to await the first successful handshake.\n */\nexport function connectTunnel(options: ConnectTunnelOptions): TunnelConnection {\n // Resolve options, eventually picking them up from env.\n const resolvedOptions = resolveOptions(options);\n\n // Built once, shared across connections and streams (it is stateless per\n // call). identityKeys delegates per-request JWT verification to the SDK —\n // it checks `aud` against the post-strip `req.url` pathname.\n const sdkHandler = createEndpointHandler({\n services: options.services,\n bidirectional: resolvedOptions.bidirectional,\n // TODO identityKeys and signingPublicKey are the very same field.\n // This can be aligned into the same field (and could be nice also for the regular SDK to read the key from env).\n identityKeys: [resolvedOptions.signingPublicKey],\n defaultServiceOptions: options.defaultServiceOptions,\n logger: options.logger,\n journalValueCodecProvider: options.journalValueCodecProvider,\n });\n\n // Logger used for debugging the tunnel\n const log = resolvedOptions.logger;\n const logWithWorker = (message: string) =>\n log(`${message} (worker_id=${resolvedOptions.tunnelWorkerId})`);\n let startupGatePassed = resolvedOptions.startupReady === undefined;\n const opts = {\n ...resolvedOptions,\n startupReady:\n resolvedOptions.startupReady === undefined\n ? undefined\n : async () => {\n await resolvedOptions.startupReady!();\n startupGatePassed = true;\n },\n };\n\n // Injected infrastructure: the per-connection layer reads these via deps.\n const activeSockets = new Set<net.Socket>();\n const activeConnections = new Set<DrainableConnection>();\n const draining = new DrainingRegistry();\n const inflight = new InflightTracker();\n\n // Observable output (valid in every phase, including after close).\n const output: Output = {\n connectionCount: 0,\n lastInfo: undefined,\n fatalError: undefined,\n };\n\n // Resolves on the first successful handshake; rejects on a fatal stop or if\n // the tunnel closes before connecting. The catch keeps a never-awaited\n // rejection from surfacing as unhandled.\n const ready = new Deferred<void>();\n void ready.promise.catch(() => {});\n\n // Assigned synchronously below once the live machinery exists; the deps\n // closures only read it at runtime, long after.\n let state: EngineState;\n\n // Opt-in process-signal registrations, removed on teardown so a closed\n // connection can never later intercept a signal and exit the host process.\n const signalUnregisters: Array<() => void> = [];\n\n const connectionDeps: ConnectionDeps = {\n opts,\n sdkHandler,\n draining,\n activeSockets,\n activeConnections,\n onEstablished: (info, identity: ConnectionIdentity) => {\n if (state.kind === \"closed\") return;\n const firstConnection = output.connectionCount === 0;\n output.connectionCount++;\n output.lastInfo = info;\n if (firstConnection) {\n log(\n `tunnel: service ready (name=${info.tunnelName}, proxy=${info.proxyUrl}, tunnel=${info.tunnelUrl}, worker_id=${identity.workerId}, connection_id=${identity.connectionId}, target=${identity.target})`\n );\n } else {\n log(\n `tunnel: additional connection ready (connections=${output.connectionCount}, name=${info.tunnelName}, worker_id=${identity.workerId}, connection_id=${identity.connectionId}, target=${identity.target})`\n );\n }\n ready.resolve();\n },\n isShuttingDown: () => state.kind === \"draining\",\n isStartupReady: () => startupGatePassed,\n inflightStarted: () => inflight.started(),\n inflightEnded: () => inflight.ended(),\n };\n\n const supervisor = new Supervisor(\n opts,\n connectionDeps,\n {\n onFatal: (err) => {\n output.fatalError = err; // E1 surfaces on ready/error\n ready.reject(err);\n },\n },\n logWithWorker\n );\n\n const keepAlive = setInterval(() => {}, 0x7fffffff);\n\n state = { kind: \"running\", active: { supervisor, keepAlive } };\n\n // ---- transitions ----\n\n /** Abrupt teardown; idempotent. Destructures the live machinery from the\n * state, so it can only run while running/draining. */\n const teardown = (): void => {\n if (state.kind === \"closed\") return;\n const { supervisor, keepAlive } = state.active;\n state = { kind: \"closed\" };\n for (const unregister of signalUnregisters) unregister();\n signalUnregisters.length = 0;\n supervisor.abortAll();\n for (const socket of activeSockets) socket.destroy();\n activeSockets.clear();\n draining.destroyAll();\n clearInterval(keepAlive);\n };\n\n // Resolves when the supervisor has fully wound down; then tear down (covers a\n // fatal that stopped us with no close()/shutdown() call) and settle `ready`.\n const done = supervisor.done.then(() => {\n teardown();\n ready.reject(\n output.fatalError ??\n new Error(\"tunnel: closed before the first handshake\")\n );\n });\n\n const drainGracefully = async (\n active: Active,\n graceMs: number\n ): Promise<void> => {\n const { supervisor } = active;\n // Stop dialing/resolving new connections (existing ones keep serving) and\n // move every live connection into a client-drain: serving ones refuse new\n // invocations and finish in-flight in place; not-yet-serving ones abort.\n // Snapshot — beginClientDrain may settle a connection, removing it.\n supervisor.stopResolving();\n for (const c of [...activeConnections]) c.beginClientDrain();\n logWithWorker(\n `tunnel: graceful shutdown — refusing new invocations, draining ${inflight.inFlight} in-flight`\n );\n const drained = await inflight.whenDrained(graceMs);\n // In-flight drained: ask h2 to close cleanly. Grace elapsed: force the\n // still-open sessions down, which tears down any stuck streams.\n await Promise.all(\n [...activeConnections].map((c) =>\n c.finishClientDrain({ force: !drained })\n )\n );\n // Now tear down the supervisor and any idle retry loops. For the graceful\n // case the live h2 sessions have already closed; for the forced case this\n // is idempotent cleanup.\n teardown();\n await done;\n };\n\n const close = async (): Promise<void> => {\n teardown();\n await done;\n };\n\n const shutdown = ({ graceMs }: { graceMs?: number } = {}): Promise<void> => {\n // Without the advertised capability the server ignores our drain sentinel,\n // so a graceful drain can't work (refused requests just keep getting\n // routed back) — fall back to an abrupt close, as documented.\n if (!resolvedOptions.supportsClientDrain) return close();\n // Coalesce: a drain already in progress, or already closed.\n if (state.kind === \"draining\") return state.completed;\n if (state.kind === \"closed\") return done;\n // Start the drain and record its promise on the state before the first\n // await, so every connection sees isShuttingDown() for the whole drain.\n // (drainGracefully runs synchronously up to inflight.whenDrained, so no new\n // invocation can interleave before `state` is set.)\n const { active } = state;\n const completed = drainGracefully(\n active,\n graceMs ?? resolvedOptions.drainGraceMs\n );\n state = { kind: \"draining\", active, completed };\n return completed;\n };\n\n // Install process-signal handlers (graceful shutdown is on by default; see\n // ConnectTunnelOptions.gracefulShutdown). Registered BEFORE the\n // already-aborted-signal handling below, so that path's synchronous close()\n // tears them down via teardown() rather than leaving a live handler on a\n // connection that is already closed.\n if (resolvedOptions.gracefulShutdown !== undefined) {\n const { signals, graceMs } = resolvedOptions.gracefulShutdown;\n signalUnregisters.push(\n registerGracefulShutdownSignals(signals, {\n async shutdown(signal) {\n logWithWorker(\n `tunnel: received ${signal} — shutting down gracefully`\n );\n await shutdown({ graceMs });\n },\n })\n );\n }\n\n if (options.signal?.aborted) {\n // An already-aborted signal means \"don't run\" — stop before dialing.\n void close();\n } else {\n options.signal?.addEventListener(\"abort\", () => void close(), {\n once: true,\n });\n }\n\n // ---- the public handle (reads the observable output) ----\n\n return {\n close,\n shutdown,\n get connectionCount() {\n return output.connectionCount;\n },\n get tunnelName() {\n return output.lastInfo?.tunnelName;\n },\n get proxyUrl() {\n return output.lastInfo?.proxyUrl;\n },\n get tunnelUrl() {\n return output.lastInfo?.tunnelUrl;\n },\n get deploymentUrl() {\n const lastInfo = output.lastInfo;\n if (lastInfo === undefined) return undefined;\n // Public clusters may advertise the proxy without a port; the proxy\n // listens on 9080. The destination (`/http/in-process/9080/`) is a\n // constant — an in-process tunnel is never dialed, so the server routes\n // purely by the tunnelName earlier in the path.\n try {\n const proxy = new URL(lastInfo.proxyUrl);\n if (proxy.port === \"\") proxy.port = \"9080\";\n const base = proxy.toString().replace(/\\/$/, \"\");\n return `${base}/http/in-process/9080/`;\n } catch {\n return `${lastInfo.proxyUrl}/http/in-process/9080/`;\n }\n },\n get error() {\n return output.fatalError;\n },\n ready: ready.promise,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@restatedev/restate-sdk-tunnel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Reverse-tunnel client for Restate Cloud — serve a Restate SDK deployment over an outbound tunnel connection, with no inbound HTTP listener",
|
|
5
5
|
"author": "Restate Developers",
|
|
6
6
|
"email": "code@restate.dev",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@restatedev/restate-sdk": "1.
|
|
36
|
+
"@restatedev/restate-sdk": "1.16.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@restatedev/restate-sdk": "^1.15.0"
|