Full Code of nats-io/nats.zig for AI

main 35e7358b3480 cached
161 files
1.8 MB
488.6k tokens
1 requests
Download .txt
Showing preview only (1,939K chars total). Download the full file or copy to clipboard to get everything.
Repository: nats-io/nats.zig
Branch: main
Commit: 35e7358b3480
Files: 161
Total size: 1.8 MB

Directory structure:
gitextract_52v3q3d0/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── claude.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── build.zig
├── build.zig.zon
├── doc/
│   ├── JetStream.md
│   └── nats-by-example/
│       ├── README.md
│       ├── auth/
│       │   ├── NKeys-JWTs.md
│       │   └── nkeys-jwts.zig
│       └── messaging/
│           ├── Concurrent.md
│           ├── Iterating-Multiple-Subscriptions.md
│           ├── Json.md
│           ├── Pub-Sub.md
│           ├── README.md
│           ├── Request-Reply.md
│           ├── concurrent.zig
│           ├── iterating-multiple-subscriptions.zig
│           ├── json.zig
│           ├── pub-sub.zig
│           └── request-reply.zig
└── src/
    ├── Client.zig
    ├── auth/
    │   ├── base32.zig
    │   ├── crc16.zig
    │   ├── creds.zig
    │   ├── jwt.zig
    │   └── nkey.zig
    ├── auth.zig
    ├── connection/
    │   ├── errors.zig
    │   ├── events.zig
    │   ├── io_task.zig
    │   ├── reconnect_test.zig
    │   ├── server_pool.zig
    │   ├── server_pool_test.zig
    │   └── state.zig
    ├── connection.zig
    ├── dbg.zig
    ├── defaults.zig
    ├── events.zig
    ├── examples/
    │   ├── README.md
    │   ├── batch_receiving.zig
    │   ├── callback.zig
    │   ├── events.zig
    │   ├── graceful_shutdown.zig
    │   ├── headers.zig
    │   ├── jetstream_async_publish.zig
    │   ├── jetstream_consume.zig
    │   ├── jetstream_publish.zig
    │   ├── jetstream_push.zig
    │   ├── kv.zig
    │   ├── kv_watch.zig
    │   ├── micro_echo.zig
    │   ├── polling_loop.zig
    │   ├── queue_groups.zig
    │   ├── reconnection.zig
    │   ├── request_reply.zig
    │   ├── request_reply_callback.zig
    │   ├── select.zig
    │   └── simple.zig
    ├── io_backend.zig
    ├── jetstream/
    │   ├── JetStream.zig
    │   ├── async_publish.zig
    │   ├── consumer.zig
    │   ├── errors.zig
    │   ├── kv.zig
    │   ├── message.zig
    │   ├── ordered.zig
    │   ├── publish_headers.zig
    │   ├── pull.zig
    │   ├── push.zig
    │   └── types.zig
    ├── jetstream.zig
    ├── memory/
    │   ├── sidmap.zig
    │   ├── sidmap_test.zig
    │   └── slab.zig
    ├── memory.zig
    ├── micro/
    │   ├── Service.zig
    │   ├── endpoint.zig
    │   ├── json_util.zig
    │   ├── protocol.zig
    │   ├── request.zig
    │   ├── stats.zig
    │   ├── timeutil.zig
    │   └── validation.zig
    ├── micro.zig
    ├── nats.zig
    ├── protocol/
    │   ├── commands.zig
    │   ├── encoder.zig
    │   ├── encoder_test.zig
    │   ├── errors.zig
    │   ├── header_map.zig
    │   ├── headers.zig
    │   ├── parser.zig
    │   └── parser_test.zig
    ├── protocol.zig
    ├── pubsub/
    │   ├── inbox.zig
    │   ├── subject.zig
    │   ├── subject_test.zig
    │   ├── subscription.zig
    │   └── subscription_test.zig
    ├── pubsub.zig
    ├── sync/
    │   ├── byte_ring.zig
    │   ├── spin_lock.zig
    │   └── spsc_queue.zig
    └── testing/
        ├── README.md
        ├── certs/
        │   ├── client-all.pem
        │   ├── client-cert.pem
        │   ├── client-key.pem
        │   ├── ip-ca.pem
        │   ├── ip-cert.pem
        │   ├── ip-key.pem
        │   ├── rootCA-key.pem
        │   ├── rootCA.pem
        │   ├── server-cert.pem
        │   └── server-key.pem
        ├── client/
        │   ├── advanced.zig
        │   ├── async_patterns.zig
        │   ├── auth.zig
        │   ├── autoflush.zig
        │   ├── basic.zig
        │   ├── callback.zig
        │   ├── concurrency.zig
        │   ├── connection.zig
        │   ├── drain.zig
        │   ├── dynamic_jwt.zig
        │   ├── edge_cases.zig
        │   ├── error_handling.zig
        │   ├── flush_confirmed.zig
        │   ├── getters.zig
        │   ├── headers.zig
        │   ├── jetstream.zig
        │   ├── jwt.zig
        │   ├── micro.zig
        │   ├── multi_client.zig
        │   ├── multithread.zig
        │   ├── nkey.zig
        │   ├── protocol.zig
        │   ├── publish.zig
        │   ├── queue.zig
        │   ├── reconnect.zig
        │   ├── request_reply.zig
        │   ├── server.zig
        │   ├── state_notifications.zig
        │   ├── stats.zig
        │   ├── stress.zig
        │   ├── stress_subs.zig
        │   ├── subscribe.zig
        │   ├── tests.zig
        │   ├── tls.zig
        │   └── wildcard.zig
        ├── configs/
        │   ├── TestUser.creds
        │   ├── jwt.conf
        │   └── tls.conf
        ├── integration_test.zig
        ├── micro_integration_test.zig
        ├── server_manager.zig
        ├── test_utils.zig
        └── tls_integration_test.zig

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  ZIG_VERSION: 0.16.0
  NATS_SERVER_VERSION: v2.12.7
  NATS_CLI_VERSION: v0.3.1

jobs:
  build:
    name: Build and unit tests
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Zig
        uses: mlugg/setup-zig@v2
        with:
          version: ${{ env.ZIG_VERSION }}

      - name: Show Zig version
        run: zig version

      - name: Check formatting
        run: zig build fmt-check

      - name: Build examples and package
        run: zig build

      - name: Run unit tests
        run: zig build test

  integration:
    name: Integration tests (${{ matrix.mode }})
    runs-on: ubuntu-latest
    timeout-minutes: 25

    strategy:
      fail-fast: false
      matrix:
        include:
          - mode: Debug
            args: ""
          - mode: ReleaseFast
            args: -Doptimize=ReleaseFast

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Zig
        uses: mlugg/setup-zig@v2
        with:
          version: ${{ env.ZIG_VERSION }}
          cache-key: integration-${{ matrix.mode }}

      - name: Install NATS tools
        shell: bash
        run: |
          set -euo pipefail

          nats_server_archive="nats-server-${NATS_SERVER_VERSION}-linux-amd64.tar.gz"
          nats_server_url="https://github.com/nats-io/nats-server/releases/download/${NATS_SERVER_VERSION}/${nats_server_archive}"
          nats_cli_version="${NATS_CLI_VERSION#v}"
          nats_cli_archive="nats-${nats_cli_version}-linux-amd64.zip"
          nats_cli_url="https://github.com/nats-io/natscli/releases/download/${NATS_CLI_VERSION}/${nats_cli_archive}"

          mkdir -p "$HOME/.local/bin" "$RUNNER_TEMP/nats-server" "$RUNNER_TEMP/nats-cli"

          curl --fail --location --show-error --silent "$nats_server_url" --output "$RUNNER_TEMP/$nats_server_archive"
          tar -xzf "$RUNNER_TEMP/$nats_server_archive" -C "$RUNNER_TEMP/nats-server" --strip-components=1
          install -m 0755 "$RUNNER_TEMP/nats-server/nats-server" "$HOME/.local/bin/nats-server"

          curl --fail --location --show-error --silent "$nats_cli_url" --output "$RUNNER_TEMP/$nats_cli_archive"
          unzip -q "$RUNNER_TEMP/$nats_cli_archive" -d "$RUNNER_TEMP/nats-cli"
          nats_cli_bin="$(find "$RUNNER_TEMP/nats-cli" -type f -name nats -print -quit)"
          test -n "$nats_cli_bin"
          install -m 0755 "$nats_cli_bin" "$HOME/.local/bin/nats"

          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Show tool versions
        run: |
          zig version
          nats-server --version
          nats --version

      - name: Run integration tests
        run: zig build test-integration ${{ matrix.args }}


================================================
FILE: .github/workflows/claude.yml
================================================
name: Claude Code

# GITHUB_TOKEN is neutered — all GitHub API access uses the App token instead.
permissions: {}

on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
  pull_request_target:
    types: [opened, reopened]

jobs:
  claude:
    uses: synadia-io/ai-workflows/.github/workflows/claude.yml@v2
    with:
      gh_app_id: ${{ vars.CLAUDE_GH_APP_ID }}
      checkout_mode: base
      review_focus: |
        Additionally focus on:
        - Zig best practices: proper error handling, comptime usage, memory management
        - Correct use of allocators and avoiding memory leaks
        - Adherence to Zig style conventions and idiomatic patterns
        - NATS protocol correctness and client implementation details
    secrets:
      claude_oauth_token: ${{ secrets.CLAUDE_OAUTH_TOKEN }}
      gh_app_private_key: ${{ secrets.CLAUDE_GH_APP_PRIVATE_KEY }}


================================================
FILE: .gitignore
================================================
# The build cache
zig-cache
.zig-cache
zig-out/
tmp/
.mcp.json
.claude/


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Thanks for helping improve `nats.zig`.

## Development Setup

Required tools:

- Zig 0.16.0 or later
- `nats-server` on `PATH` for integration tests
- `nats` CLI on `PATH` for JetStream/KV cross-verification tests

Common commands:

```sh
zig build
zig build test
zig build fmt
zig build fmt-check
zig build test-integration
```

Focused integration targets are also available:

```sh
zig build test-integration-tls
zig build test-integration-micro
```

See `src/testing/README.md` for integration test layout and fixtures.

## Before Opening a Pull Request

- Run `zig build`.
- Run `zig build test`.
- Run the relevant integration target when changing connection,
  authentication, TLS, JetStream, KV, or service behavior.
- Keep examples compiling when public APIs change.
- Update `README.md` or `src/examples/README.md` when adding or
  changing user-facing examples.

## Style

- Prefer existing module patterns over new abstractions.
- Keep ownership rules explicit in public APIs and examples.
- Avoid unrelated refactors in bug-fix changes.
- Format changed Zig files with `zig build fmt`.


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to the Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by the Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding any notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2025 The nats.zig Authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
[![CI](https://github.com/nats-io/nats.zig/actions/workflows/ci.yml/badge.svg)](https://github.com/nats-io/nats.zig/actions/workflows/ci.yml)
[![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](LICENSE)
![Zig](https://img.shields.io/badge/Zig-0.16.0-orange)

<p align="center">
  <img src="logo/logo.png">
</p>

<p align="center">
    A <a href="https://www.ziglang.org/">Zig</a> client for the <a href="https://nats.io">NATS messaging system</a>.
</p>

# nats.zig

A [Zig](https://ziglang.org/) client for the [NATS messaging system](https://nats.io).

Built on `std.Io`.

> **Pre-1.0** - This library is under active development.
> Core pub/sub, server-authenticated TLS, JetStream (pull + push
> consumers), Key-Value Store, and the Micro Services API are
> supported and covered by integration tests. Object Store and
> mTLS are not yet implemented. The API may change before 1.0.

Check out [NATS by Example](https://natsbyexample.com) for
runnable, cross-client NATS examples. This repository includes
Zig ports in [doc/nats-by-example](doc/nats-by-example/README.md).

## Contents

- [Requirements](#requirements)
- [Documentation](#documentation)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Examples](#examples)
- [Memory Ownership](#memory-ownership)
- [Publishing](#publishing)
- [Subscribing](#subscribing)
- [Request/Reply](#requestreply)
- [Headers](#headers)
- [JetStream](#jetstream)
- [Micro Services](#micro-services)
- [Async Patterns with std.Io](#async-patterns-with-stdio)
- [Connections](#connections)
- [Authentication](#authentication)
- [Error Handling](#error-handling)
- [Server Compatibility](#server-compatibility)
- [Building](#building)
- [Status](#status)

## Documentation

- [Examples](src/examples/README.md) - runnable examples built by `zig build`
- [JetStream guide](doc/JetStream.md) - stream, consumer, publish,
  pull-consume, ack, and error-handling details
- [NATS by Example ports](doc/nats-by-example/README.md) - Zig ports of
  selected cross-client examples from natsbyexample.com
- [Integration tests](src/testing/README.md) - local test layout,
  fixtures, and focused test targets

## Requirements

- Zig 0.16.0 or later
- NATS server (for running examples and tests)

## Installation

```bash
zig fetch --save https://github.com/nats-io/nats.zig/archive/refs/tags/v0.1.0.tar.gz
```

Then in `build.zig`:

```zig
const nats_dep = b.dependency("nats", .{
    .target = target,
    .optimize = optimize,
});

const exe = b.addExecutable(.{
    .name = "my-app",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
        .imports = &.{
            .{ .name = "nats", .module = nats_dep.module("nats") },
        },
    }),
});
b.installArtifact(exe);
```

## Quick Start

Subscriptions use callbacks - messages are dispatched automatically,
no manual receive loop needed. There are three ways to subscribe:

**`subscribe()` with a MsgHandler** - captures state, like a closure:

```zig
const std = @import("std");
const nats = @import("nats");

// Handler struct captures external state via pointer
const Handler = struct {
    counter: *u32,
    pub fn onMessage(self: *@This(), msg: *const nats.Message) void {
        // Modify captured state from within the callback
        self.counter.* += 1;
        std.debug.print("[{d}] {s}\n", .{ self.counter.*, msg.data });
    }
};

pub fn main(init: std.process.Init) !void {
    const client = try nats.Client.connect(
        init.gpa,
        init.io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    // State lives in main - handler captures a pointer to it
    var count: u32 = 0;
    var handler = Handler{ .counter = &count };
    const sub = try client.subscribe(
        "greet.*",
        nats.MsgHandler.init(Handler, &handler),
    );
    defer sub.deinit();

    try client.publish("greet.hello", "Hello, NATS!");
    init.io.sleep(.fromSeconds(1), .awake) catch {};

    // Main sees the mutations made by the callback
    std.debug.print("Total: {d}\n", .{count});
}
```

**`subscribeFn()` with a plain function** - when no state is needed:

```zig
const std = @import("std");
const nats = @import("nats");

pub fn main(init: std.process.Init) !void {
    const client = try nats.Client.connect(
        init.gpa,
        init.io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    const sub = try client.subscribeFn("greet.*", onMessage);
    defer sub.deinit();

    try client.publish("greet.hello", "Hello, NATS!");
    init.io.sleep(.fromSeconds(1), .awake) catch {};
}

fn onMessage(msg: *const nats.Message) void {
    std.debug.print("Received: {s}\n", .{msg.data});
}
```

> **Note:** Callback messages are freed automatically after your handler
> returns. No `msg.deinit()` needed.

**`subscribeSync()` for manual receive** - you control the receive loop:

```zig
const sub = try client.subscribeSync("greet.*");
defer sub.deinit();

try client.publish("greet.hello", "Hello, NATS!");

if (try sub.nextMsgTimeout(5000)) |msg| {
    defer msg.deinit();
    std.debug.print("Received: {s}\n", .{msg.data});
}
```

See [Examples](#examples) below for more patterns including
request/reply, queue groups, headers, and async I/O.

## Examples

Run with `zig build run-<name>` (requires `nats-server` on localhost:4222).

| Example | Run | Description |
|---------|-----|-------------|
| simple | `run-simple` | Basic pub/sub - connect, `subscribeSync`, publish, receive |
| request_reply | `run-request-reply` | RPC pattern with automatic inbox handling |
| headers | `run-headers` | Publish, receive, and parse NATS headers |
| queue_groups | `run-queue-groups` | Load-balanced workers with `io.concurrent()` |
| polling_loop | `run-polling-loop` | Non-blocking `tryNextMsg()` with priority scheduling |
| select | `run-select` | Race subscription against timeout with `Io.Select` |
| batch_receiving | `run-batch-receiving` | `nextMsgBatch()` for bulk receives, stats monitoring |
| reconnection | `run-reconnection` | Auto-reconnect, backoff, buffer during disconnect |
| events | `run-events` | EventHandler callbacks with external state |
| callback | `run-callback` | `subscribe()` and `subscribeFn()` callback subscriptions |
| request_reply_callback | `run-request-reply-callback` | Service responder via callback subscription |
| graceful_shutdown | `run-graceful-shutdown` | `drain()` lifecycle, pre-shutdown health checks |
| jetstream_publish | `run-jetstream-publish` | Create a stream and publish with ack confirmation |
| jetstream_consume | `run-jetstream-consume` | Pull consumer fetch and acknowledgement |
| jetstream_push | `run-jetstream-push` | Push consumer callback delivery |
| jetstream_async_publish | `run-jetstream-async-publish` | Async JetStream publishing |
| kv | `run-kv` | Key-Value bucket operations |
| kv_watch | `run-kv-watch` | Watch Key-Value updates |
| micro_echo | `run-micro-echo` | NATS service API echo service |

Source: `src/examples/`

### NATS by Example

Ports of [natsbyexample.com](https://natsbyexample.com) examples.

| Example | Run |
|---------|-----|
| [Pub-Sub](doc/nats-by-example/messaging/pub-sub.zig) | `run-nbe-messaging-pub-sub` |
| [Request-Reply](doc/nats-by-example/messaging/request-reply.zig) | `run-nbe-messaging-request-reply` |
| [JSON](doc/nats-by-example/messaging/json.zig) | `run-nbe-messaging-json` |
| [Concurrent](doc/nats-by-example/messaging/concurrent.zig) | `run-nbe-messaging-concurrent` |
| [Multiple Subscriptions](doc/nats-by-example/messaging/iterating-multiple-subscriptions.zig) | `run-nbe-messaging-iterating-multiple-subscriptions` |
| [NKeys & JWTs](doc/nats-by-example/auth/nkeys-jwts.zig) | `run-nbe-auth-nkeys-jwts` |

---

## Memory Ownership

Messages returned by `nextMsg()`, `tryNextMsg()`, and `nextMsgTimeout()` are **owned**.
You **must** call `deinit()` to free memory:

```zig
const msg = try sub.nextMsg();
defer msg.deinit();

// Access message fields (valid until deinit)
std.debug.print("Subject: {s}\n", .{msg.subject});
std.debug.print("Data: {s}\n", .{msg.data});
```

### Message Structure

```zig
pub const Message = struct {
    subject: []const u8,       // Message subject
    sid: u64,                  // Subscription ID
    reply_to: ?[]const u8,     // Reply-to address (for request/reply)
    data: []const u8,          // Message payload
    headers: ?[]const u8,      // Raw NATS headers (use headers.parse())
};
```

---

## Publishing

### Auto-Flush Behavior

Messages are buffered and automatically flushed to the network:

```zig
// Write to buffer - auto-flushed by io_task
try client.publish("events.click", "button1");
try client.publish("events.click", "button2");
try client.publish("events.click", "button3");
```

**How it works:**
- `publish()` encodes into a lock-free ring buffer (no mutex)
- The io_task background thread drains the ring to the socket
- Multiple rapid publishes are naturally batched for efficiency
- Works at full speed even in tight loops (100K+ msgs/sec)
- Ring size: 2MB minimum (auto-sized, power-of-2)

### Confirmed Flush

For scenarios where you need confirmation that the server received your messages,
use `flush()`. It sends PING and waits for PONG (matches Go/C client behavior):

```zig
try client.publish("events.important", data);
try client.flush(5_000_000_000); // 5 second timeout
// Server has confirmed receipt of all buffered messages
```

**When to use:**
- Critical messages where delivery confirmation matters
- Before shutting down to ensure all messages were sent
- Synchronization points in your application

### When Does Data Hit the Network?

| Method | Network I/O |
|--------|-------------|
| `publish()` | Auto-flushed |
| `publishRequest()` | Auto-flushed |
| `publishWithHeaders()` | Auto-flushed |
| `publishRequestWithHeaders()` | Auto-flushed |
| `flushBuffer()` | Yes - sends buffer to socket immediately (used internally) |
| `flush()` | Yes - sends buffer + PING, waits for PONG |
| `request()` | Yes - flushes, waits for response |
| `requestWithHeaders()` | Yes - flushes, waits for response |

---

## Subscribing

### Subscribe (Callback)

Messages are dispatched automatically via callback.

**MsgHandler pattern** (handler struct with state):

```zig
const MyHandler = struct {
    counter: *u32,
    pub fn onMessage(self: *@This(), msg: *const nats.Message) void {
        self.counter.* += 1;
        std.debug.print("got: {s}\n", .{msg.data});
    }
};

var count: u32 = 0;
var handler = MyHandler{ .counter = &count };
const sub = try client.subscribe(
    "events.>",
    nats.MsgHandler.init(MyHandler, &handler),
);
defer sub.deinit();
```

**Plain function** (no state needed):

```zig
fn onAlert(msg: *const nats.Message) void {
    std.debug.print("alert: {s}\n", .{msg.data});
}

const sub = try client.subscribeFn(
    "alerts.>",
    onAlert,
);
defer sub.deinit();
```

**Queue group** (load balancing - only one subscriber in the group
receives each message):

```zig
const sub = try client.queueSubscribe(
    "tasks.*",
    "workers",
    handler,
);
```

| Method | Handler | Queue Group |
|--------|---------|-------------|
| `subscribe` | MsgHandler | No |
| `queueSubscribe` | MsgHandler | Yes |
| `subscribeFn` | plain fn | No |
| `queueSubscribeFn` | plain fn | Yes |

> **Warning:** Do not call `nextMsg()`, `tryNextMsg()`, or other receive methods on
> a callback subscription. They assert `mode == .manual` and will trap.

### Subscribe Sync (Manual Receive)

For manual control over message receiving, use `subscribeSync()`. You call
`nextMsg()`, `tryNextMsg()`, or `nextMsgBatch()` yourself:

```zig
const sub = try client.subscribeSync("events.>");
defer sub.deinit();

// Wildcards:
// * matches single token: "events.*" matches "events.click" but not "events.user.login"
// > matches remainder: "events.>" matches "events.click" and "events.user.login"

while (true) {
    const msg = try sub.nextMsg();
    defer msg.deinit();
    std.debug.print("{s}: {s}\n", .{ msg.subject, msg.data });
}
```

**Queue group** variant:

```zig
const sub1 = try client.queueSubscribeSync("tasks.*", "workers");
const sub2 = try client.queueSubscribeSync("tasks.*", "workers");
// Message goes to either sub1 OR sub2, not both
```

### Subscription Registration

When subscribing, the SUB command is buffered and sent to the server asynchronously.
If you need to ensure the subscription is fully registered before publishing (especially
with separate publisher/subscriber clients), call `flush()` after subscribing:

```zig
const sub = try client.subscribeSync("events.>");
defer sub.deinit();

// Ensure subscription is registered on server before publishing
try client.flush(5_000_000_000);  // 5 second timeout

// Now safe to publish from another client
```

**When is this needed?**
- Multi-client scenarios where one client publishes and another subscribes
- Tests that need deterministic message delivery
- Any situation requiring subscription to be active before first publish

**When is this NOT needed?**
- Single client publishing to itself (same client does subscribe + publish)
- Using `request()` which handles synchronization internally

### Unsubscribing

**Zig deinit pattern (recommended):** Use `defer sub.deinit()` - it calls `unsubscribe()`
internally and handles errors gracefully:

```zig
const sub = try client.subscribeSync("events.>");
defer sub.deinit();  // Unsubscribes + frees memory

// ... use subscription ...
```

**Explicit unsubscribe:** For users who need to check if the server
received the UNSUB command, call `unsubscribe()` directly:

```zig
const sub = try client.subscribeSync("events.>");

// ... use subscription ...

// Explicit unsubscribe with error checking
sub.unsubscribe() catch |err| {
    std.log.warn("Unsubscribe failed: {}", .{err});
};
sub.deinit();  // Still needed to free memory
```

| Method | Returns | Purpose |
|--------|---------|---------|
| `sub.unsubscribe()` | `!void` | Sends UNSUB to server, removes from tracking |
| `sub.deinit()` | `void` | Calls unsubscribe (if needed) + frees memory |

**Note:** `unsubscribe()` is idempotent - calling it multiple times is safe.
`deinit()` always succeeds (errors are logged, not returned) making it safe for
`defer`.

### Receiving Messages

**Blocking:** `nextMsg()` blocks until a message arrives. For use in dedicated receiver loops:

```zig
while (true) {
    const msg = try sub.nextMsg();
    defer msg.deinit();  // ALWAYS defer deinit

    std.debug.print("Subject: {s}\n", .{msg.subject});
    std.debug.print("Data: {s}\n", .{msg.data});
    if (msg.reply_to) |rt| {
        std.debug.print("Reply-to: {s}\n", .{rt});
    }
}
```

**Non-Blocking:** `tryNextMsg()` returns immediately. Use for event loops or polling:

```zig
// Process all available messages without waiting
while (sub.tryNextMsg()) |msg| {
    defer msg.deinit();
    processMessage(msg);
}
// No more messages - continue with other work
```

**With Timeout:** `nextMsgTimeout()` returns `null` on timeout:

```zig
if (try sub.nextMsgTimeout(5000)) |msg| {
    defer msg.deinit();
    std.debug.print("Got: {s}\n", .{msg.data});
} else {
    std.debug.print("No message within 5 seconds\n", .{});
}
```

**Batch:** `nextMsgBatch()` / `tryNextMsgBatch()` receive multiple messages at once:

```zig
var buf: [64]Message = undefined;

// Blocking - waits for at least 1 message, returns up to 64
const count = try sub.nextMsgBatch(io, &buf);
for (buf[0..count]) |*msg| {
    defer msg.deinit();
    processMessage(msg.*);
}

// Non-blocking - returns immediately with available messages
const available = sub.tryNextMsgBatch(&buf);
for (buf[0..available]) |*msg| {
    defer msg.deinit();
    processMessage(msg.*);
}
```

### Receive Method Comparison

| Method | Blocks | Returns | Use Case |
|--------|--------|---------|----------|
| `nextMsg()` | Yes | `!Message` | Dedicated receiver loop |
| `tryNextMsg()` | No | `?Message` | Polling, event loops |
| `nextMsgTimeout()` | Yes (bounded) | `!?Message` | Request/reply, timed waits |
| `nextMsgBatch()` | Yes | `!usize` | High-throughput batching |
| `tryNextMsgBatch()` | No | `usize` | Drain queue without blocking |

### Subscription Control

**Auto-Unsubscribe:** Automatically unsubscribe after receiving a specific number of messages:

```zig
const sub = try client.subscribeSync("events.>");

// Auto-unsubscribe after 10 messages
try sub.autoUnsubscribe(10);

// Process messages (subscription closes after 10th)
while (sub.isValid()) {
    if (sub.tryNextMsg()) |msg| {
        defer msg.deinit();
        processMessage(msg);
    }
}
```

**Statistics:**

```zig
// Messages waiting in queue
const pending = sub.pending();

// Messages delivered (only tracked if autoUnsubscribe was called)
const delivered = sub.delivered();

// Check if subscription is still valid
if (sub.isValid()) {
    // Can still receive messages
}
```

**Per-Subscription Drain:** Drain a single subscription while keeping others active:

```zig
try sub.drain();
// Subscription stops receiving new messages
// Already-queued messages can still be consumed
```

---

## Request/Reply

### Using `request()` (Recommended)

The simplest way - handles inbox creation, subscription, and timeout:

```zig
// Returns null on timeout
if (try client.request("math.double", "21", 5000)) |reply| {
    defer reply.deinit();
    std.debug.print("Result: {s}\n", .{reply.data});  // "42"
} else {
    std.debug.print("Service did not respond\n", .{});
}
```

### Building a Service

Respond to requests by publishing to the `reply_to` subject:

```zig
const service = try client.subscribeSync("math.double");
defer service.deinit();

while (true) {
    const req = try service.nextMsg();
    defer req.deinit();

    // Parse request
    const num = std.fmt.parseInt(i32, req.data, 10) catch 0;

    // Build response
    var buf: [32]u8 = undefined;
    const result = std.fmt.bufPrint(&buf, "{d}", .{num * 2}) catch "error";

    // Send reply (auto-flushed)
    if (req.reply_to) |reply_to| {
        try client.publish(reply_to, result);
    }
}
```

### Responding with `msg.respond()`

Convenience method for the request/reply pattern:

```zig
const msg = try sub.nextMsg();
defer msg.deinit();

// Respond using the message's reply_to (auto-flushed)
msg.respond(client, "response data") catch |err| {
    if (err == error.NoReplyTo) {
        // Message had no reply_to address
    }
};
```

### Manual Request/Reply Pattern

For more control, manage the inbox yourself:

```zig
// Create inbox subscription
const inbox = try client.newInbox();
defer allocator.free(inbox);

const reply_sub = try client.subscribeSync(inbox);
defer reply_sub.deinit();

// Send request with reply-to (auto-flushed)
try client.publishRequest("service", inbox, "request data");

// Wait for response with timeout
if (try reply_sub.nextMsgTimeout(5000)) |reply| {
    defer reply.deinit();
    // Process reply
} else {
    // Timeout
}
```

### Check No-Responders Status

Detect when a request has no available responders (status 503):

```zig
const reply = try client.request("service.endpoint", "data", 1000);
if (reply) |msg| {
    defer msg.deinit();

    if (msg.isNoResponders()) {
        // No service available to handle request
        std.debug.print("No responders for request\n", .{});
    } else {
        // Normal response - check status code if needed
        if (msg.status()) |status| {
            std.debug.print("Status: {d}\n", .{status});
        }
    }
}
```

### Implementation Note: Response Multiplexer

`request()`, `requestMsg()`, and `requestWithHeaders()` use a shared
*response multiplexer* internally - the same pattern as the Go
client's `respMux`. The first call lazily subscribes once to a
wildcard inbox `_INBOX.<connNUID>.*` and does a PING/PONG round-trip
to confirm server registration. Every subsequent call reuses that
single subscription and just registers a per-request waiter in a
token-keyed map. The dispatcher routes incoming replies back to the
matching waiter.

Benefits over the naive per-request subscription approach:

- **No SUB/UNSUB protocol churn** - the server (and any clustered
  gateways/leaf nodes) sees one wildcard subscription per connection
  instead of one SUB+UNSUB pair per request.
- **No per-request allocations** for the subscription struct, queue
  buffer, or owned subject string.
- **No latency floor** - the old implementation burned a hardcoded
  5ms sleep on every request to give the server time to process the
  per-request SUB. The muxer pays one PING/PONG round-trip *once* on
  the first request and amortizes it to zero across subsequent calls.
- **Better concurrent throughput** - relevant for JetStream and KV
  workloads, which are RPC-heavy internally.

---

## Headers

NATS headers allow attaching metadata to messages (similar to HTTP headers).
Headers are supported with NATS server 2.2+.

### Publishing with Headers

```zig
const nats = @import("nats");
const headers = nats.protocol.headers;

// Single header
const hdrs = [_]headers.Entry{
    .{ .key = "X-Request-Id", .value = "req-123" },
};
try client.publishWithHeaders("events.user", &hdrs, "user logged in");

// Multiple headers
const multi_hdrs = [_]headers.Entry{
    .{ .key = "Content-Type", .value = "application/json" },
    .{ .key = "X-Correlation-Id", .value = "corr-456" },
    .{ .key = "X-Timestamp", .value = "2026-01-21T10:30:00Z" },
};
try client.publishWithHeaders("events.order", &multi_hdrs, order_json);
```

### Publish with Headers and Reply-To

```zig
const hdrs = [_]headers.Entry{
    .{ .key = "X-Request-Id", .value = "req-789" },
};
try client.publishRequestWithHeaders("service.echo", "my.inbox", &hdrs, "ping");
```

### Request/Reply with Headers

```zig
const hdrs = [_]headers.Entry{
    .{ .key = headers.HeaderName.msg_id, .value = "unique-001" },
};

if (try client.requestWithHeaders("service.api", &hdrs, "data", 5000)) |reply| {
    defer reply.deinit();
    std.debug.print("Response: {s}\n", .{reply.data});
} else {
    std.debug.print("Timeout\n", .{});
}
```

### Receiving and Parsing Headers

```zig
const msg = try sub.nextMsg();
defer msg.deinit();

if (msg.headers) |raw_headers| {
    var parsed = headers.parse(allocator, raw_headers);
    defer parsed.deinit();  // MUST call deinit!

    if (parsed.err == null) {
        // Iterate all headers
        for (parsed.items()) |entry| {
            std.debug.print("{s}: {s}\n", .{ entry.key, entry.value });
        }

        // Lookup by name (case-insensitive)
        if (parsed.get("X-Request-Id")) |req_id| {
            std.debug.print("Request ID: {s}\n", .{req_id});
        }

        // Check for no-responders status
        if (parsed.isNoResponders()) {
            std.debug.print("No responders available\n", .{});
        }
    }
}
```

**Important**: `ParseResult` owns its data (copies strings to heap). This means
parsed headers remain valid even after `msg.deinit()` is called. Always call
`parsed.deinit()` to free memory.

### Well-Known Header Names

Use constants from `headers.HeaderName` for JetStream and NATS features:

```zig
const hdrs = [_]headers.Entry{
    // JetStream message deduplication
    .{ .key = headers.HeaderName.msg_id, .value = "unique-msg-001" },
    // Expected stream for publish
    .{ .key = headers.HeaderName.expected_stream, .value = "ORDERS" },
};
```

| Constant | Header Name | Purpose |
|----------|-------------|---------|
| `msg_id` | `Nats-Msg-Id` | JetStream deduplication |
| `expected_stream` | `Nats-Expected-Stream` | Verify target stream |
| `expected_last_msg_id` | `Nats-Expected-Last-Msg-Id` | Optimistic concurrency |
| `expected_last_seq` | `Nats-Expected-Last-Sequence` | Sequence verification |

### HeaderMap Builder

For programmatic header construction:

```zig
const nats = @import("nats");

var headers = nats.Client.HeaderMap.init(allocator);
defer headers.deinit();

// Set headers (replaces existing)
try headers.set("Content-Type", "application/json");
try headers.set("X-Request-Id", "req-123");

// Add headers (allows multiple values for same key)
try headers.add("X-Tag", "important");
try headers.add("X-Tag", "urgent");

// Get values
if (headers.get("Content-Type")) |ct| {
    std.debug.print("Content-Type: {s}\n", .{ct});
}

// Get all values for a key
if (try headers.getAll("X-Tag")) |tags| {
    defer allocator.free(tags);
    for (tags) |tag| {
        std.debug.print("Tag: {s}\n", .{tag});
    }
}

// Delete headers
headers.delete("X-Tag");

// Publish with HeaderMap (auto-flushed)
try client.publishWithHeaderMap("subject", &headers, "payload");
```

### Header Notes

- Header values can contain colons (URLs, timestamps work fine)
- Case-insensitive lookup for header names
- Header names must be non-empty and cannot contain whitespace, control
  characters, DEL, or `:`. Header values cannot contain control characters
  or DEL. Invalid headers return `error.InvalidHeader`.
- On parse error: `items()` returns empty slice, `get()` returns null

---

## JetStream

JetStream is NATS' persistence and streaming layer. It provides
at-least-once delivery, message replay, and durable consumers --
all through a JSON request/reply API on `$JS.API.*` subjects.

For runnable examples, see `src/examples/jetstream_*.zig`,
`src/examples/kv*.zig`, the focused [JetStream guide](doc/JetStream.md),
and the feature coverage summary below.

### JetStream Example

```zig
const nats = @import("nats");
const js_mod = nats.jetstream;

// Create a JetStream context (stack-allocated, no heap)
var js = try js_mod.JetStream.init(client, .{});

// Create a stream
var stream = try js.createStream(.{
    .name = "ORDERS",
    .subjects = &.{"orders.>"},
    .storage = .memory,
});
defer stream.deinit();

// Publish with ack confirmation
var ack = try js.publish("orders.new", "order-1");
defer ack.deinit();
// ack.value.seq, ack.value.stream

// Create a pull consumer and fetch messages
var cons = try js.createConsumer("ORDERS", .{
    .name = "processor",
    .durable_name = "processor",
    .ack_policy = .explicit,
});
defer cons.deinit();

var pull = js_mod.PullSubscription{
    .js = &js,
    .stream = "ORDERS",
};
try pull.setConsumer("processor");
var result = try pull.fetch(.{
    .max_messages = 10,
    .timeout_ms = 5000,
});
defer result.deinit();

for (result.messages) |*msg| {
    try msg.ack();
}
```

### Key-Value Store Example

```zig
const js_mod = nats.jetstream;

var js = try js_mod.JetStream.init(client, .{});

// Create a KV bucket
var kv = try js.createKeyValue(.{
    .bucket = "config",
    .storage = .memory,
    .history = 5,
});

// Put and get
const rev = try kv.put("db.host", "localhost:5432");
var entry = (try kv.get("db.host")).?;
defer entry.deinit();
// entry.revision == rev, entry.operation == .put

// Optimistic concurrency
const rev2 = try kv.update("db.host", "newhost:5432", rev);

// Create only if key doesn't exist
_ = try kv.create("db.port", "5432");
_ = kv.create("db.port", "9999") catch |err| {
    // err == error.ApiError (key exists)
};

// List all keys
const keys = try kv.keys(allocator);
defer {
    for (keys) |k| allocator.free(k);
    allocator.free(keys);
}

// Watch for real-time updates
var watcher = try kv.watchAll();
defer watcher.deinit();
while (try watcher.next(5000)) |*update| {
    defer update.deinit();
    // update.key, update.revision, update.operation
}
```

Bucket names and keys are validated client-side before API requests are sent.
Bucket names may not be empty, exceed 64 bytes, or contain wildcards,
separators, whitespace, control characters, or DEL. KV keys must be non-empty
NATS subject tokens without wildcards; watch patterns may use `*` and a
terminal `>`.

### Supported JetStream Features

| Area | Supported APIs | Notes |
|------|----------------|-------|
| Streams | `createStream()`, `updateStream()`, `deleteStream()`, `streamInfo()`, `purgeStream()`, `purgeStreamSubject()` | Includes stream listing and subject-filtered purge. |
| Consumers | `createConsumer()`, `updateConsumer()`, `deleteConsumer()`, `consumerInfo()` | Pull, push, and ordered consumer workflows. |
| Listing | `streamNames()`, `streams()`, `consumerNames()`, `consumers()`, `accountInfo()` | Paginated listing APIs are available for streams and consumers. |
| Publishing | `publish()`, `publishWithOpts()`, `publishMsg()` | Publish acknowledgments, deduplication headers, optimistic concurrency, and publish TTL. |
| Pull Consumers | `fetch()`, `fetchNoWait()`, `fetchBytes()`, `next()`, `messages()`, `consume()` | Batch fetch, single-message fetch, continuous pull iteration, callbacks, heartbeat monitoring, and ordered delivery. |
| Push Consumers | `createPushConsumer()`, `PushSubscription.consume()` | Callback delivery uses `JsMsgHandler`; callback messages are borrowed and valid only during the callback. |
| Acknowledgment | `ack()`, `doubleAck()`, `nak()`, `nakWithDelay()`, `inProgress()`, `term()`, `termWithReason()` | Metadata can be parsed from JetStream reply subjects. |
| Key-Value Store | `createKeyValue()`, `keyValue()`, `deleteKeyValue()`, `put()`, `get()`, `create()`, `update()`, `delete()`, `purge()`, `keys()`, `history()`, `watch()`, `watchAll()` | Bucket management, optimistic concurrency by revision, history, filtered key listing, and live watches. |
| Error Handling | `lastApiError()` | JetStream API errors expose server status, error code, and description. |
| Domains | `try JetStream.init(client, .{ .domain = ... })` | Supports multi-tenant JetStream domains. |

### Current Limitations

| Feature | Status |
|---------|--------|
| Object Store | Not implemented |

---

## Micro Services

The `nats.micro` module implements the NATS service API for
discoverable request/reply services. Services automatically register
monitoring endpoints under `$SRV.PING`, `$SRV.INFO`, and `$SRV.STATS`
including name- and id-specific variants.

```zig
const std = @import("std");
const nats = @import("nats");

const Echo = struct {
    pub fn onRequest(_: *@This(), req: *nats.micro.Request) void {
        req.respond(req.data()) catch {};
    }
};

pub fn main(init: std.process.Init) !void {
    const client = try nats.Client.connect(
        init.gpa,
        init.io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    var echo = Echo{};
    const service = try nats.micro.addService(client, .{
        .name = "echo",
        .version = "1.0.0",
        .description = "Echo service",
        .endpoint = .{
            .subject = "echo",
            .handler = nats.micro.Handler.init(Echo, &echo),
        },
    });
    defer service.deinit();

    while (true) {
        init.io.sleep(.fromSeconds(1), .awake) catch {};
    }
}
```

Handlers can be comptime vtable handlers with `Handler.init(T, &value)`
or plain functions with `Handler.fromFn(fn)`. A request handler can
read `req.subject()`, `req.data()`, `req.headers()`, and reply with
`req.respond()`, `req.respondJson()`, or `req.respondError()`.

Services support endpoint groups, queue groups, metadata, stats reset,
and graceful stop/drain:

```zig
var api = try service.addGroup("api");
_ = try api.addEndpoint(.{
    .subject = "v1.echo",
    .handler = nats.micro.Handler.init(Echo, &echo),
});

try service.stop(null);
try service.waitStopped();
```

Run the complete example with:

```bash
zig build run-micro-echo
```

---

## Async Patterns with std.Io

### Cancellation Pattern

Always defer cancel when using `io.async()`:

```zig
var future = io.async(someFn, .{args});
defer future.cancel(io) catch {};  // defer cancel
const result = try future.await(io);
```

### Racing Operations with `Io.Select`

Wait for the first of multiple operations to complete:

```zig
fn sleepMs(io_ctx: std.Io, ms: i64) void {
    io_ctx.sleep(.fromMilliseconds(ms), .awake) catch {};
}

const Sel = std.Io.Select(union(enum) {
    message: anyerror!nats.Message,
    timeout: void,
});
var buf: [2]Sel.Union = undefined;
var sel = Sel.init(io, &buf);
sel.async(.message, nats.Client.Sub.nextMsg, .{sub});
sel.async(.timeout, sleepMs, .{ io, 5000 });

const result = sel.await() catch |err| {
    while (sel.cancel()) |remaining| {
        switch (remaining) {
            .message => |r| {
                if (r) |m| m.deinit() else |_| {}
            },
            .timeout => {},
        }
    }
    return err;
};
while (sel.cancel()) |remaining| {
    switch (remaining) {
        .message => |r| {
            if (r) |m| m.deinit() else |_| {}
        },
        .timeout => {},
    }
}

switch (result) {
    .message => |msg_result| {
        const msg = try msg_result;
        defer msg.deinit();
        std.debug.print("Received: {s}\n", .{msg.data});
    },
    .timeout => {
        std.debug.print("Timeout!\n", .{});
    },
}
```

### Async Message Receive with Ownership

When using `io.async()` to receive messages, handle ownership carefully:

```zig
var future = io.async(nats.Client.Sub.nextMsg, .{sub});
defer if (future.cancel(io)) |m| m.deinit() else |_| {};

if (future.await(io)) |msg| {
    // Message ownership transferred - use it here
    // Do not add defer msg.deinit() - outer defer handles cleanup
    std.debug.print("Got: {s}\n", .{msg.data});
    return;  // outer defer runs, cancel() returns null
} else |err| {
    std.debug.print("Error: {}\n", .{err});
}
```

**Key points:**
- After `await()` succeeds, `cancel()` returns null (message already consumed)
- If function exits before `await()`, `cancel()` returns the pending message
- Adding a second `defer msg.deinit()` inside the if-block would cause double-free

### Io.Queue for Cross-Thread Communication

Use `Io.Queue(T)` for producer/consumer patterns across threads:

```zig
const WorkResult = struct {
    worker_id: u8,
    msg: nats.Message,

    fn deinit(self: WorkResult) void {
        self.msg.deinit();
    }
};

// Fixed-size buffer backing the queue
var queue_buf: [32]WorkResult = undefined;
var queue: Io.Queue(WorkResult) = .init(&queue_buf);

// Worker thread: push results
fn worker(io: Io, sub: *Sub, q: *Io.Queue(WorkResult)) void {
    while (true) {
        const msg = sub.nextMsg() catch return;
        q.putOne(io, .{ .worker_id = 1, .msg = msg }) catch return;
    }
}

// Main thread: consume results
while (true) {
    const result = queue.getOne(io) catch break;
    defer result.deinit();
    std.debug.print("Worker {d}: {s}\n", .{ result.worker_id, result.msg.data });
}
```

**Use cases:**
- Load-balanced workers reporting to main thread
- Aggregating results from `io.concurrent()` tasks
- Decoupling message producers from consumers

---

## Connections

### Connection Options

```zig
const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    // Identity
    .name = "my-app",              // Client name (visible in server logs)

    // Buffers
    .reader_buffer_size = 1024 * 1024 + 8 * 1024, // Read buffer default
    .writer_buffer_size = 1024 * 1024 + 8 * 1024, // Write buffer default
    .sub_queue_size = 8192,            // Per-subscription queue size
    .tcp_rcvbuf = 1024 * 1024,         // TCP receive buffer hint default

    // Timeouts
    .connect_timeout_ns = 5_000_000_000,  // 5 second connect timeout

    // Reconnection
    .reconnect = true,             // Enable auto-reconnect
    .max_reconnect_attempts = 60,  // Max attempts (0 = infinite)
    .reconnect_wait_ms = 2000,     // Initial backoff

    // Keepalive
    .ping_interval_ms = 120_000,   // PING every 2 minutes
    .max_pings_outstanding = 2,    // Disconnect after 2 missed PONGs

    // Inbox prefix (for request/reply)
    .inbox_prefix = "_INBOX",      // Custom inbox prefix

    // Connection behavior
    .retry_on_failed_connect = false,     // Retry on initial failure
    .no_randomize = false,                // Don't randomize server order
    .ignore_discovered_servers = false,   // Only use explicit servers
    .drain_timeout_ms = 30_000,           // Default drain timeout
    .flush_timeout_ms = 10_000,           // Default flush timeout
});
```

### Event Callbacks

Handle connection lifecycle events using the `EventHandler` pattern - a type-safe,
Zig-idiomatic approach similar to `std.mem.Allocator`.

```zig
const MyHandler = struct {
    pub fn onConnect(self: *@This()) void {
        _ = self;
        std.log.info("Connected!", .{});
    }

    pub fn onDisconnect(self: *@This(), err: ?anyerror) void {
        _ = self;
        std.log.warn("Disconnected: {any}", .{err});
    }

    pub fn onReconnect(self: *@This()) void {
        _ = self;
        std.log.info("Reconnected!", .{});
    }
};

var handler = MyHandler{};
const client = try nats.Client.connect(allocator, io, url, .{
    .event_handler = nats.EventHandler.init(MyHandler, &handler),
});
```

**Accessing External State:** Handlers can reference external application state:

```zig
const AppState = struct {
    is_online: bool = false,
    reconnect_count: u32 = 0,
    last_error: ?anyerror = null,
};

const MyHandler = struct {
    app: *AppState,

    pub fn onConnect(self: *@This()) void {
        self.app.is_online = true;
    }

    pub fn onDisconnect(self: *@This(), err: ?anyerror) void {
        self.app.is_online = false;
        self.app.last_error = err;
    }

    pub fn onReconnect(self: *@This()) void {
        self.app.is_online = true;
        self.app.reconnect_count += 1;
    }
};

var app_state = AppState{};
var handler = MyHandler{ .app = &app_state };

const client = try nats.Client.connect(allocator, io, url, .{
    .event_handler = nats.EventHandler.init(MyHandler, &handler),
});
```

| Callback | When Fired |
|----------|------------|
| `onConnect()` | Initial connection established |
| `onDisconnect(?anyerror)` | Connection lost (error or clean close) |
| `onReconnect()` | Reconnection successful |
| `onClose()` | Connection permanently closed |
| `onError(anyerror)` | Async error (slow consumer, etc.) |
| `onLameDuck()` | Server entering shutdown mode |
| `onDiscoveredServers(u8)` | New server discovered in cluster |
| `onDraining()` | Drain process started |
| `onSubscriptionComplete(u64)` | Subscription drain finished (receives SID) |

All callbacks are **optional** - only implement the ones you need.

### Connection State

```zig
const State = @import("nats").connection.State;

const status = client.status();
switch (status) {
    .connected => std.debug.print("Connected\n", .{}),
    .reconnecting => std.debug.print("Reconnecting...\n", .{}),
    .draining => std.debug.print("Draining\n", .{}),
    .closed => std.debug.print("Closed\n", .{}),
    else => {},
}

// Convenience checks
if (client.isClosed()) { /* permanently closed */ }
if (client.isDraining()) { /* draining subscriptions */ }
if (client.isReconnecting()) { /* attempting reconnect */ }

// Subscription count
const num_subs = client.numSubscriptions();
```

### Connection Information

```zig
// Server details (from INFO response)
if (client.connectedUrl()) |url| {
    std.debug.print("Connected to: {s}\n", .{url});
}
if (client.connectedServerId()) |id| {
    std.debug.print("Server ID: {s}\n", .{id});
}
if (client.connectedServerName()) |name| {
    std.debug.print("Server name: {s}\n", .{name});
}
if (client.connectedServerVersion()) |version| {
    std.debug.print("Server version: {s}\n", .{version});
}

// Payload and feature info
const max_payload = client.maxPayload();
const supports_headers = client.headersSupported();

// Server pool (for cluster connections)
const server_count = client.serverCount();
for (0..server_count) |i| {
    if (client.serverUrl(@intCast(i))) |url| {
        std.debug.print("Known server: {s}\n", .{url});
    }
}

// RTT measurement
const rtt_ns = try client.rtt();
const rtt_ms = @as(f64, @floatFromInt(rtt_ns)) / 1_000_000.0;
std.debug.print("RTT: {d:.2}ms\n", .{rtt_ms});
```

### Connection Statistics

Monitor throughput and connection health:

```zig
const stats = client.stats();
std.debug.print("Messages: in={d} out={d}\n", .{stats.msgs_in, stats.msgs_out});
std.debug.print("Bytes: in={d} out={d}\n", .{stats.bytes_in, stats.bytes_out});
std.debug.print("Reconnects: {d}\n", .{stats.reconnects});
```

| Field | Type | Description |
|-------|------|-------------|
| `msgs_in` | `u64` | Total messages received |
| `msgs_out` | `u64` | Total messages sent |
| `bytes_in` | `u64` | Total bytes received |
| `bytes_out` | `u64` | Total bytes sent |
| `reconnects` | `u32` | Number of reconnections |
| `connects` | `u32` | Total successful connections |

### Connection Control

**Flush with Server Confirmation:**

```zig
// Sends PING and waits for PONG (confirms server received messages)
client.flush(5_000_000_000) catch |err| {
    if (err == error.Timeout) {
        std.debug.print("Flush timed out\n", .{});
    }
};
```

**Force Reconnect:**

```zig
try client.forceReconnect();
// Connection closes, io_task starts reconnection process
```

**Drain with Timeout:**

```zig
const result = client.drainTimeout(30_000_000_000) catch |err| {
    if (err == error.Timeout) {
        std.debug.print("Drain timed out\n", .{});
    }
    return err;
};
if (!result.isClean()) {
    std.debug.print("Drain had failures\n", .{});
}
```

### Handling Slow Consumers

When messages arrive faster than you process them, the queue fills up and messages are dropped:

```zig
while (true) {
    const msg = try sub.nextMsg();
    defer msg.deinit();

    // Check for dropped messages periodically
    const dropped = sub.dropped();
    if (dropped > 0) {
        std.log.warn("Dropped {d} messages - consumer too slow", .{dropped});
    }

    processMessage(msg);
}
```

**Tuning for High Throughput:**

```zig
const client = try nats.Client.connect(allocator, io, url, .{
    .sub_queue_size = 16384,          // Larger per-subscription queue
    .tcp_rcvbuf = 512 * 1024,         // 512KB TCP buffer
    .reader_buffer_size = 2 * 1024 * 1024, // 2MB read buffer
    .writer_buffer_size = 2 * 1024 * 1024, // 2MB write buffer
});
```

---

## Authentication

### Username/Password

```zig
const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    .user = "user",
    .pass = "pass",
});
```

### Token Authentication

```zig
const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    .auth_token = "my-secret-token",
});
```

### NKey Authentication

NKey authentication uses Ed25519 signatures for secure, password-less
authentication. NKeys are the recommended authentication method for production
NATS deployments.

**Using NKey Seed (Direct):**

```zig
const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    .nkey_seed = "SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY",
});
```

**Using NKey Seed File:**

```zig
const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    .nkey_seed_file = "/path/to/user.nk",
});
```

**Using Signing Callback (HSM/Hardware Keys):**

```zig
fn mySignCallback(nonce: []const u8, sig: *[64]u8) bool {
    // Sign nonce using HSM, hardware token, etc.
    return hsm.sign(nonce, sig);
}

const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    .nkey_pubkey = "UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4",
    .nkey_sign_fn = &mySignCallback,
});
```

### JWT/Credentials Authentication

For NATS deployments using the account/user JWT model.

**Using Credentials File:**

```zig
const client = try nats.Client.connect(allocator, io, "nats://localhost:4222", .{
    .creds_file = "/path/to/user.creds",
});
```

**Using Credentials Content:**

```zig
// From environment variable
const creds = std.posix.getenv("NATS_CREDS") orelse return error.MissingCreds;
const client = try nats.Client.connect(allocator, io, url, .{
    .creds = creds,
});

// Or embed at compile time
const client = try nats.Client.connect(allocator, io, url, .{
    .creds = @embedFile("user.creds"),
});
```

### NKey Generation & JWT Encoding

Generate NKey keypairs, encode JWTs, and format credentials files
programmatically. No allocator needed - all operations use
caller-provided stack buffers.

**Generate Keypairs:**

```zig
const nats = @import("nats");

// Generate operator, account, and user keypairs
var op_kp = nats.auth.KeyPair.generate(io, .operator);
defer op_kp.wipe();

var acct_kp = nats.auth.KeyPair.generate(io, .account);
defer acct_kp.wipe();

var user_kp = nats.auth.KeyPair.generate(io, .user);
defer user_kp.wipe();

// Get public key (base32-encoded, 56 chars)
var pk_buf: [56]u8 = undefined;
const pub_key = op_kp.publicKey(&pk_buf);  // "O..."

// Encode seed (base32-encoded, 58 chars)
var seed_buf: [58]u8 = undefined;
const seed = op_kp.encodeSeed(&seed_buf);  // "SO..."
```

**Encode JWTs:**

```zig
// Account JWT (signed by operator)
var acct_jwt_buf: [2048]u8 = undefined;
const acct_jwt = try nats.auth.jwt.encodeAccountClaims(
    &acct_jwt_buf,
    acct_pub,       // account public key (subject)
    "my-account",   // account name
    op_kp,          // operator keypair (signer)
    iat,            // issued-at (unix seconds)
    .{},            // AccountOptions (defaults: unlimited)
);

// User JWT with permissions (signed by account)
var user_jwt_buf: [2048]u8 = undefined;
const user_jwt = try nats.auth.jwt.encodeUserClaims(
    &user_jwt_buf,
    user_pub,       // user public key (subject)
    "my-user",      // user name
    acct_kp,        // account keypair (signer)
    iat,            // issued-at (unix seconds)
    .{
        .pub_allow = &.{"app.>"},
        .sub_allow = &.{ "app.>", "_INBOX.>" },
    },
);
```

**Format Credentials File:**

```zig
var creds_buf: [4096]u8 = undefined;
const creds = nats.auth.creds.format(
    &creds_buf,
    user_jwt,   // JWT string
    user_seed,  // NKey seed string
);
// creds contains the full .creds file content
```

**Account Options (limits):**

| Field | Default | Description |
|-------|---------|-------------|
| `subs` | `-1` | Max subscriptions (-1 = unlimited) |
| `conn` | `-1` | Max connections |
| `data` | `-1` | Max data bytes |
| `payload` | `-1` | Max message payload |
| `imports` | `-1` | Max imports |
| `exports` | `-1` | Max exports |
| `leaf` | `-1` | Max leaf node connections |
| `mem_storage` | `-1` | Max memory storage |
| `disk_storage` | `-1` | Max disk storage |
| `wildcards` | `true` | Allow wildcard subscriptions |

**User Options (permissions):**

| Field | Default | Description |
|-------|---------|-------------|
| `pub_allow` | `&.{}` | Subjects allowed to publish |
| `sub_allow` | `&.{}` | Subjects allowed to subscribe |
| `subs` | `-1` | Max subscriptions (-1 = unlimited) |
| `data` | `-1` | Max data bytes |
| `payload` | `-1` | Max message payload |

See the [NKeys & JWTs example](doc/nats-by-example/auth/nkeys-jwts.zig)
for a complete working example.

### TLS

**Enabling TLS:**

```zig
// 1. URL scheme (recommended)
const client = try nats.Client.connect(allocator, io, "tls://localhost:4443", .{});

// 2. Explicit option
const client = try nats.Client.connect(allocator, io, "nats://localhost:4443", .{
    .tls_required = true,
});

// 3. Automatic - if server requires TLS, client upgrades automatically
```

**TLS Options:**

```zig
const client = try nats.Client.connect(allocator, io, "tls://localhost:4443", .{
    // Server certificate verification (production)
    .tls_ca_file = "/path/to/ca.pem",

    // Skip verification (development only!)
    .tls_insecure_skip_verify = true,

    // TLS-first handshake (for TLS-terminating proxies)
    .tls_handshake_first = true,
});
```

**Mutual TLS (mTLS):** client certificates are planned but not
implemented yet. Setting `tls_cert_file` or `tls_key_file` currently
returns `error.MtlsNotImplemented`.

**Checking TLS Status:**

```zig
if (client.isTls()) {
    std.debug.print("Connection is encrypted\n", .{});
}
```

| Option | Type | Description |
|--------|------|-------------|
| `tls_required` | `bool` | Force TLS connection |
| `tls_ca_file` | `?[]const u8` | CA certificate file path (PEM) |
| `tls_cert_file` | `?[]const u8` | Reserved for mTLS; currently returns `error.MtlsNotImplemented` |
| `tls_key_file` | `?[]const u8` | Reserved for mTLS; currently returns `error.MtlsNotImplemented` |
| `tls_insecure_skip_verify` | `bool` | Skip server certificate verification |
| `tls_handshake_first` | `bool` | TLS handshake before NATS protocol |

### Authentication Priority

When multiple auth options are set:

1. `creds_file` / `creds` - JWT + NKey from credentials
2. `nkey_seed` / `nkey_seed_file` - NKey only
3. `nkey_sign_fn` + `nkey_pubkey` - Custom signing
4. `user` / `pass` or `auth_token` - Basic auth

### Security Notes

- The library wipes seed data from memory after use (best effort)

---

## Error Handling

```zig
client.publish(subject, data) catch |err| switch (err) {
    error.NotConnected => {
        // Connection lost - wait for reconnect or handle
    },
    error.PayloadTooLarge => {
        // Message exceeds server max_payload (usually 1MB)
    },
    error.EncodingFailed => {
        // Protocol encoding error
    },
    else => return err,
};
```

### Common Errors

| Error | Meaning |
|-------|---------|
| `NotConnected` | Not connected to server |
| `ConnectionClosed` | Connection closed unexpectedly |
| `ConnectionTimeout` | Connection attempt timed out |
| `ConnectionRefused` | Server refused connection |
| `AuthenticationFailed` | Authentication failed |
| `PayloadTooLarge` | Message exceeds max_payload |
| `TooManySubscriptions` | Subscription limit reached (16,384) |
| `Closed` | Connection was closed |
| `Canceled` | Operation was cancelled |
| `Timeout` | Operation timed out |

---

## Server Compatibility

Verify the server meets minimum version requirements:

```zig
// Check for NATS 2.10.0 or later (required for some features)
if (client.checkCompatibility(2, 10, 0)) {
    // Server supports NATS 2.10+ features
} else {
    std.debug.print("Server version too old\n", .{});
}

// Get the actual version string
if (client.connectedServerVersion()) |version| {
    std.debug.print("Connected to NATS {s}\n", .{version});
}
```

---

## Building

```bash
# Build library
zig build

# Run unit tests
zig build test

# Run integration tests (requires nats-server and nats CLI)
zig build test-integration

# Format code
zig build fmt
```

See [src/testing/README.md](src/testing/README.md) for integration test
layout, fixtures, and focused test targets.

---

## Status

| Component | Status |
|-----------|--------|
| Core Protocol | Supported |
| Pub/Sub | Supported |
| Request/Reply | Supported |
| Headers | Supported |
| Reconnection | Supported |
| Event Callbacks | Supported |
| NKey Authentication | Supported |
| JWT/Credentials | Supported |
| Server-authenticated TLS | Supported |
| mTLS client certificates | Planned |
| JetStream Core | Supported |
| JetStream Pull Consumers | Supported |
| JetStream Push Consumers | Supported |
| JetStream Ordered Consumer | Supported |
| Key-Value Store | Supported |
| Micro Services API | Supported |
| Object Store | Planned |
| Async Publish | Supported |

## Related Projects

Other Zig-based NATS implementations from the community:

- [NATS C client library, packaged for Zig](https://github.com/allyourcodebase/nats.c)
- [Zig language bindings to the NATS.c library](https://github.com/epicyclic-dev/nats-client)
- [Zig client for NATS Core and JetStream](https://github.com/g41797/nats)
- [A Zig client library for NATS, the cloud-native messaging system](https://github.com/lalinsky/nats.zig)
- [Minimal synchronous NATS Zig client](https://github.com/ianic/nats.zig)
- [Work-in-progress NATS library for Zig](https://github.com/rutgerbrf/zig-nats)

## License

Apache 2.0

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup,
test commands, and contribution guidelines.


================================================
FILE: SECURITY.md
================================================
# Security Policy

Please report suspected security vulnerabilities privately. Do not open a
public issue for a vulnerability report.

If GitHub private vulnerability reporting is enabled for this repository, use
that flow. Otherwise, contact the maintainers through the NATS project security
process before disclosing details publicly.

Public NATS security advisories are published at:

https://advisories.nats.io/

When reporting a vulnerability, include:

- affected version or commit;
- a minimal reproduction when possible;
- expected and observed behavior;
- impact assessment;
- any known workaround.


================================================
FILE: build.zig
================================================
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Debug option for reconnection events (default: false)
    const enable_debug = b.option(
        bool,
        "EnableDebug",
        "Enable debug prints for reconnection events (default: false)",
    ) orelse false;

    // Io backend selector.
    // 'threaded' = std.Io.Threaded (default, OS threads).
    // 'evented'  = std.Io.Evented (Linux: Uring, BSD: Kqueue, Apple: Dispatch).
    const io_backend_choice = b.option(
        []const u8,
        "io_backend",
        "Io backend: 'threaded' (default) or 'evented'",
    ) orelse "threaded";

    // Create build options module. Share a single Module instance
    // across all consumers (nats, io_backend, ...) — calling
    // createModule() twice would generate two distinct Modules
    // pointing at the same options.zig file, which Zig rejects
    // when both end up in the same compile graph.
    const build_options = b.addOptions();
    build_options.addOption(bool, "enable_debug", enable_debug);
    build_options.addOption([]const u8, "io_backend", io_backend_choice);
    const build_options_mod = build_options.createModule();

    const nats = b.addModule("nats", .{
        .root_source_file = b.path("src/nats.zig"),
        .target = target,
        .imports = &.{
            .{ .name = "build_options", .module = build_options_mod },
        },
    });

    const mod_tests = b.addTest(.{ .root_module = nats });
    const run_mod_tests = b.addRunArtifact(mod_tests);

    const test_step = b.step("test", "Run tests");
    test_step.dependOn(&run_mod_tests.step);

    // Backend selector module. Used by entry points (examples,
    // integration tests) so they can flip between
    // std.Io.Threaded and std.Io.Evented via -Dio_backend=...
    // The library module itself does NOT depend on this; only
    // application code chooses a backend.
    const io_backend_mod = b.createModule(.{
        .root_source_file = b.path("src/io_backend.zig"),
        .target = target,
        .imports = &.{
            .{
                .name = "build_options",
                .module = build_options_mod,
            },
        },
    });

    // Standalone test for the io_backend selector module. Ensures
    // src/io_backend.zig compiles under -Dio_backend=threaded and
    // -Dio_backend=evented.
    const io_backend_tests = b.addTest(.{ .root_module = io_backend_mod });
    const run_io_backend_tests = b.addRunArtifact(io_backend_tests);
    test_step.dependOn(&run_io_backend_tests.step);

    // 1. Simple example (hello world - entry point)
    const simple_exe = b.addExecutable(.{
        .name = "example-simple",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/simple.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(simple_exe);

    const run_simple = b.step("run-simple", "Run simple hello world example");
    const simple_cmd = b.addRunArtifact(simple_exe);
    run_simple.dependOn(&simple_cmd.step);
    simple_cmd.step.dependOn(b.getInstallStep());

    // 2. Request/Reply example (RPC pattern)
    const request_reply_exe = b.addExecutable(.{
        .name = "example-request-reply",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/request_reply.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(request_reply_exe);

    const run_request_reply = b.step(
        "run-request-reply",
        "Run request/reply RPC example",
    );
    const request_reply_cmd = b.addRunArtifact(request_reply_exe);
    run_request_reply.dependOn(&request_reply_cmd.step);
    request_reply_cmd.step.dependOn(b.getInstallStep());

    // Headers example (metadata with HPUB/HMSG)
    const headers_exe = b.addExecutable(.{
        .name = "example-headers",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/headers.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(headers_exe);

    const run_headers = b.step(
        "run-headers",
        "Run headers example",
    );
    const headers_cmd = b.addRunArtifact(headers_exe);
    run_headers.dependOn(&headers_cmd.step);
    headers_cmd.step.dependOn(b.getInstallStep());

    // 3. Queue Groups example (load balancing with workers)
    const queue_groups_exe = b.addExecutable(.{
        .name = "example-queue-groups",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/queue_groups.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(queue_groups_exe);

    const run_queue_groups = b.step(
        "run-queue-groups",
        "Run queue groups (load balancing) example",
    );
    const queue_groups_cmd = b.addRunArtifact(queue_groups_exe);
    run_queue_groups.dependOn(&queue_groups_cmd.step);
    queue_groups_cmd.step.dependOn(b.getInstallStep());

    // 4. Select example (io.select timeout pattern)
    const select_exe = b.addExecutable(.{
        .name = "example-select",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/select.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(select_exe);

    const run_select = b.step(
        "run-select",
        "Run io.select() async timeout example",
    );
    const select_cmd = b.addRunArtifact(select_exe);
    run_select.dependOn(&select_cmd.step);
    select_cmd.step.dependOn(b.getInstallStep());

    // 5. Batch Receiving example (efficient batch message retrieval)
    const batch_exe = b.addExecutable(.{
        .name = "example-batch-receiving",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/batch_receiving.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(batch_exe);

    const run_batch = b.step(
        "run-batch-receiving",
        "Run batch receiving patterns example",
    );
    const batch_cmd = b.addRunArtifact(batch_exe);
    run_batch.dependOn(&batch_cmd.step);
    batch_cmd.step.dependOn(b.getInstallStep());

    // 6. Graceful Shutdown example (drain and lifecycle)
    const shutdown_exe = b.addExecutable(.{
        .name = "example-graceful-shutdown",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/graceful_shutdown.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(shutdown_exe);

    const run_shutdown = b.step(
        "run-graceful-shutdown",
        "Run graceful shutdown (drain) example",
    );
    const shutdown_cmd = b.addRunArtifact(shutdown_exe);
    run_shutdown.dependOn(&shutdown_cmd.step);
    shutdown_cmd.step.dependOn(b.getInstallStep());

    // 7. Reconnection example (resilience patterns)
    const reconnect_exe = b.addExecutable(.{
        .name = "example-reconnection",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/reconnection.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(reconnect_exe);

    const run_reconnect = b.step(
        "run-reconnection",
        "Run reconnection resilience example",
    );
    const reconnect_cmd = b.addRunArtifact(reconnect_exe);
    run_reconnect.dependOn(&reconnect_cmd.step);
    reconnect_cmd.step.dependOn(b.getInstallStep());

    // 8. Polling Loop example (non-blocking patterns)
    const polling_exe = b.addExecutable(.{
        .name = "example-polling-loop",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/polling_loop.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(polling_exe);

    const run_polling = b.step(
        "run-polling-loop",
        "Run non-blocking polling loop example",
    );
    const polling_cmd = b.addRunArtifact(polling_exe);
    run_polling.dependOn(&polling_cmd.step);
    polling_cmd.step.dependOn(b.getInstallStep());

    // 9. Event Callbacks example (connection lifecycle)
    const events_exe = b.addExecutable(.{
        .name = "example-events",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/examples/events.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(events_exe);

    const run_events = b.step(
        "run-events",
        "Run event callbacks (connection lifecycle) example",
    );
    const events_cmd = b.addRunArtifact(events_exe);
    run_events.dependOn(&events_cmd.step);
    events_cmd.step.dependOn(b.getInstallStep());

    // 10. Callback Subscriptions example
    const callback_exe = b.addExecutable(.{
        .name = "example-callback",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/callback.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(callback_exe);

    const run_callback = b.step(
        "run-callback",
        "Run callback subscriptions example",
    );
    const callback_cmd = b.addRunArtifact(callback_exe);
    run_callback.dependOn(&callback_cmd.step);
    callback_cmd.step.dependOn(b.getInstallStep());

    // 11. Request/Reply with Callback example
    const req_rep_cb_exe = b.addExecutable(.{
        .name = "example-request-reply-callback",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/request_reply_callback.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(req_rep_cb_exe);

    const run_req_rep_cb = b.step(
        "run-request-reply-callback",
        "Run request/reply callback example",
    );
    const req_rep_cb_cmd = b.addRunArtifact(req_rep_cb_exe);
    run_req_rep_cb.dependOn(&req_rep_cb_cmd.step);
    req_rep_cb_cmd.step.dependOn(b.getInstallStep());

    // 12. JetStream Publish example
    const js_pub_exe = b.addExecutable(.{
        .name = "example-jetstream-publish",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/jetstream_publish.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(js_pub_exe);

    const run_js_pub = b.step(
        "run-jetstream-publish",
        "Run JetStream publish example",
    );
    const js_pub_cmd = b.addRunArtifact(js_pub_exe);
    run_js_pub.dependOn(&js_pub_cmd.step);
    js_pub_cmd.step.dependOn(b.getInstallStep());

    // 13. JetStream Consume example
    const js_consume_exe = b.addExecutable(.{
        .name = "example-jetstream-consume",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/jetstream_consume.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(js_consume_exe);

    const run_js_consume = b.step(
        "run-jetstream-consume",
        "Run JetStream pull consumer example",
    );
    const js_consume_cmd = b.addRunArtifact(
        js_consume_exe,
    );
    run_js_consume.dependOn(&js_consume_cmd.step);
    js_consume_cmd.step.dependOn(b.getInstallStep());

    // 14. JetStream Push Consumer example
    const js_push_exe = b.addExecutable(.{
        .name = "example-jetstream-push",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/jetstream_push.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(js_push_exe);

    const run_js_push = b.step(
        "run-jetstream-push",
        "Run JetStream push consumer example",
    );
    const js_push_cmd = b.addRunArtifact(js_push_exe);
    run_js_push.dependOn(&js_push_cmd.step);
    js_push_cmd.step.dependOn(b.getInstallStep());

    // 15. JetStream Async Publish example
    const js_async_exe = b.addExecutable(.{
        .name = "example-jetstream-async-publish",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/jetstream_async_publish.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(js_async_exe);

    const run_js_async = b.step(
        "run-jetstream-async-publish",
        "Run JetStream async publish example",
    );
    const js_async_cmd = b.addRunArtifact(
        js_async_exe,
    );
    run_js_async.dependOn(&js_async_cmd.step);
    js_async_cmd.step.dependOn(b.getInstallStep());

    // 16. Key-Value Store example
    const kv_exe = b.addExecutable(.{
        .name = "example-kv",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/kv.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(kv_exe);

    const run_kv = b.step(
        "run-kv",
        "Run KV store example",
    );
    const kv_cmd = b.addRunArtifact(kv_exe);
    run_kv.dependOn(&kv_cmd.step);
    kv_cmd.step.dependOn(b.getInstallStep());

    // 17. Key-Value Watch example
    const kv_watch_exe = b.addExecutable(.{
        .name = "example-kv-watch",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/kv_watch.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(kv_watch_exe);

    const run_kv_watch = b.step(
        "run-kv-watch",
        "Run KV watch example",
    );
    const kv_watch_cmd = b.addRunArtifact(
        kv_watch_exe,
    );
    run_kv_watch.dependOn(&kv_watch_cmd.step);
    kv_watch_cmd.step.dependOn(b.getInstallStep());

    // 18. Microservices echo example
    const micro_echo_exe = b.addExecutable(.{
        .name = "example-micro-echo",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/examples/micro_echo.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(micro_echo_exe);

    const run_micro_echo = b.step(
        "run-micro-echo",
        "Run microservices echo example",
    );
    const micro_echo_cmd = b.addRunArtifact(
        micro_echo_exe,
    );
    run_micro_echo.dependOn(&micro_echo_cmd.step);
    micro_echo_cmd.step.dependOn(b.getInstallStep());

    // NATS by Example: Pub-Sub messaging
    const nbe_pubsub_exe = b.addExecutable(.{
        .name = "nbe-messaging-pub-sub",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "doc/nats-by-example/messaging/pub-sub.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(nbe_pubsub_exe);

    const run_nbe_pubsub = b.step(
        "run-nbe-messaging-pub-sub",
        "Run NATS by Example: Pub-Sub messaging",
    );
    const nbe_pubsub_cmd = b.addRunArtifact(nbe_pubsub_exe);
    run_nbe_pubsub.dependOn(&nbe_pubsub_cmd.step);
    nbe_pubsub_cmd.step.dependOn(b.getInstallStep());

    // NATS by Example: Request-Reply
    const nbe_reqrep_exe = b.addExecutable(.{
        .name = "nbe-messaging-request-reply",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "doc/nats-by-example/messaging/request-reply.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(nbe_reqrep_exe);

    const run_nbe_reqrep = b.step(
        "run-nbe-messaging-request-reply",
        "Run NATS by Example: Request-Reply",
    );
    const nbe_reqrep_cmd = b.addRunArtifact(nbe_reqrep_exe);
    run_nbe_reqrep.dependOn(&nbe_reqrep_cmd.step);
    nbe_reqrep_cmd.step.dependOn(b.getInstallStep());

    // NATS by Example: JSON payloads
    const nbe_json_exe = b.addExecutable(.{
        .name = "nbe-messaging-json",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "doc/nats-by-example/messaging/json.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(nbe_json_exe);

    const run_nbe_json = b.step(
        "run-nbe-messaging-json",
        "Run NATS by Example: JSON payloads",
    );
    const nbe_json_cmd = b.addRunArtifact(nbe_json_exe);
    run_nbe_json.dependOn(&nbe_json_cmd.step);
    nbe_json_cmd.step.dependOn(b.getInstallStep());

    // NATS by Example: Concurrent processing
    const nbe_concurrent_exe = b.addExecutable(.{
        .name = "nbe-messaging-concurrent",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "doc/nats-by-example/messaging/concurrent.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(nbe_concurrent_exe);

    const run_nbe_concurrent = b.step(
        "run-nbe-messaging-concurrent",
        "Run NATS by Example: Concurrent processing",
    );
    const nbe_concurrent_cmd = b.addRunArtifact(
        nbe_concurrent_exe,
    );
    run_nbe_concurrent.dependOn(&nbe_concurrent_cmd.step);
    nbe_concurrent_cmd.step.dependOn(b.getInstallStep());

    // NATS by Example: Iterating multiple subscriptions
    const nbe_multisub_exe = b.addExecutable(.{
        .name = "nbe-messaging-iterating-multiple-subscriptions",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "doc/nats-by-example/messaging/" ++
                    "iterating-multiple-subscriptions.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(nbe_multisub_exe);

    const run_nbe_multisub = b.step(
        "run-nbe-messaging-iterating-multiple-subscriptions",
        "Run NATS by Example: Multiple subscriptions",
    );
    const nbe_multisub_cmd = b.addRunArtifact(
        nbe_multisub_exe,
    );
    run_nbe_multisub.dependOn(&nbe_multisub_cmd.step);
    nbe_multisub_cmd.step.dependOn(b.getInstallStep());

    // NATS by Example: NKeys and JWTs (auth)
    const nbe_nkeys_jwts_exe = b.addExecutable(.{
        .name = "nbe-auth-nkeys-jwts",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "doc/nats-by-example/auth/nkeys-jwts.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
            },
        }),
    });
    b.installArtifact(nbe_nkeys_jwts_exe);

    const run_nbe_nkeys_jwts = b.step(
        "run-nbe-auth-nkeys-jwts",
        "Run NATS by Example: NKeys and JWTs",
    );
    const nbe_nkeys_jwts_cmd = b.addRunArtifact(
        nbe_nkeys_jwts_exe,
    );
    run_nbe_nkeys_jwts.dependOn(&nbe_nkeys_jwts_cmd.step);
    nbe_nkeys_jwts_cmd.step.dependOn(b.getInstallStep());

    const fmt = b.addFmt(.{
        .paths = &.{ "src", "doc", "build.zig" },
        .check = false,
    });
    const fmt_step = b.step("fmt", "Format source code");
    fmt_step.dependOn(&fmt.step);

    const fmt_check = b.addFmt(.{
        .paths = &.{ "src", "doc", "build.zig" },
        .check = true,
    });
    const fmt_check_step = b.step("fmt-check", "Check formatting");
    fmt_check_step.dependOn(&fmt_check.step);

    // Integration tests (requires nats-server)
    const integration_exe = b.addExecutable(.{
        .name = "integration-test",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/testing/integration_test.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(integration_exe);

    const run_integration = b.step(
        "test-integration",
        "Run integration tests (requires nats-server)",
    );
    const integration_cmd = b.addRunArtifact(integration_exe);
    run_integration.dependOn(&integration_cmd.step);

    // Micro-only integration tests (faster; just the micro suite).
    const micro_integration_exe = b.addExecutable(.{
        .name = "micro-integration-test",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/testing/micro_integration_test.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(micro_integration_exe);

    const run_micro_integration = b.step(
        "test-integration-micro",
        "Run only the micro integration tests",
    );
    const micro_integration_cmd = b.addRunArtifact(
        micro_integration_exe,
    );
    run_micro_integration.dependOn(&micro_integration_cmd.step);

    // Focused JWT/TLS integration tests.
    const tls_integration_exe = b.addExecutable(.{
        .name = "tls-integration-test",
        .root_module = b.createModule(.{
            .root_source_file = b.path(
                "src/testing/tls_integration_test.zig",
            ),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "nats", .module = nats },
                .{ .name = "io_backend", .module = io_backend_mod },
            },
        }),
    });
    b.installArtifact(tls_integration_exe);

    const run_tls_integration = b.step(
        "test-integration-tls",
        "Run only the TLS integration tests",
    );
    const tls_integration_cmd = b.addRunArtifact(
        tls_integration_exe,
    );
    run_tls_integration.dependOn(&tls_integration_cmd.step);
}


================================================
FILE: build.zig.zon
================================================
.{
    .name = .nats,
    .version = "0.1.0",
    // Changing fingerprint has security and trust implications.
    .fingerprint = 0x31f7624fb15addf7,
    .minimum_zig_version = "0.16.0",
    .dependencies = .{},
    .paths = .{
        "CONTRIBUTING.md",
        "LICENSE",
        "README.md",
        "SECURITY.md",
        "build.zig",
        "build.zig.zon",
        "doc",
        "logo",
        "src",
    },
}


================================================
FILE: doc/JetStream.md
================================================
# JetStream Guide for nats.zig

JetStream is NATS' persistence and streaming layer. It provides
at-least-once delivery, message replay, and durable consumers --
all through a JSON request/reply API layered on core NATS. No new
wire protocol; everything goes through `$JS.API.*` subjects.

This guide covers the nats.zig JetStream API with side-by-side
Go comparisons for developers familiar with nats.go.

It is a focused companion to the comprehensive root
[README](../README.md). For Key-Value Store coverage, see the
README JetStream section and `src/examples/kv*.zig`.

## Table of Contents

- [Quick Start](#quick-start)
- [JetStream Context](#jetstream-context)
- [Streams](#streams)
- [Consumers](#consumers)
- [Publishing](#publishing)
- [Pull Subscription](#pull-subscription)
- [Message Acknowledgment](#message-acknowledgment)
- [Error Handling](#error-handling)
- [Response Ownership](#response-ownership)
- [Type Reference](#type-reference)

---

## Quick Start

A complete example: create a stream, publish a message, create a
consumer, fetch the message, and acknowledge it.

**Zig:**

```zig
const nats = @import("nats");
const js_mod = nats.jetstream;

// Assumes `client` is already connected
var js = try js_mod.JetStream.init(client, .{});

// Create a stream
var stream = try js.createStream(.{
    .name = "ORDERS",
    .subjects = &.{"orders.>"},
    .storage = .memory,
});
defer stream.deinit();

// Publish a message
var ack = try js.publish("orders.new", "order-1");
defer ack.deinit();
// ack.value.seq == 1, ack.value.stream == "ORDERS"

// Create a consumer
var cons = try js.createConsumer("ORDERS", .{
    .name = "processor",
    .durable_name = "processor",
    .ack_policy = .explicit,
});
defer cons.deinit();

// Fetch messages
var pull = js_mod.PullSubscription{
    .js = &js,
    .stream = "ORDERS",
};
try pull.setConsumer("processor");
var result = try pull.fetch(.{
    .max_messages = 10,
    .timeout_ms = 5000,
});
defer result.deinit();

for (result.messages) |*msg| {
    // msg.data() returns the payload
    try msg.ack();
}
```

**Go:**

```go
js, _ := jetstream.New(nc)

// Create a stream
stream, _ := js.CreateStream(ctx, jetstream.StreamConfig{
    Name:     "ORDERS",
    Subjects: []string{"orders.>"},
    Storage:  jetstream.MemoryStorage,
})

// Publish a message
ack, _ := js.Publish(ctx, "orders.new", []byte("order-1"))
// ack.Stream == "ORDERS", ack.Sequence == 1

// Create a consumer
cons, _ := js.CreateConsumer(ctx, "ORDERS",
    jetstream.ConsumerConfig{
        Durable:   "processor",
        AckPolicy: jetstream.AckExplicitPolicy,
    })

// Fetch messages
batch, _ := cons.Fetch(10)
for msg := range batch.Messages() {
    msg.Ack()
}
```

---

## JetStream Context

The JetStream context is a lightweight struct (stack-allocated) that
holds a pointer to the NATS client, the API prefix, and timeout
settings. No heap allocation is needed. `JetStream.init()` is fallible because
it validates the API prefix or domain before storing it in the fixed-size
context buffer.

### Creating a Context

**Zig:**

```zig
const js_mod = nats.jetstream;

// Default settings
var js = try js_mod.JetStream.init(client, .{});

// Custom timeout
var js2 = try js_mod.JetStream.init(client, .{
    .timeout_ms = 10000,
});

// With domain (multi-tenant)
var js3 = try js_mod.JetStream.init(client, .{
    .domain = "hub",
});
// API prefix becomes: $JS.hub.API.
```

Stream, consumer, domain, and API-prefix names are validated at runtime.
Invalid names return `error.InvalidName`, `error.InvalidApiPrefix`, or
`error.NameTooLong` instead of relying on debug-only assertions.

**Go:**

```go
js, _ := jetstream.New(nc)

// With domain
js, _ = jetstream.NewWithDomain(nc, "hub")
```

### Options

| Field | Zig | Go | Default |
|-------|-----|-----|---------|
| API prefix | `.api_prefix` | `APIPrefix` | `$JS.API.` |
| Timeout | `.timeout_ms` | `DefaultTimeout` | 5000ms |
| Domain | `.domain` | via `NewWithDomain()` | none |

---

## Streams

Streams capture messages published to matching subjects.

### Create a Stream

**Zig:**

```zig
var resp = try js.createStream(.{
    .name = "EVENTS",
    .subjects = &.{"events.>"},
    .retention = .limits,
    .storage = .file,
    .max_msgs = 100000,
    .max_bytes = 1073741824, // 1GB
});
defer resp.deinit();

const info = resp.value;
// info.config.?.name == "EVENTS"
// info.state.?.messages == 0
```

**Go:**

```go
stream, _ := js.CreateStream(ctx, jetstream.StreamConfig{
    Name:      "EVENTS",
    Subjects:  []string{"events.>"},
    Retention: jetstream.LimitsPolicy,
    Storage:   jetstream.FileStorage,
    MaxMsgs:   100000,
    MaxBytes:  1073741824,
})
info, _ := stream.Info(ctx)
```

### Get Stream Info

**Zig:**

```zig
var info = try js.streamInfo("EVENTS");
defer info.deinit();

if (info.value.state) |state| {
    // state.messages, state.bytes, state.first_seq,
    // state.last_seq, state.consumer_count
}
```

**Go:**

```go
stream, _ := js.Stream(ctx, "EVENTS")
info, _ := stream.Info(ctx)
// info.State.Msgs, info.State.Bytes, etc.
```

### Update a Stream

**Zig:**

```zig
var resp = try js.updateStream(.{
    .name = "EVENTS",
    .subjects = &.{ "events.>", "logs.>" },
    .max_msgs = 200000,
});
defer resp.deinit();
```

**Go:**

```go
stream, _ := js.UpdateStream(ctx, jetstream.StreamConfig{
    Name:     "EVENTS",
    Subjects: []string{"events.>", "logs.>"},
    MaxMsgs:  200000,
})
```

### Purge a Stream

**Zig:**

```zig
var resp = try js.purgeStream("EVENTS");
defer resp.deinit();
// resp.value.purged == number of messages removed
```

**Go:**

```go
stream, _ := js.Stream(ctx, "EVENTS")
_ = stream.Purge(ctx)
```

### Delete a Stream

**Zig:**

```zig
var resp = try js.deleteStream("EVENTS");
defer resp.deinit();
// resp.value.success == true
```

**Go:**

```go
_ = js.DeleteStream(ctx, "EVENTS")
```

### StreamConfig Reference

| Field | Type | Description |
|-------|------|-------------|
| `name` | `[]const u8` | Stream name (required) |
| `subjects` | `?[]const []const u8` | Subjects to capture |
| `retention` | `?RetentionPolicy` | limits, interest, workqueue |
| `storage` | `?StorageType` | file, memory |
| `max_msgs` | `?i64` | Max messages in stream |
| `max_bytes` | `?i64` | Max total bytes |
| `max_age` | `?i64` | Max message age (nanoseconds) |
| `max_msg_size` | `?i32` | Max single message size |
| `max_msgs_per_subject` | `?i64` | Per-subject limit |
| `max_consumers` | `?i64` | Max consumers |
| `num_replicas` | `?i32` | Replica count |
| `discard` | `?DiscardPolicy` | old, new |
| `duplicate_window` | `?i64` | Dedup window (nanoseconds) |
| `no_ack` | `?bool` | Disable publish acks |
| `compression` | `?StoreCompression` | none, s2 |

All optional fields default to `null` and are omitted from the
JSON request (server applies its own defaults).

---

## Consumers

Consumers track read position in a stream and manage message
delivery.

### Create a Consumer

**Zig:**

```zig
var resp = try js.createConsumer("EVENTS", .{
    .name = "my-worker",
    .durable_name = "my-worker",
    .ack_policy = .explicit,
    .deliver_policy = .all,
    .filter_subject = "events.orders.>",
    .max_ack_pending = 1000,
});
defer resp.deinit();

if (resp.value.name) |name| {
    // name == "my-worker"
}
```

**Go:**

```go
cons, _ := js.CreateConsumer(ctx, "EVENTS",
    jetstream.ConsumerConfig{
        Durable:       "my-worker",
        AckPolicy:     jetstream.AckExplicitPolicy,
        DeliverPolicy: jetstream.DeliverAllPolicy,
        FilterSubject: "events.orders.>",
        MaxAckPending: 1000,
    })
```

### Get Consumer Info

**Zig:**

```zig
var info = try js.consumerInfo("EVENTS", "my-worker");
defer info.deinit();
// info.value.num_pending -- messages waiting
// info.value.num_ack_pending -- delivered but unacked
```

**Go:**

```go
cons, _ := js.Consumer(ctx, "EVENTS", "my-worker")
info, _ := cons.Info(ctx)
```

### Update a Consumer

**Zig:**

```zig
var resp = try js.updateConsumer("EVENTS", .{
    .name = "my-worker",
    .durable_name = "my-worker",
    .ack_policy = .explicit,
    .max_ack_pending = 2000,
});
defer resp.deinit();
```

**Go:**

```go
cons, _ := js.UpdateConsumer(ctx, "EVENTS",
    jetstream.ConsumerConfig{
        Durable:       "my-worker",
        AckPolicy:     jetstream.AckExplicitPolicy,
        MaxAckPending: 2000,
    })
```

### Delete a Consumer

**Zig:**

```zig
var resp = try js.deleteConsumer("EVENTS", "my-worker");
defer resp.deinit();
// resp.value.success == true
```

**Go:**

```go
_ = js.DeleteConsumer(ctx, "EVENTS", "my-worker")
```

### ConsumerConfig Reference

| Field | Type | Description |
|-------|------|-------------|
| `name` | `?[]const u8` | Consumer name |
| `durable_name` | `?[]const u8` | Durable name (survives restarts) |
| `ack_policy` | `?AckPolicy` | none, all, explicit |
| `deliver_policy` | `?DeliverPolicy` | all, last, new, ... |
| `ack_wait` | `?i64` | Ack timeout (nanoseconds) |
| `max_deliver` | `?i64` | Max redelivery attempts |
| `filter_subject` | `?[]const u8` | Subject filter |
| `filter_subjects` | `?[]const []const u8` | Multiple filters |
| `replay_policy` | `?ReplayPolicy` | instant, original |
| `max_waiting` | `?i64` | Max pull requests waiting |
| `max_ack_pending` | `?i64` | Max unacked messages |
| `inactive_threshold` | `?i64` | Idle cleanup (nanoseconds) |
| `headers_only` | `?bool` | Deliver headers only |

---

## Publishing

JetStream publish goes directly to the stream subject (not through
`$JS.API`). The server returns a `PubAck` confirming storage.

### Simple Publish

**Zig:**

```zig
var ack = try js.publish("orders.new", payload);
defer ack.deinit();

// Check the ack
if (ack.value.stream) |stream| {
    // stream name that stored the message
}
const seq = ack.value.seq; // sequence number
```

**Go:**

```go
ack, _ := js.Publish(ctx, "orders.new", payload)
// ack.Stream, ack.Sequence
```

### Publish with Options

Use `publishWithOpts` for idempotency and optimistic concurrency.

**Zig:**

```zig
var ack = try js.publishWithOpts(
    "orders.new",
    payload,
    .{
        .msg_id = "order-123",
        .expected_stream = "ORDERS",
        .expected_last_seq = 41,
    },
);
defer ack.deinit();

// Check for duplicate
if (ack.value.duplicate) |dup| {
    if (dup) {
        // Message was already stored (idempotent)
    }
}
```

**Go:**

```go
ack, _ := js.Publish(ctx, "orders.new", payload,
    jetstream.WithMsgID("order-123"),
    jetstream.WithExpectStream("ORDERS"),
    jetstream.WithExpectLastSequence(41),
)
```

### Publish Option Headers

| Zig field | Header sent | Purpose |
|-----------|------------|---------|
| `msg_id` | `Nats-Msg-Id` | Deduplication key |
| `expected_stream` | `Nats-Expected-Stream` | Verify target stream |
| `expected_last_seq` | `Nats-Expected-Last-Sequence` | Optimistic concurrency |
| `expected_last_msg_id` | `Nats-Expected-Last-Msg-Id` | Sequence by msg ID |
| `expected_last_subj_seq` | `Nats-Expected-Last-Subject-Sequence` | Per-subject sequence |

---

## Pull Subscription

Pull consumers fetch messages on demand. Create a
`PullSubscription`, then call `fetch()` to get a batch.

### Setup and Fetch

**Zig:**

```zig
var pull = nats.jetstream.PullSubscription{
    .js = &js,
    .stream = "ORDERS",
};
try pull.setConsumer("processor");

var result = try pull.fetch(.{
    .max_messages = 100,
    .timeout_ms = 5000,
});
defer result.deinit();

for (result.messages) |*msg| {
    const data = msg.data();
    // process data...
    try msg.ack();
}
```

**Go:**

```go
cons, _ := js.Consumer(ctx, "ORDERS", "processor")

batch, _ := cons.Fetch(100)
for msg := range batch.Messages() {
    data := msg.Data()
    // process data...
    msg.Ack()
}
```

### FetchOpts

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `max_messages` | `u32` | 1 | Batch size |
| `timeout_ms` | `u32` | 5000 | Timeout in milliseconds |

### How Fetch Works

1. Subscribes to a temporary inbox
2. Publishes a pull request JSON to
   `$JS.API.CONSUMER.MSG.NEXT.{stream}.{consumer}`
3. Collects messages until batch is full or a status signal
   arrives:
   - **404** -- no messages available (stop)
   - **408** -- request expired (stop)
   - **409** -- leadership change (stop)
   - **100** -- idle heartbeat (skip, continue)
4. Returns `FetchResult` with collected messages

### FetchResult

```zig
const FetchResult = struct {
    messages: []JsMsg,
    allocator: Allocator,

    pub fn count(self: *const FetchResult) usize;
    pub fn deinit(self: *FetchResult) void;
};
```

Call `deinit()` to free all messages and the backing slice.

---

## Message Acknowledgment

JetStream messages must be acknowledged to confirm processing.
All ack methods publish a protocol token to the message's
reply-to subject.

### Ack Methods

| Method | Zig | Go | Payload | Repeatable |
|--------|-----|-----|---------|------------|
| Acknowledge | `msg.ack()` | `msg.Ack()` | `+ACK` | No |
| Negative ack | `msg.nak()` | `msg.Nak()` | `-NAK` | No |
| NAK with delay | `msg.nakWithDelay(ns)` | `msg.NakWithDelay(d)` | `-NAK {"delay":N}` | No |
| In progress | `msg.inProgress()` | `msg.InProgress()` | `+WPI` | Yes |
| Terminate | `msg.term()` | `msg.Term()` | `+TERM` | No |
| Terminate + reason | `msg.termWithReason(r)` | `msg.TermWithReason(r)` | `+TERM reason` | No |

### Examples

**Zig:**

```zig
for (result.messages) |*msg| {
    const data = msg.data();

    if (isValid(data)) {
        try msg.ack();
    } else if (isRetryable(data)) {
        // Retry after 5 seconds
        try msg.nakWithDelay(5_000_000_000);
    } else {
        try msg.termWithReason("invalid payload");
    }
}
```

**Go:**

```go
for msg := range batch.Messages() {
    data := msg.Data()

    if isValid(data) {
        msg.Ack()
    } else if isRetryable(data) {
        msg.NakWithDelay(5 * time.Second)
    } else {
        msg.TermWithReason("invalid payload")
    }
}
```

### Extending the Ack Deadline

For long-running processing, send periodic `inProgress()` signals
to prevent redelivery:

**Zig:**

```zig
try msg.inProgress(); // Reset ack timer
// ... do work ...
try msg.inProgress(); // Reset again
// ... finish work ...
try msg.ack();
```

### JsMsg Accessors

| Method | Returns | Description |
|--------|---------|-------------|
| `data()` | `[]const u8` | Message payload |
| `subject()` | `[]const u8` | Original subject |
| `headers()` | `?[]const u8` | Raw headers |
| `replyTo()` | `?[]const u8` | Ack reply subject |
| `deinit()` | `void` | Free message memory |

---

## Error Handling

nats.zig uses a two-layer error system for JetStream:

1. **Zig error unions** -- transport/protocol failures
2. **ApiError struct** -- server-side JetStream errors

### Layer 1: Zig Errors

```zig
pub const Error = error{
    Timeout,
    NoResponders,
    ApiError,
    JsonParseError,
    SubjectTooLong,
    NoHeartbeat,
    ConsumerDeleted,
    OrderedReset,
    InvalidKey,
    InvalidData,
    KeyNotFound,
    WrongLastRevision,
    ThreadSpawnFailed,
};
```

### Layer 2: API Errors

When `error.ApiError` is returned, call `js.lastApiError()` to
get the server-side error details:

**Zig:**

```zig
var info = js.streamInfo("NONEXISTENT");
if (info) |*r| {
    defer r.deinit();
    // use r.value...
} else |err| {
    if (err == error.ApiError) {
        if (js.lastApiError()) |api_err| {
            // api_err.code       -- HTTP-like status (404)
            // api_err.err_code   -- JetStream error code
            // api_err.description() -- error message
        }
    }
}
```

**Go:**

```go
_, err := js.Stream(ctx, "NONEXISTENT")
if err != nil {
    var jsErr jetstream.JetStreamError
    if errors.As(err, &jsErr) {
        apiErr := jsErr.APIError()
        // apiErr.Code, apiErr.ErrorCode, apiErr.Description
    }
}
```

### Common Error Codes

| Constant | Code | Meaning |
|----------|------|---------|
| `ErrCode.stream_not_found` | 10059 | Stream does not exist |
| `ErrCode.stream_name_in_use` | 10058 | Stream name taken |
| `ErrCode.consumer_not_found` | 10014 | Consumer does not exist |
| `ErrCode.consumer_already_exists` | 10105 | Consumer name taken |
| `ErrCode.js_not_enabled` | 10076 | JetStream not enabled |
| `ErrCode.bad_request` | 10003 | Invalid request |
| `ErrCode.stream_wrong_last_seq` | 10071 | Sequence mismatch |
| `ErrCode.message_not_found` | 10037 | Message not in stream |

Full list in `src/jetstream/errors.zig`.

### Checking Specific Errors

**Zig:**

```zig
const ErrCode = nats.jetstream.errors.ErrCode;

if (js.lastApiError()) |api_err| {
    if (api_err.err_code == ErrCode.stream_not_found) {
        // Handle missing stream
    }
}
```

**Go:**

```go
if errors.Is(err, jetstream.ErrStreamNotFound) {
    // Handle missing stream
}
```

---

## Response Ownership

Every JetStream operation that returns a `Response(T)` owns
parsed JSON memory. All string slices in `resp.value` point
into the parsed arena.

**You must call `deinit()` when done:**

```zig
var resp = try js.createStream(.{ .name = "TEST" });
defer resp.deinit(); // Frees parsed JSON arena

// Access data through resp.value
if (resp.value.config) |cfg| {
    // cfg.name is valid until resp.deinit()
}
```

If you need to keep data beyond `deinit()`, copy it first:

```zig
var resp = try js.streamInfo("TEST");
const msg_count = resp.value.state.?.messages;
resp.deinit(); // Safe -- msg_count is a u64 (copied)
```

String data requires explicit copying:

```zig
var resp = try js.streamInfo("TEST");
const name = try allocator.dupe(
    u8,
    resp.value.config.?.name,
);
resp.deinit(); // Safe -- name is independently owned
defer allocator.free(name);
```

---

## Type Reference

### Enums

| Zig Enum | Values | Go Equivalent |
|----------|--------|---------------|
| `RetentionPolicy` | limits, interest, workqueue | `LimitsPolicy`, `InterestPolicy`, `WorkQueuePolicy` |
| `StorageType` | file, memory | `FileStorage`, `MemoryStorage` |
| `DiscardPolicy` | old, new | `DiscardOld`, `DiscardNew` |
| `StoreCompression` | none, s2 | `NoCompression`, `S2Compression` |
| `DeliverPolicy` | all, last, new, by_start_sequence, by_start_time, last_per_subject | `DeliverAllPolicy`, `DeliverLastPolicy`, ... |
| `AckPolicy` | none, all, explicit | `AckNonePolicy`, `AckAllPolicy`, `AckExplicitPolicy` |
| `ReplayPolicy` | instant, original | `ReplayInstantPolicy`, `ReplayOriginalPolicy` |

### Key Differences from Go

| Aspect | Zig (nats.zig) | Go (nats.go) |
|--------|----------------|--------------|
| Context | Stack struct, `try JetStream.init()` | Interface, `jetstream.New()` |
| Timeout | `timeout_ms: u32` on JetStream | `context.Context` per call |
| Responses | `Response(T)` with `defer deinit()` | Go GC handles memory |
| Errors | `error.ApiError` + `lastApiError()` | `JetStreamError` interface |
| Pull | `PullSubscription.fetch()` | `consumer.Fetch()` |
| Options | Struct fields with `?T = null` | Functional options pattern |
| Enums | Lowercase tags (`.file`) | PascalCase constants (`FileStorage`) |
| Durations | Nanoseconds (`i64`) | `time.Duration` |

### Duration Conversion

JetStream JSON uses nanoseconds for all duration fields:

```zig
// 30 seconds
const thirty_sec: i64 = 30 * std.time.ns_per_s;

// 5 minutes
const five_min: i64 = 5 * 60 * std.time.ns_per_s;

// Use in config
var stream = try js.createStream(.{
    .name = "TEST",
    .max_age = five_min,
    .duplicate_window = thirty_sec,
});
```


================================================
FILE: doc/nats-by-example/README.md
================================================
# NATS by Example

Ports of [natsbyexample.com](https://natsbyexample.com) examples.

| Example | Run | Server? |
|---------|-----|---------|
| [Pub-Sub](messaging/Pub-Sub.md) | `run-nbe-messaging-pub-sub` | Yes |
| [Request-Reply](messaging/Request-Reply.md) | `run-nbe-messaging-request-reply` | Yes |
| [JSON](messaging/Json.md) | `run-nbe-messaging-json` | Yes |
| [Concurrent](messaging/Concurrent.md) | `run-nbe-messaging-concurrent` | Yes |
| [Multiple Subscriptions](messaging/Iterating-Multiple-Subscriptions.md) | `run-nbe-messaging-iterating-multiple-subscriptions` | Yes |
| [NKeys & JWTs](auth/NKeys-JWTs.md) | `run-nbe-auth-nkeys-jwts` | No |



================================================
FILE: doc/nats-by-example/auth/NKeys-JWTs.md
================================================
# NKeys and JWTs

NATS supports decentralized authentication using a three-level trust hierarchy:

- **Operator** - Top-level entity that manages accounts
- **Account** - Groups users and defines resource limits
- **User** - Authenticates to NATS with permissions

Each entity has an NKey keypair (Ed25519). Operators sign account JWTs,
and accounts sign user JWTs. This creates a chain of trust without
requiring a central authority.

## How It Works

1. Generate an **operator** keypair (prefix `SO`)
2. Generate an **account** keypair (prefix `SA`)
3. The operator signs an **account JWT** containing the account's public key
4. Generate a **user** keypair (prefix `SU`)
5. The account signs a **user JWT** with publish/subscribe permissions
6. Format a **credentials file** (`.creds`) containing the user JWT and seed

The credentials file is what a NATS client uses to authenticate. The server
validates the JWT signature chain back to a trusted operator.

## Running

No NATS server required - this is a pure cryptography example.

```sh
zig build run-nbe-auth-nkeys-jwts
```

## Output

```
== Operator ==
operator public key: <dynamic>
operator seed:       <dynamic>

== Account ==
account public key: <dynamic>
account seed:       <dynamic>

account JWT:
<dynamic>

== User ==
user public key: <dynamic>
user seed:       <dynamic>

user JWT:
<dynamic>

== Credentials File ==
-----BEGIN NATS USER JWT-----
<dynamic>
------END NATS USER JWT------

************************* IMPORTANT *************************
  NKEY Seed printed below can be used to sign and prove identity.
  NKEYs are sensitive and should be treated as secrets.

  ************************************************************

-----BEGIN USER NKEY SEED-----
<dynamic>
------END USER NKEY SEED------
```

## What's Happening

1. Three NKey keypairs are generated: operator, account, and user. Each uses
   Ed25519 with type-specific base32 prefixes (`SO`, `SA`, `SU`).
2. An account JWT is created with default limits (unlimited) and signed by the
   operator's private key. The JWT contains the account's public key as subject
   and the operator's public key as issuer.
3. A user JWT is created with publish permissions on `app.>` and subscribe
   permissions on `app.>` and `_INBOX.>`, signed by the account's private key.
4. A credentials file is formatted containing the user JWT and seed. This file
   can be passed to a NATS client via `--creds` for authentication.

## Source

See [nkeys-jwts.zig](nkeys-jwts.zig) for the full example.

Based on [natsbyexample.com/examples/auth/nkeys-jwts](https://natsbyexample.com/examples/auth/nkeys-jwts/go).


================================================
FILE: doc/nats-by-example/auth/nkeys-jwts.zig
================================================
//! NKeys and JWTs
//!
//! This example demonstrates NATS decentralized authentication using
//! the operator/account/user keypair hierarchy. It generates NKey
//! keypairs, encodes JWTs, and formats a credentials file - all
//! using pure Zig cryptography with zero external dependencies.
//!
//! No NATS server is needed - this is a pure cryptography example.
//!
//! Key concepts shown:
//! - NKey generation for operator, account, and user entities
//! - JWT encoding with Ed25519 signatures
//! - Credentials file formatting for client authentication
//! - The three-level trust hierarchy: operator > account > user
//!
//! Based on:
//!   https://natsbyexample.com/examples/auth/nkeys-jwts/go
//!
//! Run with: zig build run-nbe-auth-nkeys-jwts

const std = @import("std");
const nats = @import("nats");

pub fn main(init: std.process.Init) !void {
    const io = init.io;

    // Set up buffered stdout writer
    var stdout_buf: [8192]u8 = undefined;
    var stdout_writer = std.Io.File.stdout().writer(
        io,
        &stdout_buf,
    );
    const stdout = &stdout_writer.interface;

    // The operator is the top-level entity that manages
    // accounts. It signs account JWTs.
    try stdout.print(
        "== Operator ==\n",
        .{},
    );

    var op_kp = nats.auth.KeyPair.generate(io, .operator);
    defer op_kp.wipe();

    var op_pk_buf: [56]u8 = undefined;
    const op_pub = op_kp.publicKey(&op_pk_buf);
    try stdout.print(
        "operator public key: {s}\n",
        .{op_pub},
    );

    var op_seed_buf: [58]u8 = undefined;
    const op_seed = op_kp.encodeSeed(&op_seed_buf);
    try stdout.print(
        "operator seed:       {s}\n\n",
        .{op_seed},
    );

    // An account groups users and defines resource limits.
    // The operator signs account JWTs.
    try stdout.print(
        "== Account ==\n",
        .{},
    );

    var acct_kp = nats.auth.KeyPair.generate(io, .account);
    defer acct_kp.wipe();

    var acct_pk_buf: [56]u8 = undefined;
    const acct_pub = acct_kp.publicKey(&acct_pk_buf);
    try stdout.print(
        "account public key: {s}\n",
        .{acct_pub},
    );

    var acct_seed_buf: [58]u8 = undefined;
    const acct_seed = acct_kp.encodeSeed(&acct_seed_buf);
    try stdout.print(
        "account seed:       {s}\n\n",
        .{acct_seed},
    );

    // Encode account JWT (signed by operator)
    const ts = std.Io.Timestamp.now(io, .real);
    const iat: i64 = @intCast(
        @as(u64, @intCast(ts.nanoseconds)) /
            std.time.ns_per_s,
    );

    var acct_jwt_buf: [2048]u8 = undefined;
    const acct_jwt = try nats.auth.jwt.encodeAccountClaims(
        &acct_jwt_buf,
        acct_pub,
        "my-account",
        op_kp,
        iat,
        .{},
    );
    try stdout.print(
        "account JWT:\n{s}\n\n",
        .{acct_jwt},
    );

    // A user belongs to an account. The account signs
    // user JWTs with publish/subscribe permissions.
    try stdout.print(
        "== User ==\n",
        .{},
    );

    var user_kp = nats.auth.KeyPair.generate(io, .user);
    defer user_kp.wipe();

    var user_pk_buf: [56]u8 = undefined;
    const user_pub = user_kp.publicKey(&user_pk_buf);
    try stdout.print(
        "user public key: {s}\n",
        .{user_pub},
    );

    var user_seed_buf: [58]u8 = undefined;
    const user_seed = user_kp.encodeSeed(&user_seed_buf);
    try stdout.print(
        "user seed:       {s}\n\n",
        .{user_seed},
    );

    // Encode user JWT with permissions (signed by account)
    var user_jwt_buf: [2048]u8 = undefined;
    const user_jwt = try nats.auth.jwt.encodeUserClaims(
        &user_jwt_buf,
        user_pub,
        "my-user",
        acct_kp,
        iat,
        .{
            .pub_allow = &.{"app.>"},
            .sub_allow = &.{ "app.>", "_INBOX.>" },
        },
    );
    try stdout.print(
        "user JWT:\n{s}\n\n",
        .{user_jwt},
    );

    // Format a .creds file containing the user JWT and seed.
    // This file is what a NATS client uses to authenticate.
    try stdout.print(
        "== Credentials File ==\n",
        .{},
    );

    var creds_buf: [4096]u8 = undefined;
    const creds = nats.auth.creds.format(
        &creds_buf,
        user_jwt,
        user_seed,
    ) catch return;
    try stdout.print("{s}\n", .{creds});

    try stdout.flush();
}


================================================
FILE: doc/nats-by-example/messaging/Concurrent.md
================================================
# Concurrent Message Processing

By default, messages from a subscription are processed sequentially -
each message must finish before the next one starts. For workloads
where message processing takes variable time (API calls, database
queries, computation), concurrent processing can significantly improve
throughput.

This example uses `io.concurrent()` to spawn worker threads that
process messages in parallel. Each worker simulates variable
processing time with a random delay, causing messages to complete
out of their original order.

## Running

Prerequisites: `nats-server` running on `localhost:4222`.

```sh
nats-server &
zig build run-nbe-messaging-concurrent
```

## Output (order varies per run)

```
received message: "hello 3"
received message: "hello 0"
received message: "hello 7"
received message: "hello 1"
received message: "hello 5"
received message: "hello 8"
received message: "hello 4"
received message: "hello 9"
received message: "hello 6"
received message: "hello 2"

processed 10 messages concurrently
```

**Note**: The message order is non-deterministic. Each run produces
a different sequence because workers process with random delays.

## What's Happening

1. 10 messages are published to `greet.joe`.
2. All 10 are received on the main thread and copied into work items.
3. Three concurrent workers are spawned via `io.concurrent()`.
4. Each worker processes its assigned messages with a random delay
   (0-100ms) to simulate variable work.
5. Workers write directly to stdout using `writeStreamingAll` (atomic
   per-line writes avoid interleaved output).
6. The main thread waits for all workers to complete.

## Source

See [concurrent.zig](concurrent.zig) for the full example.

Based on [natsbyexample.com/examples/messaging/concurrent](https://natsbyexample.com/examples/messaging/concurrent/rust).


================================================
FILE: doc/nats-by-example/messaging/Iterating-Multiple-Subscriptions.md
================================================
# Iterating Over Multiple Subscriptions

NATS wildcards cover many routing cases, but sometimes you need
separate subscriptions. For example, you want `transport.cars`,
`transport.planes`, and `transport.ships` but not
`transport.spaceships`.

This example shows how to poll multiple subscriptions in a unified
loop using `tryNext()` - Zig's equivalent of merging async streams
into one iteration. Messages from all subscriptions are processed
in round-robin fashion without blocking.

## Running

Prerequisites: `nats-server` running on `localhost:4222`.

```sh
nats-server &
zig build run-nbe-messaging-iterating-multiple-subscriptions
```

## Output

```
received on cars.0: car number 0
received on planes.0: plane number 0
received on ships.0: ship number 0
received on cars.1: car number 1
received on planes.1: plane number 1
received on ships.1: ship number 1
...
received on cars.9: car number 9
received on planes.9: plane number 9
received on ships.9: ship number 9

received 30 messages from 3 subscriptions
```

## What's Happening

1. Three separate subscriptions are created: `cars.>`, `planes.>`,
   and `ships.>`.
2. 10 messages are published to each category (30 total).
3. All three subscriptions are polled in round-robin using
   `tryNext()` which returns instantly if no message is available.
4. Each message's subject and payload are printed as received.
5. A short sleep avoids busy-spinning when no messages are ready.

## Source

See [iterating-multiple-subscriptions.zig](iterating-multiple-subscriptions.zig)
for the full example.

Based on [natsbyexample.com/examples/messaging/iterating-multiple-subscriptions](https://natsbyexample.com/examples/messaging/iterating-multiple-subscriptions/rust).


================================================
FILE: doc/nats-by-example/messaging/Json.md
================================================
# JSON for Message Payloads

NATS message payloads are opaque byte sequences. It is up to the
application to define serialization. JSON is a natural choice for
cross-language compatibility.

Zig's `std.json` provides compile-time type-safe serialization and
deserialization:

- **Serialize**: `std.json.Stringify.value(struct, options, writer)`
  writes JSON to any `Io.Writer` (including fixed-buffer writers).
- **Deserialize**: `std.json.parseFromSlice(T, allocator, data, options)`
  parses JSON bytes into a typed struct, returning an error for
  invalid input.

## Running

Prerequisites: `nats-server` running on `localhost:4222`.

```sh
nats-server &
zig build run-nbe-messaging-json
```

## Output

```
received valid payload: foo=bar, bar=27
received invalid payload: not json
```

## What's Happening

1. A `Payload` struct is defined with `foo` (string) and `bar` (int)
   fields.
2. An instance is serialized to JSON using `Stringify.value` into a
   stack-allocated buffer - no heap allocation needed.
3. The JSON bytes are published to the `greet` subject.
4. A second message with invalid content (`"not json"`) is published.
5. The receiver tries `parseFromSlice` on each message. The first
   succeeds and prints the deserialized fields. The second fails
   gracefully and prints the raw payload.

## Source

See [json.zig](json.zig) for the full example.

Based on [natsbyexample.com/examples/messaging/json](https://natsbyexample.com/examples/messaging/json/go).


================================================
FILE: doc/nats-by-example/messaging/Pub-Sub.md
================================================
# Publish-Subscribe

NATS implements publish-subscribe message distribution through subject-based
routing. Publishers send messages to named subjects. Subscribers express
interest in subjects (including wildcards) and receive matching messages.

The core guarantee is **at-most-once delivery**: if there is no subscriber
listening when a message is published, the message is silently discarded.
This is similar to UDP or MQTT QoS 0. For stronger delivery guarantees, see
JetStream.

## Wildcard Subscriptions

NATS supports two wildcard tokens in subscriptions:

- `*` matches a single token: `greet.*` matches `greet.joe`, `greet.pam`
- `>` matches one or more tokens: `greet.>` matches `greet.joe`,
  `greet.joe.hello`

## Running

Prerequisites: `nats-server` running on `localhost:4222`.

```sh
nats-server &
zig build run-nbe-messaging-pub-sub
```

## Output

```
subscribed after a publish...
msg is null? true
msg data: "hello" on subject "greet.joe"
msg data: "hello" on subject "greet.pam"
msg data: "hello" on subject "greet.bob"
```

## What's Happening

1. A message is published to `greet.joe` **before** any subscription exists.
   This message is lost - at-most-once delivery means no buffering.
2. A wildcard subscription on `greet.*` is created.
3. Attempting to receive returns `null` - the earlier message is gone.
4. Two messages are published to `greet.joe` and `greet.pam`. Both are
   received because the subscription is now active and the wildcard matches.
5. A third message to `greet.bob` is also received via the same wildcard.

## Source

See [pub-sub.zig](pub-sub.zig) for the full example.

Based on [natsbyexample.com/examples/messaging/pub-sub](https://natsbyexample.com/examples/messaging/pub-sub/go).


================================================
FILE: doc/nats-by-example/messaging/README.md
================================================
# Messaging

Examples based on the [natsbyexample.com](https://natsbyexample.com/)
**Messaging** category, implemented in Zig using the nats.zig client.

## Building and Running

Prerequisites: a `nats-server` running on `localhost:4222`.

```sh
nats-server &
```

Each example is a standalone executable. Build and run with:

```sh
zig build run-nbe-messaging-<example-name>
```

For example:

```sh
zig build run-nbe-messaging-pub-sub
zig build run-nbe-messaging-request-reply
```

## Examples

| Example | Description | Source |
|---------|-------------|--------|
| [Publish-Subscribe](Pub-Sub.md) | Subject-based pub/sub with wildcard routing and at-most-once delivery | [pub-sub.zig](pub-sub.zig) |
| [Request-Reply](Request-Reply.md) | RPC-style communication using temporary inbox subjects | [request-reply.zig](request-reply.zig) |
| [JSON for Message Payloads](Json.md) | Type-safe JSON serialization/deserialization with `std.json` | [json.zig](json.zig) |
| [Concurrent Message Processing](Concurrent.md) | Parallel message processing with `io.concurrent()` worker threads | [concurrent.zig](concurrent.zig) |
| [Iterating Over Multiple Subscriptions](Iterating-Multiple-Subscriptions.md) | Polling multiple subscriptions in a unified round-robin loop | [iterating-multiple-subscriptions.zig](iterating-multiple-subscriptions.zig) |


================================================
FILE: doc/nats-by-example/messaging/Request-Reply.md
================================================
# Request-Reply

The request-reply pattern enables RPC-style communication over NATS.
Under the hood, NATS implements this as an optimized pair of
publish-subscribe operations: the requester creates a temporary inbox
subject, subscribes to it, and publishes the request with a `reply_to`
header pointing at that inbox.

Unlike strict point-to-point protocols, multiple subscribers can
potentially respond to a request. The client receives the first reply
and discards the rest.

When no handler is subscribed, the server sends a "no responders"
notification (status 503) instead of silently timing out.

## Running

Prerequisites: `nats-server` running on `localhost:4222`.

```sh
nats-server &
zig build run-nbe-messaging-request-reply
```

## Output

```
hello, joe
hello, sue
hello, bob
no responders
```

## What's Happening

1. A subscription on `greet.*` handles incoming requests in a
   background async task.
2. The handler extracts the name from the subject (`greet.joe` ->
   `joe`) and responds with `"hello, joe"`.
3. Three requests are made - `greet.joe`, `greet.sue`, `greet.bob` -
   each receiving a personalized greeting.
4. The handler subscription is unsubscribed.
5. A fourth request to `greet.joe` returns "no responders" because
   no handler is listening anymore.

## Source

See [request-reply.zig](request-reply.zig) for the full example.

Based on [natsbyexample.com/examples/messaging/request-reply](https://natsbyexample.com/examples/messaging/request-reply/go).


================================================
FILE: doc/nats-by-example/messaging/concurrent.zig
================================================
//! Concurrent Message Processing
//!
//! By default, messages from a subscription are processed
//! sequentially. This example shows how to process messages
//! concurrently using multiple worker threads.
//!
//! The pattern: receive messages on the main thread, dispatch
//! them to concurrent workers via an Io.Queue, and collect
//! results. Each worker simulates variable processing time
//! with a random delay, causing messages to complete out of
//! their original order.
//!
//! Based on: https://natsbyexample.com/examples/messaging/concurrent/rust
//!
//! Prerequisites: nats-server running on localhost:4222
//!   nats-server
//!
//! Run with: zig build run-nbe-messaging-concurrent

const std = @import("std");
const nats = @import("nats");

const Io = std.Io;

const NUM_MSGS = 10;
const NUM_WORKERS = 3;

/// Work item passed from main thread to workers.
/// Contains a copy of the message data (the original
/// Message is freed after copying).
const WorkItem = struct {
    data: [64]u8 = undefined,
    len: usize = 0,
};

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;

    const io = init.io;

    var stdout_buf: [4096]u8 = undefined;
    var stdout_writer = Io.File.stdout().writer(
        io,
        &stdout_buf,
    );
    const stdout = &stdout_writer.interface;

    const client = try nats.Client.connect(
        allocator,
        io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    const sub = try client.subscribeSync("greet.*");
    defer sub.deinit();

    // Publish 10 messages
    for (0..NUM_MSGS) |i| {
        var buf: [32]u8 = undefined;
        const payload = std.fmt.bufPrint(
            &buf,
            "hello {d}",
            .{i},
        ) catch continue;
        try client.publish("greet.joe", payload);
    }

    // Wait for messages to arrive
    io.sleep(.fromMilliseconds(50), .awake) catch {};

    // Receive all messages and copy data into work items.
    // We copy because the Message backing buffer is freed
    // on deinit, but workers need the data later.
    var items: [NUM_MSGS]WorkItem = @splat(WorkItem{});
    var received: usize = 0;
    for (0..NUM_MSGS) |_| {
        if (try sub.nextMsgTimeout(
            1000,
        )) |msg| {
            defer msg.deinit();
            const len = @min(msg.data.len, 64);
            @memcpy(
                items[received].data[0..len],
                msg.data[0..len],
            );
            items[received].len = len;
            received += 1;
        }
    }

    // Dispatch work to 3 concurrent workers. Each worker
    // gets a slice of the items array to process.
    // io.concurrent() ensures true parallel execution.
    const slice1_end = received / 3;
    const slice2_end = (received * 2) / 3;

    var w1 = try io.concurrent(processWorker, .{
        io,
        items[0..slice1_end],
    });
    defer w1.cancel(io);

    var w2 = try io.concurrent(processWorker, .{
        io,
        items[slice1_end..slice2_end],
    });
    defer w2.cancel(io);

    // Third worker runs on this thread (no extra thread needed)
    processWorker(io, items[slice2_end..received]);

    // Wait for concurrent workers to finish
    w1.await(io);
    w2.await(io);

    try stdout.print(
        "\nprocessed {d} messages concurrently\n",
        .{received},
    );
    try stdout.flush();
}

/// Worker function that processes a slice of work items.
/// Each item is "processed" with a random delay to simulate
/// variable work, then printed. The random delays cause
/// messages to complete out of their original order.
fn processWorker(io: Io, items: []WorkItem) void {
    const file_stdout = Io.File.stdout();
    for (items) |item| {
        // Random delay 0-100ms to simulate processing
        var rnd: [1]u8 = undefined;
        io.random(&rnd);
        const delay_ms: i64 = @intCast(rnd[0] % 100);
        io.sleep(
            .fromMilliseconds(delay_ms),
            .awake,
        ) catch {};

        // Write directly to stdout (single write syscall
        // per line avoids interleaved output)
        var buf: [80]u8 = undefined;
        const line = std.fmt.bufPrint(
            &buf,
            "received message: \"{s}\"\n",
            .{item.data[0..item.len]},
        ) catch continue;
        file_stdout.writeStreamingAll(io, line) catch {};
    }
}


================================================
FILE: doc/nats-by-example/messaging/iterating-multiple-subscriptions.zig
================================================
//! Iterating Over Multiple Subscriptions
//!
//! NATS wildcards cover many routing cases, but sometimes you
//! need separate subscriptions - for example, you want
//! "transport.cars", "transport.planes", and "transport.ships"
//! but NOT "transport.spaceships".
//!
//! This example shows how to poll multiple subscriptions in
//! a unified loop using tryNext() - the Zig equivalent of
//! merging multiple async streams.
//!
//! Based on: https://natsbyexample.com/examples/messaging/iterating-multiple-subscriptions/rust
//!
//! Prerequisites: nats-server running on localhost:4222
//!   nats-server
//!
//! Run with: zig build run-nbe-messaging-iterating-multiple-subscriptions

const std = @import("std");
const nats = @import("nats");

const Io = std.Io;
const NUM_MSGS_PER_CATEGORY = 10;
const TOTAL_MSGS = NUM_MSGS_PER_CATEGORY * 3;

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;

    const io = init.io;

    var stdout_buf: [8192]u8 = undefined;
    var stdout_writer = Io.File.stdout().writer(
        io,
        &stdout_buf,
    );
    const stdout = &stdout_writer.interface;

    const client = try nats.Client.connect(
        allocator,
        io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    // Create three separate subscriptions. We use ">"
    // (multi-level wildcard) to match all sub-subjects.
    const sub_cars = try client.subscribeSync("cars.>");
    defer sub_cars.deinit();

    const sub_planes = try client.subscribeSync("planes.>");
    defer sub_planes.deinit();

    const sub_ships = try client.subscribeSync("ships.>");
    defer sub_ships.deinit();

    // Publish 10 messages to each category
    for (0..NUM_MSGS_PER_CATEGORY) |i| {
        var buf: [64]u8 = undefined;

        const cars_subj = std.fmt.bufPrint(
            &buf,
            "cars.{d}",
            .{i},
        ) catch continue;
        var payload_buf: [64]u8 = undefined;
        const cars_payload = std.fmt.bufPrint(
            &payload_buf,
            "car number {d}",
            .{i},
        ) catch continue;
        try client.publish(cars_subj, cars_payload);

        const planes_subj = std.fmt.bufPrint(
            &buf,
            "planes.{d}",
            .{i},
        ) catch continue;
        const planes_payload = std.fmt.bufPrint(
            &payload_buf,
            "plane number {d}",
            .{i},
        ) catch continue;
        try client.publish(planes_subj, planes_payload);

        const ships_subj = std.fmt.bufPrint(
            &buf,
            "ships.{d}",
            .{i},
        ) catch continue;
        const ships_payload = std.fmt.bufPrint(
            &payload_buf,
            "ship number {d}",
            .{i},
        ) catch continue;
        try client.publish(ships_subj, ships_payload);
    }

    // Wait for messages to arrive
    io.sleep(.fromMilliseconds(100), .awake) catch {};

    // Poll all 3 subscriptions in round-robin fashion.
    // tryNext() is non-blocking - returns null instantly if
    // no message is available, letting us cycle to the next
    // subscription without waiting.
    const subs = [_]*nats.Client.Sub{
        sub_cars,
        sub_planes,
        sub_ships,
    };
    var total: u32 = 0;
    var idx: usize = 0;
    var empty_cycles: u32 = 0;

    while (total < TOTAL_MSGS) {
        if (subs[idx].tryNextMsg()) |msg| {
            defer msg.deinit();
            total += 1;
            empty_cycles = 0;
            try stdout.print(
                "received on {s}: {s}\n",
                .{ msg.subject, msg.data },
            );
        }
        idx = (idx + 1) % subs.len;

        // Avoid busy-spinning when no messages are ready
        if (idx == 0) {
            empty_cycles += 1;
            if (empty_cycles > 10) {
                io.sleep(
                    .fromMilliseconds(10),
                    .awake,
                ) catch {};
            }
        }

        // Safety: don't spin forever if messages are lost
        if (empty_cycles > 100) break;
    }

    try stdout.print(
        "\nreceived {d} messages from 3 subscriptions\n",
        .{total},
    );
    try stdout.flush();
}


================================================
FILE: doc/nats-by-example/messaging/json.zig
================================================
//! JSON for Message Payloads
//!
//! NATS message payloads are opaque byte sequences - the application
//! decides how to serialize and deserialize them. JSON is a common
//! choice for its cross-language compatibility and readability.
//!
//! This example demonstrates:
//! - Defining a struct type for the message payload
//! - Serializing a struct to JSON using Stringify.value
//! - Receiving and deserializing JSON back to a struct
//! - Gracefully handling invalid JSON payloads
//!
//! Based on: https://natsbyexample.com/examples/messaging/json/go
//!
//! Prerequisites: nats-server running on localhost:4222
//!   nats-server
//!
//! Run with: zig build run-nbe-messaging-json

const std = @import("std");
const nats = @import("nats");

const Io = std.Io;

/// Application payload type. Zig's std.json will serialize
/// field names directly ("foo", "bar").
const Payload = struct {
    foo: []const u8,
    bar: i32,
};

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;

    const io = init.io;

    var stdout_buf: [4096]u8 = undefined;
    var stdout_writer = Io.File.stdout().writer(
        io,
        &stdout_buf,
    );
    const stdout = &stdout_writer.interface;

    const client = try nats.Client.connect(
        allocator,
        io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    const sub = try client.subscribeSync("greet");
    defer sub.deinit();

    // Create a payload and serialize it to JSON.
    // Stringify.value writes JSON into a fixed buffer writer -
    // no heap allocation needed.
    const payload = Payload{ .foo = "bar", .bar = 27 };
    var json_buf: [256]u8 = undefined;
    var json_writer = Io.Writer.fixed(&json_buf);
    try std.json.Stringify.value(
        payload,
        .{},
        &json_writer,
    );
    const json = json_writer.buffered();

    // Publish the valid JSON payload
    try client.publish("greet", json);

    // Publish an invalid (non-JSON) payload
    try client.publish("greet", "not json");

    // Receive the first message - valid JSON.
    // parseFromSlice deserializes it back into a Payload struct.
    if (try sub.nextMsgTimeout(1000)) |msg| {
        defer msg.deinit();
        if (std.json.parseFromSlice(
            Payload,
            allocator,
            msg.data,
            .{},
        )) |parsed| {
            defer parsed.deinit();
            try stdout.print(
                "received valid payload: " ++
                    "foo={s}, bar={d}\n",
                .{ parsed.value.foo, parsed.value.bar },
            );
        } else |_| {
            try stdout.print(
                "received invalid payload: {s}\n",
                .{msg.data},
            );
        }
    }

    // Receive the second message - invalid JSON.
    // parseFromSlice returns an error, so we print raw data.
    if (try sub.nextMsgTimeout(1000)) |msg| {
        defer msg.deinit();
        if (std.json.parseFromSlice(
            Payload,
            allocator,
            msg.data,
            .{},
        )) |parsed| {
            defer parsed.deinit();
            try stdout.print(
                "received valid payload: " ++
                    "foo={s}, bar={d}\n",
                .{ parsed.value.foo, parsed.value.bar },
            );
        } else |_| {
            try stdout.print(
                "received invalid payload: {s}\n",
                .{msg.data},
            );
        }
    }

    try stdout.flush();
}


================================================
FILE: doc/nats-by-example/messaging/pub-sub.zig
================================================
//! Publish-Subscribe
//!
//! This example demonstrates the core NATS publish-subscribe pattern.
//! Pub/Sub is the fundamental messaging pattern in NATS where publishers
//! send messages to subjects and subscribers receive them.
//!
//! Key concepts shown:
//! - At-most-once delivery: if no subscriber is listening, messages
//!   are silently discarded (like UDP, or MQTT QoS 0)
//! - Wildcard subscriptions: "greet.*" matches "greet.joe",
//!   "greet.pam", etc.
//! - Subject-based routing: messages are routed by their subject
//!
//! Based on: https://natsbyexample.com/examples/messaging/pub-sub/go
//!
//! Prerequisites: nats-server running on localhost:4222
//!   nats-server
//!
//! Run with: zig build run-nbe-messaging-pub-sub

const std = @import("std");
const nats = @import("nats");

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;

    const io = init.io;

    // Set up buffered stdout writer for output
    var stdout_buf: [4096]u8 = undefined;
    var stdout_writer = std.Io.File.stdout().writer(
        io,
        &stdout_buf,
    );
    const stdout = &stdout_writer.interface;

    // Connect to NATS server
    const client = try nats.Client.connect(
        allocator,
        io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    // Publish a message BEFORE subscribing.
    // This message will be lost because NATS provides
    // at-most-once delivery - there are no subscribers
    // listening on this subject yet.
    try client.publish("greet.joe", "hello");

    // Subscribe using a wildcard subject. "greet.*" will
    // match any subject with exactly one token after "greet.",
    // for example: "greet.joe", "greet.pam", "greet.bob"
    const sub = try client.subscribeSync("greet.*");
    defer sub.deinit();

    // Try to receive the message published before subscribing.
    // The short timeout (10ms) confirms no message is available -
    // it was published before our subscription existed.
    const msg = try sub.nextMsgTimeout(10);
    try stdout.print("subscribed after a publish...\n", .{});
    try stdout.print("msg is null? {}\n", .{msg == null});
    try stdout.flush();

    // Now publish two messages AFTER subscribing.
    // These will be received because the subscription is active.
    try client.publish("greet.joe", "hello");
    try client.publish("greet.pam", "hello");

    // Receive both messages. The wildcard subscription
    // matches both "greet.joe" and "greet.pam".
    if (try sub.nextMsgTimeout(1000)) |m| {
        defer m.deinit();
        try stdout.print(
            "msg data: \"{s}\" on subject \"{s}\"\n",
            .{ m.data, m.subject },
        );
    }
    if (try sub.nextMsgTimeout(1000)) |m| {
        defer m.deinit();
        try stdout.print(
            "msg data: \"{s}\" on subject \"{s}\"\n",
            .{ m.data, m.subject },
        );
    }

    // Publish one more to a different subject that still
    // matches our wildcard pattern.
    try client.publish("greet.bob", "hello");

    if (try sub.nextMsgTimeout(1000)) |m| {
        defer m.deinit();
        try stdout.print(
            "msg data: \"{s}\" on subject \"{s}\"\n",
            .{ m.data, m.subject },
        );
    }

    try stdout.flush();
}


================================================
FILE: doc/nats-by-example/messaging/request-reply.zig
================================================
//! Request-Reply
//!
//! The request-reply pattern allows a client to send a request and
//! wait for a response. Under the hood, NATS implements this as an
//! optimized pair of publish-subscribe operations using an auto-
//! generated inbox subject for the reply.
//!
//! Key concepts shown:
//! - Subscribing to handle requests in a background task
//! - Extracting info from the subject (e.g. a name)
//! - Responding to requests with msg.respond()
//! - Detecting "no responders" when no handler is available
//!
//! Based on: https://natsbyexample.com/examples/messaging/request-reply/go
//!
//! Prerequisites: nats-server running on localhost:4222
//!   nats-server
//!
//! Run with: zig build run-nbe-messaging-request-reply

const std = @import("std");
const nats = @import("nats");

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;

    const io = init.io;

    var stdout_buf: [4096]u8 = undefined;
    var stdout_writer = std.Io.File.stdout().writer(
        io,
        &stdout_buf,
    );
    const stdout = &stdout_writer.interface;

    // Connect to NATS
    const client = try nats.Client.connect(
        allocator,
        io,
        "nats://localhost:4222",
        .{},
    );
    defer client.deinit();

    // Subscribe to "greet.*" to handle incoming requests.
    // The handler extracts the name from the subject and
    // responds with a greeting.
    const sub = try client.subscribeSync("greet.*");
    defer sub.deinit();

    // Run the request handler in a background async task.
    // It will process exactly 3 requests then exit.
    var handler = io.async(handleRequests, .{
        client,
        sub,
    });
    defer handler.cancel(io);

    // Give the subscription time to register on the server
    io.sleep(.fromMilliseconds(50), .awake) catch {};

    // Send 3 requests - each will be handled by our
    // background task and we'll get a personalized greeting.
    if (try client.request(
        "greet.joe",
        "",
        1000,
    )) |reply| {
        defer reply.deinit();
        if (reply.isNoResponders()) {
            try stdout.print("no responders\n", .{});
        } else {
            try stdout.print("{s}\n", .{reply.data});
        }
    }

    if (try client.request(
        "greet.sue",
        "",
        1000,
    )) |reply| {
        defer reply.deinit();
        if (reply.isNoResponders()) {
            try stdout.print("no responders\n", .{});
        } else {
            try stdout.print("{s}\n", .{reply.data});
        }
    }

    if (try client.request(
        "greet.bob",
        "",
        1000,
    )) |reply| {
        defer reply.deinit();
        if (reply.isNoResponders()) {
            try stdout.print("no responders\n", .{});
        } else {
            try stdout.print("{s}\n", .{reply.data});
        }
    }

    // Unsubscribe the handler so no one is listening anymore
    try sub.unsubscribe();

    // This request will fail with "no responders" because
    // we just unsubscribed the only handler.
    if (try client.request(
        "greet.joe",
        "",
        1000,
    )) |reply| {
        defer reply.deinit();
        if (reply.isNoResponders()) {
            try stdout.print("no responders\n", .{});
        } else {
            try stdout.print("{s}\n", .{reply.data});
        }
    }

    try stdout.flush();
}

/// Background handler that processes incoming requests.
/// Extracts the name from the subject ("greet.joe" -> "joe")
/// and responds with "hello, <name>".
fn handleRequests(
    client: *nats.Client,
    sub: *nats.Client.Sub,
) void {
    for (0..3) |_| {
        const req = sub.nextMsgTimeout(
            2000,
        ) catch return;
        if (req) |r| {
            defer r.deinit();
            // "greet.joe" -> "joe"
            const name = r.subject[6..];
            var buf: [64]u8 = undefined;
            const reply = std.fmt.bufPrint(
                &buf,
                "hello, {s}",
                .{name},
            ) catch return;
            r.respond(client, reply) catch {};
        }
    }
}


================================================
FILE: src/Client.zig
================================================
//! NATS Client
//!
//! High-level client API for connecting to NATS servers.
//! Uses std.Io for native async I/O with concurrent subscription support.
//!
//! Key features:
//! - Dedicated reader task routes messages to per-subscription Io.Queue
//! - Multiple subscriptions can call nextMsg() concurrently
//! - Reader task starts automatically on connect
//! - Colorblind async: works blocking or async based on Io implementation
//!
//! Connection-scoped: Allocator, Io, Reader, Writer stored for lifetime.

const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;

const Io = std.Io;
const net = Io.net;
const tls = std.crypto.tls;
const Certificate = std.crypto.Certificate;

const protocol = @import("protocol.zig");
const Parser = protocol.Parser;
const ServerInfo = protocol.ServerInfo;
const connection = @import("connection.zig");
const State = connection.State;
const pubsub = @import("pubsub.zig");
const subscription_mod = @import("pubsub/subscription.zig");
const memory = @import("memory.zig");
const SidMap = memory.SidMap;
const TieredSlab = memory.TieredSlab;
const SpscQueue = @import("sync/spsc_queue.zig").SpscQueue;
const byte_ring = @import("sync/byte_ring.zig");
pub const ByteRing = byte_ring.ByteRing;
const RING_HDR_SIZE = byte_ring.HDR_SIZE;
const SpinLock = @import("sync/spin_lock.zig").SpinLock;
const dbg = @import("dbg.zig");
const defaults = @import("defaults.zig");
const events_mod = @import("events.zig");
pub const Event = events_mod.Event;
pub const EventHandler = events_mod.EventHandler;

const headers = @import("protocol/headers.zig");
pub const HeaderEntry = headers.Entry;
pub const HeaderMap = protocol.HeaderMap;

const nkey_auth = @import("auth.zig");
const creds_auth = nkey_auth.creds;

const Client = @This();

/// Type-erased message handler for callback subscriptions.
/// Uses comptime vtable pattern (same as EventHandler).
///
/// ```zig
/// const MyHandler = struct {
///     counter: *u32,
///     pub fn onMessage(self: *@This(), msg: *const Message) void {
///         self.counter.* += 1;
///     }
/// };
/// var handler = MyHandler{ .counter = &count };
/// const sub = try client.subscribe(
///     "subject", MsgHandler.init(MyHandler, &handler),
/// );
/// ```
pub const MsgHandler = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        onMessage: *const fn (*anyopaque, *const Message) void,
    };

    /// Create handler from concrete type using comptime.
    pub fn init(comptime T: type, ptr: *T) MsgHandler {
        const gen = struct {
            fn onMessage(p: *anyopaque, msg: *const Message) void {
                const self: *T = @ptrCast(@alignCast(p));
                self.onMessage(msg);
            }
        };
        return .{
            .ptr = ptr,
            .vtable = &.{ .onMessage = gen.onMessage },
        };
    }

    /// Dispatch message to handler.
    pub fn dispatch(
        self: MsgHandler,
        msg: *const Message,
    ) void {
        self.vtable.onMessage(self.ptr, msg);
    }
};

/// Checks if a file descriptor is valid using fcntl.
/// Returns false for negative fds or closed fds.
/// Used to guard shutdown/close calls that panic on
/// BADF in debug mode (Io.Threaded treats as bug).
fn isValidFd(fd: std.posix.fd_t) bool {
    if (fd < 0) return false;
    const F_GETFD = 1;
    const rc = std.posix.system.fcntl(fd, F_GETFD, 0);
    // fcntl returns the fd flags on success, or a
    // large unsigned value (wrapped errno) on failure
    return rc < 0x1000;
}

/// Gets current monotonic time in nanoseconds.
fn getNowNs(io: Io) u64 {
    const ts = Io.Timestamp.now(io, .awake);
    return @intCast(ts.nanoseconds);
}

/// Message received on a subscription. Call deinit() to free.
pub const Message = struct {
    subject: []const u8,
    sid: u64,
    reply_to: ?[]const u8,
    data: []const u8,
    headers: ?[]const u8,
    allocator: Allocator = undefined,
    owned: bool = true,
    /// Single backing buffer (all slices point into this).
    backing_buf: ?[]u8 = null,
    /// Return queue for thread-safe deallocation (reader thread frees).
    return_queue: ?*SpscQueue([]u8) = null,
    /// Spinlock for multi-thread return_queue.push() safety.
    return_lock: ?*SpinLock = null,

    /// Frees message data. Pushes to return queue for slab-allocated msgs.
    /// Thread-safe: return_lock serializes concurrent push().
    // REVIEWED(2025-03): SPSC queue used as MPSC here is safe.
    // The spinlock's lock/unlock provides acquire/release ordering,
    // ensuring each thread sees prior push() head updates.
    pub fn deinit(self: *const Message) void {
        if (!self.owned) return;
        if (self.backing_buf) |buf| {
            assert(self.return_queue != null);
            const rq = self.return_queue.?;
            if (self.return_lock) |sl| {
                sl.lock();
                defer sl.unlock();
                while (!rq.push(buf)) {
                    sl.unlock();
                    std.Thread.yield() catch {};
                    sl.lock();
                }
            } else {
                while (!rq.push(buf)) {
                    std.Thread.yield() catch {};
                }
            }
            return;
        }
        const allocator = self.allocator;
        allocator.free(self.subject);
        allocator.free(self.data);
        if (self.reply_to) |rt| allocator.free(rt);
        if (self.headers) |h| allocator.free(h);
    }

    /// Sends a reply to this message using the reply_to subject.
    /// Convenience method for request/reply pattern.
    /// Returns error.NoReplyTo if message has no reply_to subject.
    pub fn respond(
        self: *const Message,
        client: *Client,
        payload: []const u8,
    ) !void {
        const reply_to = self.reply_to orelse return error.NoReplyTo;
        assert(reply_to.len > 0);
        try client.publish(reply_to, payload);
    }

    /// Returns the total size of the message in bytes.
    /// Includes subject, data, reply_to, and headers.
    pub fn size(self: *const Message) usize {
        var total: usize = self.subject.len + self.data.len;
        if (self.reply_to) |rt| total += rt.len;
        if (self.headers) |h| total += h.len;
        return total;
    }

    /// Extracts HTTP-like status code from headers (on-demand parsing).
    /// Returns null if no headers or no status code present.
    /// Common codes: 503 (no responders), 408 (timeout), 404 (not found).
    pub fn status(self: *const Message) ?u16 {
        const hdrs = self.headers orelse return null;
        return headers.extractStatus(hdrs);
    }

    /// Returns true if this is a no-responders message (status 503).
    /// Used to detect when a request has no available responders.
    pub fn isNoResponders(self: *const Message) bool {
        return self.status() == 503;
    }
};

/// Per-request waiter for the response multiplexer.
///
/// Lives on the request()'s stack. The dispatcher fills `msg`
/// with a cloned response Message and sets `done`; the request
/// task spin-yields on `done` until the response arrives or the
/// timeout fires.
pub const RespWaiter = struct {
    msg: ?Message = null,
    done: std.atomic.Value(bool) =
        std.atomic.Value(bool).init(false),
};

/// Lazy response multiplexer for request/reply.
///
/// Replaces per-request SUB/UNSUB churn with one wildcard inbox
/// subscription per connection. The first request triggers
/// ensureRespMux which subscribes to "_INBOX.<NUID>.*" and does a
/// PING/PONG round-trip to confirm server registration; every
/// subsequent request just registers a waiter in `map` and
/// publishes. The dispatcher (handler on the wildcard sub) routes
/// incoming replies by extracting the token suffix from the
/// message subject and waking the matching waiter.
///
/// Owns its own mutex (NOT sub_mutex) so the demuxer cannot
/// contend with subscribe/unsubscribe.
pub const RespMux = struct {
    /// Back-reference to the owning client. Set during the first
    /// ensureRespMux call so the handler vtable can resolve back
    /// to client.io and client.allocator.
    client: ?*Client = null,

    /// "_INBOX.<NUID>." with trailing dot. Allocated on first
    /// request, freed by closeRespMux.
    prefix: ?[]u8 = null,
    prefix_len: usize = 0,

    /// Wildcard subscription "_INBOX.<NUID>.*". Lives for the
    /// connection lifetime; cleaned up by closeRespMux.
    sub: ?*Subscription = null,

    /// Active waiters keyed by the 8-char base62 token.
    /// Token slices borrow from the requester's stack buffer.
    map: std.StringHashMapUnmanaged(*RespWaiter) = .empty,

    /// Monotonic counter for unique token generation. Atomic so
    /// concurrent request() calls can mint tokens without locking.
    next_token: std.atomic.Value(u64) =
        std.atomic.Value(u64).init(0),

    /// Dedicated mutex protecting `map`. Separate from sub_mutex
    /// so the dispatcher cannot deadlock against subscribe paths.
    mutex: Io.Mutex = .init,

    /// Set true once prefix/sub are valid. Acquire-loaded on the
    /// fast path of every request() to skip re-initialization.
    initialized: std.atomic.Value(bool) =
        std.atomic.Value(bool).init(false),

    /// Handler hook called by the standard callback drain task
    /// for every reply arriving on the wildcard subscription.
    /// Extracts the token suffix from msg.subject, looks up the
    /// matching waiter under `mutex`, clones the message into the
    /// waiter, and signals `done`. Late deliveries (waiter already
    /// removed by timeout) are silently dropped.
    ///
    /// IMPORTANT: clone+write happens INSIDE the lock so the
    /// request() cleanup defer (which acquires the same mutex)
    /// blocks until we are done writing to the waiter. This
    /// prevents use-after-free on the stack-allocated waiter.
    pub fn onMessage(
        self: *RespMux,
        msg: *const Message,
    ) void {
        const client = self.client orelse return;
        assert(self.prefix_len > 0);
        const subj = msg.subject;
        if (subj.len <= self.prefix_len) return;
        const token = subj[self.prefix_len..];

        const io = client.io;
        self.mutex.lockUncancelable(io);
        defer self.mutex.unlock(io);

        const entry = self.map.fetchRemove(token);
        const waiter = if (entry) |e| e.value else return;

        const cloned = cloneMessageContents(
            client.allocator,
            msg,
        ) catch {
            waiter.done.store(true, .release);
            return;
        };
        waiter.msg = cloned;
        waiter.done.store(true, .release);
    }
};

/// Client connection options.
///
/// All fields have sensible defaults. Common customizations:
/// - name: Client identifier visible in server logs
/// - user/pass or auth_token: Authentication credentials
/// - reader_buffer_size/writer_buffer_size: tune protocol buffers
/// - sub_queue_size: Messages buffered per subscription (default 1024)
pub const Options = struct {
    /// Client name for identification.
    name: ?[]const u8 = null,
    /// Enable verbose mode.
    verbose: bool = false,
    /// Enable pedantic mode.
    pedantic: bool = false,
    /// Username for auth.
    user: ?[]const u8 = null,
    /// Password for auth.
    pass: ?[]const u8 = null,
    /// Auth token.
    auth_token: ?[]const u8 = null,
    /// Connection timeout in nanoseconds.
    connect_timeout_ns: u64 = defaults.Connection.timeout_ns,
    /// Per-subscription queue size (messages buffered before dropping).
    sub_queue_size: u32 = defaults.Memory.queue_size.value(),
    /// Echo messages back to sender (default true).
    echo: bool = true,
    /// Enable message headers support.
    headers: bool = true,
    /// Request no_responders notification for requests.
    no_responders: bool = true,
    /// Require TLS connection.
    tls_required: bool = false,

    // TLS OPTIONS

    /// Path to CA certificate file (PEM). Null = use system CAs.
    tls_ca_file: ?[]const u8 = null,
    /// Path to client certificate file for mTLS (PEM).
    tls_cert_file: ?[]const u8 = null,
    /// Path to client private key file for mTLS (PEM).
    tls_key_file: ?[]const u8 = null,
    /// Skip server certificate verification (INSECURE - testing only).
    tls_insecure_skip_verify: bool = false,
    /// Perform TLS handshake before NATS protocol (required by some proxies).
    tls_handshake_first: bool = false,

    /// NKey seed for authentication.
    nkey_seed: ?[]const u8 = null,
    /// NKey seed file path (alternative to nkey_seed).
    nkey_seed_file: ?[]const u8 = null,
    /// NKey public key for callback-based signing.
    nkey_pubkey: ?[]const u8 = null,
    /// NKey signing callback (returns true on success).
    nkey_sign_fn: ?*const fn (nonce: []const u8, sig: *[64]u8) bool = null,
    /// JWT for authentication.
    jwt: ?[]const u8 = null,
    /// Credentials file path (.creds file with JWT + NKey seed).
    /// Mutually exclusive with jwt/nkey_seed options.
    creds_file: ?[]const u8 = null,
    /// Credentials content (alternative to file path).
    /// Use when credentials are loaded from environment/memory.
    creds: ?[]const u8 = null,
    /// Read buffer size. Must be >= max message size you expect (1MB).
    reader_buffer_size: usize = defaults.Connection.reader_buffer_size,
    /// Write buffer size. Smaller values force more frequent flushes.
    writer_buffer_size: usize = defaults.Connection.writer_buffer_size,
    /// TCP receive buffer size hint. Larger values allow more messages to
    /// queue in the kernel before backpressure kicks in. Default 1MB.
    /// Set to 0 to use system default.
    tcp_rcvbuf: u32 = defaults.Connection.tcp_rcvbuf,

    // RECONNECTION OPTIONS

    /// Enable automatic reconnection on disconnect.
    reconnect: bool = defaults.Reconnection.enabled,
    /// Maximum reconnection attempts (0 = infinite).
    max_reconnect_attempts: u32 = defaults.Reconnection.max_attempts,
    /// Initial wait between reconnect attempts (ms).
    reconnect_wait_ms: u32 = defaults.Reconnection.wait_ms,
    /// Maximum wait with exponential backoff (ms).
    reconnect_wait_max_ms: u32 = defaults.Reconnection.wait_max_ms,
    /// Jitter percentage for backoff (0-50).
    reconnect_jitter_percent: u8 = defaults.Reconnection.jitter_percent,
    /// Custom reconnect delay callback. If set, overrides default exponential
    /// backoff. Called with attempt number (1-based), returns delay in ms.
    /// Example: `fn(attempt: u32) u32 { return attempt * 1000; }`
    custom_reconnect_delay: ?*const fn (attempt: u32) u32 = null,
    /// Discover servers from INFO connect_urls.
    discover_servers: bool = defaults.Reconnection.discover_servers,
    /// Size of pending buffer for publishes during reconnect.
    /// Set to 0 to disable buffering (publish returns error during reconnect).
    pending_buffer_size: usize = defaults.Reconnection.pending_buffer_size,

    // PING/PONG HEALTH CHECK

    /// Interval between client-initiated PINGs (ms). 0 = disable.
    ping_interval_ms: u32 = defaults.Connection.ping_interval_ms,
    /// Max outstanding PINGs before connection is considered stale.
    max_pings_outstanding: u8 = defaults.Connection.max_pings_outstanding,

    // ERROR REPORTING

    /// Messages between rate-limited error notifications.
    /// After first error (alloc_failed, protocol_error), subsequent errors
    /// only notify every N messages. Prevents event queue flooding.
    error_notify_interval_msgs: u64 =
        defaults.ErrorReporting.notify_interval_msgs,

    // EVENT CALLBACKS

    /// Event handler for connection lifecycle callbacks (optional).
    /// Use EventHandler.init(T, &handler) to create from a handler struct.
    event_handler: ?EventHandler = null,

    // INBOX/REQUEST OPTIONS

    /// Custom prefix for inbox subjects. Default is "_INBOX".
    /// Used for request/reply pattern inbox generation.
    inbox_prefix: []const u8 = "_INBOX",

    // CONNECTION BEHAVIOR

    /// Retry connection on initial connect failure (before returning error).
    /// When true, connect() will retry using reconnect settings.
    retry_on_failed_connect: bool = false,
    /// Don't randomize server order for connection attempts.
    /// When true, servers are tried in the order provided.
    no_randomize: bool = false,
    /// Ignore servers discovered via cluster INFO.
    /// Only use explicitly configured servers.
    ignore_discovered_servers: bool = false,
    /// Default timeout for drain operations (ms).
    drain_timeout_ms: u32 = 30_000,
    /// Default timeout for flush operations (ms).
    flush_timeout_ms: u32 = 10_000,

    // ADDITIONAL SERVERS

    /// Additional server URLs for reconnection pool.
    /// These are added to the server pool after the primary URL.
    /// Max MAX_SERVERS total (see connection/server_pool.zig).
    servers: ?[]const []const u8 = null,
};

/// Connection statistics.
/// Thread ownership: io_task exclusively writes msgs_in/bytes_in.
/// msgs_out/bytes_out use atomics for multi-thread publish safety.
pub const Statistics = struct {
    /// Total messages received (written by io_task only).
    msgs_in: u64 = 0,
    /// Total messages sent (atomic: multi-thread publish).
    msgs_out: std.atomic.Value(u64) =
        std.atomic.Value(u64).init(0),
    /// Total bytes received (written by io_task only).
    bytes_in: u64 = 0,
    /// Total bytes sent (atomic: multi-thread publish).
    bytes_out: std.atomic.Value(u64) =
        std.atomic.Value(u64).init(0),
    /// Number of reconnects.
    reconnects: std.atomic.Value(u32) =
        std.atomic.Value(u32).init(0),
    /// Total successful connections (initial + reconnects).
    connects: std.atomic.Value(u32) =
        std.atomic.Value(u32).init(0),

    /// Returns a snapshot of stats with atomics loaded.
    pub fn snapshot(self: *const Statistics) StatsSnapshot {
        return .{
            .msgs_in = self.msgs_in,
            .msgs_out = self.msgs_out.load(.monotonic),
            .bytes_in = self.bytes_in,
            .bytes_out = self.bytes_out.load(.monotonic),
            .reconnects = self.reconnects.load(.monotonic),
            .connects = self.connects.load(.monotonic),
        };
    }
};

/// Plain stats snapshot (no atomics). Returned by stats().
pub const StatsSnapshot = struct {
    msgs_in: u64 = 0,
    msgs_out: u64 = 0,
    bytes_in: u64 = 0,
    bytes_out: u64 = 0,
    reconnects: u32 = 0,
    connects: u32 = 0,
};

/// Debug counters for io_task buffer operations.
/// Only incremented when dbg.enabled.
/// Written exclusively by io_task thread, safe to read after deinit.
pub const IoTaskStats = struct {
    /// Number of tryFillBuffer() calls.
    fill_calls: u64 = 0,
    /// Cumulative bytes already buffered (before read).
    fill_buffered_hits: u64 = 0,
    /// Poll timeouts (no data available).
    fill_poll_timeouts: u64 = 0,
    /// Successful socket reads.
    fill_read_success: u64 = 0,
};

/// Subscription backup for restoration after reconnect.
/// Stores essential subscription state with inline buffers.
pub const SubBackup = struct {
    sid: u64 = 0,
    subject_buf: [256]u8 = undefined,
    subject_len: u8 = 0,
    queue_group_buf: [64]u8 = undefined,
    queue_group_len: u8 = 0,

    /// Get subject as slice.
    pub fn getSubject(self: *const SubBackup) []const u8 {
        return self.subject_buf[0..self.subject_len];
    }

    /// Get queue group as optional slice.
    pub fn queueGroup(self: *const SubBackup) ?[]const u8 {
        if (self.queue_group_len == 0) return null;
        return self.queue_group_buf[0..self.queue_group_len];
    }
};

/// Result of drain operation.
pub const DrainResult = struct {
    /// Count of UNSUB commands that failed to encode.
    unsub_failures: u16 = 0,
    /// True if final flush failed (data may not have reached server).
    flush_failed: bool = false,

    /// Returns true if drain completed without any failures.
    pub fn isClean(self: DrainResult) bool {
        return self.unsub_failures == 0 and !self.flush_failed;
    }
};

/// Subscribe command data (used by restoreSubscriptions).
pub const SubscribeCmd = struct {
    sid: u64,
    subject: []const u8,
    queue_group: ?[]const u8,
};

/// Parse result for NATS URL.
pub const ParsedUrl = struct {
    host: []const u8,
    port: u16,
    user: ?[]const u8,
    pass: ?[]const u8,
    use_tls: bool,
};

/// Fixed subscription limits (from defaults.zig).
pub const MAX_SUBSCRIPTIONS: u16 = defaults.Client.max_subscriptions;
pub const SIDMAP_CAPACITY: u32 = defaults.Client.sidmap_capacity;

/// Default queue size per subscription (messages buffered before dropping).
pub const DEFAULT_QUEUE_SIZE: u32 = defaults.Memory.queue_size.value();

comptime {
    assert(SIDMAP_CAPACITY >= MAX_SUBSCRIPTIONS);
}

/// Parses a NATS URL like nats://user:pass@host:port or tls://host:port
pub fn parseUrl(url: []const u8) error{InvalidUrl}!ParsedUrl {
    if (url.len == 0) return error.InvalidUrl;
    var remaining = url;
    var use_tls = false;

    if (std.mem.startsWith(u8, remaining, "tls://")) {
        remaining = remaining[6..];
        use_tls = true;
    } else if (std.mem.startsWith(u8, remaining, "nats://")) {
        remaining = remaining[7..];
    }

    var user: ?[]const u8 = null;
    var pass: ?[]const u8 = null;

    if (std.mem.indexOf(u8, remaining, "@")) |at_pos| {
        const auth = remaining[0..at_pos];
        remaining = remaining[at_pos + 1 ..];

        if (std.mem.indexOf(u8, auth, ":")) |colon_pos| {
            user = auth[0..colon_pos];
            pass = auth[colon_pos + 1 ..];
        } else {
            user = auth;
        }
    }

    if (remaining.len == 0) return error.InvalidUrl;

    var host: []const u8 = undefined;
    var port: u16 = 4222;

    if (remaining[0] == '[') {
        const end = std.mem.indexOfScalar(u8, remaining, ']') orelse
            return error.InvalidUrl;
        host = remaining[1..end];
        const after = remaining[end + 1 ..];
        if (after.len > 0) {
            if (after[0] != ':' or after.len == 1) return error.InvalidUrl;
            port = std.fmt.parseInt(u16, after[1..], 10) catch {
                return error.InvalidUrl;
            };
        }
    } else {
        var colon_count: u8 = 0;
        var colon_pos: usize = 0;
        for (remaining, 0..) |c, i| {
            if (c == '[' or c == ']') return error.InvalidUrl;
            if (c == ':') {
                colon_count += 1;
                colon_pos = i;
            }
        }

        if (colon_count == 1) {
            host = remaining[0..colon_pos];
            if (colon_pos + 1 >= remaining.len) return error.InvalidUrl;
            port = std.fmt.parseInt(u16, remaining[colon_pos + 1 ..], 10) catch {
                return error.InvalidUrl;
            };
        } else {
            host = remaining;
        }
    }

    if (host.len == 0) return error.InvalidUrl;

    assert(host.len > 0);
    if (port == 0) return error.InvalidUrl;
    return .{
        .host = host,
        .port = port,
        .user = user,
        .pass = pass,
        .use_tls = use_tls,
    };
}

fn connectToHost(io: Io, host: []const u8, port: u16) !net.Stream {
    if (net.IpAddress.resolve(io, host, port)) |address| {
        return net.IpAddress.connect(&address, io, .{
            .mode = .stream,
            .protocol = .tcp,
        }) catch return error.ConnectionFailed;
    } else |_| {}

    const hostname = net.HostName.init(host) catch {
        return error.InvalidAddress;
    };
    return net.HostName.connect(hostname, io, port, .{
        .mode = .stream,
        .protocol = .tcp,
    }) catch return error.ConnectionFailed;
}

/// Subscription type alias.
pub const Sub = Subscription;

io: Io,
allocator: Allocator,
stream: net.Stream,
reader: net.Stream.Reader,
writer: net.Stream.Writer,
/// Active reader interface (TCP or TLS). Set once at connection, used by io_task.
active_reader: *Io.Reader = undefined,
/// Active writer interface (TCP or TLS). Set once at connection, used by io_task.
active_writer: *Io.Writer = undefined,
options: Options,

read_buffer: []u8,
write_buffer: []u8,

sidmap: SidMap,
sidmap_keys: [SIDMAP_CAPACITY]u64,
sidmap_vals: [SIDMAP_CAPACITY]u16,
free_slots: [MAX_SUBSCRIPTIONS]u16,

parser: Parser = .{},
server_info: ?ServerInfo = null,
state: State = .connecting,
sub_ptrs: [MAX_SUBSCRIPTIONS]?*Sub = [_]?*Sub{null} ** MAX_SUBSCRIPTIONS,
free_count: u16 = MAX_SUBSCRIPTIONS,
next_sid: u64 = 1,
/// Serializes reader-task routing with unsubscribe/deinit so a
/// loaded subscription pointer cannot be freed while in use.
read_mutex: Io.Mutex = .init,
statistics: Statistics = .{},

// Thread-safety mutexes for multi-thread publish/subscribe.
// Lock ordering: sub_mutex -> read_mutex -> write_mutex.
// publish_mutex and return_lock are independent.

/// Serializes multi-thread publish (encode-to-ring path).
publish_mutex: Io.Mutex = .init,
/// Serializes multi-thread subscribe/unsubscribe bookkeeping.
sub_mutex: Io.Mutex = .init,
/// Lazy response multiplexer for request/reply.
/// Owns its own mutex; does NOT share sub_mutex.
resp_mux: RespMux = .{},
/// Spinlock for multi-thread return_queue.push() in msg.deinit().
/// Atomic spinlock (not Io.Mutex) because Message.deinit() has no io.
return_lock: SpinLock = .{},

// Connection diagnostics
tcp_nodelay_set: bool = false,
tcp_rcvbuf_set: bool = false,

// Fast path cache for single-subscription case
cached_sub: ?*Sub = null,

// Cached max_payload from server_info
max_payload: usize = 1024 * 1024,

// Slab allocator for message buffers
tiered_slab: TieredSlab = undefined,

// Return queue for cross-thread buffer deallocation (main -> reader thread)
// Main thread pushes used buffers here, io_task drains and frees to slab
return_queue: SpscQueue([]u8) = undefined,
return_queue_buf: [][]u8 = undefined,

// Reconnection state
server_pool: connection.ServerPool = undefined,
server_pool_initialized: bool = false,
sub_backups: [MAX_SUBSCRIPTIONS]SubBackup =
    [_]SubBackup{.{}} ** MAX_SUBSCRIPTIONS,
sub_backup_count: u16 = 0,
reconnect_attempt: u32 = 0,
original_url: [256]u8 = undefined,
original_url_len: u8 = 0,

// Pending buffer for publishes during reconnect
pending_buffer: ?[]u8 = null,
pending_buffer_pos: usize = 0,
pending_buffer_capacity: usize = 0,

// PING/PONG health check state (atomics for cross-thread access)
// Main thread reads during health check (~100ms), io_task writes on PONG.
// Uses monotonic ordering - exact timing not critical, eventual visibility suffices.
last_ping_sent_ns: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
last_pong_received_ns: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
pings_outstanding: std.atomic.Value(u8) = std.atomic.Value(u8).init(0),

/// Auto-flush signal: set by publish(), cleared by io_task after flush.
flush_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),

/// Lock-free publish ring buffer. Producer: main thread, Consumer: io_task.
/// Publishes encode directly into this ring; io_task drains to socket.
publish_ring: ByteRing = undefined,
publish_ring_buf: ?[]u8 = null,

// Debug counters for io_task (only used when dbg.enabled)
io_task_stats: IoTaskStats = .{},

// Error rate-limiting state (written by io_task only)
/// Count of protocol parse errors encountered.
protocol_errors: u64 = 0,
/// msgs_in value when protocol_error event was last pushed (rate-limit).
last_parse_error_notified_at: u64 = 0,

// Last async error tracking (written by io_task,
// cleared by user via clearLastError)
/// Last async error that occurred on the connection.
last_error: ?anyerror = null,
/// Message associated with last error (inline buffer, no allocation).
last_error_msg: [256]u8 = undefined,
/// Length of last_error_msg content.
last_error_msg_len: u8 = 0,

// Background I/O task infrastructure
write_mutex: Io.Mutex = .init,
/// Future for background I/O task (for proper cancellation in deinit).
io_task_future: ?Io.Future(void) = null,

// Event callback infrastructure
/// Event queue for io_task -> callback_task communication.
/// SpscQueue for non-blocking push from io_task hot path.
event_queue: ?*SpscQueue(Event) = null,
/// Serializes event producers so the SPSC queue still sees a single writer.
event_queue_mutex: Io.Mutex = .init,
/// Buffer backing the event queue.
event_queue_buf: ?[]Event = null,
/// Future for callback task (dispatches events to user handler).
callback_task_future: ?Io.Future(void) = null,
/// Event handler (copied from options for callback_task access).
event_handler: ?EventHandler = null,
/// Flag to track if lame duck event has been fired.
lame_duck_notified: bool = false,

// TLS state
/// TLS client instance (owns decryption state).
tls_client: ?tls.Client = null,
/// TLS read buffer (must be at least tls.Client.min_buffer_len).
tls_read_buffer: ?[]u8 = null,
/// TLS write buffer (must be at least tls.Client.min_buffer_len).
tls_write_buffer: ?[]u8 = null,
/// CA certificate bundle for verification.
ca_bundle: ?Certificate.Bundle = null,
ca_bundle_lock: Io.RwLock = .init,
/// Whether TLS is enabled for this connection.
use_tls: bool = false,
/// Host for TLS SNI and certificate verification.
tls_host: [256]u8 = undefined,
/// Length of tls_host.
tls_host_len: u8 = 0,

/// Connects to a NATS server.
///
/// Arguments:
///     allocator: Allocator for client and buffer memory
///     io: Io interface for async I/O operations
///     url: NATS server URL (e.g., "nats://localhost:4222")
///     opts: Connection options (timeouts, auth, buffer sizes)
///
/// Returns pointer to connected Client. Caller owns and must call deinit().
pub fn connect(
    allocator: Allocator,
    io: Io,
    url: []const u8,
    opts: Options,
) !*Client {
    // Validate URL length - reject rather than truncate
    if (url.len >= defaults.Server.max_url_len) return error.UrlTooLong;

    const parsed = try parseUrl(url);
    const wants_tls = parsed.use_tls or opts.tls_required or
        opts.tls_ca_file != null or opts.tls_handshake_first;
    if (wants_tls and parsed.host.len > 255) return error.HostTooLong;

    const client = try allocator.create(Client);
    client.allocator = allocator;
    client.server_info = null;
    client.parser = .{};
    client.state = .connecting;
    client.sub_ptrs = [_]?*Sub{null} ** MAX_SUBSCRIPTIONS;
    client.free_count = MAX_SUBSCRIPTIONS;
    client.next_sid = 1;
    client.read_mutex = .init;
    client.sub_mutex = .init;
    client.publish_mutex = .init;
    client.return_lock = .{};
    client.statistics = .{};
    client.cached_sub = null;
    client.max_payload = 1024 * 1024;
    client.tcp_nodelay_set = false;
    client.tcp_rcvbuf_set = false;
    client.flush_requested = std.atomic.Value(bool).init(false);
    client.resp_mux = .{};

    // Initialize reconnection state
    client.server_pool = undefined;
    client.server_pool_initialized = false;
    client.sub_backups = [_]SubBackup{.{}} ** MAX_SUBSCRIPTIONS;
    client.sub_backup_count = 0;
    client.reconnect_attempt = 0;
    client.original_url = undefined;
    client.original_url_len = 0;

    // Initialize pending buffer state
    client.pending_buffer = null;
    client.pending_buffer_pos = 0;
    client.pending_buffer_capacity = 0;

    // Initialize health check state (atomics)
    client.last_ping_sent_ns.raw = 0;
    client.last_pong_received_ns.raw = 0;
    client.pings_outstanding.raw = 0;
    client.io_task_stats = .{};

    // Initialize error tracking state
    client.protocol_errors = 0;
    client.last_parse_error_notified_at = 0;
    client.last_error = null;
    client.last_error_msg_len = 0;

    // Initialize background I/O task infrastructure
    client.write_mutex = .init;
    client.io_task_future = null;
    client.publish_ring_buf = null;

    // Initialize event callback infrastructure
    client.event_queue = null;
    client.event_queue_mutex = .init;
    client.event_queue_buf = null;
    client.callback_task_future = null;
    client.event_handler = opts.event_handler;
    client.lame_duck_notified = false;

    // Initialize TLS state
    client.tls_client = null;
    client.tls_read_buffer = null;
    client.tls_write_buffer = null;
    client.ca_bundle = null;
    client.ca_bundle_lock = .init;
    // Determine if TLS should be used: URL scheme, explicit option, or CA file set
    client.use_tls = wants_tls;
    client.tls_host = undefined;
    client.tls_host_len = 0;

    // Store host for TLS SNI and certificate verification
    if (client.use_tls) {
        const host_len: u8 = @intCast(parsed.host.len);
        @memcpy(client.tls_host[0..host_len], parsed.host);
        client.tls_host_len = host_len;
    }

    // Initialize slab allocator (critical for O(1) message allocation)
    client.tiered_slab = TieredSlab.init(allocator) catch |err| {
        allocator.destroy(client);
        return err;
    };

    // Initialize return queue for cross-thread buffer deallocation
    // Size must exceed slab tier capacity to avoid blocking when buffers
    // are split between sub_queue, processing, and return_queue
    const rq_size = opts.sub_queue_size * 2;
    client.return_queue_buf = allocator.alloc([]u8, rq_size) catch |err| {
        client.tiered_slab.deinit();
        allocator.destroy(client);
        return err;
    };
    client.return_queue = SpscQueue([]u8).init(client.return_queue_buf);

    errdefer {
        allocator.free(client.return_queue_buf);
        client.tiered_slab.deinit();
        if (client.server_info) |*info| {
            info.deinit(allocator);
        }
        // TLS cleanup
        if (client.tls_read_buffer) |buf| allocator.free(buf);
        if (client.tls_write_buffer) |buf| allocator.free(buf);
        if (client.ca_bundle) |*bundle| bundle.deinit(allocator);
        allocator.destroy(client);
    }

    client.stream = try connectToHost(io, parsed.host, parsed.port);
    errdefer client.stream.close(io);

    // TCP_NODELAY
    const enable: u32 = 1;
    client.tcp_nodelay_set = true;
    std.posix.setsockopt(
        client.stream.socket.handle,
        std.posix.IPPROTO.TCP,
        std.posix.TCP.NODELAY,
        std.mem.asBytes(&enable),
    ) catch {
        client.tcp_nodelay_set = false;
    };

    // Set TCP receive buffer size for better backpressure handling
    client.tcp_rcvbuf_set = opts.tcp_rcvbuf > 0;
    if (opts.tcp_rcvbuf > 0) {
        std.posix.setsockopt(
            client.stream.socket.handle,
            std.posix.SOL.SOCKET,
            std.posix.SO.RCVBUF,
            std.mem.asBytes(&opts.tcp_rcvbuf),
        ) catch {
            client.tcp_rcvbuf_set = false;
        };
    }

    client.read_buffer = allocator.alloc(u8, opts.reader_buffer_size) catch {
        return error.OutOfMemory;
    };
    errdefer allocator.free(client.read_buffer);

    client.write_buffer = allocator.alloc(u8, opts.writer_buffer_size) catch {
        return error.OutOfMemory;
    };
    errdefer allocator.free(client.write_buffer);

    // Publish ring: power-of-2, must be > 2x largest
    // possible entry. The ring rejects entries that
    // exceed capacity/2. Entry size = RING_HDR_SIZE +
    // PUB overhead + payload. Add 512 bytes headroom
    // for subject, reply-to, and length digits.
    const min_ring =
        (defaults.Protocol.max_payload + 512) * 2;
    const ring_size = std.math.ceilPowerOfTwo(
        usize,
        @max(min_ring, opts.writer_buffer_size),
    ) catch {
        return error.OutOfMemory;
    };
    client.publish_ring_buf = allocator.alloc(
        u8,
        ring_size,
    ) catch {
        return error.OutOfMemory;
    };
    errdefer if (client.publish_ring_buf) |b| allocator.free(b);
    client.publish_ring = ByteRing.init(
        client.publish_ring_buf.?,
    );

    client.io = io;
    client.reader = client.stream.reader(io, client.read_buffer);
    client.writer = client.stream.writer(io, client.write_buffer);
    // Default to TCP reader/writer (updated by upgradeTls if TLS is used)
    client.active_reader = &client.reader.interface;
    client.active_writer = &client.writer.interface;
    client.options = opts;

    client.sidmap_keys = undefined;
    client.sidmap_vals = undefined;
    client.sidmap = .init(&client.sidmap_keys, &client.sidmap_vals);
    for (0..MAX_SUBSCRIPTIONS) |i| {
        client.free_slots[i] = @intCast(MAX_SUBSCRIPTIONS - 1 - i);
    }

    // TLS-first mode: upgrade to TLS before NATS protocol
    if (client.use_tls and opts.tls_handshake_first) {
        try client.upgradeTls(opts);
    }

    try client.handshake(opts, parsed);
    // Note: TLS upgrade (if needed) now happens inside handshake(),
    // between receiving INFO and sending CONNECT per NATS protocol.

    assert(url.len <= defaults.Server.max_url_len);
    const url_len: u8 = @intCast(url.len);
    @memcpy(client.original_url[0..url_len], url);
    client.original_url_len = url_len;

    client.server_pool = connection.ServerPool.init(url) catch {
        return error.InvalidUrl;
    };
    client.server_pool_initialized = true;

    // Add additional servers from options
    if (opts.servers) |servers| {
        for (servers) |server_url| {
            client.server_pool.addServer(server_url) catch continue;
        }
    }

    if (opts.discover_servers) {
        if (client.server_info) |info| {
            const new_servers = client.server_pool.addFromConnectUrls(
                &info.connect_urls,
                &info.connect_urls_lens,
                info.connect_urls_count,
            );
            if (new_servers > 0) {
                client.pushEvent(
                    .{ .discovered_servers = .{ .count = new_servers } },
                );
            }
        }
    }

    try client.initPendingBuffer();

    const now_ns = getNowNs(io);
    client.last_ping_sent_ns.store(now_ns, .monotonic);
    client.last_pong_received_ns.store(now_ns, .monotonic);

    // concurrent() required - async() may deadlock on flush()
    client.io_task_future = io.concurrent(
        connection.io_task.run,
        .{client},
    ) catch blk: {
        dbg.print("WARNING: concurrent() failed, using async()", .{});
        break :blk io.async(connection.io_task.run, .{client});
    };

    // Spawn callback task if event handler provided
    if (opts.event_handler != null) {
        // Allocate event queue buffer (256 events is plenty for lifecycle)
        const eq_buf = try allocator.alloc(Event, 256);
        client.event_queue_buf = eq_buf;
        errdefer {
            allocator.free(eq_buf);
            client.event_queue_buf = null;
        }

        // Create event queue (SpscQueue for non-blocking push from io_task)
        const eq = try allocator.create(SpscQueue(Event));
        eq.* = SpscQueue(Event).init(eq_buf);
        client.event_queue = eq;
        errdefer {
            allocator.destroy(eq);
            client.event_queue = null;
        }

        // Spawn callback task
        client.callback_task_future = io.concurrent(
            callbackTaskFn,
            .{client},
        ) catch blk: {
            dbg.print(
                "WARNING: callback concurrent() failed, using async()",
                .{},
            );
            break :blk io.async(callbackTaskFn, .{client});
        };

        // Push initial connected event
        _ = eq.push(.{ .connected = {} });

        // Push socket option warnings (non-fatal, performance impact)
        if (!client.tcp_nodelay_set) {
            _ = eq.push(.{
                .err = .{
                    .err = events_mod.Error.TcpNoDelayFailed,
                    .msg = null,
                },
            });
        }
        if (!client.tcp_rcvbuf_set and opts.tcp_rcvbuf > 0) {
            _ = eq.push(.{
                .err = .{
                    .err = events_mod.Error.TcpRcvBufFailed,
                    .msg = null,
                },
            });
        }
    }

    assert(client.next_sid >= 1);
    assert(client.state == .connected);
    return client;
}

/// Push event to callback queue (called by io_task).
/// Non-blocking, drops event if queue is full.
// REVIEWED(2025-03): Silent drop is intentional. Blocking
// would stall the io_task hot path. Users can monitor via
// subscription dropped_msgs counters.
pub fn pushEvent(self: *Client, event: Event) void {
    self.event_queue_mutex.lock(self.io) catch return;
    defer self.event_queue_mutex.unlock(self.io);

    if (self.event_queue) |q| {
        _ = q.push(event);
    }
}

/// Callback task: drains event queue and dispatches to user handler.
/// Runs concurrently, uses io.sleep(0) for async-aware yield with cancellation.
/// Exits on .closed event, null queue (deinit), or when canceled during shutdown.
fn callbackTaskFn(client: *Client) void {
    dbg.print("callback_task: STARTED", .{});

    const handler = client.event_handler orelse return;

    while (State.atomicLoad(&client.state) != .closed) {
        // Check if queue was nulled by deinit() - must exit immediately
        const queue = client.event_queue orelse break;

        // Drain all pending events
        while (queue.pop()) |event| {
            switch (event) {
                .connected => handler.dispatchConnect(),
                .disconnected => |e| handler.dispatchDisconnect(e.err),
                .reconnected => handler.dispatchReconnect(),
                .closed => {
                    handler.dispatchClose();
                    dbg.print("callback_task: EXITED (closed event)", .{});
                    return;
                },
                .slow_consumer => {
                    handler.dispatchError(events_mod.Error.SlowConsumer);
                },
                .err => |e| handler.dispatchError(e.err),
                .lame_duck => handler.dispatchLameDuck(),
                .alloc_failed => {
                    handler.dispatchError(events_mod.Error.AllocationFailed);
                },
                .protocol_error => {
                    handler.dispatchError(events_mod.Error.ProtocolParseError);
                },
                .discovered_servers => |e| {
                    handler.dispatchDiscoveredServers(e.count);
                },
                .draining => handler.dispatchDraining(),
                .subscription_complete => |e| {
                    handler.dispatchSubscriptionComplete(e.sid);
                },
            }
        }
        // REVIEWED(2025-03): yield-based polling is intentional.
        // Blocking alternatives (futex/condvar) add complexity
        // for the event dispatch path with minimal benefit.
        std.Thread.yield() catch {};
    }

    // Drain any remaining events queued during shutdown
    if (client.event_queue) |queue| {
        while (queue.pop()) |event| {
            switch (event) {
                .connected => handler.dispatchConnect(),
                .disconnected => |e| handler.dispatchDisconnect(e.err),
                .reconnected => handler.dispatchReconnect(),
                .closed => {}, // Will dispatch below
                .slow_consumer => {
                    handler.dispatchError(events_mod.Error.SlowConsumer);
                },
                .err => |e| handler.dispatchError(e.err),
                .lame_duck => handler.dispatchLameDuck(),
                .alloc_failed => {
                    handler.dispatchError(events_mod.Error.AllocationFailed);
                },
                .protocol_error => {
                    handler.dispatchError(events_mod.Error.ProtocolParseError);
                },
                .discovered_servers => |e| {
                    handler.dispatchDiscoveredServers(e.count);
                },
                .draining => handler.dispatchDraining(),
                .subscription_complete => |e| {
                    handler.dispatchSubscriptionComplete(e.sid);
                },
            }
        }
    }

    // Dispatch final close event if not already done
    handler.dispatchClose();
    dbg.print("callback_task: EXITED (state closed)", .{});
}

/// Drain loop for MsgHandler callback subscriptions.
/// Runs as io.async task, pops messages and dispatches to handler.
/// Automatically frees each message after dispatch.
fn callbackDrainFn(
    sub: *Subscription,
    handler: MsgHandler,
) void {
    assert(sub.mode == .callback);
    const io = sub.client.io;
    while (sub.state == .active or sub.state == .draining) {
        const msg = sub.nextRaw(io) catch |err| {
            if (err == error.Canceled or
                err == error.Closed) break;
            continue;
        };
        handler.dispatch(&msg);
        msg.deinit();
    }
}

/// Drain loop for plain fn callback subscriptions.
/// Same as callbackDrainFn but calls a plain function pointer.
fn callbackDrainFnPlain(
    sub: *Subscription,
    cb: *const fn (*const Message) void,
) void {
    assert(sub.mode == .callback);
    const io = sub.client.io;
    while (sub.state == .active or sub.state == .draining) {
        const msg = sub.nextRaw(io) catch |err| {
            if (err == error.Canceled or
                err == error.Closed) break;
            continue;
        };
        cb(&msg);
        msg.deinit();
    }
}

/// Clones a Message into freshly allocated buffers owned by the
/// caller. Used by RespMux.onMessage to transfer a borrowed reply
/// (freed by the callback drain) into a Message the requester
/// can keep beyond the handler return.
fn cloneMessageContents(
    allocator: Allocator,
    src: *const Message,
) !Message {
    assert(src.subject.len > 0);

    const subject = try allocator.dupe(u8, src.subject);
    errdefer allocator.free(subject);

    const data = try allocator.dupe(u8, src.data);
    errdefer allocator.free(data);

    const reply_to: ?[]const u8 = if (src.reply_to) |rt|
        try allocator.dupe(u8, rt)
    else
        null;
    errdef
Download .txt
gitextract_52v3q3d0/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── claude.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── build.zig
├── build.zig.zon
├── doc/
│   ├── JetStream.md
│   └── nats-by-example/
│       ├── README.md
│       ├── auth/
│       │   ├── NKeys-JWTs.md
│       │   └── nkeys-jwts.zig
│       └── messaging/
│           ├── Concurrent.md
│           ├── Iterating-Multiple-Subscriptions.md
│           ├── Json.md
│           ├── Pub-Sub.md
│           ├── README.md
│           ├── Request-Reply.md
│           ├── concurrent.zig
│           ├── iterating-multiple-subscriptions.zig
│           ├── json.zig
│           ├── pub-sub.zig
│           └── request-reply.zig
└── src/
    ├── Client.zig
    ├── auth/
    │   ├── base32.zig
    │   ├── crc16.zig
    │   ├── creds.zig
    │   ├── jwt.zig
    │   └── nkey.zig
    ├── auth.zig
    ├── connection/
    │   ├── errors.zig
    │   ├── events.zig
    │   ├── io_task.zig
    │   ├── reconnect_test.zig
    │   ├── server_pool.zig
    │   ├── server_pool_test.zig
    │   └── state.zig
    ├── connection.zig
    ├── dbg.zig
    ├── defaults.zig
    ├── events.zig
    ├── examples/
    │   ├── README.md
    │   ├── batch_receiving.zig
    │   ├── callback.zig
    │   ├── events.zig
    │   ├── graceful_shutdown.zig
    │   ├── headers.zig
    │   ├── jetstream_async_publish.zig
    │   ├── jetstream_consume.zig
    │   ├── jetstream_publish.zig
    │   ├── jetstream_push.zig
    │   ├── kv.zig
    │   ├── kv_watch.zig
    │   ├── micro_echo.zig
    │   ├── polling_loop.zig
    │   ├── queue_groups.zig
    │   ├── reconnection.zig
    │   ├── request_reply.zig
    │   ├── request_reply_callback.zig
    │   ├── select.zig
    │   └── simple.zig
    ├── io_backend.zig
    ├── jetstream/
    │   ├── JetStream.zig
    │   ├── async_publish.zig
    │   ├── consumer.zig
    │   ├── errors.zig
    │   ├── kv.zig
    │   ├── message.zig
    │   ├── ordered.zig
    │   ├── publish_headers.zig
    │   ├── pull.zig
    │   ├── push.zig
    │   └── types.zig
    ├── jetstream.zig
    ├── memory/
    │   ├── sidmap.zig
    │   ├── sidmap_test.zig
    │   └── slab.zig
    ├── memory.zig
    ├── micro/
    │   ├── Service.zig
    │   ├── endpoint.zig
    │   ├── json_util.zig
    │   ├── protocol.zig
    │   ├── request.zig
    │   ├── stats.zig
    │   ├── timeutil.zig
    │   └── validation.zig
    ├── micro.zig
    ├── nats.zig
    ├── protocol/
    │   ├── commands.zig
    │   ├── encoder.zig
    │   ├── encoder_test.zig
    │   ├── errors.zig
    │   ├── header_map.zig
    │   ├── headers.zig
    │   ├── parser.zig
    │   └── parser_test.zig
    ├── protocol.zig
    ├── pubsub/
    │   ├── inbox.zig
    │   ├── subject.zig
    │   ├── subject_test.zig
    │   ├── subscription.zig
    │   └── subscription_test.zig
    ├── pubsub.zig
    ├── sync/
    │   ├── byte_ring.zig
    │   ├── spin_lock.zig
    │   └── spsc_queue.zig
    └── testing/
        ├── README.md
        ├── certs/
        │   ├── client-all.pem
        │   ├── client-cert.pem
        │   ├── client-key.pem
        │   ├── ip-ca.pem
        │   ├── ip-cert.pem
        │   ├── ip-key.pem
        │   ├── rootCA-key.pem
        │   ├── rootCA.pem
        │   ├── server-cert.pem
        │   └── server-key.pem
        ├── client/
        │   ├── advanced.zig
        │   ├── async_patterns.zig
        │   ├── auth.zig
        │   ├── autoflush.zig
        │   ├── basic.zig
        │   ├── callback.zig
        │   ├── concurrency.zig
        │   ├── connection.zig
        │   ├── drain.zig
        │   ├── dynamic_jwt.zig
        │   ├── edge_cases.zig
        │   ├── error_handling.zig
        │   ├── flush_confirmed.zig
        │   ├── getters.zig
        │   ├── headers.zig
        │   ├── jetstream.zig
        │   ├── jwt.zig
        │   ├── micro.zig
        │   ├── multi_client.zig
        │   ├── multithread.zig
        │   ├── nkey.zig
        │   ├── protocol.zig
        │   ├── publish.zig
        │   ├── queue.zig
        │   ├── reconnect.zig
        │   ├── request_reply.zig
        │   ├── server.zig
        │   ├── state_notifications.zig
        │   ├── stats.zig
        │   ├── stress.zig
        │   ├── stress_subs.zig
        │   ├── subscribe.zig
        │   ├── tests.zig
        │   ├── tls.zig
        │   └── wildcard.zig
        ├── configs/
        │   ├── TestUser.creds
        │   ├── jwt.conf
        │   └── tls.conf
        ├── integration_test.zig
        ├── micro_integration_test.zig
        ├── server_manager.zig
        ├── test_utils.zig
        └── tls_integration_test.zig
Condensed preview — 161 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,012K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2983,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nco"
  },
  {
    "path": ".github/workflows/claude.yml",
    "chars": 907,
    "preview": "name: Claude Code\n\n# GITHUB_TOKEN is neutered — all GitHub API access uses the App token instead.\npermissions: {}\n\non:\n "
  },
  {
    "path": ".gitignore",
    "chars": 72,
    "preview": "# The build cache\nzig-cache\n.zig-cache\nzig-out/\ntmp/\n.mcp.json\n.claude/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1115,
    "preview": "# Contributing\n\nThanks for helping improve `nats.zig`.\n\n## Development Setup\n\nRequired tools:\n\n- Zig 0.16.0 or later\n- `"
  },
  {
    "path": "LICENSE",
    "chars": 10772,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 52091,
    "preview": "[![CI](https://github.com/nats-io/nats.zig/actions/workflows/ci.yml/badge.svg)](https://github.com/nats-io/nats.zig/acti"
  },
  {
    "path": "SECURITY.md",
    "chars": 609,
    "preview": "# Security Policy\n\nPlease report suspected security vulnerabilities privately. Do not open a\npublic issue for a vulnerab"
  },
  {
    "path": "build.zig",
    "chars": 25207,
    "preview": "const std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const target = b.standardTargetOptions(.{});\n    con"
  },
  {
    "path": "build.zig.zon",
    "chars": 419,
    "preview": ".{\n    .name = .nats,\n    .version = \"0.1.0\",\n    // Changing fingerprint has security and trust implications.\n    .fing"
  },
  {
    "path": "doc/JetStream.md",
    "chars": 19487,
    "preview": "# JetStream Guide for nats.zig\n\nJetStream is NATS' persistence and streaming layer. It provides\nat-least-once delivery, "
  },
  {
    "path": "doc/nats-by-example/README.md",
    "chars": 658,
    "preview": "# NATS by Example\n\nPorts of [natsbyexample.com](https://natsbyexample.com) examples.\n\n| Example | Run | Server? |\n|-----"
  },
  {
    "path": "doc/nats-by-example/auth/NKeys-JWTs.md",
    "chars": 2632,
    "preview": "# NKeys and JWTs\n\nNATS supports decentralized authentication using a three-level trust hierarchy:\n\n- **Operator** - Top-"
  },
  {
    "path": "doc/nats-by-example/auth/nkeys-jwts.zig",
    "chars": 4357,
    "preview": "//! NKeys and JWTs\n//!\n//! This example demonstrates NATS decentralized authentication using\n//! the operator/account/us"
  },
  {
    "path": "doc/nats-by-example/messaging/Concurrent.md",
    "chars": 1840,
    "preview": "# Concurrent Message Processing\n\nBy default, messages from a subscription are processed sequentially -\neach message must"
  },
  {
    "path": "doc/nats-by-example/messaging/Iterating-Multiple-Subscriptions.md",
    "chars": 1726,
    "preview": "# Iterating Over Multiple Subscriptions\n\nNATS wildcards cover many routing cases, but sometimes you need\nseparate subscr"
  },
  {
    "path": "doc/nats-by-example/messaging/Json.md",
    "chars": 1483,
    "preview": "# JSON for Message Payloads\n\nNATS message payloads are opaque byte sequences. It is up to the\napplication to define seri"
  },
  {
    "path": "doc/nats-by-example/messaging/Pub-Sub.md",
    "chars": 1736,
    "preview": "# Publish-Subscribe\n\nNATS implements publish-subscribe message distribution through subject-based\nrouting. Publishers se"
  },
  {
    "path": "doc/nats-by-example/messaging/README.md",
    "chars": 1344,
    "preview": "# Messaging\n\nExamples based on the [natsbyexample.com](https://natsbyexample.com/)\n**Messaging** category, implemented i"
  },
  {
    "path": "doc/nats-by-example/messaging/Request-Reply.md",
    "chars": 1492,
    "preview": "# Request-Reply\n\nThe request-reply pattern enables RPC-style communication over NATS.\nUnder the hood, NATS implements th"
  },
  {
    "path": "doc/nats-by-example/messaging/concurrent.zig",
    "chars": 4380,
    "preview": "//! Concurrent Message Processing\n//!\n//! By default, messages from a subscription are processed\n//! sequentially. This "
  },
  {
    "path": "doc/nats-by-example/messaging/iterating-multiple-subscriptions.zig",
    "chars": 4211,
    "preview": "//! Iterating Over Multiple Subscriptions\n//!\n//! NATS wildcards cover many routing cases, but sometimes you\n//! need se"
  },
  {
    "path": "doc/nats-by-example/messaging/json.zig",
    "chars": 3499,
    "preview": "//! JSON for Message Payloads\n//!\n//! NATS message payloads are opaque byte sequences - the application\n//! decides how "
  },
  {
    "path": "doc/nats-by-example/messaging/pub-sub.zig",
    "chars": 3289,
    "preview": "//! Publish-Subscribe\n//!\n//! This example demonstrates the core NATS publish-subscribe pattern.\n//! Pub/Sub is the fund"
  },
  {
    "path": "doc/nats-by-example/messaging/request-reply.zig",
    "chars": 4097,
    "preview": "//! Request-Reply\n//!\n//! The request-reply pattern allows a client to send a request and\n//! wait for a response. Under"
  },
  {
    "path": "src/Client.zig",
    "chars": 166880,
    "preview": "//! NATS Client\n//!\n//! High-level client API for connecting to NATS servers.\n//! Uses std.Io for native async I/O with "
  },
  {
    "path": "src/auth/base32.zig",
    "chars": 5302,
    "preview": "//! Base32 encoder/decoder (RFC 4648).\n//!\n//! Uses standard alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\n\nconst std = @im"
  },
  {
    "path": "src/auth/crc16.zig",
    "chars": 1053,
    "preview": "//! CRC16-CCITT checksum for NKey validation.\n//!\n//! Thin wrapper around std.hash.crc.Crc16Xmodem.\n\nconst std = @import"
  },
  {
    "path": "src/auth/creds.zig",
    "chars": 9969,
    "preview": "//! Credentials file parser and formatter for NATS JWT authentication.\n//!\n//! Parses and formats .creds files containin"
  },
  {
    "path": "src/auth/jwt.zig",
    "chars": 27733,
    "preview": "//! JWT encoding for NATS decentralized authentication.\n//!\n//! Encodes account and user JWTs signed with NKey Ed25519 k"
  },
  {
    "path": "src/auth/nkey.zig",
    "chars": 13427,
    "preview": "//! NKey authentication for NATS.\n//!\n//! Generates, parses, and encodes NKey seeds. Signs server nonces\n//! for authent"
  },
  {
    "path": "src/auth.zig",
    "chars": 703,
    "preview": "//! Authentication modules for NATS.\n//!\n//! Provides NKey authentication (Ed25519 signatures) and credentials\n//! file "
  },
  {
    "path": "src/connection/errors.zig",
    "chars": 2530,
    "preview": "//! Connection Errors\n//!\n//! Error types for connection-related failures including authentication,\n//! timeouts, and co"
  },
  {
    "path": "src/connection/events.zig",
    "chars": 2276,
    "preview": "//! Connection Events\n//!\n//! Event queue for connection lifecycle and message events.\n\nconst std = @import(\"std\");\ncons"
  },
  {
    "path": "src/connection/io_task.zig",
    "chars": 33205,
    "preview": "//! Background I/O Task for NATS Client\n//!\n//! Async task that handles:\n//! - All socket reads (fillMore)\n//! - Message"
  },
  {
    "path": "src/connection/reconnect_test.zig",
    "chars": 12016,
    "preview": "//! Reconnection Logic Unit Tests\n//!\n//! Tests for subscription backup/restore, backoff calculations,\n//! and pending b"
  },
  {
    "path": "src/connection/server_pool.zig",
    "chars": 11827,
    "preview": "//! Server Pool for Reconnection\n//!\n//! Manages multiple servers for reconnection with round-robin rotation.\n//! Server"
  },
  {
    "path": "src/connection/server_pool_test.zig",
    "chars": 13160,
    "preview": "//! Server Pool Tests\n//!\n//! Unit tests for ServerPool including edge cases,\n//! failure tracking, cooldown behavior, a"
  },
  {
    "path": "src/connection/state.zig",
    "chars": 5402,
    "preview": "//! Connection State Machine\n//!\n//! Manages the connection state transitions for NATS protocol.\n\nconst std = @import(\"s"
  },
  {
    "path": "src/connection.zig",
    "chars": 980,
    "preview": "//! Connection Module\n//!\n//! Provides connection state management and events for NATS.\n\nconst std = @import(\"std\");\n\npu"
  },
  {
    "path": "src/dbg.zig",
    "chars": 1975,
    "preview": "//! Debug printing utilities for NATS client.\n//!\n//! Compile with -DEnableDebug=true to enable debug output.\n//! When d"
  },
  {
    "path": "src/defaults.zig",
    "chars": 5792,
    "preview": "//! Centralized Default Configuration\n//!\n//! Queue size is the master value. Slab tier counts derive from it.\n//! Chang"
  },
  {
    "path": "src/events.zig",
    "chars": 17276,
    "preview": "//! Event Callbacks for NATS Client\n//!\n//! Type-erased event handler using comptime vtable pattern (like std.mem.Alloca"
  },
  {
    "path": "src/examples/README.md",
    "chars": 1728,
    "preview": "# Examples\n\nRun with `zig build run-<name>` (requires `nats-server` on localhost:4222).\n\n| Example | Run | Description |"
  },
  {
    "path": "src/examples/batch_receiving.zig",
    "chars": 4037,
    "preview": "//! Batch Receiving Patterns\n//!\n//! Demonstrates efficient batch message retrieval:\n//! - nextMsgBatch(): blocking batc"
  },
  {
    "path": "src/examples/callback.zig",
    "chars": 3098,
    "preview": "//! Callback Subscriptions\n//!\n//! Demonstrates callback-style message handling using MsgHandler\n//! (vtable pattern) an"
  },
  {
    "path": "src/examples/events.zig",
    "chars": 4321,
    "preview": "//! Event Callbacks Example\n//!\n//! Demonstrates how to handle connection lifecycle events using the\n//! EventHandler pa"
  },
  {
    "path": "src/examples/graceful_shutdown.zig",
    "chars": 4084,
    "preview": "//! Graceful Shutdown Pattern\n//!\n//! Demonstrates production-ready lifecycle management:\n//! - drain() for graceful sub"
  },
  {
    "path": "src/examples/headers.zig",
    "chars": 2204,
    "preview": "//! NATS Headers\n//!\n//! Demonstrates publishing messages with headers and parsing\n//! received header metadata.\n//!\n//!"
  },
  {
    "path": "src/examples/jetstream_async_publish.zig",
    "chars": 3394,
    "preview": "//! JetStream Async Publish -- non-blocking publish with\n//! futures.\n//!\n//! AsyncPublisher decouples publishing from a"
  },
  {
    "path": "src/examples/jetstream_consume.zig",
    "chars": 4459,
    "preview": "//! JetStream Pull Consumer -- fetch, iterate, and consume\n//! patterns.\n//!\n//! Demonstrates three ways to receive mess"
  },
  {
    "path": "src/examples/jetstream_publish.zig",
    "chars": 3692,
    "preview": "//! JetStream Publish -- stream CRUD and publish with\n//! acknowledgment.\n//!\n//! Creates a stream, publishes messages w"
  },
  {
    "path": "src/examples/jetstream_push.zig",
    "chars": 4353,
    "preview": "//! JetStream Push Consumer -- server-side message delivery.\n//!\n//! Push consumers have the server send messages to a\n/"
  },
  {
    "path": "src/examples/kv.zig",
    "chars": 5884,
    "preview": "//! Key-Value Store -- CRUD, concurrency, listing.\n//!\n//! NATS KV is a distributed key-value store backed by\n//! JetStr"
  },
  {
    "path": "src/examples/kv_watch.zig",
    "chars": 3967,
    "preview": "//! Key-Value Watch -- real-time change notifications.\n//!\n//! KV watch creates an ephemeral consumer that delivers\n//! "
  },
  {
    "path": "src/examples/micro_echo.zig",
    "chars": 851,
    "preview": "const std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Echo = struct {\n    pub fn onRequest(_: *@This(), req: "
  },
  {
    "path": "src/examples/polling_loop.zig",
    "chars": 5395,
    "preview": "//! Non-Blocking Polling Pattern\n//!\n//! Demonstrates non-blocking message processing with tryNextMsg():\n//! - Event loo"
  },
  {
    "path": "src/examples/queue_groups.zig",
    "chars": 3715,
    "preview": "//! Queue Groups - Load-Balanced Workers\n//!\n//! Demonstrates horizontal scaling with NATS queue groups. Multiple worker"
  },
  {
    "path": "src/examples/reconnection.zig",
    "chars": 4298,
    "preview": "//! Reconnection and Resilience\n//!\n//! Demonstrates NATS client resilience features:\n//! - Reconnection configuration o"
  },
  {
    "path": "src/examples/request_reply.zig",
    "chars": 2750,
    "preview": "//! Request/Reply Pattern\n//!\n//! Demonstrates RPC-style request/reply communication.\n//! Run with: zig build run-reques"
  },
  {
    "path": "src/examples/request_reply_callback.zig",
    "chars": 3358,
    "preview": "//! Request/Reply with Callback Subscription\n//!\n//! Demonstrates building a service responder using callback-style\n//! "
  },
  {
    "path": "src/examples/select.zig",
    "chars": 3980,
    "preview": "//! Io.Select Pattern - Subscription with Timeout\n//!\n//! Demonstrates Io.Select to race a subscription receive against "
  },
  {
    "path": "src/examples/simple.zig",
    "chars": 1680,
    "preview": "//! Simple NATS Example\n//!\n//! Minimal \"hello world\" - connect, subscribe, publish, receive one message.\n//! A starting"
  },
  {
    "path": "src/io_backend.zig",
    "chars": 2616,
    "preview": "//! Comptime selector for the std.Io backend used by entry points.\n//!\n//! Picks between `std.Io.Threaded` (default) and"
  },
  {
    "path": "src/jetstream/JetStream.zig",
    "chars": 40324,
    "preview": "//! JetStream context providing stream/consumer CRUD, publish,\n//! and pull subscription operations over core NATS reque"
  },
  {
    "path": "src/jetstream/async_publish.zig",
    "chars": 12783,
    "preview": "//! Async JetStream publish with PubAckFuture.\n//!\n//! Non-blocking publish that returns a future for the ack.\n//! Uses "
  },
  {
    "path": "src/jetstream/consumer.zig",
    "chars": 8703,
    "preview": "//! Shared consumer abstractions for JetStream pull and push.\n//!\n//! Types here are designed for reuse across consumpti"
  },
  {
    "path": "src/jetstream/errors.zig",
    "chars": 4013,
    "preview": "//! JetStream error types and error codes.\n//!\n//! Two-layer error handling: Zig error unions for transport/protocol\n//!"
  },
  {
    "path": "src/jetstream/kv.zig",
    "chars": 38734,
    "preview": "//! JetStream Key-Value Store.\n//!\n//! A key-value store backed by a JetStream stream. Keys are\n//! NATS subjects under "
  },
  {
    "path": "src/jetstream/message.zig",
    "chars": 10035,
    "preview": "//! JetStream message wrapper with acknowledgment protocol.\n//!\n//! Wraps a core NATS Message and adds JetStream ack/nak"
  },
  {
    "path": "src/jetstream/ordered.zig",
    "chars": 8914,
    "preview": "//! Ordered consumer for gap-free, in-order delivery.\n//!\n//! Internal type used by KV Watch. Automatically recreates\n//"
  },
  {
    "path": "src/jetstream/publish_headers.zig",
    "chars": 2274,
    "preview": "const std = @import(\"std\");\n\nconst nats = @import(\"../nats.zig\");\nconst headers = nats.protocol.headers;\nconst types = @"
  },
  {
    "path": "src/jetstream/pull.zig",
    "chars": 19244,
    "preview": "//! JetStream pull-based subscription.\n//!\n//! Implements fetch-based message consumption: subscribe to a\n//! temporary "
  },
  {
    "path": "src/jetstream/push.zig",
    "chars": 9850,
    "preview": "//! JetStream push-based consumer subscription.\n//!\n//! Push consumers have a deliver_subject configured and the\n//! ser"
  },
  {
    "path": "src/jetstream/types.zig",
    "chars": 20140,
    "preview": "//! JetStream type definitions for stream/consumer configuration,\n//! API responses, and request payloads.\n//!\n//! All s"
  },
  {
    "path": "src/jetstream.zig",
    "chars": 2310,
    "preview": "//! JetStream -- NATS persistence and streaming layer.\n//!\n//! Provides stream/consumer CRUD, publish with ack, pull-bas"
  },
  {
    "path": "src/memory/sidmap.zig",
    "chars": 5123,
    "preview": "//! SidMap - Zero-Alloc Subscription ID Router\n//!\n//! Pre-allocated open-addressing hash map optimized for O(1) subscri"
  },
  {
    "path": "src/memory/sidmap_test.zig",
    "chars": 19349,
    "preview": "//! SidMap Edge Case Tests\n//!\n//! - Sentinel value handling (EMPTY/TOMB corruption)\n//! - Hash collision stress testing"
  },
  {
    "path": "src/memory/slab.zig",
    "chars": 11825,
    "preview": "//! Tiered Slab Allocator\n//!\n//! High-performance message buffer allocator with O(1) alloc/free.\n//! Uses tiered slabs "
  },
  {
    "path": "src/memory.zig",
    "chars": 389,
    "preview": "//! Memory Management\n//!\n//! Provides SidMap for O(1) subscription routing and TieredSlab for\n//! high-performance mess"
  },
  {
    "path": "src/micro/Service.zig",
    "chars": 17464,
    "preview": "const std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst pubsub = @import(\"../pubsub.zig\");\nconst endp"
  },
  {
    "path": "src/micro/endpoint.zig",
    "chars": 5142,
    "preview": "const std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst protocol = @import(\"protocol.zig\");\nconst req"
  },
  {
    "path": "src/micro/json_util.zig",
    "chars": 711,
    "preview": "const std = @import(\"std\");\n\nconst json_stringify_opts: std.json.Stringify.Options = .{\n    .emit_null_optional_fields ="
  },
  {
    "path": "src/micro/protocol.zig",
    "chars": 1901,
    "preview": "const std = @import(\"std\");\n\npub const Type = struct {\n    pub const ping = \"io.nats.micro.v1.ping_response\";\n    pub co"
  },
  {
    "path": "src/micro/request.zig",
    "chars": 3171,
    "preview": "const std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst headers_mod = @import(\"../protocol/headers.zi"
  },
  {
    "path": "src/micro/stats.zig",
    "chars": 3383,
    "preview": "const std = @import(\"std\");\nconst protocol = @import(\"protocol.zig\");\nconst SpinLock = @import(\"../sync/spin_lock.zig\")."
  },
  {
    "path": "src/micro/timeutil.zig",
    "chars": 958,
    "preview": "const std = @import(\"std\");\n\npub fn nowRfc3339(io: std.Io, buf: []u8) ![]const u8 {\n    const ts = std.Io.Timestamp.now("
  },
  {
    "path": "src/micro/validation.zig",
    "chars": 1386,
    "preview": "const std = @import(\"std\");\nconst pubsub = @import(\"../pubsub.zig\");\n\npub const Error = error{\n    InvalidName,\n    Inva"
  },
  {
    "path": "src/micro.zig",
    "chars": 808,
    "preview": "const std = @import(\"std\");\n\npub const Service = @import(\"micro/Service.zig\");\npub const Config = Service.Config;\npub co"
  },
  {
    "path": "src/nats.zig",
    "chars": 2229,
    "preview": "//! NATS Client Library for Zig\n//!\n//! A Zig implementation of the NATS messaging protocol with native\n//! async I/O us"
  },
  {
    "path": "src/protocol/commands.zig",
    "chars": 10328,
    "preview": "//! NATS Protocol Command Definitions\n//!\n//! Defines the structure of all NATS protocol commands for both\n//! server-to"
  },
  {
    "path": "src/protocol/encoder.zig",
    "chars": 7184,
    "preview": "//! NATS Protocol Encoder\n//!\n//! Encodes client commands into NATS wire protocol format.\n//! All string fields are vali"
  },
  {
    "path": "src/protocol/encoder_test.zig",
    "chars": 28566,
    "preview": "//! Protocol Encoder Edge Case Tests\n//!\n//! - Integer conversion edge cases\n//! - CRLF injection attacks (SECURITY)\n//!"
  },
  {
    "path": "src/protocol/errors.zig",
    "chars": 1803,
    "preview": "//! Protocol Errors\n//!\n//! Error types for protocol-related failures including parsing errors,\n//! server errors, and a"
  },
  {
    "path": "src/protocol/header_map.zig",
    "chars": 13247,
    "preview": "//! Header Map Builder\n//!\n//! Programmatic builder for NATS message headers.\n//! Supports multi-value headers (same key"
  },
  {
    "path": "src/protocol/headers.zig",
    "chars": 21011,
    "preview": "//! NATS Protocol Headers\n//!\n//! Handles NATS message headers in the NATS/1.0 format.\n//! Headers are used with HPUB/HM"
  },
  {
    "path": "src/protocol/parser.zig",
    "chars": 14658,
    "preview": "//! NATS Protocol Parser\n//!\n//! Parses incoming data from NATS server into structured commands.\n//! Handles streaming d"
  },
  {
    "path": "src/protocol/parser_test.zig",
    "chars": 44070,
    "preview": "//! Parser Edge Case Tests\n//!\n//! - Integer parsing edge cases (overflow, boundaries, invalid chars)\n//! - MSG/HMSG par"
  },
  {
    "path": "src/protocol.zig",
    "chars": 1377,
    "preview": "//! NATS Protocol Implementation\n//!\n//! This module handles the NATS wire protocol including parsing server\n//! command"
  },
  {
    "path": "src/pubsub/inbox.zig",
    "chars": 4494,
    "preview": "//! Inbox Generation\n//!\n//! Generates unique inbox subjects for request/reply patterns.\n//! Inbox format: _INBOX.<rando"
  },
  {
    "path": "src/pubsub/subject.zig",
    "chars": 4695,
    "preview": "//! Subject Validation and Matching\n//!\n//! NATS subjects are dot-separated tokens. Wildcards:\n//! - `*` matches exactly"
  },
  {
    "path": "src/pubsub/subject_test.zig",
    "chars": 17150,
    "preview": "//! Subject Validation Edge Case Tests\n//!\n//! - Empty/boundary inputs\n//! - Null bytes and control characters\n//! - Wil"
  },
  {
    "path": "src/pubsub/subscription.zig",
    "chars": 5755,
    "preview": "//! Subscription Types (for embedded/zero-allocation use)\n//!\n//! This module contains types for embedded/no-alloc scena"
  },
  {
    "path": "src/pubsub/subscription_test.zig",
    "chars": 25057,
    "preview": "//! Subscription Module Tests\n//!\n//! Tests ring buffer wraparound, capacity limits, state transitions,\n//! and subject "
  },
  {
    "path": "src/pubsub.zig",
    "chars": 1160,
    "preview": "//! Pub/Sub Module\n//!\n//! Provides publish/subscribe utilities including inbox generation\n//! and subject validation. F"
  },
  {
    "path": "src/sync/byte_ring.zig",
    "chars": 14908,
    "preview": "//! Lock-free SPSC Byte Ring Buffer\n//!\n//! Variable-length message passing between producer and consumer threads.\n//! E"
  },
  {
    "path": "src/sync/spin_lock.zig",
    "chars": 1802,
    "preview": "//! Lightweight atomic spinlock for short critical sections.\n//!\n//! Used where Io.Mutex is unavailable (e.g., Message.d"
  },
  {
    "path": "src/sync/spsc_queue.zig",
    "chars": 7961,
    "preview": "//! Lock-free Single Producer Single Consumer Queue\n//!\n//! Zero syscalls, zero mutex, maximum throughput.\n//! Designed "
  },
  {
    "path": "src/testing/README.md",
    "chars": 1247,
    "preview": "# Integration Tests\n\nThis directory contains the integration test harness and fixtures for the\nNATS Zig client. These te"
  },
  {
    "path": "src/testing/certs/client-all.pem",
    "chars": 5110,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCznnzpbfhgAUBe\nNtyUVTdHJZ93vWx7WeuO3q8zMeP"
  },
  {
    "path": "src/testing/certs/client-cert.pem",
    "chars": 1663,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIEnjCCAwagAwIBAgIQciuy77HHsdMG7UD/WPT1gzANBgkqhkiG9w0BAQsFADCB\ngzEeMBwGA1UEChMVbWtjZXJ0IGR"
  },
  {
    "path": "src/testing/certs/client-key.pem",
    "chars": 1704,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCznnzpbfhgAUBe\nNtyUVTdHJZ93vWx7WeuO3q8zMeP"
  },
  {
    "path": "src/testing/certs/ip-ca.pem",
    "chars": 1643,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL\nBQAwcTELMAkGA1UEBhMCVVMxEzA"
  },
  {
    "path": "src/testing/certs/ip-cert.pem",
    "chars": 5644,
    "preview": "Certificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number:\n            1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:0"
  },
  {
    "path": "src/testing/certs/ip-key.pem",
    "chars": 1700,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L\nzdVqeVQ8B1/rWnErK+VvvjH7FmV"
  },
  {
    "path": "src/testing/certs/rootCA-key.pem",
    "chars": 2484,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCyRbzelNj5Qkxg\nMxYIr4yEqLtTz/jSiX1W6uUymI+"
  },
  {
    "path": "src/testing/certs/rootCA.pem",
    "chars": 1740,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIRAIW/i8Ryvk+oZGg+/FvDpW8wDQYJKoZIhvcNAQELBQAw\ngYMxHjAcBgNVBAoTFW1rY2VydCB"
  },
  {
    "path": "src/testing/certs/server-cert.pem",
    "chars": 1598,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIEbjCCAtagAwIBAgIRAI5awGA99MSpuYlAyXOE32AwDQYJKoZIhvcNAQELBQAw\ngYMxHjAcBgNVBAoTFW1rY2VydCB"
  },
  {
    "path": "src/testing/certs/server-key.pem",
    "chars": 1704,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8fwDpIKWP9XCG\nndAiacjmZSDlxApnLr/YbP7lptN"
  },
  {
    "path": "src/testing/client/advanced.zig",
    "chars": 9773,
    "preview": "//! Tests for checkCompatibility, publishMsg, requestMsg, and message status.\n\nconst std = @import(\"std\");\nconst utils ="
  },
  {
    "path": "src/testing/client/async_patterns.zig",
    "chars": 25562,
    "preview": "//! Async Patterns Integration Tests\n//!\n//! Tests std.Io async patterns with NATS client including:\n//! - Io.Select for"
  },
  {
    "path": "src/testing/client/auth.zig",
    "chars": 6943,
    "preview": "//! Auth Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = uti"
  },
  {
    "path": "src/testing/client/autoflush.zig",
    "chars": 43704,
    "preview": "//! Autoflush Integration Tests\n//!\n//! Tests automatic buffer flushing for ALL code paths that set\n//! flush_requested,"
  },
  {
    "path": "src/testing/client/basic.zig",
    "chars": 6275,
    "preview": "//! Basic Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = ut"
  },
  {
    "path": "src/testing/client/callback.zig",
    "chars": 18778,
    "preview": "//! Callback Subscription Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = ut"
  },
  {
    "path": "src/testing/client/concurrency.zig",
    "chars": 21841,
    "preview": "//! Concurrency Tests for NATS Client\n//!\n//! Tests for race conditions, concurrent operations, and thread safety.\n//! T"
  },
  {
    "path": "src/testing/client/connection.zig",
    "chars": 13346,
    "preview": "//! Connection Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats"
  },
  {
    "path": "src/testing/client/drain.zig",
    "chars": 7788,
    "preview": "//! Drain Tests for NATS  Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = u"
  },
  {
    "path": "src/testing/client/dynamic_jwt.zig",
    "chars": 8616,
    "preview": "//! Dynamic JWT Integration Tests\n//!\n//! Generates operator/account/user JWTs at test time using\n//! the auth API, writ"
  },
  {
    "path": "src/testing/client/edge_cases.zig",
    "chars": 32038,
    "preview": "//! Edge Cases Tests for NATS  Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nat"
  },
  {
    "path": "src/testing/client/error_handling.zig",
    "chars": 7345,
    "preview": "//! Error Handling Integration Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats"
  },
  {
    "path": "src/testing/client/flush_confirmed.zig",
    "chars": 14377,
    "preview": "//! FlushConfirmed Integration Tests\n//!\n//! Tests for flushConfirmed() which sends buffered data + PING and waits\n//! f"
  },
  {
    "path": "src/testing/client/getters.zig",
    "chars": 9254,
    "preview": "//! Getters Tests for NATS Client\n//!\n//! Tests for connection info and subscription info getters.\n\nconst std = @import("
  },
  {
    "path": "src/testing/client/headers.zig",
    "chars": 29585,
    "preview": "//! Headers API Integration Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = "
  },
  {
    "path": "src/testing/client/jetstream.zig",
    "chars": 232147,
    "preview": "//! JetStream Integration Tests\n//!\n//! End-to-end tests for JetStream stream/consumer CRUD,\n//! publish with ack, and p"
  },
  {
    "path": "src/testing/client/jwt.zig",
    "chars": 6140,
    "preview": "//! JWT/Credentials Authentication Tests for NATS Client\n//!\n//! Tests JWT authentication with credentials files against"
  },
  {
    "path": "src/testing/client/micro.zig",
    "chars": 47185,
    "preview": "//! Microservices Integration Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats "
  },
  {
    "path": "src/testing/client/multi_client.zig",
    "chars": 10424,
    "preview": "//! Multi-Client Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst na"
  },
  {
    "path": "src/testing/client/multithread.zig",
    "chars": 18515,
    "preview": "//! Multi-Thread Safety Tests for NATS Client\n//!\n//! Tests that a single Client can be safely used from multiple OS\n//!"
  },
  {
    "path": "src/testing/client/nkey.zig",
    "chars": 9431,
    "preview": "//! NKey Authentication Tests for NATS Client\n//!\n//! Tests NKey (Ed25519) authentication against nats-server.\n\nconst st"
  },
  {
    "path": "src/testing/client/protocol.zig",
    "chars": 17211,
    "preview": "//! Protocol Tests for NATS Client\n//!\n//! Tests for NATS protocol handling including -ERR responses,\n//! PING/PONG keep"
  },
  {
    "path": "src/testing/client/publish.zig",
    "chars": 11982,
    "preview": "//! Publish Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = "
  },
  {
    "path": "src/testing/client/queue.zig",
    "chars": 20823,
    "preview": "//! Queue Group Tests for NATS Client\n//!\n//! Tests for queue group subscriptions and message distribution.\n\nconst std ="
  },
  {
    "path": "src/testing/client/reconnect.zig",
    "chars": 52737,
    "preview": "//! Reconnection Integration Tests\n//!\n//! Tests automatic reconnection functionality including subscription\n//! restora"
  },
  {
    "path": "src/testing/client/request_reply.zig",
    "chars": 22917,
    "preview": "//! Request-Reply Tests for NATS Client\n//!\n//! Tests for request-reply pattern.\n\nconst std = @import(\"std\");\nconst util"
  },
  {
    "path": "src/testing/client/server.zig",
    "chars": 6430,
    "preview": "//! Server Tests for NATS Client\n//!\n//! Tests for server info and protocol handling.\n\nconst std = @import(\"std\");\nconst"
  },
  {
    "path": "src/testing/client/state_notifications.zig",
    "chars": 10850,
    "preview": "//! State Notification Tests for NATS Client\n//!\n//! Tests for: LastError, discovered_servers event,\n//! draining event,"
  },
  {
    "path": "src/testing/client/stats.zig",
    "chars": 11505,
    "preview": "//! Stats Tests for NATS  Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = u"
  },
  {
    "path": "src/testing/client/stress.zig",
    "chars": 10546,
    "preview": "//! Stress Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = u"
  },
  {
    "path": "src/testing/client/stress_subs.zig",
    "chars": 42481,
    "preview": "//! Stress Tests for Subscriptions, Publishing, and Edge Cases\n//!\n//! Tests subscription counts, SidMap churn, payload "
  },
  {
    "path": "src/testing/client/subscribe.zig",
    "chars": 16933,
    "preview": "//! Subscribe Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats "
  },
  {
    "path": "src/testing/client/tests.zig",
    "chars": 3123,
    "preview": "//! Client Test Suite\n//!\n//! Re-exports all client test modules.\n\nconst std = @import(\"std\");\nconst utils = @import(\".."
  },
  {
    "path": "src/testing/client/tls.zig",
    "chars": 10504,
    "preview": "//! TLS Tests for NATS Client\n//!\n//! Tests TLS connection functionality including:\n//! - TLS connection with CA certifi"
  },
  {
    "path": "src/testing/client/wildcard.zig",
    "chars": 7968,
    "preview": "//! Wildcard Tests for NATS Client\n//!\n//! Tests for wildcard subscriptions (* and >) and pattern matching.\n\nconst std ="
  },
  {
    "path": "src/testing/configs/TestUser.creds",
    "chars": 979,
    "preview": "-----BEGIN NATS USER JWT-----\neyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJMN1dBT1hJU0tPSUZNM1QyNEhMQ09ENzJ"
  },
  {
    "path": "src/testing/configs/jwt.conf",
    "chars": 3146,
    "preview": "// Operator \"TestOperator\"\noperator: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJVWlhRWjVXR1VGSUY0S0ZKS1dF"
  },
  {
    "path": "src/testing/configs/tls.conf",
    "chars": 299,
    "preview": "port: 14226\ntls {\n    cert_file: \"src/testing/certs/server-cert.pem\"\n    key_file: \"src/testing/certs/server-key.pem\"\n  "
  },
  {
    "path": "src/testing/integration_test.zig",
    "chars": 4382,
    "preview": "//! NATS Integration Tests\n//!\n//! Tests against a nats-server instance.\n//! Run with: zig build test-integration\n\nconst"
  },
  {
    "path": "src/testing/micro_integration_test.zig",
    "chars": 1084,
    "preview": "//! Focused microservices integration tests.\n//!\n//! Run with: zig build test-integration-micro\n\nconst std = @import(\"st"
  },
  {
    "path": "src/testing/server_manager.zig",
    "chars": 8627,
    "preview": "//! NATS Test Server\n//!\n//! Self-contained nats-server for integration testing.\n//! Returns by value - each test owns i"
  },
  {
    "path": "src/testing/test_utils.zig",
    "chars": 5300,
    "preview": "//! Shared test utilities for NATS integration tests.\n\nconst std = @import(\"std\");\npub const nats = @import(\"nats\");\npub"
  },
  {
    "path": "src/testing/tls_integration_test.zig",
    "chars": 3898,
    "preview": "//! Focused JWT + TLS integration tests to debug hangs around the TLS block.\n//!\n//! Run with: zig build test-integratio"
  }
]

About this extraction

This page contains the full source code of the nats-io/nats.zig GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 161 files (1.8 MB), approximately 488.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!