Document list | Previous: Java STREAM
Java Bundle | STREAM | Samples
Java/Kotlin Stream Connector¶
This document is the Java/Kotlin projection of the Stream Connector Common Spec. Transport/wire/lifecycle/error meaning is owned by the common spec, and this document fixes the exact public surface that meaning has in Java/Kotlin.
1. Goal¶
Stream Connector is a module separate from the server framework. It
lets an external client build and interpret the same
framework-header-based STREAM packet the server's ZLinkSession
receives.
This module doesn't depend on a Spring Boot server adapter, SPOT, or Registry. It only has the dependency needed for client execution, such as transport, codec, compression, reconnect, and dispatch queue.
1.1 Target Execution Environment¶
The connector responsible per engine × build target is owned by Common Spec §2. Per that assignment, what the Java/Kotlin connector is responsible for is a JVM application (server tool/E2E test/bot), and it isn't responsible for a game engine or browser.
Since there's only one target, this assignment leaves no effect on the Java/Kotlin surface. The usage guide only separately describes the per-language async usage in the Java guide and the Kotlin guide.
2. Module¶
The Java connector's Maven coordinate is
systems.zlink:zlink-stream-connector.
| Module | Role |
|---|---|
zlink-stream-connector |
TCP/TLS/WS/WSS transport, frame codec, send/request, dispatch |
zlink-framework-kotlin |
coroutine, Flow, DSL extension |
zlink-framework-codec-protobuf |
The Protobuf codec extension shared across framework/connector/http-client |
zlink-framework-codec-msgpack |
The MessagePack codec extension shared across framework/connector/http-client |
JSON is the framework default codec. A Protobuf and MessagePack
payload isn't a connector-dedicated package — it's used by applying
the zlink-framework-codec-protobuf, zlink-framework-codec-msgpack
framework codec extension to the connector too.
3. Public API¶
public interface ZLinkStreamConnector {
boolean isConnected();
ZLinkStreamConnectionState state();
ZLinkStreamConnectorOptions options();
// The last close reason. Empty when it has never disconnected (see "Session close reason").
Optional<ZLinkStreamCloseReason> closeReason();
int pendingDispatchCount();
int receivedCount(String name);
// there are only three lifecycle surfaces. Manual reconnect is a state
// transition of connect(), and automatic reconnect is handled by options
// (Common Spec 32 §6).
ZLinkStreamLifecycleCall connect();
ZLinkStreamLifecycleCall close();
ZLinkStreamLifecycleCall dispatch();
ZLinkStreamSendCall send(ZLinkStreamEncodedPayload payload);
ZLinkStreamRequestCall request(ZLinkStreamEncodedPayload payload);
ZLinkTypedStreamSendCall send(Object payload);
ZLinkTypedStreamSendCall send(String name, Object payload);
ZLinkTypedStreamRequestCall request(Object payload);
ZLinkTypedStreamRequestCall request(String name, Object payload);
ZLinkStreamWaitCall waitFor(String name);
ZLinkStreamWaitCall waitFor(Class<?> payloadType);
ZLinkStreamExpectNoneCall expectNone(String name);
ZLinkStreamExpectNoneCall expectNone(Class<?> payloadType);
ZLinkStreamSequenceCall waitForSequence(String name);
ZLinkStreamSequenceCall waitForSequence(Class<?> payloadType);
AutoCloseable on(
String name,
ZLinkStreamMessageHandler<ZLinkStreamEncodedPayload> handler);
<TPayload> AutoCloseable on(
Class<TPayload> payloadType,
ZLinkStreamMessageHandler<TPayload> handler);
<TPayload> AutoCloseable on(
String name,
Class<TPayload> payloadType,
ZLinkStreamMessageHandler<TPayload> handler);
AutoCloseable onRequestSending(ZLinkStreamRequestSendingHandler handler);
AutoCloseable onReplyReceived(ZLinkStreamReplyReceivedHandler handler);
AutoCloseable onErrorReceived(ZLinkStreamErrorHandler handler);
AutoCloseable onDisconnected(ZLinkStreamDisconnectedHandler handler);
AutoCloseable onConnectionStateChanged(ZLinkStreamConnectionStateHandler handler);
// The Actor handles bound right now (common spec §5.6). The application never creates one.
List<ZLinkStreamActor> actors();
Optional<ZLinkStreamActor> actor(String actorId);
AutoCloseable onActorBound(ZLinkStreamActorHandler handler);
AutoCloseable onActorUnbound(ZLinkStreamActorHandler handler);
}
public interface ZLinkStreamActor {
String actorId();
boolean isBound(); // false after the unbound announcement
ZLinkStreamSendCall send(ZLinkStreamEncodedPayload payload); // carries this Actor's slot
ZLinkStreamRequestCall request(ZLinkStreamEncodedPayload payload);
ZLinkTypedStreamSendCall send(Object payload);
ZLinkTypedStreamSendCall send(String name, Object payload);
ZLinkTypedStreamRequestCall request(Object payload);
ZLinkTypedStreamRequestCall request(String name, Object payload);
AutoCloseable on(String name, ZLinkStreamMessageHandler<ZLinkStreamEncodedPayload> handler); // only messages whose counterpart is this Actor
<TPayload> AutoCloseable on(Class<TPayload> payloadType, ZLinkStreamMessageHandler<TPayload> handler);
<TPayload> AutoCloseable on(
String name,
Class<TPayload> payloadType,
ZLinkStreamMessageHandler<TPayload> handler);
}
@FunctionalInterface
public interface ZLinkStreamActorHandler {
CompletionStage<Void> handle(ZLinkStreamActor actor);
}
public interface ZLinkStreamLifecycleCall {
CompletionStage<Void> submit();
}
public final class ZLinkStreamConnectorFactory {
public static ZLinkStreamConnector create(ZLinkStreamConnectorOptions options);
}
Java exposes an event as an on... registration. The meaning is the
same as .NET's event. Deregistration is done through the returned
AutoCloseable's close(), and closing the same value twice isn't
treated as an error
(Common Spec §7). The
push handler and the error/disconnect/connection state handlers all use
the same return type.
Session close reason. The value set and meaning is owned by
Common Spec §6.2.
Java expresses this as the closed enum ZLinkStreamCloseReason
(CLIENT_CLOSE, IDLE_TIMEOUT, HEARTBEAT_TIMEOUT, SERVER_DRAIN,
PROTOCOL_ERROR, TRANSPORT_ERROR). The read surface is the
connector's Optional<ZLinkStreamCloseReason> closeReason() method.
It returns Optional.empty() when the connector has never
disconnected. The disconnect event's ZLinkStreamCloseReason
closeReason(), which ZLinkStreamDisconnectedHandler receives, is a
surface added on top of that read surface.
waitFor(...) returns a call builder that waits once for a server push
of a specific packet name. When only a specific message is needed, use
the builder's where(...). Once the timeout passes, the returned
CompletionStage ends with a timeout failure. If a separate timeout
isn't specified, the connector options' waitTimeout() value is used.
Since waitFor(...) directly consumes an unconsumed receive packet in
both dispatch modes, dispatch().submit() isn't needed in MANUAL
either. dispatch().submit() runs a registered push handler, error/
disconnect handler, and request callback.
In the Java API, submit(...) starts an async operation. A one-way
send's submit() returns CompletionStage<Void>. This stage only
delivers completion and failure, and doesn't include transport result
or admission status. A request/wait/lifecycle's submit() returns a
CompletionStage carrying each operation's result
(04 §1).
The Java connector doesn't provide a separate blocking terminator that
waits for the same operation on the current thread. Lifecycle also
follows the same call builder convention, like connect().submit(),
dispatch().submit().
The Kotlin wrapper waits for the CompletionStage obtained from
submit() as a coroutine suspension. This execution meaning follows
the framework common policy.
4. Options¶
The default value is owned by
Common Spec §6.1. Java expresses
this as a record with a flat field (heartbeat/reconnect aren't put as
a nested object). A default instance is built with
createDefault(URI endpoint).
// transport (TCP/TLS/WS/WSS) is decided by the endpoint URI scheme. Heartbeat/reconnect
// configuration is a flat field, not a separate nested object. A default instance is built
// with `createDefault(URI endpoint)`.
public record ZLinkStreamConnectorOptions(
URI endpoint,
ZLinkStreamDispatchMode dispatchMode, // default MANUAL
Duration requestTimeout, // default 30s
Duration waitTimeout, // default 5s
int maxReconnectAttempts, // default 3. UNLIMITED_RECONNECT_ATTEMPTS(-1) means unlimited
Duration connectTimeout, // default 5s
int maxSendPayloadSize, // default 64 * 1024
int maxReceivePayloadSize, // default 64 * 1024
boolean heartbeatEnabled, // default true
Duration heartbeatInterval, // default 1s
Duration heartbeatTimeout, // default 5s
boolean reconnectEnabled, // default true
Duration reconnectInitialDelay, // default 250ms
Duration reconnectMaxDelay, // default 5s
double reconnectBackoffFactor, // default 2.0
boolean skipServerCertificateValidation,
ZLinkStreamCompression compression,
ZLinkStreamCompressionCodec compressionCodec,
ZLinkStreamPacketNameResolver nameResolver, // the name resolver injection point of common spec §5.4
ZLinkStreamTypedCodec typedCodec) { // typed payload codec injection point from common spec §5.4
// The named constant expressing the unlimited reconnect common spec §6 requires.
public static final int UNLIMITED_RECONNECT_ATTEMPTS = -1;
}
The option validation timing is owned by
Common Spec §6.3.
In Java, ZLinkStreamConnectorFactory.create(options) checks every
option, and on a validation failure it builds no ZLinkStreamConnector
instance and fails with a ZLinkStreamException (§11). A single value
out of range carries VALIDATION_FAILED, and a mismatch between
options carries CONFIGURATION_ERROR. maxReconnectAttempts must be
UNLIMITED_RECONNECT_ATTEMPTS or positive.
skipServerCertificateValidation is used only for a test's self-signed
certificate. The production default is false. Setting this value to
true passes both TLS transport and WSS transport without trusting the
server certificate, so it must not be used in a production
environment.
5. Transport And Codec¶
The scheme → transport mapping and TLS validation rule is owned by Common Spec §3. Java infers the transport from the endpoint URI scheme instead of choosing it as a separate enum option.
public enum ZLinkStreamTransport { TCP, TLS, WEB_SOCKET, WEB_SOCKET_SECURE }
public enum ZLinkStreamCodec { RAW, JSON, MESSAGE_PACK, PROTOBUF }
public enum ZLinkStreamCompression { NONE, LZ4 }
Hostname validation uses the HTTPS endpoint identification rule.
6. Packet Model¶
public record ZLinkStreamEncodedPayload(
String packetName,
Message payload,
Map<String, String> metadata,
ZLinkStreamCodec codec) {
}
public record ZLinkStreamMessage<TPayload>(
String packetName,
TPayload payload,
Map<String, String> metadata,
String actorId) { // the counterpart bound Actor; null for a frame without a slot (common spec §5.6)
}
The means of attaching a packet name to a type that Common Spec §5 requires is an annotation.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ZLinkStreamPacketName {
String value(); // the packet name attached to the type
}
A typed object's packet identity prioritizes the payload type's
@ZLinkStreamPacketName, and uses the type's SimpleName if absent.
If the caller specifies it with packetName(...), that name takes
priority (Common Spec 32 §5). The identity of an already-encoded raw
payload is specified in ZLinkStreamEncodedPayload.packetName().
Metadata only carries a small key-value. Large work data is sent as payload. The STREAM wire header is a runtime-internal type. The connector user and server session don't build or pass a header object — they only handle the packet name and a metadata snapshot in the public model.
7. Send And Request¶
public interface ZLinkStreamSendCall {
ZLinkStreamSendCall packetName(String name); // per-call override. If specified, this name takes priority
ZLinkStreamSendCall metadata(String key, String value);
ZLinkStreamSendCall metadata(Map<String, String> metadata);
ZLinkStreamSendCall compress();
CompletionStage<Void> submit();
}
public interface ZLinkStreamRequestCall {
ZLinkStreamRequestCall packetName(String name); // per-call override
ZLinkStreamRequestCall metadata(String key, String value);
ZLinkStreamRequestCall metadata(Map<String, String> metadata);
ZLinkStreamRequestCall timeout(Duration timeout);
ZLinkStreamRequestCall compress();
CompletionStage<ZLinkStreamEncodedPayload> submit();
<TReply> CompletionStage<TReply> submit(Class<TReply> replyType);
}
public interface ZLinkTypedStreamSendCall {
ZLinkTypedStreamSendCall packetName(String name); // per-call override
ZLinkTypedStreamSendCall metadata(String key, String value);
ZLinkTypedStreamSendCall metadata(Map<String, String> metadata);
ZLinkTypedStreamSendCall compress();
CompletionStage<Void> submit();
}
public interface ZLinkTypedStreamRequestCall {
ZLinkTypedStreamRequestCall packetName(String name); // per-call override
ZLinkTypedStreamRequestCall metadata(String key, String value);
ZLinkTypedStreamRequestCall metadata(Map<String, String> metadata);
ZLinkTypedStreamRequestCall timeout(Duration timeout);
ZLinkTypedStreamRequestCall compress();
<TReply> CompletionStage<TReply> submit(Class<TReply> replyType);
}
public interface ZLinkStreamWaitCall {
ZLinkStreamWaitCall timeout(Duration timeout);
ZLinkStreamWaitCall where(
Predicate<ZLinkStreamMessage<ZLinkStreamEncodedPayload>> predicate);
<TPayload> ZLinkStreamWaitCall where(
Class<TPayload> payloadType,
Predicate<ZLinkStreamMessage<TPayload>> predicate);
CompletionStage<ZLinkStreamMessage<ZLinkStreamEncodedPayload>> submit();
<TPayload> CompletionStage<ZLinkStreamMessage<TPayload>> submit(
Class<TPayload> payloadType);
}
Pending cleanup on request timeout follows
Common Spec §5.2.
Java delivers the result through CompletionStage.
7.1 Request Hooks¶
The two hooks in Common Spec §5.7
are registered on the connector as
AutoCloseable onRequestSending(ZLinkStreamRequestSendingHandler) and
AutoCloseable onReplyReceived(ZLinkStreamReplyReceivedHandler) and removed through the
returned value's close().
public interface ZLinkStreamRequestSendingHandler {
void handle(ZLinkStreamRequestSendingContext context);
}
public interface ZLinkStreamReplyReceivedHandler {
void handle(ZLinkStreamReplyReceivedContext context);
}
ZLinkStreamRequestSendingContext exposes requestPacketName(), nullable
actorId(), and setMetadata(String key, String value). The reply context exposes
read-only requestPacketName(), nullable actorId(), succeeded(),
reply() on success, error() on failure, and elapsed().
Common Spec §5.7 and §7 govern metadata validation and hook failures.
7.2 Test Wait Surface¶
The contract is owned by Common Spec §10.1. The Java surface is below.
Push observation — connector method (the same spot as waitFor).
Each returns a builder.
Each surface carries both an overload where the caller states the
packet name and one that decides it from the payload type
(Common Spec §10.1.1).
For the type overload, options' nameResolver decides the name.
ZLinkStreamWaitCall waitFor(String name); // waits until it arrives
ZLinkStreamWaitCall waitFor(Class<?> payloadType);
ZLinkStreamExpectNoneCall expectNone(String name); // whether it doesn't arrive during .within(window)
ZLinkStreamExpectNoneCall expectNone(Class<?> payloadType);
ZLinkStreamSequenceCall waitForSequence(String name); // .expect(p).expect(p)… in order
ZLinkStreamSequenceCall waitForSequence(Class<?> payloadType);
public interface ZLinkStreamExpectNoneCall {
ZLinkStreamExpectNoneCall within(Duration window); // the observation window; must be given
CompletionStage<Void> submit();
}
public interface ZLinkStreamSequenceCall {
// adds the next predicate to apply in arrival order. Its argument is the message, not the payload.
ZLinkStreamSequenceCall expect(
Predicate<ZLinkStreamMessage<ZLinkStreamEncodedPayload>> predicate);
<TPayload> ZLinkStreamSequenceCall expect(
Class<TPayload> payloadType,
Predicate<ZLinkStreamMessage<TPayload>> predicate);
ZLinkStreamSequenceCall timeout(Duration timeout);
CompletionStage<List<ZLinkStreamMessage<ZLinkStreamEncodedPayload>>> submit();
<TPayload> CompletionStage<List<ZLinkStreamMessage<TPayload>>> submit(
Class<TPayload> payloadType);
}
expectNone(name).within(Duration).submit()— fails with aZLinkStreamExceptioncarryingVALIDATION_FAILEDif it arrives within the window. The symmetric ofwaitFor.waitForSequence(name).expect(p1).expect(p2)….timeout(t).submit()returnsList<ZLinkStreamMessage<TPayload>>; sequence observation and failure follow Common Spec §10.1.- The predicate and the return value handle
ZLinkStreamMessage. The argumentwhere(...)andexpect(...)receive is the message, not the payload. -
A status-only surface isn't provided. Since status is a payload field, it's expressed as
waitFor(T.class).where(T.class, m -> m.payload().status() == …). -
Domain REST polling isn't this surface. That's
ZLinkHttpClient's job.
8. Typed Payload Codec¶
The base connector keeps the wire payload as ZLinkStreamEncodedPayload.
The typed surface uses options' single typedCodec to encode/
decode the work DTO (default is JSON). Application code generally
doesn't directly handle a raw Message or codec helper.
The ZLinkStreamConnector.send(Object), request(Object),
on(Class<TPayload>, ...), waitFor(...) above are the typed payload
surface.
The packet name the typed surface builds directly uses the core
connector's name resolver as is. A payload that can't be expressed by
the codec fails as a configuration error.
When waiting for a server push, the base connector's wait builder is
used. If a payload condition is needed, the core wait builder's where
is used, like
connector.waitFor(name).where(payloadType, predicate).submit(payloadType).
A sample client waits for a server push using the connector member
waitFor(...).where(...).submit(...) or the Kotlin wrapper
waitFor<T>(...).where { ... }.await() form.
The typed surface operates based on a work object payload the registry
can encode/decode. A raw payload, such as String, byte[], Message,
is only handled on a connector sub-path or explicit raw use.
The Kotlin surface doesn't directly expose the Java call to the application — it wraps it with a dedicated wrapper. Reply type is fixed when building the request wrapper, so type or operation name isn't repeated in the terminal.
// Reply type is fixed on the wrapper when building the request, and the result is awaited with await().
val reply: LoginReply = connector
.request<LoginReply>(LoginRequest("user-1"))
.await()
// Server push is also awaited with the Kotlin-dedicated wait wrapper's await().
val pushed: ZLinkStreamMessage<Notice> = connector
.waitFor<Notice>()
.where { it.payload.important }
.await()
9. Dispatch Mode¶
public enum ZLinkStreamDispatchMode {
MANUAL, // default
IMMEDIATE // runs inline on the receive path (Common Spec 32 §7)
}
The callback execution of MANUAL and IMMEDIATE and its relationship
to dispatch().submit() follow
Common Spec §7.
10. Connection State¶
The state's meaning and transition is owned by Common Spec §6. Java expresses this as a closed enum.
public enum ZLinkStreamConnectionState {
CREATED,
DISCONNECTED,
CONNECTING,
CONNECTED,
RECONNECTING,
CLOSED
}
Once close().submit() completes, it's CLOSED and a new
connect() fails.
CREATED is the initial state before the first connection attempt.
Once a connection attempt fails, it switches to DISCONNECTED, so
"never connected" and "disconnected" are distinguished.
11. Error Code¶
The error's meaning is owned by
Common Spec §9. Java expresses this
as a closed enum. The dedicated exception type carrying the code
that
Common Spec §9.2
requires is ZLinkStreamException.
public record ZLinkStreamError(
ZLinkStreamErrorCode code, // the closed set of 13 values below
String message,
Throwable exception) { // the causing exception; null when there is none
}
public final class ZLinkStreamException extends RuntimeException {
public ZLinkStreamException(ZLinkStreamError error);
public ZLinkStreamError error(); // where the code is read
public ZLinkStreamErrorCode errorCode(); // shorthand for error().code()
}
ZLinkStreamException is the only exception the connector throws or
completes a CompletionStage with. A language-standard exception such
as IllegalArgumentException or IllegalStateException is never thrown
as is — those types have no place to carry a code, so the caller
cannot tell VALIDATION_FAILED from CONFIGURATION_ERROR. An option
validation failure (§4) and a wait-surface violation (§7.2) are
delivered through the same exception.
public enum ZLinkStreamErrorCode {
DISCONNECTED,
CONFIGURATION_ERROR,
VALIDATION_FAILED,
REQUEST_TIMEOUT,
CONNECT_TIMEOUT,
FRAME_DECODE_FAILED,
FRAME_TOO_LARGE,
SEND_FAILED,
COMPRESSION_FAILED,
TLS_VALIDATION_FAILED,
DECOMPRESSION_FAILED,
USER_CALLBACK_FAILED,
REMOTE_ERROR
}
12. Kotlin Surface¶
The Kotlin module is a thin wrapper on top of the Java connector. An
operation with a completion value, such as lifecycle and request, is
awaited with the Kotlin wrapper's suspend await(). This await()
waits for the Java CompletionStage as a coroutine suspension. A
one-way send also waits for completion and failure with await(), but
doesn't receive a transport result or admission status.
Kotlin adds no exception hierarchy of its own — it propagates Java's
ZLinkStreamException (§11) as is. When await() fails, the same
exception is raised at the call site, and the caller reads the error
code through error().code().
fun ZLinkStreamConnector.kotlin(): ZLinkKotlinStreamConnector
fun ZLinkStreamConnectorOptions.withDefaultStreamCompression(): ZLinkStreamConnectorOptions
fun ZLinkStreamConnectorOptions.withLz4StreamCompression(): ZLinkStreamConnectorOptions
fun ZLinkStreamConnectorOptions.withStreamCompression(
codec: ZLinkStreamCompressionCodec,
): ZLinkStreamConnectorOptions
fun ZLinkStreamConnectorOptions.withoutStreamCompression(): ZLinkStreamConnectorOptions
class ZLinkKotlinStreamConnector {
fun on(name: String, handler: ZLinkStreamMessageHandler<ZLinkStreamEncodedPayload>): AutoCloseable
inline fun <reified TPayload> on(handler: ZLinkStreamMessageHandler<TPayload>): AutoCloseable
fun <TPayload : Any> on(name: String, payloadType: KClass<TPayload>, handler: ZLinkStreamMessageHandler<TPayload>): AutoCloseable
fun receivedCount(name: String): Int
fun connect(): ZLinkKotlinLifecycleCall
fun close(): ZLinkKotlinLifecycleCall
fun dispatch(): ZLinkKotlinLifecycleCall
fun send(payload: ZLinkStreamEncodedPayload): ZLinkKotlinSendCall
fun send(payload: Any): ZLinkKotlinSendCall
fun request(
payload: ZLinkStreamEncodedPayload,
): ZLinkKotlinRawRequestCall
fun <TReply : Any> request(
payload: Any,
replyType: KClass<TReply>,
): ZLinkKotlinRequestCall<TReply>
fun closeReason(): ZLinkStreamCloseReason?
fun <TPayload> waitFor(): ZLinkStreamTypedWaitCall<TPayload>
fun <TPayload> waitFor(name: String): ZLinkStreamTypedWaitCall<TPayload>
fun <TPayload> expectNone(): ZLinkStreamTypedExpectNoneCall<TPayload>
fun <TPayload> expectNone(name: String): ZLinkStreamTypedExpectNoneCall<TPayload>
fun <TPayload> waitForSequence(): ZLinkStreamTypedSequenceCall<TPayload>
fun <TPayload> waitForSequence(name: String): ZLinkStreamTypedSequenceCall<TPayload>
fun messages(packetName: String): Flow<ZLinkStreamMessage<ZLinkStreamEncodedPayload>>
inline fun <reified TPayload> messages(): Flow<ZLinkStreamMessage<TPayload>>
fun <TPayload : Any> messages(packetName: String, payloadType: KClass<TPayload>): Flow<ZLinkStreamMessage<TPayload>>
fun onRequestSending(handler: ZLinkStreamRequestSendingHandler): AutoCloseable
fun repliesReceived(): Flow<ZLinkStreamReplyReceivedContext>
fun errors(): Flow<ZLinkStreamError>
fun actors(): List<ZLinkKotlinStreamActor>
fun actor(actorId: String): ZLinkKotlinStreamActor?
fun actorBound(): Flow<ZLinkKotlinStreamActor>
fun actorUnbound(): Flow<ZLinkKotlinStreamActor>
}
class ZLinkKotlinStreamActor {
fun on(name: String, handler: ZLinkStreamMessageHandler<ZLinkStreamEncodedPayload>): AutoCloseable
inline fun <reified TPayload> on(handler: ZLinkStreamMessageHandler<TPayload>): AutoCloseable
fun <TPayload : Any> on(name: String, payloadType: KClass<TPayload>, handler: ZLinkStreamMessageHandler<TPayload>): AutoCloseable
val actorId: String
val isBound: Boolean
fun send(payload: ZLinkStreamEncodedPayload): ZLinkKotlinSendCall
fun send(payload: Any): ZLinkKotlinSendCall
fun request(payload: ZLinkStreamEncodedPayload): ZLinkKotlinRawRequestCall
fun <TReply : Any> request(
payload: Any,
replyType: KClass<TReply>,
): ZLinkKotlinRequestCall<TReply>
fun messages(packetName: String): Flow<ZLinkStreamMessage<ZLinkStreamEncodedPayload>>
inline fun <reified TPayload> messages(): Flow<ZLinkStreamMessage<TPayload>>
fun <TPayload : Any> messages(packetName: String, payloadType: KClass<TPayload>): Flow<ZLinkStreamMessage<TPayload>>
}
inline fun <reified TReply : Any> ZLinkKotlinStreamActor.request(
payload: Any,
): ZLinkKotlinRequestCall<TReply> =
request(payload, TReply::class)
class ZLinkKotlinLifecycleCall {
suspend fun await()
}
class ZLinkKotlinSendCall {
fun packetName(name: String): ZLinkKotlinSendCall
suspend fun await(): Unit
}
class ZLinkKotlinRawRequestCall {
fun packetName(name: String): ZLinkKotlinRawRequestCall
fun metadata(key: String, value: String): ZLinkKotlinRawRequestCall
fun timeout(timeout: Duration): ZLinkKotlinRawRequestCall
fun compress(): ZLinkKotlinRawRequestCall
suspend fun await(): ZLinkStreamEncodedPayload
}
class ZLinkKotlinRequestCall<TReply : Any> {
fun packetName(name: String): ZLinkKotlinRequestCall<TReply>
fun metadata(key: String, value: String): ZLinkKotlinRequestCall<TReply>
fun timeout(timeout: Duration): ZLinkKotlinRequestCall<TReply>
fun compress(): ZLinkKotlinRequestCall<TReply>
suspend fun await(): TReply
}
inline fun <reified TReply : Any> ZLinkKotlinStreamConnector.request(
payload: Any,
): ZLinkKotlinRequestCall<TReply> =
request(payload, TReply::class)
class ZLinkStreamTypedWaitCall<TPayload> {
fun timeout(timeout: Duration): ZLinkStreamTypedWaitCall<TPayload>
fun where(predicate: (ZLinkStreamMessage<TPayload>) -> Boolean): ZLinkStreamTypedWaitCall<TPayload>
suspend fun await(): ZLinkStreamMessage<TPayload>
}
class ZLinkStreamTypedExpectNoneCall<TPayload> {
fun within(window: Duration): ZLinkStreamTypedExpectNoneCall<TPayload>
suspend fun await() // exception if it arrives within the window
}
class ZLinkStreamTypedSequenceCall<TPayload> {
fun expect(predicate: (ZLinkStreamMessage<TPayload>) -> Boolean): ZLinkStreamTypedSequenceCall<TPayload>
fun timeout(timeout: Duration): ZLinkStreamTypedSequenceCall<TPayload>
suspend fun await(): List<ZLinkStreamMessage<TPayload>> // arrives in predicate order
}
The close reason and the Actor handle are read from the wrapper itself. Code that uses only
the wrapper must reach them, so it is not left to pull the Java connector out. A Java Optional
becomes a Kotlin nullable: the close reason is null when the connection has never ended, and
actor(id) is null for an id that is not bound
(common spec §5.6).
All three wait surfaces offer both the named path and the payload-type
path. With no name, the name resolution rules for TPayload (§5) settle it.
If only waitFor has both paths while expectNone and waitForSequence
demand a name, the three surfaces are called differently inside one test.
The Kotlin wrapper must not build a different state transition or
buffering policy from the Java connector. The extension copying options
must preserve every option value currently defined.
A Flow surface for a dispatch-mode callback wraps the corresponding Java registration:
the connector's messages(...), errors(), actorBound() and actorUnbound() wrap on(...),
onErrorReceived(...), onActorBound(...) and onActorUnbound(...), and an Actor's messages(...)
wraps that Actor handle's ZLinkStreamActor.on(...). So in
manual dispatch mode, just
like Java, the Kotlin wrapper's dispatch().await() must be called for
the collector to receive a message or error event.
The reply-received hook follows §7 dispatch mode, so Kotlin projects it as
repliesReceived(): Flow. Under §5.7, request sending runs synchronously on the
request-calling thread before frame construction, so Kotlin registers it as
onRequestSending(handler: ZLinkStreamRequestSendingHandler): AutoCloseable.
The handler mutates metadata synchronously and has no result or cancellation parameter.
13. Verification Standard¶
The Java connector has the tests below as a separate suite.
- Public API export test
- Transport scheme inference and mismatch validation
- Header encode/decode roundtrip
- Metadata validation
- Send frame size limit
- Request timeout pending cleanup
- Manual dispatch queue and
pendingDispatchCount - Immediate dispatch callback
- Heartbeat ping/pong and timeout
- Reconnect backoff and max attempts
- Typed handler registry add/remove
- JSON, MessagePack, Protobuf codec smoke
- Typed helper packet name resolver and codec selection
- Typed request/reply decode
- Request hook order, metadata, failure outcome, and callback error
- Structural inbound flow check with value discarded; outbound flag 0x10 unset
- Both packet-name forms on connector and Actor surfaces
- Kotlin coroutine/Flow wrapper smoke