Skip to content

Go client

Complete surface of the Go client package: ClientConfig with defaults, every builder method and its signature, the Admin methods that reach a route that exists, and the streaming SDK.

Updated View as Markdown

Import path github.com/smartpricing/queen/clients/client-go, package name queen. Requires Go 1.24 or newer. Every network method takes a context.Context as its first argument. Version 1.3.0, aligned with the broker’s 1.3 line.

import queen "github.com/smartpricing/queen/clients/client-go"

The streaming SDK is a separate package under .../client-go/streams.

Constructor

func New(config interface{}) (*Queen, error)

config accepts a string (single URL), a []string (multiple URLs), a ClientConfig, or a *ClientConfig. Anything else returns invalid config type: expected string, []string, or ClientConfig.

ClientConfig

Field Type Default applied Effect
URLs []string none Multiple brokers. More than one entry activates the load balancer.
URL string none Single broker, converted to URLs.
TimeoutMillis int 30000 Per-request deadline.
RetryAttempts int 3 Retries after the first attempt, so the default is 4 attempts in total. A negative value means exactly one attempt.
RetryDelayMillis int 1000 First retry delay; doubles per attempt.
LoadBalancingStrategy string "affinity" "affinity", "round-robin" or "session".
AffinityHashRing int 128 Virtual nodes per backend on the hash ring.
EnableFailover bool false in practice (see below) Whether a failing backend is marked unhealthy.
HealthRetryAfterMillis int 5000 How long a backend stays marked unhealthy.
BearerToken string "" Sent as Authorization: Bearer <token>.
Headers map[string]string nil Extra headers on every request.
MaxIdleConnsPerHost int 256 when <= 0 Go’s own default is 2, which cripples a single-host high-concurrency producer pool.
MaxConnsPerHost int 0 (unlimited) Hard cap on total connections per host.
Retry429 *Retry429Config nil HTTP 429 backoff policy.

The exported ClientDefaults, QueueDefaults, ConsumeDefaults, PopDefaults and BufferDefaults variables hold these values, so an application can read one instead of restating it. Unlike the other clients, the load balancer is constructed unconditionally, even for a single URL, so LoadBalancingStrategy and the health settings are always in play.

Retry429Config

Retry429Config (MaxAttempts, BaseMs, CapMs) covers HTTP 429 separately from RetryAttempts, and a zero field means “use the kind-based default”; the defaults, the Retry-After contract and the jitter are in What the clients do with a 429.

*Queen

Method Signature
Queue Queue(name string) *QueueBuilder
Admin Admin() *Admin. Lazily created, one per client
Transaction Transaction() *TransactionBuilder
Ack Ack(ctx, messages interface{}, success bool, opts AckOptions) ([]AckResponse, error)
Renew Renew(ctx, messages interface{}) ([]RenewResponse, error)
FlushAllBuffers FlushAllBuffers(ctx) error
GetBufferStats GetBufferStats() BufferStats
DeleteConsumerGroup DeleteConsumerGroup(ctx, consumerGroup string, deleteMetadata bool) error
UpdateConsumerGroupTimestamp UpdateConsumerGroupTimestamp(ctx, consumerGroup string, timestamp time.Time) error
Close Close(ctx) error. Flushes buffers, then closes the HTTP client
GetHttpClient GetHttpClient() *HttpClient
GetBufferManager GetBufferManager() *BufferManager

Ack

messages accepts *Message, Message, []*Message or []Message; anything else returns an error. success maps to AckStatusCompleted ("completed") or AckStatusFailed ("failed"). AckOptions carries ConsumerGroup and Error.

One message uses POST /api/v1/ack, more than one uses POST /api/v1/ack/batch. Both return one AckResponse{Success, Error} per message in request order. ValidateMessages runs first, so a message missing TransactionID or PartitionID fails before any request is sent.

Renew

Accepts a lease-id string, []string, or any of the message forms. Lease ids are deduplicated first: with multi-partition pop every message in a batch shares one LeaseID and one extend call renews every claimed partition, so passing the whole slice issues one HTTP call. RenewResponse carries LeaseID, Success, NewExpiresAt time.Time and Error.

*QueueBuilder

client.Queue(name). Configuration methods return *QueueBuilder; terminal methods are Push, Pop, Consume, ConsumeBatch, Create, Delete, DLQ and FlushBuffer.

Addressing

Method Default Notes
Name() string none The queue name (used by the streaming SDK).
Partition(name) DefaultPartition ("Default") The ordered lane. Anything else switches pop to the partition-scoped route.
Namespace(name) "" Grouping label at create time; also a pop filter.
Task(name) "" Second grouping label; same dual role.
Group(name) "" Consumer group. Absent, the broker uses QueueModeConsumerGroup ("__QUEUE_MODE__").

Queue lifecycle

Method Wire call Returns
Config(config QueueConfig) none *QueueBuilder
Create() POST /api/v1/configure *OperationBuilder
Delete() DELETE /api/v1/resources/queues/:queue *OperationBuilder

*OperationBuilder has one method: Execute(ctx) (map[string]interface{}, error). Create validates the queue name first and adds "configured": true to the returned map for parity with the other clients.

QueueConfig holds the fields Create can send, each mapped onto its camelCase wire key: LeaseTime, RetryLimit, DelayedProcessing, WindowBuffer, RetentionSeconds, CompletedRetentionSeconds and EncryptionEnabled, whose QueueDefaults values are the shared client-side defaults listed under Queue option defaults, plus three booleans the other clients’ defaults dicts do not carry: RetentionEnabled, DeadLetterQueue and DlqAfterMaxRetries, all false, which by the rule below means they are not sent at all.

Two builder methods sit on *OperationBuilder for this:

Method Effect
Replace(enabled bool) true puts "mode":"replace" at the top level of the body. false, or never calling it, sends no mode key at all, so the request stays byte-identical to every released version of this SDK. Read only by the create path.
Option(key string, value any) Puts one option on the wire literally, applied after the QueueConfig bag and therefore winning over it. This is the only way to send an explicit false or 0, which the omit-the-zero rule above cannot express, and nil sends JSON null, the broker’s “restore this option’s default”. The spelling is the wire’s camelCase and is not validated here.
_, err := client.Queue("orders").
	Config(queen.QueueConfig{LeaseTime: 60}).
	Option("deadLetterQueue", false). // an explicit false, unsendable from QueueConfig
	Option("retentionSinkHold", nil). // null: back to the default
	Create().
	Execute(ctx)

A broker older than 1.6.0 ignores mode and replaces either way, which is what it has always done.

Producing

Method Notes
Buffer(config BufferConfig) {MessageCount, TimeMillis, MaxSize, RetryDelayMillis}; BufferDefaults is 100 / 1000 / 400 / 250. Turns Push into a bounded enqueue.
Push(payload interface{}) *PushBuilder payload is one value or a slice.

*PushBuilder:

Method Notes
TransactionID(id string) Explicit id. Applied to the first item only.
TraceID(id string) Trace id, forwarded only when it is a valid UUID.
Execute(ctx) ([]PushResponse, error) Sends POST /api/v1/push, or enqueues when Buffer is set.

PushResponse carries Status ("queued", "duplicate", "failed"), TransactionID and Error. The client mints a UUIDv7 transactionId for items that have none.

clients/client-go/tests/docs_test.gogo
res, err := client.Queue("orders").
	Partition("customer-42").
	Push(map[string]any{"orderId": 9137, "amount": 99.5}).
	Execute(ctx)
if err != nil {
	return err
}
// res[0].Status == "queued"

Consuming

Method Pop default Consume default Notes
Batch(size) broker-sized broker-sized Total message budget for the call, shared across every partition it claims. Left unset the broker sizes it (pop autopilot); the pre-1.2 client default of 1 comes back with autopilot off.
Partitions(n) broker-sized broker-sized Claim up to N partitions per call; all share one LeaseID. Left unset the broker sizes it (pop autopilot). Sent verbatim when set, 1 included, because a pinned width is a decision the broker must not widen; with autopilot off it is sent only when > 1.
Autopilot(enabled) true true Broker-side pop sizing for the knobs above that you did not set. On by default; false restores the pre-1.2 client defaults (batch 1, partitions 1) and sends no autopilot parameter. QUEEN_SDK_POP_AUTOPILOT=off does the same for a whole process, read once when the client is built. Setting both knobs leaves nothing to decide, so no parameter is sent then either. See pop autopilot. Requires broker >= 1.2; an older one applies its own defaults (batch 200, partitions 1) to the omitted knobs, which is a sizing difference and nothing more.
Wait(enabled) false true Long-poll. Tri-state internally: unset falls back to the per-method default.
TimeoutMillis(ms) 30000 30000 Server-side deadline. Pop adds 5 s of client slack when waiting.
AutoAck(enabled) not sent true The two meanings differ. See the warning.
SubscriptionMode(mode) "" "" SubscriptionModeAll, SubscriptionModeNew, SubscriptionModeNewOnly.
SubscriptionFrom(from) "" "" SubscriptionFromNow ("now") or an ISO timestamp.
Conflation(enabled) false false Last-value delivery: the pop returns only each partition’s newest visible message and retires the rest. Sent only when true. A property of the consumer GROUP, stored on its first registration; see conflation. Requires broker >= 1.1.0; against an older one the call raises rather than draining the backlog quietly.
Concurrency(count) n/a 1 Consume only. Worker goroutines.
Limit(count) n/a 0 (unlimited) Consume only.
IdleMillis(ms) n/a 0 (no timeout) Consume only.
RenewLease(enabled, intervalMillis) n/a false Consume only.
Each() n/a batch mode Consume only. One message per handler call.

Pop

func (qb *QueueBuilder) Pop(ctx context.Context) ([]*Message, error)

Returns queue name, namespace, or task is required when none of the three is set. Route selection: queue plus non-Default partition uses /api/v1/pop/queue/:queue/partition/:partition; queue alone uses /api/v1/pop/queue/:queue; namespace or task alone uses /api/v1/pop.

clients/client-go/tests/docs_test.gogo
messages, err := client.Queue("orders").
	Batch(10).
	Wait(true).
	Pop(ctx)
if err != nil {
	return err
}

PopResult

func (qb *QueueBuilder) PopResult(ctx context.Context) (PopResult, error)

The same call as Pop, returning PopResult{Messages, Autopilot}: the broker’s account of how it sized the claim, with Partitions, Batch, and an optional WaitMillis pacing hint the consume loop honours in place of its own delay between empty polls. Autopilot is nil when the pop did not engage autopilot, when the broker is older than 1.2, or when the answer was a bodiless 204, which has no body to carry an echo. See pop autopilot.

Consume and ConsumeBatch

func (qb *QueueBuilder) Consume(ctx, handler MessageHandler) *ConsumeBuilder
func (qb *QueueBuilder) ConsumeBatch(ctx, handler BatchMessageHandler) *ConsumeBuilder
clients/client-go/tests/docs_test.gogo
err = client.Queue("orders").
	Group("billing").
	SubscriptionMode("all").
	Limit(1).
	Each().
	Consume(ctx, func(ctx context.Context, msg *queen.Message) error {
		fmt.Println(msg.Data)
		return nil
	}).
	Execute(ctx)
if err != nil {
	return err
}

MessageHandler is func(ctx context.Context, msg *Message) error; BatchMessageHandler is func(ctx context.Context, msgs []*Message) error. *ConsumeBuilder exposes Execute(ctx) error and Start(ctx) error (Start is an alias). Both block until every worker stops; cancel the context to stop them.

Worker loop behaviour:

  • A handler error with AutoAck on nacks the message and the worker continues.
  • A 429 that escapes the HTTP layer’s own retry backs off and continues.
  • A 403 stops the worker. Use (*HTTPError).IsClusterSuspended() to distinguish the terminal case.

(*Message).Trace

func (m *Message) Trace(ctx context.Context, config TraceConfig) (*TraceResponse, error)

TraceConfig carries TraceName string, TraceNames []string, EventType string and Data map[string]interface{}. Posts to /api/v1/traces.

Buffering

FlushBuffer(ctx) error flushes this builder’s queue/partition buffer. *BufferManager exposes Add, Flush(ctx, key), FlushAll(ctx), GetStats() BufferStats, GetBuffer(key) and Clear(). BufferStats carries ActiveBuffers, TotalBufferedMessages, OldestBufferAge and FlushesPerformed.

The buffer is bounded and lossless under errors. At MaxSize waiting messages (default 4 * MessageCount), Add blocks, honoring its context, until the flusher drains below the bound. A batch whose POST fails goes back to the front of the buffer, in order, and is retried every RetryDelayMillis; it is never dropped, though occupancy can overshoot MaxSize by that one restored batch. A broker outage therefore shows up as blocked producers and a full buffer, not as silent loss.

Dead-letter queue

DLQ(consumerGroup string) *DLQBuilder.

Method Notes
Limit(count) / Offset(count) Paging.
From(timestamp) / To(timestamp) Time filters.
Get(ctx) (*DLQResponse, error) DLQResponse{Messages []Message, Total int}

The listing is read-only, but Admin.RetryMessage(ctx, partitionID, transactionID) replays. Since broker 1.6.0 it runs on a move: one transaction claims the newest dead-letter row at the address under a row lock, pushes the snapshot back into the log and deletes that row. Only dead-lettered addresses replay, and a live one answers 404.

It is now idempotent by row. The replayed frame carries the deterministic transaction id dlq:<dead-letter row id>, a second call for the same address answers 404 because the row is gone, and two concurrent callers serialise on the lock so the loser is told the row is gone rather than pushing a second copy. Only the addressed consumer group’s record is removed: an address can carry one row per group, and the others stay where they were. A result:"duplicate" verdict means nothing was written and nothing was removed, and a 503 means push maintenance is on, which refuses the move and leaves the row. The client still sends it with failover retry disabled, because it is a write and a blind resend by the transport would hide a verdict the caller has to read. Admin.MoveMessageToDLQ is still a stub, because the broker registers no force-move route.

*TransactionBuilder

client.Transaction(). Pushes and acks in one PostgreSQL transaction, all-or-nothing.

Method Notes
Ack(messages interface{}, status string, opts AckOptions) *TransactionBuilder Same message forms as Queen.Ack. A LeaseID is collected into requiredLeases.
Queue(name string) *TransactionQueueBuilder Sub-builder with Partition(name) and Push(payload) *TransactionBuilder. Pass a PushItem to control that item’s TransactionID, TraceID and Partition; pass anything else and it is treated as a bare payload with a minted id.
Commit(ctx) (*TransactionResponse, error) POST /api/v1/transaction. Check resp.Success as well as err.
clients/client-go/tests/docs_test.gogo
messages, err := client.Queue("orders").
	Group("invoicing").
	SubscriptionMode("all").
	Batch(1).
	Wait(true).
	Pop(ctx)
if err != nil {
	return err
}
message := messages[0]

_, err = client.Transaction().
	Queue("invoices").
	Push(map[string]any{"orderId": message.Data["orderId"], "invoiced": true}).
	Ack(message, queen.AckStatusCompleted, queen.AckOptions{ConsumerGroup: "invoicing"}).
	Commit(ctx)
if err != nil {
	return err
}

The two operations can be given in either order: the builder collects them and Commit sends one request.

A transaction is atomic, not exactly-once end to end; see the limit.

*Admin

client.Admin(). Every method takes a context and returns map[string]interface{}, the broker’s JSON body verbatim, except where noted.

Resources

Method Route
GetOverview(ctx) GET /api/v1/resources/overview
GetNamespaces(ctx) GET /api/v1/resources/namespaces
GetTasks(ctx) GET /api/v1/resources/tasks
ListQueues(ctx, params ListQueuesParams) GET /api/v1/resources/queues
GetQueue(ctx, name) GET /api/v1/resources/queues/:name
GetPartitions(ctx, queueName) GET /api/v1/status/queues/:queue?includePartitions=true

ListQueuesParams carries Namespace, Task, Limit, Offset. GetPartitions requires a queue name and returns the queue-detail payload: the broker bundles partition data there rather than exposing a partitions collection.

Messages, DLQ and traces

Method Route
ListMessages(ctx, params ListMessagesParams) GET /api/v1/messages
GetMessage(ctx, partitionID, transactionID) GET /api/v1/messages/:partitionId/:transactionId
DeleteMessage(ctx, partitionID, transactionID) DELETE /api/v1/messages/:partitionId/:transactionId
ListDLQ(ctx, params ListDLQParams) GET /api/v1/dlq
GetTraceNames(ctx, limit, offset) GET /api/v1/traces/names
GetTracesByName(ctx, traceName, limit, offset) GET /api/v1/traces/by-name/:traceName
GetTracesForMessage(ctx, partitionID, transactionID) GET /api/v1/traces/:partitionId/:transactionId

ListMessagesParams carries Queue, Partition, Status, ConsumerGroup, Limit, Offset. ListDLQParams carries Queue, ConsumerGroup, Partition, From, To, Limit, Offset.

Status and analytics

Method Route
GetStatus(ctx, params GetStatusParams) GET /api/v1/status
GetQueueStats(ctx, namespace, task) GET /api/v1/status/queues
GetQueueDetail(ctx, name, includePartitions bool) GET /api/v1/status/queues/:name
GetAnalytics(ctx, from, to) GET /api/v1/status/analytics
GetQueueLagAnalytics(ctx, from, to) GET /api/v1/analytics/queue-lag
GetQueueOpsAnalytics(ctx, from, to) GET /api/v1/analytics/queue-ops
GetQueueParkedReplicas(ctx, from, to) GET /api/v1/analytics/queue-parked-replicas
GetRetentionAnalytics(ctx, from, to) GET /api/v1/analytics/retention
GetSystemMetrics(ctx, from, to) GET /api/v1/analytics/system-metrics
GetWorkerMetrics(ctx, from, to) GET /api/v1/analytics/worker-metrics
GetPostgresStats(ctx) GET /api/v1/analytics/postgres-stats

GetStatusParams carries Queue, Namespace, Task.

Consumer groups

Method Route
ListConsumerGroups(ctx) GET /api/v1/consumer-groups
GetConsumerGroup(ctx, name) GET /api/v1/consumer-groups/:name
GetLaggingConsumers(ctx, minLagSeconds) GET /api/v1/consumer-groups/lagging?minLagSeconds=
DeleteConsumerGroupForQueue(ctx, group, queue, deleteMetadata) DELETE /api/v1/consumer-groups/:group/queues/:queue
SeekConsumerGroup(ctx, group, queue, opts SeekConsumerGroupOptions) POST /api/v1/consumer-groups/:group/queues/:queue/seek
SeekConsumerGroupPartition(ctx, group, queue, partition, opts) POST /api/v1/consumer-groups/:group/queues/:queue/partitions/:partition/seek
RefreshConsumerStats(ctx) POST /api/v1/stats/refresh

SeekConsumerGroupOptions carries Timestamp string (RFC3339) and ToEnd bool, which are mutually exclusive. The wire body is {"toEnd": true} or {"timestamp": "…"}.

System

Method Route Returns
Health(ctx) GET /health map
Metrics(ctx) GET /metrics (string, error): the raw body when it is not JSON, "" when it parsed as JSON
PrometheusMetrics(ctx) GET /metrics/prometheus (string, error)
GetMaintenanceMode(ctx) GET /api/v1/system/maintenance map
SetMaintenanceMode(ctx, enabled) POST /api/v1/system/maintenance map
GetPopMaintenanceMode(ctx) GET /api/v1/system/maintenance/pop map
SetPopMaintenanceMode(ctx, enabled) POST /api/v1/system/maintenance/pop map

Errors

Non-2xx responses come back as *HTTPError:

var he *queen.HTTPError
if errors.As(err, &he) && he.StatusCode == 429 { /* ... */ }
Field Notes
StatusCode int HTTP status.
Body string Raw body.
Code string The body’s code field, empty when the body has none; the proxy’s stable values are listed under Behind the proxy.
RetryAfterSeconds *float64 Parsed from the Retry-After header on a 429; nil when absent or non-numeric.

IsClusterSuspended() reports the terminal 403 that no amount of retrying resolves. Error() renders as HTTP <status> [<code>]: <body>.

A 204 response yields a nil map: a 204 carries no body at all.

*HttpClient

Reachable via GetHttpClient() when you need a route the SDK does not wrap.

Method Notes
Get(ctx, path string, timeoutMs int, affinityKey string, opts ...RequestOption) timeoutMs of 0 uses the client default.
Post(ctx, path, body interface{}, opts ...RequestOption)
PostWithAffinity(ctx, path, body, affinityKey, opts ...RequestOption)
Delete(ctx, path, opts ...RequestOption)
GetLoadBalancer()
Close()

All four return (map[string]interface{}, error); a top-level JSON array is wrapped as {"data": [...]}. WithLongPollRetry() is the only RequestOption: it marks the request as a long-poll pop so a 429 backs off indefinitely instead of using the bounded budget.

Logging

Set by the QUEEN_CLIENT_LOG environment variable, read once at package init:

Value Level
unset LogLevelNone
debug, true, 1 LogLevelDebug
info LogLevelInfo
warn, warning LogLevelWarn
error LogLevelError
anything else LogLevelNone

SetLogLevel(level LogLevel) and GetLogLevel() LogLevel change it at runtime.

Helpers

Function Notes
GenerateUUID() string The id used for transactionId.
GenerateUUIDv4() string, GenerateUUIDv7() (string, error) Explicit versions.
ParseUUID(s) (uuid.UUID, error), MustParseUUID(s) uuid.UUID
IsValidUUID(s) bool
ValidateURL(raw) (string, error), ValidateURLs(urls) ([]string, error)
ValidateQueueName, ValidatePartitionName, ValidateConsumerGroup (string, error): normalise and reject.
ValidateBatchSize, ValidateConcurrency, ValidateTimeout error.
ValidateMessage(*Message) error, ValidateMessages([]*Message) error Run by Ack before any request.

Message

Field JSON Notes
TransactionID transactionId
PartitionID partitionId Required for every ack.
LeaseID leaseId Empty when the pop used server-side auto-ack.
Queue, Partition queue, partition
Data data map[string]interface{}.
CreatedAt createdAt
ErrorMessage errorMessage
RetryCount retryCount
ProducerSub producerSub The authenticated producer identity, stamped by the broker from the JWT sub at push time. Present only when JWT auth is on. Clients cannot set it.

Streaming SDK

import "github.com/smartpricing/queen/clients/client-go/streams"

s := streams.From(queen.Queue("orders").AsStreamSource())

AsStreamSource() on a *QueueBuilder adapts it to runtime.Source. Every combinator returns a new *Stream.

Group Methods
Stateless Map(MapFn), Filter(FilterFn), FlatMap(FlatMapFn)
Keying KeyBy(KeyFn)
Windows WindowTumbling(seconds float64, opts ...WindowOption), WindowSliding(size, slide float64, opts ...), WindowSession(gap float64, opts ...), WindowCron(every string, opts ...)
Reduce Reduce(ReduceFn, initial interface{}), Aggregate(map[string]ExtractorFn, fieldOrder ...string)
Gate Gate(GateFn)
Sink To(Sink), ToPartitioned(Sink, resolver), Foreach(ForeachFn)
Terminal Run(ctx, opts RunOptions) (*Runner, error), Compile() (*runtime.CompiledStream, error)

Every WindowX constructor takes the same five WindowOption values, which control when a window closes and what happens to whatever arrives after it did:

Option Default What it controls
WithGracePeriod(seconds float64) 0 Seconds a window stays open past its own end before it closes, so a straggler still lands in the bucket it belongs to.
WithIdleFlushMs(ms int64) 5000, but 1000 for WindowSession and 30000 for WindowCron How often the runner sweeps partitions that have gone quiet for windows that are ripe to close. 0 disables the sweep, and a window on a silent partition then stays open until traffic returns.
WithEventTime(fn EventTimeFn) none Switches bucketing from the broker’s createdAt to your own timestamp, and turns on a per-partition watermark.
WithAllowedLateness(seconds float64) 0 Event-time mode only. Seconds of out-of-orderness tolerated before an event is called late, measured against that partition’s watermark.
WithOnLate(policy string) "drop" What happens to an event older than the watermark minus the allowed lateness. "drop" is the only value that keeps emits exact. "include" accumulates it anyway, which for an already-flushed window recreates the state row and produces a second emit for it.

To(sink) reuses the source partition name for each push; ToPartitioned(sink, resolver) chooses the destination partition instead, resolver being a fixed partition string or a func(value interface{}) string called per emit (anything else panics), the mirror of the JavaScript .to(queue, { partition }) form. The resolver is not part of the config hash, so switching between the two, or changing the resolver, neither trips the registration mismatch check nor resets query state.

streams/helpers provides TokenBucketGate(TokenBucketGateOptions) and SlidingWindowGate(SlidingWindowGateOptions), both returning a GateFn.

RunOptions

Field Default
QueryID required, the durable identity
URL required, the broker base URL for the /streams/v1/* calls
BearerToken ""
BatchSize 200
MaxPartitions 4
MaxWaitMillis 1000
SubscriptionMode ""
SubscriptionFrom ""
Conflation false. Requests last-value delivery on the source pop; the reducer only ever sees each partition’s newest message, so messages conflation skips never reach it. See conflation
Reset false (wipe state on a config-hash mismatch)
ConsumerGroup streams.<QueryID>
Logger nil

*Runner exposes Metrics() Metrics with the counters CyclesTotal, FlushCyclesTotal, MessagesTotal, PushItemsTotal, StateOpsTotal, LateEventsTotal, ErrorsTotal, GateAllowsTotal, GateDenialsTotal and LastError.

The chain shape is fingerprinted into a config hash; re-deploying a different chain under the same QueryID is rejected at registration unless Reset is set. Only operator kinds and their structural config are hashed, not the bodies of your functions.

Same surface, other languages

Parity here is a claim about the surface, not about shared code: each client is written natively in its own idiom. These sixteen rows are that surface, read out of clients/ on this tree. Six of them are full parity across all six clients; the other ten are where a client stops.

Capability JavaScript Python Go Rust PHP C++
Push, pop, ack, multi-partition claim yes yes yes yes yes yes
Client-side push buffering yes yes yes yes yes yes
Transactions through POST /api/v1/transaction yes yes yes yes yes yes
Dead-letter reader yes yes yes yes yes yes
HTTP 429 policy, separate from the retry counter yes yes yes yes yes yes
kv and timers riders on a transaction yes yes yes yes yes yes
Key/value: the seven operations yes yes yes yes yes five of seven
Timers: schedule, cancel, peek, list yes yes yes yes yes two of four
once, the idempotency marker in one call yes yes no no no no
Admin facade yes yes yes yes yes no
affinity load-balancing strategy yes yes yes yes yes no
Streams DSL over /streams/v1/* yes yes yes yes no no
Lease renewal inside the consume loop yes yes yes yes yes no
Per-message trace helper yes yes yes no yes no
Host override independent of the address dialled yes no no yes no no
pop reports a failure instead of an empty result no no yes yes yes no

The ten divergent rows, with their conditions:

  • Key/value. The seven operations are get, getMany, getPrefix, put, putIfAbsent, delete and incr. C++ wraps five of them: getMany and getPrefix have no method, and both remain reachable through POST /api/v1/kv on the shared HttpClient. getPrefix inside a transaction is refused by the broker in every client, which is its rule and not a client’s gap.
  • Timers. C++ has schedule and cancel only; peek and list are reads and stay on GET /api/v1/timers/{queue}[/{timerKey}]. There is no reschedule operation anywhere, because schedule is the upsert and status reports which happened; Python, Go, Rust and PHP still spell a reschedule alias over it, JavaScript and C++ do not.
  • once. JavaScript (kv.once, transaction().once) and Python (kv.once, transaction().once) fold putIfAbsent plus required into the question people actually ask, “did I win?”. The other four write the putIfAbsent themselves, which is the same wire and one line longer.
  • kv on a streams operator context. No client has it, so it is not a row. Inside a stream the state primitive is state_ops, which commits with the sink and the ack in the cycle’s own transaction; a key/value write from an operator would not, and that atomicity is the thing the stream already gives you for free.
  • Admin facade. C++ has no Admin class. Every management and observability route is still reachable, through the shared HttpClient returned by get_http_client().
  • affinity. The C++ LoadBalancer implements round-robin and "session" only, and load_balancing_strategy = "affinity" falls through to round-robin silently. The strategy matters because it keeps one consumer’s pops on one backend, which is what keeps two clients from contending on the same partition claim.
  • Streams. The Stream builder, the four window kinds, gates and the /streams/v1/* runtime are in the JavaScript, Python, Go and Rust clients. PHP and C++ have none of it.
  • Lease renewal. In C++, renew_lease() sets a flag the consumer loop does not act on: the worker carries an unimplemented placeholder where the timer would go. Call client.renew(message) yourself before the lease expires.
  • Per-message trace. Where it exists it is attached by the consume loop, so a message returned by a manual pop() never carries it. Rust records a trace through Admin::record_trace instead of hanging a method off the message. C++ cannot hang one off a JSON object at all, and its trace hook is an explicit no-op.
  • Host override. JavaScript (hostHeader) and Rust (host_header) advertise a request authority, and a TLS SNI, independent of the address the socket dials. That is what addresses a named tenant cluster behind a shared proxy endpoint. The other four clients send the dialled address.
  • pop failures. Go, Rust and PHP surface a 4xx, an exhausted 429 budget, a terminal 403 or a network fault to the caller, so an empty result means an empty queue. JavaScript, Python and C++ log the failure and return an empty result, which does not distinguish an empty queue from revoked credentials.

The go suite in the test matrix runs the root-package unit tests, then ./tests/ and ./tests/streams_integration/ with -count=1 so a cached result can never hide a live-broker failure, on the single, ha and tenanted topologies. The ./streams/... package is green at 13 of 13 on this tree, with one known deterministic failure named on that page.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close