@restatedev/restate-sdk-tunnel 0.0.0-dev → 1.15.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -1
- package/dist/index.cjs +955 -0
- package/dist/index.d.cts +288 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +288 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +927 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
package/README.md
CHANGED
|
@@ -1 +1,175 @@
|
|
|
1
|
-
# @restatedev/restate-sdk-tunnel
|
|
1
|
+
# @restatedev/restate-sdk-tunnel
|
|
2
|
+
|
|
3
|
+
Serve a Restate SDK deployment over an **outbound** connection to Restate
|
|
4
|
+
Cloud's tunnel — no inbound HTTP listener, no public ingress into your
|
|
5
|
+
network. A drop-in alternative to binding a listener with `restate.serve(...)`
|
|
6
|
+
for deployments running in private networks.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import * as restate from "@restatedev/restate-sdk";
|
|
10
|
+
import { connectTunnel } from "@restatedev/restate-sdk-tunnel";
|
|
11
|
+
|
|
12
|
+
const greeter = restate.service({
|
|
13
|
+
name: "greeter",
|
|
14
|
+
handlers: { greet: async (_ctx, name: string) => `Hello ${name}!` },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const connection = connectTunnel({
|
|
18
|
+
region: "us", // Restate Cloud region (tunnel servers discovered via DNS)
|
|
19
|
+
environmentId: "env_...", // your environment ID (env_ prefix included)
|
|
20
|
+
authToken: process.env.RESTATE_AUTH_TOKEN!, // Cloud API key with the Full role
|
|
21
|
+
signingPublicKey: "publickeyv1_...", // your environment's request-identity key
|
|
22
|
+
tunnelName: "greeter-v1", // the deployment's identity: unique per deployment, shared by its replicas
|
|
23
|
+
services: [greeter],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await connection.ready;
|
|
27
|
+
console.log(`register me: ${connection.deploymentUrl}`);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Once connected, register the deployment against Restate Cloud at
|
|
31
|
+
`connection.deploymentUrl` —
|
|
32
|
+
`<proxyUrl>/http/in-process/9080/`, e.g.:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
restate dep register https://tunnel.us.restate.cloud:9080/<unprefixed-env-id>/greeter-v1/http/in-process/9080/
|
|
36
|
+
└──────────────── connection.proxyUrl ─────────────────┘└── constant ──┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Two things to know about the URL's anatomy:
|
|
40
|
+
|
|
41
|
+
- **`tunnelName` is the deployment's identity** — the tunnel server keys
|
|
42
|
+
connections by `<environment>/<tunnelName>` and load-balances every
|
|
43
|
+
proxied invocation across the connections registered under that key.
|
|
44
|
+
Give each distinct deployment its own `tunnelName`; let replicas of the
|
|
45
|
+
_same_ deployment share one (that's the HA/load-balancing path).
|
|
46
|
+
- **The `/http/in-process/9080/` destination is a constant** — the
|
|
47
|
+
standalone tunnel client is a forwarder that dials this segment on the
|
|
48
|
+
far side; an in-process tunnel terminates in this very process, so the
|
|
49
|
+
destination is never dialed and plays no role. It only needs to be
|
|
50
|
+
stable, because Restate identifies deployments by their full URI.
|
|
51
|
+
|
|
52
|
+
## Zero config on Kubernetes (restate-operator)
|
|
53
|
+
|
|
54
|
+
The identity options and `region` fall back to `RESTATE_INPROC_*`
|
|
55
|
+
environment variables when not given (option > environment > throw):
|
|
56
|
+
|
|
57
|
+
| Option | Environment variable |
|
|
58
|
+
| ------------------ | ------------------------------------------------------------- |
|
|
59
|
+
| `tunnelName` | `RESTATE_INPROC_TUNNEL_NAME` |
|
|
60
|
+
| `environmentId` | `RESTATE_INPROC_ENVIRONMENT_ID` |
|
|
61
|
+
| `region` | `RESTATE_INPROC_CLOUD_REGION` |
|
|
62
|
+
| `signingPublicKey` | `RESTATE_INPROC_SIGNING_PUBLIC_KEY` |
|
|
63
|
+
| `authToken` | the file named by `RESTATE_INPROC_AUTH_TOKEN_FILE` (see below) |
|
|
64
|
+
|
|
65
|
+
The [restate-operator](https://github.com/restatedev/restate-operator)
|
|
66
|
+
injects the first four into the pods of a `tunnelMode: in-process`
|
|
67
|
+
RestateDeployment — with a per-revision `tunnelName` — and registers the
|
|
68
|
+
matching tunnel URL for every revision automatically. There, a complete
|
|
69
|
+
configuration is:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
connectTunnel({ services: [greeter] });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
plus a mounted API-key Secret: credentials are never injected, so mount one
|
|
76
|
+
yourself and set `RESTATE_INPROC_AUTH_TOKEN_FILE` to its path. The file is
|
|
77
|
+
**re-read on every reconnect**, so a rotated Secret is picked up without a
|
|
78
|
+
restart (a transient read failure during rotation is treated as a retryable
|
|
79
|
+
connection failure, not a crash). Mount the Secret as a whole volume rather
|
|
80
|
+
than via `subPath` — Kubernetes does not update `subPath` mounts in place, so
|
|
81
|
+
a rotated token would never reach the file.
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
`connectTunnel` is the in-process analog of Restate Cloud's standalone
|
|
86
|
+
[tunnel client](https://github.com/restatedev/restate-cloud-tunnel-client):
|
|
87
|
+
|
|
88
|
+
1. **Dial out** to the tunnel servers (region-based DNS discovery, or
|
|
89
|
+
explicit `tunnelServers`) — **one connection per resolved server**, like
|
|
90
|
+
the standalone client: SRV discovery expands every target to all of its
|
|
91
|
+
addresses, the set is re-resolved periodically, and connections are
|
|
92
|
+
started/torn down as servers appear/vanish. TLS with **ALPN `h2`** —
|
|
93
|
+
the same offer the standalone Rust client makes; the negotiation must
|
|
94
|
+
succeed (see the server-version note below).
|
|
95
|
+
2. **Role-flip:** Restate Cloud drives HTTP/2 as the _client_ over the
|
|
96
|
+
connection we dialed; the deployment becomes the HTTP/2 _server_ on it.
|
|
97
|
+
3. **Handshake:** the cloud opens `GET /_/start-tunnel`; we answer with the
|
|
98
|
+
environment credentials and receive the tunnel confirmation (including
|
|
99
|
+
the public `proxy-url`) as HTTP/2 trailers.
|
|
100
|
+
4. **Serve:** each invocation arrives as one HTTP/2 stream. The tunnel's
|
|
101
|
+
`/<scheme>/<host>/<port>` destination prefix is stripped and the request
|
|
102
|
+
is handed to the SDK's own endpoint handler (full-duplex streaming —
|
|
103
|
+
`BIDI_STREAM`), exactly as if it had arrived on a local listener.
|
|
104
|
+
5. **Verify:** every forwarded request carries Restate's request-identity
|
|
105
|
+
JWT (`x-restate-jwt-v1`). Verification is delegated to the SDK against
|
|
106
|
+
`signingPublicKey`, so only requests signed by _your_ environment are
|
|
107
|
+
served.
|
|
108
|
+
6. **Reconnect** on disconnect with jittered exponential backoff.
|
|
109
|
+
Authorization failures (`unauthorized`, `bad-tunnel-name`) are **fatal**
|
|
110
|
+
— they surface on `connection.error` / `connection.ready` instead of
|
|
111
|
+
hammering the auth path.
|
|
112
|
+
7. **Drain gracefully:** the engine advertises `supports-drain`. When
|
|
113
|
+
Restate Cloud rolls a tunnel node it sends `/_/drain-tunnel`; the engine
|
|
114
|
+
immediately dials a replacement while the old connection keeps serving
|
|
115
|
+
its in-flight invocations (up to `drainGraceMs`, default 120s) — zero
|
|
116
|
+
dropped requests across cloud rollovers.
|
|
117
|
+
|
|
118
|
+
## API
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
function connectTunnel(options: ConnectTunnelOptions): TunnelConnection;
|
|
122
|
+
|
|
123
|
+
interface TunnelConnection {
|
|
124
|
+
close(): Promise<void>; // stop reconnecting + close
|
|
125
|
+
readonly ready: Promise<void>; // first successful handshake (rejects on fatal)
|
|
126
|
+
readonly connectionCount: number;
|
|
127
|
+
readonly tunnelName: string | undefined; // learned from the handshake
|
|
128
|
+
readonly proxyUrl: string | undefined; // proxy base for this tunnel
|
|
129
|
+
readonly deploymentUrl: string | undefined; // ready-made registration URL
|
|
130
|
+
readonly tunnelUrl: string | undefined;
|
|
131
|
+
readonly error: Error | undefined; // set on fatal (no more reconnects)
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Key options (see `ConnectTunnelOptions` for the full surface and defaults):
|
|
136
|
+
|
|
137
|
+
| Option | Meaning |
|
|
138
|
+
| ----------------------------------------------- | --------------------------------------------------------------------------- |
|
|
139
|
+
| `region` / `tunnelServersSrv` / `tunnelServers` | Tunnel server discovery — exactly one; one connection per resolved server |
|
|
140
|
+
| `resolveIntervalMs` | SRV re-resolution cadence (30s; Node hides DNS TTLs) |
|
|
141
|
+
| `environmentId` | `env_...` — the environment to tunnel to |
|
|
142
|
+
| `authToken` | Cloud API key (`key_...`, Full role) presented in the handshake |
|
|
143
|
+
| `signingPublicKey` | `publickeyv1_...` — request-identity verification (required) |
|
|
144
|
+
| `tunnelName` | The deployment's identity — unique per deployment, shared by its replicas |
|
|
145
|
+
| `services` | Same shape `restate.serve` accepts |
|
|
146
|
+
| `tls` | Default on (system trust, ALPN `h2`); object form for CA/mTLS |
|
|
147
|
+
| `connectTimeoutMs` | TCP+TLS dial deadline (5s, mirrors the standalone client) |
|
|
148
|
+
| `reconnectInitialMs/MaxMs/Factor` | Jittered exponential backoff (10ms → 120s, reset after a stable connection) |
|
|
149
|
+
| `supportsDrain` / `drainGraceMs` | Graceful-drain handover on cloud rollovers (on, 120s grace) |
|
|
150
|
+
| `pingIntervalMs/TimeoutMs/MaxMissed` | Liveness watchdog (75s cadence) |
|
|
151
|
+
| `maxConcurrentStreams` etc. | HTTP/2 tuning for high-concurrency serving |
|
|
152
|
+
|
|
153
|
+
## Install
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm install @restatedev/restate-sdk-tunnel @restatedev/restate-sdk
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`@restatedev/restate-sdk` is a peer dependency.
|
|
160
|
+
|
|
161
|
+
## Scope and status
|
|
162
|
+
|
|
163
|
+
- Serves the deployment **in-process** — this package does not proxy to a
|
|
164
|
+
separate local service, and does not expose the standalone tunnel client's
|
|
165
|
+
"remote proxy" (local ingress/admin forwarding) feature.
|
|
166
|
+
- Multi-homed like the standalone client: one connection per resolved
|
|
167
|
+
tunnel server (per IP for SRV discovery), each with its own reconnect
|
|
168
|
+
loop; the server set is reconciled as DNS changes. A fatal handshake on
|
|
169
|
+
any connection (`unauthorized`, `bad-tunnel-name`) stops the whole
|
|
170
|
+
tunnel — the credentials are shared.
|
|
171
|
+
- **Requires a tunnel server with standard-h2 control traffic** (ALPN `h2`
|
|
172
|
+
advertised on the tunnel listener, `:authority` on control requests).
|
|
173
|
+
Older tunnel servers complete the TLS handshake without a negotiated
|
|
174
|
+
protocol and are rejected by this client with a clear log message.
|
|
175
|
+
- Node-only (uses `node:tls`, `node:http2`, `node:dns`). Node ≥ 22.
|