Apache Pulsar binary protocol client for MoonBit: connection management, producer (sync/async send, batching) and consumer (subscription modes, ack/nack, flow control)
| Feature | Linux | macOS |
|---|---|---|
| Plain TCP (pulsar://), compression, RSA message encryption | No OpenSSL or system zlib runtime needed | Same |
| Ordinary TLS (pulsar+ssl://, including a custom CA) | moonbitlang/async@0.22.1 loads libssl.so.3, with fallbacks to libssl.so.1.1 and libssl.so | Uses the system TLS library through moonbitlang/async@0.22.1 |
| TLS with a client certificate or explicit protocol/cipher settings | OpenSSL 3: libssl.so.3 and libcrypto.so.3 | Homebrew openssl@3 |
| ECIES message encryption | OpenSSL 3: libcrypto.so.3 | Homebrew openssl@3 |
moon view pangbit/moonpulsar --versions --jsongit clone https://github.com/pangbit/moonpulsar.git
cd moonpulsar
moon update
moon check --target native
moon test --target nativedocker run --rm --name moonpulsar-demo -p 6650:6650 -p 8080:8080 \
apachepulsar/pulsar:4.2.4 bin/pulsar standalone -a 127.0.0.1moon run examples/roundtrip --target nativeimport {
"pangbit/moonpulsar" @pulsar,
"moonbitlang/async",
}
supported_targets = "+native"
pkgtype(kind: "executable")///|
async fn main {
@async.with_task_group(async fn(group) {
let client = @pulsar.Client::connect(group, "pulsar://127.0.0.1:6650")
let topic = "persistent://public/default/my-topic"
let consumer = client.create_consumer(topic, "my-subscription")
let producer = client.create_producer(topic)
let receipt = producer.send(@pulsar.ProducerMessage::new(b"hello"))
println("sent entry=\{receipt.message_id.entry_id}")
let message = consumer.receive()
message.ack()
consumer.close()
producer.close()
client.close()
})
}pub(open) trait Authentication {
fn method_name(Self) -> String
fn auth_data(Self) -> Bytes
}pub(all) suberror PulsarError {
ConnectionClosed
ConnectFailed(String)
ServerError(code~ : Int, message~ : String)
UnexpectedResponse(String)
LookupFailed(String)
TooManyRedirects
ProducerClosed
ProducerQueueFull
ClientMemoryFull
ConsumerClosed
ChecksumMismatch
InvalidFrame(String)
SchemaIncompatible(String)
RequestTimeout
AuthenticationFailed(String)
} derive(Debug)impl Show for PulsarErrorpub struct AckGroupingOptions {
// private fields
}fn AckGroupingOptions::new(max_size? : Int, max_time_ms? : Int) -> AckGroupingOptions raise PulsarErrorpub struct Admin {
// private fields
}async fn Admin::create_subscription(self : Admin, topic : String, subscription : String, initial_position? : SubscriptionInitialPosition) -> Unitpub(all) struct AthenzZtsOptions {
zts_url : String
provider_domain : String
tenant_domain : String
tenant_service : String
private_key_file : String
certificate_file : String?
ca_pem_file : String?
key_id : String
role : String
principal_header : String
min_expiry_secs : Int
max_expiry_secs : Int
refresh_before_ms : Int
max_retries : Int
retry_delay_ms : Int
}fn AthenzZtsOptions::new(zts_url : String, provider_domain : String, tenant_domain : String, tenant_service : String, private_key_file : String, certificate_file? : String, ca_pem_file? : String, key_id? : String, role? : String, principal_header? : String, min_expiry_secs? : Int, max_expiry_secs? : Int, refresh_before_ms? : Int, max_retries? : Int, retry_delay_ms? : Int) -> AthenzZtsOptions raise PulsarErrorpub(all) struct BasicAuth {
username : String
password : String
}impl Authentication for BasicAuthpub(all) struct BatchingConfig {
max_messages : Int
max_bytes : Int
max_delay_ms : Int
} derive(Debug)pub struct ChunkAssemblyPolicy {
// private fields
}fn ChunkAssemblyPolicy::new(expiry_ms? : Int, max_pending_messages? : Int, auto_ack_incomplete? : Bool) -> ChunkAssemblyPolicy raise PulsarErrorpub struct Client {
// private fields
}async fn Client::connect(group : TaskGroup[Unit], service_url : String, auth? : &Authentication) -> Clientasync fn Client::connect_tls_insecure(group : TaskGroup[Unit], service_url : String, auth? : &Authentication) -> Clientasync fn Client::connect_tls_with_ca(group : TaskGroup[Unit], service_url : String, ca_pem_file : String, auth? : &Authentication) -> Clientasync fn Client::create_consumer(self : Client, topic : String, subscription : String, sub_type? : SubscriptionType, key_shared_policy? : KeySharedPolicy, consumer_name? : String, initial_position? : SubscriptionInitialPosition, priority_level? : Int, durable? : Bool, read_compacted? : Bool, properties? : Map[String, String], subscription_properties? : Map[String, String], receiver_queue_size? : Int, ack_timeout_ms? : Int, nack_backoff? : NackBackoff, dead_letter_policy? : DeadLetterPolicy, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction, encryption_key_names? : Array[String], partition_refresh_ms? : Int, chunk_policy? : ChunkAssemblyPolicy, retry_topic? : String, enable_batch_index_ack? : Bool, ack_grouping? : AckGroupingOptions, auto_scaled_receiver_queue? : Bool, interceptors? : Array[ConsumerInterceptor]) -> Consumerasync fn Client::create_multi_topic_consumer(self : Client, topics : Array[String], subscription : String, sub_type? : SubscriptionType, key_shared_policy? : KeySharedPolicy, consumer_name? : String, initial_position? : SubscriptionInitialPosition, priority_level? : Int, durable? : Bool, read_compacted? : Bool, properties? : Map[String, String], subscription_properties? : Map[String, String], receiver_queue_size? : Int, ack_timeout_ms? : Int, nack_backoff? : NackBackoff, dead_letter_policy? : DeadLetterPolicy, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction, encryption_key_names? : Array[String], partition_refresh_ms? : Int, chunk_policy? : ChunkAssemblyPolicy, retry_topic? : String, enable_batch_index_ack? : Bool, ack_grouping? : AckGroupingOptions, auto_scaled_receiver_queue? : Bool, interceptors? : Array[ConsumerInterceptor]) -> Consumerasync fn Client::create_pattern_consumer(self : Client, ns : String, pattern : String, subscription : String, refresh_interval_ms? : Int, sub_type? : SubscriptionType, key_shared_policy? : KeySharedPolicy, consumer_name? : String, initial_position? : SubscriptionInitialPosition, priority_level? : Int, durable? : Bool, read_compacted? : Bool, properties? : Map[String, String], subscription_properties? : Map[String, String], receiver_queue_size? : Int, ack_timeout_ms? : Int, nack_backoff? : NackBackoff, dead_letter_policy? : DeadLetterPolicy, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction, encryption_key_names? : Array[String], partition_refresh_ms? : Int, chunk_policy? : ChunkAssemblyPolicy, retry_topic? : String, enable_batch_index_ack? : Bool, ack_grouping? : AckGroupingOptions, auto_scaled_receiver_queue? : Bool, interceptors? : Array[ConsumerInterceptor]) -> Consumerasync fn Client::create_producer(self : Client, topic : String, name? : String, properties? : Map[String, String], access_mode? : ProducerAccessMode, batching? : BatchingConfig, compression? : CompressionCodec, message_crypto? : MessageCrypto, encryption_key_names? : Array[String], chunk_size? : Int, automatic_chunking? : Bool, schema? : SchemaInfo, partition_refresh_ms? : Int, routing? : ProducerRoutingMode, max_pending_messages? : Int, block_if_queue_full? : Bool, send_timeout_ms? : Int, interceptors? : Array[ProducerInterceptor]) -> Producerasync fn Client::create_reader(self : Client, topic : String, start_message_id? : MessageId, receiver_queue_size? : Int, read_compacted? : Bool, partition_refresh_ms? : Int, include_start_message_id? : Bool, name? : String, properties? : Map[String, String], subscription_name? : String, subscription_role_prefix? : String, chunk_policy? : ChunkAssemblyPolicy, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction) -> Readerasync fn Client::create_retry_consumer(self : Client, topic : String, subscription : String, retry_topic : String, dead_letter_policy : DeadLetterPolicy, sub_type? : SubscriptionType, key_shared_policy? : KeySharedPolicy, consumer_name? : String, initial_position? : SubscriptionInitialPosition, priority_level? : Int, durable? : Bool, read_compacted? : Bool, properties? : Map[String, String], subscription_properties? : Map[String, String], receiver_queue_size? : Int, ack_timeout_ms? : Int, nack_backoff? : NackBackoff, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction, encryption_key_names? : Array[String], partition_refresh_ms? : Int, chunk_policy? : ChunkAssemblyPolicy, enable_batch_index_ack? : Bool, ack_grouping? : AckGroupingOptions, auto_scaled_receiver_queue? : Bool, interceptors? : Array[ConsumerInterceptor]) -> Consumerasync fn Client::create_table_view(self : Client, topic : String, partition_refresh_ms? : Int, schema? : SchemaInfo, message_crypto? : MessageCrypto) -> TableViewasync fn[T] Client::create_typed_table_view(self : Client, topic : String, codec : SchemaCodec[T], partition_refresh_ms? : Int, message_crypto? : MessageCrypto) -> TypedTableView[T]async fn Client::topics_of_namespace(self : Client, ns : String, pattern? : String, mode? : NamespaceTopicMode) -> Array[String]pub(all) enum ClientEvent {
SendCompleted(String, ProducerMessage, Result[SendReceipt, Error])
MessageReceived(Message)
ReceiveFailed(String, Error)
Acknowledged(String, MessageId, Result[Unit, Error])
NegativelyAcknowledged(String, MessageId, Result[Unit, Error])
ProducerReconnecting(String)
ProducerReconnected(String, Result[Unit, Error])
ConsumerReconnecting(String)
ConsumerReconnected(String, Result[Unit, Error])
}pub struct ClientMetrics {
// private fields
}pub(all) struct ClientMetricsSnapshot {
sends : Int64
send_failures : Int64
received : Int64
receive_failures : Int64
acknowledgments : Int64
ack_failures : Int64
negative_acknowledgments : Int64
negative_ack_failures : Int64
producer_reconnects : Int64
consumer_reconnects : Int64
reconnect_failures : Int64
} derive(Eq, Debug)pub(all) enum ClientOperation {
Send
Receive
Ack
Reconnect
}pub struct ClientOptions {
// private fields
}fn ClientOptions::new(service_url : String, auth? : &Authentication, auth_provider? : async () -> AuthData, connection_timeout_ms? : Int, operation_timeout_ms? : Int, keepalive_interval_ms? : Int, tls_insecure? : Bool, ca_pem_file? : String, tls_identity? : TlsIdentity, tls_identity_provider? : async () -> TlsIdentity, tls_min_version? : TlsVersion, tls_max_version? : TlsVersion, tls_cipher_list? : String, tls13_ciphersuites? : String, listener_name? : String, lookup_properties? : Map[String, String], reconnect_policy? : RetryPolicy, max_connections_per_broker? : Int, connection_idle_timeout_ms? : Int, max_memory_bytes? : Int, on_event? : (ClientEvent) -> Unit, on_span? : (ClientSpanEvent) -> Unit) -> ClientOptions raise PulsarErrorpub(all) struct ClientSpan {
id : Int64
operation : ClientOperation
topic : String
message_id : MessageId?
started_at_ms : Int64
}pub struct Connection {
// private fields
}async fn Connection::connect(group : TaskGroup[Unit], addr : String, auth? : AuthData, auth_provider? : () -> AuthData, tls? : Bool, tls_insecure? : Bool, tls_trust? : TrustedRoot, tls_identity? : TlsIdentity, tls_identity_provider? : async () -> TlsIdentity, tls_min_version? : TlsVersion, tls_max_version? : TlsVersion, tls_cipher_list? : String, tls13_ciphersuites? : String, keepalive_interval_ms? : Int, timeout_ms? : Int, connection_timeout_ms? : Int, async_auth_provider? : async () -> AuthData) -> Connectionasync fn Connection::request(self : Connection, cmd : BaseCommand, timeout_ms? : Int) -> BaseCommandasync fn Connection::send_payload_frame(self : Connection, cmd : BaseCommand, metadata : MessageMetadata, payload : Bytes) -> Unitpub struct ConnectionManager {
// private fields
}fn ConnectionManager::new(group : TaskGroup[Unit], auth? : AuthData, auth_provider? : () -> AuthData, tls? : Bool, tls_insecure? : Bool, tls_trust? : TrustedRoot, tls_identity? : TlsIdentity, tls_identity_provider? : async () -> TlsIdentity, tls_min_version? : TlsVersion, tls_max_version? : TlsVersion, tls_cipher_list? : String, tls13_ciphersuites? : String, connection_timeout_ms? : Int, operation_timeout_ms? : Int, keepalive_interval_ms? : Int, listener_name? : String, lookup_properties? : Map[String, String], async_auth_provider? : async () -> AuthData, max_connections_per_broker? : Int, connection_idle_timeout_ms? : Int) -> ConnectionManager raise PulsarErrorpub struct Consumer {
// private fields
}async fn Consumer::ack_txn(self : Consumer, txn : Transaction, id : MessageId, await_response? : Bool) -> Unitpub struct ConsumerInterceptor {
// private fields
}fn ConsumerInterceptor::new(before_consume? : (Message) -> Unit, on_receive_error? : (Error) -> Unit, on_ack? : (MessageId, Result[Unit, Error]) -> Unit, on_nack? : (MessageId, Result[Unit, Error]) -> Unit) -> ConsumerInterceptorpub struct DeadLetterPolicy {
// private fields
}fn DeadLetterPolicy::new(max_deliveries : UInt, topic : String) -> DeadLetterPolicy raise PulsarErrorpub(all) enum DecryptionFailureAction {
Fail
Consume
Discard
}pub struct EncryptionPrivateKey {
// private fields
}pub struct EncryptionPublicKey {
// private fields
}pub(all) struct IncomingFrame {
cmd : BaseCommand
metadata : MessageMetadata?
payload : Bytes?
wire_size : Int
} derive(Debug)pub struct KeySharedPolicy {
// private fields
}fn KeySharedPolicy::sticky(ranges : Array[(Int, Int)], allow_out_of_order_delivery? : Bool) -> KeySharedPolicy raise PulsarErrorpub struct Message {
publish_time : UInt64
// private fields
}pub struct MessageCrypto {
// private fields
}fn MessageCrypto::add_private_key(self : MessageCrypto, name : String, pem : Bytes) -> Unit raise PulsarErrorfn MessageCrypto::add_public_key(self : MessageCrypto, name : String, pem : Bytes) -> Unit raise PulsarErrorfn MessageCrypto::decrypt(self : MessageCrypto, metadata : MessageMetadata, payload : Bytes) -> Bytes raise PulsarErrorasync fn MessageCrypto::encrypt(self : MessageCrypto, metadata : MessageMetadata, payload : Bytes, key_names : Array[String]) -> Bytes raise PulsarErrorasync fn MessageCrypto::load_private_key_file(self : MessageCrypto, name : String, path : String) -> Unit raise PulsarErrorasync fn MessageCrypto::load_public_key_file(self : MessageCrypto, name : String, path : String) -> Unit raise PulsarErrorfn MessageCrypto::new(public_key_reader? : (String) -> Bytes?, private_key_reader? : (String) -> Bytes?) -> MessageCryptopub struct NackBackoff {
// private fields
}pub struct Producer {
// private fields
}async fn Producer::send_txn(self : Producer, txn : Transaction, msg : ProducerMessage) -> SendReceiptpub struct ProducerInterceptor {
// private fields
}fn ProducerInterceptor::new(before_send? : (ProducerMessage) -> ProducerMessage, on_send_result? : (ProducerMessage, Result[SendReceipt, Error]) -> Unit) -> ProducerInterceptorpub(all) struct ProducerMessage {
payload : Bytes
key : String?
properties : Map[String, String]
event_time : UInt64?
sequence_id : UInt64?
ordering_key : Bytes?
deliver_at : UInt64?
deliver_after : UInt64?
}fn ProducerMessage::new(payload : Bytes, key? : String, properties? : Map[String, String], event_time? : UInt64, sequence_id? : UInt64, ordering_key? : Bytes, deliver_at? : UInt64, deliver_after? : UInt64) -> ProducerMessagepub(all) enum ProducerRoutingMode {
KeyHash
RoundRobin
SinglePartition
Custom((ProducerMessage, Int) -> Int)
}pub struct Reader {
// private fields
}pub struct ReceiptFuture {
// private fields
}pub(all) struct RetryPolicy {
max_retries : Int
initial_backoff_ms : Int
max_backoff_ms : Int
} derive(Debug)pub struct SchemaCodec[T] {
// private fields
}fn[T] SchemaCodec::avro(definition : Bytes, to_datum : (T) -> Datum raise PulsarError, from_datum : (Datum) -> T raise PulsarError) -> SchemaCodec[T] raise PulsarErrorfn[T] SchemaCodec::new(info : SchemaInfo, encode : (T) -> Bytes raise PulsarError, decode : (Bytes) -> T raise PulsarError) -> SchemaCodec[T]fn[M : Read + Write] SchemaCodec::protobuf_native(descriptor_set : Bytes, root_file : String, root_message : String) -> SchemaCodec[M] raise PulsarErrorpub(all) struct SchemaInfo {
name : String
type_ : Schema_Type
schema_data : Bytes
properties : Map[String, String]
} derive(Eq, Debug)fn SchemaInfo::protobuf_native(descriptor_set : Bytes, root_file : String, root_message : String) -> SchemaInfo raise PulsarErrorpub struct SingleConsumer {
// private fields
}async fn SingleConsumer::ack_cumulative(self : SingleConsumer, id : MessageId, await_response? : Bool) -> Unitasync fn SingleConsumer::ack_ids(self : SingleConsumer, ids : Array[MessageId], await_response? : Bool) -> Unitasync fn SingleConsumer::ack_txn(self : SingleConsumer, txn : Transaction, id : MessageId, await_response? : Bool) -> Unitasync fn SingleConsumer::create(group : TaskGroup[Unit], conn : Connection, topic : String, subscription : String, sub_type? : SubscriptionType, key_shared_policy? : KeySharedPolicy, consumer_name? : String, initial_position? : SubscriptionInitialPosition, priority_level? : Int, durable? : Bool, read_compacted? : Bool, properties? : Map[String, String], subscription_properties? : Map[String, String], receiver_queue_size? : Int, ack_timeout_ms? : Int, nack_backoff? : NackBackoff, reconnect_with? : async () -> Connection, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction, reconnect_policy? : RetryPolicy, chunk_policy? : ChunkAssemblyPolicy, enable_batch_index_ack? : Bool, ack_grouping? : AckGroupingOptions, auto_scaled_receiver_queue? : Bool, on_event? : (ClientEvent) -> Unit) -> SingleConsumerpub struct SingleProducer {
// private fields
}async fn SingleProducer::create(group : TaskGroup[Unit], conn : Connection, topic : String, name? : String, properties? : Map[String, String], access_mode? : ProducerAccessMode, batching? : BatchingConfig, reconnect_with? : async () -> Connection, compression? : CompressionCodec, message_crypto? : MessageCrypto, encryption_key_names? : Array[String], chunk_size? : Int, automatic_chunking? : Bool, schema? : SchemaInfo, reconnect_policy? : RetryPolicy, max_pending_messages? : Int, block_if_queue_full? : Bool, send_timeout_ms? : Int) -> SingleProducerasync fn SingleProducer::send_txn(self : SingleProducer, txn : Transaction, msg : ProducerMessage) -> SendReceiptpub struct SingleReader {
// private fields
}async fn SingleReader::create(group : TaskGroup[Unit], conn : Connection, topic : String, start_message_id? : MessageId, receiver_queue_size? : Int, read_compacted? : Bool, include_start_message_id? : Bool, name? : String, properties? : Map[String, String], subscription_name? : String, subscription_role_prefix? : String, chunk_policy? : ChunkAssemblyPolicy, schema? : SchemaInfo, message_crypto? : MessageCrypto, decryption_failure_action? : DecryptionFailureAction) -> SingleReaderfn SubscriptionInitialPosition::equal(SubscriptionInitialPosition, SubscriptionInitialPosition) -> Boolfn SubscriptionInitialPosition::not_equal(x : SubscriptionInitialPosition, y : SubscriptionInitialPosition) -> Boolpub struct TableView {
// private fields
}pub(all) struct TlsIdentity {
certificate_file : String
private_key_file : String
}fn TlsIdentity::new(certificate_file : String, private_key_file : String) -> TlsIdentity raise PulsarErrorpub(all) enum TlsVersion {
TLS12
TLS13
}pub(all) struct TokenAuth {
token : String
}impl Authentication for TokenAuthpub struct Transaction {
// private fields
}async fn Transaction::add_subscription(self : Transaction, topic : String, subscription : String) -> Unitpub struct TransactionCoordinator {
// private fields
}async fn TransactionCoordinator::connect(group : TaskGroup[Unit], addr : String, tc_id : UInt64, auth? : AuthData, tls? : Bool) -> TransactionCoordinatorasync fn TransactionCoordinator::new_transaction(self : TransactionCoordinator, ttl_ms? : UInt64) -> Transactionpub struct TypedTableView[T] {
// private fields
}fn[T] TypedTableView::for_each(self : TypedTableView[T], action : (String, T) -> Unit) -> Unit raise PulsarErrorfn[T] TypedTableView::for_each_and_listen(self : TypedTableView[T], action : (String, Result[T?, PulsarError]) -> Unit) -> Unitfn decompress(codec : CompressionCodec, payload : Bytes, uncompressed_size? : Int) -> Bytes raise PulsarErrorasync fn get_topics_of_namespace(manager : ConnectionManager, addr : String, ns : String, pattern? : String, mode? : NamespaceTopicMode) -> Array[String]async fn lookup_topic(manager : ConnectionManager, addr : String, topic : String, authoritative? : Bool) -> Connectionfn lz4_compress_block(data : BytesView) -> Bytesfn oauth2_client_credentials_provider(token_endpoint : String, client_id : String, client_secret : String, audience? : String, scope? : String) -> (async () -> AuthData) raise PulsarErrorfn snappy_compress(data : Bytes) -> BytesInstall
Download zipApache Pulsar binary protocol client for MoonBit: connection management, producer (sync/async send, batching) and consumer (subscription modes, ack/nack, flow control)