Repository navigation
Add connect(): serve over an outbound connection to Restate Cloud - #822
Merged
Merged
Conversation
The in-process tunnel client moves from @restatedev/restate-sdk-tunnel into restate-sdk under src/endpoint/tunnel and is exported from the /node entry (connectTunnel plus its option/handle types), so serve() can connect to Restate Cloud without an extra package. The engine builds its handler from NodeEndpoint directly and takes Duration/millis helpers from restate-sdk-core; the fetch and lambda entries are untouched. @restatedev/restate-sdk-tunnel stays as a compatibility re-export of @restatedev/restate-sdk/node with a workspace peer range. Its tests move to restate-sdk/test/tunnel and run against the SDK source. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One URL-shaped string identifies a Cloud environment and carries its credential: restate://<api key>@<bare environment id>.env.<region domain>/ with optional query parameters. The SDK's connect() reads it from RESTATE_CONNECT_KEY and the clients package from RESTATE_INGRESS_KEY, so the parser lives in restate-sdk-core: standard `new URL()` parsing, the API key from the userinfo slot (optional, percent-decoded), the environment id and region domain derived from the host, the parameters handed back for the caller to interpret. A bad scheme, path, port, fragment or host is an error naming the source variable and the expected shape. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
restate.connect({ services, name }) is the counterpart of serve() for Restate
Cloud: an outbound connection, no listener, no mode detection, nothing in the
environment flips one into the other. It is exported from the root and /node
entries and configured by two inputs:
- name: option → RESTATE_CONNECT_NAME → error. No inference; an unnamed
deployment is a mistake and the error says what to set.
- key: option → RESTATE_CONNECT_KEY → error naming the variable and the
Cloud UI page (Development → Configure SDK). The key's signing-key
parameter is the identity key unless identityKeys is given; tunnel-srv /
tunnel-servers (or the servers / serversSrv options) override the SRV
derived from the region domain; unknown parameters are ignored with one
warning.
The older spellings keep resolving behind those two, undocumented: the
RESTATE_INPROC_* names the restate-operator injects, RESTATE_TUNNEL_NAME,
and the expanded RESTATE_* variables (environment id, token or token file,
region or SRV / servers, signing key).
On ready the SDK logs how to register, through the SDK logger:
Connecting to Restate Cloud environment env_… (<region domain>)
Connected as "<name>"
Register it once:
restate deployments register connect://<name>
or open https://cloud.restate.dev/to/overview?register-deployment=connect://<name>
The handle is Connection (ready, name, deploymentUrl, close, shutdown,
connectionCount, error) and the options ConnectOptions / ConnectTuningOptions
/ ConnectTlsOptions, none of them carrying tunnel-era names. The legacy
connectTunnel() with its original ConnectTunnelOptions / TunnelConnection
runs on the same engine but is not part of this package's surface: /node
exports it only as _connectTunnel (and _-prefixed types) for
@restatedev/restate-sdk-tunnel to re-export.
The engine (src/endpoint/tunnel) now takes fully resolved EngineOptions:
identity resolution moved to src/endpoint/connect, options.ts validates
tuning only, the deployment URL always uses port 9080 (never the zone port
in the proxy-url trailer), and diagnostics go through the SDK LoggerTransport
with levels.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The package re-exports the legacy API under its public names (connectTunnel, ConnectTunnelOptions, TunnelConnection, TunnelTlsOptions) from the _-prefixed internal exports of @restatedev/restate-sdk/node, which no longer exposes connectTunnel itself. Options and behaviour are unchanged; the function warns once that connect() replaces it. Description and README point at connect() with RESTATE_CONNECT_KEY and RESTATE_CONNECT_NAME. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ingress key from the Cloud UI (Development → Configure Client) has the same grammar as the connect key, without a signing key: restate://<api key>@<environment id>.env.<region domain>/. Without a url, connect() uses https://<host>:8080 and the key as bearer (unless an Authorization header is given); explicit options win; with neither a url nor the variable it fails fast, as a url was always required. url is now optional in ConnectionOpts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The greeter connected to Restate Cloud with
connect({ services, name: "greeter-v1" }), run with `pnpm greeter_connect`
after exporting RESTATE_CONNECT_KEY. greeter.ts stays on serve().
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`tsc -b` built the referenced SDK projects too, emitting their declaration files into packages/libs/restate-sdk/dist. The SDK's own `tsc --noEmit` (composite) records changed sources as pending emit, so that build then overwrote tsdown's flattened entry declarations (node.d.ts) with raw per-file ones, and api-extractor failed on the node entry whenever an entry source had changed. Turbo already builds the dependencies first, and this package resolves them through node_modules, so the references were only there for `-b`; drop both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The legacy package no longer needs anything from restate-sdk beyond the public API: connectTunnel() translates its options into connect()'s — tunnelName → name, the identity fields → the key string they encode (restate://<authToken>@<bare env id>.env.<region>.restate.cloud/?signing-key=…), the flat tuning → connection, tunnelDiagnosticLogger → a logger transport that routes the engine's `tunnel:` lines to the hook — and maps the Connection handle back onto TunnelConnection (tunnelName, proxyUrl, deploymentUrl, …). The _-prefixed internal exports, the legacy types and src/endpoint/connect/tunnel.ts are gone from restate-sdk, and the expanded variables are now read from the environment only. The engine tests keep their legacy spelling through the same adapter, imported from the tunnel package's source, so the adapter is exercised against the engine end to end; the tunnel package gets unit tests for the option mapping. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
connect() is documented in the SDK README and release note; the examples package keeps greeter.ts on serve(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
parseCloudKey moves from restate-sdk-core, where it was public only so the clients package could share it, into restate-sdk/src/endpoint/connect/key.ts, unexported. The clients package gets its own trimmed parser for the ingress key (same grammar and error messages, no parameters to interpret), so no package exposes the parser. The parser also lowercases the host, rejects a bare id that still carries env_, no longer echoes the URL path in errors, and reports an empty tunnel-servers parameter as such. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The package is exactly what main ships — its own copy of the tunnel engine, its own tests — plus a deprecation notice in the README, the package description and the connectTunnel() doc comment. restate.connect() in restate-sdk has its own copy of the engine; nothing is shared or adapted between the two, so existing connectTunnel() users see no change at all. The engine tests in restate-sdk keep their legacy spelling through a small test-only shim onto connect(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The "Connecting to Restate Cloud environment …" line is logged once the
engine has validated the tuning, so an invalid servers entry no longer
prints it before throwing. The doc comment's {@link serve} becomes plain
text (serve is not in scope for api-extractor). A new test runs connect()
with only the RESTATE_INPROC_* variables and a token file, as the
restate-operator configures a pod, against the fake cloud.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
No deprecation notice either; the package is untouched by this change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Registration is by name (connect://<name>); the URL stays internal to the engine and is logged at debug. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
connectionCount and error leave the public handle; close() and shutdown() reject with the fatal error when the connection had already stopped on a non-retryable failure, so it still surfaces where the caller waits. The engine tests read the counters through connectEngine(), the un-projected entry point. ConnectTuningOptions folds into ConnectionOptions: it was the shared base for the legacy options, which no longer exist here. The public types are now exactly those reachable from connect()'s signature: ConnectOptions, ConnectionOptions, ConnectTlsOptions, ReconnectRetryPolicy, Connection. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…etry policies
A non-retryable stop is now explained through the SDK logger at error
level, in plain words with the fix: a rejected key ("Restate Cloud rejected
the connect key for environment env_…: it is invalid, revoked, or for
another environment. Create a new key at <Cloud UI link> and set
RESTATE_CONNECT_KEY."), a rejected name, a failed startupReady gate, or
giving up reconnecting. The engine's own "tunnel: FATAL — …" line moves to
debug. The engine gains an onFatal hook for this.
ReconnectRetryPolicy takes the shape of the invocation and client retry
policies: initialInterval, maxInterval, exponentiationFactor, plus
maxAttempts and maxDuration (number | false). Both bounds default to false,
the reconnect loop stays unbounded unless asked; when a bound is reached
the engine stops with "gave up reconnecting after …", surfaced like every
other fatal stop (ready/close()/shutdown() reject, explained in the log).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ingressFromKey is private to the clients package and its connect() doc is shorter; the ConnectOptions docs are reworded. The maxAttempts / maxDuration fields of ReconnectRetryPolicy, lost to an editor save in the previous commit, are back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The connection is always HTTP/2, so the handler always runs full-duplex; serve() does not expose the mode either. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Nobody needs a readiness gate (call connect() when ready) or a switch to tell Restate Cloud that this engine does not support a drain protocol it implements. The startup gate machinery leaves the engine; both drain protocols are always advertised. The tests of the switched-off paths go with them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…te Cloud From the run: DNS resolution failures count toward the reconnect bounds while nothing is connected (a wrong tunnel-srv no longer retries forever when a bound is set); no SNI for IP-literal servers (Node's DEP0123 warning); the engine's own "service ready" line moves to debug, the banner says it. From the review: neither parser echoes the host or path in errors any more (a key pasted with a lost `@` put the credential in the message); every parse error links the Cloud UI page; the clients parser matches the SDK's (case-insensitive host, no env_ in the bare id, header-safe token); a connect key for one environment next to RESTATE_[INPROC_]ENVIRONMENT_ID for another warns that the key wins; an empty tunnel-srv or both tunnel-srv and tunnel-servers in a key are errors naming the parameters; reaching a reconnect bound stops the deployment only when no other connection is alive, otherwise that server is dropped until discovery brings it back; stale doc references to the former API are gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
restate.connect({ services, name })is the counterpart ofserve()for Restate Cloud: the services are served over an outbound connection to the environment, with no inbound listener, no tunnel container and no extra package.serve()is unchanged; there is no mode detection and nothing in the environment flips one into the other.Configuration is two environment variables (or the matching options):
RESTATE_CONNECT_KEY— one URL-shaped string from the Cloud UI (Development → Configure SDK):restate://<api key>@<environment id>.env.<region domain>/?signing-key=…, carrying the credential, the environment, the region and the request-identity key. Optionaltunnel-srv/tunnel-serversparameters override discovery; unknown parameters are ignored with one warning.RESTATE_CONNECT_NAME— the deployment identity (connect({ name })in code). No inference: an unnamed deployment is an error that says what to set.On the first successful handshake the SDK logs how to register, once:
Errors link the Cloud UI page they refer to (
/to/developers/integration#configure-sdkand#configure-client). A non-retryable stop is explained through the SDK logger at error level, in plain words with the fix, e.g. for a rejected key:API
ConnectOptions = EndpointOptions & { name?, key?, connection?: ConnectionOptions }.ConnectionOptionsholds server discovery (servers,serversSrv),workerIdand the tuning (reconnect, drain, HTTP/2 limits,tls: ConnectTlsOptions).ReconnectRetryPolicyhas the shape of the invocation and client retry policies (initialInterval,maxInterval,exponentiationFactor,maxAttempts,maxDuration); the bounds default tofalsesince a deployment should keep reconnecting, and reaching one is a fatal stop like any other.Connection:ready,name,close(),shutdown().close()/shutdown()reject with the fatal error if the connection had already stopped on a non-retryable failure. Registration is by name; the deployment URL stays internal (logged at debug, always on port 9080).@restatedev/restate-sdk-clients:connect()without aurlreadsRESTATE_INGRESS_KEY(same grammar, from Development → Configure Client) and useshttps://<host>:8080with the key as bearer. Nothing set still fails fast, since a URL was always required.parseCloudKeyinrestate-sdk-coreis the shared key parser.Compatibility
@restatedev/restate-sdk-tunnelis untouched: identical tomain, its own copy of the engine, its own tests.restate.connect()has its own copy of the engine insiderestate-sdk; nothing is shared or adapted between the two, so existingconnectTunnel()users see no change at all.RESTATE_INPROC_*(restate-operator),RESTATE_TUNNEL_NAME, and the expandedRESTATE_*set (environment id, token or token file, region or SRV / servers, signing key). An operator-configured pod (RESTATE_INPROC_*plus a token file) works withconnect({ services })unchanged; there is an end-to-end test for it.Build fix
packages/tests/restate-e2e-servicesbuilt withtsc -band referenced the SDK's composite project, so it emitted raw declaration files intorestate-sdk/dist, overwriting tsdown's flattenednode.d.tswhenever an entry source had changed and breaking api-extractor on the node entry. It now builds withtsc --projectand without the references (turbo already builds the dependencies first, and the package resolves them throughnode_modules).Verification
Full pipeline green (76/76 tasks): format, lint, types, build, exports, API report, tests. The fake-cloud engine tests run through
connect()with a key (and, for their legacy spelling, through a small test-only shim); new tests cover key parsing, name/key resolution order, error messages, the banner, the operator setup, and the clients defaults.Release note
🤖 Generated with Claude Code