kubemq-js
Version:
KubeMQ SDK for Node.js — gRPC-based messaging client for Events, Queues, Commands, and Queries
1,366 lines (1,354 loc) • 106 kB
text/typescript
/**
* Machine-readable error codes identifying the specific failure condition.
*
* @remarks
* Every {@link KubeMQError} carries an `ErrorCode`. Use these constants in
* `switch` / `if` checks rather than comparing error message strings.
*
* @see {@link KubeMQError.code}
* @see {@link ErrorCategory}
*/
declare const ErrorCode: {
/** The initial connection or a reconnection attempt timed out. */
readonly ConnectionTimeout: "CONNECTION_TIMEOUT";
/** Authentication credentials were rejected by the server. */
readonly AuthFailed: "AUTH_FAILED";
/** A message or request failed input validation before being sent. */
readonly ValidationFailed: "VALIDATION_FAILED";
/** The server is temporarily unavailable (transient). */
readonly Unavailable: "UNAVAILABLE";
/** An operation exceeded its deadline. */
readonly Timeout: "TIMEOUT";
/** The server throttled the request due to rate limiting. */
readonly Throttled: "THROTTLED";
/** The requested channel or resource does not exist. */
readonly NotFound: "NOT_FOUND";
/** The authenticated identity lacks permission for this operation. */
readonly PermissionDenied: "PERMISSION_DENIED";
/** An unrecoverable internal error. */
readonly Fatal: "FATAL";
/** The operation was cancelled via `AbortSignal`. */
readonly Cancelled: "CANCELLED";
/** The reconnect buffer is full and cannot accept more messages. */
readonly BufferFull: "BUFFER_FULL";
/** A streaming connection broke mid-flight. */
readonly StreamBroken: "STREAM_BROKEN";
/** The client has been closed; no further operations are allowed. */
readonly ClientClosed: "CLIENT_CLOSED";
/** The requested feature is not implemented in this SDK version. */
readonly NotImplemented: "NOT_IMPLEMENTED";
/** A client configuration value is invalid. */
readonly ConfigurationError: "CONFIGURATION_ERROR";
/** The transport connection is not in a ready state. */
readonly ConnectionNotReady: "CONNECTION_NOT_READY";
/** All retry attempts have been exhausted. */
readonly RetryExhausted: "RETRY_EXHAUSTED";
};
/** @see {@link ErrorCode} */
type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
/**
* Broad error categories for high-level error handling strategies.
*
* @remarks
* While {@link ErrorCode} identifies the specific failure, `ErrorCategory`
* groups errors by recovery strategy:
* - **Transient / Timeout / Throttling** — safe to retry with backoff.
* - **Authentication / Authorization** — fix credentials or permissions.
* - **Validation / Configuration** — fix the input or config, then retry.
* - **Fatal / Cancellation** — not recoverable by retry.
* - **Backpressure** — slow down or increase buffer capacity.
* - **NotFound** — the target channel or resource doesn't exist.
*
* @see {@link KubeMQError.category}
* @see {@link ErrorCode}
*/
declare const ErrorCategory: {
/** A temporary failure that may self-resolve (e.g. network blip). */
readonly Transient: "Transient";
/** An operation exceeded its deadline. */
readonly Timeout: "Timeout";
/** The server is rate-limiting the client. */
readonly Throttling: "Throttling";
/** Credentials are invalid or expired. */
readonly Authentication: "Authentication";
/** The identity lacks required permissions. */
readonly Authorization: "Authorization";
/** Input did not pass validation rules. */
readonly Validation: "Validation";
/** The target resource was not found. */
readonly NotFound: "NotFound";
/** Unrecoverable failure. */
readonly Fatal: "Fatal";
/** The operation was explicitly cancelled. */
readonly Cancellation: "Cancellation";
/** The system is applying backpressure (buffer full). */
readonly Backpressure: "Backpressure";
/** A configuration parameter is invalid. */
readonly Configuration: "Configuration";
};
/** @see {@link ErrorCategory} */
type ErrorCategory = (typeof ErrorCategory)[keyof typeof ErrorCategory];
/**
* Construction options shared by all {@link KubeMQError} subclasses.
*
* @see {@link KubeMQError}
*/
interface KubeMQErrorOptions {
/** Machine-readable error code. Defaults to {@link ErrorCode.Fatal}. */
code?: ErrorCode;
/** Human-readable error description. */
message: string;
/** The SDK operation that failed (e.g. `'sendEvent'`). */
operation: string;
/** The channel involved, if any. */
channel?: string;
/** Whether the operation is safe to retry. */
isRetryable?: boolean;
/** The underlying cause, if this error wraps another. */
cause?: Error;
/** Correlation ID for tracing. Auto-generated UUID if omitted. */
requestId?: string;
/** gRPC status code, when the error originates from the server. */
statusCode?: number;
/** Address of the KubeMQ server that returned the error. */
serverAddress?: string;
/** Actionable suggestion for resolving the error. */
suggestion?: string;
/** Number of retry attempts made before this error was raised. */
retryAttempts?: number;
/** Maximum retries configured in the active {@link RetryPolicy}. */
maxRetries?: number;
/** Total wall-clock time spent retrying, in milliseconds. */
retryDuration?: number;
}
/**
* Construction options for {@link StreamBrokenError}.
*
* @see {@link StreamBrokenError}
*/
interface StreamBrokenErrorOptions extends KubeMQErrorOptions {
/** IDs of messages that were sent but not acknowledged before the stream broke. */
unacknowledgedMessageIds: string[];
}
/**
* Construction options for {@link RetryExhaustedError}.
*
* @see {@link RetryExhaustedError}
*/
interface RetryExhaustedErrorOptions extends KubeMQErrorOptions {
/** Total number of attempts made (initial + retries). */
attempts: number;
/** Total wall-clock time spent across all retry attempts, in milliseconds. */
totalDuration: number;
/** The error from the final failed attempt. */
lastError: Error;
}
/**
* Construction options for {@link PartialFailureError}.
*
* @see {@link PartialFailureError}
*/
interface PartialFailureErrorOptions extends KubeMQErrorOptions {
/** Per-item failures with their batch index and error. */
failures: {
index: number;
error: KubeMQError;
}[];
}
/**
* Base error class for all KubeMQ SDK errors.
*
* @remarks
* All errors thrown by the SDK are instances of `KubeMQError` (or a subclass).
* Use `instanceof KubeMQError` for broad catches, or check specific subclasses
* for targeted handling. Cross-version `instanceof` safety is provided via
* `Symbol.hasInstance`.
*
* Key properties for error handling:
* - {@link KubeMQError.code | code} — machine-readable {@link ErrorCode}
* - {@link KubeMQError.category | category} — broad {@link ErrorCategory} for strategy selection
* - {@link KubeMQError.isRetryable | isRetryable} — whether automatic retry is appropriate
* - {@link KubeMQError.suggestion | suggestion} — actionable fix hint
*
* @see {@link ErrorCode}
* @see {@link ErrorCategory}
*/
declare class KubeMQError extends Error {
/**
* Cross-version instanceof check via well-known symbol.
* Only used on the base class — subclass discrimination uses the
* standard prototype chain (preserved by Object.setPrototypeOf).
*/
static [Symbol.hasInstance](instance: unknown): boolean;
name: string;
readonly code: ErrorCode;
readonly operation: string;
readonly channel: string | undefined;
readonly isRetryable: boolean;
readonly requestId: string;
readonly statusCode: number | undefined;
readonly serverAddress: string | undefined;
readonly timestamp: Date;
readonly category: ErrorCategory;
readonly suggestion: string | undefined;
constructor(options: KubeMQErrorOptions);
toJSON(): Record<string, unknown>;
toSanitizedString(): string;
}
/**
* Thrown when the SDK cannot establish or maintain a connection to the server.
*
* @remarks
* Retryable by default. The reconnection policy handles automatic recovery;
* this error surfaces only if reconnection is disabled or exhausted.
*
* @see {@link KubeMQError}
* @see {@link ConnectionNotReadyError}
*/
declare class ConnectionError extends KubeMQError {
readonly category: "Transient";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when the server rejects the provided authentication credentials.
*
* @remarks
* Not retryable. Verify the token or {@link CredentialProvider} configuration.
*
* @see {@link KubeMQError}
* @see {@link AuthorizationError}
*/
declare class AuthenticationError extends KubeMQError {
readonly category: "Authentication";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when the authenticated identity lacks permission for the requested operation.
*
* @remarks
* Not retryable. The credentials are valid but the associated role/policy
* does not grant access to the target channel or operation.
*
* @see {@link KubeMQError}
* @see {@link AuthenticationError}
*/
declare class AuthorizationError extends KubeMQError {
readonly category: "Authorization";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when an operation exceeds its configured timeout.
*
* @remarks
* Retryable by default. Consider increasing the timeout via
* {@link OperationOptions.timeout} or the relevant default in {@link ClientOptions}.
*
* @see {@link KubeMQError}
*/
declare class KubeMQTimeoutError extends KubeMQError {
readonly category: "Timeout";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when a message or request fails client-side validation before sending.
*
* @remarks
* Not retryable. Fix the invalid input (e.g. empty channel name, missing body)
* and re-submit. The `suggestion` property usually indicates what to fix.
*
* @see {@link KubeMQError}
*/
declare class ValidationError extends KubeMQError {
readonly category: "Validation";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown for temporary server-side failures that are expected to self-resolve.
*
* @remarks
* Retryable by default. The SDK's built-in retry policy handles these
* automatically; this error surfaces only when retries are exhausted.
*
* @see {@link KubeMQError}
* @see {@link RetryPolicy}
*/
declare class TransientError extends KubeMQError {
readonly category: "Transient";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when the server rate-limits the client.
*
* @remarks
* Retryable by default with backoff. Reduce request rate or increase
* server-side rate limits.
*
* @see {@link KubeMQError}
*/
declare class ThrottlingError extends KubeMQError {
readonly category: "Throttling";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when the target channel or resource does not exist on the server.
*
* @remarks
* Not retryable. Create the channel first via
* {@link KubeMQClient.createChannel} or one of the convenience aliases.
*
* @see {@link KubeMQError}
*/
declare class NotFoundError extends KubeMQError {
readonly category: "NotFound";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown for unrecoverable internal failures.
*
* @remarks
* Not retryable by default. Indicates a server-side or SDK-internal bug.
* Report to the KubeMQ team if recurring.
*
* @see {@link KubeMQError}
*/
declare class FatalError extends KubeMQError {
readonly category: "Fatal";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when an operation is cancelled via `AbortSignal`.
*
* @remarks
* Not retryable. This is the expected error when cooperative cancellation
* is triggered by the caller through {@link OperationOptions.signal}.
*
* @see {@link KubeMQError}
*/
declare class CancellationError extends KubeMQError {
readonly category: "Cancellation";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when the reconnect buffer is full and cannot accept more messages.
*
* @remarks
* Not retryable. Increase {@link ClientOptions.reconnectBufferSize} or
* switch `reconnectBufferMode` to `'block'` for flow control.
*
* @see {@link KubeMQError}
* @see {@link ClientOptions.reconnectBufferSize}
*/
declare class BufferFullError extends KubeMQError {
readonly category: "Backpressure";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when a streaming connection (subscription or queue stream) breaks mid-flight.
*
* @remarks
* Retryable by default. Carries `unacknowledgedMessageIds` — the IDs of messages
* that were sent but not yet acknowledged at the time the stream broke.
* Application code should decide whether to re-send or deduplicate these messages.
*
* @see {@link KubeMQError}
* @see {@link StreamBrokenErrorOptions}
*/
declare class StreamBrokenError extends KubeMQError {
readonly category: "Transient";
readonly unacknowledgedMessageIds: string[];
constructor(options: StreamBrokenErrorOptions);
}
/**
* Thrown when an operation is attempted on a client that has already been closed.
*
* @remarks
* Not retryable. Create a new {@link KubeMQClient} instance if further
* operations are needed.
*
* @see {@link KubeMQError}
* @see {@link KubeMQClient.close}
*/
declare class ClientClosedError extends KubeMQError {
readonly category: "Fatal";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when an operation is attempted before the transport connection is ready.
*
* @remarks
* Not retryable by default. Usually indicates the client is still connecting
* or in the process of reconnecting. Wait for the `'connected'` or
* `'reconnected'` event before retrying.
*
* @see {@link ConnectionError}
* @see {@link ConnectionState}
*/
declare class ConnectionNotReadyError extends ConnectionError {
readonly category: "Transient";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when {@link ClientOptions} contain invalid or conflicting values.
*
* @remarks
* Not retryable. Fix the configuration and create a new client.
*
* @see {@link KubeMQError}
* @see {@link ClientOptions}
*/
declare class ConfigurationError extends KubeMQError {
readonly category: "Configuration";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when all retry attempts for an operation have been exhausted.
*
* @remarks
* Not retryable. Carries detailed retry diagnostics: `attempts`, `totalDuration`,
* and `lastError` (the error from the final attempt). Consider increasing
* {@link RetryPolicy.maxRetries} or investigating the root cause via `lastError`.
*
* @see {@link KubeMQError}
* @see {@link RetryPolicy}
* @see {@link RetryExhaustedErrorOptions}
*/
declare class RetryExhaustedError extends KubeMQError {
readonly attempts: number;
readonly maxRetries: number;
readonly totalDuration: number;
readonly lastError: Error;
constructor(options: RetryExhaustedErrorOptions);
toSanitizedString(): string;
}
/**
* Thrown when a requested feature is not implemented in this SDK version.
*
* @remarks
* Not retryable. Check the SDK release notes for feature availability
* or use an alternative API surface.
*
* @see {@link KubeMQError}
*/
declare class NotImplementedError extends KubeMQError {
readonly category: "Fatal";
constructor(options: KubeMQErrorOptions);
}
/**
* Thrown when a batch operation partially succeeds — some items failed while others succeeded.
*
* @remarks
* Inspect the {@link PartialFailureError.failures | failures} array for per-item
* error details including the batch `index` and the associated {@link KubeMQError}.
*
* @see {@link KubeMQError}
* @see {@link PartialFailureErrorOptions}
* @see {@link KubeMQClient.sendQueueMessagesBatch}
*/
declare class PartialFailureError extends KubeMQError {
readonly failures: {
index: number;
error: KubeMQError;
}[];
constructor(options: PartialFailureErrorOptions);
}
/**
* Thrown when a user-provided callback or handler throws an unhandled error.
*
* @remarks
* Not retryable by default. Wraps the original error thrown by the user's
* subscription callback or event handler. Fix the handler code to prevent
* unhandled exceptions.
*
* @see {@link KubeMQError}
*/
declare class HandlerError extends KubeMQError {
readonly category: "Fatal";
constructor(options: KubeMQErrorOptions);
}
/**
* Connection lifecycle states.
*
* Transition diagram:
* IDLE ──> CONNECTING ──> READY
* ^ │ │
* │ v v
* │ RECONNECTING ──> READY
* │ │ ↺ (self)
* v v
* CLOSED (terminal)
*
* RECONNECTING → RECONNECTING is a valid self-transition representing
* a new reconnection attempt. The 'reconnecting' event fires on each attempt.
*
* @internal
*/
declare enum ConnectionState {
IDLE = "IDLE",
CONNECTING = "CONNECTING",
READY = "READY",
RECONNECTING = "RECONNECTING",
CLOSED = "CLOSED"
}
/** @internal — not part of public API */
interface TransportCallOptions {
deadline?: Date;
signal?: AbortSignal;
}
interface StreamHandle<TWrite, TRead> {
write(msg: TWrite): boolean;
onData(handler: (msg: TRead) => void): void;
onError(handler: (err: Error) => void): void;
onEnd(handler: () => void): void;
cancel(): void;
end(): void;
/** Pause the readable side of the stream (C3 backpressure). No-op if not supported. */
pause(): void;
/** Resume the readable side of the stream (C3 backpressure). No-op if not supported. */
resume(): void;
/** Remove all listeners from the underlying stream (H2 rebind cleanup). */
removeAllListeners(): void;
/** Register a one-shot handler for when the write buffer drains (writable-side backpressure). */
onDrain(handler: () => void): void;
}
interface Transport {
readonly state: ConnectionState;
connect(): Promise<void>;
close(timeoutMs?: number): Promise<void>;
unaryCall<TReq, TRes>(method: string, request: TReq, options?: TransportCallOptions): Promise<TRes>;
serverStream<TReq, TRes>(method: string, request: TReq, options?: TransportCallOptions): StreamHandle<never, TRes>;
duplexStream<TReq, TRes>(method: string, options?: TransportCallOptions): StreamHandle<TReq, TRes>;
getMetadata(): Record<string, string>;
setMetadata(key: string, value: string): void;
on(event: 'stateChange', handler: (state: ConnectionState) => void): void;
off(event: 'stateChange', handler: (state: ConnectionState) => void): void;
}
/**
* Structured logging interface. Users inject their preferred logger
* (pino, winston, bunyan, etc.) via ClientOptions.
*
* Default: noopLogger — zero output unless configured.
*/
interface Logger {
debug(msg: string, fields?: Record<string, unknown>): void;
info(msg: string, fields?: Record<string, unknown>): void;
warn(msg: string, fields?: Record<string, unknown>): void;
error(msg: string, fields?: Record<string, unknown>): void;
}
/** Structured key-value context attached to log entries. */
type LogContext = Record<string, unknown>;
/** Log severity levels. 'off' disables all output. */
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
/**
* Default no-op logger. All methods are empty — zero overhead,
* zero output. Users replace this via ClientOptions.logger.
*/
declare const noopLogger: Logger;
/**
* Creates a console-based logger filtered by level.
* Intended for development/debugging — NOT used internally by the SDK.
*
* @example
* ```ts
* import { createConsoleLogger } from 'kubemq-js';
*
* const client = await KubeMQClient.create({
* address: 'localhost:50000',
* logger: createConsoleLogger('debug'),
* });
* ```
*/
declare function createConsoleLogger(level: LogLevel): Logger;
/**
* @internal — Abstract base class for shared bidi streaming senders.
*
* Provides: bounded queue, setImmediate-based drain loop with write
* backpressure, transport state integration (no self-reconnect),
* deadline sweep, AbortSignal support, error handler fan-out, and
* observability stats.
*
* Subclasses implement openStream(), extractPendingKey(), handleResponse().
*/
type SenderStreamState = 'initializing' | 'connected' | 'reconnecting' | 'closed';
interface SenderStats {
queueDepth: number;
pendingAcks: number;
streamState: SenderStreamState;
reconnectionCount: number;
}
/**
* Pluggable credential provider interface for authentication.
*
* Implementations must be safe for concurrent invocation from
* the event loop (the SDK serializes calls, but user code may not).
*
* The SDK caches the returned token and re-invokes the provider only when:
* - No cached token exists
* - The cached token is invalidated by a server UNAUTHENTICATED response
* - Proactive refresh determines the token is approaching expiry
*
* At most one outstanding getToken() call is in flight at any time.
*/
interface CredentialProvider {
getToken(): Promise<{
token: string;
expiresAt?: Date;
}>;
}
/**
* Simple credential provider that returns a fixed authentication token.
*
* @remarks
* Suitable for development or environments where tokens do not expire.
* For production with rotating tokens, implement a custom {@link CredentialProvider}.
*
* Passing a plain string to {@link ClientOptions.credentials} automatically
* wraps it in a `StaticTokenProvider`.
*
* @see {@link CredentialProvider}
* @see {@link ClientOptions.credentials}
*/
declare class StaticTokenProvider implements CredentialProvider {
#private;
constructor(token: string);
getToken(): Promise<{
token: string;
expiresAt?: Date;
}>;
toString(): string;
toJSON(): Record<string, unknown>;
}
/**
* Jitter strategy applied to retry and reconnection backoff delays.
*
* @remarks
* - `'full'` — Uniform random jitter in `[0, computedDelay]`. Best general-purpose choice.
* - `'equal'` — Half the computed delay is fixed, the other half is randomized.
* - `'none'` — No jitter; uses the raw exponential delay. Risk of thundering-herd.
*
* @see {@link RetryPolicy}
* @see {@link ReconnectionPolicy}
*/
type JitterType = 'full' | 'equal' | 'none';
/**
* Policy governing automatic retries for failed operations.
*
* @remarks
* Applied to all retriable SDK operations (publish, send, queue send, etc.).
* The actual delay between retries is computed as
* `min(initialBackoffMs * multiplier^attempt, maxBackoffMs)`, then jittered
* according to the {@link JitterType} strategy.
*
* @see {@link DEFAULT_RETRY_POLICY}
* @see {@link ClientOptions.retry}
*/
interface RetryPolicy {
/** Maximum number of retry attempts before throwing {@link RetryExhaustedError}. */
readonly maxRetries: number;
/** Base delay in milliseconds for the first retry. */
readonly initialBackoffMs: number;
/** Upper bound on the computed backoff delay in milliseconds. */
readonly maxBackoffMs: number;
/** Exponential multiplier applied to the backoff after each attempt. */
readonly multiplier: number;
/** Jitter strategy to reduce retry contention. */
readonly jitter: JitterType;
}
declare const DEFAULT_RETRY_POLICY: Readonly<RetryPolicy>;
declare const DEFAULT_KEEPALIVE: Readonly<KeepaliveOptions>;
declare const DEFAULT_RECONNECTION_POLICY: Readonly<ReconnectionPolicy>;
declare const DEFAULT_CONNECTION_TIMEOUT_MS = 10000;
declare const DEFAULT_MAX_MESSAGE_SIZE = 104857600;
declare const DEFAULT_RECONNECT_BUFFER_SIZE = 8388608;
declare const DEFAULT_SEND_TIMEOUT_MS = 5000;
declare const DEFAULT_SUBSCRIBE_TIMEOUT_MS = 10000;
declare const DEFAULT_RPC_TIMEOUT_MS = 10000;
declare const DEFAULT_QUEUE_RECEIVE_TIMEOUT_MS = 10000;
declare const DEFAULT_QUEUE_POLL_TIMEOUT_MS = 30000;
declare const DEFAULT_MAX_CONCURRENT_RETRIES = 10;
/**
* Policy governing automatic reconnection when the gRPC transport disconnects.
*
* @remarks
* When the connection drops, the client enters `RECONNECTING` state and
* attempts to re-establish the connection using exponential backoff with jitter.
* Set `maxAttempts` to `-1` for unlimited reconnection attempts.
*
* Active subscriptions are automatically re-established after a successful
* reconnection. Buffered messages (if `reconnectBufferSize > 0`) are replayed.
*
* @see {@link DEFAULT_RECONNECTION_POLICY}
* @see {@link ClientOptions.reconnect}
* @see {@link ConnectionState}
*/
interface ReconnectionPolicy {
/** Maximum reconnection attempts. Use `-1` for unlimited. */
readonly maxAttempts: number;
/** Base delay in milliseconds for the first reconnection attempt. */
readonly initialDelayMs: number;
/** Upper bound on the computed reconnection delay in milliseconds. */
readonly maxDelayMs: number;
/** Exponential multiplier applied to the delay after each attempt. */
readonly multiplier: number;
/** Jitter strategy to reduce reconnection contention across clients. */
readonly jitter: JitterType;
}
/**
* TLS/SSL configuration for encrypting the gRPC connection.
*
* @remarks
* Set `enabled: true` (or pass `tls: true` in {@link ClientOptions}) for
* server-authenticated TLS. For mutual TLS (mTLS), also provide `clientCert`
* and `clientKey`. Certificate values accept PEM-encoded strings or raw Buffers.
*
* @see {@link ClientOptions.tls}
*/
interface TlsOptions {
/** Whether TLS is enabled. Defaults to `false`. */
enabled?: boolean;
/** PEM-encoded CA certificate or bundle for server verification. */
caCert?: string | Buffer;
/** PEM-encoded client certificate for mutual TLS. */
clientCert?: string | Buffer;
/** PEM-encoded client private key for mutual TLS. */
clientKey?: string | Buffer;
/** Override the server name used for certificate verification. */
serverNameOverride?: string;
/** Skip server certificate verification. **Insecure — use only for development.** */
insecureSkipVerify?: boolean;
/** Minimum TLS version to accept. */
minVersion?: 'TLSv1.2' | 'TLSv1.3';
}
/**
* gRPC HTTP/2 keepalive configuration.
*
* @remarks
* Keepalive pings prevent idle connections from being silently dropped
* by firewalls or load balancers. The defaults are tuned for most
* cloud environments (10s ping interval, 5s timeout).
*
* @see {@link DEFAULT_KEEPALIVE}
* @see {@link ClientOptions.keepalive}
*/
interface KeepaliveOptions {
/** Interval in milliseconds between keepalive pings. */
readonly timeMs: number;
/** Time in milliseconds to wait for a keepalive ping response before closing. */
readonly timeoutMs: number;
/** Whether to send pings even when there are no active RPCs. */
readonly permitWithoutCalls: boolean;
}
/**
* Options for individual async operations.
*
* @remarks
* Pass to any async method on `KubeMQClient` to control cancellation
* and timeout behavior for that specific operation.
*/
interface OperationOptions {
/**
* AbortSignal for cooperative cancellation.
* When aborted, the operation is cancelled and throws `CancellationError`.
*
* @example
* ```typescript
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 5000);
* await client.sendEvent(msg, { signal: controller.signal });
* ```
*/
signal?: AbortSignal;
/**
* Operation timeout in milliseconds.
* Overrides the client-level default timeout for this operation.
* When exceeded, throws `KubeMQTimeoutError`.
*/
timeout?: number;
}
/**
* Options for subscription operations, extending OperationOptions.
*/
interface SubscriptionOptions extends OperationOptions {
/**
* Maximum number of concurrent callback invocations.
* Default varies by subscription type: events=100, events_store=20, commands/queries=1.
* Set to a higher value for parallel message processing.
*
* @remarks
* When > 1, messages are dispatched to the callback concurrently
* using an internal semaphore. Message ordering is NOT guaranteed
* when concurrency > 1. Use 1 for ordered processing.
*/
maxConcurrentCallbacks?: number;
/**
* Maximum internal queue depth before backpressure is applied.
* Default: 1000.
*/
maxQueueDepth?: number;
/**
* If true, drop messages when the internal queue is full instead of
* pausing the gRPC stream. Useful for pub/sub patterns where message
* loss is acceptable but stream stalls are not. Default: false.
*/
dropOnHighWater?: boolean;
}
/**
* Options for the `close()` method.
*/
interface CloseOptions {
/**
* Max time to wait for in-flight gRPC operations to drain, in seconds.
* Default: 5.
*/
timeoutSeconds?: number;
/**
* Max time to wait for in-flight subscription callbacks to complete, in seconds.
* Default: 30.
*
* Callbacks that haven't completed within this timeout are abandoned —
* they may still be running in the background but the client will
* proceed to close.
*/
callbackTimeoutSeconds?: number;
}
/**
* Configuration options for creating a {@link KubeMQClient}.
*
* @remarks
* Only `address` is required. All other options have sensible defaults.
* Pass to {@link KubeMQClient.create} to build a connected client.
*
* @example
* ```typescript
* const client = await KubeMQClient.create({
* address: 'localhost:50000',
* clientId: 'order-service',
* logger: createConsoleLogger('info'),
* retry: { ...DEFAULT_RETRY_POLICY, maxRetries: 5 },
* });
* ```
*
* @see {@link KubeMQClient.create}
*/
interface ClientOptions {
/** KubeMQ server address in `host:port` format. */
address: string;
/** Unique client identifier. Auto-generated UUID if omitted. */
clientId?: string;
/** Authentication credentials — a token string or a {@link CredentialProvider}. */
credentials?: CredentialProvider | string;
/** TLS configuration. Pass `true` for default TLS, or a {@link TlsOptions} object. */
tls?: TlsOptions | boolean;
/** HTTP/2 keepalive settings. Uses {@link DEFAULT_KEEPALIVE} if omitted. */
keepalive?: KeepaliveOptions;
/** Retry policy for failed operations. Uses {@link DEFAULT_RETRY_POLICY} if omitted. */
retry?: RetryPolicy;
/** Reconnection policy for dropped connections. Uses {@link DEFAULT_RECONNECTION_POLICY} if omitted. */
reconnect?: ReconnectionPolicy;
/** Maximum time in seconds to wait for the initial connection. Default: 10. */
connectionTimeoutSeconds?: number;
/** Maximum inbound message size in bytes. Default: {@link DEFAULT_MAX_MESSAGE_SIZE} (100 MiB). */
maxReceiveMessageSize?: number;
/** Maximum outbound message size in bytes. Default: {@link DEFAULT_MAX_MESSAGE_SIZE} (100 MiB). */
maxSendMessageSize?: number;
/** Block until the gRPC channel is ready instead of failing fast. */
waitForReady?: boolean;
/** Structured logger implementation. Default: silent no-op logger. */
logger?: Logger;
/** OpenTelemetry TracerProvider for distributed tracing. */
tracerProvider?: unknown;
/** OpenTelemetry MeterProvider for metrics collection. */
meterProvider?: unknown;
/** Size in bytes of the in-memory buffer for messages sent during reconnection. Default: {@link DEFAULT_RECONNECT_BUFFER_SIZE}. */
reconnectBufferSize?: number;
/** Behavior when the reconnect buffer is full: `'error'` throws {@link BufferFullError}, `'block'` waits. */
reconnectBufferMode?: 'error' | 'block';
/** Maximum number of concurrent retry operations across all calls. Default: {@link DEFAULT_MAX_CONCURRENT_RETRIES}. */
maxConcurrentRetries?: number;
/** Default timeout in seconds for publish/send operations. Default: 5. */
defaultSendTimeoutSeconds?: number;
/** Default timeout in seconds for subscribe operations. Default: 10. */
defaultSubscribeTimeoutSeconds?: number;
/** Default timeout in seconds for RPC (command/query) operations. Default: 10. */
defaultRpcTimeoutSeconds?: number;
/** Default timeout in seconds for queue receive operations. Default: 10. */
defaultQueueReceiveTimeoutSeconds?: number;
/** Default timeout in seconds for queue poll operations. Default: 30. */
defaultQueuePollTimeoutSeconds?: number;
}
/** @internal */
/**
* Union type for message body inputs. Accepts string (auto-encoded to UTF-8),
* Uint8Array, or Node.js Buffer (zero-copy view extracted).
*
* Public API methods and factory functions accept `MessageBody`;
* internally the SDK normalizes to `Uint8Array` before sending to gRPC.
*/
type MessageBody = string | Uint8Array | Buffer;
/**
* Normalize any accepted body input to a `Uint8Array`.
*
* - `string` → UTF-8 encoded via cached `TextEncoder`
* - `Buffer` → zero-copy `Uint8Array` view (no data copy)
* - `Uint8Array` → returned as-is
*/
declare function normalizeBody(body: MessageBody): Uint8Array;
/**
* Decode a `Uint8Array` body to a UTF-8 string.
* Uses the cached `TextDecoder` singleton.
*/
declare function bodyToString(body: Uint8Array): string;
/**
* Outbound event message.
*
* @remarks
* **Async safety:** Not safe for concurrent modification. Create a new instance
* per send operation. Do not share outbound message objects between concurrent
* async operations. Message objects are frozen (`Object.freeze()`) by factory
* functions — modification after creation throws a `TypeError`.
*/
interface EventMessage {
readonly channel: string;
readonly body?: MessageBody;
readonly metadata?: string;
readonly tags?: Record<string, string>;
readonly id?: string;
readonly clientId?: string;
}
/**
* Received event from a subscription.
*
* @remarks
* **Async safety:** Safe to read from multiple async contexts concurrently.
* Do not modify received message objects — they are shared references from
* the subscription's delivery pipeline. Fields are readonly.
*/
interface EventReceived {
readonly id: string;
readonly channel: string;
readonly timestamp: Date;
readonly body: Uint8Array;
readonly metadata: string;
readonly tags: Record<string, string>;
}
/**
* Subscription request for events.
*
* @remarks
* **Async safety:** Subscription callbacks fire sequentially on the Node.js
* event loop by default. Opt-in concurrent processing is available via
* `maxConcurrentCallbacks` in `SubscriptionOptions`. When concurrency > 1,
* message ordering is NOT guaranteed.
*/
interface EventsSubscription {
readonly channel: string;
readonly group?: string;
readonly onEvent: (event: EventReceived) => void;
readonly onError: (err: KubeMQError) => void;
}
/**
* Handle for a persistent event publishing stream (fire-and-forget).
*
* @remarks
* Obtained from {@link KubeMQClient.createEventStream}. Keeps a single gRPC
* bidirectional stream open for high-throughput publishing. The `send()` method
* is synchronous (fire-and-forget) — errors are delivered asynchronously via
* the `onError` handler.
*
* @see {@link KubeMQClient.createEventStream}
* @see {@link EventMessage}
*/
interface EventStreamHandle {
/** Publish an event over the stream. Returns a Promise that resolves when the write buffer has capacity (backpressure-aware). */
send(msg: EventMessage): Promise<void>;
/** Register a handler for asynchronous stream errors. */
onError(handler: (err: Error) => void): void;
/** Close the stream and release resources. */
close(): void;
/** Whether the stream is still active. */
readonly isActive: boolean;
}
/**
* Create a validated, frozen EventMessage with defaults applied.
*
* - `id` defaults to a random UUID
* - `metadata` defaults to `''`
* - `tags` defaults to `{}`
* - String/Buffer body is normalized to `Uint8Array`
*
* @example
* ```typescript
* const event = createEventMessage({
* channel: 'events.notifications',
* body: new TextEncoder().encode('hello world'),
* metadata: 'greeting',
* tags: { source: 'api' },
* });
* await client.sendEvent(event);
* ```
*/
declare function createEventMessage(opts: Omit<EventMessage, 'id'> & {
id?: string;
}): Readonly<EventMessage>;
/**
* Subscription start position for event-store channels.
*
* @remarks
* Determines where in the persisted event stream a new subscriber begins
* receiving messages. Used in {@link EventStoreSubscription.startFrom}.
*
* @see {@link EventStoreSubscription}
* @see {@link KubeMQClient.subscribeToEventsStore}
*/
declare enum EventStoreStartPosition {
/** Receive only events published after the subscription is created. */
StartFromNew = 1,
/** Replay all events from the beginning of the stream. */
StartFromFirst = 2,
/** Start from the most recent event and receive new ones going forward. */
StartFromLast = 3,
/** Start at a specific sequence number (set via {@link EventStoreSubscription.startValue}). */
StartAtSequence = 4,
/** Start at a specific point in time (set via {@link EventStoreSubscription.startValue} as Unix epoch ms). */
StartAtTime = 5,
/** Start at a time delta in seconds from now (set via {@link EventStoreSubscription.startValue}). */
StartAtTimeDelta = 6
}
/**
* Outbound persistent event message.
*
* @remarks
* **Async safety:** Not safe for concurrent modification. Create a new instance
* per send operation. Do not share outbound message objects between concurrent
* async operations. Message objects are frozen (`Object.freeze()`) by factory
* functions — modification after creation throws a `TypeError`.
*/
interface EventStoreMessage {
readonly channel: string;
readonly body?: MessageBody;
readonly metadata?: string;
readonly tags?: Record<string, string>;
readonly id?: string;
readonly clientId?: string;
}
/**
* Received persistent event from a subscription.
*
* @remarks
* **Async safety:** Safe to read from multiple async contexts concurrently.
* Do not modify received message objects — they are shared references from
* the subscription's delivery pipeline. Fields are readonly.
*/
interface EventStoreReceived {
readonly id: string;
readonly channel: string;
readonly timestamp: Date;
readonly body: Uint8Array;
readonly metadata: string;
readonly tags: Record<string, string>;
readonly sequence: number;
}
/**
* Subscription request for persistent events.
*
* @remarks
* **Async safety:** Subscription callbacks fire sequentially on the Node.js
* event loop by default. Opt-in concurrent processing is available via
* `maxConcurrentCallbacks` in `SubscriptionOptions`. When concurrency > 1,
* message ordering is NOT guaranteed.
*/
interface EventStoreSubscription {
readonly channel: string;
readonly group?: string;
readonly startFrom: EventStoreStartPosition;
readonly startValue?: number;
readonly onEvent: (event: EventStoreReceived) => void;
readonly onError: (err: KubeMQError) => void;
}
/**
* Result of a persistent event-store send operation.
*
* @see {@link KubeMQClient.sendEventStore}
*/
interface EventStoreResult {
/** Server-assigned event ID. */
readonly id: string;
/** Whether the event was successfully persisted. */
readonly sent: boolean;
/** Error message if the send failed. */
readonly error: string;
}
/**
* Handle for a persistent event-store publishing stream with delivery confirmation.
*
* @remarks
* Obtained from {@link KubeMQClient.createEventStoreStream}. Unlike
* {@link EventStreamHandle}, the `send()` method returns a `Promise`
* that resolves when the server confirms persistence, or rejects on failure.
*
* @see {@link KubeMQClient.createEventStoreStream}
* @see {@link EventStoreMessage}
*/
interface EventStoreStreamHandle {
/** Publish an event-store message. Resolves when the server confirms persistence. */
send(msg: EventStoreMessage): Promise<void>;
/** Register a handler for asynchronous stream errors. */
onError(handler: (err: Error) => void): void;
/** Close the stream and release resources. */
close(): void;
/** Whether the stream is still active. */
readonly isActive: boolean;
}
/**
* Create a validated, frozen EventStoreMessage with defaults applied.
*
* - `id` defaults to a random UUID
* - `metadata` defaults to `''`
* - `tags` defaults to `{}`
* - String/Buffer body is normalized to `Uint8Array`
*
* @example
* ```typescript
* const event = createEventStoreMessage({
* channel: 'events-store.audit-log',
* body: new TextEncoder().encode(JSON.stringify({ action: 'login', userId: '42' })),
* tags: { service: 'auth' },
* });
* await client.sendEventStore(event);
* ```
*/
declare function createEventStoreMessage(opts: Omit<EventStoreMessage, 'id'> & {
id?: string;
}): Readonly<EventStoreMessage>;
/**
* Delivery policy for a queue message controlling expiration, delay, and dead-letter behavior.
*
* @remarks
* Attach to a {@link QueueMessage} via the `policy` property.
* When `maxReceiveCount` is exceeded, the message is routed to `maxReceiveQueue`
* (dead-letter queue) if specified, otherwise it is discarded.
*
* @see {@link QueueMessage}
* @see {@link KubeMQClient.sendQueueMessage}
*/
interface QueueMessagePolicy {
/** Time in seconds after which the message expires and is discarded. */
readonly expirationSeconds?: number;
/** Delay in seconds before the message becomes visible to consumers. */
readonly delaySeconds?: number;
/** Maximum number of delivery attempts before dead-lettering. */
readonly maxReceiveCount?: number;
/** Dead-letter queue channel. Messages exceeding `maxReceiveCount` are routed here. */
readonly maxReceiveQueue?: string;
}
/**
* Outbound queue message.
*
* @remarks
* **Async safety:** Not safe for concurrent modification. Create a new instance
* per send operation. Do not share outbound message objects between concurrent
* async operations. Message objects are frozen (`Object.freeze()`) by factory
* functions — modification after creation throws a `TypeError`.
*/
interface QueueMessage {
readonly channel: string;
readonly body?: MessageBody;
readonly metadata?: string;
readonly tags?: Record<string, string>;
readonly policy?: QueueMessagePolicy;
readonly id?: string;
readonly clientId?: string;
}
/**
* Received queue message with acknowledgment methods.
*
* @remarks
* **Async safety:** The `ack()`, `nack()`, and `reQueue()` methods are safe
* to call from any async context, but each message MUST be acknowledged exactly
* once. Calling `ack()` after `nack()` (or vice versa) throws a
* `ValidationError`.
*/
interface ReceivedQueueMessage {
readonly id: string;
readonly channel: string;
readonly fromClientId: string;
readonly body: Uint8Array;
readonly metadata: string;
readonly tags: Record<string, string>;
readonly timestamp: Date;
readonly sequence: number;
readonly receiveCount: number;
readonly isReRouted: boolean;
readonly reRouteFromQueue?: string;
readonly expiredAt?: Date;
readonly delayedTo?: Date;
ack(): Promise<void>;
nack(): Promise<void>;
reQueue(channel: string): Promise<void>;
}
/**
* Request parameters for polling messages from a queue channel.
*
* @remarks
* Used by {@link KubeMQClient.receiveQueueMessages} and
* {@link KubeMQClient.peekQueueMessages}.
*
* @see {@link KubeMQClient.receiveQueueMessages}
* @see {@link ReceivedQueueMessage}
*/
interface QueuePollRequest {
/** Queue channel to poll from. */
readonly channel: string;
/** Maximum time in seconds to wait for messages before returning an empty result. */
readonly waitTimeoutSeconds: number;
/** Maximum number of messages to receive in a single poll. Default: 1. */
readonly maxMessages?: number;
/**
* Automatically acknowledge messages upon receipt. Default: `false`.
* @remarks **No effect on the unary receive API** — messages are always auto-acked server-side.
* Use {@link KubeMQClient.streamQueueMessages} for explicit ack/reject control.
*/
readonly autoAck?: boolean;
}
/**
* Result of a successful queue message send operation.
*
* @see {@link KubeMQClient.sendQueueMessage}
* @see {@link QueueMessage}
*/
interface QueueSendResult {
/** Server-assigned message ID. */
readonly messageId: string;
/** Timestamp when the message was persisted by the server. */
readonly sentAt: Date;
/** When the message will expire, if an expiration policy was set. */
readonly expirationAt?: Date;
/** When the message becomes visible, if a delay policy was set. */
readonly delayedTo?: Date;
}
/**
* Result of a batch queue message send operation.
*
* @remarks
* Each item in `results` corresponds to the message at the same index in
* the original batch. Check individual `error` fields for per-message failures.
*
* @see {@link KubeMQClient.sendQueueMessagesBatch}
* @see {@link QueueSendResult}
*/
interface BatchSendResult {
/** Per-message results, ordered by batch index. */
readonly results: {
/** Index of the message in the original batch array. */
index: number;
/** Server-assigned message ID on success. */
messageId?: string;
/** Error details if this particular message failed. */
error?: KubeMQError;
}[];
/** Number of messages successfully sent. */
readonly successCount: number;
/** Number of messages that failed. */
readonly failureCount: number;
}
/**
* Options for batch send operations.
*
* @see {@link KubeMQClient.sendQueueMessagesBatch}
*/
interface BatchSendOptions {
/** Number of messages to include per batch request. */
readonly batchSize?: number;
}
/**
* Options for creating a streaming queue consumer via {@link KubeMQClient.streamQueueMessages}.
*
* @remarks
* The streaming API provides transactional message processing with explicit
* ack/reject/requeue per message or per batch, unlike the simple poll API.
*
* @see {@link KubeMQClient.streamQueueMessages}
* @see {@link QueueStreamHandle}
*/
interface QueueStreamOptions {
/** Queue channel to consume from. */
readonly channel: string;
/** Time in seconds to wait for messages before the stream returns empty. */
readonly waitTimeoutSeconds?: number;
/** Maximum number of messages per batch. */
readonly maxMessages?: number;
/** Automatically acknowledge messages after delivery to the handler. */
readonly autoAck?: boolean;
/** Custom metadata key-value pairs sent with each downstream request. */
readonly metadata?: Record<string, string>;
}
/**
* A queue message received via the streaming API with synchronous settlement methods.
*
* @remarks
* Unlike {@link ReceivedQueueMessage} (from the poll API), the streaming message's
* `ack()`, `nack()`, and `reQueue()` methods are synchronous — they write to
* the underlying gRPC stream without awaiting a response.
*
* Each message within a transaction must be settled exactly once.
*
* @see {@link QueueStreamHandle}
* @see {@link ReceivedQueueMessage}
*/
interface QueueStreamMessage {
/** Server-assigned message ID. */
readonly id: string;
/** Channel the message was received from. */
readonly channel: string;
/** Raw message body. */
readonly body: Uint8Array;
/** Application metadata string. */
readonly metadata: string;
/** User-defined key-value tags. */
readonly tags: Record<string, string>;
/** Server-side timestamp when the message was enqueued. */
readonly timestamp: Date;
/** Monotonically increasing sequence number within the channel. */
readonly sequence: number;
/** Number of times this message has been delivered. */
readonly receiveCount: number;
/** MD5 hash of the body, if computed by the server. */
readonly md5OfBody?: string;
/** Whether this message was re-routed from another queue. */
readonly isReRouted: boolean;
/** Original queue channel if the message was re-routed. */
readonly reRouteFromQueue?: string;
/** Expiration timestamp, if an expiration policy was set. */
readonly expiredAt?: Date;
/** Delayed-until timestamp, if a delay policy was set. */
readonly delayedTo?: Date;
/** Acknowledge the message, removing it from the queue. */
ack(): void;
/** Reject (nack) the message, returning it to the queue for redelivery. */
nack(): void;
/** Move the message to a different queue channel. */
reQueue(channel: string): void;
}
/**
* Handle for an active streaming queue consumer, providing batch settlement and lifecycle control.
*
* @remarks
* Obtained from {@link KubeMQClient.streamQueueMessages}. Register handlers
* via `onMessages`, `onError`, and `onClose`, then settle messages individually
* or in bulk. Call `close()` to gracefully shut down the stream.
*
* @see {@link KubeMQClient.streamQueueMessages}
* @see {@link QueueStreamMessage}
* @see {@link QueueStreamOptions}
*/
interface QueueStreamHandle {
/** Whether the stream is still active and accepting operations. */
readonly isActive: boolean;
/** Metadata returned by the most recent server response. */
readonly responseMetadata: Record<string, string>;
/** Register a handler invoked when a batch of messages is received. */
onMessages(handler: (messages: QueueStreamMessage[]) => void): void;
/** Register a handler invoked when a stream error occurs. */
onError(handler: (err: Error) => void): void;
/** Register a handler invoked when the stream closes. */
onClose(handler: () => void): void;
/*