[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\nenv:\n  ZIG_VERSION: 0.16.0\n  NATS_SERVER_VERSION: v2.12.7\n  NATS_CLI_VERSION: v0.3.1\n\njobs:\n  build:\n    name: Build and unit tests\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Set up Zig\n        uses: mlugg/setup-zig@v2\n        with:\n          version: ${{ env.ZIG_VERSION }}\n\n      - name: Show Zig version\n        run: zig version\n\n      - name: Check formatting\n        run: zig build fmt-check\n\n      - name: Build examples and package\n        run: zig build\n\n      - name: Run unit tests\n        run: zig build test\n\n  integration:\n    name: Integration tests (${{ matrix.mode }})\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - mode: Debug\n            args: \"\"\n          - mode: ReleaseFast\n            args: -Doptimize=ReleaseFast\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Set up Zig\n        uses: mlugg/setup-zig@v2\n        with:\n          version: ${{ env.ZIG_VERSION }}\n          cache-key: integration-${{ matrix.mode }}\n\n      - name: Install NATS tools\n        shell: bash\n        run: |\n          set -euo pipefail\n\n          nats_server_archive=\"nats-server-${NATS_SERVER_VERSION}-linux-amd64.tar.gz\"\n          nats_server_url=\"https://github.com/nats-io/nats-server/releases/download/${NATS_SERVER_VERSION}/${nats_server_archive}\"\n          nats_cli_version=\"${NATS_CLI_VERSION#v}\"\n          nats_cli_archive=\"nats-${nats_cli_version}-linux-amd64.zip\"\n          nats_cli_url=\"https://github.com/nats-io/natscli/releases/download/${NATS_CLI_VERSION}/${nats_cli_archive}\"\n\n          mkdir -p \"$HOME/.local/bin\" \"$RUNNER_TEMP/nats-server\" \"$RUNNER_TEMP/nats-cli\"\n\n          curl --fail --location --show-error --silent \"$nats_server_url\" --output \"$RUNNER_TEMP/$nats_server_archive\"\n          tar -xzf \"$RUNNER_TEMP/$nats_server_archive\" -C \"$RUNNER_TEMP/nats-server\" --strip-components=1\n          install -m 0755 \"$RUNNER_TEMP/nats-server/nats-server\" \"$HOME/.local/bin/nats-server\"\n\n          curl --fail --location --show-error --silent \"$nats_cli_url\" --output \"$RUNNER_TEMP/$nats_cli_archive\"\n          unzip -q \"$RUNNER_TEMP/$nats_cli_archive\" -d \"$RUNNER_TEMP/nats-cli\"\n          nats_cli_bin=\"$(find \"$RUNNER_TEMP/nats-cli\" -type f -name nats -print -quit)\"\n          test -n \"$nats_cli_bin\"\n          install -m 0755 \"$nats_cli_bin\" \"$HOME/.local/bin/nats\"\n\n          echo \"$HOME/.local/bin\" >> \"$GITHUB_PATH\"\n\n      - name: Show tool versions\n        run: |\n          zig version\n          nats-server --version\n          nats --version\n\n      - name: Run integration tests\n        run: zig build test-integration ${{ matrix.args }}\n"
  },
  {
    "path": ".github/workflows/claude.yml",
    "content": "name: Claude Code\n\n# GITHUB_TOKEN is neutered — all GitHub API access uses the App token instead.\npermissions: {}\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  pull_request_target:\n    types: [opened, reopened]\n\njobs:\n  claude:\n    uses: synadia-io/ai-workflows/.github/workflows/claude.yml@v2\n    with:\n      gh_app_id: ${{ vars.CLAUDE_GH_APP_ID }}\n      checkout_mode: base\n      review_focus: |\n        Additionally focus on:\n        - Zig best practices: proper error handling, comptime usage, memory management\n        - Correct use of allocators and avoiding memory leaks\n        - Adherence to Zig style conventions and idiomatic patterns\n        - NATS protocol correctness and client implementation details\n    secrets:\n      claude_oauth_token: ${{ secrets.CLAUDE_OAUTH_TOKEN }}\n      gh_app_private_key: ${{ secrets.CLAUDE_GH_APP_PRIVATE_KEY }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# The build cache\nzig-cache\n.zig-cache\nzig-out/\ntmp/\n.mcp.json\n.claude/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThanks for helping improve `nats.zig`.\n\n## Development Setup\n\nRequired tools:\n\n- Zig 0.16.0 or later\n- `nats-server` on `PATH` for integration tests\n- `nats` CLI on `PATH` for JetStream/KV cross-verification tests\n\nCommon commands:\n\n```sh\nzig build\nzig build test\nzig build fmt\nzig build fmt-check\nzig build test-integration\n```\n\nFocused integration targets are also available:\n\n```sh\nzig build test-integration-tls\nzig build test-integration-micro\n```\n\nSee `src/testing/README.md` for integration test layout and fixtures.\n\n## Before Opening a Pull Request\n\n- Run `zig build`.\n- Run `zig build test`.\n- Run the relevant integration target when changing connection,\n  authentication, TLS, JetStream, KV, or service behavior.\n- Keep examples compiling when public APIs change.\n- Update `README.md` or `src/examples/README.md` when adding or\n  changing user-facing examples.\n\n## Style\n\n- Prefer existing module patterns over new abstractions.\n- Keep ownership rules explicit in public APIs and examples.\n- Avoid unrelated refactors in bug-fix changes.\n- Format changed Zig files with `zig build fmt`.\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to the Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by the Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding any notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   Copyright 2025 The nats.zig Authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "[![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)\n[![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](LICENSE)\n![Zig](https://img.shields.io/badge/Zig-0.16.0-orange)\n\n<p align=\"center\">\n  <img src=\"logo/logo.png\">\n</p>\n\n<p align=\"center\">\n    A <a href=\"https://www.ziglang.org/\">Zig</a> client for the <a href=\"https://nats.io\">NATS messaging system</a>.\n</p>\n\n# nats.zig\n\nA [Zig](https://ziglang.org/) client for the [NATS messaging system](https://nats.io).\n\nBuilt on `std.Io`.\n\n> **Pre-1.0** - This library is under active development.\n> Core pub/sub, server-authenticated TLS, JetStream (pull + push\n> consumers), Key-Value Store, and the Micro Services API are\n> supported and covered by integration tests. Object Store and\n> mTLS are not yet implemented. The API may change before 1.0.\n\nCheck out [NATS by Example](https://natsbyexample.com) for\nrunnable, cross-client NATS examples. This repository includes\nZig ports in [doc/nats-by-example](doc/nats-by-example/README.md).\n\n## Contents\n\n- [Requirements](#requirements)\n- [Documentation](#documentation)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Examples](#examples)\n- [Memory Ownership](#memory-ownership)\n- [Publishing](#publishing)\n- [Subscribing](#subscribing)\n- [Request/Reply](#requestreply)\n- [Headers](#headers)\n- [JetStream](#jetstream)\n- [Micro Services](#micro-services)\n- [Async Patterns with std.Io](#async-patterns-with-stdio)\n- [Connections](#connections)\n- [Authentication](#authentication)\n- [Error Handling](#error-handling)\n- [Server Compatibility](#server-compatibility)\n- [Building](#building)\n- [Status](#status)\n\n## Documentation\n\n- [Examples](src/examples/README.md) - runnable examples built by `zig build`\n- [JetStream guide](doc/JetStream.md) - stream, consumer, publish,\n  pull-consume, ack, and error-handling details\n- [NATS by Example ports](doc/nats-by-example/README.md) - Zig ports of\n  selected cross-client examples from natsbyexample.com\n- [Integration tests](src/testing/README.md) - local test layout,\n  fixtures, and focused test targets\n\n## Requirements\n\n- Zig 0.16.0 or later\n- NATS server (for running examples and tests)\n\n## Installation\n\n```bash\nzig fetch --save https://github.com/nats-io/nats.zig/archive/refs/tags/v0.1.0.tar.gz\n```\n\nThen in `build.zig`:\n\n```zig\nconst nats_dep = b.dependency(\"nats\", .{\n    .target = target,\n    .optimize = optimize,\n});\n\nconst exe = b.addExecutable(.{\n    .name = \"my-app\",\n    .root_module = b.createModule(.{\n        .root_source_file = b.path(\"src/main.zig\"),\n        .target = target,\n        .optimize = optimize,\n        .imports = &.{\n            .{ .name = \"nats\", .module = nats_dep.module(\"nats\") },\n        },\n    }),\n});\nb.installArtifact(exe);\n```\n\n## Quick Start\n\nSubscriptions use callbacks - messages are dispatched automatically,\nno manual receive loop needed. There are three ways to subscribe:\n\n**`subscribe()` with a MsgHandler** - captures state, like a closure:\n\n```zig\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\n// Handler struct captures external state via pointer\nconst Handler = struct {\n    counter: *u32,\n    pub fn onMessage(self: *@This(), msg: *const nats.Message) void {\n        // Modify captured state from within the callback\n        self.counter.* += 1;\n        std.debug.print(\"[{d}] {s}\\n\", .{ self.counter.*, msg.data });\n    }\n};\n\npub fn main(init: std.process.Init) !void {\n    const client = try nats.Client.connect(\n        init.gpa,\n        init.io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    // State lives in main - handler captures a pointer to it\n    var count: u32 = 0;\n    var handler = Handler{ .counter = &count };\n    const sub = try client.subscribe(\n        \"greet.*\",\n        nats.MsgHandler.init(Handler, &handler),\n    );\n    defer sub.deinit();\n\n    try client.publish(\"greet.hello\", \"Hello, NATS!\");\n    init.io.sleep(.fromSeconds(1), .awake) catch {};\n\n    // Main sees the mutations made by the callback\n    std.debug.print(\"Total: {d}\\n\", .{count});\n}\n```\n\n**`subscribeFn()` with a plain function** - when no state is needed:\n\n```zig\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const client = try nats.Client.connect(\n        init.gpa,\n        init.io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    const sub = try client.subscribeFn(\"greet.*\", onMessage);\n    defer sub.deinit();\n\n    try client.publish(\"greet.hello\", \"Hello, NATS!\");\n    init.io.sleep(.fromSeconds(1), .awake) catch {};\n}\n\nfn onMessage(msg: *const nats.Message) void {\n    std.debug.print(\"Received: {s}\\n\", .{msg.data});\n}\n```\n\n> **Note:** Callback messages are freed automatically after your handler\n> returns. No `msg.deinit()` needed.\n\n**`subscribeSync()` for manual receive** - you control the receive loop:\n\n```zig\nconst sub = try client.subscribeSync(\"greet.*\");\ndefer sub.deinit();\n\ntry client.publish(\"greet.hello\", \"Hello, NATS!\");\n\nif (try sub.nextMsgTimeout(5000)) |msg| {\n    defer msg.deinit();\n    std.debug.print(\"Received: {s}\\n\", .{msg.data});\n}\n```\n\nSee [Examples](#examples) below for more patterns including\nrequest/reply, queue groups, headers, and async I/O.\n\n## Examples\n\nRun with `zig build run-<name>` (requires `nats-server` on localhost:4222).\n\n| Example | Run | Description |\n|---------|-----|-------------|\n| simple | `run-simple` | Basic pub/sub - connect, `subscribeSync`, publish, receive |\n| request_reply | `run-request-reply` | RPC pattern with automatic inbox handling |\n| headers | `run-headers` | Publish, receive, and parse NATS headers |\n| queue_groups | `run-queue-groups` | Load-balanced workers with `io.concurrent()` |\n| polling_loop | `run-polling-loop` | Non-blocking `tryNextMsg()` with priority scheduling |\n| select | `run-select` | Race subscription against timeout with `Io.Select` |\n| batch_receiving | `run-batch-receiving` | `nextMsgBatch()` for bulk receives, stats monitoring |\n| reconnection | `run-reconnection` | Auto-reconnect, backoff, buffer during disconnect |\n| events | `run-events` | EventHandler callbacks with external state |\n| callback | `run-callback` | `subscribe()` and `subscribeFn()` callback subscriptions |\n| request_reply_callback | `run-request-reply-callback` | Service responder via callback subscription |\n| graceful_shutdown | `run-graceful-shutdown` | `drain()` lifecycle, pre-shutdown health checks |\n| jetstream_publish | `run-jetstream-publish` | Create a stream and publish with ack confirmation |\n| jetstream_consume | `run-jetstream-consume` | Pull consumer fetch and acknowledgement |\n| jetstream_push | `run-jetstream-push` | Push consumer callback delivery |\n| jetstream_async_publish | `run-jetstream-async-publish` | Async JetStream publishing |\n| kv | `run-kv` | Key-Value bucket operations |\n| kv_watch | `run-kv-watch` | Watch Key-Value updates |\n| micro_echo | `run-micro-echo` | NATS service API echo service |\n\nSource: `src/examples/`\n\n### NATS by Example\n\nPorts of [natsbyexample.com](https://natsbyexample.com) examples.\n\n| Example | Run |\n|---------|-----|\n| [Pub-Sub](doc/nats-by-example/messaging/pub-sub.zig) | `run-nbe-messaging-pub-sub` |\n| [Request-Reply](doc/nats-by-example/messaging/request-reply.zig) | `run-nbe-messaging-request-reply` |\n| [JSON](doc/nats-by-example/messaging/json.zig) | `run-nbe-messaging-json` |\n| [Concurrent](doc/nats-by-example/messaging/concurrent.zig) | `run-nbe-messaging-concurrent` |\n| [Multiple Subscriptions](doc/nats-by-example/messaging/iterating-multiple-subscriptions.zig) | `run-nbe-messaging-iterating-multiple-subscriptions` |\n| [NKeys & JWTs](doc/nats-by-example/auth/nkeys-jwts.zig) | `run-nbe-auth-nkeys-jwts` |\n\n---\n\n## Memory Ownership\n\nMessages returned by `nextMsg()`, `tryNextMsg()`, and `nextMsgTimeout()` are **owned**.\nYou **must** call `deinit()` to free memory:\n\n```zig\nconst msg = try sub.nextMsg();\ndefer msg.deinit();\n\n// Access message fields (valid until deinit)\nstd.debug.print(\"Subject: {s}\\n\", .{msg.subject});\nstd.debug.print(\"Data: {s}\\n\", .{msg.data});\n```\n\n### Message Structure\n\n```zig\npub const Message = struct {\n    subject: []const u8,       // Message subject\n    sid: u64,                  // Subscription ID\n    reply_to: ?[]const u8,     // Reply-to address (for request/reply)\n    data: []const u8,          // Message payload\n    headers: ?[]const u8,      // Raw NATS headers (use headers.parse())\n};\n```\n\n---\n\n## Publishing\n\n### Auto-Flush Behavior\n\nMessages are buffered and automatically flushed to the network:\n\n```zig\n// Write to buffer - auto-flushed by io_task\ntry client.publish(\"events.click\", \"button1\");\ntry client.publish(\"events.click\", \"button2\");\ntry client.publish(\"events.click\", \"button3\");\n```\n\n**How it works:**\n- `publish()` encodes into a lock-free ring buffer (no mutex)\n- The io_task background thread drains the ring to the socket\n- Multiple rapid publishes are naturally batched for efficiency\n- Works at full speed even in tight loops (100K+ msgs/sec)\n- Ring size: 2MB minimum (auto-sized, power-of-2)\n\n### Confirmed Flush\n\nFor scenarios where you need confirmation that the server received your messages,\nuse `flush()`. It sends PING and waits for PONG (matches Go/C client behavior):\n\n```zig\ntry client.publish(\"events.important\", data);\ntry client.flush(5_000_000_000); // 5 second timeout\n// Server has confirmed receipt of all buffered messages\n```\n\n**When to use:**\n- Critical messages where delivery confirmation matters\n- Before shutting down to ensure all messages were sent\n- Synchronization points in your application\n\n### When Does Data Hit the Network?\n\n| Method | Network I/O |\n|--------|-------------|\n| `publish()` | Auto-flushed |\n| `publishRequest()` | Auto-flushed |\n| `publishWithHeaders()` | Auto-flushed |\n| `publishRequestWithHeaders()` | Auto-flushed |\n| `flushBuffer()` | Yes - sends buffer to socket immediately (used internally) |\n| `flush()` | Yes - sends buffer + PING, waits for PONG |\n| `request()` | Yes - flushes, waits for response |\n| `requestWithHeaders()` | Yes - flushes, waits for response |\n\n---\n\n## Subscribing\n\n### Subscribe (Callback)\n\nMessages are dispatched automatically via callback.\n\n**MsgHandler pattern** (handler struct with state):\n\n```zig\nconst MyHandler = struct {\n    counter: *u32,\n    pub fn onMessage(self: *@This(), msg: *const nats.Message) void {\n        self.counter.* += 1;\n        std.debug.print(\"got: {s}\\n\", .{msg.data});\n    }\n};\n\nvar count: u32 = 0;\nvar handler = MyHandler{ .counter = &count };\nconst sub = try client.subscribe(\n    \"events.>\",\n    nats.MsgHandler.init(MyHandler, &handler),\n);\ndefer sub.deinit();\n```\n\n**Plain function** (no state needed):\n\n```zig\nfn onAlert(msg: *const nats.Message) void {\n    std.debug.print(\"alert: {s}\\n\", .{msg.data});\n}\n\nconst sub = try client.subscribeFn(\n    \"alerts.>\",\n    onAlert,\n);\ndefer sub.deinit();\n```\n\n**Queue group** (load balancing - only one subscriber in the group\nreceives each message):\n\n```zig\nconst sub = try client.queueSubscribe(\n    \"tasks.*\",\n    \"workers\",\n    handler,\n);\n```\n\n| Method | Handler | Queue Group |\n|--------|---------|-------------|\n| `subscribe` | MsgHandler | No |\n| `queueSubscribe` | MsgHandler | Yes |\n| `subscribeFn` | plain fn | No |\n| `queueSubscribeFn` | plain fn | Yes |\n\n> **Warning:** Do not call `nextMsg()`, `tryNextMsg()`, or other receive methods on\n> a callback subscription. They assert `mode == .manual` and will trap.\n\n### Subscribe Sync (Manual Receive)\n\nFor manual control over message receiving, use `subscribeSync()`. You call\n`nextMsg()`, `tryNextMsg()`, or `nextMsgBatch()` yourself:\n\n```zig\nconst sub = try client.subscribeSync(\"events.>\");\ndefer sub.deinit();\n\n// Wildcards:\n// * matches single token: \"events.*\" matches \"events.click\" but not \"events.user.login\"\n// > matches remainder: \"events.>\" matches \"events.click\" and \"events.user.login\"\n\nwhile (true) {\n    const msg = try sub.nextMsg();\n    defer msg.deinit();\n    std.debug.print(\"{s}: {s}\\n\", .{ msg.subject, msg.data });\n}\n```\n\n**Queue group** variant:\n\n```zig\nconst sub1 = try client.queueSubscribeSync(\"tasks.*\", \"workers\");\nconst sub2 = try client.queueSubscribeSync(\"tasks.*\", \"workers\");\n// Message goes to either sub1 OR sub2, not both\n```\n\n### Subscription Registration\n\nWhen subscribing, the SUB command is buffered and sent to the server asynchronously.\nIf you need to ensure the subscription is fully registered before publishing (especially\nwith separate publisher/subscriber clients), call `flush()` after subscribing:\n\n```zig\nconst sub = try client.subscribeSync(\"events.>\");\ndefer sub.deinit();\n\n// Ensure subscription is registered on server before publishing\ntry client.flush(5_000_000_000);  // 5 second timeout\n\n// Now safe to publish from another client\n```\n\n**When is this needed?**\n- Multi-client scenarios where one client publishes and another subscribes\n- Tests that need deterministic message delivery\n- Any situation requiring subscription to be active before first publish\n\n**When is this NOT needed?**\n- Single client publishing to itself (same client does subscribe + publish)\n- Using `request()` which handles synchronization internally\n\n### Unsubscribing\n\n**Zig deinit pattern (recommended):** Use `defer sub.deinit()` - it calls `unsubscribe()`\ninternally and handles errors gracefully:\n\n```zig\nconst sub = try client.subscribeSync(\"events.>\");\ndefer sub.deinit();  // Unsubscribes + frees memory\n\n// ... use subscription ...\n```\n\n**Explicit unsubscribe:** For users who need to check if the server\nreceived the UNSUB command, call `unsubscribe()` directly:\n\n```zig\nconst sub = try client.subscribeSync(\"events.>\");\n\n// ... use subscription ...\n\n// Explicit unsubscribe with error checking\nsub.unsubscribe() catch |err| {\n    std.log.warn(\"Unsubscribe failed: {}\", .{err});\n};\nsub.deinit();  // Still needed to free memory\n```\n\n| Method | Returns | Purpose |\n|--------|---------|---------|\n| `sub.unsubscribe()` | `!void` | Sends UNSUB to server, removes from tracking |\n| `sub.deinit()` | `void` | Calls unsubscribe (if needed) + frees memory |\n\n**Note:** `unsubscribe()` is idempotent - calling it multiple times is safe.\n`deinit()` always succeeds (errors are logged, not returned) making it safe for\n`defer`.\n\n### Receiving Messages\n\n**Blocking:** `nextMsg()` blocks until a message arrives. For use in dedicated receiver loops:\n\n```zig\nwhile (true) {\n    const msg = try sub.nextMsg();\n    defer msg.deinit();  // ALWAYS defer deinit\n\n    std.debug.print(\"Subject: {s}\\n\", .{msg.subject});\n    std.debug.print(\"Data: {s}\\n\", .{msg.data});\n    if (msg.reply_to) |rt| {\n        std.debug.print(\"Reply-to: {s}\\n\", .{rt});\n    }\n}\n```\n\n**Non-Blocking:** `tryNextMsg()` returns immediately. Use for event loops or polling:\n\n```zig\n// Process all available messages without waiting\nwhile (sub.tryNextMsg()) |msg| {\n    defer msg.deinit();\n    processMessage(msg);\n}\n// No more messages - continue with other work\n```\n\n**With Timeout:** `nextMsgTimeout()` returns `null` on timeout:\n\n```zig\nif (try sub.nextMsgTimeout(5000)) |msg| {\n    defer msg.deinit();\n    std.debug.print(\"Got: {s}\\n\", .{msg.data});\n} else {\n    std.debug.print(\"No message within 5 seconds\\n\", .{});\n}\n```\n\n**Batch:** `nextMsgBatch()` / `tryNextMsgBatch()` receive multiple messages at once:\n\n```zig\nvar buf: [64]Message = undefined;\n\n// Blocking - waits for at least 1 message, returns up to 64\nconst count = try sub.nextMsgBatch(io, &buf);\nfor (buf[0..count]) |*msg| {\n    defer msg.deinit();\n    processMessage(msg.*);\n}\n\n// Non-blocking - returns immediately with available messages\nconst available = sub.tryNextMsgBatch(&buf);\nfor (buf[0..available]) |*msg| {\n    defer msg.deinit();\n    processMessage(msg.*);\n}\n```\n\n### Receive Method Comparison\n\n| Method | Blocks | Returns | Use Case |\n|--------|--------|---------|----------|\n| `nextMsg()` | Yes | `!Message` | Dedicated receiver loop |\n| `tryNextMsg()` | No | `?Message` | Polling, event loops |\n| `nextMsgTimeout()` | Yes (bounded) | `!?Message` | Request/reply, timed waits |\n| `nextMsgBatch()` | Yes | `!usize` | High-throughput batching |\n| `tryNextMsgBatch()` | No | `usize` | Drain queue without blocking |\n\n### Subscription Control\n\n**Auto-Unsubscribe:** Automatically unsubscribe after receiving a specific number of messages:\n\n```zig\nconst sub = try client.subscribeSync(\"events.>\");\n\n// Auto-unsubscribe after 10 messages\ntry sub.autoUnsubscribe(10);\n\n// Process messages (subscription closes after 10th)\nwhile (sub.isValid()) {\n    if (sub.tryNextMsg()) |msg| {\n        defer msg.deinit();\n        processMessage(msg);\n    }\n}\n```\n\n**Statistics:**\n\n```zig\n// Messages waiting in queue\nconst pending = sub.pending();\n\n// Messages delivered (only tracked if autoUnsubscribe was called)\nconst delivered = sub.delivered();\n\n// Check if subscription is still valid\nif (sub.isValid()) {\n    // Can still receive messages\n}\n```\n\n**Per-Subscription Drain:** Drain a single subscription while keeping others active:\n\n```zig\ntry sub.drain();\n// Subscription stops receiving new messages\n// Already-queued messages can still be consumed\n```\n\n---\n\n## Request/Reply\n\n### Using `request()` (Recommended)\n\nThe simplest way - handles inbox creation, subscription, and timeout:\n\n```zig\n// Returns null on timeout\nif (try client.request(\"math.double\", \"21\", 5000)) |reply| {\n    defer reply.deinit();\n    std.debug.print(\"Result: {s}\\n\", .{reply.data});  // \"42\"\n} else {\n    std.debug.print(\"Service did not respond\\n\", .{});\n}\n```\n\n### Building a Service\n\nRespond to requests by publishing to the `reply_to` subject:\n\n```zig\nconst service = try client.subscribeSync(\"math.double\");\ndefer service.deinit();\n\nwhile (true) {\n    const req = try service.nextMsg();\n    defer req.deinit();\n\n    // Parse request\n    const num = std.fmt.parseInt(i32, req.data, 10) catch 0;\n\n    // Build response\n    var buf: [32]u8 = undefined;\n    const result = std.fmt.bufPrint(&buf, \"{d}\", .{num * 2}) catch \"error\";\n\n    // Send reply (auto-flushed)\n    if (req.reply_to) |reply_to| {\n        try client.publish(reply_to, result);\n    }\n}\n```\n\n### Responding with `msg.respond()`\n\nConvenience method for the request/reply pattern:\n\n```zig\nconst msg = try sub.nextMsg();\ndefer msg.deinit();\n\n// Respond using the message's reply_to (auto-flushed)\nmsg.respond(client, \"response data\") catch |err| {\n    if (err == error.NoReplyTo) {\n        // Message had no reply_to address\n    }\n};\n```\n\n### Manual Request/Reply Pattern\n\nFor more control, manage the inbox yourself:\n\n```zig\n// Create inbox subscription\nconst inbox = try client.newInbox();\ndefer allocator.free(inbox);\n\nconst reply_sub = try client.subscribeSync(inbox);\ndefer reply_sub.deinit();\n\n// Send request with reply-to (auto-flushed)\ntry client.publishRequest(\"service\", inbox, \"request data\");\n\n// Wait for response with timeout\nif (try reply_sub.nextMsgTimeout(5000)) |reply| {\n    defer reply.deinit();\n    // Process reply\n} else {\n    // Timeout\n}\n```\n\n### Check No-Responders Status\n\nDetect when a request has no available responders (status 503):\n\n```zig\nconst reply = try client.request(\"service.endpoint\", \"data\", 1000);\nif (reply) |msg| {\n    defer msg.deinit();\n\n    if (msg.isNoResponders()) {\n        // No service available to handle request\n        std.debug.print(\"No responders for request\\n\", .{});\n    } else {\n        // Normal response - check status code if needed\n        if (msg.status()) |status| {\n            std.debug.print(\"Status: {d}\\n\", .{status});\n        }\n    }\n}\n```\n\n### Implementation Note: Response Multiplexer\n\n`request()`, `requestMsg()`, and `requestWithHeaders()` use a shared\n*response multiplexer* internally - the same pattern as the Go\nclient's `respMux`. The first call lazily subscribes once to a\nwildcard inbox `_INBOX.<connNUID>.*` and does a PING/PONG round-trip\nto confirm server registration. Every subsequent call reuses that\nsingle subscription and just registers a per-request waiter in a\ntoken-keyed map. The dispatcher routes incoming replies back to the\nmatching waiter.\n\nBenefits over the naive per-request subscription approach:\n\n- **No SUB/UNSUB protocol churn** - the server (and any clustered\n  gateways/leaf nodes) sees one wildcard subscription per connection\n  instead of one SUB+UNSUB pair per request.\n- **No per-request allocations** for the subscription struct, queue\n  buffer, or owned subject string.\n- **No latency floor** - the old implementation burned a hardcoded\n  5ms sleep on every request to give the server time to process the\n  per-request SUB. The muxer pays one PING/PONG round-trip *once* on\n  the first request and amortizes it to zero across subsequent calls.\n- **Better concurrent throughput** - relevant for JetStream and KV\n  workloads, which are RPC-heavy internally.\n\n---\n\n## Headers\n\nNATS headers allow attaching metadata to messages (similar to HTTP headers).\nHeaders are supported with NATS server 2.2+.\n\n### Publishing with Headers\n\n```zig\nconst nats = @import(\"nats\");\nconst headers = nats.protocol.headers;\n\n// Single header\nconst hdrs = [_]headers.Entry{\n    .{ .key = \"X-Request-Id\", .value = \"req-123\" },\n};\ntry client.publishWithHeaders(\"events.user\", &hdrs, \"user logged in\");\n\n// Multiple headers\nconst multi_hdrs = [_]headers.Entry{\n    .{ .key = \"Content-Type\", .value = \"application/json\" },\n    .{ .key = \"X-Correlation-Id\", .value = \"corr-456\" },\n    .{ .key = \"X-Timestamp\", .value = \"2026-01-21T10:30:00Z\" },\n};\ntry client.publishWithHeaders(\"events.order\", &multi_hdrs, order_json);\n```\n\n### Publish with Headers and Reply-To\n\n```zig\nconst hdrs = [_]headers.Entry{\n    .{ .key = \"X-Request-Id\", .value = \"req-789\" },\n};\ntry client.publishRequestWithHeaders(\"service.echo\", \"my.inbox\", &hdrs, \"ping\");\n```\n\n### Request/Reply with Headers\n\n```zig\nconst hdrs = [_]headers.Entry{\n    .{ .key = headers.HeaderName.msg_id, .value = \"unique-001\" },\n};\n\nif (try client.requestWithHeaders(\"service.api\", &hdrs, \"data\", 5000)) |reply| {\n    defer reply.deinit();\n    std.debug.print(\"Response: {s}\\n\", .{reply.data});\n} else {\n    std.debug.print(\"Timeout\\n\", .{});\n}\n```\n\n### Receiving and Parsing Headers\n\n```zig\nconst msg = try sub.nextMsg();\ndefer msg.deinit();\n\nif (msg.headers) |raw_headers| {\n    var parsed = headers.parse(allocator, raw_headers);\n    defer parsed.deinit();  // MUST call deinit!\n\n    if (parsed.err == null) {\n        // Iterate all headers\n        for (parsed.items()) |entry| {\n            std.debug.print(\"{s}: {s}\\n\", .{ entry.key, entry.value });\n        }\n\n        // Lookup by name (case-insensitive)\n        if (parsed.get(\"X-Request-Id\")) |req_id| {\n            std.debug.print(\"Request ID: {s}\\n\", .{req_id});\n        }\n\n        // Check for no-responders status\n        if (parsed.isNoResponders()) {\n            std.debug.print(\"No responders available\\n\", .{});\n        }\n    }\n}\n```\n\n**Important**: `ParseResult` owns its data (copies strings to heap). This means\nparsed headers remain valid even after `msg.deinit()` is called. Always call\n`parsed.deinit()` to free memory.\n\n### Well-Known Header Names\n\nUse constants from `headers.HeaderName` for JetStream and NATS features:\n\n```zig\nconst hdrs = [_]headers.Entry{\n    // JetStream message deduplication\n    .{ .key = headers.HeaderName.msg_id, .value = \"unique-msg-001\" },\n    // Expected stream for publish\n    .{ .key = headers.HeaderName.expected_stream, .value = \"ORDERS\" },\n};\n```\n\n| Constant | Header Name | Purpose |\n|----------|-------------|---------|\n| `msg_id` | `Nats-Msg-Id` | JetStream deduplication |\n| `expected_stream` | `Nats-Expected-Stream` | Verify target stream |\n| `expected_last_msg_id` | `Nats-Expected-Last-Msg-Id` | Optimistic concurrency |\n| `expected_last_seq` | `Nats-Expected-Last-Sequence` | Sequence verification |\n\n### HeaderMap Builder\n\nFor programmatic header construction:\n\n```zig\nconst nats = @import(\"nats\");\n\nvar headers = nats.Client.HeaderMap.init(allocator);\ndefer headers.deinit();\n\n// Set headers (replaces existing)\ntry headers.set(\"Content-Type\", \"application/json\");\ntry headers.set(\"X-Request-Id\", \"req-123\");\n\n// Add headers (allows multiple values for same key)\ntry headers.add(\"X-Tag\", \"important\");\ntry headers.add(\"X-Tag\", \"urgent\");\n\n// Get values\nif (headers.get(\"Content-Type\")) |ct| {\n    std.debug.print(\"Content-Type: {s}\\n\", .{ct});\n}\n\n// Get all values for a key\nif (try headers.getAll(\"X-Tag\")) |tags| {\n    defer allocator.free(tags);\n    for (tags) |tag| {\n        std.debug.print(\"Tag: {s}\\n\", .{tag});\n    }\n}\n\n// Delete headers\nheaders.delete(\"X-Tag\");\n\n// Publish with HeaderMap (auto-flushed)\ntry client.publishWithHeaderMap(\"subject\", &headers, \"payload\");\n```\n\n### Header Notes\n\n- Header values can contain colons (URLs, timestamps work fine)\n- Case-insensitive lookup for header names\n- Header names must be non-empty and cannot contain whitespace, control\n  characters, DEL, or `:`. Header values cannot contain control characters\n  or DEL. Invalid headers return `error.InvalidHeader`.\n- On parse error: `items()` returns empty slice, `get()` returns null\n\n---\n\n## JetStream\n\nJetStream is NATS' persistence and streaming layer. It provides\nat-least-once delivery, message replay, and durable consumers --\nall through a JSON request/reply API on `$JS.API.*` subjects.\n\nFor runnable examples, see `src/examples/jetstream_*.zig`,\n`src/examples/kv*.zig`, the focused [JetStream guide](doc/JetStream.md),\nand the feature coverage summary below.\n\n### JetStream Example\n\n```zig\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\n// Create a JetStream context (stack-allocated, no heap)\nvar js = try js_mod.JetStream.init(client, .{});\n\n// Create a stream\nvar stream = try js.createStream(.{\n    .name = \"ORDERS\",\n    .subjects = &.{\"orders.>\"},\n    .storage = .memory,\n});\ndefer stream.deinit();\n\n// Publish with ack confirmation\nvar ack = try js.publish(\"orders.new\", \"order-1\");\ndefer ack.deinit();\n// ack.value.seq, ack.value.stream\n\n// Create a pull consumer and fetch messages\nvar cons = try js.createConsumer(\"ORDERS\", .{\n    .name = \"processor\",\n    .durable_name = \"processor\",\n    .ack_policy = .explicit,\n});\ndefer cons.deinit();\n\nvar pull = js_mod.PullSubscription{\n    .js = &js,\n    .stream = \"ORDERS\",\n};\ntry pull.setConsumer(\"processor\");\nvar result = try pull.fetch(.{\n    .max_messages = 10,\n    .timeout_ms = 5000,\n});\ndefer result.deinit();\n\nfor (result.messages) |*msg| {\n    try msg.ack();\n}\n```\n\n### Key-Value Store Example\n\n```zig\nconst js_mod = nats.jetstream;\n\nvar js = try js_mod.JetStream.init(client, .{});\n\n// Create a KV bucket\nvar kv = try js.createKeyValue(.{\n    .bucket = \"config\",\n    .storage = .memory,\n    .history = 5,\n});\n\n// Put and get\nconst rev = try kv.put(\"db.host\", \"localhost:5432\");\nvar entry = (try kv.get(\"db.host\")).?;\ndefer entry.deinit();\n// entry.revision == rev, entry.operation == .put\n\n// Optimistic concurrency\nconst rev2 = try kv.update(\"db.host\", \"newhost:5432\", rev);\n\n// Create only if key doesn't exist\n_ = try kv.create(\"db.port\", \"5432\");\n_ = kv.create(\"db.port\", \"9999\") catch |err| {\n    // err == error.ApiError (key exists)\n};\n\n// List all keys\nconst keys = try kv.keys(allocator);\ndefer {\n    for (keys) |k| allocator.free(k);\n    allocator.free(keys);\n}\n\n// Watch for real-time updates\nvar watcher = try kv.watchAll();\ndefer watcher.deinit();\nwhile (try watcher.next(5000)) |*update| {\n    defer update.deinit();\n    // update.key, update.revision, update.operation\n}\n```\n\nBucket names and keys are validated client-side before API requests are sent.\nBucket names may not be empty, exceed 64 bytes, or contain wildcards,\nseparators, whitespace, control characters, or DEL. KV keys must be non-empty\nNATS subject tokens without wildcards; watch patterns may use `*` and a\nterminal `>`.\n\n### Supported JetStream Features\n\n| Area | Supported APIs | Notes |\n|------|----------------|-------|\n| Streams | `createStream()`, `updateStream()`, `deleteStream()`, `streamInfo()`, `purgeStream()`, `purgeStreamSubject()` | Includes stream listing and subject-filtered purge. |\n| Consumers | `createConsumer()`, `updateConsumer()`, `deleteConsumer()`, `consumerInfo()` | Pull, push, and ordered consumer workflows. |\n| Listing | `streamNames()`, `streams()`, `consumerNames()`, `consumers()`, `accountInfo()` | Paginated listing APIs are available for streams and consumers. |\n| Publishing | `publish()`, `publishWithOpts()`, `publishMsg()` | Publish acknowledgments, deduplication headers, optimistic concurrency, and publish TTL. |\n| Pull Consumers | `fetch()`, `fetchNoWait()`, `fetchBytes()`, `next()`, `messages()`, `consume()` | Batch fetch, single-message fetch, continuous pull iteration, callbacks, heartbeat monitoring, and ordered delivery. |\n| Push Consumers | `createPushConsumer()`, `PushSubscription.consume()` | Callback delivery uses `JsMsgHandler`; callback messages are borrowed and valid only during the callback. |\n| Acknowledgment | `ack()`, `doubleAck()`, `nak()`, `nakWithDelay()`, `inProgress()`, `term()`, `termWithReason()` | Metadata can be parsed from JetStream reply subjects. |\n| 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. |\n| Error Handling | `lastApiError()` | JetStream API errors expose server status, error code, and description. |\n| Domains | `try JetStream.init(client, .{ .domain = ... })` | Supports multi-tenant JetStream domains. |\n\n### Current Limitations\n\n| Feature | Status |\n|---------|--------|\n| Object Store | Not implemented |\n\n---\n\n## Micro Services\n\nThe `nats.micro` module implements the NATS service API for\ndiscoverable request/reply services. Services automatically register\nmonitoring endpoints under `$SRV.PING`, `$SRV.INFO`, and `$SRV.STATS`\nincluding name- and id-specific variants.\n\n```zig\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Echo = struct {\n    pub fn onRequest(_: *@This(), req: *nats.micro.Request) void {\n        req.respond(req.data()) catch {};\n    }\n};\n\npub fn main(init: std.process.Init) !void {\n    const client = try nats.Client.connect(\n        init.gpa,\n        init.io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    var echo = Echo{};\n    const service = try nats.micro.addService(client, .{\n        .name = \"echo\",\n        .version = \"1.0.0\",\n        .description = \"Echo service\",\n        .endpoint = .{\n            .subject = \"echo\",\n            .handler = nats.micro.Handler.init(Echo, &echo),\n        },\n    });\n    defer service.deinit();\n\n    while (true) {\n        init.io.sleep(.fromSeconds(1), .awake) catch {};\n    }\n}\n```\n\nHandlers can be comptime vtable handlers with `Handler.init(T, &value)`\nor plain functions with `Handler.fromFn(fn)`. A request handler can\nread `req.subject()`, `req.data()`, `req.headers()`, and reply with\n`req.respond()`, `req.respondJson()`, or `req.respondError()`.\n\nServices support endpoint groups, queue groups, metadata, stats reset,\nand graceful stop/drain:\n\n```zig\nvar api = try service.addGroup(\"api\");\n_ = try api.addEndpoint(.{\n    .subject = \"v1.echo\",\n    .handler = nats.micro.Handler.init(Echo, &echo),\n});\n\ntry service.stop(null);\ntry service.waitStopped();\n```\n\nRun the complete example with:\n\n```bash\nzig build run-micro-echo\n```\n\n---\n\n## Async Patterns with std.Io\n\n### Cancellation Pattern\n\nAlways defer cancel when using `io.async()`:\n\n```zig\nvar future = io.async(someFn, .{args});\ndefer future.cancel(io) catch {};  // defer cancel\nconst result = try future.await(io);\n```\n\n### Racing Operations with `Io.Select`\n\nWait for the first of multiple operations to complete:\n\n```zig\nfn sleepMs(io_ctx: std.Io, ms: i64) void {\n    io_ctx.sleep(.fromMilliseconds(ms), .awake) catch {};\n}\n\nconst Sel = std.Io.Select(union(enum) {\n    message: anyerror!nats.Message,\n    timeout: void,\n});\nvar buf: [2]Sel.Union = undefined;\nvar sel = Sel.init(io, &buf);\nsel.async(.message, nats.Client.Sub.nextMsg, .{sub});\nsel.async(.timeout, sleepMs, .{ io, 5000 });\n\nconst result = sel.await() catch |err| {\n    while (sel.cancel()) |remaining| {\n        switch (remaining) {\n            .message => |r| {\n                if (r) |m| m.deinit() else |_| {}\n            },\n            .timeout => {},\n        }\n    }\n    return err;\n};\nwhile (sel.cancel()) |remaining| {\n    switch (remaining) {\n        .message => |r| {\n            if (r) |m| m.deinit() else |_| {}\n        },\n        .timeout => {},\n    }\n}\n\nswitch (result) {\n    .message => |msg_result| {\n        const msg = try msg_result;\n        defer msg.deinit();\n        std.debug.print(\"Received: {s}\\n\", .{msg.data});\n    },\n    .timeout => {\n        std.debug.print(\"Timeout!\\n\", .{});\n    },\n}\n```\n\n### Async Message Receive with Ownership\n\nWhen using `io.async()` to receive messages, handle ownership carefully:\n\n```zig\nvar future = io.async(nats.Client.Sub.nextMsg, .{sub});\ndefer if (future.cancel(io)) |m| m.deinit() else |_| {};\n\nif (future.await(io)) |msg| {\n    // Message ownership transferred - use it here\n    // Do not add defer msg.deinit() - outer defer handles cleanup\n    std.debug.print(\"Got: {s}\\n\", .{msg.data});\n    return;  // outer defer runs, cancel() returns null\n} else |err| {\n    std.debug.print(\"Error: {}\\n\", .{err});\n}\n```\n\n**Key points:**\n- After `await()` succeeds, `cancel()` returns null (message already consumed)\n- If function exits before `await()`, `cancel()` returns the pending message\n- Adding a second `defer msg.deinit()` inside the if-block would cause double-free\n\n### Io.Queue for Cross-Thread Communication\n\nUse `Io.Queue(T)` for producer/consumer patterns across threads:\n\n```zig\nconst WorkResult = struct {\n    worker_id: u8,\n    msg: nats.Message,\n\n    fn deinit(self: WorkResult) void {\n        self.msg.deinit();\n    }\n};\n\n// Fixed-size buffer backing the queue\nvar queue_buf: [32]WorkResult = undefined;\nvar queue: Io.Queue(WorkResult) = .init(&queue_buf);\n\n// Worker thread: push results\nfn worker(io: Io, sub: *Sub, q: *Io.Queue(WorkResult)) void {\n    while (true) {\n        const msg = sub.nextMsg() catch return;\n        q.putOne(io, .{ .worker_id = 1, .msg = msg }) catch return;\n    }\n}\n\n// Main thread: consume results\nwhile (true) {\n    const result = queue.getOne(io) catch break;\n    defer result.deinit();\n    std.debug.print(\"Worker {d}: {s}\\n\", .{ result.worker_id, result.msg.data });\n}\n```\n\n**Use cases:**\n- Load-balanced workers reporting to main thread\n- Aggregating results from `io.concurrent()` tasks\n- Decoupling message producers from consumers\n\n---\n\n## Connections\n\n### Connection Options\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    // Identity\n    .name = \"my-app\",              // Client name (visible in server logs)\n\n    // Buffers\n    .reader_buffer_size = 1024 * 1024 + 8 * 1024, // Read buffer default\n    .writer_buffer_size = 1024 * 1024 + 8 * 1024, // Write buffer default\n    .sub_queue_size = 8192,            // Per-subscription queue size\n    .tcp_rcvbuf = 1024 * 1024,         // TCP receive buffer hint default\n\n    // Timeouts\n    .connect_timeout_ns = 5_000_000_000,  // 5 second connect timeout\n\n    // Reconnection\n    .reconnect = true,             // Enable auto-reconnect\n    .max_reconnect_attempts = 60,  // Max attempts (0 = infinite)\n    .reconnect_wait_ms = 2000,     // Initial backoff\n\n    // Keepalive\n    .ping_interval_ms = 120_000,   // PING every 2 minutes\n    .max_pings_outstanding = 2,    // Disconnect after 2 missed PONGs\n\n    // Inbox prefix (for request/reply)\n    .inbox_prefix = \"_INBOX\",      // Custom inbox prefix\n\n    // Connection behavior\n    .retry_on_failed_connect = false,     // Retry on initial failure\n    .no_randomize = false,                // Don't randomize server order\n    .ignore_discovered_servers = false,   // Only use explicit servers\n    .drain_timeout_ms = 30_000,           // Default drain timeout\n    .flush_timeout_ms = 10_000,           // Default flush timeout\n});\n```\n\n### Event Callbacks\n\nHandle connection lifecycle events using the `EventHandler` pattern - a type-safe,\nZig-idiomatic approach similar to `std.mem.Allocator`.\n\n```zig\nconst MyHandler = struct {\n    pub fn onConnect(self: *@This()) void {\n        _ = self;\n        std.log.info(\"Connected!\", .{});\n    }\n\n    pub fn onDisconnect(self: *@This(), err: ?anyerror) void {\n        _ = self;\n        std.log.warn(\"Disconnected: {any}\", .{err});\n    }\n\n    pub fn onReconnect(self: *@This()) void {\n        _ = self;\n        std.log.info(\"Reconnected!\", .{});\n    }\n};\n\nvar handler = MyHandler{};\nconst client = try nats.Client.connect(allocator, io, url, .{\n    .event_handler = nats.EventHandler.init(MyHandler, &handler),\n});\n```\n\n**Accessing External State:** Handlers can reference external application state:\n\n```zig\nconst AppState = struct {\n    is_online: bool = false,\n    reconnect_count: u32 = 0,\n    last_error: ?anyerror = null,\n};\n\nconst MyHandler = struct {\n    app: *AppState,\n\n    pub fn onConnect(self: *@This()) void {\n        self.app.is_online = true;\n    }\n\n    pub fn onDisconnect(self: *@This(), err: ?anyerror) void {\n        self.app.is_online = false;\n        self.app.last_error = err;\n    }\n\n    pub fn onReconnect(self: *@This()) void {\n        self.app.is_online = true;\n        self.app.reconnect_count += 1;\n    }\n};\n\nvar app_state = AppState{};\nvar handler = MyHandler{ .app = &app_state };\n\nconst client = try nats.Client.connect(allocator, io, url, .{\n    .event_handler = nats.EventHandler.init(MyHandler, &handler),\n});\n```\n\n| Callback | When Fired |\n|----------|------------|\n| `onConnect()` | Initial connection established |\n| `onDisconnect(?anyerror)` | Connection lost (error or clean close) |\n| `onReconnect()` | Reconnection successful |\n| `onClose()` | Connection permanently closed |\n| `onError(anyerror)` | Async error (slow consumer, etc.) |\n| `onLameDuck()` | Server entering shutdown mode |\n| `onDiscoveredServers(u8)` | New server discovered in cluster |\n| `onDraining()` | Drain process started |\n| `onSubscriptionComplete(u64)` | Subscription drain finished (receives SID) |\n\nAll callbacks are **optional** - only implement the ones you need.\n\n### Connection State\n\n```zig\nconst State = @import(\"nats\").connection.State;\n\nconst status = client.status();\nswitch (status) {\n    .connected => std.debug.print(\"Connected\\n\", .{}),\n    .reconnecting => std.debug.print(\"Reconnecting...\\n\", .{}),\n    .draining => std.debug.print(\"Draining\\n\", .{}),\n    .closed => std.debug.print(\"Closed\\n\", .{}),\n    else => {},\n}\n\n// Convenience checks\nif (client.isClosed()) { /* permanently closed */ }\nif (client.isDraining()) { /* draining subscriptions */ }\nif (client.isReconnecting()) { /* attempting reconnect */ }\n\n// Subscription count\nconst num_subs = client.numSubscriptions();\n```\n\n### Connection Information\n\n```zig\n// Server details (from INFO response)\nif (client.connectedUrl()) |url| {\n    std.debug.print(\"Connected to: {s}\\n\", .{url});\n}\nif (client.connectedServerId()) |id| {\n    std.debug.print(\"Server ID: {s}\\n\", .{id});\n}\nif (client.connectedServerName()) |name| {\n    std.debug.print(\"Server name: {s}\\n\", .{name});\n}\nif (client.connectedServerVersion()) |version| {\n    std.debug.print(\"Server version: {s}\\n\", .{version});\n}\n\n// Payload and feature info\nconst max_payload = client.maxPayload();\nconst supports_headers = client.headersSupported();\n\n// Server pool (for cluster connections)\nconst server_count = client.serverCount();\nfor (0..server_count) |i| {\n    if (client.serverUrl(@intCast(i))) |url| {\n        std.debug.print(\"Known server: {s}\\n\", .{url});\n    }\n}\n\n// RTT measurement\nconst rtt_ns = try client.rtt();\nconst rtt_ms = @as(f64, @floatFromInt(rtt_ns)) / 1_000_000.0;\nstd.debug.print(\"RTT: {d:.2}ms\\n\", .{rtt_ms});\n```\n\n### Connection Statistics\n\nMonitor throughput and connection health:\n\n```zig\nconst stats = client.stats();\nstd.debug.print(\"Messages: in={d} out={d}\\n\", .{stats.msgs_in, stats.msgs_out});\nstd.debug.print(\"Bytes: in={d} out={d}\\n\", .{stats.bytes_in, stats.bytes_out});\nstd.debug.print(\"Reconnects: {d}\\n\", .{stats.reconnects});\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `msgs_in` | `u64` | Total messages received |\n| `msgs_out` | `u64` | Total messages sent |\n| `bytes_in` | `u64` | Total bytes received |\n| `bytes_out` | `u64` | Total bytes sent |\n| `reconnects` | `u32` | Number of reconnections |\n| `connects` | `u32` | Total successful connections |\n\n### Connection Control\n\n**Flush with Server Confirmation:**\n\n```zig\n// Sends PING and waits for PONG (confirms server received messages)\nclient.flush(5_000_000_000) catch |err| {\n    if (err == error.Timeout) {\n        std.debug.print(\"Flush timed out\\n\", .{});\n    }\n};\n```\n\n**Force Reconnect:**\n\n```zig\ntry client.forceReconnect();\n// Connection closes, io_task starts reconnection process\n```\n\n**Drain with Timeout:**\n\n```zig\nconst result = client.drainTimeout(30_000_000_000) catch |err| {\n    if (err == error.Timeout) {\n        std.debug.print(\"Drain timed out\\n\", .{});\n    }\n    return err;\n};\nif (!result.isClean()) {\n    std.debug.print(\"Drain had failures\\n\", .{});\n}\n```\n\n### Handling Slow Consumers\n\nWhen messages arrive faster than you process them, the queue fills up and messages are dropped:\n\n```zig\nwhile (true) {\n    const msg = try sub.nextMsg();\n    defer msg.deinit();\n\n    // Check for dropped messages periodically\n    const dropped = sub.dropped();\n    if (dropped > 0) {\n        std.log.warn(\"Dropped {d} messages - consumer too slow\", .{dropped});\n    }\n\n    processMessage(msg);\n}\n```\n\n**Tuning for High Throughput:**\n\n```zig\nconst client = try nats.Client.connect(allocator, io, url, .{\n    .sub_queue_size = 16384,          // Larger per-subscription queue\n    .tcp_rcvbuf = 512 * 1024,         // 512KB TCP buffer\n    .reader_buffer_size = 2 * 1024 * 1024, // 2MB read buffer\n    .writer_buffer_size = 2 * 1024 * 1024, // 2MB write buffer\n});\n```\n\n---\n\n## Authentication\n\n### Username/Password\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    .user = \"user\",\n    .pass = \"pass\",\n});\n```\n\n### Token Authentication\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    .auth_token = \"my-secret-token\",\n});\n```\n\n### NKey Authentication\n\nNKey authentication uses Ed25519 signatures for secure, password-less\nauthentication. NKeys are the recommended authentication method for production\nNATS deployments.\n\n**Using NKey Seed (Direct):**\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    .nkey_seed = \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\",\n});\n```\n\n**Using NKey Seed File:**\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    .nkey_seed_file = \"/path/to/user.nk\",\n});\n```\n\n**Using Signing Callback (HSM/Hardware Keys):**\n\n```zig\nfn mySignCallback(nonce: []const u8, sig: *[64]u8) bool {\n    // Sign nonce using HSM, hardware token, etc.\n    return hsm.sign(nonce, sig);\n}\n\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    .nkey_pubkey = \"UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4\",\n    .nkey_sign_fn = &mySignCallback,\n});\n```\n\n### JWT/Credentials Authentication\n\nFor NATS deployments using the account/user JWT model.\n\n**Using Credentials File:**\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n    .creds_file = \"/path/to/user.creds\",\n});\n```\n\n**Using Credentials Content:**\n\n```zig\n// From environment variable\nconst creds = std.posix.getenv(\"NATS_CREDS\") orelse return error.MissingCreds;\nconst client = try nats.Client.connect(allocator, io, url, .{\n    .creds = creds,\n});\n\n// Or embed at compile time\nconst client = try nats.Client.connect(allocator, io, url, .{\n    .creds = @embedFile(\"user.creds\"),\n});\n```\n\n### NKey Generation & JWT Encoding\n\nGenerate NKey keypairs, encode JWTs, and format credentials files\nprogrammatically. No allocator needed - all operations use\ncaller-provided stack buffers.\n\n**Generate Keypairs:**\n\n```zig\nconst nats = @import(\"nats\");\n\n// Generate operator, account, and user keypairs\nvar op_kp = nats.auth.KeyPair.generate(io, .operator);\ndefer op_kp.wipe();\n\nvar acct_kp = nats.auth.KeyPair.generate(io, .account);\ndefer acct_kp.wipe();\n\nvar user_kp = nats.auth.KeyPair.generate(io, .user);\ndefer user_kp.wipe();\n\n// Get public key (base32-encoded, 56 chars)\nvar pk_buf: [56]u8 = undefined;\nconst pub_key = op_kp.publicKey(&pk_buf);  // \"O...\"\n\n// Encode seed (base32-encoded, 58 chars)\nvar seed_buf: [58]u8 = undefined;\nconst seed = op_kp.encodeSeed(&seed_buf);  // \"SO...\"\n```\n\n**Encode JWTs:**\n\n```zig\n// Account JWT (signed by operator)\nvar acct_jwt_buf: [2048]u8 = undefined;\nconst acct_jwt = try nats.auth.jwt.encodeAccountClaims(\n    &acct_jwt_buf,\n    acct_pub,       // account public key (subject)\n    \"my-account\",   // account name\n    op_kp,          // operator keypair (signer)\n    iat,            // issued-at (unix seconds)\n    .{},            // AccountOptions (defaults: unlimited)\n);\n\n// User JWT with permissions (signed by account)\nvar user_jwt_buf: [2048]u8 = undefined;\nconst user_jwt = try nats.auth.jwt.encodeUserClaims(\n    &user_jwt_buf,\n    user_pub,       // user public key (subject)\n    \"my-user\",      // user name\n    acct_kp,        // account keypair (signer)\n    iat,            // issued-at (unix seconds)\n    .{\n        .pub_allow = &.{\"app.>\"},\n        .sub_allow = &.{ \"app.>\", \"_INBOX.>\" },\n    },\n);\n```\n\n**Format Credentials File:**\n\n```zig\nvar creds_buf: [4096]u8 = undefined;\nconst creds = nats.auth.creds.format(\n    &creds_buf,\n    user_jwt,   // JWT string\n    user_seed,  // NKey seed string\n);\n// creds contains the full .creds file content\n```\n\n**Account Options (limits):**\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `subs` | `-1` | Max subscriptions (-1 = unlimited) |\n| `conn` | `-1` | Max connections |\n| `data` | `-1` | Max data bytes |\n| `payload` | `-1` | Max message payload |\n| `imports` | `-1` | Max imports |\n| `exports` | `-1` | Max exports |\n| `leaf` | `-1` | Max leaf node connections |\n| `mem_storage` | `-1` | Max memory storage |\n| `disk_storage` | `-1` | Max disk storage |\n| `wildcards` | `true` | Allow wildcard subscriptions |\n\n**User Options (permissions):**\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `pub_allow` | `&.{}` | Subjects allowed to publish |\n| `sub_allow` | `&.{}` | Subjects allowed to subscribe |\n| `subs` | `-1` | Max subscriptions (-1 = unlimited) |\n| `data` | `-1` | Max data bytes |\n| `payload` | `-1` | Max message payload |\n\nSee the [NKeys & JWTs example](doc/nats-by-example/auth/nkeys-jwts.zig)\nfor a complete working example.\n\n### TLS\n\n**Enabling TLS:**\n\n```zig\n// 1. URL scheme (recommended)\nconst client = try nats.Client.connect(allocator, io, \"tls://localhost:4443\", .{});\n\n// 2. Explicit option\nconst client = try nats.Client.connect(allocator, io, \"nats://localhost:4443\", .{\n    .tls_required = true,\n});\n\n// 3. Automatic - if server requires TLS, client upgrades automatically\n```\n\n**TLS Options:**\n\n```zig\nconst client = try nats.Client.connect(allocator, io, \"tls://localhost:4443\", .{\n    // Server certificate verification (production)\n    .tls_ca_file = \"/path/to/ca.pem\",\n\n    // Skip verification (development only!)\n    .tls_insecure_skip_verify = true,\n\n    // TLS-first handshake (for TLS-terminating proxies)\n    .tls_handshake_first = true,\n});\n```\n\n**Mutual TLS (mTLS):** client certificates are planned but not\nimplemented yet. Setting `tls_cert_file` or `tls_key_file` currently\nreturns `error.MtlsNotImplemented`.\n\n**Checking TLS Status:**\n\n```zig\nif (client.isTls()) {\n    std.debug.print(\"Connection is encrypted\\n\", .{});\n}\n```\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `tls_required` | `bool` | Force TLS connection |\n| `tls_ca_file` | `?[]const u8` | CA certificate file path (PEM) |\n| `tls_cert_file` | `?[]const u8` | Reserved for mTLS; currently returns `error.MtlsNotImplemented` |\n| `tls_key_file` | `?[]const u8` | Reserved for mTLS; currently returns `error.MtlsNotImplemented` |\n| `tls_insecure_skip_verify` | `bool` | Skip server certificate verification |\n| `tls_handshake_first` | `bool` | TLS handshake before NATS protocol |\n\n### Authentication Priority\n\nWhen multiple auth options are set:\n\n1. `creds_file` / `creds` - JWT + NKey from credentials\n2. `nkey_seed` / `nkey_seed_file` - NKey only\n3. `nkey_sign_fn` + `nkey_pubkey` - Custom signing\n4. `user` / `pass` or `auth_token` - Basic auth\n\n### Security Notes\n\n- The library wipes seed data from memory after use (best effort)\n\n---\n\n## Error Handling\n\n```zig\nclient.publish(subject, data) catch |err| switch (err) {\n    error.NotConnected => {\n        // Connection lost - wait for reconnect or handle\n    },\n    error.PayloadTooLarge => {\n        // Message exceeds server max_payload (usually 1MB)\n    },\n    error.EncodingFailed => {\n        // Protocol encoding error\n    },\n    else => return err,\n};\n```\n\n### Common Errors\n\n| Error | Meaning |\n|-------|---------|\n| `NotConnected` | Not connected to server |\n| `ConnectionClosed` | Connection closed unexpectedly |\n| `ConnectionTimeout` | Connection attempt timed out |\n| `ConnectionRefused` | Server refused connection |\n| `AuthenticationFailed` | Authentication failed |\n| `PayloadTooLarge` | Message exceeds max_payload |\n| `TooManySubscriptions` | Subscription limit reached (16,384) |\n| `Closed` | Connection was closed |\n| `Canceled` | Operation was cancelled |\n| `Timeout` | Operation timed out |\n\n---\n\n## Server Compatibility\n\nVerify the server meets minimum version requirements:\n\n```zig\n// Check for NATS 2.10.0 or later (required for some features)\nif (client.checkCompatibility(2, 10, 0)) {\n    // Server supports NATS 2.10+ features\n} else {\n    std.debug.print(\"Server version too old\\n\", .{});\n}\n\n// Get the actual version string\nif (client.connectedServerVersion()) |version| {\n    std.debug.print(\"Connected to NATS {s}\\n\", .{version});\n}\n```\n\n---\n\n## Building\n\n```bash\n# Build library\nzig build\n\n# Run unit tests\nzig build test\n\n# Run integration tests (requires nats-server and nats CLI)\nzig build test-integration\n\n# Format code\nzig build fmt\n```\n\nSee [src/testing/README.md](src/testing/README.md) for integration test\nlayout, fixtures, and focused test targets.\n\n---\n\n## Status\n\n| Component | Status |\n|-----------|--------|\n| Core Protocol | Supported |\n| Pub/Sub | Supported |\n| Request/Reply | Supported |\n| Headers | Supported |\n| Reconnection | Supported |\n| Event Callbacks | Supported |\n| NKey Authentication | Supported |\n| JWT/Credentials | Supported |\n| Server-authenticated TLS | Supported |\n| mTLS client certificates | Planned |\n| JetStream Core | Supported |\n| JetStream Pull Consumers | Supported |\n| JetStream Push Consumers | Supported |\n| JetStream Ordered Consumer | Supported |\n| Key-Value Store | Supported |\n| Micro Services API | Supported |\n| Object Store | Planned |\n| Async Publish | Supported |\n\n## Related Projects\n\nOther Zig-based NATS implementations from the community:\n\n- [NATS C client library, packaged for Zig](https://github.com/allyourcodebase/nats.c)\n- [Zig language bindings to the NATS.c library](https://github.com/epicyclic-dev/nats-client)\n- [Zig client for NATS Core and JetStream](https://github.com/g41797/nats)\n- [A Zig client library for NATS, the cloud-native messaging system](https://github.com/lalinsky/nats.zig)\n- [Minimal synchronous NATS Zig client](https://github.com/ianic/nats.zig)\n- [Work-in-progress NATS library for Zig](https://github.com/rutgerbrf/zig-nats)\n\n## License\n\nApache 2.0\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup,\ntest commands, and contribution guidelines.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nPlease report suspected security vulnerabilities privately. Do not open a\npublic issue for a vulnerability report.\n\nIf GitHub private vulnerability reporting is enabled for this repository, use\nthat flow. Otherwise, contact the maintainers through the NATS project security\nprocess before disclosing details publicly.\n\nPublic NATS security advisories are published at:\n\nhttps://advisories.nats.io/\n\nWhen reporting a vulnerability, include:\n\n- affected version or commit;\n- a minimal reproduction when possible;\n- expected and observed behavior;\n- impact assessment;\n- any known workaround.\n"
  },
  {
    "path": "build.zig",
    "content": "const std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const target = b.standardTargetOptions(.{});\n    const optimize = b.standardOptimizeOption(.{});\n\n    // Debug option for reconnection events (default: false)\n    const enable_debug = b.option(\n        bool,\n        \"EnableDebug\",\n        \"Enable debug prints for reconnection events (default: false)\",\n    ) orelse false;\n\n    // Io backend selector.\n    // 'threaded' = std.Io.Threaded (default, OS threads).\n    // 'evented'  = std.Io.Evented (Linux: Uring, BSD: Kqueue, Apple: Dispatch).\n    const io_backend_choice = b.option(\n        []const u8,\n        \"io_backend\",\n        \"Io backend: 'threaded' (default) or 'evented'\",\n    ) orelse \"threaded\";\n\n    // Create build options module. Share a single Module instance\n    // across all consumers (nats, io_backend, ...) — calling\n    // createModule() twice would generate two distinct Modules\n    // pointing at the same options.zig file, which Zig rejects\n    // when both end up in the same compile graph.\n    const build_options = b.addOptions();\n    build_options.addOption(bool, \"enable_debug\", enable_debug);\n    build_options.addOption([]const u8, \"io_backend\", io_backend_choice);\n    const build_options_mod = build_options.createModule();\n\n    const nats = b.addModule(\"nats\", .{\n        .root_source_file = b.path(\"src/nats.zig\"),\n        .target = target,\n        .imports = &.{\n            .{ .name = \"build_options\", .module = build_options_mod },\n        },\n    });\n\n    const mod_tests = b.addTest(.{ .root_module = nats });\n    const run_mod_tests = b.addRunArtifact(mod_tests);\n\n    const test_step = b.step(\"test\", \"Run tests\");\n    test_step.dependOn(&run_mod_tests.step);\n\n    // Backend selector module. Used by entry points (examples,\n    // integration tests) so they can flip between\n    // std.Io.Threaded and std.Io.Evented via -Dio_backend=...\n    // The library module itself does NOT depend on this; only\n    // application code chooses a backend.\n    const io_backend_mod = b.createModule(.{\n        .root_source_file = b.path(\"src/io_backend.zig\"),\n        .target = target,\n        .imports = &.{\n            .{\n                .name = \"build_options\",\n                .module = build_options_mod,\n            },\n        },\n    });\n\n    // Standalone test for the io_backend selector module. Ensures\n    // src/io_backend.zig compiles under -Dio_backend=threaded and\n    // -Dio_backend=evented.\n    const io_backend_tests = b.addTest(.{ .root_module = io_backend_mod });\n    const run_io_backend_tests = b.addRunArtifact(io_backend_tests);\n    test_step.dependOn(&run_io_backend_tests.step);\n\n    // 1. Simple example (hello world - entry point)\n    const simple_exe = b.addExecutable(.{\n        .name = \"example-simple\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/simple.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(simple_exe);\n\n    const run_simple = b.step(\"run-simple\", \"Run simple hello world example\");\n    const simple_cmd = b.addRunArtifact(simple_exe);\n    run_simple.dependOn(&simple_cmd.step);\n    simple_cmd.step.dependOn(b.getInstallStep());\n\n    // 2. Request/Reply example (RPC pattern)\n    const request_reply_exe = b.addExecutable(.{\n        .name = \"example-request-reply\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/request_reply.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(request_reply_exe);\n\n    const run_request_reply = b.step(\n        \"run-request-reply\",\n        \"Run request/reply RPC example\",\n    );\n    const request_reply_cmd = b.addRunArtifact(request_reply_exe);\n    run_request_reply.dependOn(&request_reply_cmd.step);\n    request_reply_cmd.step.dependOn(b.getInstallStep());\n\n    // Headers example (metadata with HPUB/HMSG)\n    const headers_exe = b.addExecutable(.{\n        .name = \"example-headers\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/headers.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(headers_exe);\n\n    const run_headers = b.step(\n        \"run-headers\",\n        \"Run headers example\",\n    );\n    const headers_cmd = b.addRunArtifact(headers_exe);\n    run_headers.dependOn(&headers_cmd.step);\n    headers_cmd.step.dependOn(b.getInstallStep());\n\n    // 3. Queue Groups example (load balancing with workers)\n    const queue_groups_exe = b.addExecutable(.{\n        .name = \"example-queue-groups\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/queue_groups.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(queue_groups_exe);\n\n    const run_queue_groups = b.step(\n        \"run-queue-groups\",\n        \"Run queue groups (load balancing) example\",\n    );\n    const queue_groups_cmd = b.addRunArtifact(queue_groups_exe);\n    run_queue_groups.dependOn(&queue_groups_cmd.step);\n    queue_groups_cmd.step.dependOn(b.getInstallStep());\n\n    // 4. Select example (io.select timeout pattern)\n    const select_exe = b.addExecutable(.{\n        .name = \"example-select\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/select.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(select_exe);\n\n    const run_select = b.step(\n        \"run-select\",\n        \"Run io.select() async timeout example\",\n    );\n    const select_cmd = b.addRunArtifact(select_exe);\n    run_select.dependOn(&select_cmd.step);\n    select_cmd.step.dependOn(b.getInstallStep());\n\n    // 5. Batch Receiving example (efficient batch message retrieval)\n    const batch_exe = b.addExecutable(.{\n        .name = \"example-batch-receiving\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/batch_receiving.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(batch_exe);\n\n    const run_batch = b.step(\n        \"run-batch-receiving\",\n        \"Run batch receiving patterns example\",\n    );\n    const batch_cmd = b.addRunArtifact(batch_exe);\n    run_batch.dependOn(&batch_cmd.step);\n    batch_cmd.step.dependOn(b.getInstallStep());\n\n    // 6. Graceful Shutdown example (drain and lifecycle)\n    const shutdown_exe = b.addExecutable(.{\n        .name = \"example-graceful-shutdown\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/graceful_shutdown.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(shutdown_exe);\n\n    const run_shutdown = b.step(\n        \"run-graceful-shutdown\",\n        \"Run graceful shutdown (drain) example\",\n    );\n    const shutdown_cmd = b.addRunArtifact(shutdown_exe);\n    run_shutdown.dependOn(&shutdown_cmd.step);\n    shutdown_cmd.step.dependOn(b.getInstallStep());\n\n    // 7. Reconnection example (resilience patterns)\n    const reconnect_exe = b.addExecutable(.{\n        .name = \"example-reconnection\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/reconnection.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(reconnect_exe);\n\n    const run_reconnect = b.step(\n        \"run-reconnection\",\n        \"Run reconnection resilience example\",\n    );\n    const reconnect_cmd = b.addRunArtifact(reconnect_exe);\n    run_reconnect.dependOn(&reconnect_cmd.step);\n    reconnect_cmd.step.dependOn(b.getInstallStep());\n\n    // 8. Polling Loop example (non-blocking patterns)\n    const polling_exe = b.addExecutable(.{\n        .name = \"example-polling-loop\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/polling_loop.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(polling_exe);\n\n    const run_polling = b.step(\n        \"run-polling-loop\",\n        \"Run non-blocking polling loop example\",\n    );\n    const polling_cmd = b.addRunArtifact(polling_exe);\n    run_polling.dependOn(&polling_cmd.step);\n    polling_cmd.step.dependOn(b.getInstallStep());\n\n    // 9. Event Callbacks example (connection lifecycle)\n    const events_exe = b.addExecutable(.{\n        .name = \"example-events\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/examples/events.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(events_exe);\n\n    const run_events = b.step(\n        \"run-events\",\n        \"Run event callbacks (connection lifecycle) example\",\n    );\n    const events_cmd = b.addRunArtifact(events_exe);\n    run_events.dependOn(&events_cmd.step);\n    events_cmd.step.dependOn(b.getInstallStep());\n\n    // 10. Callback Subscriptions example\n    const callback_exe = b.addExecutable(.{\n        .name = \"example-callback\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/callback.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(callback_exe);\n\n    const run_callback = b.step(\n        \"run-callback\",\n        \"Run callback subscriptions example\",\n    );\n    const callback_cmd = b.addRunArtifact(callback_exe);\n    run_callback.dependOn(&callback_cmd.step);\n    callback_cmd.step.dependOn(b.getInstallStep());\n\n    // 11. Request/Reply with Callback example\n    const req_rep_cb_exe = b.addExecutable(.{\n        .name = \"example-request-reply-callback\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/request_reply_callback.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(req_rep_cb_exe);\n\n    const run_req_rep_cb = b.step(\n        \"run-request-reply-callback\",\n        \"Run request/reply callback example\",\n    );\n    const req_rep_cb_cmd = b.addRunArtifact(req_rep_cb_exe);\n    run_req_rep_cb.dependOn(&req_rep_cb_cmd.step);\n    req_rep_cb_cmd.step.dependOn(b.getInstallStep());\n\n    // 12. JetStream Publish example\n    const js_pub_exe = b.addExecutable(.{\n        .name = \"example-jetstream-publish\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/jetstream_publish.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(js_pub_exe);\n\n    const run_js_pub = b.step(\n        \"run-jetstream-publish\",\n        \"Run JetStream publish example\",\n    );\n    const js_pub_cmd = b.addRunArtifact(js_pub_exe);\n    run_js_pub.dependOn(&js_pub_cmd.step);\n    js_pub_cmd.step.dependOn(b.getInstallStep());\n\n    // 13. JetStream Consume example\n    const js_consume_exe = b.addExecutable(.{\n        .name = \"example-jetstream-consume\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/jetstream_consume.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(js_consume_exe);\n\n    const run_js_consume = b.step(\n        \"run-jetstream-consume\",\n        \"Run JetStream pull consumer example\",\n    );\n    const js_consume_cmd = b.addRunArtifact(\n        js_consume_exe,\n    );\n    run_js_consume.dependOn(&js_consume_cmd.step);\n    js_consume_cmd.step.dependOn(b.getInstallStep());\n\n    // 14. JetStream Push Consumer example\n    const js_push_exe = b.addExecutable(.{\n        .name = \"example-jetstream-push\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/jetstream_push.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(js_push_exe);\n\n    const run_js_push = b.step(\n        \"run-jetstream-push\",\n        \"Run JetStream push consumer example\",\n    );\n    const js_push_cmd = b.addRunArtifact(js_push_exe);\n    run_js_push.dependOn(&js_push_cmd.step);\n    js_push_cmd.step.dependOn(b.getInstallStep());\n\n    // 15. JetStream Async Publish example\n    const js_async_exe = b.addExecutable(.{\n        .name = \"example-jetstream-async-publish\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/jetstream_async_publish.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(js_async_exe);\n\n    const run_js_async = b.step(\n        \"run-jetstream-async-publish\",\n        \"Run JetStream async publish example\",\n    );\n    const js_async_cmd = b.addRunArtifact(\n        js_async_exe,\n    );\n    run_js_async.dependOn(&js_async_cmd.step);\n    js_async_cmd.step.dependOn(b.getInstallStep());\n\n    // 16. Key-Value Store example\n    const kv_exe = b.addExecutable(.{\n        .name = \"example-kv\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/kv.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(kv_exe);\n\n    const run_kv = b.step(\n        \"run-kv\",\n        \"Run KV store example\",\n    );\n    const kv_cmd = b.addRunArtifact(kv_exe);\n    run_kv.dependOn(&kv_cmd.step);\n    kv_cmd.step.dependOn(b.getInstallStep());\n\n    // 17. Key-Value Watch example\n    const kv_watch_exe = b.addExecutable(.{\n        .name = \"example-kv-watch\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/kv_watch.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(kv_watch_exe);\n\n    const run_kv_watch = b.step(\n        \"run-kv-watch\",\n        \"Run KV watch example\",\n    );\n    const kv_watch_cmd = b.addRunArtifact(\n        kv_watch_exe,\n    );\n    run_kv_watch.dependOn(&kv_watch_cmd.step);\n    kv_watch_cmd.step.dependOn(b.getInstallStep());\n\n    // 18. Microservices echo example\n    const micro_echo_exe = b.addExecutable(.{\n        .name = \"example-micro-echo\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/examples/micro_echo.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(micro_echo_exe);\n\n    const run_micro_echo = b.step(\n        \"run-micro-echo\",\n        \"Run microservices echo example\",\n    );\n    const micro_echo_cmd = b.addRunArtifact(\n        micro_echo_exe,\n    );\n    run_micro_echo.dependOn(&micro_echo_cmd.step);\n    micro_echo_cmd.step.dependOn(b.getInstallStep());\n\n    // NATS by Example: Pub-Sub messaging\n    const nbe_pubsub_exe = b.addExecutable(.{\n        .name = \"nbe-messaging-pub-sub\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"doc/nats-by-example/messaging/pub-sub.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(nbe_pubsub_exe);\n\n    const run_nbe_pubsub = b.step(\n        \"run-nbe-messaging-pub-sub\",\n        \"Run NATS by Example: Pub-Sub messaging\",\n    );\n    const nbe_pubsub_cmd = b.addRunArtifact(nbe_pubsub_exe);\n    run_nbe_pubsub.dependOn(&nbe_pubsub_cmd.step);\n    nbe_pubsub_cmd.step.dependOn(b.getInstallStep());\n\n    // NATS by Example: Request-Reply\n    const nbe_reqrep_exe = b.addExecutable(.{\n        .name = \"nbe-messaging-request-reply\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"doc/nats-by-example/messaging/request-reply.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(nbe_reqrep_exe);\n\n    const run_nbe_reqrep = b.step(\n        \"run-nbe-messaging-request-reply\",\n        \"Run NATS by Example: Request-Reply\",\n    );\n    const nbe_reqrep_cmd = b.addRunArtifact(nbe_reqrep_exe);\n    run_nbe_reqrep.dependOn(&nbe_reqrep_cmd.step);\n    nbe_reqrep_cmd.step.dependOn(b.getInstallStep());\n\n    // NATS by Example: JSON payloads\n    const nbe_json_exe = b.addExecutable(.{\n        .name = \"nbe-messaging-json\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"doc/nats-by-example/messaging/json.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(nbe_json_exe);\n\n    const run_nbe_json = b.step(\n        \"run-nbe-messaging-json\",\n        \"Run NATS by Example: JSON payloads\",\n    );\n    const nbe_json_cmd = b.addRunArtifact(nbe_json_exe);\n    run_nbe_json.dependOn(&nbe_json_cmd.step);\n    nbe_json_cmd.step.dependOn(b.getInstallStep());\n\n    // NATS by Example: Concurrent processing\n    const nbe_concurrent_exe = b.addExecutable(.{\n        .name = \"nbe-messaging-concurrent\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"doc/nats-by-example/messaging/concurrent.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(nbe_concurrent_exe);\n\n    const run_nbe_concurrent = b.step(\n        \"run-nbe-messaging-concurrent\",\n        \"Run NATS by Example: Concurrent processing\",\n    );\n    const nbe_concurrent_cmd = b.addRunArtifact(\n        nbe_concurrent_exe,\n    );\n    run_nbe_concurrent.dependOn(&nbe_concurrent_cmd.step);\n    nbe_concurrent_cmd.step.dependOn(b.getInstallStep());\n\n    // NATS by Example: Iterating multiple subscriptions\n    const nbe_multisub_exe = b.addExecutable(.{\n        .name = \"nbe-messaging-iterating-multiple-subscriptions\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"doc/nats-by-example/messaging/\" ++\n                    \"iterating-multiple-subscriptions.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(nbe_multisub_exe);\n\n    const run_nbe_multisub = b.step(\n        \"run-nbe-messaging-iterating-multiple-subscriptions\",\n        \"Run NATS by Example: Multiple subscriptions\",\n    );\n    const nbe_multisub_cmd = b.addRunArtifact(\n        nbe_multisub_exe,\n    );\n    run_nbe_multisub.dependOn(&nbe_multisub_cmd.step);\n    nbe_multisub_cmd.step.dependOn(b.getInstallStep());\n\n    // NATS by Example: NKeys and JWTs (auth)\n    const nbe_nkeys_jwts_exe = b.addExecutable(.{\n        .name = \"nbe-auth-nkeys-jwts\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"doc/nats-by-example/auth/nkeys-jwts.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n            },\n        }),\n    });\n    b.installArtifact(nbe_nkeys_jwts_exe);\n\n    const run_nbe_nkeys_jwts = b.step(\n        \"run-nbe-auth-nkeys-jwts\",\n        \"Run NATS by Example: NKeys and JWTs\",\n    );\n    const nbe_nkeys_jwts_cmd = b.addRunArtifact(\n        nbe_nkeys_jwts_exe,\n    );\n    run_nbe_nkeys_jwts.dependOn(&nbe_nkeys_jwts_cmd.step);\n    nbe_nkeys_jwts_cmd.step.dependOn(b.getInstallStep());\n\n    const fmt = b.addFmt(.{\n        .paths = &.{ \"src\", \"doc\", \"build.zig\" },\n        .check = false,\n    });\n    const fmt_step = b.step(\"fmt\", \"Format source code\");\n    fmt_step.dependOn(&fmt.step);\n\n    const fmt_check = b.addFmt(.{\n        .paths = &.{ \"src\", \"doc\", \"build.zig\" },\n        .check = true,\n    });\n    const fmt_check_step = b.step(\"fmt-check\", \"Check formatting\");\n    fmt_check_step.dependOn(&fmt_check.step);\n\n    // Integration tests (requires nats-server)\n    const integration_exe = b.addExecutable(.{\n        .name = \"integration-test\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\"src/testing/integration_test.zig\"),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(integration_exe);\n\n    const run_integration = b.step(\n        \"test-integration\",\n        \"Run integration tests (requires nats-server)\",\n    );\n    const integration_cmd = b.addRunArtifact(integration_exe);\n    run_integration.dependOn(&integration_cmd.step);\n\n    // Micro-only integration tests (faster; just the micro suite).\n    const micro_integration_exe = b.addExecutable(.{\n        .name = \"micro-integration-test\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/testing/micro_integration_test.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(micro_integration_exe);\n\n    const run_micro_integration = b.step(\n        \"test-integration-micro\",\n        \"Run only the micro integration tests\",\n    );\n    const micro_integration_cmd = b.addRunArtifact(\n        micro_integration_exe,\n    );\n    run_micro_integration.dependOn(&micro_integration_cmd.step);\n\n    // Focused JWT/TLS integration tests.\n    const tls_integration_exe = b.addExecutable(.{\n        .name = \"tls-integration-test\",\n        .root_module = b.createModule(.{\n            .root_source_file = b.path(\n                \"src/testing/tls_integration_test.zig\",\n            ),\n            .target = target,\n            .optimize = optimize,\n            .imports = &.{\n                .{ .name = \"nats\", .module = nats },\n                .{ .name = \"io_backend\", .module = io_backend_mod },\n            },\n        }),\n    });\n    b.installArtifact(tls_integration_exe);\n\n    const run_tls_integration = b.step(\n        \"test-integration-tls\",\n        \"Run only the TLS integration tests\",\n    );\n    const tls_integration_cmd = b.addRunArtifact(\n        tls_integration_exe,\n    );\n    run_tls_integration.dependOn(&tls_integration_cmd.step);\n}\n"
  },
  {
    "path": "build.zig.zon",
    "content": ".{\n    .name = .nats,\n    .version = \"0.1.0\",\n    // Changing fingerprint has security and trust implications.\n    .fingerprint = 0x31f7624fb15addf7,\n    .minimum_zig_version = \"0.16.0\",\n    .dependencies = .{},\n    .paths = .{\n        \"CONTRIBUTING.md\",\n        \"LICENSE\",\n        \"README.md\",\n        \"SECURITY.md\",\n        \"build.zig\",\n        \"build.zig.zon\",\n        \"doc\",\n        \"logo\",\n        \"src\",\n    },\n}\n"
  },
  {
    "path": "doc/JetStream.md",
    "content": "# JetStream Guide for nats.zig\n\nJetStream is NATS' persistence and streaming layer. It provides\nat-least-once delivery, message replay, and durable consumers --\nall through a JSON request/reply API layered on core NATS. No new\nwire protocol; everything goes through `$JS.API.*` subjects.\n\nThis guide covers the nats.zig JetStream API with side-by-side\nGo comparisons for developers familiar with nats.go.\n\nIt is a focused companion to the comprehensive root\n[README](../README.md). For Key-Value Store coverage, see the\nREADME JetStream section and `src/examples/kv*.zig`.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [JetStream Context](#jetstream-context)\n- [Streams](#streams)\n- [Consumers](#consumers)\n- [Publishing](#publishing)\n- [Pull Subscription](#pull-subscription)\n- [Message Acknowledgment](#message-acknowledgment)\n- [Error Handling](#error-handling)\n- [Response Ownership](#response-ownership)\n- [Type Reference](#type-reference)\n\n---\n\n## Quick Start\n\nA complete example: create a stream, publish a message, create a\nconsumer, fetch the message, and acknowledge it.\n\n**Zig:**\n\n```zig\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\n// Assumes `client` is already connected\nvar js = try js_mod.JetStream.init(client, .{});\n\n// Create a stream\nvar stream = try js.createStream(.{\n    .name = \"ORDERS\",\n    .subjects = &.{\"orders.>\"},\n    .storage = .memory,\n});\ndefer stream.deinit();\n\n// Publish a message\nvar ack = try js.publish(\"orders.new\", \"order-1\");\ndefer ack.deinit();\n// ack.value.seq == 1, ack.value.stream == \"ORDERS\"\n\n// Create a consumer\nvar cons = try js.createConsumer(\"ORDERS\", .{\n    .name = \"processor\",\n    .durable_name = \"processor\",\n    .ack_policy = .explicit,\n});\ndefer cons.deinit();\n\n// Fetch messages\nvar pull = js_mod.PullSubscription{\n    .js = &js,\n    .stream = \"ORDERS\",\n};\ntry pull.setConsumer(\"processor\");\nvar result = try pull.fetch(.{\n    .max_messages = 10,\n    .timeout_ms = 5000,\n});\ndefer result.deinit();\n\nfor (result.messages) |*msg| {\n    // msg.data() returns the payload\n    try msg.ack();\n}\n```\n\n**Go:**\n\n```go\njs, _ := jetstream.New(nc)\n\n// Create a stream\nstream, _ := js.CreateStream(ctx, jetstream.StreamConfig{\n    Name:     \"ORDERS\",\n    Subjects: []string{\"orders.>\"},\n    Storage:  jetstream.MemoryStorage,\n})\n\n// Publish a message\nack, _ := js.Publish(ctx, \"orders.new\", []byte(\"order-1\"))\n// ack.Stream == \"ORDERS\", ack.Sequence == 1\n\n// Create a consumer\ncons, _ := js.CreateConsumer(ctx, \"ORDERS\",\n    jetstream.ConsumerConfig{\n        Durable:   \"processor\",\n        AckPolicy: jetstream.AckExplicitPolicy,\n    })\n\n// Fetch messages\nbatch, _ := cons.Fetch(10)\nfor msg := range batch.Messages() {\n    msg.Ack()\n}\n```\n\n---\n\n## JetStream Context\n\nThe JetStream context is a lightweight struct (stack-allocated) that\nholds a pointer to the NATS client, the API prefix, and timeout\nsettings. No heap allocation is needed. `JetStream.init()` is fallible because\nit validates the API prefix or domain before storing it in the fixed-size\ncontext buffer.\n\n### Creating a Context\n\n**Zig:**\n\n```zig\nconst js_mod = nats.jetstream;\n\n// Default settings\nvar js = try js_mod.JetStream.init(client, .{});\n\n// Custom timeout\nvar js2 = try js_mod.JetStream.init(client, .{\n    .timeout_ms = 10000,\n});\n\n// With domain (multi-tenant)\nvar js3 = try js_mod.JetStream.init(client, .{\n    .domain = \"hub\",\n});\n// API prefix becomes: $JS.hub.API.\n```\n\nStream, consumer, domain, and API-prefix names are validated at runtime.\nInvalid names return `error.InvalidName`, `error.InvalidApiPrefix`, or\n`error.NameTooLong` instead of relying on debug-only assertions.\n\n**Go:**\n\n```go\njs, _ := jetstream.New(nc)\n\n// With domain\njs, _ = jetstream.NewWithDomain(nc, \"hub\")\n```\n\n### Options\n\n| Field | Zig | Go | Default |\n|-------|-----|-----|---------|\n| API prefix | `.api_prefix` | `APIPrefix` | `$JS.API.` |\n| Timeout | `.timeout_ms` | `DefaultTimeout` | 5000ms |\n| Domain | `.domain` | via `NewWithDomain()` | none |\n\n---\n\n## Streams\n\nStreams capture messages published to matching subjects.\n\n### Create a Stream\n\n**Zig:**\n\n```zig\nvar resp = try js.createStream(.{\n    .name = \"EVENTS\",\n    .subjects = &.{\"events.>\"},\n    .retention = .limits,\n    .storage = .file,\n    .max_msgs = 100000,\n    .max_bytes = 1073741824, // 1GB\n});\ndefer resp.deinit();\n\nconst info = resp.value;\n// info.config.?.name == \"EVENTS\"\n// info.state.?.messages == 0\n```\n\n**Go:**\n\n```go\nstream, _ := js.CreateStream(ctx, jetstream.StreamConfig{\n    Name:      \"EVENTS\",\n    Subjects:  []string{\"events.>\"},\n    Retention: jetstream.LimitsPolicy,\n    Storage:   jetstream.FileStorage,\n    MaxMsgs:   100000,\n    MaxBytes:  1073741824,\n})\ninfo, _ := stream.Info(ctx)\n```\n\n### Get Stream Info\n\n**Zig:**\n\n```zig\nvar info = try js.streamInfo(\"EVENTS\");\ndefer info.deinit();\n\nif (info.value.state) |state| {\n    // state.messages, state.bytes, state.first_seq,\n    // state.last_seq, state.consumer_count\n}\n```\n\n**Go:**\n\n```go\nstream, _ := js.Stream(ctx, \"EVENTS\")\ninfo, _ := stream.Info(ctx)\n// info.State.Msgs, info.State.Bytes, etc.\n```\n\n### Update a Stream\n\n**Zig:**\n\n```zig\nvar resp = try js.updateStream(.{\n    .name = \"EVENTS\",\n    .subjects = &.{ \"events.>\", \"logs.>\" },\n    .max_msgs = 200000,\n});\ndefer resp.deinit();\n```\n\n**Go:**\n\n```go\nstream, _ := js.UpdateStream(ctx, jetstream.StreamConfig{\n    Name:     \"EVENTS\",\n    Subjects: []string{\"events.>\", \"logs.>\"},\n    MaxMsgs:  200000,\n})\n```\n\n### Purge a Stream\n\n**Zig:**\n\n```zig\nvar resp = try js.purgeStream(\"EVENTS\");\ndefer resp.deinit();\n// resp.value.purged == number of messages removed\n```\n\n**Go:**\n\n```go\nstream, _ := js.Stream(ctx, \"EVENTS\")\n_ = stream.Purge(ctx)\n```\n\n### Delete a Stream\n\n**Zig:**\n\n```zig\nvar resp = try js.deleteStream(\"EVENTS\");\ndefer resp.deinit();\n// resp.value.success == true\n```\n\n**Go:**\n\n```go\n_ = js.DeleteStream(ctx, \"EVENTS\")\n```\n\n### StreamConfig Reference\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | `[]const u8` | Stream name (required) |\n| `subjects` | `?[]const []const u8` | Subjects to capture |\n| `retention` | `?RetentionPolicy` | limits, interest, workqueue |\n| `storage` | `?StorageType` | file, memory |\n| `max_msgs` | `?i64` | Max messages in stream |\n| `max_bytes` | `?i64` | Max total bytes |\n| `max_age` | `?i64` | Max message age (nanoseconds) |\n| `max_msg_size` | `?i32` | Max single message size |\n| `max_msgs_per_subject` | `?i64` | Per-subject limit |\n| `max_consumers` | `?i64` | Max consumers |\n| `num_replicas` | `?i32` | Replica count |\n| `discard` | `?DiscardPolicy` | old, new |\n| `duplicate_window` | `?i64` | Dedup window (nanoseconds) |\n| `no_ack` | `?bool` | Disable publish acks |\n| `compression` | `?StoreCompression` | none, s2 |\n\nAll optional fields default to `null` and are omitted from the\nJSON request (server applies its own defaults).\n\n---\n\n## Consumers\n\nConsumers track read position in a stream and manage message\ndelivery.\n\n### Create a Consumer\n\n**Zig:**\n\n```zig\nvar resp = try js.createConsumer(\"EVENTS\", .{\n    .name = \"my-worker\",\n    .durable_name = \"my-worker\",\n    .ack_policy = .explicit,\n    .deliver_policy = .all,\n    .filter_subject = \"events.orders.>\",\n    .max_ack_pending = 1000,\n});\ndefer resp.deinit();\n\nif (resp.value.name) |name| {\n    // name == \"my-worker\"\n}\n```\n\n**Go:**\n\n```go\ncons, _ := js.CreateConsumer(ctx, \"EVENTS\",\n    jetstream.ConsumerConfig{\n        Durable:       \"my-worker\",\n        AckPolicy:     jetstream.AckExplicitPolicy,\n        DeliverPolicy: jetstream.DeliverAllPolicy,\n        FilterSubject: \"events.orders.>\",\n        MaxAckPending: 1000,\n    })\n```\n\n### Get Consumer Info\n\n**Zig:**\n\n```zig\nvar info = try js.consumerInfo(\"EVENTS\", \"my-worker\");\ndefer info.deinit();\n// info.value.num_pending -- messages waiting\n// info.value.num_ack_pending -- delivered but unacked\n```\n\n**Go:**\n\n```go\ncons, _ := js.Consumer(ctx, \"EVENTS\", \"my-worker\")\ninfo, _ := cons.Info(ctx)\n```\n\n### Update a Consumer\n\n**Zig:**\n\n```zig\nvar resp = try js.updateConsumer(\"EVENTS\", .{\n    .name = \"my-worker\",\n    .durable_name = \"my-worker\",\n    .ack_policy = .explicit,\n    .max_ack_pending = 2000,\n});\ndefer resp.deinit();\n```\n\n**Go:**\n\n```go\ncons, _ := js.UpdateConsumer(ctx, \"EVENTS\",\n    jetstream.ConsumerConfig{\n        Durable:       \"my-worker\",\n        AckPolicy:     jetstream.AckExplicitPolicy,\n        MaxAckPending: 2000,\n    })\n```\n\n### Delete a Consumer\n\n**Zig:**\n\n```zig\nvar resp = try js.deleteConsumer(\"EVENTS\", \"my-worker\");\ndefer resp.deinit();\n// resp.value.success == true\n```\n\n**Go:**\n\n```go\n_ = js.DeleteConsumer(ctx, \"EVENTS\", \"my-worker\")\n```\n\n### ConsumerConfig Reference\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | `?[]const u8` | Consumer name |\n| `durable_name` | `?[]const u8` | Durable name (survives restarts) |\n| `ack_policy` | `?AckPolicy` | none, all, explicit |\n| `deliver_policy` | `?DeliverPolicy` | all, last, new, ... |\n| `ack_wait` | `?i64` | Ack timeout (nanoseconds) |\n| `max_deliver` | `?i64` | Max redelivery attempts |\n| `filter_subject` | `?[]const u8` | Subject filter |\n| `filter_subjects` | `?[]const []const u8` | Multiple filters |\n| `replay_policy` | `?ReplayPolicy` | instant, original |\n| `max_waiting` | `?i64` | Max pull requests waiting |\n| `max_ack_pending` | `?i64` | Max unacked messages |\n| `inactive_threshold` | `?i64` | Idle cleanup (nanoseconds) |\n| `headers_only` | `?bool` | Deliver headers only |\n\n---\n\n## Publishing\n\nJetStream publish goes directly to the stream subject (not through\n`$JS.API`). The server returns a `PubAck` confirming storage.\n\n### Simple Publish\n\n**Zig:**\n\n```zig\nvar ack = try js.publish(\"orders.new\", payload);\ndefer ack.deinit();\n\n// Check the ack\nif (ack.value.stream) |stream| {\n    // stream name that stored the message\n}\nconst seq = ack.value.seq; // sequence number\n```\n\n**Go:**\n\n```go\nack, _ := js.Publish(ctx, \"orders.new\", payload)\n// ack.Stream, ack.Sequence\n```\n\n### Publish with Options\n\nUse `publishWithOpts` for idempotency and optimistic concurrency.\n\n**Zig:**\n\n```zig\nvar ack = try js.publishWithOpts(\n    \"orders.new\",\n    payload,\n    .{\n        .msg_id = \"order-123\",\n        .expected_stream = \"ORDERS\",\n        .expected_last_seq = 41,\n    },\n);\ndefer ack.deinit();\n\n// Check for duplicate\nif (ack.value.duplicate) |dup| {\n    if (dup) {\n        // Message was already stored (idempotent)\n    }\n}\n```\n\n**Go:**\n\n```go\nack, _ := js.Publish(ctx, \"orders.new\", payload,\n    jetstream.WithMsgID(\"order-123\"),\n    jetstream.WithExpectStream(\"ORDERS\"),\n    jetstream.WithExpectLastSequence(41),\n)\n```\n\n### Publish Option Headers\n\n| Zig field | Header sent | Purpose |\n|-----------|------------|---------|\n| `msg_id` | `Nats-Msg-Id` | Deduplication key |\n| `expected_stream` | `Nats-Expected-Stream` | Verify target stream |\n| `expected_last_seq` | `Nats-Expected-Last-Sequence` | Optimistic concurrency |\n| `expected_last_msg_id` | `Nats-Expected-Last-Msg-Id` | Sequence by msg ID |\n| `expected_last_subj_seq` | `Nats-Expected-Last-Subject-Sequence` | Per-subject sequence |\n\n---\n\n## Pull Subscription\n\nPull consumers fetch messages on demand. Create a\n`PullSubscription`, then call `fetch()` to get a batch.\n\n### Setup and Fetch\n\n**Zig:**\n\n```zig\nvar pull = nats.jetstream.PullSubscription{\n    .js = &js,\n    .stream = \"ORDERS\",\n};\ntry pull.setConsumer(\"processor\");\n\nvar result = try pull.fetch(.{\n    .max_messages = 100,\n    .timeout_ms = 5000,\n});\ndefer result.deinit();\n\nfor (result.messages) |*msg| {\n    const data = msg.data();\n    // process data...\n    try msg.ack();\n}\n```\n\n**Go:**\n\n```go\ncons, _ := js.Consumer(ctx, \"ORDERS\", \"processor\")\n\nbatch, _ := cons.Fetch(100)\nfor msg := range batch.Messages() {\n    data := msg.Data()\n    // process data...\n    msg.Ack()\n}\n```\n\n### FetchOpts\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `max_messages` | `u32` | 1 | Batch size |\n| `timeout_ms` | `u32` | 5000 | Timeout in milliseconds |\n\n### How Fetch Works\n\n1. Subscribes to a temporary inbox\n2. Publishes a pull request JSON to\n   `$JS.API.CONSUMER.MSG.NEXT.{stream}.{consumer}`\n3. Collects messages until batch is full or a status signal\n   arrives:\n   - **404** -- no messages available (stop)\n   - **408** -- request expired (stop)\n   - **409** -- leadership change (stop)\n   - **100** -- idle heartbeat (skip, continue)\n4. Returns `FetchResult` with collected messages\n\n### FetchResult\n\n```zig\nconst FetchResult = struct {\n    messages: []JsMsg,\n    allocator: Allocator,\n\n    pub fn count(self: *const FetchResult) usize;\n    pub fn deinit(self: *FetchResult) void;\n};\n```\n\nCall `deinit()` to free all messages and the backing slice.\n\n---\n\n## Message Acknowledgment\n\nJetStream messages must be acknowledged to confirm processing.\nAll ack methods publish a protocol token to the message's\nreply-to subject.\n\n### Ack Methods\n\n| Method | Zig | Go | Payload | Repeatable |\n|--------|-----|-----|---------|------------|\n| Acknowledge | `msg.ack()` | `msg.Ack()` | `+ACK` | No |\n| Negative ack | `msg.nak()` | `msg.Nak()` | `-NAK` | No |\n| NAK with delay | `msg.nakWithDelay(ns)` | `msg.NakWithDelay(d)` | `-NAK {\"delay\":N}` | No |\n| In progress | `msg.inProgress()` | `msg.InProgress()` | `+WPI` | Yes |\n| Terminate | `msg.term()` | `msg.Term()` | `+TERM` | No |\n| Terminate + reason | `msg.termWithReason(r)` | `msg.TermWithReason(r)` | `+TERM reason` | No |\n\n### Examples\n\n**Zig:**\n\n```zig\nfor (result.messages) |*msg| {\n    const data = msg.data();\n\n    if (isValid(data)) {\n        try msg.ack();\n    } else if (isRetryable(data)) {\n        // Retry after 5 seconds\n        try msg.nakWithDelay(5_000_000_000);\n    } else {\n        try msg.termWithReason(\"invalid payload\");\n    }\n}\n```\n\n**Go:**\n\n```go\nfor msg := range batch.Messages() {\n    data := msg.Data()\n\n    if isValid(data) {\n        msg.Ack()\n    } else if isRetryable(data) {\n        msg.NakWithDelay(5 * time.Second)\n    } else {\n        msg.TermWithReason(\"invalid payload\")\n    }\n}\n```\n\n### Extending the Ack Deadline\n\nFor long-running processing, send periodic `inProgress()` signals\nto prevent redelivery:\n\n**Zig:**\n\n```zig\ntry msg.inProgress(); // Reset ack timer\n// ... do work ...\ntry msg.inProgress(); // Reset again\n// ... finish work ...\ntry msg.ack();\n```\n\n### JsMsg Accessors\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `data()` | `[]const u8` | Message payload |\n| `subject()` | `[]const u8` | Original subject |\n| `headers()` | `?[]const u8` | Raw headers |\n| `replyTo()` | `?[]const u8` | Ack reply subject |\n| `deinit()` | `void` | Free message memory |\n\n---\n\n## Error Handling\n\nnats.zig uses a two-layer error system for JetStream:\n\n1. **Zig error unions** -- transport/protocol failures\n2. **ApiError struct** -- server-side JetStream errors\n\n### Layer 1: Zig Errors\n\n```zig\npub const Error = error{\n    Timeout,\n    NoResponders,\n    ApiError,\n    JsonParseError,\n    SubjectTooLong,\n    NoHeartbeat,\n    ConsumerDeleted,\n    OrderedReset,\n    InvalidKey,\n    InvalidData,\n    KeyNotFound,\n    WrongLastRevision,\n    ThreadSpawnFailed,\n};\n```\n\n### Layer 2: API Errors\n\nWhen `error.ApiError` is returned, call `js.lastApiError()` to\nget the server-side error details:\n\n**Zig:**\n\n```zig\nvar info = js.streamInfo(\"NONEXISTENT\");\nif (info) |*r| {\n    defer r.deinit();\n    // use r.value...\n} else |err| {\n    if (err == error.ApiError) {\n        if (js.lastApiError()) |api_err| {\n            // api_err.code       -- HTTP-like status (404)\n            // api_err.err_code   -- JetStream error code\n            // api_err.description() -- error message\n        }\n    }\n}\n```\n\n**Go:**\n\n```go\n_, err := js.Stream(ctx, \"NONEXISTENT\")\nif err != nil {\n    var jsErr jetstream.JetStreamError\n    if errors.As(err, &jsErr) {\n        apiErr := jsErr.APIError()\n        // apiErr.Code, apiErr.ErrorCode, apiErr.Description\n    }\n}\n```\n\n### Common Error Codes\n\n| Constant | Code | Meaning |\n|----------|------|---------|\n| `ErrCode.stream_not_found` | 10059 | Stream does not exist |\n| `ErrCode.stream_name_in_use` | 10058 | Stream name taken |\n| `ErrCode.consumer_not_found` | 10014 | Consumer does not exist |\n| `ErrCode.consumer_already_exists` | 10105 | Consumer name taken |\n| `ErrCode.js_not_enabled` | 10076 | JetStream not enabled |\n| `ErrCode.bad_request` | 10003 | Invalid request |\n| `ErrCode.stream_wrong_last_seq` | 10071 | Sequence mismatch |\n| `ErrCode.message_not_found` | 10037 | Message not in stream |\n\nFull list in `src/jetstream/errors.zig`.\n\n### Checking Specific Errors\n\n**Zig:**\n\n```zig\nconst ErrCode = nats.jetstream.errors.ErrCode;\n\nif (js.lastApiError()) |api_err| {\n    if (api_err.err_code == ErrCode.stream_not_found) {\n        // Handle missing stream\n    }\n}\n```\n\n**Go:**\n\n```go\nif errors.Is(err, jetstream.ErrStreamNotFound) {\n    // Handle missing stream\n}\n```\n\n---\n\n## Response Ownership\n\nEvery JetStream operation that returns a `Response(T)` owns\nparsed JSON memory. All string slices in `resp.value` point\ninto the parsed arena.\n\n**You must call `deinit()` when done:**\n\n```zig\nvar resp = try js.createStream(.{ .name = \"TEST\" });\ndefer resp.deinit(); // Frees parsed JSON arena\n\n// Access data through resp.value\nif (resp.value.config) |cfg| {\n    // cfg.name is valid until resp.deinit()\n}\n```\n\nIf you need to keep data beyond `deinit()`, copy it first:\n\n```zig\nvar resp = try js.streamInfo(\"TEST\");\nconst msg_count = resp.value.state.?.messages;\nresp.deinit(); // Safe -- msg_count is a u64 (copied)\n```\n\nString data requires explicit copying:\n\n```zig\nvar resp = try js.streamInfo(\"TEST\");\nconst name = try allocator.dupe(\n    u8,\n    resp.value.config.?.name,\n);\nresp.deinit(); // Safe -- name is independently owned\ndefer allocator.free(name);\n```\n\n---\n\n## Type Reference\n\n### Enums\n\n| Zig Enum | Values | Go Equivalent |\n|----------|--------|---------------|\n| `RetentionPolicy` | limits, interest, workqueue | `LimitsPolicy`, `InterestPolicy`, `WorkQueuePolicy` |\n| `StorageType` | file, memory | `FileStorage`, `MemoryStorage` |\n| `DiscardPolicy` | old, new | `DiscardOld`, `DiscardNew` |\n| `StoreCompression` | none, s2 | `NoCompression`, `S2Compression` |\n| `DeliverPolicy` | all, last, new, by_start_sequence, by_start_time, last_per_subject | `DeliverAllPolicy`, `DeliverLastPolicy`, ... |\n| `AckPolicy` | none, all, explicit | `AckNonePolicy`, `AckAllPolicy`, `AckExplicitPolicy` |\n| `ReplayPolicy` | instant, original | `ReplayInstantPolicy`, `ReplayOriginalPolicy` |\n\n### Key Differences from Go\n\n| Aspect | Zig (nats.zig) | Go (nats.go) |\n|--------|----------------|--------------|\n| Context | Stack struct, `try JetStream.init()` | Interface, `jetstream.New()` |\n| Timeout | `timeout_ms: u32` on JetStream | `context.Context` per call |\n| Responses | `Response(T)` with `defer deinit()` | Go GC handles memory |\n| Errors | `error.ApiError` + `lastApiError()` | `JetStreamError` interface |\n| Pull | `PullSubscription.fetch()` | `consumer.Fetch()` |\n| Options | Struct fields with `?T = null` | Functional options pattern |\n| Enums | Lowercase tags (`.file`) | PascalCase constants (`FileStorage`) |\n| Durations | Nanoseconds (`i64`) | `time.Duration` |\n\n### Duration Conversion\n\nJetStream JSON uses nanoseconds for all duration fields:\n\n```zig\n// 30 seconds\nconst thirty_sec: i64 = 30 * std.time.ns_per_s;\n\n// 5 minutes\nconst five_min: i64 = 5 * 60 * std.time.ns_per_s;\n\n// Use in config\nvar stream = try js.createStream(.{\n    .name = \"TEST\",\n    .max_age = five_min,\n    .duplicate_window = thirty_sec,\n});\n```\n"
  },
  {
    "path": "doc/nats-by-example/README.md",
    "content": "# NATS by Example\n\nPorts of [natsbyexample.com](https://natsbyexample.com) examples.\n\n| Example | Run | Server? |\n|---------|-----|---------|\n| [Pub-Sub](messaging/Pub-Sub.md) | `run-nbe-messaging-pub-sub` | Yes |\n| [Request-Reply](messaging/Request-Reply.md) | `run-nbe-messaging-request-reply` | Yes |\n| [JSON](messaging/Json.md) | `run-nbe-messaging-json` | Yes |\n| [Concurrent](messaging/Concurrent.md) | `run-nbe-messaging-concurrent` | Yes |\n| [Multiple Subscriptions](messaging/Iterating-Multiple-Subscriptions.md) | `run-nbe-messaging-iterating-multiple-subscriptions` | Yes |\n| [NKeys & JWTs](auth/NKeys-JWTs.md) | `run-nbe-auth-nkeys-jwts` | No |\n\n"
  },
  {
    "path": "doc/nats-by-example/auth/NKeys-JWTs.md",
    "content": "# NKeys and JWTs\n\nNATS supports decentralized authentication using a three-level trust hierarchy:\n\n- **Operator** - Top-level entity that manages accounts\n- **Account** - Groups users and defines resource limits\n- **User** - Authenticates to NATS with permissions\n\nEach entity has an NKey keypair (Ed25519). Operators sign account JWTs,\nand accounts sign user JWTs. This creates a chain of trust without\nrequiring a central authority.\n\n## How It Works\n\n1. Generate an **operator** keypair (prefix `SO`)\n2. Generate an **account** keypair (prefix `SA`)\n3. The operator signs an **account JWT** containing the account's public key\n4. Generate a **user** keypair (prefix `SU`)\n5. The account signs a **user JWT** with publish/subscribe permissions\n6. Format a **credentials file** (`.creds`) containing the user JWT and seed\n\nThe credentials file is what a NATS client uses to authenticate. The server\nvalidates the JWT signature chain back to a trusted operator.\n\n## Running\n\nNo NATS server required - this is a pure cryptography example.\n\n```sh\nzig build run-nbe-auth-nkeys-jwts\n```\n\n## Output\n\n```\n== Operator ==\noperator public key: <dynamic>\noperator seed:       <dynamic>\n\n== Account ==\naccount public key: <dynamic>\naccount seed:       <dynamic>\n\naccount JWT:\n<dynamic>\n\n== User ==\nuser public key: <dynamic>\nuser seed:       <dynamic>\n\nuser JWT:\n<dynamic>\n\n== Credentials File ==\n-----BEGIN NATS USER JWT-----\n<dynamic>\n------END NATS USER JWT------\n\n************************* IMPORTANT *************************\n  NKEY Seed printed below can be used to sign and prove identity.\n  NKEYs are sensitive and should be treated as secrets.\n\n  ************************************************************\n\n-----BEGIN USER NKEY SEED-----\n<dynamic>\n------END USER NKEY SEED------\n```\n\n## What's Happening\n\n1. Three NKey keypairs are generated: operator, account, and user. Each uses\n   Ed25519 with type-specific base32 prefixes (`SO`, `SA`, `SU`).\n2. An account JWT is created with default limits (unlimited) and signed by the\n   operator's private key. The JWT contains the account's public key as subject\n   and the operator's public key as issuer.\n3. A user JWT is created with publish permissions on `app.>` and subscribe\n   permissions on `app.>` and `_INBOX.>`, signed by the account's private key.\n4. A credentials file is formatted containing the user JWT and seed. This file\n   can be passed to a NATS client via `--creds` for authentication.\n\n## Source\n\nSee [nkeys-jwts.zig](nkeys-jwts.zig) for the full example.\n\nBased on [natsbyexample.com/examples/auth/nkeys-jwts](https://natsbyexample.com/examples/auth/nkeys-jwts/go).\n"
  },
  {
    "path": "doc/nats-by-example/auth/nkeys-jwts.zig",
    "content": "//! NKeys and JWTs\n//!\n//! This example demonstrates NATS decentralized authentication using\n//! the operator/account/user keypair hierarchy. It generates NKey\n//! keypairs, encodes JWTs, and formats a credentials file - all\n//! using pure Zig cryptography with zero external dependencies.\n//!\n//! No NATS server is needed - this is a pure cryptography example.\n//!\n//! Key concepts shown:\n//! - NKey generation for operator, account, and user entities\n//! - JWT encoding with Ed25519 signatures\n//! - Credentials file formatting for client authentication\n//! - The three-level trust hierarchy: operator > account > user\n//!\n//! Based on:\n//!   https://natsbyexample.com/examples/auth/nkeys-jwts/go\n//!\n//! Run with: zig build run-nbe-auth-nkeys-jwts\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const io = init.io;\n\n    // Set up buffered stdout writer\n    var stdout_buf: [8192]u8 = undefined;\n    var stdout_writer = std.Io.File.stdout().writer(\n        io,\n        &stdout_buf,\n    );\n    const stdout = &stdout_writer.interface;\n\n    // The operator is the top-level entity that manages\n    // accounts. It signs account JWTs.\n    try stdout.print(\n        \"== Operator ==\\n\",\n        .{},\n    );\n\n    var op_kp = nats.auth.KeyPair.generate(io, .operator);\n    defer op_kp.wipe();\n\n    var op_pk_buf: [56]u8 = undefined;\n    const op_pub = op_kp.publicKey(&op_pk_buf);\n    try stdout.print(\n        \"operator public key: {s}\\n\",\n        .{op_pub},\n    );\n\n    var op_seed_buf: [58]u8 = undefined;\n    const op_seed = op_kp.encodeSeed(&op_seed_buf);\n    try stdout.print(\n        \"operator seed:       {s}\\n\\n\",\n        .{op_seed},\n    );\n\n    // An account groups users and defines resource limits.\n    // The operator signs account JWTs.\n    try stdout.print(\n        \"== Account ==\\n\",\n        .{},\n    );\n\n    var acct_kp = nats.auth.KeyPair.generate(io, .account);\n    defer acct_kp.wipe();\n\n    var acct_pk_buf: [56]u8 = undefined;\n    const acct_pub = acct_kp.publicKey(&acct_pk_buf);\n    try stdout.print(\n        \"account public key: {s}\\n\",\n        .{acct_pub},\n    );\n\n    var acct_seed_buf: [58]u8 = undefined;\n    const acct_seed = acct_kp.encodeSeed(&acct_seed_buf);\n    try stdout.print(\n        \"account seed:       {s}\\n\\n\",\n        .{acct_seed},\n    );\n\n    // Encode account JWT (signed by operator)\n    const ts = std.Io.Timestamp.now(io, .real);\n    const iat: i64 = @intCast(\n        @as(u64, @intCast(ts.nanoseconds)) /\n            std.time.ns_per_s,\n    );\n\n    var acct_jwt_buf: [2048]u8 = undefined;\n    const acct_jwt = try nats.auth.jwt.encodeAccountClaims(\n        &acct_jwt_buf,\n        acct_pub,\n        \"my-account\",\n        op_kp,\n        iat,\n        .{},\n    );\n    try stdout.print(\n        \"account JWT:\\n{s}\\n\\n\",\n        .{acct_jwt},\n    );\n\n    // A user belongs to an account. The account signs\n    // user JWTs with publish/subscribe permissions.\n    try stdout.print(\n        \"== User ==\\n\",\n        .{},\n    );\n\n    var user_kp = nats.auth.KeyPair.generate(io, .user);\n    defer user_kp.wipe();\n\n    var user_pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&user_pk_buf);\n    try stdout.print(\n        \"user public key: {s}\\n\",\n        .{user_pub},\n    );\n\n    var user_seed_buf: [58]u8 = undefined;\n    const user_seed = user_kp.encodeSeed(&user_seed_buf);\n    try stdout.print(\n        \"user seed:       {s}\\n\\n\",\n        .{user_seed},\n    );\n\n    // Encode user JWT with permissions (signed by account)\n    var user_jwt_buf: [2048]u8 = undefined;\n    const user_jwt = try nats.auth.jwt.encodeUserClaims(\n        &user_jwt_buf,\n        user_pub,\n        \"my-user\",\n        acct_kp,\n        iat,\n        .{\n            .pub_allow = &.{\"app.>\"},\n            .sub_allow = &.{ \"app.>\", \"_INBOX.>\" },\n        },\n    );\n    try stdout.print(\n        \"user JWT:\\n{s}\\n\\n\",\n        .{user_jwt},\n    );\n\n    // Format a .creds file containing the user JWT and seed.\n    // This file is what a NATS client uses to authenticate.\n    try stdout.print(\n        \"== Credentials File ==\\n\",\n        .{},\n    );\n\n    var creds_buf: [4096]u8 = undefined;\n    const creds = nats.auth.creds.format(\n        &creds_buf,\n        user_jwt,\n        user_seed,\n    ) catch return;\n    try stdout.print(\"{s}\\n\", .{creds});\n\n    try stdout.flush();\n}\n"
  },
  {
    "path": "doc/nats-by-example/messaging/Concurrent.md",
    "content": "# Concurrent Message Processing\n\nBy default, messages from a subscription are processed sequentially -\neach message must finish before the next one starts. For workloads\nwhere message processing takes variable time (API calls, database\nqueries, computation), concurrent processing can significantly improve\nthroughput.\n\nThis example uses `io.concurrent()` to spawn worker threads that\nprocess messages in parallel. Each worker simulates variable\nprocessing time with a random delay, causing messages to complete\nout of their original order.\n\n## Running\n\nPrerequisites: `nats-server` running on `localhost:4222`.\n\n```sh\nnats-server &\nzig build run-nbe-messaging-concurrent\n```\n\n## Output (order varies per run)\n\n```\nreceived message: \"hello 3\"\nreceived message: \"hello 0\"\nreceived message: \"hello 7\"\nreceived message: \"hello 1\"\nreceived message: \"hello 5\"\nreceived message: \"hello 8\"\nreceived message: \"hello 4\"\nreceived message: \"hello 9\"\nreceived message: \"hello 6\"\nreceived message: \"hello 2\"\n\nprocessed 10 messages concurrently\n```\n\n**Note**: The message order is non-deterministic. Each run produces\na different sequence because workers process with random delays.\n\n## What's Happening\n\n1. 10 messages are published to `greet.joe`.\n2. All 10 are received on the main thread and copied into work items.\n3. Three concurrent workers are spawned via `io.concurrent()`.\n4. Each worker processes its assigned messages with a random delay\n   (0-100ms) to simulate variable work.\n5. Workers write directly to stdout using `writeStreamingAll` (atomic\n   per-line writes avoid interleaved output).\n6. The main thread waits for all workers to complete.\n\n## Source\n\nSee [concurrent.zig](concurrent.zig) for the full example.\n\nBased on [natsbyexample.com/examples/messaging/concurrent](https://natsbyexample.com/examples/messaging/concurrent/rust).\n"
  },
  {
    "path": "doc/nats-by-example/messaging/Iterating-Multiple-Subscriptions.md",
    "content": "# Iterating Over Multiple Subscriptions\n\nNATS wildcards cover many routing cases, but sometimes you need\nseparate subscriptions. For example, you want `transport.cars`,\n`transport.planes`, and `transport.ships` but not\n`transport.spaceships`.\n\nThis example shows how to poll multiple subscriptions in a unified\nloop using `tryNext()` - Zig's equivalent of merging async streams\ninto one iteration. Messages from all subscriptions are processed\nin round-robin fashion without blocking.\n\n## Running\n\nPrerequisites: `nats-server` running on `localhost:4222`.\n\n```sh\nnats-server &\nzig build run-nbe-messaging-iterating-multiple-subscriptions\n```\n\n## Output\n\n```\nreceived on cars.0: car number 0\nreceived on planes.0: plane number 0\nreceived on ships.0: ship number 0\nreceived on cars.1: car number 1\nreceived on planes.1: plane number 1\nreceived on ships.1: ship number 1\n...\nreceived on cars.9: car number 9\nreceived on planes.9: plane number 9\nreceived on ships.9: ship number 9\n\nreceived 30 messages from 3 subscriptions\n```\n\n## What's Happening\n\n1. Three separate subscriptions are created: `cars.>`, `planes.>`,\n   and `ships.>`.\n2. 10 messages are published to each category (30 total).\n3. All three subscriptions are polled in round-robin using\n   `tryNext()` which returns instantly if no message is available.\n4. Each message's subject and payload are printed as received.\n5. A short sleep avoids busy-spinning when no messages are ready.\n\n## Source\n\nSee [iterating-multiple-subscriptions.zig](iterating-multiple-subscriptions.zig)\nfor the full example.\n\nBased on [natsbyexample.com/examples/messaging/iterating-multiple-subscriptions](https://natsbyexample.com/examples/messaging/iterating-multiple-subscriptions/rust).\n"
  },
  {
    "path": "doc/nats-by-example/messaging/Json.md",
    "content": "# JSON for Message Payloads\n\nNATS message payloads are opaque byte sequences. It is up to the\napplication to define serialization. JSON is a natural choice for\ncross-language compatibility.\n\nZig's `std.json` provides compile-time type-safe serialization and\ndeserialization:\n\n- **Serialize**: `std.json.Stringify.value(struct, options, writer)`\n  writes JSON to any `Io.Writer` (including fixed-buffer writers).\n- **Deserialize**: `std.json.parseFromSlice(T, allocator, data, options)`\n  parses JSON bytes into a typed struct, returning an error for\n  invalid input.\n\n## Running\n\nPrerequisites: `nats-server` running on `localhost:4222`.\n\n```sh\nnats-server &\nzig build run-nbe-messaging-json\n```\n\n## Output\n\n```\nreceived valid payload: foo=bar, bar=27\nreceived invalid payload: not json\n```\n\n## What's Happening\n\n1. A `Payload` struct is defined with `foo` (string) and `bar` (int)\n   fields.\n2. An instance is serialized to JSON using `Stringify.value` into a\n   stack-allocated buffer - no heap allocation needed.\n3. The JSON bytes are published to the `greet` subject.\n4. A second message with invalid content (`\"not json\"`) is published.\n5. The receiver tries `parseFromSlice` on each message. The first\n   succeeds and prints the deserialized fields. The second fails\n   gracefully and prints the raw payload.\n\n## Source\n\nSee [json.zig](json.zig) for the full example.\n\nBased on [natsbyexample.com/examples/messaging/json](https://natsbyexample.com/examples/messaging/json/go).\n"
  },
  {
    "path": "doc/nats-by-example/messaging/Pub-Sub.md",
    "content": "# Publish-Subscribe\n\nNATS implements publish-subscribe message distribution through subject-based\nrouting. Publishers send messages to named subjects. Subscribers express\ninterest in subjects (including wildcards) and receive matching messages.\n\nThe core guarantee is **at-most-once delivery**: if there is no subscriber\nlistening when a message is published, the message is silently discarded.\nThis is similar to UDP or MQTT QoS 0. For stronger delivery guarantees, see\nJetStream.\n\n## Wildcard Subscriptions\n\nNATS supports two wildcard tokens in subscriptions:\n\n- `*` matches a single token: `greet.*` matches `greet.joe`, `greet.pam`\n- `>` matches one or more tokens: `greet.>` matches `greet.joe`,\n  `greet.joe.hello`\n\n## Running\n\nPrerequisites: `nats-server` running on `localhost:4222`.\n\n```sh\nnats-server &\nzig build run-nbe-messaging-pub-sub\n```\n\n## Output\n\n```\nsubscribed after a publish...\nmsg is null? true\nmsg data: \"hello\" on subject \"greet.joe\"\nmsg data: \"hello\" on subject \"greet.pam\"\nmsg data: \"hello\" on subject \"greet.bob\"\n```\n\n## What's Happening\n\n1. A message is published to `greet.joe` **before** any subscription exists.\n   This message is lost - at-most-once delivery means no buffering.\n2. A wildcard subscription on `greet.*` is created.\n3. Attempting to receive returns `null` - the earlier message is gone.\n4. Two messages are published to `greet.joe` and `greet.pam`. Both are\n   received because the subscription is now active and the wildcard matches.\n5. A third message to `greet.bob` is also received via the same wildcard.\n\n## Source\n\nSee [pub-sub.zig](pub-sub.zig) for the full example.\n\nBased on [natsbyexample.com/examples/messaging/pub-sub](https://natsbyexample.com/examples/messaging/pub-sub/go).\n"
  },
  {
    "path": "doc/nats-by-example/messaging/README.md",
    "content": "# Messaging\n\nExamples based on the [natsbyexample.com](https://natsbyexample.com/)\n**Messaging** category, implemented in Zig using the nats.zig client.\n\n## Building and Running\n\nPrerequisites: a `nats-server` running on `localhost:4222`.\n\n```sh\nnats-server &\n```\n\nEach example is a standalone executable. Build and run with:\n\n```sh\nzig build run-nbe-messaging-<example-name>\n```\n\nFor example:\n\n```sh\nzig build run-nbe-messaging-pub-sub\nzig build run-nbe-messaging-request-reply\n```\n\n## Examples\n\n| Example | Description | Source |\n|---------|-------------|--------|\n| [Publish-Subscribe](Pub-Sub.md) | Subject-based pub/sub with wildcard routing and at-most-once delivery | [pub-sub.zig](pub-sub.zig) |\n| [Request-Reply](Request-Reply.md) | RPC-style communication using temporary inbox subjects | [request-reply.zig](request-reply.zig) |\n| [JSON for Message Payloads](Json.md) | Type-safe JSON serialization/deserialization with `std.json` | [json.zig](json.zig) |\n| [Concurrent Message Processing](Concurrent.md) | Parallel message processing with `io.concurrent()` worker threads | [concurrent.zig](concurrent.zig) |\n| [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) |\n"
  },
  {
    "path": "doc/nats-by-example/messaging/Request-Reply.md",
    "content": "# Request-Reply\n\nThe request-reply pattern enables RPC-style communication over NATS.\nUnder the hood, NATS implements this as an optimized pair of\npublish-subscribe operations: the requester creates a temporary inbox\nsubject, subscribes to it, and publishes the request with a `reply_to`\nheader pointing at that inbox.\n\nUnlike strict point-to-point protocols, multiple subscribers can\npotentially respond to a request. The client receives the first reply\nand discards the rest.\n\nWhen no handler is subscribed, the server sends a \"no responders\"\nnotification (status 503) instead of silently timing out.\n\n## Running\n\nPrerequisites: `nats-server` running on `localhost:4222`.\n\n```sh\nnats-server &\nzig build run-nbe-messaging-request-reply\n```\n\n## Output\n\n```\nhello, joe\nhello, sue\nhello, bob\nno responders\n```\n\n## What's Happening\n\n1. A subscription on `greet.*` handles incoming requests in a\n   background async task.\n2. The handler extracts the name from the subject (`greet.joe` ->\n   `joe`) and responds with `\"hello, joe\"`.\n3. Three requests are made - `greet.joe`, `greet.sue`, `greet.bob` -\n   each receiving a personalized greeting.\n4. The handler subscription is unsubscribed.\n5. A fourth request to `greet.joe` returns \"no responders\" because\n   no handler is listening anymore.\n\n## Source\n\nSee [request-reply.zig](request-reply.zig) for the full example.\n\nBased on [natsbyexample.com/examples/messaging/request-reply](https://natsbyexample.com/examples/messaging/request-reply/go).\n"
  },
  {
    "path": "doc/nats-by-example/messaging/concurrent.zig",
    "content": "//! Concurrent Message Processing\n//!\n//! By default, messages from a subscription are processed\n//! sequentially. This example shows how to process messages\n//! concurrently using multiple worker threads.\n//!\n//! The pattern: receive messages on the main thread, dispatch\n//! them to concurrent workers via an Io.Queue, and collect\n//! results. Each worker simulates variable processing time\n//! with a random delay, causing messages to complete out of\n//! their original order.\n//!\n//! Based on: https://natsbyexample.com/examples/messaging/concurrent/rust\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server\n//!\n//! Run with: zig build run-nbe-messaging-concurrent\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Io = std.Io;\n\nconst NUM_MSGS = 10;\nconst NUM_WORKERS = 3;\n\n/// Work item passed from main thread to workers.\n/// Contains a copy of the message data (the original\n/// Message is freed after copying).\nconst WorkItem = struct {\n    data: [64]u8 = undefined,\n    len: usize = 0,\n};\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    const io = init.io;\n\n    var stdout_buf: [4096]u8 = undefined;\n    var stdout_writer = Io.File.stdout().writer(\n        io,\n        &stdout_buf,\n    );\n    const stdout = &stdout_writer.interface;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    const sub = try client.subscribeSync(\"greet.*\");\n    defer sub.deinit();\n\n    // Publish 10 messages\n    for (0..NUM_MSGS) |i| {\n        var buf: [32]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"hello {d}\",\n            .{i},\n        ) catch continue;\n        try client.publish(\"greet.joe\", payload);\n    }\n\n    // Wait for messages to arrive\n    io.sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Receive all messages and copy data into work items.\n    // We copy because the Message backing buffer is freed\n    // on deinit, but workers need the data later.\n    var items: [NUM_MSGS]WorkItem = @splat(WorkItem{});\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        if (try sub.nextMsgTimeout(\n            1000,\n        )) |msg| {\n            defer msg.deinit();\n            const len = @min(msg.data.len, 64);\n            @memcpy(\n                items[received].data[0..len],\n                msg.data[0..len],\n            );\n            items[received].len = len;\n            received += 1;\n        }\n    }\n\n    // Dispatch work to 3 concurrent workers. Each worker\n    // gets a slice of the items array to process.\n    // io.concurrent() ensures true parallel execution.\n    const slice1_end = received / 3;\n    const slice2_end = (received * 2) / 3;\n\n    var w1 = try io.concurrent(processWorker, .{\n        io,\n        items[0..slice1_end],\n    });\n    defer w1.cancel(io);\n\n    var w2 = try io.concurrent(processWorker, .{\n        io,\n        items[slice1_end..slice2_end],\n    });\n    defer w2.cancel(io);\n\n    // Third worker runs on this thread (no extra thread needed)\n    processWorker(io, items[slice2_end..received]);\n\n    // Wait for concurrent workers to finish\n    w1.await(io);\n    w2.await(io);\n\n    try stdout.print(\n        \"\\nprocessed {d} messages concurrently\\n\",\n        .{received},\n    );\n    try stdout.flush();\n}\n\n/// Worker function that processes a slice of work items.\n/// Each item is \"processed\" with a random delay to simulate\n/// variable work, then printed. The random delays cause\n/// messages to complete out of their original order.\nfn processWorker(io: Io, items: []WorkItem) void {\n    const file_stdout = Io.File.stdout();\n    for (items) |item| {\n        // Random delay 0-100ms to simulate processing\n        var rnd: [1]u8 = undefined;\n        io.random(&rnd);\n        const delay_ms: i64 = @intCast(rnd[0] % 100);\n        io.sleep(\n            .fromMilliseconds(delay_ms),\n            .awake,\n        ) catch {};\n\n        // Write directly to stdout (single write syscall\n        // per line avoids interleaved output)\n        var buf: [80]u8 = undefined;\n        const line = std.fmt.bufPrint(\n            &buf,\n            \"received message: \\\"{s}\\\"\\n\",\n            .{item.data[0..item.len]},\n        ) catch continue;\n        file_stdout.writeStreamingAll(io, line) catch {};\n    }\n}\n"
  },
  {
    "path": "doc/nats-by-example/messaging/iterating-multiple-subscriptions.zig",
    "content": "//! Iterating Over Multiple Subscriptions\n//!\n//! NATS wildcards cover many routing cases, but sometimes you\n//! need separate subscriptions - for example, you want\n//! \"transport.cars\", \"transport.planes\", and \"transport.ships\"\n//! but NOT \"transport.spaceships\".\n//!\n//! This example shows how to poll multiple subscriptions in\n//! a unified loop using tryNext() - the Zig equivalent of\n//! merging multiple async streams.\n//!\n//! Based on: https://natsbyexample.com/examples/messaging/iterating-multiple-subscriptions/rust\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server\n//!\n//! Run with: zig build run-nbe-messaging-iterating-multiple-subscriptions\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Io = std.Io;\nconst NUM_MSGS_PER_CATEGORY = 10;\nconst TOTAL_MSGS = NUM_MSGS_PER_CATEGORY * 3;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    const io = init.io;\n\n    var stdout_buf: [8192]u8 = undefined;\n    var stdout_writer = Io.File.stdout().writer(\n        io,\n        &stdout_buf,\n    );\n    const stdout = &stdout_writer.interface;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    // Create three separate subscriptions. We use \">\"\n    // (multi-level wildcard) to match all sub-subjects.\n    const sub_cars = try client.subscribeSync(\"cars.>\");\n    defer sub_cars.deinit();\n\n    const sub_planes = try client.subscribeSync(\"planes.>\");\n    defer sub_planes.deinit();\n\n    const sub_ships = try client.subscribeSync(\"ships.>\");\n    defer sub_ships.deinit();\n\n    // Publish 10 messages to each category\n    for (0..NUM_MSGS_PER_CATEGORY) |i| {\n        var buf: [64]u8 = undefined;\n\n        const cars_subj = std.fmt.bufPrint(\n            &buf,\n            \"cars.{d}\",\n            .{i},\n        ) catch continue;\n        var payload_buf: [64]u8 = undefined;\n        const cars_payload = std.fmt.bufPrint(\n            &payload_buf,\n            \"car number {d}\",\n            .{i},\n        ) catch continue;\n        try client.publish(cars_subj, cars_payload);\n\n        const planes_subj = std.fmt.bufPrint(\n            &buf,\n            \"planes.{d}\",\n            .{i},\n        ) catch continue;\n        const planes_payload = std.fmt.bufPrint(\n            &payload_buf,\n            \"plane number {d}\",\n            .{i},\n        ) catch continue;\n        try client.publish(planes_subj, planes_payload);\n\n        const ships_subj = std.fmt.bufPrint(\n            &buf,\n            \"ships.{d}\",\n            .{i},\n        ) catch continue;\n        const ships_payload = std.fmt.bufPrint(\n            &payload_buf,\n            \"ship number {d}\",\n            .{i},\n        ) catch continue;\n        try client.publish(ships_subj, ships_payload);\n    }\n\n    // Wait for messages to arrive\n    io.sleep(.fromMilliseconds(100), .awake) catch {};\n\n    // Poll all 3 subscriptions in round-robin fashion.\n    // tryNext() is non-blocking - returns null instantly if\n    // no message is available, letting us cycle to the next\n    // subscription without waiting.\n    const subs = [_]*nats.Client.Sub{\n        sub_cars,\n        sub_planes,\n        sub_ships,\n    };\n    var total: u32 = 0;\n    var idx: usize = 0;\n    var empty_cycles: u32 = 0;\n\n    while (total < TOTAL_MSGS) {\n        if (subs[idx].tryNextMsg()) |msg| {\n            defer msg.deinit();\n            total += 1;\n            empty_cycles = 0;\n            try stdout.print(\n                \"received on {s}: {s}\\n\",\n                .{ msg.subject, msg.data },\n            );\n        }\n        idx = (idx + 1) % subs.len;\n\n        // Avoid busy-spinning when no messages are ready\n        if (idx == 0) {\n            empty_cycles += 1;\n            if (empty_cycles > 10) {\n                io.sleep(\n                    .fromMilliseconds(10),\n                    .awake,\n                ) catch {};\n            }\n        }\n\n        // Safety: don't spin forever if messages are lost\n        if (empty_cycles > 100) break;\n    }\n\n    try stdout.print(\n        \"\\nreceived {d} messages from 3 subscriptions\\n\",\n        .{total},\n    );\n    try stdout.flush();\n}\n"
  },
  {
    "path": "doc/nats-by-example/messaging/json.zig",
    "content": "//! JSON for Message Payloads\n//!\n//! NATS message payloads are opaque byte sequences - the application\n//! decides how to serialize and deserialize them. JSON is a common\n//! choice for its cross-language compatibility and readability.\n//!\n//! This example demonstrates:\n//! - Defining a struct type for the message payload\n//! - Serializing a struct to JSON using Stringify.value\n//! - Receiving and deserializing JSON back to a struct\n//! - Gracefully handling invalid JSON payloads\n//!\n//! Based on: https://natsbyexample.com/examples/messaging/json/go\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server\n//!\n//! Run with: zig build run-nbe-messaging-json\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Io = std.Io;\n\n/// Application payload type. Zig's std.json will serialize\n/// field names directly (\"foo\", \"bar\").\nconst Payload = struct {\n    foo: []const u8,\n    bar: i32,\n};\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    const io = init.io;\n\n    var stdout_buf: [4096]u8 = undefined;\n    var stdout_writer = Io.File.stdout().writer(\n        io,\n        &stdout_buf,\n    );\n    const stdout = &stdout_writer.interface;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    const sub = try client.subscribeSync(\"greet\");\n    defer sub.deinit();\n\n    // Create a payload and serialize it to JSON.\n    // Stringify.value writes JSON into a fixed buffer writer -\n    // no heap allocation needed.\n    const payload = Payload{ .foo = \"bar\", .bar = 27 };\n    var json_buf: [256]u8 = undefined;\n    var json_writer = Io.Writer.fixed(&json_buf);\n    try std.json.Stringify.value(\n        payload,\n        .{},\n        &json_writer,\n    );\n    const json = json_writer.buffered();\n\n    // Publish the valid JSON payload\n    try client.publish(\"greet\", json);\n\n    // Publish an invalid (non-JSON) payload\n    try client.publish(\"greet\", \"not json\");\n\n    // Receive the first message - valid JSON.\n    // parseFromSlice deserializes it back into a Payload struct.\n    if (try sub.nextMsgTimeout(1000)) |msg| {\n        defer msg.deinit();\n        if (std.json.parseFromSlice(\n            Payload,\n            allocator,\n            msg.data,\n            .{},\n        )) |parsed| {\n            defer parsed.deinit();\n            try stdout.print(\n                \"received valid payload: \" ++\n                    \"foo={s}, bar={d}\\n\",\n                .{ parsed.value.foo, parsed.value.bar },\n            );\n        } else |_| {\n            try stdout.print(\n                \"received invalid payload: {s}\\n\",\n                .{msg.data},\n            );\n        }\n    }\n\n    // Receive the second message - invalid JSON.\n    // parseFromSlice returns an error, so we print raw data.\n    if (try sub.nextMsgTimeout(1000)) |msg| {\n        defer msg.deinit();\n        if (std.json.parseFromSlice(\n            Payload,\n            allocator,\n            msg.data,\n            .{},\n        )) |parsed| {\n            defer parsed.deinit();\n            try stdout.print(\n                \"received valid payload: \" ++\n                    \"foo={s}, bar={d}\\n\",\n                .{ parsed.value.foo, parsed.value.bar },\n            );\n        } else |_| {\n            try stdout.print(\n                \"received invalid payload: {s}\\n\",\n                .{msg.data},\n            );\n        }\n    }\n\n    try stdout.flush();\n}\n"
  },
  {
    "path": "doc/nats-by-example/messaging/pub-sub.zig",
    "content": "//! Publish-Subscribe\n//!\n//! This example demonstrates the core NATS publish-subscribe pattern.\n//! Pub/Sub is the fundamental messaging pattern in NATS where publishers\n//! send messages to subjects and subscribers receive them.\n//!\n//! Key concepts shown:\n//! - At-most-once delivery: if no subscriber is listening, messages\n//!   are silently discarded (like UDP, or MQTT QoS 0)\n//! - Wildcard subscriptions: \"greet.*\" matches \"greet.joe\",\n//!   \"greet.pam\", etc.\n//! - Subject-based routing: messages are routed by their subject\n//!\n//! Based on: https://natsbyexample.com/examples/messaging/pub-sub/go\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server\n//!\n//! Run with: zig build run-nbe-messaging-pub-sub\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    const io = init.io;\n\n    // Set up buffered stdout writer for output\n    var stdout_buf: [4096]u8 = undefined;\n    var stdout_writer = std.Io.File.stdout().writer(\n        io,\n        &stdout_buf,\n    );\n    const stdout = &stdout_writer.interface;\n\n    // Connect to NATS server\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    // Publish a message BEFORE subscribing.\n    // This message will be lost because NATS provides\n    // at-most-once delivery - there are no subscribers\n    // listening on this subject yet.\n    try client.publish(\"greet.joe\", \"hello\");\n\n    // Subscribe using a wildcard subject. \"greet.*\" will\n    // match any subject with exactly one token after \"greet.\",\n    // for example: \"greet.joe\", \"greet.pam\", \"greet.bob\"\n    const sub = try client.subscribeSync(\"greet.*\");\n    defer sub.deinit();\n\n    // Try to receive the message published before subscribing.\n    // The short timeout (10ms) confirms no message is available -\n    // it was published before our subscription existed.\n    const msg = try sub.nextMsgTimeout(10);\n    try stdout.print(\"subscribed after a publish...\\n\", .{});\n    try stdout.print(\"msg is null? {}\\n\", .{msg == null});\n    try stdout.flush();\n\n    // Now publish two messages AFTER subscribing.\n    // These will be received because the subscription is active.\n    try client.publish(\"greet.joe\", \"hello\");\n    try client.publish(\"greet.pam\", \"hello\");\n\n    // Receive both messages. The wildcard subscription\n    // matches both \"greet.joe\" and \"greet.pam\".\n    if (try sub.nextMsgTimeout(1000)) |m| {\n        defer m.deinit();\n        try stdout.print(\n            \"msg data: \\\"{s}\\\" on subject \\\"{s}\\\"\\n\",\n            .{ m.data, m.subject },\n        );\n    }\n    if (try sub.nextMsgTimeout(1000)) |m| {\n        defer m.deinit();\n        try stdout.print(\n            \"msg data: \\\"{s}\\\" on subject \\\"{s}\\\"\\n\",\n            .{ m.data, m.subject },\n        );\n    }\n\n    // Publish one more to a different subject that still\n    // matches our wildcard pattern.\n    try client.publish(\"greet.bob\", \"hello\");\n\n    if (try sub.nextMsgTimeout(1000)) |m| {\n        defer m.deinit();\n        try stdout.print(\n            \"msg data: \\\"{s}\\\" on subject \\\"{s}\\\"\\n\",\n            .{ m.data, m.subject },\n        );\n    }\n\n    try stdout.flush();\n}\n"
  },
  {
    "path": "doc/nats-by-example/messaging/request-reply.zig",
    "content": "//! Request-Reply\n//!\n//! The request-reply pattern allows a client to send a request and\n//! wait for a response. Under the hood, NATS implements this as an\n//! optimized pair of publish-subscribe operations using an auto-\n//! generated inbox subject for the reply.\n//!\n//! Key concepts shown:\n//! - Subscribing to handle requests in a background task\n//! - Extracting info from the subject (e.g. a name)\n//! - Responding to requests with msg.respond()\n//! - Detecting \"no responders\" when no handler is available\n//!\n//! Based on: https://natsbyexample.com/examples/messaging/request-reply/go\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server\n//!\n//! Run with: zig build run-nbe-messaging-request-reply\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    const io = init.io;\n\n    var stdout_buf: [4096]u8 = undefined;\n    var stdout_writer = std.Io.File.stdout().writer(\n        io,\n        &stdout_buf,\n    );\n    const stdout = &stdout_writer.interface;\n\n    // Connect to NATS\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    // Subscribe to \"greet.*\" to handle incoming requests.\n    // The handler extracts the name from the subject and\n    // responds with a greeting.\n    const sub = try client.subscribeSync(\"greet.*\");\n    defer sub.deinit();\n\n    // Run the request handler in a background async task.\n    // It will process exactly 3 requests then exit.\n    var handler = io.async(handleRequests, .{\n        client,\n        sub,\n    });\n    defer handler.cancel(io);\n\n    // Give the subscription time to register on the server\n    io.sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Send 3 requests - each will be handled by our\n    // background task and we'll get a personalized greeting.\n    if (try client.request(\n        \"greet.joe\",\n        \"\",\n        1000,\n    )) |reply| {\n        defer reply.deinit();\n        if (reply.isNoResponders()) {\n            try stdout.print(\"no responders\\n\", .{});\n        } else {\n            try stdout.print(\"{s}\\n\", .{reply.data});\n        }\n    }\n\n    if (try client.request(\n        \"greet.sue\",\n        \"\",\n        1000,\n    )) |reply| {\n        defer reply.deinit();\n        if (reply.isNoResponders()) {\n            try stdout.print(\"no responders\\n\", .{});\n        } else {\n            try stdout.print(\"{s}\\n\", .{reply.data});\n        }\n    }\n\n    if (try client.request(\n        \"greet.bob\",\n        \"\",\n        1000,\n    )) |reply| {\n        defer reply.deinit();\n        if (reply.isNoResponders()) {\n            try stdout.print(\"no responders\\n\", .{});\n        } else {\n            try stdout.print(\"{s}\\n\", .{reply.data});\n        }\n    }\n\n    // Unsubscribe the handler so no one is listening anymore\n    try sub.unsubscribe();\n\n    // This request will fail with \"no responders\" because\n    // we just unsubscribed the only handler.\n    if (try client.request(\n        \"greet.joe\",\n        \"\",\n        1000,\n    )) |reply| {\n        defer reply.deinit();\n        if (reply.isNoResponders()) {\n            try stdout.print(\"no responders\\n\", .{});\n        } else {\n            try stdout.print(\"{s}\\n\", .{reply.data});\n        }\n    }\n\n    try stdout.flush();\n}\n\n/// Background handler that processes incoming requests.\n/// Extracts the name from the subject (\"greet.joe\" -> \"joe\")\n/// and responds with \"hello, <name>\".\nfn handleRequests(\n    client: *nats.Client,\n    sub: *nats.Client.Sub,\n) void {\n    for (0..3) |_| {\n        const req = sub.nextMsgTimeout(\n            2000,\n        ) catch return;\n        if (req) |r| {\n            defer r.deinit();\n            // \"greet.joe\" -> \"joe\"\n            const name = r.subject[6..];\n            var buf: [64]u8 = undefined;\n            const reply = std.fmt.bufPrint(\n                &buf,\n                \"hello, {s}\",\n                .{name},\n            ) catch return;\n            r.respond(client, reply) catch {};\n        }\n    }\n}\n"
  },
  {
    "path": "src/Client.zig",
    "content": "//! NATS Client\n//!\n//! High-level client API for connecting to NATS servers.\n//! Uses std.Io for native async I/O with concurrent subscription support.\n//!\n//! Key features:\n//! - Dedicated reader task routes messages to per-subscription Io.Queue\n//! - Multiple subscriptions can call nextMsg() concurrently\n//! - Reader task starts automatically on connect\n//! - Colorblind async: works blocking or async based on Io implementation\n//!\n//! Connection-scoped: Allocator, Io, Reader, Writer stored for lifetime.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\n\nconst Io = std.Io;\nconst net = Io.net;\nconst tls = std.crypto.tls;\nconst Certificate = std.crypto.Certificate;\n\nconst protocol = @import(\"protocol.zig\");\nconst Parser = protocol.Parser;\nconst ServerInfo = protocol.ServerInfo;\nconst connection = @import(\"connection.zig\");\nconst State = connection.State;\nconst pubsub = @import(\"pubsub.zig\");\nconst subscription_mod = @import(\"pubsub/subscription.zig\");\nconst memory = @import(\"memory.zig\");\nconst SidMap = memory.SidMap;\nconst TieredSlab = memory.TieredSlab;\nconst SpscQueue = @import(\"sync/spsc_queue.zig\").SpscQueue;\nconst byte_ring = @import(\"sync/byte_ring.zig\");\npub const ByteRing = byte_ring.ByteRing;\nconst RING_HDR_SIZE = byte_ring.HDR_SIZE;\nconst SpinLock = @import(\"sync/spin_lock.zig\").SpinLock;\nconst dbg = @import(\"dbg.zig\");\nconst defaults = @import(\"defaults.zig\");\nconst events_mod = @import(\"events.zig\");\npub const Event = events_mod.Event;\npub const EventHandler = events_mod.EventHandler;\n\nconst headers = @import(\"protocol/headers.zig\");\npub const HeaderEntry = headers.Entry;\npub const HeaderMap = protocol.HeaderMap;\n\nconst nkey_auth = @import(\"auth.zig\");\nconst creds_auth = nkey_auth.creds;\n\nconst Client = @This();\n\n/// Type-erased message handler for callback subscriptions.\n/// Uses comptime vtable pattern (same as EventHandler).\n///\n/// ```zig\n/// const MyHandler = struct {\n///     counter: *u32,\n///     pub fn onMessage(self: *@This(), msg: *const Message) void {\n///         self.counter.* += 1;\n///     }\n/// };\n/// var handler = MyHandler{ .counter = &count };\n/// const sub = try client.subscribe(\n///     \"subject\", MsgHandler.init(MyHandler, &handler),\n/// );\n/// ```\npub const MsgHandler = struct {\n    ptr: *anyopaque,\n    vtable: *const VTable,\n\n    pub const VTable = struct {\n        onMessage: *const fn (*anyopaque, *const Message) void,\n    };\n\n    /// Create handler from concrete type using comptime.\n    pub fn init(comptime T: type, ptr: *T) MsgHandler {\n        const gen = struct {\n            fn onMessage(p: *anyopaque, msg: *const Message) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onMessage(msg);\n            }\n        };\n        return .{\n            .ptr = ptr,\n            .vtable = &.{ .onMessage = gen.onMessage },\n        };\n    }\n\n    /// Dispatch message to handler.\n    pub fn dispatch(\n        self: MsgHandler,\n        msg: *const Message,\n    ) void {\n        self.vtable.onMessage(self.ptr, msg);\n    }\n};\n\n/// Checks if a file descriptor is valid using fcntl.\n/// Returns false for negative fds or closed fds.\n/// Used to guard shutdown/close calls that panic on\n/// BADF in debug mode (Io.Threaded treats as bug).\nfn isValidFd(fd: std.posix.fd_t) bool {\n    if (fd < 0) return false;\n    const F_GETFD = 1;\n    const rc = std.posix.system.fcntl(fd, F_GETFD, 0);\n    // fcntl returns the fd flags on success, or a\n    // large unsigned value (wrapped errno) on failure\n    return rc < 0x1000;\n}\n\n/// Gets current monotonic time in nanoseconds.\nfn getNowNs(io: Io) u64 {\n    const ts = Io.Timestamp.now(io, .awake);\n    return @intCast(ts.nanoseconds);\n}\n\n/// Message received on a subscription. Call deinit() to free.\npub const Message = struct {\n    subject: []const u8,\n    sid: u64,\n    reply_to: ?[]const u8,\n    data: []const u8,\n    headers: ?[]const u8,\n    allocator: Allocator = undefined,\n    owned: bool = true,\n    /// Single backing buffer (all slices point into this).\n    backing_buf: ?[]u8 = null,\n    /// Return queue for thread-safe deallocation (reader thread frees).\n    return_queue: ?*SpscQueue([]u8) = null,\n    /// Spinlock for multi-thread return_queue.push() safety.\n    return_lock: ?*SpinLock = null,\n\n    /// Frees message data. Pushes to return queue for slab-allocated msgs.\n    /// Thread-safe: return_lock serializes concurrent push().\n    // REVIEWED(2025-03): SPSC queue used as MPSC here is safe.\n    // The spinlock's lock/unlock provides acquire/release ordering,\n    // ensuring each thread sees prior push() head updates.\n    pub fn deinit(self: *const Message) void {\n        if (!self.owned) return;\n        if (self.backing_buf) |buf| {\n            assert(self.return_queue != null);\n            const rq = self.return_queue.?;\n            if (self.return_lock) |sl| {\n                sl.lock();\n                defer sl.unlock();\n                while (!rq.push(buf)) {\n                    sl.unlock();\n                    std.Thread.yield() catch {};\n                    sl.lock();\n                }\n            } else {\n                while (!rq.push(buf)) {\n                    std.Thread.yield() catch {};\n                }\n            }\n            return;\n        }\n        const allocator = self.allocator;\n        allocator.free(self.subject);\n        allocator.free(self.data);\n        if (self.reply_to) |rt| allocator.free(rt);\n        if (self.headers) |h| allocator.free(h);\n    }\n\n    /// Sends a reply to this message using the reply_to subject.\n    /// Convenience method for request/reply pattern.\n    /// Returns error.NoReplyTo if message has no reply_to subject.\n    pub fn respond(\n        self: *const Message,\n        client: *Client,\n        payload: []const u8,\n    ) !void {\n        const reply_to = self.reply_to orelse return error.NoReplyTo;\n        assert(reply_to.len > 0);\n        try client.publish(reply_to, payload);\n    }\n\n    /// Returns the total size of the message in bytes.\n    /// Includes subject, data, reply_to, and headers.\n    pub fn size(self: *const Message) usize {\n        var total: usize = self.subject.len + self.data.len;\n        if (self.reply_to) |rt| total += rt.len;\n        if (self.headers) |h| total += h.len;\n        return total;\n    }\n\n    /// Extracts HTTP-like status code from headers (on-demand parsing).\n    /// Returns null if no headers or no status code present.\n    /// Common codes: 503 (no responders), 408 (timeout), 404 (not found).\n    pub fn status(self: *const Message) ?u16 {\n        const hdrs = self.headers orelse return null;\n        return headers.extractStatus(hdrs);\n    }\n\n    /// Returns true if this is a no-responders message (status 503).\n    /// Used to detect when a request has no available responders.\n    pub fn isNoResponders(self: *const Message) bool {\n        return self.status() == 503;\n    }\n};\n\n/// Per-request waiter for the response multiplexer.\n///\n/// Lives on the request()'s stack. The dispatcher fills `msg`\n/// with a cloned response Message and sets `done`; the request\n/// task spin-yields on `done` until the response arrives or the\n/// timeout fires.\npub const RespWaiter = struct {\n    msg: ?Message = null,\n    done: std.atomic.Value(bool) =\n        std.atomic.Value(bool).init(false),\n};\n\n/// Lazy response multiplexer for request/reply.\n///\n/// Replaces per-request SUB/UNSUB churn with one wildcard inbox\n/// subscription per connection. The first request triggers\n/// ensureRespMux which subscribes to \"_INBOX.<NUID>.*\" and does a\n/// PING/PONG round-trip to confirm server registration; every\n/// subsequent request just registers a waiter in `map` and\n/// publishes. The dispatcher (handler on the wildcard sub) routes\n/// incoming replies by extracting the token suffix from the\n/// message subject and waking the matching waiter.\n///\n/// Owns its own mutex (NOT sub_mutex) so the demuxer cannot\n/// contend with subscribe/unsubscribe.\npub const RespMux = struct {\n    /// Back-reference to the owning client. Set during the first\n    /// ensureRespMux call so the handler vtable can resolve back\n    /// to client.io and client.allocator.\n    client: ?*Client = null,\n\n    /// \"_INBOX.<NUID>.\" with trailing dot. Allocated on first\n    /// request, freed by closeRespMux.\n    prefix: ?[]u8 = null,\n    prefix_len: usize = 0,\n\n    /// Wildcard subscription \"_INBOX.<NUID>.*\". Lives for the\n    /// connection lifetime; cleaned up by closeRespMux.\n    sub: ?*Subscription = null,\n\n    /// Active waiters keyed by the 8-char base62 token.\n    /// Token slices borrow from the requester's stack buffer.\n    map: std.StringHashMapUnmanaged(*RespWaiter) = .empty,\n\n    /// Monotonic counter for unique token generation. Atomic so\n    /// concurrent request() calls can mint tokens without locking.\n    next_token: std.atomic.Value(u64) =\n        std.atomic.Value(u64).init(0),\n\n    /// Dedicated mutex protecting `map`. Separate from sub_mutex\n    /// so the dispatcher cannot deadlock against subscribe paths.\n    mutex: Io.Mutex = .init,\n\n    /// Set true once prefix/sub are valid. Acquire-loaded on the\n    /// fast path of every request() to skip re-initialization.\n    initialized: std.atomic.Value(bool) =\n        std.atomic.Value(bool).init(false),\n\n    /// Handler hook called by the standard callback drain task\n    /// for every reply arriving on the wildcard subscription.\n    /// Extracts the token suffix from msg.subject, looks up the\n    /// matching waiter under `mutex`, clones the message into the\n    /// waiter, and signals `done`. Late deliveries (waiter already\n    /// removed by timeout) are silently dropped.\n    ///\n    /// IMPORTANT: clone+write happens INSIDE the lock so the\n    /// request() cleanup defer (which acquires the same mutex)\n    /// blocks until we are done writing to the waiter. This\n    /// prevents use-after-free on the stack-allocated waiter.\n    pub fn onMessage(\n        self: *RespMux,\n        msg: *const Message,\n    ) void {\n        const client = self.client orelse return;\n        assert(self.prefix_len > 0);\n        const subj = msg.subject;\n        if (subj.len <= self.prefix_len) return;\n        const token = subj[self.prefix_len..];\n\n        const io = client.io;\n        self.mutex.lockUncancelable(io);\n        defer self.mutex.unlock(io);\n\n        const entry = self.map.fetchRemove(token);\n        const waiter = if (entry) |e| e.value else return;\n\n        const cloned = cloneMessageContents(\n            client.allocator,\n            msg,\n        ) catch {\n            waiter.done.store(true, .release);\n            return;\n        };\n        waiter.msg = cloned;\n        waiter.done.store(true, .release);\n    }\n};\n\n/// Client connection options.\n///\n/// All fields have sensible defaults. Common customizations:\n/// - name: Client identifier visible in server logs\n/// - user/pass or auth_token: Authentication credentials\n/// - reader_buffer_size/writer_buffer_size: tune protocol buffers\n/// - sub_queue_size: Messages buffered per subscription (default 1024)\npub const Options = struct {\n    /// Client name for identification.\n    name: ?[]const u8 = null,\n    /// Enable verbose mode.\n    verbose: bool = false,\n    /// Enable pedantic mode.\n    pedantic: bool = false,\n    /// Username for auth.\n    user: ?[]const u8 = null,\n    /// Password for auth.\n    pass: ?[]const u8 = null,\n    /// Auth token.\n    auth_token: ?[]const u8 = null,\n    /// Connection timeout in nanoseconds.\n    connect_timeout_ns: u64 = defaults.Connection.timeout_ns,\n    /// Per-subscription queue size (messages buffered before dropping).\n    sub_queue_size: u32 = defaults.Memory.queue_size.value(),\n    /// Echo messages back to sender (default true).\n    echo: bool = true,\n    /// Enable message headers support.\n    headers: bool = true,\n    /// Request no_responders notification for requests.\n    no_responders: bool = true,\n    /// Require TLS connection.\n    tls_required: bool = false,\n\n    // TLS OPTIONS\n\n    /// Path to CA certificate file (PEM). Null = use system CAs.\n    tls_ca_file: ?[]const u8 = null,\n    /// Path to client certificate file for mTLS (PEM).\n    tls_cert_file: ?[]const u8 = null,\n    /// Path to client private key file for mTLS (PEM).\n    tls_key_file: ?[]const u8 = null,\n    /// Skip server certificate verification (INSECURE - testing only).\n    tls_insecure_skip_verify: bool = false,\n    /// Perform TLS handshake before NATS protocol (required by some proxies).\n    tls_handshake_first: bool = false,\n\n    /// NKey seed for authentication.\n    nkey_seed: ?[]const u8 = null,\n    /// NKey seed file path (alternative to nkey_seed).\n    nkey_seed_file: ?[]const u8 = null,\n    /// NKey public key for callback-based signing.\n    nkey_pubkey: ?[]const u8 = null,\n    /// NKey signing callback (returns true on success).\n    nkey_sign_fn: ?*const fn (nonce: []const u8, sig: *[64]u8) bool = null,\n    /// JWT for authentication.\n    jwt: ?[]const u8 = null,\n    /// Credentials file path (.creds file with JWT + NKey seed).\n    /// Mutually exclusive with jwt/nkey_seed options.\n    creds_file: ?[]const u8 = null,\n    /// Credentials content (alternative to file path).\n    /// Use when credentials are loaded from environment/memory.\n    creds: ?[]const u8 = null,\n    /// Read buffer size. Must be >= max message size you expect (1MB).\n    reader_buffer_size: usize = defaults.Connection.reader_buffer_size,\n    /// Write buffer size. Smaller values force more frequent flushes.\n    writer_buffer_size: usize = defaults.Connection.writer_buffer_size,\n    /// TCP receive buffer size hint. Larger values allow more messages to\n    /// queue in the kernel before backpressure kicks in. Default 1MB.\n    /// Set to 0 to use system default.\n    tcp_rcvbuf: u32 = defaults.Connection.tcp_rcvbuf,\n\n    // RECONNECTION OPTIONS\n\n    /// Enable automatic reconnection on disconnect.\n    reconnect: bool = defaults.Reconnection.enabled,\n    /// Maximum reconnection attempts (0 = infinite).\n    max_reconnect_attempts: u32 = defaults.Reconnection.max_attempts,\n    /// Initial wait between reconnect attempts (ms).\n    reconnect_wait_ms: u32 = defaults.Reconnection.wait_ms,\n    /// Maximum wait with exponential backoff (ms).\n    reconnect_wait_max_ms: u32 = defaults.Reconnection.wait_max_ms,\n    /// Jitter percentage for backoff (0-50).\n    reconnect_jitter_percent: u8 = defaults.Reconnection.jitter_percent,\n    /// Custom reconnect delay callback. If set, overrides default exponential\n    /// backoff. Called with attempt number (1-based), returns delay in ms.\n    /// Example: `fn(attempt: u32) u32 { return attempt * 1000; }`\n    custom_reconnect_delay: ?*const fn (attempt: u32) u32 = null,\n    /// Discover servers from INFO connect_urls.\n    discover_servers: bool = defaults.Reconnection.discover_servers,\n    /// Size of pending buffer for publishes during reconnect.\n    /// Set to 0 to disable buffering (publish returns error during reconnect).\n    pending_buffer_size: usize = defaults.Reconnection.pending_buffer_size,\n\n    // PING/PONG HEALTH CHECK\n\n    /// Interval between client-initiated PINGs (ms). 0 = disable.\n    ping_interval_ms: u32 = defaults.Connection.ping_interval_ms,\n    /// Max outstanding PINGs before connection is considered stale.\n    max_pings_outstanding: u8 = defaults.Connection.max_pings_outstanding,\n\n    // ERROR REPORTING\n\n    /// Messages between rate-limited error notifications.\n    /// After first error (alloc_failed, protocol_error), subsequent errors\n    /// only notify every N messages. Prevents event queue flooding.\n    error_notify_interval_msgs: u64 =\n        defaults.ErrorReporting.notify_interval_msgs,\n\n    // EVENT CALLBACKS\n\n    /// Event handler for connection lifecycle callbacks (optional).\n    /// Use EventHandler.init(T, &handler) to create from a handler struct.\n    event_handler: ?EventHandler = null,\n\n    // INBOX/REQUEST OPTIONS\n\n    /// Custom prefix for inbox subjects. Default is \"_INBOX\".\n    /// Used for request/reply pattern inbox generation.\n    inbox_prefix: []const u8 = \"_INBOX\",\n\n    // CONNECTION BEHAVIOR\n\n    /// Retry connection on initial connect failure (before returning error).\n    /// When true, connect() will retry using reconnect settings.\n    retry_on_failed_connect: bool = false,\n    /// Don't randomize server order for connection attempts.\n    /// When true, servers are tried in the order provided.\n    no_randomize: bool = false,\n    /// Ignore servers discovered via cluster INFO.\n    /// Only use explicitly configured servers.\n    ignore_discovered_servers: bool = false,\n    /// Default timeout for drain operations (ms).\n    drain_timeout_ms: u32 = 30_000,\n    /// Default timeout for flush operations (ms).\n    flush_timeout_ms: u32 = 10_000,\n\n    // ADDITIONAL SERVERS\n\n    /// Additional server URLs for reconnection pool.\n    /// These are added to the server pool after the primary URL.\n    /// Max MAX_SERVERS total (see connection/server_pool.zig).\n    servers: ?[]const []const u8 = null,\n};\n\n/// Connection statistics.\n/// Thread ownership: io_task exclusively writes msgs_in/bytes_in.\n/// msgs_out/bytes_out use atomics for multi-thread publish safety.\npub const Statistics = struct {\n    /// Total messages received (written by io_task only).\n    msgs_in: u64 = 0,\n    /// Total messages sent (atomic: multi-thread publish).\n    msgs_out: std.atomic.Value(u64) =\n        std.atomic.Value(u64).init(0),\n    /// Total bytes received (written by io_task only).\n    bytes_in: u64 = 0,\n    /// Total bytes sent (atomic: multi-thread publish).\n    bytes_out: std.atomic.Value(u64) =\n        std.atomic.Value(u64).init(0),\n    /// Number of reconnects.\n    reconnects: std.atomic.Value(u32) =\n        std.atomic.Value(u32).init(0),\n    /// Total successful connections (initial + reconnects).\n    connects: std.atomic.Value(u32) =\n        std.atomic.Value(u32).init(0),\n\n    /// Returns a snapshot of stats with atomics loaded.\n    pub fn snapshot(self: *const Statistics) StatsSnapshot {\n        return .{\n            .msgs_in = self.msgs_in,\n            .msgs_out = self.msgs_out.load(.monotonic),\n            .bytes_in = self.bytes_in,\n            .bytes_out = self.bytes_out.load(.monotonic),\n            .reconnects = self.reconnects.load(.monotonic),\n            .connects = self.connects.load(.monotonic),\n        };\n    }\n};\n\n/// Plain stats snapshot (no atomics). Returned by stats().\npub const StatsSnapshot = struct {\n    msgs_in: u64 = 0,\n    msgs_out: u64 = 0,\n    bytes_in: u64 = 0,\n    bytes_out: u64 = 0,\n    reconnects: u32 = 0,\n    connects: u32 = 0,\n};\n\n/// Debug counters for io_task buffer operations.\n/// Only incremented when dbg.enabled.\n/// Written exclusively by io_task thread, safe to read after deinit.\npub const IoTaskStats = struct {\n    /// Number of tryFillBuffer() calls.\n    fill_calls: u64 = 0,\n    /// Cumulative bytes already buffered (before read).\n    fill_buffered_hits: u64 = 0,\n    /// Poll timeouts (no data available).\n    fill_poll_timeouts: u64 = 0,\n    /// Successful socket reads.\n    fill_read_success: u64 = 0,\n};\n\n/// Subscription backup for restoration after reconnect.\n/// Stores essential subscription state with inline buffers.\npub const SubBackup = struct {\n    sid: u64 = 0,\n    subject_buf: [256]u8 = undefined,\n    subject_len: u8 = 0,\n    queue_group_buf: [64]u8 = undefined,\n    queue_group_len: u8 = 0,\n\n    /// Get subject as slice.\n    pub fn getSubject(self: *const SubBackup) []const u8 {\n        return self.subject_buf[0..self.subject_len];\n    }\n\n    /// Get queue group as optional slice.\n    pub fn queueGroup(self: *const SubBackup) ?[]const u8 {\n        if (self.queue_group_len == 0) return null;\n        return self.queue_group_buf[0..self.queue_group_len];\n    }\n};\n\n/// Result of drain operation.\npub const DrainResult = struct {\n    /// Count of UNSUB commands that failed to encode.\n    unsub_failures: u16 = 0,\n    /// True if final flush failed (data may not have reached server).\n    flush_failed: bool = false,\n\n    /// Returns true if drain completed without any failures.\n    pub fn isClean(self: DrainResult) bool {\n        return self.unsub_failures == 0 and !self.flush_failed;\n    }\n};\n\n/// Subscribe command data (used by restoreSubscriptions).\npub const SubscribeCmd = struct {\n    sid: u64,\n    subject: []const u8,\n    queue_group: ?[]const u8,\n};\n\n/// Parse result for NATS URL.\npub const ParsedUrl = struct {\n    host: []const u8,\n    port: u16,\n    user: ?[]const u8,\n    pass: ?[]const u8,\n    use_tls: bool,\n};\n\n/// Fixed subscription limits (from defaults.zig).\npub const MAX_SUBSCRIPTIONS: u16 = defaults.Client.max_subscriptions;\npub const SIDMAP_CAPACITY: u32 = defaults.Client.sidmap_capacity;\n\n/// Default queue size per subscription (messages buffered before dropping).\npub const DEFAULT_QUEUE_SIZE: u32 = defaults.Memory.queue_size.value();\n\ncomptime {\n    assert(SIDMAP_CAPACITY >= MAX_SUBSCRIPTIONS);\n}\n\n/// Parses a NATS URL like nats://user:pass@host:port or tls://host:port\npub fn parseUrl(url: []const u8) error{InvalidUrl}!ParsedUrl {\n    if (url.len == 0) return error.InvalidUrl;\n    var remaining = url;\n    var use_tls = false;\n\n    if (std.mem.startsWith(u8, remaining, \"tls://\")) {\n        remaining = remaining[6..];\n        use_tls = true;\n    } else if (std.mem.startsWith(u8, remaining, \"nats://\")) {\n        remaining = remaining[7..];\n    }\n\n    var user: ?[]const u8 = null;\n    var pass: ?[]const u8 = null;\n\n    if (std.mem.indexOf(u8, remaining, \"@\")) |at_pos| {\n        const auth = remaining[0..at_pos];\n        remaining = remaining[at_pos + 1 ..];\n\n        if (std.mem.indexOf(u8, auth, \":\")) |colon_pos| {\n            user = auth[0..colon_pos];\n            pass = auth[colon_pos + 1 ..];\n        } else {\n            user = auth;\n        }\n    }\n\n    if (remaining.len == 0) return error.InvalidUrl;\n\n    var host: []const u8 = undefined;\n    var port: u16 = 4222;\n\n    if (remaining[0] == '[') {\n        const end = std.mem.indexOfScalar(u8, remaining, ']') orelse\n            return error.InvalidUrl;\n        host = remaining[1..end];\n        const after = remaining[end + 1 ..];\n        if (after.len > 0) {\n            if (after[0] != ':' or after.len == 1) return error.InvalidUrl;\n            port = std.fmt.parseInt(u16, after[1..], 10) catch {\n                return error.InvalidUrl;\n            };\n        }\n    } else {\n        var colon_count: u8 = 0;\n        var colon_pos: usize = 0;\n        for (remaining, 0..) |c, i| {\n            if (c == '[' or c == ']') return error.InvalidUrl;\n            if (c == ':') {\n                colon_count += 1;\n                colon_pos = i;\n            }\n        }\n\n        if (colon_count == 1) {\n            host = remaining[0..colon_pos];\n            if (colon_pos + 1 >= remaining.len) return error.InvalidUrl;\n            port = std.fmt.parseInt(u16, remaining[colon_pos + 1 ..], 10) catch {\n                return error.InvalidUrl;\n            };\n        } else {\n            host = remaining;\n        }\n    }\n\n    if (host.len == 0) return error.InvalidUrl;\n\n    assert(host.len > 0);\n    if (port == 0) return error.InvalidUrl;\n    return .{\n        .host = host,\n        .port = port,\n        .user = user,\n        .pass = pass,\n        .use_tls = use_tls,\n    };\n}\n\nfn connectToHost(io: Io, host: []const u8, port: u16) !net.Stream {\n    if (net.IpAddress.resolve(io, host, port)) |address| {\n        return net.IpAddress.connect(&address, io, .{\n            .mode = .stream,\n            .protocol = .tcp,\n        }) catch return error.ConnectionFailed;\n    } else |_| {}\n\n    const hostname = net.HostName.init(host) catch {\n        return error.InvalidAddress;\n    };\n    return net.HostName.connect(hostname, io, port, .{\n        .mode = .stream,\n        .protocol = .tcp,\n    }) catch return error.ConnectionFailed;\n}\n\n/// Subscription type alias.\npub const Sub = Subscription;\n\nio: Io,\nallocator: Allocator,\nstream: net.Stream,\nreader: net.Stream.Reader,\nwriter: net.Stream.Writer,\n/// Active reader interface (TCP or TLS). Set once at connection, used by io_task.\nactive_reader: *Io.Reader = undefined,\n/// Active writer interface (TCP or TLS). Set once at connection, used by io_task.\nactive_writer: *Io.Writer = undefined,\noptions: Options,\n\nread_buffer: []u8,\nwrite_buffer: []u8,\n\nsidmap: SidMap,\nsidmap_keys: [SIDMAP_CAPACITY]u64,\nsidmap_vals: [SIDMAP_CAPACITY]u16,\nfree_slots: [MAX_SUBSCRIPTIONS]u16,\n\nparser: Parser = .{},\nserver_info: ?ServerInfo = null,\nstate: State = .connecting,\nsub_ptrs: [MAX_SUBSCRIPTIONS]?*Sub = [_]?*Sub{null} ** MAX_SUBSCRIPTIONS,\nfree_count: u16 = MAX_SUBSCRIPTIONS,\nnext_sid: u64 = 1,\n/// Serializes reader-task routing with unsubscribe/deinit so a\n/// loaded subscription pointer cannot be freed while in use.\nread_mutex: Io.Mutex = .init,\nstatistics: Statistics = .{},\n\n// Thread-safety mutexes for multi-thread publish/subscribe.\n// Lock ordering: sub_mutex -> read_mutex -> write_mutex.\n// publish_mutex and return_lock are independent.\n\n/// Serializes multi-thread publish (encode-to-ring path).\npublish_mutex: Io.Mutex = .init,\n/// Serializes multi-thread subscribe/unsubscribe bookkeeping.\nsub_mutex: Io.Mutex = .init,\n/// Lazy response multiplexer for request/reply.\n/// Owns its own mutex; does NOT share sub_mutex.\nresp_mux: RespMux = .{},\n/// Spinlock for multi-thread return_queue.push() in msg.deinit().\n/// Atomic spinlock (not Io.Mutex) because Message.deinit() has no io.\nreturn_lock: SpinLock = .{},\n\n// Connection diagnostics\ntcp_nodelay_set: bool = false,\ntcp_rcvbuf_set: bool = false,\n\n// Fast path cache for single-subscription case\ncached_sub: ?*Sub = null,\n\n// Cached max_payload from server_info\nmax_payload: usize = 1024 * 1024,\n\n// Slab allocator for message buffers\ntiered_slab: TieredSlab = undefined,\n\n// Return queue for cross-thread buffer deallocation (main -> reader thread)\n// Main thread pushes used buffers here, io_task drains and frees to slab\nreturn_queue: SpscQueue([]u8) = undefined,\nreturn_queue_buf: [][]u8 = undefined,\n\n// Reconnection state\nserver_pool: connection.ServerPool = undefined,\nserver_pool_initialized: bool = false,\nsub_backups: [MAX_SUBSCRIPTIONS]SubBackup =\n    [_]SubBackup{.{}} ** MAX_SUBSCRIPTIONS,\nsub_backup_count: u16 = 0,\nreconnect_attempt: u32 = 0,\noriginal_url: [256]u8 = undefined,\noriginal_url_len: u8 = 0,\n\n// Pending buffer for publishes during reconnect\npending_buffer: ?[]u8 = null,\npending_buffer_pos: usize = 0,\npending_buffer_capacity: usize = 0,\n\n// PING/PONG health check state (atomics for cross-thread access)\n// Main thread reads during health check (~100ms), io_task writes on PONG.\n// Uses monotonic ordering - exact timing not critical, eventual visibility suffices.\nlast_ping_sent_ns: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),\nlast_pong_received_ns: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),\npings_outstanding: std.atomic.Value(u8) = std.atomic.Value(u8).init(0),\n\n/// Auto-flush signal: set by publish(), cleared by io_task after flush.\nflush_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),\n\n/// Lock-free publish ring buffer. Producer: main thread, Consumer: io_task.\n/// Publishes encode directly into this ring; io_task drains to socket.\npublish_ring: ByteRing = undefined,\npublish_ring_buf: ?[]u8 = null,\n\n// Debug counters for io_task (only used when dbg.enabled)\nio_task_stats: IoTaskStats = .{},\n\n// Error rate-limiting state (written by io_task only)\n/// Count of protocol parse errors encountered.\nprotocol_errors: u64 = 0,\n/// msgs_in value when protocol_error event was last pushed (rate-limit).\nlast_parse_error_notified_at: u64 = 0,\n\n// Last async error tracking (written by io_task,\n// cleared by user via clearLastError)\n/// Last async error that occurred on the connection.\nlast_error: ?anyerror = null,\n/// Message associated with last error (inline buffer, no allocation).\nlast_error_msg: [256]u8 = undefined,\n/// Length of last_error_msg content.\nlast_error_msg_len: u8 = 0,\n\n// Background I/O task infrastructure\nwrite_mutex: Io.Mutex = .init,\n/// Future for background I/O task (for proper cancellation in deinit).\nio_task_future: ?Io.Future(void) = null,\n\n// Event callback infrastructure\n/// Event queue for io_task -> callback_task communication.\n/// SpscQueue for non-blocking push from io_task hot path.\nevent_queue: ?*SpscQueue(Event) = null,\n/// Serializes event producers so the SPSC queue still sees a single writer.\nevent_queue_mutex: Io.Mutex = .init,\n/// Buffer backing the event queue.\nevent_queue_buf: ?[]Event = null,\n/// Future for callback task (dispatches events to user handler).\ncallback_task_future: ?Io.Future(void) = null,\n/// Event handler (copied from options for callback_task access).\nevent_handler: ?EventHandler = null,\n/// Flag to track if lame duck event has been fired.\nlame_duck_notified: bool = false,\n\n// TLS state\n/// TLS client instance (owns decryption state).\ntls_client: ?tls.Client = null,\n/// TLS read buffer (must be at least tls.Client.min_buffer_len).\ntls_read_buffer: ?[]u8 = null,\n/// TLS write buffer (must be at least tls.Client.min_buffer_len).\ntls_write_buffer: ?[]u8 = null,\n/// CA certificate bundle for verification.\nca_bundle: ?Certificate.Bundle = null,\nca_bundle_lock: Io.RwLock = .init,\n/// Whether TLS is enabled for this connection.\nuse_tls: bool = false,\n/// Host for TLS SNI and certificate verification.\ntls_host: [256]u8 = undefined,\n/// Length of tls_host.\ntls_host_len: u8 = 0,\n\n/// Connects to a NATS server.\n///\n/// Arguments:\n///     allocator: Allocator for client and buffer memory\n///     io: Io interface for async I/O operations\n///     url: NATS server URL (e.g., \"nats://localhost:4222\")\n///     opts: Connection options (timeouts, auth, buffer sizes)\n///\n/// Returns pointer to connected Client. Caller owns and must call deinit().\npub fn connect(\n    allocator: Allocator,\n    io: Io,\n    url: []const u8,\n    opts: Options,\n) !*Client {\n    // Validate URL length - reject rather than truncate\n    if (url.len >= defaults.Server.max_url_len) return error.UrlTooLong;\n\n    const parsed = try parseUrl(url);\n    const wants_tls = parsed.use_tls or opts.tls_required or\n        opts.tls_ca_file != null or opts.tls_handshake_first;\n    if (wants_tls and parsed.host.len > 255) return error.HostTooLong;\n\n    const client = try allocator.create(Client);\n    client.allocator = allocator;\n    client.server_info = null;\n    client.parser = .{};\n    client.state = .connecting;\n    client.sub_ptrs = [_]?*Sub{null} ** MAX_SUBSCRIPTIONS;\n    client.free_count = MAX_SUBSCRIPTIONS;\n    client.next_sid = 1;\n    client.read_mutex = .init;\n    client.sub_mutex = .init;\n    client.publish_mutex = .init;\n    client.return_lock = .{};\n    client.statistics = .{};\n    client.cached_sub = null;\n    client.max_payload = 1024 * 1024;\n    client.tcp_nodelay_set = false;\n    client.tcp_rcvbuf_set = false;\n    client.flush_requested = std.atomic.Value(bool).init(false);\n    client.resp_mux = .{};\n\n    // Initialize reconnection state\n    client.server_pool = undefined;\n    client.server_pool_initialized = false;\n    client.sub_backups = [_]SubBackup{.{}} ** MAX_SUBSCRIPTIONS;\n    client.sub_backup_count = 0;\n    client.reconnect_attempt = 0;\n    client.original_url = undefined;\n    client.original_url_len = 0;\n\n    // Initialize pending buffer state\n    client.pending_buffer = null;\n    client.pending_buffer_pos = 0;\n    client.pending_buffer_capacity = 0;\n\n    // Initialize health check state (atomics)\n    client.last_ping_sent_ns.raw = 0;\n    client.last_pong_received_ns.raw = 0;\n    client.pings_outstanding.raw = 0;\n    client.io_task_stats = .{};\n\n    // Initialize error tracking state\n    client.protocol_errors = 0;\n    client.last_parse_error_notified_at = 0;\n    client.last_error = null;\n    client.last_error_msg_len = 0;\n\n    // Initialize background I/O task infrastructure\n    client.write_mutex = .init;\n    client.io_task_future = null;\n    client.publish_ring_buf = null;\n\n    // Initialize event callback infrastructure\n    client.event_queue = null;\n    client.event_queue_mutex = .init;\n    client.event_queue_buf = null;\n    client.callback_task_future = null;\n    client.event_handler = opts.event_handler;\n    client.lame_duck_notified = false;\n\n    // Initialize TLS state\n    client.tls_client = null;\n    client.tls_read_buffer = null;\n    client.tls_write_buffer = null;\n    client.ca_bundle = null;\n    client.ca_bundle_lock = .init;\n    // Determine if TLS should be used: URL scheme, explicit option, or CA file set\n    client.use_tls = wants_tls;\n    client.tls_host = undefined;\n    client.tls_host_len = 0;\n\n    // Store host for TLS SNI and certificate verification\n    if (client.use_tls) {\n        const host_len: u8 = @intCast(parsed.host.len);\n        @memcpy(client.tls_host[0..host_len], parsed.host);\n        client.tls_host_len = host_len;\n    }\n\n    // Initialize slab allocator (critical for O(1) message allocation)\n    client.tiered_slab = TieredSlab.init(allocator) catch |err| {\n        allocator.destroy(client);\n        return err;\n    };\n\n    // Initialize return queue for cross-thread buffer deallocation\n    // Size must exceed slab tier capacity to avoid blocking when buffers\n    // are split between sub_queue, processing, and return_queue\n    const rq_size = opts.sub_queue_size * 2;\n    client.return_queue_buf = allocator.alloc([]u8, rq_size) catch |err| {\n        client.tiered_slab.deinit();\n        allocator.destroy(client);\n        return err;\n    };\n    client.return_queue = SpscQueue([]u8).init(client.return_queue_buf);\n\n    errdefer {\n        allocator.free(client.return_queue_buf);\n        client.tiered_slab.deinit();\n        if (client.server_info) |*info| {\n            info.deinit(allocator);\n        }\n        // TLS cleanup\n        if (client.tls_read_buffer) |buf| allocator.free(buf);\n        if (client.tls_write_buffer) |buf| allocator.free(buf);\n        if (client.ca_bundle) |*bundle| bundle.deinit(allocator);\n        allocator.destroy(client);\n    }\n\n    client.stream = try connectToHost(io, parsed.host, parsed.port);\n    errdefer client.stream.close(io);\n\n    // TCP_NODELAY\n    const enable: u32 = 1;\n    client.tcp_nodelay_set = true;\n    std.posix.setsockopt(\n        client.stream.socket.handle,\n        std.posix.IPPROTO.TCP,\n        std.posix.TCP.NODELAY,\n        std.mem.asBytes(&enable),\n    ) catch {\n        client.tcp_nodelay_set = false;\n    };\n\n    // Set TCP receive buffer size for better backpressure handling\n    client.tcp_rcvbuf_set = opts.tcp_rcvbuf > 0;\n    if (opts.tcp_rcvbuf > 0) {\n        std.posix.setsockopt(\n            client.stream.socket.handle,\n            std.posix.SOL.SOCKET,\n            std.posix.SO.RCVBUF,\n            std.mem.asBytes(&opts.tcp_rcvbuf),\n        ) catch {\n            client.tcp_rcvbuf_set = false;\n        };\n    }\n\n    client.read_buffer = allocator.alloc(u8, opts.reader_buffer_size) catch {\n        return error.OutOfMemory;\n    };\n    errdefer allocator.free(client.read_buffer);\n\n    client.write_buffer = allocator.alloc(u8, opts.writer_buffer_size) catch {\n        return error.OutOfMemory;\n    };\n    errdefer allocator.free(client.write_buffer);\n\n    // Publish ring: power-of-2, must be > 2x largest\n    // possible entry. The ring rejects entries that\n    // exceed capacity/2. Entry size = RING_HDR_SIZE +\n    // PUB overhead + payload. Add 512 bytes headroom\n    // for subject, reply-to, and length digits.\n    const min_ring =\n        (defaults.Protocol.max_payload + 512) * 2;\n    const ring_size = std.math.ceilPowerOfTwo(\n        usize,\n        @max(min_ring, opts.writer_buffer_size),\n    ) catch {\n        return error.OutOfMemory;\n    };\n    client.publish_ring_buf = allocator.alloc(\n        u8,\n        ring_size,\n    ) catch {\n        return error.OutOfMemory;\n    };\n    errdefer if (client.publish_ring_buf) |b| allocator.free(b);\n    client.publish_ring = ByteRing.init(\n        client.publish_ring_buf.?,\n    );\n\n    client.io = io;\n    client.reader = client.stream.reader(io, client.read_buffer);\n    client.writer = client.stream.writer(io, client.write_buffer);\n    // Default to TCP reader/writer (updated by upgradeTls if TLS is used)\n    client.active_reader = &client.reader.interface;\n    client.active_writer = &client.writer.interface;\n    client.options = opts;\n\n    client.sidmap_keys = undefined;\n    client.sidmap_vals = undefined;\n    client.sidmap = .init(&client.sidmap_keys, &client.sidmap_vals);\n    for (0..MAX_SUBSCRIPTIONS) |i| {\n        client.free_slots[i] = @intCast(MAX_SUBSCRIPTIONS - 1 - i);\n    }\n\n    // TLS-first mode: upgrade to TLS before NATS protocol\n    if (client.use_tls and opts.tls_handshake_first) {\n        try client.upgradeTls(opts);\n    }\n\n    try client.handshake(opts, parsed);\n    // Note: TLS upgrade (if needed) now happens inside handshake(),\n    // between receiving INFO and sending CONNECT per NATS protocol.\n\n    assert(url.len <= defaults.Server.max_url_len);\n    const url_len: u8 = @intCast(url.len);\n    @memcpy(client.original_url[0..url_len], url);\n    client.original_url_len = url_len;\n\n    client.server_pool = connection.ServerPool.init(url) catch {\n        return error.InvalidUrl;\n    };\n    client.server_pool_initialized = true;\n\n    // Add additional servers from options\n    if (opts.servers) |servers| {\n        for (servers) |server_url| {\n            client.server_pool.addServer(server_url) catch continue;\n        }\n    }\n\n    if (opts.discover_servers) {\n        if (client.server_info) |info| {\n            const new_servers = client.server_pool.addFromConnectUrls(\n                &info.connect_urls,\n                &info.connect_urls_lens,\n                info.connect_urls_count,\n            );\n            if (new_servers > 0) {\n                client.pushEvent(\n                    .{ .discovered_servers = .{ .count = new_servers } },\n                );\n            }\n        }\n    }\n\n    try client.initPendingBuffer();\n\n    const now_ns = getNowNs(io);\n    client.last_ping_sent_ns.store(now_ns, .monotonic);\n    client.last_pong_received_ns.store(now_ns, .monotonic);\n\n    // concurrent() required - async() may deadlock on flush()\n    client.io_task_future = io.concurrent(\n        connection.io_task.run,\n        .{client},\n    ) catch blk: {\n        dbg.print(\"WARNING: concurrent() failed, using async()\", .{});\n        break :blk io.async(connection.io_task.run, .{client});\n    };\n\n    // Spawn callback task if event handler provided\n    if (opts.event_handler != null) {\n        // Allocate event queue buffer (256 events is plenty for lifecycle)\n        const eq_buf = try allocator.alloc(Event, 256);\n        client.event_queue_buf = eq_buf;\n        errdefer {\n            allocator.free(eq_buf);\n            client.event_queue_buf = null;\n        }\n\n        // Create event queue (SpscQueue for non-blocking push from io_task)\n        const eq = try allocator.create(SpscQueue(Event));\n        eq.* = SpscQueue(Event).init(eq_buf);\n        client.event_queue = eq;\n        errdefer {\n            allocator.destroy(eq);\n            client.event_queue = null;\n        }\n\n        // Spawn callback task\n        client.callback_task_future = io.concurrent(\n            callbackTaskFn,\n            .{client},\n        ) catch blk: {\n            dbg.print(\n                \"WARNING: callback concurrent() failed, using async()\",\n                .{},\n            );\n            break :blk io.async(callbackTaskFn, .{client});\n        };\n\n        // Push initial connected event\n        _ = eq.push(.{ .connected = {} });\n\n        // Push socket option warnings (non-fatal, performance impact)\n        if (!client.tcp_nodelay_set) {\n            _ = eq.push(.{\n                .err = .{\n                    .err = events_mod.Error.TcpNoDelayFailed,\n                    .msg = null,\n                },\n            });\n        }\n        if (!client.tcp_rcvbuf_set and opts.tcp_rcvbuf > 0) {\n            _ = eq.push(.{\n                .err = .{\n                    .err = events_mod.Error.TcpRcvBufFailed,\n                    .msg = null,\n                },\n            });\n        }\n    }\n\n    assert(client.next_sid >= 1);\n    assert(client.state == .connected);\n    return client;\n}\n\n/// Push event to callback queue (called by io_task).\n/// Non-blocking, drops event if queue is full.\n// REVIEWED(2025-03): Silent drop is intentional. Blocking\n// would stall the io_task hot path. Users can monitor via\n// subscription dropped_msgs counters.\npub fn pushEvent(self: *Client, event: Event) void {\n    self.event_queue_mutex.lock(self.io) catch return;\n    defer self.event_queue_mutex.unlock(self.io);\n\n    if (self.event_queue) |q| {\n        _ = q.push(event);\n    }\n}\n\n/// Callback task: drains event queue and dispatches to user handler.\n/// Runs concurrently, uses io.sleep(0) for async-aware yield with cancellation.\n/// Exits on .closed event, null queue (deinit), or when canceled during shutdown.\nfn callbackTaskFn(client: *Client) void {\n    dbg.print(\"callback_task: STARTED\", .{});\n\n    const handler = client.event_handler orelse return;\n\n    while (State.atomicLoad(&client.state) != .closed) {\n        // Check if queue was nulled by deinit() - must exit immediately\n        const queue = client.event_queue orelse break;\n\n        // Drain all pending events\n        while (queue.pop()) |event| {\n            switch (event) {\n                .connected => handler.dispatchConnect(),\n                .disconnected => |e| handler.dispatchDisconnect(e.err),\n                .reconnected => handler.dispatchReconnect(),\n                .closed => {\n                    handler.dispatchClose();\n                    dbg.print(\"callback_task: EXITED (closed event)\", .{});\n                    return;\n                },\n                .slow_consumer => {\n                    handler.dispatchError(events_mod.Error.SlowConsumer);\n                },\n                .err => |e| handler.dispatchError(e.err),\n                .lame_duck => handler.dispatchLameDuck(),\n                .alloc_failed => {\n                    handler.dispatchError(events_mod.Error.AllocationFailed);\n                },\n                .protocol_error => {\n                    handler.dispatchError(events_mod.Error.ProtocolParseError);\n                },\n                .discovered_servers => |e| {\n                    handler.dispatchDiscoveredServers(e.count);\n                },\n                .draining => handler.dispatchDraining(),\n                .subscription_complete => |e| {\n                    handler.dispatchSubscriptionComplete(e.sid);\n                },\n            }\n        }\n        // REVIEWED(2025-03): yield-based polling is intentional.\n        // Blocking alternatives (futex/condvar) add complexity\n        // for the event dispatch path with minimal benefit.\n        std.Thread.yield() catch {};\n    }\n\n    // Drain any remaining events queued during shutdown\n    if (client.event_queue) |queue| {\n        while (queue.pop()) |event| {\n            switch (event) {\n                .connected => handler.dispatchConnect(),\n                .disconnected => |e| handler.dispatchDisconnect(e.err),\n                .reconnected => handler.dispatchReconnect(),\n                .closed => {}, // Will dispatch below\n                .slow_consumer => {\n                    handler.dispatchError(events_mod.Error.SlowConsumer);\n                },\n                .err => |e| handler.dispatchError(e.err),\n                .lame_duck => handler.dispatchLameDuck(),\n                .alloc_failed => {\n                    handler.dispatchError(events_mod.Error.AllocationFailed);\n                },\n                .protocol_error => {\n                    handler.dispatchError(events_mod.Error.ProtocolParseError);\n                },\n                .discovered_servers => |e| {\n                    handler.dispatchDiscoveredServers(e.count);\n                },\n                .draining => handler.dispatchDraining(),\n                .subscription_complete => |e| {\n                    handler.dispatchSubscriptionComplete(e.sid);\n                },\n            }\n        }\n    }\n\n    // Dispatch final close event if not already done\n    handler.dispatchClose();\n    dbg.print(\"callback_task: EXITED (state closed)\", .{});\n}\n\n/// Drain loop for MsgHandler callback subscriptions.\n/// Runs as io.async task, pops messages and dispatches to handler.\n/// Automatically frees each message after dispatch.\nfn callbackDrainFn(\n    sub: *Subscription,\n    handler: MsgHandler,\n) void {\n    assert(sub.mode == .callback);\n    const io = sub.client.io;\n    while (sub.state == .active or sub.state == .draining) {\n        const msg = sub.nextRaw(io) catch |err| {\n            if (err == error.Canceled or\n                err == error.Closed) break;\n            continue;\n        };\n        handler.dispatch(&msg);\n        msg.deinit();\n    }\n}\n\n/// Drain loop for plain fn callback subscriptions.\n/// Same as callbackDrainFn but calls a plain function pointer.\nfn callbackDrainFnPlain(\n    sub: *Subscription,\n    cb: *const fn (*const Message) void,\n) void {\n    assert(sub.mode == .callback);\n    const io = sub.client.io;\n    while (sub.state == .active or sub.state == .draining) {\n        const msg = sub.nextRaw(io) catch |err| {\n            if (err == error.Canceled or\n                err == error.Closed) break;\n            continue;\n        };\n        cb(&msg);\n        msg.deinit();\n    }\n}\n\n/// Clones a Message into freshly allocated buffers owned by the\n/// caller. Used by RespMux.onMessage to transfer a borrowed reply\n/// (freed by the callback drain) into a Message the requester\n/// can keep beyond the handler return.\nfn cloneMessageContents(\n    allocator: Allocator,\n    src: *const Message,\n) !Message {\n    assert(src.subject.len > 0);\n\n    const subject = try allocator.dupe(u8, src.subject);\n    errdefer allocator.free(subject);\n\n    const data = try allocator.dupe(u8, src.data);\n    errdefer allocator.free(data);\n\n    const reply_to: ?[]const u8 = if (src.reply_to) |rt|\n        try allocator.dupe(u8, rt)\n    else\n        null;\n    errdefer if (reply_to) |rt| allocator.free(rt);\n\n    const hdrs: ?[]const u8 = if (src.headers) |h|\n        try allocator.dupe(u8, h)\n    else\n        null;\n\n    return .{\n        .subject = subject,\n        .sid = src.sid,\n        .reply_to = reply_to,\n        .data = data,\n        .headers = hdrs,\n        .allocator = allocator,\n        .owned = true,\n        .backing_buf = null,\n        .return_queue = null,\n        .return_lock = null,\n    };\n}\n\n/// Encodes a u64 into an 8-character base62 token in `buf`.\n/// Used by request() to generate unique reply suffixes from a\n/// monotonic counter without allocation or RNG state.\nfn generateRespToken(buf: *[8]u8, n: u64) []const u8 {\n    const digits =\n        \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\" ++\n        \"abcdefghijklmnopqrstuvwxyz\";\n    assert(digits.len == 62);\n    var x = n;\n    var i: usize = 8;\n    while (i > 0) {\n        i -= 1;\n        buf[i] = digits[@intCast(x % 62)];\n        x /= 62;\n    }\n    return buf[0..];\n}\n\n/// Upgrades the connection to TLS.\n/// Allocates TLS buffers, loads CA certificates, and performs handshake.\nfn upgradeTls(\n    self: *Client,\n    opts: Options,\n) !void {\n    const allocator = self.allocator;\n    assert(self.use_tls);\n    assert(self.tls_client == null);\n    assert(self.tls_host_len > 0);\n\n    // mTLS not yet implemented\n    if (opts.tls_cert_file != null or\n        opts.tls_key_file != null)\n        return error.MtlsNotImplemented;\n\n    // Allocate TLS buffers if not already done\n    if (self.tls_read_buffer == null) {\n        self.tls_read_buffer =\n            try allocator.alloc(u8, defaults.Tls.buffer_size);\n    }\n    errdefer if (self.tls_read_buffer) |buf| {\n        allocator.free(buf);\n        self.tls_read_buffer = null;\n    };\n\n    if (self.tls_write_buffer == null) {\n        self.tls_write_buffer =\n            try allocator.alloc(u8, defaults.Tls.buffer_size);\n    }\n    errdefer if (self.tls_write_buffer) |buf| {\n        allocator.free(buf);\n        self.tls_write_buffer = null;\n    };\n\n    // Load CA bundle (unless insecure mode)\n    if (!opts.tls_insecure_skip_verify) {\n        if (self.ca_bundle == null) {\n            self.ca_bundle = .empty;\n        }\n        const now = Io.Clock.real.now(self.io);\n        if (opts.tls_ca_file) |ca_path| {\n            // Load custom CA bundle from file (propagates file system errors)\n            try self.ca_bundle.?.addCertsFromFilePathAbsolute(\n                allocator,\n                self.io,\n                now,\n                ca_path,\n            );\n        } else {\n            // Use system CAs\n            try self.ca_bundle.?.rescan(allocator, self.io, now);\n        }\n    }\n\n    // Generate entropy for TLS handshake\n    var entropy: [tls.Client.Options.entropy_len]u8 = undefined;\n    self.io.randomSecure(&entropy) catch\n        return error.SecureEntropyUnavailable;\n\n    // Get current timestamp for certificate validation\n    const now = Io.Clock.real.now(self.io);\n\n    // Build TLS options with inline unions\n    const tls_opts: tls.Client.Options = .{\n        .host = if (opts.tls_insecure_skip_verify)\n            .no_verification\n        else\n            .{ .explicit = self.tls_host[0..self.tls_host_len] },\n        .ca = if (opts.tls_insecure_skip_verify)\n            .no_verification\n        else\n            .{ .bundle = .{\n                .gpa = self.allocator,\n                .io = self.io,\n                .lock = &self.ca_bundle_lock,\n                .bundle = &self.ca_bundle.?,\n            } },\n        .read_buffer = self.tls_read_buffer.?,\n        .write_buffer = self.tls_write_buffer.?,\n        .entropy = &entropy,\n        .realtime_now = now,\n    };\n\n    // Perform TLS handshake (propagates TLS errors)\n    self.tls_client = try tls.Client.init(\n        &self.reader.interface,\n        &self.writer.interface,\n        tls_opts,\n    );\n\n    // Update active reader/writer to TLS (no branching in io_task hot path)\n    self.active_reader = &self.tls_client.?.reader;\n    self.active_writer = &self.tls_client.?.writer;\n\n    dbg.print(\"TLS handshake completed\", .{});\n}\n\n/// Performs NATS handshake (INFO/CONNECT exchange).\nfn handshake(\n    self: *Client,\n    opts: Options,\n    parsed: ParsedUrl,\n) !void {\n    const allocator = self.allocator;\n    // Allow both initial connect and reconnection states\n    assert(self.state == .connecting or self.state == .reconnecting);\n    assert(parsed.host.len > 0);\n\n    // Use active reader (TLS or TCP depending on connection state)\n    // Note: writer is fetched later after potential TLS upgrade\n    const reader = self.active_reader;\n\n    // Read INFO from server with connection timeout\n    const info_data =\n        try self.peekWithTimeout(reader, opts.connect_timeout_ns);\n\n    var consumed: usize = 0;\n    const cmd = self.parser.parse(allocator, info_data, &consumed) catch {\n        return error.ProtocolError;\n    };\n\n    assert(consumed <= info_data.len);\n    reader.toss(consumed);\n\n    if (cmd) |c| {\n        switch (c) {\n            .info => |parsed_info| {\n                // Free old server_info if reconnecting\n                if (self.server_info) |*old| {\n                    old.deinit(allocator);\n                }\n                self.server_info = parsed_info;\n                self.max_payload = parsed_info.max_payload;\n                self.state = .connected;\n                _ = self.statistics.connects.fetchAdd(1, .monotonic);\n            },\n            else => return error.UnexpectedCommand,\n        }\n    } else {\n        return error.NoInfoReceived;\n    }\n\n    // REVIEWED(2025-03): TLS upgrade occurs HERE, before CONNECT.\n    // Credentials in CONNECT are sent over TLS when required.\n    // Server sends INFO in plain text, then expects TLS handshake.\n    if (self.use_tls and self.tls_client == null) {\n        const server_tls =\n            if (self.server_info) |info| info.tls_required else false;\n        const client_tls_required =\n            parsed.use_tls or opts.tls_required or opts.tls_ca_file != null;\n        if (server_tls or client_tls_required) {\n            try self.upgradeTls(opts);\n        }\n    }\n\n    // Send CONNECT (now over TLS if upgraded)\n    // Re-fetch writer since TLS upgrade may have changed active_writer\n    const writer_for_connect = self.active_writer;\n\n    const pass = opts.pass orelse parsed.pass;\n    var user = opts.user orelse parsed.user;\n    var auth_token = opts.auth_token;\n\n    if (parsed.user != null and parsed.pass == null and opts.user == null) {\n        auth_token = parsed.user;\n        user = null;\n    }\n\n    // Authentication: sign nonce if credentials provided\n    // Priority: creds_file > creds > nkey_seed > nkey_seed_file > nkey_sign_fn\n    var sig_buf: [86]u8 = undefined;\n    var pubkey_buf: [56]u8 = undefined;\n    var sig_slice: ?[]const u8 = null;\n    var pubkey_slice: ?[]const u8 = null;\n    // Buffer for credentials file (must outlive signing operation)\n    var creds_buf: [8192]u8 = undefined;\n    defer std.crypto.secureZero(u8, &creds_buf);\n    // Buffer for seed from file (must outlive signing operation)\n    var file_seed_buf: [128]u8 = undefined;\n    defer std.crypto.secureZero(u8, &file_seed_buf);\n    // JWT to send (may come from opts.jwt or parsed credentials)\n    var jwt_to_send: ?[]const u8 = opts.jwt;\n\n    if (opts.creds_file) |path| {\n        // Load credentials from file (propagates file system errors)\n        const creds = try creds_auth.loadFile(self.io, path, &creds_buf);\n        jwt_to_send = creds.jwt;\n\n        if (self.server_info.?.nonce) |nonce| {\n            var kp = nkey_auth.KeyPair.fromSeed(creds.seed) catch {\n                return error.InvalidNKeySeed;\n            };\n            defer kp.wipe();\n\n            sig_slice = kp.signEncoded(nonce, &sig_buf);\n            pubkey_slice = kp.publicKey(&pubkey_buf);\n        }\n        // Note: creds_buf contains JWT (not secret) and seed.\n        // Seed is wiped via kp.wipe(). Buffer on stack gets overwritten.\n    } else if (opts.creds) |content| {\n        // Parse credentials from provided content\n        const creds = try creds_auth.parse(content);\n        jwt_to_send = creds.jwt;\n\n        if (self.server_info.?.nonce) |nonce| {\n            var kp = nkey_auth.KeyPair.fromSeed(creds.seed) catch {\n                return error.InvalidNKeySeed;\n            };\n            defer kp.wipe();\n\n            sig_slice = kp.signEncoded(nonce, &sig_buf);\n            pubkey_slice = kp.publicKey(&pubkey_buf);\n        }\n    } else if (opts.nkey_seed) |seed| {\n        if (self.server_info.?.nonce) |nonce| {\n            var kp = nkey_auth.KeyPair.fromSeed(seed) catch {\n                return error.InvalidNKeySeed;\n            };\n            defer kp.wipe();\n\n            sig_slice = kp.signEncoded(nonce, &sig_buf);\n            pubkey_slice = kp.publicKey(&pubkey_buf);\n        }\n    } else if (opts.nkey_seed_file) |path| {\n        if (self.server_info.?.nonce) |nonce| {\n            const seed = try readSeedFile(self.io, path, &file_seed_buf);\n            defer std.crypto.secureZero(u8, file_seed_buf[0..seed.len]);\n\n            var kp = nkey_auth.KeyPair.fromSeed(seed) catch {\n                return error.InvalidNKeySeed;\n            };\n            defer kp.wipe();\n\n            sig_slice = kp.signEncoded(nonce, &sig_buf);\n            pubkey_slice = kp.publicKey(&pubkey_buf);\n        }\n    } else if (opts.nkey_sign_fn) |sign_fn| {\n        if (self.server_info.?.nonce) |nonce| {\n            var raw_sig: [64]u8 = undefined;\n            if (!sign_fn(nonce, &raw_sig)) {\n                return error.NKeySigningFailed;\n            }\n            sig_slice = std.base64.url_safe_no_pad.Encoder.encode(\n                &sig_buf,\n                &raw_sig,\n            );\n            pubkey_slice = opts.nkey_pubkey;\n        }\n    }\n\n    const connect_opts = protocol.ConnectOptions{\n        .verbose = opts.verbose,\n        .pedantic = opts.pedantic,\n        .name = opts.name,\n        .user = user,\n        .pass = pass,\n        .auth_token = auth_token,\n        .lang = \"zig\",\n        .version = \"0.1.0\",\n        .protocol = 1,\n        .echo = opts.echo,\n        .headers = opts.headers,\n        .no_responders = opts.no_responders,\n        .tls_required = opts.tls_required or parsed.use_tls,\n        .jwt = jwt_to_send,\n        .nkey = pubkey_slice,\n        .sig = sig_slice,\n    };\n\n    protocol.Encoder.encodeConnect(writer_for_connect, connect_opts) catch {\n        return error.EncodingFailed;\n    };\n\n    writer_for_connect.flush() catch {\n        return error.WriteFailed;\n    };\n\n    // Check for auth rejection\n    if (self.server_info.?.auth_required) {\n        try self.checkAuthRejection(opts.connect_timeout_ns);\n    }\n}\n\n/// Checks auth by sending a bounded PING and waiting for PONG or -ERR.\nfn checkAuthRejection(self: *Client, timeout_ns: u64) !void {\n    assert(self.state == .connected);\n    assert(timeout_ns > 0);\n\n    const reader = self.active_reader;\n    const writer = self.active_writer;\n\n    writer.writeAll(\"PING\\r\\n\") catch return error.WriteFailed;\n    writer.flush() catch return error.WriteFailed;\n    if (self.tls_client != null) {\n        self.writer.interface.flush() catch return error.WriteFailed;\n    }\n\n    const start_ns = getNowNs(self.io);\n    while (true) {\n        const now_ns = getNowNs(self.io);\n        if (now_ns - start_ns >= timeout_ns) {\n            return error.ConnectionTimeout;\n        }\n        const remaining = timeout_ns - (now_ns - start_ns);\n        const response = try self.peekWithTimeout(reader, remaining);\n\n        var consumed: usize = 0;\n        const cmd = self.parser.parse(\n            self.allocator,\n            response,\n            &consumed,\n        ) catch return error.ProtocolError;\n\n        if (consumed > 0) reader.toss(consumed);\n\n        if (cmd) |c| {\n            switch (c) {\n                .pong => return,\n                .err => {\n                    self.state = .closed;\n                    return error.AuthorizationViolation;\n                },\n                .ok => continue,\n                .ping => {\n                    writer.writeAll(\"PONG\\r\\n\") catch return error.WriteFailed;\n                    writer.flush() catch return error.WriteFailed;\n                },\n                .info => |info| {\n                    var owned = info;\n                    owned.deinit(self.allocator);\n                },\n                else => continue,\n            }\n        } else {\n            self.io.sleep(.fromNanoseconds(0), .awake) catch {};\n        }\n    }\n}\n\n/// Reads from socket with connection timeout using Io.Select.\n/// Returns data or error.ConnectionTimeout if timeout expires.\nfn peekWithTimeout(\n    self: *Client,\n    reader: *Io.Reader,\n    timeout_ns: u64,\n) ![]u8 {\n    assert(timeout_ns > 0);\n\n    const Sel = Io.Select(union(enum) {\n        read: anyerror![]u8,\n        timeout: void,\n    });\n    var buf: [2]Sel.Union = undefined;\n    var sel = Sel.init(self.io, &buf);\n    sel.async(.read, peekGreedyAsync, .{ reader, self.io });\n    sel.async(.timeout, sleepNs, .{ self.io, timeout_ns });\n\n    const result = sel.await() catch {\n        sel.cancelDiscard();\n        return error.ConnectionFailed;\n    };\n    sel.cancelDiscard();\n\n    switch (result) {\n        .read => |read_result| {\n            return read_result catch error.ConnectionFailed;\n        },\n        .timeout => {\n            return error.ConnectionTimeout;\n        },\n    }\n}\n\n/// Async wrapper for peekGreedy (used with io.async).\nfn peekGreedyAsync(reader: *Io.Reader, io: Io) ![]u8 {\n    _ = io;\n    return reader.peekGreedy(1);\n}\n\n/// Cleanup subscription resources after failed registration.\n/// Inline to avoid function call overhead in error path.\ninline fn cleanupFailedSub(\n    self: *Client,\n    sub: *Sub,\n    slot_idx: u16,\n    queue_buf: []Message,\n    owned_queue: ?[]const u8,\n    owned_subject: []const u8,\n    remove_from_sidmap: bool,\n) void {\n    const allocator = self.allocator;\n    if (remove_from_sidmap) {\n        _ = self.sidmap.remove(sub.sid);\n        self.sub_ptrs[slot_idx] = null;\n    }\n    if (self.cached_sub == sub) self.cached_sub = null;\n    self.free_slots[self.free_count] = slot_idx;\n    self.free_count += 1;\n    allocator.free(queue_buf);\n    if (owned_queue) |qg| allocator.free(qg);\n    allocator.free(owned_subject);\n    allocator.destroy(sub);\n}\n\n/// Subscribes to a subject.\n///\n/// Arguments:\n///     subject: Subject pattern to subscribe to (wildcards allowed: *, >)\n///\n/// Returns subscription pointer. Caller must call sub.deinit() when done.\npub fn subscribeSync(\n    self: *Client,\n    subject: []const u8,\n) !*Sub {\n    return self.queueSubscribeSync(subject, null);\n}\n\n/// Subscribes with queue group for load balancing.\n///\n/// Arguments:\n///     subject: Subject pattern to subscribe to\n///     queue_group: Queue group name (messages distributed among members)\n///\n/// Queue groups allow multiple subscribers to share the message load.\n/// Only one subscriber in the group receives each message.\npub fn queueSubscribeSync(\n    self: *Client,\n    subject: []const u8,\n    queue_group: ?[]const u8,\n) !*Sub {\n    const allocator = self.allocator;\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n    try pubsub.validateSubscribe(subject);\n    if (queue_group) |qg| try pubsub.validateQueueGroup(qg);\n\n    // Validate lengths for backup buffer compatibility\n    if (subject.len >= defaults.Limits.max_subject_len)\n        return error.SubjectTooLong;\n\n    if (queue_group) |qg| {\n        if (qg.len > defaults.Limits.max_queue_group_len) {\n            return error.QueueGroupTooLong;\n        }\n    }\n    assert(self.next_sid >= 1);\n\n    // sub_mutex serializes slot/SID allocation and SUB encoding\n    // for multi-thread subscribe safety.\n    self.sub_mutex.lockUncancelable(self.io);\n    defer self.sub_mutex.unlock(self.io);\n\n    // Allocate slot\n    if (self.free_count == 0) {\n        return error.TooManySubscriptions;\n    }\n    self.free_count -= 1;\n    const slot_idx = self.free_slots[self.free_count];\n\n    const sid = self.next_sid;\n    self.next_sid += 1;\n\n    // Create subscription\n    const sub = try allocator.create(Sub);\n    errdefer {\n        allocator.destroy(sub);\n        self.free_slots[self.free_count] = slot_idx;\n        self.free_count += 1;\n    }\n\n    const owned_subject = try allocator.dupe(u8, subject);\n    errdefer allocator.free(owned_subject);\n\n    const owned_queue = if (queue_group) |qg|\n        try allocator.dupe(u8, qg)\n    else\n        null;\n    errdefer if (owned_queue) |qg| allocator.free(qg);\n\n    // Allocate Io.Queue buffer\n    const queue_size = self.options.sub_queue_size;\n    const queue_buf = try allocator.alloc(Message, queue_size);\n    errdefer allocator.free(queue_buf);\n\n    sub.* = .{\n        .client = self,\n        .sid = sid,\n        .subject = owned_subject,\n        .queue_group = owned_queue,\n        .queue_buf = queue_buf,\n        .queue = .init(queue_buf),\n        .state = .active,\n        .received_msgs = 0,\n    };\n\n    // Store in SidMap\n    self.sidmap.put(sid, slot_idx) catch {\n        self.cleanupFailedSub(\n            sub,\n            slot_idx,\n            queue_buf,\n            owned_queue,\n            owned_subject,\n            false,\n        );\n        return error.TooManySubscriptions;\n    };\n    self.sub_ptrs[slot_idx] = sub;\n    self.cached_sub = sub;\n\n    // Enqueue SUB onto the ordered outbound ring so it cannot\n    // overtake or be overtaken by queued publishes.\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    self.encodeSubToRing(.{\n        .subject = subject,\n        .queue_group = queue_group,\n        .sid = sid,\n    }) catch {\n        self.cleanupFailedSub(\n            sub,\n            slot_idx,\n            queue_buf,\n            owned_queue,\n            owned_subject,\n            true,\n        );\n        return error.EncodingFailed;\n    };\n\n    // Signal auto-flush to register subscription promptly\n    self.flush_requested.store(true, .release);\n\n    return sub;\n}\n\n/// Subscribes with a MsgHandler callback.\n/// Messages are dispatched to handler.onMessage() automatically.\n/// The drain task frees each message after dispatch.\n///\n/// Do NOT call nextMsg()/tryNextMsg() on the returned subscription.\npub fn subscribe(\n    self: *Client,\n    subject: []const u8,\n    handler: MsgHandler,\n) !*Sub {\n    return self.queueSubscribe(\n        subject,\n        null,\n        handler,\n    );\n}\n\n/// Subscribes with a MsgHandler callback and queue group.\npub fn queueSubscribe(\n    self: *Client,\n    subject: []const u8,\n    queue_group: ?[]const u8,\n    handler: MsgHandler,\n) !*Sub {\n    const sub = try self.queueSubscribeSync(\n        subject,\n        queue_group,\n    );\n    errdefer sub.deinit();\n    sub.mode = .callback;\n    sub.callback_future = self.io.concurrent(\n        callbackDrainFn,\n        .{ sub, handler },\n    ) catch self.io.async(\n        callbackDrainFn,\n        .{ sub, handler },\n    );\n    return sub;\n}\n\n/// Subscribes with a plain function callback.\n/// Simpler alternative when no handler state is needed.\npub fn subscribeFn(\n    self: *Client,\n    subject: []const u8,\n    cb: *const fn (*const Message) void,\n) !*Sub {\n    return self.queueSubscribeFn(\n        subject,\n        null,\n        cb,\n    );\n}\n\n/// Subscribes with a plain function callback and queue group.\npub fn queueSubscribeFn(\n    self: *Client,\n    subject: []const u8,\n    queue_group: ?[]const u8,\n    cb: *const fn (*const Message) void,\n) !*Sub {\n    const sub = try self.queueSubscribeSync(\n        subject,\n        queue_group,\n    );\n    errdefer sub.deinit();\n    sub.mode = .callback;\n    sub.callback_future = self.io.concurrent(\n        callbackDrainFnPlain,\n        .{ sub, cb },\n    ) catch self.io.async(\n        callbackDrainFnPlain,\n        .{ sub, cb },\n    );\n    return sub;\n}\n\n/// Publishes a message to a subject.\n///\n/// Thread-safe: serialized by publish_mutex. Encodes into\n/// the publish ring buffer; io_task drains to socket.\npub fn publish(\n    self: *Client,\n    subject: []const u8,\n    payload: []const u8,\n) !void {\n    const state = State.atomicLoad(&self.state);\n    try pubsub.validatePublish(subject);\n    if (payload.len > self.max_payload)\n        return error.PayloadTooLarge;\n\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    if (!state.canSend()) {\n        if (self.options.reconnect and\n            (state == .reconnecting or state == .disconnected))\n        {\n            try self.bufferPendingPublish(subject, payload);\n            _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n            _ = self.statistics.bytes_out.fetchAdd(payload.len, .monotonic);\n            return;\n        }\n        return error.NotConnected;\n    }\n\n    try self.encodePubToRing(\n        subject,\n        null,\n        payload,\n    );\n\n    _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n    _ = self.statistics.bytes_out.fetchAdd(\n        payload.len,\n        .monotonic,\n    );\n    self.flush_requested.store(true, .release);\n}\n\n/// Publishes with a reply-to subject.\n/// Thread-safe: serialized by publish_mutex.\npub fn publishRequest(\n    self: *Client,\n    subject: []const u8,\n    reply_to: []const u8,\n    payload: []const u8,\n) !void {\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n    try pubsub.validatePublish(subject);\n    try pubsub.validateReplyTo(reply_to);\n    if (payload.len > self.max_payload)\n        return error.PayloadTooLarge;\n\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    try self.encodePubToRing(\n        subject,\n        reply_to,\n        payload,\n    );\n\n    _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n    _ = self.statistics.bytes_out.fetchAdd(\n        payload.len,\n        .monotonic,\n    );\n    self.flush_requested.store(true, .release);\n}\n\n/// Publishes a message with headers.\n/// Thread-safe: serialized by publish_mutex.\npub fn publishWithHeaders(\n    self: *Client,\n    subject: []const u8,\n    hdrs: []const headers.Entry,\n    payload: []const u8,\n) !void {\n    if (!State.atomicLoad(&self.state).canSend()) return error.NotConnected;\n    try pubsub.validatePublish(subject);\n    try headers.validateEntries(hdrs);\n\n    const hdr_size = headers.encodedSize(hdrs);\n    if (hdr_size + payload.len > self.max_payload)\n        return error.PayloadTooLarge;\n\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    try self.encodeHPubToRing(\n        subject,\n        null,\n        hdrs,\n        payload,\n    );\n\n    _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n    _ = self.statistics.bytes_out.fetchAdd(\n        payload.len,\n        .monotonic,\n    );\n    self.flush_requested.store(true, .release);\n}\n\n/// Publishes with headers and reply-to subject.\n/// Thread-safe: serialized by publish_mutex.\npub fn publishRequestWithHeaders(\n    self: *Client,\n    subject: []const u8,\n    reply_to: []const u8,\n    hdrs: []const headers.Entry,\n    payload: []const u8,\n) !void {\n    if (!State.atomicLoad(&self.state).canSend()) return error.NotConnected;\n    try pubsub.validatePublish(subject);\n    try pubsub.validateReplyTo(reply_to);\n    try headers.validateEntries(hdrs);\n\n    const hdr_size = headers.encodedSize(hdrs);\n    if (hdr_size + payload.len > self.max_payload)\n        return error.PayloadTooLarge;\n\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    try self.encodeHPubToRing(\n        subject,\n        reply_to,\n        hdrs,\n        payload,\n    );\n\n    _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n    _ = self.statistics.bytes_out.fetchAdd(\n        payload.len,\n        .monotonic,\n    );\n    self.flush_requested.store(true, .release);\n}\n\n/// Publishes with a HeaderMap builder.\n/// Thread-safe: serialized by publish_mutex.\npub fn publishWithHeaderMap(\n    self: *Client,\n    subject: []const u8,\n    header_map: *const protocol.HeaderMap,\n    payload: []const u8,\n) !void {\n    if (header_map.isEmpty()) return error.EmptyHeaders;\n    if (!State.atomicLoad(&self.state).canSend()) return error.NotConnected;\n    try pubsub.validatePublish(subject);\n\n    const hdr_bytes = try header_map.encode();\n    defer header_map.allocator.free(hdr_bytes);\n\n    if (hdr_bytes.len + payload.len > self.max_payload)\n        return error.PayloadTooLarge;\n\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    try self.encodeHPubRawToRing(\n        subject,\n        null,\n        hdr_bytes,\n        payload,\n    );\n\n    _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n    _ = self.statistics.bytes_out.fetchAdd(\n        payload.len,\n        .monotonic,\n    );\n    self.flush_requested.store(true, .release);\n}\n\n/// Publishes a Message object (convenience for forwarding).\n/// Thread-safe: serialized by publish_mutex.\npub fn publishMsg(\n    self: *Client,\n    msg: *const Message,\n) !void {\n    if (!State.atomicLoad(&self.state).canSend()) return error.NotConnected;\n    try pubsub.validatePublish(msg.subject);\n\n    const total_size = if (msg.headers) |h|\n        h.len + msg.data.len\n    else\n        msg.data.len;\n    if (total_size > self.max_payload)\n        return error.PayloadTooLarge;\n\n    self.publish_mutex.lockUncancelable(self.io);\n    defer self.publish_mutex.unlock(self.io);\n\n    if (msg.headers) |hdrs| {\n        try self.encodeHPubRawToRing(\n            msg.subject,\n            null,\n            hdrs,\n            msg.data,\n        );\n    } else {\n        try self.encodePubToRing(\n            msg.subject,\n            null,\n            msg.data,\n        );\n    }\n\n    _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n    _ = self.statistics.bytes_out.fetchAdd(\n        msg.data.len,\n        .monotonic,\n    );\n    self.flush_requested.store(true, .release);\n}\n\n/// Sends a request with headers and waits for a reply with timeout.\n///\n/// Arguments:\n///     subject: Request destination subject\n///     hdrs: Header entries to include in request\n///     payload: Request data\n///     timeout_ms: Maximum time to wait for reply in milliseconds\n///\n/// Sends request with reply-to and headers, then waits for the\n/// response multiplexer using a direct deadline loop.\n/// Returns null on timeout.\npub fn requestWithHeaders(\n    self: *Client,\n    subject: []const u8,\n    hdrs: []const headers.Entry,\n    payload: []const u8,\n    timeout_ms: u32,\n) !?Message {\n    assert(timeout_ms > 0);\n    assert(subject.len > 0);\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n\n    var waiter: RespWaiter = .{};\n    var reply_buf: [256]u8 = undefined;\n    const reply = try self.requestPrepReply(&waiter, &reply_buf);\n    errdefer self.cleanupRespWaiter(&waiter, reply);\n\n    try self.publishRequestWithHeaders(\n        subject,\n        reply,\n        hdrs,\n        payload,\n    );\n\n    try self.flushBuffer();\n\n    return self.requestAwaitResp(&waiter, reply, timeout_ms);\n}\n\n/// Flushes pending writes to the server.\n///\n/// Sends all buffered data to the TCP socket. This is a simple TCP flush\n/// without PING/PONG verification - for maximum performance.\n/// Sends all buffered data to the network (buffer-to-socket only).\n///\n/// This is a fast flush that does not wait for server confirmation.\n/// For confirmed delivery, use flush() which sends PING and waits for PONG.\npub fn flushBuffer(self: *Client) !void {\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n\n    try self.write_mutex.lock(self.io);\n    defer self.write_mutex.unlock(self.io);\n\n    try self.flushBufferLocked();\n}\n\nfn flushBufferLocked(self: *Client) !void {\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n\n    while (self.publish_ring.peek()) |data| {\n        self.active_writer.writeAll(data) catch return error.WriteFailed;\n        self.publish_ring.advance();\n    }\n\n    self.active_writer.flush() catch return error.WriteFailed;\n\n    // TLS: active_writer.flush() only encrypts to TCP buffer.\n    // Must also flush the underlying TCP writer to send to network.\n    if (self.use_tls) {\n        self.writer.interface.flush() catch return error.WriteFailed;\n    }\n}\n\n/// Flushes all buffered data and confirms server received it.\n///\n/// Sends all buffered data, then sends PING and waits for PONG response.\n/// This confirms the server received all messages up to this point.\n/// Safe in both sync and async contexts.\n///\n/// This matches Go/C client Flush() behavior (PING/PONG confirmation).\n/// For fast buffer-only flush without confirmation, use flushBuffer().\n///\n/// Note: Concurrent calls may have PONG mismatch issues. Use single-caller\n/// semantics or serialize calls externally.\npub fn flush(\n    self: *Client,\n    timeout_ns: u64,\n) !void {\n    assert(timeout_ns > 0);\n    if (!State.atomicLoad(&self.state).canSend()) return error.NotConnected;\n\n    const old_pong_ns = self.last_pong_received_ns.load(.acquire);\n\n    // Step 1: Drain publish ring + flush + send PING (holding mutex)\n    try self.write_mutex.lock(self.io);\n\n    // Drain any pending publishes from the ring first\n    while (self.publish_ring.peek()) |data| {\n        self.active_writer.writeAll(data) catch {\n            self.write_mutex.unlock(self.io);\n            return error.WriteFailed;\n        };\n        self.publish_ring.advance();\n    }\n\n    self.active_writer.flush() catch {\n        self.write_mutex.unlock(self.io);\n        return error.WriteFailed;\n    };\n    if (self.use_tls) {\n        self.writer.interface.flush() catch {\n            self.write_mutex.unlock(self.io);\n            return error.WriteFailed;\n        };\n    }\n\n    // Send PING while still holding mutex (ensures ordering)\n    self.active_writer.writeAll(\"PING\\r\\n\") catch {\n        self.write_mutex.unlock(self.io);\n        return error.WriteFailed;\n    };\n    self.active_writer.flush() catch {\n        self.write_mutex.unlock(self.io);\n        return error.WriteFailed;\n    };\n    if (self.use_tls) {\n        self.writer.interface.flush() catch {\n            self.write_mutex.unlock(self.io);\n            return error.WriteFailed;\n        };\n    }\n    self.write_mutex.unlock(self.io);\n\n    // Step 2: Poll for PONG with timeout (direct loop, no Io.Select).\n    // Using direct polling avoids layering a second async wait inside\n    // a synchronous flush call on this client's Io.\n    const deadline_ns = getNowNs(self.io) +| timeout_ns;\n    var iteration: u32 = 0;\n\n    dbg.print(\"flush: waiting for PONG, old_pong_ns={d}\", .{old_pong_ns});\n\n    while (true) {\n        // Check for PONG\n        const current = self.last_pong_received_ns.load(.acquire);\n        if (current > old_pong_ns) {\n            dbg.print(\"flush: got PONG, current={d}\", .{current});\n            return; // Success!\n        }\n\n        // Check timeout\n        const now = getNowNs(self.io);\n        if (now >= deadline_ns) return error.Timeout;\n\n        // Yield periodically to allow io_task to process incoming PONG\n        iteration += 1;\n        if (iteration >= 100) {\n            iteration = 0;\n            std.Thread.yield() catch {};\n        }\n        std.atomic.spinLoopHint();\n    }\n}\n\n/// Forces an immediate reconnection attempt.\n/// Closes the current connection and triggers reconnection logic.\n/// Subscriptions will be restored automatically.\npub fn forceReconnect(self: *Client) !void {\n    const state = State.atomicLoad(&self.state);\n    if (state == .closed) return error.ConnectionClosed;\n    if (state == .reconnecting) return;\n    if (state != .connected) return error.NotConnected;\n\n    assert(self.next_sid >= 1);\n\n    // State first: canSend() returns false, no new writes\n    State.atomicStore(&self.state, .reconnecting);\n    // Shutdown read only -- in-flight writes stay safe.\n    // io_task sees EndOfStream, enters handleDisconnect\n    // which calls cleanupForReconnect for full close\n    // with write_mutex. Guard: fd may be invalid.\n    if (isValidFd(self.stream.socket.handle)) {\n        self.stream.shutdown(self.io, .recv) catch {};\n    }\n}\n\n/// Gracefully drains with a timeout.\n/// Returns error.Timeout if drain doesn't complete in time.\npub fn drainTimeout(\n    self: *Client,\n    timeout_ns: u64,\n) !DrainResult {\n    assert(timeout_ns > 0);\n    if (State.atomicLoad(&self.state) != .connected) {\n        return error.NotConnected;\n    }\n\n    const Sel = Io.Select(union(enum) {\n        drain: anyerror!DrainResult,\n        timeout: void,\n    });\n    var buf: [2]Sel.Union = undefined;\n    var sel = Sel.init(self.io, &buf);\n    sel.async(.drain, drainHelper, .{self});\n    sel.async(.timeout, sleepNs, .{ self.io, timeout_ns });\n\n    const select_result = sel.await() catch {\n        sel.cancelDiscard();\n        return error.Canceled;\n    };\n    sel.cancelDiscard();\n\n    switch (select_result) {\n        .drain => |result| {\n            return result;\n        },\n        .timeout => {\n            return error.Timeout;\n        },\n    }\n}\n\n/// Helper for async drain.\nfn drainHelper(self: *Client) !DrainResult {\n    return self.drain();\n}\n\n/// Lazily initializes the response multiplexer on the first\n/// request() call. Subsequent calls fast-path on the atomic\n/// `initialized` flag.\n///\n/// Init steps (run once per connection lifetime):\n///   1. Build the inbox prefix and wildcard subject\n///   2. subscribe(wildcard, RespMux as handler)\n///   3. flush() - PING/PONG round-trip confirms server has the SUB\n///   4. Publish init under resp_mux.mutex with double-checked\n///      pattern (concurrent first-requesters race; loser cleans up)\n///\n/// Lock discipline: resp_mux.mutex is NOT held across the slow\n/// flush() call. Holding it would risk deadlock if the io_task\n/// were spinning trying to call respMux.onMessage while flush()\n/// spin-waits on the PONG atomic. The window is safe because the\n/// wildcard \"_INBOX.<NUID>.*\" is unique to this connection and no\n/// peer can publish to it before the first request.\nfn ensureRespMux(self: *Client) !void {\n    if (self.resp_mux.initialized.load(.acquire)) return;\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n\n    const allocator = self.allocator;\n    const base = try self.newInbox();\n    defer allocator.free(base);\n    assert(base.len > 0);\n\n    const prefix = try std.fmt.allocPrint(\n        allocator,\n        \"{s}.\",\n        .{base},\n    );\n    errdefer allocator.free(prefix);\n\n    // wildcard = \"<base>.*\" — the trailing dot is in prefix already.\n    var wildcard_buf: [256]u8 = undefined;\n    const wildcard = std.fmt.bufPrint(\n        &wildcard_buf,\n        \"{s}*\",\n        .{prefix},\n    ) catch return error.SubjectTooLong;\n\n    self.resp_mux.client = self;\n\n    const sub = try self.subscribe(\n        wildcard,\n        MsgHandler.init(RespMux, &self.resp_mux),\n    );\n    errdefer sub.deinit();\n\n    try self.flush(5 * std.time.ns_per_s);\n\n    self.resp_mux.mutex.lockUncancelable(self.io);\n    defer self.resp_mux.mutex.unlock(self.io);\n\n    if (self.resp_mux.initialized.load(.acquire)) {\n        sub.deinit();\n        allocator.free(prefix);\n        return;\n    }\n\n    self.resp_mux.prefix = prefix;\n    self.resp_mux.prefix_len = prefix.len;\n    self.resp_mux.sub = sub;\n    self.resp_mux.initialized.store(true, .release);\n}\n\n/// Inner helper: registers a stack-allocated waiter in the resp\n/// map and returns the assembled reply subject. The caller MUST\n/// invoke requestAwaitResp (or manually clean up the map) before\n/// the waiter goes out of scope.\n///\n/// Note: the map key is a slice of reply_buf (the caller's stack\n/// buffer). The cleanup defer in requestAwaitResp removes the\n/// entry before the caller's frame is unwound.\nfn requestPrepReply(\n    self: *Client,\n    waiter: *RespWaiter,\n    reply_buf: *[256]u8,\n) ![]const u8 {\n    try self.ensureRespMux();\n    assert(self.resp_mux.prefix != null);\n    assert(self.resp_mux.prefix_len > 0);\n\n    var token_buf: [8]u8 = undefined;\n    const token = generateRespToken(\n        &token_buf,\n        self.resp_mux.next_token.fetchAdd(1, .monotonic),\n    );\n\n    const reply = std.fmt.bufPrint(\n        reply_buf,\n        \"{s}{s}\",\n        .{ self.resp_mux.prefix.?, token },\n    ) catch return error.SubjectTooLong;\n\n    // Register using a slice of `reply` (lives in caller's frame).\n    // This must outlive the map entry — caller's deferred cleanup\n    // in requestAwaitResp removes the entry before reply_buf dies.\n    const map_key = reply[self.resp_mux.prefix_len..];\n\n    self.resp_mux.mutex.lockUncancelable(self.io);\n    self.resp_mux.map.put(\n        self.allocator,\n        map_key,\n        waiter,\n    ) catch {\n        self.resp_mux.mutex.unlock(self.io);\n        return error.OutOfMemory;\n    };\n    self.resp_mux.mutex.unlock(self.io);\n\n    return reply;\n}\n\n/// Removes a waiter from the resp map. Used as an error-path\n/// cleanup helper when publish fails after the waiter is\n/// registered but before requestAwaitResp would run. If the\n/// dispatcher already removed it (and possibly populated msg),\n/// the returned message is freed by the caller via the same\n/// path requestAwaitResp would use.\nfn cleanupRespWaiter(\n    self: *Client,\n    waiter: *RespWaiter,\n    reply: []const u8,\n) void {\n    assert(self.resp_mux.prefix_len > 0);\n    const map_key = reply[self.resp_mux.prefix_len..];\n    self.resp_mux.mutex.lockUncancelable(self.io);\n    _ = self.resp_mux.map.remove(map_key);\n    self.resp_mux.mutex.unlock(self.io);\n    if (waiter.msg) |m| {\n        m.deinit();\n        waiter.msg = null;\n    }\n}\n\n/// Inner helper: waits for the waiter or deadline, cleans up the\n/// map entry on every exit path, and returns the response or null.\n/// Acquiring resp_mux.mutex in the cleanup defer serializes against\n/// any concurrent dispatcher (which holds the same mutex during\n/// clone+write), preventing use-after-free of the stack-allocated\n/// waiter.\nfn requestAwaitResp(\n    self: *Client,\n    waiter: *RespWaiter,\n    reply: []const u8,\n    timeout_ms: u32,\n) ?Message {\n    assert(self.resp_mux.prefix_len > 0);\n    const map_key = reply[self.resp_mux.prefix_len..];\n    const timeout_ns: u64 =\n        @as(u64, timeout_ms) * std.time.ns_per_ms;\n    const start = getNowNs(self.io);\n    var spin_count: u32 = 0;\n\n    defer {\n        self.resp_mux.mutex.lockUncancelable(self.io);\n        _ = self.resp_mux.map.remove(map_key);\n        self.resp_mux.mutex.unlock(self.io);\n    }\n\n    while (!waiter.done.load(.acquire)) {\n        spin_count += 1;\n        if (spin_count < defaults.Spin.max_spins) {\n            std.atomic.spinLoopHint();\n        } else {\n            self.io.sleep(\n                .fromNanoseconds(0),\n                .awake,\n            ) catch |err| {\n                if (err == error.Canceled)\n                    return waiter.msg;\n            };\n            spin_count = 0;\n\n            const now = getNowNs(self.io);\n            if (now -| start >= timeout_ns)\n                return waiter.msg;\n        }\n    }\n\n    return waiter.msg;\n}\n\n/// Cleans up the response multiplexer. Called from Client.deinit\n/// before subscriptions are torn down. Signals all in-flight\n/// waiters with null msg so any blocked request() calls unblock\n/// and return null cleanly.\nfn closeRespMux(self: *Client) void {\n    const io = self.io;\n    self.resp_mux.mutex.lockUncancelable(io);\n    var it = self.resp_mux.map.iterator();\n    while (it.next()) |entry| {\n        entry.value_ptr.*.done.store(true, .release);\n    }\n    self.resp_mux.map.clearAndFree(self.allocator);\n    self.resp_mux.mutex.unlock(io);\n\n    if (self.resp_mux.sub) |sub| {\n        sub.deinit();\n        self.resp_mux.sub = null;\n    }\n    if (self.resp_mux.prefix) |p| {\n        self.allocator.free(p);\n        self.resp_mux.prefix = null;\n        self.resp_mux.prefix_len = 0;\n    }\n    self.resp_mux.initialized.store(false, .release);\n    self.resp_mux.client = null;\n}\n\n/// Sends a request and waits for a reply with timeout.\n///\n/// Arguments:\n///     subject: Request destination subject\n///     payload: Request data\n///     timeout_ms: Maximum time to wait for reply in milliseconds\n///\n/// Uses the response multiplexer: a single wildcard inbox\n/// subscription is created on first call (with PING/PONG sync)\n/// and reused for the connection lifetime. Each call registers\n/// a stack-allocated waiter in the resp_map keyed by an 8-char\n/// token, publishes the request with reply-to set to the\n/// per-request inbox, and races the response against the timeout.\n/// Returns null on timeout, cancellation, or no-responders.\npub fn request(\n    self: *Client,\n    subject: []const u8,\n    payload: []const u8,\n    timeout_ms: u32,\n) !?Message {\n    assert(timeout_ms > 0);\n    assert(subject.len > 0);\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n\n    var waiter: RespWaiter = .{};\n    var reply_buf: [256]u8 = undefined;\n    const reply = try self.requestPrepReply(&waiter, &reply_buf);\n    errdefer self.cleanupRespWaiter(&waiter, reply);\n\n    try self.publishRequest(subject, reply, payload);\n    try self.flushBuffer();\n\n    return self.requestAwaitResp(&waiter, reply, timeout_ms);\n}\n\n/// Sends a request using a Message object and waits for a reply.\n///\n/// Arguments:\n///     msg: Message to send (uses subject, data, and headers if present)\n///     timeout_ms: Maximum time to wait for reply in milliseconds\n///\n/// Useful for forwarding request messages or republishing with same\n/// content. Routes through the response multiplexer (see request()).\n/// The msg.headers field carries pre-encoded raw header bytes; this\n/// preserves them verbatim across the request/reply round-trip.\npub fn requestMsg(\n    self: *Client,\n    msg: *const Message,\n    timeout_ms: u32,\n) !?Message {\n    assert(msg.subject.len > 0);\n    assert(timeout_ms > 0);\n    if (!State.atomicLoad(&self.state).canSend()) {\n        return error.NotConnected;\n    }\n\n    var waiter: RespWaiter = .{};\n    var reply_buf: [256]u8 = undefined;\n    const reply = try self.requestPrepReply(&waiter, &reply_buf);\n    errdefer self.cleanupRespWaiter(&waiter, reply);\n\n    {\n        self.publish_mutex.lockUncancelable(self.io);\n        defer self.publish_mutex.unlock(self.io);\n\n        if (msg.headers) |hdrs| {\n            try self.encodeHPubRawToRing(\n                msg.subject,\n                reply,\n                hdrs,\n                msg.data,\n            );\n        } else {\n            try self.encodePubToRing(\n                msg.subject,\n                reply,\n                msg.data,\n            );\n        }\n\n        _ = self.statistics.msgs_out.fetchAdd(1, .monotonic);\n        _ = self.statistics.bytes_out.fetchAdd(\n            msg.data.len,\n            .monotonic,\n        );\n\n        self.flush_requested.store(true, .release);\n    }\n\n    try self.flushBuffer();\n\n    return self.requestAwaitResp(&waiter, reply, timeout_ms);\n}\n\n/// Helper for connection timeout (nanoseconds).\nfn sleepNs(io: Io, timeout_ns: u64) void {\n    io.sleep(.fromNanoseconds(timeout_ns), .awake) catch {};\n}\n\n/// Reads NKey seed from file, trimming whitespace.\n/// Returns slice into buf containing the seed.\n/// File system errors (FileNotFound, AccessDenied, etc.) propagate directly.\n/// Returns InvalidNKeySeedFile only for content issues (empty/whitespace-only).\nfn readSeedFile(io: Io, path: []const u8, buf: *[128]u8) ![]const u8 {\n    assert(path.len > 0);\n\n    const data = try Io.Dir.readFile(.cwd(), io, path, buf);\n\n    if (data.len == 0) return error.InvalidNKeySeedFile;\n    assert(data.len > 0);\n\n    // Trim leading/trailing whitespace\n    var start: usize = 0;\n    var end: usize = data.len;\n\n    while (start < end and std.ascii.isWhitespace(buf[start])) {\n        start += 1;\n    }\n    while (end > start and std.ascii.isWhitespace(buf[end - 1])) {\n        end -= 1;\n    }\n\n    if (start >= end) return error.InvalidNKeySeedFile;\n    assert(start < end);\n    return buf[start..end];\n}\n\n/// Gracefully drains subscriptions and closes the connection.\n///\n/// Returns DrainResult indicating any failures during cleanup.\n/// Unsubscribes all active subscriptions, drains remaining messages,\n/// and closes the connection. Use for graceful shutdown.\npub fn drain(self: *Client) !DrainResult {\n    const alloc = self.allocator;\n    if (State.atomicLoad(&self.state) != .connected) {\n        return error.NotConnected;\n    }\n    assert(self.next_sid >= 1);\n\n    var result: DrainResult = .{};\n\n    // Lock ordering: sub_mutex -> read_mutex -> write_mutex.\n    self.sub_mutex.lockUncancelable(self.io);\n\n    // Acquire mutex for subscription cleanup (prevents races with nextMsg())\n    self.read_mutex.lockUncancelable(self.io);\n\n    // Acquire write mutex for thread-safe UNSUB encoding\n    self.write_mutex.lockUncancelable(self.io);\n    const writer = self.active_writer;\n\n    // Unsubscribe all active subscriptions\n    for (self.sub_ptrs, 0..) |maybe_sub, slot_idx| {\n        if (maybe_sub) |sub| {\n            // Buffer UNSUB command (no I/O yet)\n            protocol.Encoder.encodeUnsub(writer, .{\n                .sid = sub.sid,\n                .max_msgs = null,\n            }) catch {\n                result.unsub_failures += 1;\n            };\n\n            // Close queue and clear from data structures\n            sub.queue.close(self.io);\n            _ = self.sidmap.remove(sub.sid);\n            self.sub_ptrs[slot_idx] = null;\n            if (self.cached_sub == sub) self.cached_sub = null;\n            self.free_slots[self.free_count] = @intCast(slot_idx);\n            self.free_count += 1;\n\n            // Drain remaining messages from queue (in-memory, no socket I/O)\n            var drain_buf: [1]Message = undefined;\n            while (true) {\n                const n = sub.queue.popBatch(&drain_buf);\n                if (n == 0) break;\n                drain_buf[0].deinit();\n            }\n\n            // Mark subscription state - sub.deinit() frees resources\n            sub.state = .unsubscribed;\n            sub.client_destroyed = true;\n        }\n    }\n\n    // Flush while still holding write mutex\n    writer.flush() catch {\n        result.flush_failed = true;\n    };\n    self.write_mutex.unlock(self.io);\n\n    self.read_mutex.unlock(self.io);\n    self.sub_mutex.unlock(self.io);\n    State.atomicStore(&self.state, .draining);\n    self.pushEvent(.{ .draining = {} });\n\n    State.atomicStore(&self.state, .closed);\n    // Shutdown read to unblock io_task. Guard: fd may\n    // already be closed by io_task during disconnect.\n    if (isValidFd(self.stream.socket.handle)) {\n        self.stream.shutdown(self.io, .recv) catch {};\n    }\n    // Wait for io_task to exit\n    if (self.io_task_future) |*future| {\n        _ = future.cancel(self.io);\n        self.io_task_future = null;\n    }\n    // Full close -- io_task exited\n    self.stream.close(self.io);\n\n    if (self.server_info) |*info| {\n        info.deinit(alloc);\n        self.server_info = null;\n    }\n\n    // Push err event if drain had failures\n    if (!result.isClean()) {\n        self.pushEvent(.{\n            .err = .{\n                .err = events_mod.Error.DrainIncomplete,\n                .msg = null,\n            },\n        });\n    }\n\n    return result;\n}\n\n/// Returns true if connected.\npub fn isConnected(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    // Use atomic load for cross-thread visibility (io_task may update state)\n    return @atomicLoad(State, &self.state, .acquire) == .connected;\n}\n\n/// Returns connection statistics snapshot.\npub fn stats(self: *const Client) StatsSnapshot {\n    assert(self.next_sid >= 1);\n    return self.statistics.snapshot();\n}\n\n/// Returns server info.\npub fn serverInfo(self: *const Client) ?*const ServerInfo {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |*info| {\n        return info;\n    }\n    return null;\n}\n\n/// Returns true if connection is using TLS.\npub fn isTls(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    return self.use_tls;\n}\n\n/// Returns true if TCP_NODELAY was successfully set.\npub fn isTcpNoDelaySet(self: *const Client) bool {\n    return self.tcp_nodelay_set;\n}\n\n/// Returns true if TCP receive buffer was successfully set.\npub fn isTcpRcvBufSet(self: *const Client) bool {\n    return self.tcp_rcvbuf_set;\n}\n\n// Connection Info Getters\n\n/// Returns the currently connected server URL.\n/// Returns the original URL used to connect, or null if not connected.\npub fn connectedUrl(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.original_url_len == 0) return null;\n    return self.original_url[0..self.original_url_len];\n}\n\n/// Returns the connected server's unique ID.\n/// This is the `server_id` from the INFO response.\npub fn connectedServerId(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        if (info.server_id.len > 0) return info.server_id;\n    }\n    return null;\n}\n\n/// Returns the connected server's name.\n/// This is the `server_name` from the INFO response.\npub fn connectedServerName(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        if (info.server_name.len > 0) return info.server_name;\n    }\n    return null;\n}\n\n/// Returns the connected server's version string.\n/// This is the `version` from the INFO response (e.g., \"2.10.0\").\npub fn connectedServerVersion(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        if (info.version.len > 0) return info.version;\n    }\n    return null;\n}\n\n/// Checks if the server version meets minimum requirements.\n///\n/// Arguments:\n///     min_major: Minimum major version required\n///     min_minor: Minimum minor version required\n///     min_patch: Minimum patch version required\n///\n/// Returns true if server version >= min_major.min_minor.min_patch.\n/// Returns false if not connected or version cannot be parsed.\n///\n/// Example: `client.checkCompatibility(2, 10, 0)` checks for NATS 2.10.0+.\npub fn checkCompatibility(\n    self: *const Client,\n    min_major: u16,\n    min_minor: u16,\n    min_patch: u16,\n) bool {\n    assert(self.next_sid >= 1);\n    const version = self.connectedServerVersion() orelse return false;\n\n    // Parse version string (e.g., \"2.10.0\" or \"2.10.0-beta\")\n    var parts = std.mem.splitScalar(u8, version, '.');\n    const major_str = parts.next() orelse return false;\n    const minor_str = parts.next() orelse return false;\n    const patch_str = parts.next() orelse \"0\";\n\n    // Parse major\n    const major = std.fmt.parseInt(u16, major_str, 10) catch return false;\n\n    // Parse minor\n    const minor = std.fmt.parseInt(u16, minor_str, 10) catch return false;\n\n    // Parse patch (strip suffix like \"-beta\" if present)\n    var patch_clean = patch_str;\n    if (std.mem.indexOfScalar(u8, patch_str, '-')) |idx| {\n        patch_clean = patch_str[0..idx];\n    }\n    const patch = std.fmt.parseInt(u16, patch_clean, 10) catch 0;\n\n    // Compare: major > min OR (major == min AND minor > min) OR ...\n    if (major > min_major) return true;\n    if (major < min_major) return false;\n    if (minor > min_minor) return true;\n    if (minor < min_minor) return false;\n    return patch >= min_patch;\n}\n\n/// Returns the maximum payload size allowed by the server.\n/// Defaults to 1MB if not yet connected.\npub fn maxPayload(self: *const Client) usize {\n    assert(self.next_sid >= 1);\n    return self.max_payload;\n}\n\n/// Returns true if the server supports message headers (NATS 2.2+).\npub fn headersSupported(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.headers;\n    }\n    return false;\n}\n\n/// Returns the number of known servers in the connection pool.\n/// This includes the original server and any discovered via cluster INFO.\npub fn serverCount(self: *const Client) u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_pool_initialized) {\n        return self.server_pool.serverCount();\n    }\n    return 0;\n}\n\n/// Returns a server URL from the pool at the given index.\n/// Use with serverCount() to iterate all known servers.\npub fn serverUrl(self: *const Client, index: u8) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (!self.server_pool_initialized) return null;\n    if (index >= self.server_pool.count) return null;\n    return self.server_pool.servers[index].getUrl();\n}\n\n/// Returns the count of discovered servers from cluster INFO.\n/// These are additional servers beyond the original connection URL.\npub fn discoveredServerCount(self: *const Client) u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.connect_urls_count;\n    }\n    return 0;\n}\n\n/// Returns a discovered server URL at the given index.\n/// Use with discoveredServerCount() to iterate discovered servers.\npub fn discoveredServerUrl(self: *const Client, index: u8) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.getConnectUrl(index);\n    }\n    return null;\n}\n\n/// Returns the connected server's cluster name.\n/// This is the `cluster` from the INFO response.\npub fn connectedClusterName(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.cluster;\n    }\n    return null;\n}\n\n/// Returns true if the server requires authentication.\n/// Derived from `auth_required` in the INFO response.\npub fn authRequired(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.auth_required;\n    }\n    return false;\n}\n\n/// Returns true if the server requires TLS.\n/// Derived from `tls_required` in the INFO response.\npub fn tlsRequired(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.tls_required;\n    }\n    return false;\n}\n\n/// Returns the client name from options.\n/// This is the name used in the CONNECT command.\npub fn name(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    return self.options.name;\n}\n\n/// Returns the client ID assigned by the server.\n/// This is the `client_id` from the INFO response.\npub fn clientId(self: *const Client) ?u64 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.client_id;\n    }\n    return null;\n}\n\n/// Returns the client IP as seen by the server.\n/// This is the `client_ip` from the INFO response.\npub fn clientIp(self: *const Client) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        return info.client_ip;\n    }\n    return null;\n}\n\n/// Returns the connected server address as \"host:port\" string.\n/// Writes to the provided buffer and returns the slice.\n/// Returns null if not connected or buffer too small.\npub fn connectedAddr(\n    self: *const Client,\n    buf: []u8,\n) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.server_info) |info| {\n        if (info.host.len == 0) return null;\n        assert(info.port > 0);\n\n        // Format \"host:port\" into buffer\n        const result = std.fmt.bufPrint(buf, \"{s}:{d}\", .{\n            info.host,\n            info.port,\n        }) catch return null;\n        return result;\n    }\n    return null;\n}\n\n/// Returns the connected URL with password redacted.\n/// Replaces password with \"***\" for safe logging.\n/// Writes to the provided buffer and returns the slice.\npub fn connectedUrlRedacted(\n    self: *const Client,\n    buf: []u8,\n) ?[]const u8 {\n    assert(self.next_sid >= 1);\n    if (self.original_url_len == 0) return null;\n\n    const url = self.original_url[0..self.original_url_len];\n\n    // Check if URL has credentials (look for @ in URL)\n    const at_pos = std.mem.indexOf(u8, url, \"@\") orelse {\n        // No credentials, return as-is\n        if (buf.len < url.len) return null;\n        @memcpy(buf[0..url.len], url);\n        return buf[0..url.len];\n    };\n\n    // Find protocol prefix (nats:// or tls://)\n    var prefix_len: usize = 0;\n    if (std.mem.startsWith(u8, url, \"nats://\")) {\n        prefix_len = 7;\n    } else if (std.mem.startsWith(u8, url, \"tls://\")) {\n        prefix_len = 6;\n    }\n\n    const auth_part = url[prefix_len..at_pos];\n    const colon_pos = std.mem.indexOf(u8, auth_part, \":\") orelse {\n        // No password (just token or user), return as-is\n        if (buf.len < url.len) return null;\n        @memcpy(buf[0..url.len], url);\n        return buf[0..url.len];\n    };\n\n    // Redact password: \"user:pass@host\" -> \"user:***@host\"\n    const user = auth_part[0..colon_pos];\n    const host_part = url[at_pos..];\n    const redacted_pass = \"***\";\n\n    const new_len =\n        prefix_len + user.len + 1 + redacted_pass.len + host_part.len;\n\n    if (buf.len < new_len) return null;\n\n    var pos: usize = 0;\n    @memcpy(buf[pos..][0..prefix_len], url[0..prefix_len]);\n    pos += prefix_len;\n    @memcpy(buf[pos..][0..user.len], user);\n    pos += user.len;\n    buf[pos] = ':';\n    pos += 1;\n    @memcpy(buf[pos..][0..redacted_pass.len], redacted_pass);\n    pos += redacted_pass.len;\n    @memcpy(buf[pos..][0..host_part.len], host_part);\n    pos += host_part.len;\n\n    assert(pos == new_len);\n    return buf[0..new_len];\n}\n\n/// Last error info returned by lastError().\npub const LastErrorInfo = struct {\n    err: anyerror,\n    msg: ?[]const u8,\n};\n\n/// Returns the last async error that occurred on the connection.\n/// This includes server -ERR messages and other async errors.\n/// The error message is from the server (e.g., permission violation).\n/// Returns null if no error has occurred since last clear.\npub fn lastError(self: *const Client) ?LastErrorInfo {\n    assert(self.next_sid >= 1);\n    if (self.last_error) |err| {\n        const msg: ?[]const u8 = if (self.last_error_msg_len > 0)\n            self.last_error_msg[0..self.last_error_msg_len]\n        else\n            null;\n        return .{ .err = err, .msg = msg };\n    }\n    return null;\n}\n\n/// Clears the last error.\n/// Call after handling the error to reset state.\npub fn clearLastError(self: *Client) void {\n    assert(self.next_sid >= 1);\n    self.last_error = null;\n    self.last_error_msg_len = 0;\n}\n\n// Connection State Methods\n\n/// Returns the current connection state.\n/// Thread-safe: uses atomic load for cross-thread visibility.\npub fn status(self: *const Client) State {\n    assert(self.next_sid >= 1);\n    return State.atomicLoad(&self.state);\n}\n\n/// Returns true if the connection is permanently closed.\n/// Once closed, the client cannot be reconnected.\npub fn isClosed(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    return State.atomicLoad(&self.state) == .closed;\n}\n\n/// Returns true if the connection is draining.\n/// During drain, no new subscriptions allowed but existing messages are delivered.\npub fn isDraining(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    return State.atomicLoad(&self.state) == .draining;\n}\n\n/// Returns true if the connection is attempting to reconnect.\npub fn isReconnecting(self: *const Client) bool {\n    assert(self.next_sid >= 1);\n    return State.atomicLoad(&self.state) == .reconnecting;\n}\n\n/// Returns the number of active subscriptions.\npub fn numSubscriptions(self: *const Client) usize {\n    assert(self.next_sid >= 1);\n    // free_count tracks available slots; total - free = active\n    return MAX_SUBSCRIPTIONS - self.free_count;\n}\n\n/// Measures round-trip time to the server by sending PING and waiting for PONG.\n/// Returns RTT in nanoseconds.\n/// This is a blocking operation that waits for the server to respond.\npub fn rtt(self: *Client) !u64 {\n    if (!State.atomicLoad(&self.state).canSend()) return error.NotConnected;\n    assert(self.next_sid >= 1);\n\n    // Record start time\n    const start_ns = getNowNs(self.io);\n    const old_pong_ns = self.last_pong_received_ns.load(.acquire);\n\n    // Send PING\n    try self.write_mutex.lock(self.io);\n    self.active_writer.writeAll(\"PING\\r\\n\") catch {\n        self.write_mutex.unlock(self.io);\n        return error.WriteFailed;\n    };\n    self.active_writer.flush() catch {\n        self.write_mutex.unlock(self.io);\n        return error.WriteFailed;\n    };\n    if (self.use_tls) {\n        self.writer.interface.flush() catch {\n            self.write_mutex.unlock(self.io);\n            return error.WriteFailed;\n        };\n    }\n    self.write_mutex.unlock(self.io);\n\n    // Wait for PONG - poll last_pong_received_ns\n    // io_task handles PONG and updates the timestamp\n    const timeout_ns: u64 = 5_000_000_000;\n    var spin_count: u32 = 0;\n\n    while (true) {\n        const current_pong_ns = self.last_pong_received_ns.load(.acquire);\n        if (current_pong_ns > old_pong_ns) {\n            const end_ns = getNowNs(self.io);\n            return end_ns - start_ns;\n        }\n\n        spin_count += 1;\n        if (spin_count < defaults.Spin.max_spins) {\n            std.atomic.spinLoopHint();\n        } else {\n            self.io.sleep(\n                .fromNanoseconds(0),\n                .awake,\n            ) catch {};\n            spin_count = 0;\n            const now_ns = getNowNs(self.io);\n            if (now_ns - start_ns >= timeout_ns) {\n                return error.Timeout;\n            }\n        }\n    }\n}\n\n/// Generates a new unique inbox subject using the configured prefix.\n/// Caller owns returned memory.\npub fn newInbox(self: *Client) ![]u8 {\n    const allocator = self.allocator;\n    assert(self.next_sid >= 1);\n    const prefix = self.options.inbox_prefix;\n    const random_len = 22;\n    const total_len = prefix.len + 1 + random_len; // prefix.random\n\n    const result = try allocator.alloc(u8, total_len);\n    @memcpy(result[0..prefix.len], prefix);\n    result[prefix.len] = '.';\n\n    // Fill random portion with base62 characters\n    const alphabet = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\" ++\n        \"abcdefghijklmnopqrstuvwxyz\";\n    self.io.random(result[prefix.len + 1 ..]);\n    for (result[prefix.len + 1 ..]) |*b| {\n        b.* = alphabet[@mod(b.*, alphabet.len)];\n    }\n\n    return result;\n}\n\n/// Reset rate-limit counters, allowing errors to re-trigger events.\n/// Call this if you want immediate re-notification of ongoing errors.\n/// Resets both subscription alloc_failed and client protocol_error thresholds.\npub fn resetErrorNotifications(self: *Client) void {\n    for (self.sub_ptrs) |maybe_sub| {\n        if (maybe_sub) |sub| {\n            sub.last_alloc_notified_at = 0;\n        }\n    }\n    self.last_parse_error_notified_at = 0;\n}\n\n/// Get subscription by SID.\n/// Uses cached pointer for fast path when single subscription matches.\n// Caller must hold read_mutex while using the returned pointer if\n// another task could unsubscribe/deinit the subscription concurrently.\npub inline fn getSubscriptionBySid(self: *Client, sid: u64) ?*Sub {\n    assert(sid > 0);\n    // Fast path: cached subscription (common in benchmarks)\n    if (self.cached_sub) |sub| {\n        if (sub.sid == sid) return sub;\n    }\n    // Normal hash lookup\n    if (self.sidmap.get(sid)) |slot_idx| {\n        return self.sub_ptrs[slot_idx];\n    }\n    return null;\n}\n\n/// Sends PONG response.\n/// Sends PING for health check.\nfn sendPing(self: *Client) !void {\n    assert(self.state == .connected);\n    self.write_mutex.lock(self.io) catch {\n        return error.WriteFailed;\n    };\n    defer self.write_mutex.unlock(self.io);\n    const writer = self.active_writer;\n    writer.writeAll(\"PING\\r\\n\") catch {\n        return error.WriteFailed;\n    };\n    writer.flush() catch {\n        return error.WriteFailed;\n    };\n    if (self.use_tls) {\n        self.writer.interface.flush() catch {\n            return error.WriteFailed;\n        };\n    }\n    const now = getNowNs(self.io);\n    self.last_ping_sent_ns.store(now, .monotonic);\n    const new_outstanding =\n        self.pings_outstanding.fetchAdd(1, .monotonic) + 1;\n    dbg.pingPong(\"PING_SENT\", new_outstanding);\n}\n\n/// Handles PONG response from server.\nfn handlePong(self: *Client) void {\n    const now = getNowNs(self.io);\n    self.last_pong_received_ns.store(now, .release);\n    self.pings_outstanding.store(0, .monotonic);\n    dbg.pingPong(\"PONG_RECEIVED\", 0);\n}\n\n/// Checks connection health, sends PING if needed.\n/// Returns true if connection is stale (should trigger disconnect).\n/// Called from io_task loop with throttling.\npub fn checkHealthAndDetectStale(self: *Client) bool {\n    if (self.options.ping_interval_ms == 0) return false;\n    if (State.atomicLoad(&self.state) != .connected) return false;\n\n    const now_ns = getNowNs(self.io);\n    const interval_ns: u64 =\n        @as(u64, self.options.ping_interval_ms) * 1_000_000;\n\n    // Check if too many PINGs outstanding (connection stale)\n    const outstanding = self.pings_outstanding.load(.monotonic);\n    if (outstanding >= self.options.max_pings_outstanding) {\n        dbg.print(\n            \"[HEALTH] STALE: pings_outstanding={d} >= max={d}\",\n            .{ outstanding, self.options.max_pings_outstanding },\n        );\n        return true;\n    }\n\n    // Check if it's time to send PING\n    const last_ping = self.last_ping_sent_ns.load(.monotonic);\n    if (now_ns - last_ping >= interval_ns) {\n        dbg.print(\n            \"[HEALTH] Sending PING, pings_outstanding={d}\",\n            .{outstanding},\n        );\n        self.sendPing() catch |err| {\n            dbg.print(\"[HEALTH] PING failed: {s}\", .{@errorName(err)});\n            // Write failure indicates disconnection\n            if (err == error.BrokenPipe or err == error.ConnectionResetByPeer) {\n                return true;\n            }\n        };\n    }\n\n    return false;\n}\n\n/// Closes all subscription queues (wakes waiters with error).\npub fn closeAllQueues(self: *Client) void {\n    for (self.sub_ptrs) |maybe_sub| {\n        if (maybe_sub) |sub| {\n            sub.queue.close(self.io);\n        }\n    }\n}\n\n/// Closes the connection and frees all resources.\n///\n/// Closes connection, stops io_task, frees buffers.\n/// Uses shutdown-recv-then-close pattern for reliable shutdown.\n/// Safe to call multiple times.\npub fn deinit(self: *Client) void {\n    const alloc = self.allocator;\n    assert(self.next_sid >= 1);\n\n    // SHUTDOWN-RECV-THEN-CLOSE PATTERN\n    // shutdown(.recv) unblocks fillMore() (returns EndOfStream)\n    // while keeping write fd valid for in-flight writes.\n\n    // 1. Atomic state transition\n    const was_open = State.atomicLoad(&self.state) != .closed;\n    State.atomicStore(&self.state, .closed);\n\n    // 2. Shutdown read side only -- unblocks fillMore()\n    //    Write fd stays valid for in-flight writes.\n    //    Guard: fd may already be closed by io_task during\n    //    disconnect or failed reconnect. BADF panics in\n    //    debug mode (Io.Threaded treats it as programmer\n    //    bug, not catchable). Validate fd before syscall.\n    if (was_open and isValidFd(self.stream.socket.handle)) {\n        self.stream.shutdown(self.io, .recv) catch {};\n    }\n\n    // 3. Wait for io_task to exit (no concurrent writers after)\n    if (self.io_task_future) |*future| {\n        _ = future.cancel(self.io);\n        self.io_task_future = null;\n    }\n\n    // 4. Full close -- io_task exited, no concurrent writers\n    if (was_open and isValidFd(self.stream.socket.handle)) {\n        self.stream.close(self.io);\n    }\n\n    // 5. Cancel callback task and free event queue\n    // SAFETY: Set event_queue = null BEFORE canceling to signal callback_task\n    // to exit. This prevents use-after-free if callback_task is mid-loop.\n    self.event_queue_mutex.lockUncancelable(self.io);\n    const eq = self.event_queue;\n    self.event_queue = null; // Signal callback_task to exit\n    if (eq) |queue| {\n        // Push closed event so callback_task can dispatch final onClose\n        _ = queue.push(.{ .closed = {} });\n    }\n    self.event_queue_mutex.unlock(self.io);\n    if (self.callback_task_future) |*future| {\n        _ = future.cancel(self.io);\n        self.callback_task_future = null;\n    }\n    // Now safe to free - callback_task has exited (state=closed + null check)\n    if (eq) |queue| {\n        alloc.destroy(queue);\n    }\n    if (self.event_queue_buf) |buf| {\n        alloc.free(buf);\n        self.event_queue_buf = null;\n    }\n\n    // 5b. Tear down the response multiplexer.\n    // Owns a wildcard subscription that we destroy here so the\n    // step-6 sub_ptrs loop sees a cleared slot. Signals all\n    // in-flight waiters so blocked request() calls return null.\n    self.closeRespMux();\n\n    // 6. Cleanup subscriptions (io_task is now gone)\n    self.closeAllQueues();\n    for (self.sub_ptrs) |maybe_sub| {\n        if (maybe_sub) |sub| {\n            sub.state = .unsubscribed;\n            sub.client_destroyed = true;\n        }\n    }\n\n    // Free client resources\n    if (self.server_info) |*info| {\n        info.deinit(alloc);\n    }\n\n    // Drain return queue before destroying slab (free any pending buffers)\n    while (self.return_queue.pop()) |buf| {\n        self.tiered_slab.free(buf);\n    }\n    alloc.free(self.return_queue_buf);\n    self.tiered_slab.deinit();\n\n    // Free pending buffer\n    self.deinitPendingBuffer();\n\n    // Free TLS resources\n    self.tls_client = null;\n    if (self.tls_read_buffer) |buf| {\n        alloc.free(buf);\n        self.tls_read_buffer = null;\n    }\n    if (self.tls_write_buffer) |buf| {\n        alloc.free(buf);\n        self.tls_write_buffer = null;\n    }\n    if (self.ca_bundle) |*bundle| {\n        bundle.deinit(alloc);\n        self.ca_bundle = null;\n    }\n\n    alloc.free(self.read_buffer);\n    alloc.free(self.write_buffer);\n    if (self.publish_ring_buf) |b| alloc.free(b);\n    alloc.destroy(self);\n}\n\n// -- Reconnection Support\n\n/// Backup all active subscriptions for restoration after reconnect.\n/// Stores SID, subject, and queue_group in inline buffers (no allocation).\n/// Returns error if any subject > 256 bytes or queue_group > 64 bytes.\npub fn backupSubscriptions(self: *Client) error{ SubjectTooLong, QueueGroupTooLong }!void {\n    self.sub_mutex.lockUncancelable(self.io);\n    defer self.sub_mutex.unlock(self.io);\n\n    self.sub_backup_count = 0;\n\n    for (self.sub_ptrs) |maybe_sub| {\n        if (maybe_sub) |sub| {\n            if (sub.state != .active) continue;\n            if (self.sub_backup_count >= MAX_SUBSCRIPTIONS) break;\n\n            // Validate lengths - reject truncation\n            if (sub.subject.len >= defaults.Limits.max_subject_len) {\n                self.pushEvent(.{\n                    .err = .{\n                        .err = events_mod.Error.SubjectTooLong,\n                        .msg = null,\n                    },\n                });\n                return error.SubjectTooLong;\n            }\n            if (sub.queue_group) |qg| {\n                if (qg.len > defaults.Limits.max_queue_group_len) {\n                    self.pushEvent(.{\n                        .err = .{\n                            .err = events_mod.Error.QueueGroupTooLong,\n                            .msg = null,\n                        },\n                    });\n                    return error.QueueGroupTooLong;\n                }\n            }\n\n            var backup = &self.sub_backups[self.sub_backup_count];\n            backup.sid = sub.sid;\n\n            // Copy subject (validated above)\n            const subj_len: u8 = @intCast(sub.subject.len);\n            @memcpy(backup.subject_buf[0..subj_len], sub.subject);\n            backup.subject_len = subj_len;\n\n            // Copy queue_group if present (validated above)\n            if (sub.queue_group) |qg| {\n                const qg_len: u8 = @intCast(qg.len);\n                @memcpy(backup.queue_group_buf[0..qg_len], qg);\n                backup.queue_group_len = qg_len;\n            } else {\n                backup.queue_group_len = 0;\n            }\n\n            self.sub_backup_count += 1;\n        }\n    }\n}\n\n/// Restore subscriptions after reconnect (preserves original SIDs).\n/// Re-sends SUB commands to server with the same SIDs so existing\n/// subscription pointers continue to work.\npub fn restoreSubscriptions(self: *Client) !void {\n    if (self.sub_backup_count == 0) return;\n\n    // Acquire write_mutex — restoreSubscriptions writes to socket.\n    try self.write_mutex.lock(self.io);\n    defer self.write_mutex.unlock(self.io);\n\n    const writer = self.active_writer;\n\n    for (self.sub_backups[0..self.sub_backup_count]) |*backup| {\n        if (backup.sid == 0) continue;\n\n        const subject = backup.getSubject();\n        const queue_group = backup.queueGroup();\n\n        // Send SUB with SAME SID\n        protocol.Encoder.encodeSub(writer, .{\n            .subject = subject,\n            .queue_group = queue_group,\n            .sid = backup.sid,\n        }) catch return error.RestoreSubscriptionsFailed;\n    }\n\n    writer.flush() catch return error.RestoreSubscriptionsFailed;\n}\n\n/// Initialize pending buffer for publishes during reconnect.\nfn initPendingBuffer(self: *Client) !void {\n    const allocator = self.allocator;\n    if (self.options.pending_buffer_size == 0) return;\n    if (self.pending_buffer != null) return;\n\n    self.pending_buffer = try allocator.alloc(\n        u8,\n        self.options.pending_buffer_size,\n    );\n    self.pending_buffer_capacity = self.options.pending_buffer_size;\n    self.pending_buffer_pos = 0;\n}\n\n/// Free pending buffer.\nfn deinitPendingBuffer(self: *Client) void {\n    const allocator = self.allocator;\n    if (self.pending_buffer) |buf| {\n        allocator.free(buf);\n        self.pending_buffer = null;\n        self.pending_buffer_pos = 0;\n        self.pending_buffer_capacity = 0;\n    }\n}\n\n/// Fast integer-to-string (avoids std.fmt overhead).\nfn writeUsizeToSlice(buf: *[20]u8, value: usize) []const u8 {\n    if (value == 0) {\n        buf[19] = '0';\n        return buf[19..20];\n    }\n    var v = value;\n    var i: usize = 20;\n    while (v > 0) : (v /= 10) {\n        i -= 1;\n        buf[i] = @intCast((v % 10) + '0');\n    }\n    return buf[i..20];\n}\n\n/// Max encoded size for PUB subject [reply] len\\r\\npayload\\r\\n\nfn pubEncodedSize(\n    subject: []const u8,\n    reply_to: ?[]const u8,\n    payload: []const u8,\n) usize {\n    // \"PUB \" + subject + \" \" + [reply + \" \"] + len(max 20) + \"\\r\\n\" + payload + \"\\r\\n\"\n    var size: usize = 4 + subject.len + 1 + 20 + 2 + payload.len + 2;\n    if (reply_to) |r| size += r.len + 1;\n    return size;\n}\n\n/// Encode PUB into publish ring (lock-free).\n/// Spins briefly if ring is full, then returns error.\nfn encodePubToRing(\n    self: *Client,\n    subject: []const u8,\n    reply_to: ?[]const u8,\n    payload: []const u8,\n) !void {\n    const max_size = pubEncodedSize(\n        subject,\n        reply_to,\n        payload,\n    );\n    const entry = self.reserveRingEntry(max_size) orelse\n        return error.PublishBufferFull;\n\n    const buf = entry[RING_HDR_SIZE..];\n    var pos: usize = 0;\n\n    // \"PUB \"\n    @memcpy(buf[pos..][0..4], \"PUB \");\n    pos += 4;\n\n    // subject\n    @memcpy(buf[pos..][0..subject.len], subject);\n    pos += subject.len;\n\n    // [reply_to]\n    if (reply_to) |r| {\n        buf[pos] = ' ';\n        pos += 1;\n        @memcpy(buf[pos..][0..r.len], r);\n        pos += r.len;\n    }\n\n    // \" \" + payload length\n    buf[pos] = ' ';\n    pos += 1;\n    var num_buf: [20]u8 = undefined;\n    const len_str = writeUsizeToSlice(\n        &num_buf,\n        payload.len,\n    );\n    @memcpy(buf[pos..][0..len_str.len], len_str);\n    pos += len_str.len;\n\n    // \"\\r\\n\"\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    // payload\n    @memcpy(buf[pos..][0..payload.len], payload);\n    pos += payload.len;\n\n    // \"\\r\\n\"\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    self.publish_ring.commit(entry, pos);\n}\n\n/// Encode HPUB with structured header entries into ring.\nfn encodeHPubToRing(\n    self: *Client,\n    subject: []const u8,\n    reply_to: ?[]const u8,\n    hdrs: []const headers.Entry,\n    payload: []const u8,\n) !void {\n    try headers.validateEntries(hdrs);\n    const hdr_size = headers.encodedSize(hdrs);\n    const total_len = hdr_size + payload.len;\n\n    // \"HPUB \" + subject + \" \" + [reply + \" \"] + hdr_len(20) + \" \" + total_len(20) + \"\\r\\n\" + headers + payload + \"\\r\\n\"\n    var max_size: usize = 5 + subject.len + 1 + 20 + 1 + 20 + 2 + hdr_size + payload.len + 2;\n    if (reply_to) |r| max_size += r.len + 1;\n\n    const entry = self.reserveRingEntry(max_size) orelse\n        return error.PublishBufferFull;\n\n    const buf = entry[RING_HDR_SIZE..];\n    var pos: usize = 0;\n\n    @memcpy(buf[pos..][0..5], \"HPUB \");\n    pos += 5;\n\n    @memcpy(buf[pos..][0..subject.len], subject);\n    pos += subject.len;\n\n    if (reply_to) |r| {\n        buf[pos] = ' ';\n        pos += 1;\n        @memcpy(buf[pos..][0..r.len], r);\n        pos += r.len;\n    }\n\n    var num_buf: [20]u8 = undefined;\n    buf[pos] = ' ';\n    pos += 1;\n    const hdr_str = writeUsizeToSlice(&num_buf, hdr_size);\n    @memcpy(buf[pos..][0..hdr_str.len], hdr_str);\n    pos += hdr_str.len;\n\n    buf[pos] = ' ';\n    pos += 1;\n    const tot_str = writeUsizeToSlice(&num_buf, total_len);\n    @memcpy(buf[pos..][0..tot_str.len], tot_str);\n    pos += tot_str.len;\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    // Encode headers into buffer\n    const hdr_buf = buf[pos .. pos + hdr_size];\n    headers.encodeToBuf(hdr_buf, hdrs);\n    pos += hdr_size;\n\n    @memcpy(buf[pos..][0..payload.len], payload);\n    pos += payload.len;\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    self.publish_ring.commit(entry, pos);\n}\n\n/// Encode HPUB with raw pre-encoded header bytes into ring.\nfn encodeHPubRawToRing(\n    self: *Client,\n    subject: []const u8,\n    reply_to: ?[]const u8,\n    hdr_bytes: []const u8,\n    payload: []const u8,\n) !void {\n    const total_len = hdr_bytes.len + payload.len;\n\n    var max_size: usize = 5 + subject.len + 1 + 20 + 1 + 20 + 2 + hdr_bytes.len + payload.len + 2;\n    if (reply_to) |r| max_size += r.len + 1;\n\n    const entry = self.reserveRingEntry(max_size) orelse\n        return error.PublishBufferFull;\n\n    const buf = entry[RING_HDR_SIZE..];\n    var pos: usize = 0;\n\n    @memcpy(buf[pos..][0..5], \"HPUB \");\n    pos += 5;\n\n    @memcpy(buf[pos..][0..subject.len], subject);\n    pos += subject.len;\n\n    if (reply_to) |r| {\n        buf[pos] = ' ';\n        pos += 1;\n        @memcpy(buf[pos..][0..r.len], r);\n        pos += r.len;\n    }\n\n    var num_buf: [20]u8 = undefined;\n    buf[pos] = ' ';\n    pos += 1;\n    const hdr_str = writeUsizeToSlice(\n        &num_buf,\n        hdr_bytes.len,\n    );\n    @memcpy(buf[pos..][0..hdr_str.len], hdr_str);\n    pos += hdr_str.len;\n\n    buf[pos] = ' ';\n    pos += 1;\n    const tot_str = writeUsizeToSlice(\n        &num_buf,\n        total_len,\n    );\n    @memcpy(buf[pos..][0..tot_str.len], tot_str);\n    pos += tot_str.len;\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    @memcpy(buf[pos..][0..hdr_bytes.len], hdr_bytes);\n    pos += hdr_bytes.len;\n\n    @memcpy(buf[pos..][0..payload.len], payload);\n    pos += payload.len;\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    self.publish_ring.commit(entry, pos);\n}\n\n/// Encode SUB into publish ring.\nfn encodeSubToRing(\n    self: *Client,\n    args: protocol.SubArgs,\n) !void {\n    try pubsub.validateSubscribe(args.subject);\n    if (args.sid == 0) return error.InvalidSid;\n    if (args.queue_group) |queue| {\n        if (queue.len > 0) {\n            try pubsub.validateQueueGroup(queue);\n        }\n    }\n\n    var max_size: usize = 4 + args.subject.len + 1 + 20 + 2;\n    if (args.queue_group) |queue| {\n        if (queue.len > 0) max_size += queue.len + 1;\n    }\n\n    const entry = self.reserveRingEntry(max_size) orelse\n        return error.PublishBufferFull;\n    const buf = entry[RING_HDR_SIZE..];\n    var pos: usize = 0;\n\n    @memcpy(buf[pos..][0..4], \"SUB \");\n    pos += 4;\n\n    @memcpy(buf[pos..][0..args.subject.len], args.subject);\n    pos += args.subject.len;\n\n    if (args.queue_group) |queue| {\n        if (queue.len > 0) {\n            buf[pos] = ' ';\n            pos += 1;\n            @memcpy(buf[pos..][0..queue.len], queue);\n            pos += queue.len;\n        }\n    }\n\n    buf[pos] = ' ';\n    pos += 1;\n    var num_buf: [20]u8 = undefined;\n    const sid_str = writeUsizeToSlice(&num_buf, args.sid);\n    @memcpy(buf[pos..][0..sid_str.len], sid_str);\n    pos += sid_str.len;\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    self.publish_ring.commit(entry, pos);\n}\n\n/// Encode UNSUB into publish ring.\nfn encodeUnsubToRing(\n    self: *Client,\n    args: protocol.commands.UnsubArgs,\n) !void {\n    if (args.sid == 0) return error.InvalidSid;\n\n    var max_size: usize = 6 + 20 + 2;\n    if (args.max_msgs != null) max_size += 1 + 20;\n\n    const entry = self.reserveRingEntry(max_size) orelse\n        return error.PublishBufferFull;\n    const buf = entry[RING_HDR_SIZE..];\n    var pos: usize = 0;\n\n    @memcpy(buf[pos..][0..6], \"UNSUB \");\n    pos += 6;\n\n    var num_buf: [20]u8 = undefined;\n    const sid_str = writeUsizeToSlice(&num_buf, args.sid);\n    @memcpy(buf[pos..][0..sid_str.len], sid_str);\n    pos += sid_str.len;\n\n    if (args.max_msgs) |max| {\n        buf[pos] = ' ';\n        pos += 1;\n        const max_str = writeUsizeToSlice(&num_buf, max);\n        @memcpy(buf[pos..][0..max_str.len], max_str);\n        pos += max_str.len;\n    }\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    self.publish_ring.commit(entry, pos);\n}\n\n/// Reserve a ring entry with brief spin on full.\n/// Returns the entry slice or null if still full after ~1ms.\nfn reserveRingEntry(\n    self: *Client,\n    max_size: usize,\n) ?[]u8 {\n    // Try immediately\n    if (self.publish_ring.reserve(max_size)) |e| return e;\n\n    // Backpressure: spin → yield → sleep → fail\n    for (0..256) |_| {\n        std.atomic.spinLoopHint();\n        if (self.publish_ring.reserve(max_size)) |e| return e;\n    }\n    for (0..64) |_| {\n        std.Thread.yield() catch {};\n        if (self.publish_ring.reserve(max_size)) |e| return e;\n    }\n    // Sleep in 10us increments to let io_task drain.\n    // 1000 iterations = ~10ms budget, well above the\n    // io_task's ~1ms drain cycle.\n    for (0..1000) |_| {\n        var ts: std.posix.timespec = .{\n            .sec = 0,\n            .nsec = 10_000,\n        };\n        _ = std.posix.system.nanosleep(&ts, &ts);\n        if (self.publish_ring.reserve(max_size)) |e|\n            return e;\n    }\n    return null;\n}\n\n/// Buffer a publish during reconnect.\n/// Returns error if buffer is full or not initialized.\nfn bufferPendingPublish(\n    self: *Client,\n    subject: []const u8,\n    payload: []const u8,\n) !void {\n    const buf = self.pending_buffer orelse return error.NotConnected;\n    const remaining = self.pending_buffer_capacity - self.pending_buffer_pos;\n\n    // Estimate encoded size: \"PUB subject len\\r\\npayload\\r\\n\"\n    // PUB + space + subject + space + len(max 10 digits) + \\r\\n + payload + \\r\\n\n    const encoded_size = 4 + subject.len + 1 + 10 + 2 + payload.len + 2;\n    if (encoded_size > remaining) {\n        return error.PendingBufferFull;\n    }\n\n    var writer = Io.Writer.fixed(buf[self.pending_buffer_pos..]);\n\n    // Write PUB command manually (simpler than using Encoder)\n    try writer.writeAll(\"PUB \");\n    try writer.writeAll(subject);\n    try writer.writeByte(' ');\n\n    // Write payload length\n    var len_buf: [10]u8 = undefined;\n    const len_str = std.fmt.bufPrint(&len_buf, \"{d}\", .{payload.len}) catch {\n        return error.EncodingFailed;\n    };\n    try writer.writeAll(len_str);\n    try writer.writeAll(\"\\r\\n\");\n    try writer.writeAll(payload);\n    try writer.writeAll(\"\\r\\n\");\n\n    self.pending_buffer_pos += writer.end;\n}\n\n/// Flush pending buffer after reconnect.\nfn flushPendingBuffer(self: *Client) !void {\n    if (self.pending_buffer_pos == 0) return;\n\n    const buf = self.pending_buffer orelse return;\n    self.active_writer.writeAll(buf[0..self.pending_buffer_pos]) catch {\n        return error.WriteFailed;\n    };\n    self.active_writer.flush() catch return error.WriteFailed;\n    self.pending_buffer_pos = 0;\n    dbg.pendingBuffer(\"FLUSHED\", 0, self.pending_buffer_capacity);\n}\n\n/// Cleanup client state for reconnection.\n/// Closes old stream but preserves subscriptions and pending buffer.\npub fn cleanupForReconnect(self: *Client) void {\n    dbg.stateChange(\"cleanup\", \"for_reconnect\");\n\n    // Wait for in-flight main-thread writes.\n    // State is already .disconnected -- no new\n    // writes will start (canSend() returns false).\n    self.write_mutex.lock(self.io) catch {\n        self.stream.close(self.io);\n        self.tls_client = null;\n        self.active_reader = &self.reader.interface;\n        self.active_writer = &self.writer.interface;\n        return;\n    };\n    self.stream.close(self.io);\n    self.tls_client = null;\n    // Reset to raw TCP to avoid dangling TLS pointers\n    self.active_reader = &self.reader.interface;\n    self.active_writer = &self.writer.interface;\n    self.write_mutex.unlock(self.io);\n}\n\n/// Attempt connection to a single server.\n/// Returns true on success, error on failure.\npub fn tryConnect(\n    self: *Client,\n    server: *connection.server_pool.Server,\n) !void {\n    const raw_host = server.getHost();\n    const port = server.port;\n\n    dbg.reconnectEvent(\n        \"CONNECTING\",\n        self.reconnect_attempt + 1,\n        server.getUrl(),\n    );\n\n    // Connect\n    self.stream = try connectToHost(self.io, raw_host, port);\n    errdefer self.stream.close(self.io);\n\n    // Set TCP_NODELAY\n    const enable: u32 = 1;\n    std.posix.setsockopt(\n        self.stream.socket.handle,\n        std.posix.IPPROTO.TCP,\n        std.posix.TCP.NODELAY,\n        std.mem.asBytes(&enable),\n    ) catch {};\n\n    // Reinitialize reader/writer with existing buffers\n    self.reader = self.stream.reader(self.io, self.read_buffer);\n    self.writer = self.stream.writer(self.io, self.write_buffer);\n    // Reset to TCP reader/writer (upgradeTls will update if needed)\n    self.active_reader = &self.reader.interface;\n    self.active_writer = &self.writer.interface;\n\n    // Parse URL to get auth info and TLS flag\n    const parsed = parseUrl(server.getUrl()) catch ParsedUrl{\n        .host = raw_host,\n        .port = port,\n        .user = null,\n        .pass = null,\n        .use_tls = self.use_tls,\n    };\n\n    self.use_tls = self.use_tls or parsed.use_tls or\n        self.options.tls_required or self.options.tls_ca_file != null or\n        self.options.tls_handshake_first;\n\n    // Update TLS host for reconnection (server might have different hostname)\n    if (self.use_tls) {\n        const actual_host = parsed.host;\n        if (actual_host.len == 0 or actual_host.len > 255)\n            return error.HostTooLong;\n        const host_len: u8 = @intCast(actual_host.len);\n        @memcpy(self.tls_host[0..host_len], actual_host);\n        self.tls_host_len = host_len;\n    }\n\n    // TLS-first mode: upgrade to TLS before NATS protocol\n    if (self.use_tls and self.options.tls_handshake_first) {\n        try self.upgradeTls(self.options);\n    }\n\n    // Perform handshake (includes TLS upgrade after INFO if needed)\n    try self.handshake(self.options, parsed);\n\n    // Initialize health check timestamps (atomics)\n    const now_ns = getNowNs(self.io);\n    self.last_ping_sent_ns.store(now_ns, .monotonic);\n    self.last_pong_received_ns.store(now_ns, .monotonic);\n    self.pings_outstanding.store(0, .monotonic);\n\n    dbg.reconnectEvent(\n        \"CONNECTED\",\n        self.reconnect_attempt + 1,\n        server.getUrl(),\n    );\n}\n\n/// Wait with exponential backoff + jitter.\npub fn waitBackoff(self: *Client) void {\n    const opts = self.options;\n    const attempt = @min(self.reconnect_attempt, 10);\n\n    const base: u64 = opts.reconnect_wait_ms;\n    const exp_wait = base << @as(u6, @intCast(attempt));\n    const capped = @min(exp_wait, opts.reconnect_wait_max_ms);\n\n    // Add jitter using Io.random()\n    var rand_buf: [4]u8 = undefined;\n    self.io.random(&rand_buf);\n    const rand = std.mem.readInt(u32, &rand_buf, .little);\n    const jitter_range = capped * opts.reconnect_jitter_percent / 100;\n\n    // Calculate final wait with jitter\n    var final_wait = capped;\n    if (jitter_range > 0) {\n        const jitter_val = rand % (jitter_range * 2 + 1);\n        if (jitter_val > jitter_range) {\n            final_wait = capped + (jitter_val - jitter_range);\n        } else {\n            final_wait = capped -| jitter_range + jitter_val;\n        }\n    }\n    final_wait = @max(100, final_wait);\n\n    dbg.print(\n        \"Backoff wait: {d}ms (attempt {d})\",\n        .{ final_wait, attempt + 1 },\n    );\n    self.io.sleep(.fromMilliseconds(final_wait), .awake) catch {};\n}\n\n/// Attempt reconnection with exponential backoff.\n/// Can be called automatically (from io_task) or manually by user.\npub fn reconnect(self: *Client) !void {\n    if (self.state != .disconnected and self.state != .reconnecting) {\n        if (self.state == .connected) return;\n        return error.InvalidState;\n    }\n\n    if (!self.options.reconnect) {\n        return error.ReconnectDisabled;\n    }\n\n    // Initialize server pool if not already done\n    if (!self.server_pool_initialized) {\n        const url = self.original_url[0..self.original_url_len];\n        self.server_pool = connection.ServerPool.init(url) catch {\n            return error.InvalidUrl;\n        };\n        self.server_pool_initialized = true;\n    }\n\n    // Backup subscriptions before cleanup (error = subs won't restore)\n    self.backupSubscriptions() catch |err| {\n        dbg.print(\"backupSubscriptions failed: {s}\", .{@errorName(err)});\n        // Error event already pushed by backupSubscriptions\n    };\n    self.cleanupForReconnect();\n    State.atomicStore(&self.state, .reconnecting);\n\n    const max = self.options.max_reconnect_attempts;\n    const infinite = max == 0;\n\n    dbg.print(\n        \"Starting reconnect (max_attempts={d}, infinite={any})\",\n        .{ max, infinite },\n    );\n\n    while (infinite or self.reconnect_attempt < max) {\n        // Get next server\n        const now_ns = getNowNs(self.io);\n        const server = self.server_pool.nextServer(now_ns) orelse {\n            dbg.print(\"All servers on cooldown, waiting...\", .{});\n            self.waitBackoff();\n            continue;\n        };\n\n        dbg.reconnectEvent(\n            \"ATTEMPT\",\n            self.reconnect_attempt + 1,\n            server.getUrl(),\n        );\n\n        // Attempt connection\n        if (self.tryConnect(server)) {\n            // Connection succeeded\n            self.restoreSubscriptions() catch |err| {\n                dbg.print(\n                    \"Failed to restore subscriptions: {s}\",\n                    .{@errorName(err)},\n                );\n                // Notify user - subscriptions may be broken, they can re-sub\n                self.pushEvent(.{\n                    .err = .{\n                        .err = events_mod.Error.SubscriptionRestoreFailed,\n                        .msg = null,\n                    },\n                });\n            };\n            self.flushPendingBuffer() catch |err| {\n                dbg.print(\n                    \"Failed to flush pending buffer: {s}\",\n                    .{@errorName(err)},\n                );\n            };\n\n            State.atomicStore(&self.state, .connected);\n            self.reconnect_attempt = 0;\n            const reconnects =\n                self.statistics.reconnects.fetchAdd(1, .monotonic) + 1;\n            self.server_pool.resetFailures();\n\n            dbg.stateChange(\"reconnecting\", \"connected\");\n            dbg.print(\n                \"Reconnect successful (total reconnects: {d})\",\n                .{reconnects},\n            );\n            return;\n        } else |err| {\n            dbg.reconnectEvent(\n                \"FAILED\",\n                self.reconnect_attempt + 1,\n                server.getUrl(),\n            );\n            dbg.print(\"Connection attempt failed: {s}\", .{@errorName(err)});\n\n            self.server_pool.markCurrentFailed();\n            self.reconnect_attempt += 1;\n            self.waitBackoff();\n        }\n    }\n\n    // All attempts exhausted\n    self.state = .closed;\n    dbg.stateChange(\"reconnecting\", \"closed\");\n    dbg.print(\"Reconnect failed: max attempts ({d}) exhausted\", .{max});\n    return error.ReconnectFailed;\n}\n\n/// Subscription state.\npub const SubscriptionState = enum {\n    active,\n    draining,\n    unsubscribed,\n};\n\n/// Whether a subscription is manually polled or callback-driven.\npub const SubscriptionMode = enum {\n    manual,\n    callback,\n};\n\n/// Subscription with Io.Queue for async message delivery.\n///\n/// Supports multiple concurrent consumers via inline routing:\n/// - First subscriber to call nextMsg() reads from socket\n/// - Messages for other subscriptions are routed to their queues\n/// - Io.Mutex ensures only one reader at a time\n///\n/// Use nextMsg() for blocking receive, nextMsgTimeout() for bounded waits,\n/// or tryNextMsg() for non-blocking poll.\npub const Subscription = struct {\n    client: *Client,\n    sid: u64,\n    subject: []const u8,\n    queue_group: ?[]const u8,\n    queue_buf: []Message,\n    queue: SpscQueue(Message),\n    state: SubscriptionState,\n    received_msgs: u64,\n    dropped_msgs: u64 = 0,\n    alloc_failed_msgs: u64 = 0,\n    client_destroyed: bool = false,\n    /// msgs_in value when alloc_failed event was last pushed (rate-limit).\n    last_alloc_notified_at: u64 = 0,\n\n    // Callback subscription support\n    /// Whether this sub is manually polled or callback-driven.\n    mode: SubscriptionMode = .manual,\n    /// Future for the callback drain task (set for callback subs).\n    callback_future: ?Io.Future(void) = null,\n\n    // Auto-unsubscribe support\n    /// Maximum messages before auto-unsubscribe. Null = no limit.\n    /// When set, subscription auto-unsubscribes after this many messages.\n    max_msgs: ?u64 = null,\n    /// Count of delivered messages for auto-unsubscribe tracking.\n    /// Only tracked when max_msgs is set (opt-in for performance).\n    delivered_count: u64 = 0,\n    /// Flag set when auto-unsubscribe triggered (for io_task to send UNSUB).\n    auto_unsub_triggered: bool = false,\n\n    // Pending limits (flow control)\n    /// Maximum pending messages allowed in queue. 0 = no limit.\n    pending_limit: usize = 0,\n    /// Maximum pending bytes allowed in queue. 0 = no limit.\n    pending_bytes_limit: usize = 0,\n\n    // REVIEWED(2025-03): pending_bytes is non-atomic by design.\n    // io_task writes (+), user thread reads/decrements (-|).\n    // Race is bounded: worst case slightly over/under-counts,\n    // acceptable for flow control approximation. Atomics would\n    // add overhead to every message push/pop on the hot path.\n    pending_bytes: u64 = 0,\n    /// High watermark for pending message count.\n    max_pending_msgs: u64 = 0,\n    /// High watermark for pending bytes.\n    max_pending_bytes: u64 = 0,\n\n    /// Spin-yield loop to pop next message from queue.\n    /// Internal: used by both nextMsg() and callback drain tasks.\n    fn nextRaw(self: *Subscription, io: Io) !Message {\n        assert(self.state == .active or self.state == .draining);\n\n        dbg.print(\n            \"Sub.nextRaw: ENTERED, queue len={d}\",\n            .{self.queue.len()},\n        );\n\n        var spin_count: u32 = 0;\n        var yield_count: u32 = 0;\n\n        while (true) {\n            if (self.queue.pop()) |msg| {\n                const msg_size =\n                    if (msg.backing_buf) |buf| buf.len else msg.size();\n                self.pending_bytes -|= msg_size;\n                dbg.print(\n                    \"Sub.nextRaw: GOT MESSAGE after {d} yields\",\n                    .{yield_count},\n                );\n                return msg;\n            }\n            if (self.queue.isClosed()) {\n                return error.Closed;\n            }\n            if (self.state != .active and\n                self.state != .draining)\n            {\n                return error.Closed;\n            }\n\n            spin_count += 1;\n            if (spin_count < defaults.Spin.max_spins) {\n                std.atomic.spinLoopHint();\n            } else {\n                io.sleep(\n                    .fromNanoseconds(0),\n                    .awake,\n                ) catch |err| {\n                    if (err == error.Canceled)\n                        return error.Canceled;\n                };\n                spin_count = 0;\n                yield_count += 1;\n                if (yield_count % 10000 == 0) {\n                    dbg.print(\n                        \"Sub.nextRaw: still waiting, yields={d}\",\n                        .{yield_count},\n                    );\n                }\n            }\n        }\n    }\n\n    /// Blocks until a message is available or connection is closed.\n    ///\n    /// Only valid for manual-mode subscriptions (not callback).\n    /// Returns owned Message that caller must free via msg.deinit().\n    pub fn nextMsg(\n        self: *Subscription,\n    ) !Message {\n        assert(self.mode == .manual);\n        if (self.mode != .manual)\n            return error.SubscriptionModeConflict;\n        return self.nextRaw(self.client.io);\n    }\n\n    /// Try receive without blocking. Returns null if no message.\n    /// Only valid for manual-mode subscriptions.\n    pub fn tryNextMsg(self: *Subscription) ?Message {\n        assert(self.mode == .manual);\n        if (self.mode != .manual) return null;\n        if (self.queue.pop()) |msg| {\n            const msg_size =\n                if (msg.backing_buf) |buf| buf.len else msg.size();\n            self.pending_bytes -|= msg_size;\n            return msg;\n        }\n        return null;\n    }\n\n    /// Batch receive - waits for at least 1, returns up to buf.len.\n    /// Only valid for manual-mode subscriptions.\n    pub fn nextMsgBatch(\n        self: *Subscription,\n        io: Io,\n        buf: []Message,\n    ) !usize {\n        assert(self.mode == .manual);\n        if (self.mode != .manual)\n            return error.SubscriptionModeConflict;\n        assert(self.state == .active or self.state == .draining);\n        assert(buf.len > 0);\n\n        var spin_count: u32 = 0;\n\n        while (true) {\n            const count = self.queue.popBatch(buf);\n            if (count > 0) {\n                for (buf[0..count]) |msg| {\n                    const msg_size =\n                        if (msg.backing_buf) |buf_|\n                            buf_.len\n                        else\n                            msg.size();\n                    self.pending_bytes -|= msg_size;\n                }\n                return count;\n            }\n            if (self.queue.isClosed()) {\n                return error.Closed;\n            }\n            if (self.state != .active and\n                self.state != .draining)\n            {\n                return error.Closed;\n            }\n\n            spin_count += 1;\n            if (spin_count < defaults.Spin.max_spins) {\n                std.atomic.spinLoopHint();\n            } else {\n                io.sleep(\n                    .fromNanoseconds(0),\n                    .awake,\n                ) catch |err| {\n                    if (err == error.Canceled)\n                        return error.Canceled;\n                };\n                spin_count = 0;\n            }\n        }\n    }\n\n    /// Non-blocking batch receive.\n    /// Only valid for manual-mode subscriptions.\n    pub fn tryNextMsgBatch(self: *Subscription, buf: []Message) usize {\n        assert(self.mode == .manual);\n        if (self.mode != .manual) return 0;\n        const count = self.queue.popBatch(buf);\n        for (buf[0..count]) |msg| {\n            const msg_size =\n                if (msg.backing_buf) |buf_| buf_.len else msg.size();\n            self.pending_bytes -|= msg_size;\n        }\n        return count;\n    }\n\n    /// Receive with timeout. Spins briefly for low\n    /// latency, then yields to the IO event loop.\n    /// Returns null on timeout. Supports cancellation\n    /// via io.sleep yield points.\n    pub fn nextMsgTimeout(\n        self: *Subscription,\n        timeout_ms: u32,\n    ) !?Message {\n        assert(self.mode == .manual);\n        if (self.mode != .manual)\n            return error.SubscriptionModeConflict;\n        assert(self.state == .active or\n            self.state == .draining);\n        assert(timeout_ms > 0);\n\n        const io = self.client.io;\n        const start = getNowNs(io);\n        const timeout_ns: u64 =\n            @as(u64, timeout_ms) * std.time.ns_per_ms;\n        var spin_count: u32 = 0;\n\n        while (true) {\n            if (self.queue.pop()) |msg| {\n                const msg_size =\n                    if (msg.backing_buf) |buf|\n                        buf.len\n                    else\n                        msg.size();\n                self.pending_bytes -|= msg_size;\n                return msg;\n            }\n            if (self.queue.isClosed()) {\n                return error.Closed;\n            }\n            if (self.state != .active and\n                self.state != .draining)\n            {\n                return error.Closed;\n            }\n\n            spin_count += 1;\n            if (spin_count < defaults.Spin.max_spins) {\n                std.atomic.spinLoopHint();\n            } else {\n                // Yield to IO event loop. This:\n                // - stops burning CPU\n                // - enables future.cancel()\n                // - works in Debug mode\n                io.sleep(\n                    .fromNanoseconds(0),\n                    .awake,\n                ) catch |err| {\n                    if (err == error.Canceled)\n                        return error.Canceled;\n                };\n                spin_count = 0;\n                // Check timeout after yielding\n                const now = getNowNs(io);\n                if (now -| start >= timeout_ns)\n                    return null;\n            }\n        }\n    }\n\n    /// Returns queue capacity.\n    pub fn capacity(self: *const Subscription) usize {\n        return self.queue.capacity;\n    }\n\n    /// Returns count of messages dropped due to queue overflow.\n    /// Only incremented when other subscriptions route messages to this one\n    /// and the queue is full. The reading subscription bypasses its queue.\n    pub fn dropped(self: *const Subscription) u64 {\n        return self.dropped_msgs;\n    }\n\n    /// Returns count of messages dropped due to allocation failure.\n    pub fn allocFailed(self: *const Subscription) u64 {\n        return self.alloc_failed_msgs;\n    }\n\n    // Subscription Control Methods\n\n    /// Auto-unsubscribe after receiving max messages.\n    /// Sends UNSUB with max_msgs to server for server-side enforcement.\n    /// Client also tracks and triggers local cleanup.\n    pub fn autoUnsubscribe(self: *Subscription, max: u64) !void {\n        assert(max > 0);\n        if (self.state != .active) return error.InvalidState;\n        if (self.client_destroyed) return error.InvalidState;\n\n        self.max_msgs = max;\n        self.delivered_count = 0;\n\n        // Enqueue UNSUB with max_msgs so it stays ordered with publishes.\n        const client = self.client;\n        if (State.atomicLoad(&client.state).canSend()) {\n            client.publish_mutex.lockUncancelable(client.io);\n            defer client.publish_mutex.unlock(client.io);\n\n            client.encodeUnsubToRing(.{\n                .sid = self.sid,\n                .max_msgs = max,\n            }) catch return error.EncodingFailed;\n\n            // Signal auto-flush to send UNSUB promptly\n            client.flush_requested.store(true, .release);\n        }\n    }\n\n    /// Gracefully drain this subscription.\n    /// Stops receiving new messages but delivers already-queued messages.\n    pub fn drain(self: *Subscription) !void {\n        if (self.state != .active) return;\n        if (self.client_destroyed) return error.InvalidState;\n\n        const client = self.client;\n        if (State.atomicLoad(&client.state).canSend()) {\n            client.publish_mutex.lockUncancelable(client.io);\n            defer client.publish_mutex.unlock(client.io);\n\n            client.encodeUnsubToRing(.{\n                .sid = self.sid,\n                .max_msgs = null,\n            }) catch return error.EncodingFailed;\n\n            // Signal auto-flush to send UNSUB promptly\n            client.flush_requested.store(true, .release);\n        }\n\n        self.state = .draining;\n    }\n\n    /// Blocks until the subscription queue is empty (drained) or timeout.\n    ///\n    /// Call after drain() to wait for all queued messages to be consumed.\n    /// Returns error.Timeout if the queue is not empty after timeout_ms.\n    /// Returns error.NotDraining if subscription is not in draining state.\n    ///\n    /// Note: This only waits for the queue to empty. Call unsubscribe() or\n    /// deinit() afterward to fully clean up the subscription.\n    ///\n    /// Example:\n    /// ```\n    /// try sub.drain();\n    /// // ... consume messages with nextMsg() ...\n    /// try sub.waitDrained(5000); // Wait up to 5 seconds\n    /// sub.deinit(allocator);     // Clean up\n    /// ```\n    pub fn waitDrained(self: *Subscription, timeout_ms: u32) !void {\n        assert(timeout_ms > 0);\n\n        if (self.state != .draining) {\n            return error.NotDraining;\n        }\n\n        // Already drained\n        if (self.queue.len() == 0) {\n            return;\n        }\n\n        const io = self.client.io;\n        const start = getNowNs(io);\n        const timeout_ns: u64 =\n            @as(u64, timeout_ms) * std.time.ns_per_ms;\n        var spin_count: u32 = 0;\n\n        while (true) {\n            if (self.queue.len() == 0) {\n                return;\n            }\n\n            spin_count += 1;\n            if (spin_count < defaults.Spin.max_spins) {\n                std.atomic.spinLoopHint();\n            } else {\n                io.sleep(\n                    .fromNanoseconds(0),\n                    .awake,\n                ) catch {};\n                spin_count = 0;\n                const now = getNowNs(io);\n                if (now -| start >= timeout_ns)\n                    return error.Timeout;\n            }\n        }\n    }\n\n    /// Returns the number of messages pending in the queue.\n    pub fn pending(self: *const Subscription) usize {\n        return self.queue.len();\n    }\n\n    /// Returns the count of delivered messages.\n    /// Only tracked when autoUnsubscribe is set.\n    pub fn delivered(self: *const Subscription) u64 {\n        return self.delivered_count;\n    }\n\n    /// Sets the maximum pending message limit.\n    /// When exceeded, new messages are dropped (slow consumer).\n    /// Set to 0 for no limit (default).\n    pub fn setPendingLimits(self: *Subscription, msg_limit: usize) void {\n        self.pending_limit = msg_limit;\n    }\n\n    /// Returns the current pending message limit. 0 means no limit.\n    pub fn pendingLimits(self: *const Subscription) usize {\n        return self.pending_limit;\n    }\n\n    /// Sets the maximum pending bytes limit.\n    /// When exceeded, new messages are dropped (slow consumer).\n    /// Set to 0 for no limit (default).\n    pub fn setPendingBytesLimit(self: *Subscription, bytes_limit: usize) void {\n        self.pending_bytes_limit = bytes_limit;\n    }\n\n    /// Returns the current pending bytes limit. 0 means no limit.\n    pub fn pendingBytesLimit(self: *const Subscription) usize {\n        return self.pending_bytes_limit;\n    }\n\n    /// Returns true if the subscription is valid and can receive messages.\n    pub fn isValid(self: *const Subscription) bool {\n        if (self.client_destroyed) return false;\n        return self.state == .active or self.state == .draining;\n    }\n\n    // Subscription Info Getters\n\n    /// Returns the subscription ID (SID).\n    /// Unique within the connection, assigned during subscribe.\n    pub fn getSid(self: *const Subscription) u64 {\n        assert(self.sid > 0);\n        return self.sid;\n    }\n\n    /// Returns the subscription subject pattern.\n    /// May contain wildcards (* and >).\n    pub fn getSubject(self: *const Subscription) []const u8 {\n        assert(self.subject.len > 0);\n        return self.subject;\n    }\n\n    /// Returns the queue group name if subscribed as a queue subscriber.\n    /// Returns null for regular subscriptions.\n    pub fn queueGroup(self: *const Subscription) ?[]const u8 {\n        return self.queue_group;\n    }\n\n    /// Returns true if this subscription is currently draining.\n    /// A draining subscription will deliver queued messages but not receive new ones.\n    pub fn isDraining(self: *const Subscription) bool {\n        return self.state == .draining;\n    }\n\n    // Subscription Statistics\n\n    /// Subscription statistics snapshot.\n    pub const SubStats = struct {\n        /// Current messages pending in queue.\n        pending_msgs: usize,\n        /// Current bytes pending in queue.\n        pending_bytes: u64,\n        /// High watermark for pending message count.\n        max_pending_msgs: u64,\n        /// High watermark for pending bytes.\n        max_pending_bytes: u64,\n        /// Total messages delivered to this subscription.\n        delivered: u64,\n        /// Messages dropped due to slow consumer (queue overflow).\n        dropped: u64,\n        /// Messages lost due to allocation failure.\n        alloc_failed: u64,\n    };\n\n    /// Returns current pending bytes in queue.\n    pub fn pendingBytes(self: *const Subscription) u64 {\n        return self.pending_bytes;\n    }\n\n    /// Returns high watermarks for pending messages and bytes.\n    pub fn maxPending(self: *const Subscription) struct {\n        msgs: u64,\n        bytes: u64,\n    } {\n        return .{\n            .msgs = self.max_pending_msgs,\n            .bytes = self.max_pending_bytes,\n        };\n    }\n\n    /// Resets high watermark counters to current values.\n    pub fn clearMaxPending(self: *Subscription) void {\n        self.max_pending_msgs = self.queue.len();\n        self.max_pending_bytes = self.pending_bytes;\n    }\n\n    /// Returns a snapshot of subscription statistics.\n    pub fn subStats(self: *const Subscription) SubStats {\n        return .{\n            .pending_msgs = self.queue.len(),\n            .pending_bytes = self.pending_bytes,\n            .max_pending_msgs = self.max_pending_msgs,\n            .max_pending_bytes = self.max_pending_bytes,\n            .delivered = self.delivered_count,\n            .dropped = self.dropped_msgs,\n            .alloc_failed = self.alloc_failed_msgs,\n        };\n    }\n\n    /// Push message to queue (called by io_task).\n    /// Lock-free, never blocks.\n    pub fn pushMessage(self: *Subscription, msg: Message) !void {\n        if (self.pending_limit > 0 or\n            self.pending_bytes_limit > 0)\n        {\n            // Slow path: flow control active\n            const queue_len = self.queue.len();\n            if (self.pending_limit > 0 and\n                queue_len >= self.pending_limit)\n                return error.QueueFull;\n            const msg_size =\n                if (msg.backing_buf) |buf|\n                    buf.len\n                else\n                    msg.size();\n            if (self.pending_bytes_limit > 0 and\n                self.pending_bytes + msg_size >\n                    self.pending_bytes_limit)\n            {\n                return error.QueueFull;\n            }\n            if (!self.queue.push(msg))\n                return error.QueueFull;\n            self.pending_bytes += msg_size;\n            // Watermarks (reuse queue_len)\n            const new_len = queue_len + 1;\n            if (new_len > self.max_pending_msgs)\n                self.max_pending_msgs = new_len;\n            if (self.pending_bytes > self.max_pending_bytes)\n                self.max_pending_bytes = self.pending_bytes;\n        } else {\n            // Fast path: no flow control (3 atomic ops)\n            if (!self.queue.push(msg))\n                return error.QueueFull;\n            const msg_size =\n                if (msg.backing_buf) |buf|\n                    buf.len\n                else\n                    msg.size();\n            self.pending_bytes += msg_size;\n            if (self.pending_bytes > self.max_pending_bytes)\n                self.max_pending_bytes = self.pending_bytes;\n        }\n\n        // Auto-unsubscribe (only when max_msgs set)\n        if (self.max_msgs != null) {\n            self.delivered_count += 1;\n            if (self.delivered_count >= self.max_msgs.? and\n                !self.auto_unsub_triggered)\n            {\n                self.auto_unsub_triggered = true;\n                self.client.pushEvent(.{\n                    .subscription_complete = .{\n                        .sid = self.sid,\n                    },\n                });\n            }\n        }\n    }\n\n    /// Unsubscribes from the subject.\n    ///\n    /// Sends UNSUB to server, removes from client tracking, closes queue.\n    /// Idempotent - returns immediately if already unsubscribed.\n    /// Does NOT free memory - call deinit() for that.\n    ///\n    /// Returns error.NotConnected if UNSUB couldn't be sent (local cleanup\n    /// still succeeds). Returns error.EncodingFailed for protocol errors.\n    pub fn unsubscribe(self: *Subscription) !void {\n        // Idempotent - already unsubscribed\n        if (self.state == .unsubscribed) return;\n\n        // Client already destroyed, mark state only\n        if (self.client_destroyed) {\n            self.state = .unsubscribed;\n            return;\n        }\n\n        const client = self.client;\n        const can_send = State.atomicLoad(&client.state).canSend();\n\n        // sub_mutex serializes with subscribe for\n        // sidmap/sub_ptrs/free_slots safety.\n        // Lock ordering: sub_mutex -> read_mutex -> write_mutex.\n        client.sub_mutex.lockUncancelable(client.io);\n        defer client.sub_mutex.unlock(client.io);\n\n        // Acquire mutex for thread-safe cleanup\n        client.read_mutex.lockUncancelable(client.io);\n        defer client.read_mutex.unlock(client.io);\n\n        // Track UNSUB enqueue success\n        var send_failed = false;\n\n        // Enqueue UNSUB protocol if connected\n        if (can_send) {\n            client.publish_mutex.lockUncancelable(client.io);\n            client.encodeUnsubToRing(.{\n                .sid = self.sid,\n                .max_msgs = null,\n            }) catch {\n                send_failed = true;\n            };\n            client.publish_mutex.unlock(client.io);\n\n            // Signal auto-flush to send UNSUB promptly\n            client.flush_requested.store(true, .release);\n        }\n\n        // Always remove from client tracking (inside mutex)\n        // This must happen even if not connected to prevent use-after-free\n        if (client.sidmap.get(self.sid)) |slot_idx| {\n            client.sub_ptrs[slot_idx] = null;\n            if (client.cached_sub == self) client.cached_sub = null;\n            _ = client.sidmap.remove(self.sid);\n            client.free_slots[client.free_count] = slot_idx;\n            client.free_count += 1;\n        }\n\n        // Close queue (inside mutex)\n        self.queue.close(client.io);\n\n        // Mark as unsubscribed\n        self.state = .unsubscribed;\n\n        // Report errors after cleanup completes\n        if (!can_send) return error.NotConnected;\n        if (send_failed) return error.EncodingFailed;\n    }\n\n    /// Frees all memory resources.\n    ///\n    /// If not yet unsubscribed, calls unsubscribe() and ignores errors.\n    /// Safe to use in defer blocks (like Rust's Drop trait).\n    pub fn deinit(self: *Subscription) void {\n        const allocator = self.client.allocator;\n        // Cancel callback drain task before unsubscribe\n        if (self.callback_future) |*future| {\n            _ = future.cancel(self.client.io);\n            self.callback_future = null;\n        }\n\n        // Ensure unsubscribed (errors ignored - like Rust Drop)\n        if (self.state != .unsubscribed) {\n            self.unsubscribe() catch |err| {\n                dbg.print(\n                    \"deinit: unsubscribe failed: {s}\",\n                    .{@errorName(err)},\n                );\n            };\n        }\n\n        // Drain remaining messages (return buffers to pool)\n        var drain_buf: [1]Message = undefined;\n        while (true) {\n            const n = self.queue.popBatch(&drain_buf);\n            if (n == 0) break;\n            drain_buf[0].deinit();\n        }\n\n        // Free subscription resources\n        allocator.free(self.queue_buf);\n        allocator.free(self.subject);\n        if (self.queue_group) |qg| allocator.free(qg);\n        allocator.destroy(self);\n    }\n};\n\ntest \"parse url\" {\n    {\n        const parsed = try parseUrl(\"nats://localhost:4222\");\n        try std.testing.expectEqualSlices(u8, \"localhost\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n        try std.testing.expect(parsed.user == null);\n        try std.testing.expect(!parsed.use_tls);\n    }\n\n    {\n        const parsed = try parseUrl(\"nats://user:pass@localhost:4222\");\n        try std.testing.expectEqualSlices(u8, \"localhost\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n        try std.testing.expectEqualSlices(u8, \"user\", parsed.user.?);\n        try std.testing.expectEqualSlices(u8, \"pass\", parsed.pass.?);\n        try std.testing.expect(!parsed.use_tls);\n    }\n\n    {\n        const parsed = try parseUrl(\"localhost\");\n        try std.testing.expectEqualSlices(u8, \"localhost\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n        try std.testing.expect(!parsed.use_tls);\n    }\n\n    {\n        const parsed = try parseUrl(\"127.0.0.1:4223\");\n        try std.testing.expectEqualSlices(u8, \"127.0.0.1\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4223), parsed.port);\n        try std.testing.expect(!parsed.use_tls);\n    }\n}\n\ntest \"parse url tls scheme\" {\n    {\n        const parsed = try parseUrl(\"tls://secure.example.com:4222\");\n        try std.testing.expectEqualSlices(u8, \"secure.example.com\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n        try std.testing.expect(parsed.use_tls);\n    }\n\n    {\n        const parsed = try parseUrl(\"tls://user:pass@secure.example.com:4222\");\n        try std.testing.expectEqualSlices(u8, \"secure.example.com\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n        try std.testing.expectEqualSlices(u8, \"user\", parsed.user.?);\n        try std.testing.expectEqualSlices(u8, \"pass\", parsed.pass.?);\n        try std.testing.expect(parsed.use_tls);\n    }\n\n    {\n        const parsed = try parseUrl(\"tls://localhost\");\n        try std.testing.expectEqualSlices(u8, \"localhost\", parsed.host);\n        try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n        try std.testing.expect(parsed.use_tls);\n    }\n}\n\ntest \"parse url bracketed ipv6 host\" {\n    const parsed = try parseUrl(\"tls://user:pass@[::1]:4223\");\n    try std.testing.expectEqualSlices(u8, \"::1\", parsed.host);\n    try std.testing.expectEqual(@as(u16, 4223), parsed.port);\n    try std.testing.expectEqualSlices(u8, \"user\", parsed.user.?);\n    try std.testing.expectEqualSlices(u8, \"pass\", parsed.pass.?);\n    try std.testing.expect(parsed.use_tls);\n}\n\ntest \"parse url with user only\" {\n    const parsed = try parseUrl(\"nats://admin@localhost:4222\");\n    try std.testing.expectEqualSlices(u8, \"localhost\", parsed.host);\n    try std.testing.expectEqualSlices(u8, \"admin\", parsed.user.?);\n    try std.testing.expect(parsed.pass == null);\n}\n\ntest \"parse url invalid\" {\n    try std.testing.expectError(error.InvalidUrl, parseUrl(\"nats://\"));\n    try std.testing.expectError(error.InvalidUrl, parseUrl(\"nats://:4222\"));\n}\n\ntest \"parse url default port\" {\n    const parsed = try parseUrl(\"nats://myserver\");\n    try std.testing.expectEqualSlices(u8, \"myserver\", parsed.host);\n    try std.testing.expectEqual(@as(u16, 4222), parsed.port);\n}\n\ntest \"options defaults\" {\n    const opts: Options = .{};\n    try std.testing.expect(opts.name == null);\n    try std.testing.expect(!opts.verbose);\n    try std.testing.expect(!opts.pedantic);\n    try std.testing.expect(opts.user == null);\n    try std.testing.expect(opts.pass == null);\n    try std.testing.expectEqual(\n        defaults.Connection.timeout_ns,\n        opts.connect_timeout_ns,\n    );\n    try std.testing.expectEqual(\n        defaults.Memory.queue_size.value(),\n        opts.sub_queue_size,\n    );\n}\n\ntest \"stats defaults\" {\n    const s: Statistics = .{};\n    const snap = s.snapshot();\n    try std.testing.expectEqual(@as(u64, 0), snap.msgs_in);\n    try std.testing.expectEqual(@as(u64, 0), snap.msgs_out);\n    try std.testing.expectEqual(@as(u64, 0), snap.bytes_in);\n    try std.testing.expectEqual(@as(u64, 0), snap.bytes_out);\n    try std.testing.expectEqual(@as(u32, 0), snap.reconnects);\n    try std.testing.expectEqual(@as(u32, 0), snap.connects);\n}\n"
  },
  {
    "path": "src/auth/base32.zig",
    "content": "//! Base32 encoder/decoder (RFC 4648).\n//!\n//! Uses standard alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\npub const Error = error{\n    InvalidCharacter,\n    InvalidPadding,\n    OutputTooSmall,\n};\n\n/// Standard RFC 4648 base32 alphabet.\nconst alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\";\n\n/// Decoding lookup table (256 entries, 0xFF = invalid).\nconst decode_table: [256]u8 = blk: {\n    var t: [256]u8 = .{0xFF} ** 256;\n    for (alphabet, 0..) |c, i| {\n        t[c] = @intCast(i);\n        // Also accept lowercase\n        if (c >= 'A' and c <= 'Z') {\n            t[c + 32] = @intCast(i);\n        }\n    }\n    break :blk t;\n};\n\n/// Calculates decoded byte length from encoded character length.\n/// Does not account for padding characters.\npub fn decodedLen(encoded_len: usize) usize {\n    return (encoded_len * 5) / 8;\n}\n\n/// Calculates encoded character length from decoded byte length.\npub fn encodedLen(decoded_len: usize) usize {\n    return (decoded_len * 8 + 4) / 5;\n}\n\n/// Decodes base32 string into dest buffer.\n/// Returns slice of decoded bytes.\npub fn decode(dest: []u8, source: []const u8) Error![]u8 {\n    if (source.len == 0) return dest[0..0];\n\n    const needed = decodedLen(source.len);\n    if (dest.len < needed) return error.OutputTooSmall;\n    assert(dest.len >= needed);\n\n    var bits: u32 = 0;\n    var bit_count: u5 = 0;\n    var out_idx: usize = 0;\n\n    for (source) |c| {\n        if (c == '=') break;\n\n        const val = decode_table[c];\n        if (val == 0xFF) return error.InvalidCharacter;\n\n        bits = (bits << 5) | val;\n        bit_count += 5;\n\n        if (bit_count >= 8) {\n            bit_count -= 8;\n            dest[out_idx] = @intCast((bits >> bit_count) & 0xFF);\n            out_idx += 1;\n        }\n    }\n\n    return dest[0..out_idx];\n}\n\n/// Encodes bytes into base32 string in dest buffer.\n/// Returns slice of encoded characters.\npub fn encode(dest: []u8, source: []const u8) Error![]u8 {\n    if (source.len == 0) return dest[0..0];\n\n    const needed = encodedLen(source.len);\n    if (dest.len < needed) return error.OutputTooSmall;\n    assert(dest.len >= needed);\n\n    var bits: u32 = 0;\n    var bit_count: u5 = 0;\n    var out_idx: usize = 0;\n\n    for (source) |byte| {\n        bits = (bits << 8) | byte;\n        bit_count += 8;\n\n        while (bit_count >= 5) {\n            bit_count -= 5;\n            dest[out_idx] = alphabet[(bits >> bit_count) & 0x1F];\n            out_idx += 1;\n        }\n    }\n\n    // Handle remaining bits (if any)\n    if (bit_count > 0) {\n        dest[out_idx] = alphabet[(bits << (5 - bit_count)) & 0x1F];\n        out_idx += 1;\n    }\n\n    return dest[0..out_idx];\n}\n\ntest \"decode empty\" {\n    var buf: [1]u8 = undefined;\n    const result = try decode(&buf, \"\");\n    try std.testing.expectEqual(@as(usize, 0), result.len);\n}\n\ntest \"decode single char\" {\n    var buf: [1]u8 = undefined;\n    const result = try decode(&buf, \"ME\");\n    try std.testing.expectEqual(@as(usize, 1), result.len);\n    try std.testing.expectEqual(@as(u8, 'a'), result[0]);\n}\n\ntest \"decode lowercase accepted\" {\n    var buf: [1]u8 = undefined;\n    const result = try decode(&buf, \"me\");\n    try std.testing.expectEqual(@as(u8, 'a'), result[0]);\n}\n\ntest \"decode test string\" {\n    var buf: [16]u8 = undefined;\n    const result = try decode(&buf, \"ORSXG5A\");\n    try std.testing.expectEqualSlices(u8, \"test\", result);\n}\n\ntest \"decode invalid character\" {\n    var buf: [16]u8 = undefined;\n    try std.testing.expectError(error.InvalidCharacter, decode(&buf, \"ME!!\"));\n}\n\ntest \"decode with padding\" {\n    var buf: [16]u8 = undefined;\n    const result = try decode(&buf, \"ORSXG5A=\");\n    try std.testing.expectEqualSlices(u8, \"test\", result);\n}\n\ntest \"encode empty\" {\n    var buf: [1]u8 = undefined;\n    const result = try encode(&buf, \"\");\n    try std.testing.expectEqual(@as(usize, 0), result.len);\n}\n\ntest \"encode single byte\" {\n    var buf: [8]u8 = undefined;\n    const result = try encode(&buf, \"a\");\n    try std.testing.expectEqualSlices(u8, \"ME\", result);\n}\n\ntest \"encode test string\" {\n    var buf: [16]u8 = undefined;\n    const result = try encode(&buf, \"test\");\n    try std.testing.expectEqualSlices(u8, \"ORSXG5A\", result);\n}\n\ntest \"encode decode roundtrip\" {\n    const original = \"Hello, World!\";\n    var enc_buf: [64]u8 = undefined;\n    var dec_buf: [64]u8 = undefined;\n\n    const encoded = try encode(&enc_buf, original);\n    const decoded = try decode(&dec_buf, encoded);\n\n    try std.testing.expectEqualSlices(u8, original, decoded);\n}\n\ntest \"decodedLen\" {\n    try std.testing.expectEqual(@as(usize, 0), decodedLen(0));\n    try std.testing.expectEqual(@as(usize, 0), decodedLen(1));\n    try std.testing.expectEqual(@as(usize, 1), decodedLen(2));\n    try std.testing.expectEqual(@as(usize, 2), decodedLen(4));\n    try std.testing.expectEqual(@as(usize, 5), decodedLen(8));\n    // NKey seed: 57 chars -> 35 bytes\n    try std.testing.expectEqual(@as(usize, 35), decodedLen(57));\n}\n\ntest \"encodedLen\" {\n    try std.testing.expectEqual(@as(usize, 0), encodedLen(0));\n    try std.testing.expectEqual(@as(usize, 2), encodedLen(1));\n    try std.testing.expectEqual(@as(usize, 8), encodedLen(5));\n    // Public key: 35 bytes -> 56 chars\n    try std.testing.expectEqual(@as(usize, 56), encodedLen(35));\n}\n"
  },
  {
    "path": "src/auth/crc16.zig",
    "content": "//! CRC16-CCITT checksum for NKey validation.\n//!\n//! Thin wrapper around std.hash.crc.Crc16Xmodem.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\nconst Crc16Xmodem = std.hash.crc.Crc16Xmodem;\n\n/// Computes CRC16-CCITT (XMODEM) checksum over data.\npub fn compute(data: []const u8) u16 {\n    assert(data.len > 0);\n    return Crc16Xmodem.hash(data);\n}\n\n/// Validates CRC16 checksum against expected value.\npub fn validate(data: []const u8, expected: u16) bool {\n    assert(data.len > 0);\n    return compute(data) == expected;\n}\n\ntest \"compute single byte\" {\n    const result = compute(&.{0x31});\n    try std.testing.expectEqual(@as(u16, 0x2672), result);\n}\n\ntest \"compute ascii string\" {\n    const result = compute(\"123456789\");\n    // CRC16-CCITT (XMODEM) of \"123456789\" = 0x31C3\n    try std.testing.expectEqual(@as(u16, 0x31C3), result);\n}\n\ntest \"validate correct checksum\" {\n    try std.testing.expect(validate(\"123456789\", 0x31C3));\n}\n\ntest \"validate incorrect checksum\" {\n    try std.testing.expect(!validate(\"123456789\", 0x0000));\n}\n"
  },
  {
    "path": "src/auth/creds.zig",
    "content": "//! Credentials file parser and formatter for NATS JWT authentication.\n//!\n//! Parses and formats .creds files containing JWT and NKey seed.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Io = std.Io;\n\npub const Error = error{\n    InvalidCredentials,\n    MissingJwt,\n    MissingSeed,\n};\n\n/// Parsed credentials containing JWT and NKey seed.\n/// Slices point into the original content buffer.\npub const Credentials = struct {\n    jwt: []const u8,\n    seed: []const u8,\n};\n\nconst JWT_BEGIN = \"-----BEGIN NATS USER JWT-----\";\nconst JWT_END = \"------END NATS USER JWT------\";\nconst SEED_BEGIN = \"-----BEGIN USER NKEY SEED-----\";\nconst SEED_END = \"------END USER NKEY SEED------\";\n\n/// Parses credentials from content buffer.\n/// Returns slices into the input buffer.\npub fn parse(content: []const u8) Error!Credentials {\n    assert(content.len > 0);\n\n    // Find JWT section\n    const jwt_start_idx = std.mem.indexOf(u8, content, JWT_BEGIN) orelse {\n        return error.MissingJwt;\n    };\n    const jwt_content_start = jwt_start_idx + JWT_BEGIN.len;\n\n    const jwt_end_idx = std.mem.indexOfPos(\n        u8,\n        content,\n        jwt_content_start,\n        JWT_END,\n    ) orelse {\n        return error.MissingJwt;\n    };\n\n    // Find seed section\n    const seed_start_idx = std.mem.indexOf(u8, content, SEED_BEGIN) orelse {\n        return error.MissingSeed;\n    };\n    const seed_content_start = seed_start_idx + SEED_BEGIN.len;\n\n    const seed_end_idx = std.mem.indexOfPos(\n        u8,\n        content,\n        seed_content_start,\n        SEED_END,\n    ) orelse {\n        return error.MissingSeed;\n    };\n\n    // Extract and trim JWT\n    const jwt_raw = content[jwt_content_start..jwt_end_idx];\n    const jwt = trimWhitespace(jwt_raw);\n    if (jwt.len == 0) return error.MissingJwt;\n\n    // Extract and trim seed\n    const seed_raw = content[seed_content_start..seed_end_idx];\n    const seed = trimWhitespace(seed_raw);\n    if (seed.len == 0) return error.MissingSeed;\n\n    assert(jwt.len > 0);\n    assert(seed.len > 0);\n\n    return .{\n        .jwt = jwt,\n        .seed = seed,\n    };\n}\n\n/// Trims leading and trailing ASCII whitespace.\n/// Returns slice into original buffer.\nfn trimWhitespace(s: []const u8) []const u8 {\n    var start: usize = 0;\n    var end: usize = s.len;\n\n    while (start < end and std.ascii.isWhitespace(s[start])) {\n        start += 1;\n    }\n    while (end > start and std.ascii.isWhitespace(s[end - 1])) {\n        end -= 1;\n    }\n\n    return s[start..end];\n}\n\nconst WARN_TEXT =\n    \"\\n\\n\" ++\n    \"************************* IMPORTANT ****\" ++\n    \"*********************\\n\" ++\n    \"  NKEY Seed printed below can be used to\" ++\n    \" sign and prove identity.\\n\" ++\n    \"  NKEYs are sensitive and should be treat\" ++\n    \"ed as secrets.\\n\\n\" ++\n    \"  *************************************\" ++\n    \"************************\\n\\n\";\n\n/// Formats a credentials file from JWT and seed strings.\n/// Writes into caller-provided buffer, returns slice.\npub fn format(\n    buf: []u8,\n    jwt_str: []const u8,\n    seed_str: []const u8,\n) error{BufferTooSmall}![]const u8 {\n    assert(jwt_str.len > 0);\n    assert(seed_str.len > 0);\n    if (buf.len < jwt_str.len + seed_str.len + 256)\n        return error.BufferTooSmall;\n\n    var w = Io.Writer.fixed(buf);\n    w.writeAll(JWT_BEGIN) catch unreachable;\n    w.writeAll(\"\\n\") catch unreachable;\n    w.writeAll(jwt_str) catch unreachable;\n    w.writeAll(\"\\n\") catch unreachable;\n    w.writeAll(JWT_END) catch unreachable;\n    w.writeAll(WARN_TEXT) catch unreachable;\n    w.writeAll(SEED_BEGIN) catch unreachable;\n    w.writeAll(\"\\n\") catch unreachable;\n    w.writeAll(seed_str) catch unreachable;\n    w.writeAll(\"\\n\") catch unreachable;\n    w.writeAll(SEED_END) catch unreachable;\n    w.writeAll(\"\\n\") catch unreachable;\n\n    const result = w.buffered();\n    assert(result.len > 0);\n    return result;\n}\n\n/// Loads and parses credentials from file path.\n/// Caller provides buffer for file content.\n/// Returns slices pointing into buf.\npub fn loadFile(\n    io: Io,\n    path: []const u8,\n    buf: *[8192]u8,\n) !Credentials {\n    assert(path.len > 0);\n\n    const data = try Io.Dir.readFile(.cwd(), io, path, buf);\n    if (data.len == 0) return error.InvalidCredentials;\n\n    return parse(data);\n}\n\ntest \"parse valid credentials\" {\n    const content =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJBQkNERUZHIiw\n        \\\\------END NATS USER JWT------\n        \\\\\n        \\\\************************* IMPORTANT *************************\n        \\\\NKEY Seed printed below can be used to sign and prove identity.\n        \\\\\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n        \\\\------END USER NKEY SEED------\n    ;\n\n    const creds = try parse(content);\n\n    try std.testing.expectEqualStrings(\n        \"eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJBQkNERUZHIiw\",\n        creds.jwt,\n    );\n    try std.testing.expectEqualStrings(\n        \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\",\n        creds.seed,\n    );\n}\n\ntest \"parse credentials with extra whitespace\" {\n    const content =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\\n        \\\\  eyJhbGciOiJlZDI1NTE5In0.eyJzdWIiOiJVQSJ9\n        \\\\\n        \\\\------END NATS USER JWT------\n        \\\\\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\   SUATEST1234567890ABCDEF\n        \\\\------END USER NKEY SEED------\n    ;\n\n    const creds = try parse(content);\n\n    try std.testing.expectEqualStrings(\n        \"eyJhbGciOiJlZDI1NTE5In0.eyJzdWIiOiJVQSJ9\",\n        creds.jwt,\n    );\n    try std.testing.expectEqualStrings(\"SUATEST1234567890ABCDEF\", creds.seed);\n}\n\ntest \"parse credentials missing JWT\" {\n    const content =\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n        \\\\------END USER NKEY SEED------\n    ;\n\n    try std.testing.expectError(error.MissingJwt, parse(content));\n}\n\ntest \"parse credentials missing seed\" {\n    const content =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\eyJhbGciOiJlZDI1NTE5In0\n        \\\\------END NATS USER JWT------\n    ;\n\n    try std.testing.expectError(error.MissingSeed, parse(content));\n}\n\ntest \"parse credentials empty JWT\" {\n    const content =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\\n        \\\\------END NATS USER JWT------\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\SUATEST\n        \\\\------END USER NKEY SEED------\n    ;\n\n    try std.testing.expectError(error.MissingJwt, parse(content));\n}\n\ntest \"parse credentials empty seed\" {\n    const content =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\eyJhbGciOiJlZDI1NTE5In0\n        \\\\------END NATS USER JWT------\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\\n        \\\\------END USER NKEY SEED------\n    ;\n\n    try std.testing.expectError(error.MissingSeed, parse(content));\n}\n\ntest \"parse credentials malformed - no end marker for JWT\" {\n    const content =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\eyJhbGciOiJlZDI1NTE5In0\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\SUATEST\n        \\\\------END USER NKEY SEED------\n    ;\n\n    try std.testing.expectError(error.MissingJwt, parse(content));\n}\n\ntest \"trimWhitespace\" {\n    try std.testing.expectEqualStrings(\"hello\", trimWhitespace(\"  hello  \"));\n    try std.testing.expectEqualStrings(\"hello\", trimWhitespace(\"hello\"));\n    try std.testing.expectEqualStrings(\"hello\", trimWhitespace(\"\\n\\thello\\r\\n\"));\n    try std.testing.expectEqualStrings(\"\", trimWhitespace(\"   \"));\n    try std.testing.expectEqualStrings(\"\", trimWhitespace(\"\"));\n}\n\ntest \"format and parse roundtrip\" {\n    const jwt_str = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5In0.test\";\n    const seed_str =\n        \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV\" ++\n        \"7NSWFFEW63UXMRLFM2XLAXK4GY\";\n\n    var buf: [2048]u8 = undefined;\n    const formatted = try format(&buf, jwt_str, seed_str);\n\n    const creds = try parse(formatted);\n    try std.testing.expectEqualStrings(jwt_str, creds.jwt);\n    try std.testing.expectEqualStrings(\n        seed_str,\n        creds.seed,\n    );\n}\n\ntest \"realistic generated content roundtrip\" {\n    const nkey = @import(\"nkey.zig\");\n    const jwt = @import(\"jwt.zig\");\n    const Ed25519 = std.crypto.sign.Ed25519;\n\n    // Deterministic account keypair\n    const acct_seed = [_]u8{30} ** 32;\n    const acct_ed = Ed25519.KeyPair.generateDeterministic(\n        acct_seed,\n    ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_ed,\n        .key_type = .account,\n    };\n\n    // Deterministic user keypair\n    const user_seed = [_]u8{40} ** 32;\n    const user_ed = Ed25519.KeyPair.generateDeterministic(\n        user_seed,\n    ) catch unreachable;\n    const user_kp = nkey.KeyPair{\n        .kp = user_ed,\n        .key_type = .user,\n    };\n\n    // Encode user JWT\n    var pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt_str = try jwt.encodeUserClaims(\n        &jwt_buf,\n        user_pub,\n        \"roundtrip-user\",\n        acct_kp,\n        1700000000,\n        .{ .pub_allow = &.{\">\"} },\n    );\n\n    // Encode user seed\n    var seed_buf: [58]u8 = undefined;\n    const seed_str = user_kp.encodeSeed(&seed_buf);\n\n    // Format credentials\n    var creds_buf: [4096]u8 = undefined;\n    const formatted = try format(\n        &creds_buf,\n        jwt_str,\n        seed_str,\n    );\n\n    // Parse back\n    const parsed = try parse(formatted);\n    try std.testing.expectEqualStrings(\n        jwt_str,\n        parsed.jwt,\n    );\n    try std.testing.expectEqualStrings(\n        seed_str,\n        parsed.seed,\n    );\n\n    // Verify parsed seed creates valid keypair\n    var kp = try nkey.KeyPair.fromSeed(parsed.seed);\n    defer kp.wipe();\n    var pk2: [56]u8 = undefined;\n    try std.testing.expectEqualStrings(\n        user_pub,\n        kp.publicKey(&pk2),\n    );\n}\n"
  },
  {
    "path": "src/auth/jwt.zig",
    "content": "//! JWT encoding for NATS decentralized authentication.\n//!\n//! Encodes account and user JWTs signed with NKey Ed25519 keypairs.\n//! No allocator needed - all encoding uses caller-provided buffers.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\nconst Io = std.Io;\nconst Ed25519 = std.crypto.sign.Ed25519;\nconst Sha512_256 = std.crypto.hash.sha2.Sha512_256;\nconst base64 = std.base64.url_safe_no_pad;\n\nconst nkey = @import(\"nkey.zig\");\nconst base32 = @import(\"base32.zig\");\n\npub const Error = error{\n    BufferTooSmall,\n    WriteFailed,\n};\n\n/// Pre-encoded JWT header: {\"typ\":\"JWT\",\"alg\":\"ed25519-nkey\"}\nconst HEADER_B64 =\n    \"eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ\";\n\n/// Account JWT options (NATS account limits).\npub const AccountOptions = struct {\n    subs: i64 = -1,\n    data: i64 = -1,\n    payload: i64 = -1,\n    imports: i64 = -1,\n    exports: i64 = -1,\n    conn: i64 = -1,\n    leaf: i64 = -1,\n    mem_storage: i64 = -1,\n    disk_storage: i64 = -1,\n    wildcards: bool = true,\n};\n\n/// Operator JWT options.\npub const OperatorOptions = struct {\n    system_account: []const u8 = \"\",\n};\n\n/// User JWT options (permissions and limits).\npub const UserOptions = struct {\n    pub_allow: []const []const u8 = &.{},\n    sub_allow: []const []const u8 = &.{},\n    subs: i64 = -1,\n    data: i64 = -1,\n    payload: i64 = -1,\n};\n\n/// Encodes a NATS account JWT signed by an operator keypair.\npub fn encodeAccountClaims(\n    buf: []u8,\n    subject: []const u8,\n    name: []const u8,\n    signer: nkey.KeyPair,\n    iat: i64,\n    opts: AccountOptions,\n) Error![]const u8 {\n    assert(subject.len > 0);\n    assert(name.len > 0);\n    if (buf.len < 512) return error.BufferTooSmall;\n\n    var pk_buf: [56]u8 = undefined;\n    const iss = signer.publicKey(&pk_buf);\n\n    // Pass 1: build payload without JTI to compute hash\n    var tmp: [1024]u8 = undefined;\n    const pre_jti = writeAccountJson(\n        &tmp,\n        \"\",\n        iat,\n        iss,\n        name,\n        subject,\n        opts,\n    ) orelse return error.WriteFailed;\n\n    const jti = computeJti(pre_jti);\n\n    // Pass 2: build payload with JTI\n    var payload_buf: [1024]u8 = undefined;\n    const payload = writeAccountJson(\n        &payload_buf,\n        &jti.str,\n        iat,\n        iss,\n        name,\n        subject,\n        opts,\n    ) orelse return error.WriteFailed;\n\n    return assembleJwt(buf, payload, signer);\n}\n\n/// Encodes a NATS user JWT signed by an account keypair.\npub fn encodeUserClaims(\n    buf: []u8,\n    subject: []const u8,\n    name: []const u8,\n    signer: nkey.KeyPair,\n    iat: i64,\n    opts: UserOptions,\n) Error![]const u8 {\n    assert(subject.len > 0);\n    assert(name.len > 0);\n    if (buf.len < 512) return error.BufferTooSmall;\n\n    var pk_buf: [56]u8 = undefined;\n    const iss = signer.publicKey(&pk_buf);\n\n    // Pass 1: build payload without JTI\n    var tmp: [1024]u8 = undefined;\n    const pre_jti = writeUserJson(\n        &tmp,\n        \"\",\n        iat,\n        iss,\n        name,\n        subject,\n        opts,\n    ) orelse return error.WriteFailed;\n\n    const jti = computeJti(pre_jti);\n\n    // Pass 2: build payload with JTI\n    var payload_buf: [1024]u8 = undefined;\n    const payload = writeUserJson(\n        &payload_buf,\n        &jti.str,\n        iat,\n        iss,\n        name,\n        subject,\n        opts,\n    ) orelse return error.WriteFailed;\n\n    return assembleJwt(buf, payload, signer);\n}\n\n/// Encodes a NATS operator JWT (self-signed by operator).\npub fn encodeOperatorClaims(\n    buf: []u8,\n    subject: []const u8,\n    name: []const u8,\n    signer: nkey.KeyPair,\n    iat: i64,\n    opts: OperatorOptions,\n) Error![]const u8 {\n    assert(subject.len > 0);\n    assert(name.len > 0);\n    if (buf.len < 512) return error.BufferTooSmall;\n\n    var pk_buf: [56]u8 = undefined;\n    const iss = signer.publicKey(&pk_buf);\n\n    // Pass 1: build payload without JTI\n    var tmp: [1024]u8 = undefined;\n    const pre_jti = writeOperatorJson(\n        &tmp,\n        \"\",\n        iat,\n        iss,\n        name,\n        subject,\n        opts,\n    ) orelse return error.WriteFailed;\n\n    const jti = computeJti(pre_jti);\n\n    // Pass 2: build payload with JTI\n    var payload_buf: [1024]u8 = undefined;\n    const payload = writeOperatorJson(\n        &payload_buf,\n        &jti.str,\n        iat,\n        iss,\n        name,\n        subject,\n        opts,\n    ) orelse return error.WriteFailed;\n\n    return assembleJwt(buf, payload, signer);\n}\n\nconst Jti = struct { str: [52]u8 };\n\n/// SHA-512/256 hash of payload, base32-encoded.\nfn computeJti(payload: []const u8) Jti {\n    assert(payload.len > 0);\n\n    var hash: [32]u8 = undefined;\n    Sha512_256.hash(payload, &hash, .{});\n\n    var result: Jti = undefined;\n    _ = base32.encode(&result.str, &hash) catch unreachable;\n    return result;\n}\n\n/// Assembles header.payload.signature into buf.\nfn assembleJwt(\n    buf: []u8,\n    payload: []const u8,\n    signer: nkey.KeyPair,\n) Error![]const u8 {\n    assert(payload.len > 0);\n    if (buf.len < 512) return error.BufferTooSmall;\n\n    const payload_b64_len = base64.Encoder.calcSize(\n        payload.len,\n    );\n    // header + \".\" + payload_b64 + \".\" + sig_b64(86)\n    const sig_b64_len = 86;\n    const total = HEADER_B64.len + 1 + payload_b64_len +\n        1 + sig_b64_len;\n    if (buf.len < total) return error.BufferTooSmall;\n\n    var pos: usize = 0;\n\n    // Header\n    @memcpy(buf[pos..][0..HEADER_B64.len], HEADER_B64);\n    pos += HEADER_B64.len;\n\n    // Dot\n    buf[pos] = '.';\n    pos += 1;\n\n    // Base64url-encoded payload\n    _ = base64.Encoder.encode(\n        buf[pos..][0..payload_b64_len],\n        payload,\n    );\n    pos += payload_b64_len;\n\n    // Sign everything before the second dot\n    const sign_data = buf[0..pos];\n    const sig = signer.kp.sign(\n        sign_data,\n        null,\n    ) catch unreachable;\n    const sig_bytes = sig.toBytes();\n\n    // Second dot\n    buf[pos] = '.';\n    pos += 1;\n\n    // Base64url-encoded signature\n    _ = base64.Encoder.encode(\n        buf[pos..][0..sig_b64_len],\n        &sig_bytes,\n    );\n    pos += sig_b64_len;\n\n    assert(pos == total);\n    return buf[0..pos];\n}\n\n/// Writes account claims JSON into buf. Returns slice or null.\n// REVIEWED(2025-03): name/sub/iss fields come from NKey\n// public keys (hex-encoded), never user-controlled input.\n// No JSON injection risk — no escaping needed.\nfn writeAccountJson(\n    buf: []u8,\n    jti: []const u8,\n    iat: i64,\n    iss: []const u8,\n    name: []const u8,\n    sub: []const u8,\n    opts: AccountOptions,\n) ?[]const u8 {\n    assert(iss.len > 0);\n    assert(sub.len > 0);\n    if (buf.len < 256) return null;\n\n    var w = Io.Writer.fixed(buf);\n    w.writeAll(\"{\\\"jti\\\":\\\"\") catch return null;\n    w.writeAll(jti) catch return null;\n    w.writeAll(\"\\\",\\\"iat\\\":\") catch return null;\n    w.print(\"{d}\", .{iat}) catch return null;\n    w.writeAll(\",\\\"iss\\\":\\\"\") catch return null;\n    w.writeAll(iss) catch return null;\n    w.writeAll(\"\\\",\\\"name\\\":\\\"\") catch return null;\n    w.writeAll(name) catch return null;\n    w.writeAll(\"\\\",\\\"sub\\\":\\\"\") catch return null;\n    w.writeAll(sub) catch return null;\n    w.writeAll(\"\\\",\\\"nats\\\":{\\\"limits\\\":{\") catch return null;\n    w.print(\"\\\"subs\\\":{d}\", .{opts.subs}) catch return null;\n    w.print(\",\\\"data\\\":{d}\", .{opts.data}) catch return null;\n    w.print(\n        \",\\\"payload\\\":{d}\",\n        .{opts.payload},\n    ) catch return null;\n    w.print(\n        \",\\\"imports\\\":{d}\",\n        .{opts.imports},\n    ) catch return null;\n    w.print(\n        \",\\\"exports\\\":{d}\",\n        .{opts.exports},\n    ) catch return null;\n    w.print(\",\\\"conn\\\":{d}\", .{opts.conn}) catch return null;\n    w.print(\",\\\"leaf\\\":{d}\", .{opts.leaf}) catch return null;\n    w.print(\n        \",\\\"mem_storage\\\":{d}\",\n        .{opts.mem_storage},\n    ) catch return null;\n    w.print(\n        \",\\\"disk_storage\\\":{d}\",\n        .{opts.disk_storage},\n    ) catch return null;\n    if (opts.wildcards) {\n        w.writeAll(\",\\\"wildcards\\\":true\") catch return null;\n    } else {\n        w.writeAll(\",\\\"wildcards\\\":false\") catch return null;\n    }\n    w.writeAll(\n        \"},\\\"type\\\":\\\"account\\\",\\\"version\\\":2}}\",\n    ) catch return null;\n\n    const result = w.buffered();\n    assert(result.len > 0);\n    return result;\n}\n\n/// Writes user claims JSON into buf. Returns slice or null.\nfn writeUserJson(\n    buf: []u8,\n    jti: []const u8,\n    iat: i64,\n    iss: []const u8,\n    name: []const u8,\n    sub: []const u8,\n    opts: UserOptions,\n) ?[]const u8 {\n    assert(iss.len > 0);\n    assert(sub.len > 0);\n    if (buf.len < 256) return null;\n\n    var w = Io.Writer.fixed(buf);\n    w.writeAll(\"{\\\"jti\\\":\\\"\") catch return null;\n    w.writeAll(jti) catch return null;\n    w.writeAll(\"\\\",\\\"iat\\\":\") catch return null;\n    w.print(\"{d}\", .{iat}) catch return null;\n    w.writeAll(\",\\\"iss\\\":\\\"\") catch return null;\n    w.writeAll(iss) catch return null;\n    w.writeAll(\"\\\",\\\"name\\\":\\\"\") catch return null;\n    w.writeAll(name) catch return null;\n    w.writeAll(\"\\\",\\\"sub\\\":\\\"\") catch return null;\n    w.writeAll(sub) catch return null;\n    w.writeAll(\"\\\",\\\"nats\\\":{\") catch return null;\n\n    // Publish permissions\n    if (opts.pub_allow.len > 0) {\n        w.writeAll(\"\\\"pub\\\":{\\\"allow\\\":[\") catch return null;\n        writeStringArray(&w, opts.pub_allow) orelse\n            return null;\n        w.writeAll(\"]},\") catch return null;\n    }\n\n    // Subscribe permissions\n    if (opts.sub_allow.len > 0) {\n        w.writeAll(\"\\\"sub\\\":{\\\"allow\\\":[\") catch return null;\n        writeStringArray(&w, opts.sub_allow) orelse\n            return null;\n        w.writeAll(\"]},\") catch return null;\n    }\n\n    w.print(\"\\\"subs\\\":{d}\", .{opts.subs}) catch return null;\n    w.print(\",\\\"data\\\":{d}\", .{opts.data}) catch return null;\n    w.print(\n        \",\\\"payload\\\":{d}\",\n        .{opts.payload},\n    ) catch return null;\n    w.writeAll(\n        \",\\\"type\\\":\\\"user\\\",\\\"version\\\":2}}\",\n    ) catch return null;\n\n    const result = w.buffered();\n    assert(result.len > 0);\n    return result;\n}\n\n/// Writes a JSON string array (without brackets).\nfn writeStringArray(\n    w: *Io.Writer,\n    items: []const []const u8,\n) ?void {\n    assert(items.len > 0);\n\n    for (items, 0..) |item, i| {\n        if (i > 0) w.writeAll(\",\") catch return null;\n        w.writeAll(\"\\\"\") catch return null;\n        w.writeAll(item) catch return null;\n        w.writeAll(\"\\\"\") catch return null;\n    }\n}\n\n/// Writes operator claims JSON into buf.\nfn writeOperatorJson(\n    buf: []u8,\n    jti: []const u8,\n    iat: i64,\n    iss: []const u8,\n    name: []const u8,\n    sub: []const u8,\n    opts: OperatorOptions,\n) ?[]const u8 {\n    assert(iss.len > 0);\n    assert(sub.len > 0);\n    if (buf.len < 256) return null;\n\n    var w = Io.Writer.fixed(buf);\n    w.writeAll(\"{\\\"jti\\\":\\\"\") catch return null;\n    w.writeAll(jti) catch return null;\n    w.writeAll(\"\\\",\\\"iat\\\":\") catch return null;\n    w.print(\"{d}\", .{iat}) catch return null;\n    w.writeAll(\",\\\"iss\\\":\\\"\") catch return null;\n    w.writeAll(iss) catch return null;\n    w.writeAll(\"\\\",\\\"name\\\":\\\"\") catch return null;\n    w.writeAll(name) catch return null;\n    w.writeAll(\"\\\",\\\"sub\\\":\\\"\") catch return null;\n    w.writeAll(sub) catch return null;\n    w.writeAll(\"\\\",\\\"nats\\\":{\") catch return null;\n    if (opts.system_account.len > 0) {\n        w.writeAll(\n            \"\\\"system_account\\\":\\\"\",\n        ) catch return null;\n        w.writeAll(\n            opts.system_account,\n        ) catch return null;\n        w.writeAll(\"\\\",\") catch return null;\n    }\n    w.writeAll(\n        \"\\\"type\\\":\\\"operator\\\",\\\"version\\\":2}}\",\n    ) catch return null;\n\n    const result = w.buffered();\n    assert(result.len > 0);\n    return result;\n}\n\ntest \"encode account JWT structure\" {\n    const test_seed = [_]u8{10} ** 32;\n    const op_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            test_seed,\n        ) catch unreachable;\n    const op_kp = nkey.KeyPair{\n        .kp = op_kp_inner,\n        .key_type = .operator,\n    };\n\n    const acct_seed = [_]u8{20} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const acct_pub = acct_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeAccountClaims(\n        &jwt_buf,\n        acct_pub,\n        \"test-account\",\n        op_kp,\n        1700000000,\n        .{},\n    );\n\n    // Split on dots\n    assert(jwt.len > 0);\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n\n    // Verify header decodes to expected JSON\n    const hdr_exp =\n        \"{\\\"typ\\\":\\\"JWT\\\",\\\"alg\\\":\\\"ed25519-nkey\\\"}\";\n    var hdr_buf: [64]u8 = undefined;\n    const hdr_len = base64.Decoder.calcSizeForSlice(\n        jwt[0..dot1],\n    ) catch unreachable;\n    base64.Decoder.decode(\n        hdr_buf[0..hdr_len],\n        jwt[0..dot1],\n    ) catch unreachable;\n    try std.testing.expectEqualStrings(\n        hdr_exp,\n        hdr_buf[0..hdr_len],\n    );\n\n    // Verify payload contains expected fields\n    var pay_buf: [1024]u8 = undefined;\n    const payload_b64 = jwt[dot1 + 1 .. dot2];\n    const pay_len = base64.Decoder.calcSizeForSlice(\n        payload_b64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..pay_len],\n        payload_b64,\n    ) catch unreachable;\n    const pay = pay_buf[0..pay_len];\n\n    // Check key fields exist\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"jti\\\":\\\"\") != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"iat\\\":1700000000\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"name\\\":\\\"test-account\\\"\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(\n            u8,\n            pay,\n            \"\\\"type\\\":\\\"account\\\"\",\n        ) != null,\n    );\n\n    // Verify Ed25519 signature\n    const sig_b64 = jwt[dot2 + 1 ..];\n    var sig_raw: [64]u8 = undefined;\n    base64.Decoder.decode(\n        &sig_raw,\n        sig_b64,\n    ) catch unreachable;\n    const sig = Ed25519.Signature.fromBytes(sig_raw);\n    sig.verify(jwt[0..dot2], op_kp.kp.public_key) catch {\n        return error.WriteFailed;\n    };\n}\n\ntest \"encode user JWT with permissions\" {\n    const acct_seed = [_]u8{30} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    const user_seed = [_]u8{40} ** 32;\n    const user_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            user_seed,\n        ) catch unreachable;\n    const user_kp = nkey.KeyPair{\n        .kp = user_kp_inner,\n        .key_type = .user,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeUserClaims(\n        &jwt_buf,\n        user_pub,\n        \"test-user\",\n        acct_kp,\n        1700000000,\n        .{\n            .pub_allow = &.{ \"foo.>\", \"bar.>\" },\n            .sub_allow = &.{\"_INBOX.>\"},\n        },\n    );\n\n    assert(jwt.len > 0);\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n\n    var pay_buf: [1024]u8 = undefined;\n    const payload_b64 = jwt[dot1 + 1 .. dot2];\n    const pay_len = base64.Decoder.calcSizeForSlice(\n        payload_b64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..pay_len],\n        payload_b64,\n    ) catch unreachable;\n    const pay = pay_buf[0..pay_len];\n\n    // Verify permissions in payload\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"pub\\\":{\\\"allow\\\":[\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"foo.>\\\"\") != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"_INBOX.>\\\"\") != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"type\\\":\\\"user\\\"\") !=\n            null,\n    );\n\n    // Verify signature\n    const sig_b64 = jwt[dot2 + 1 ..];\n    var sig_raw: [64]u8 = undefined;\n    base64.Decoder.decode(\n        &sig_raw,\n        sig_b64,\n    ) catch unreachable;\n    const sig = Ed25519.Signature.fromBytes(sig_raw);\n    sig.verify(\n        jwt[0..dot2],\n        acct_kp.kp.public_key,\n    ) catch {\n        return error.WriteFailed;\n    };\n}\n\ntest \"encode operator JWT self-signed\" {\n    const op_seed = [_]u8{10} ** 32;\n    const op_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            op_seed,\n        ) catch unreachable;\n    const op_kp = nkey.KeyPair{\n        .kp = op_kp_inner,\n        .key_type = .operator,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const op_pub = op_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeOperatorClaims(\n        &jwt_buf,\n        op_pub,\n        \"test-operator\",\n        op_kp,\n        1700000000,\n        .{},\n    );\n\n    assert(jwt.len > 0);\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n\n    // Decode and verify payload\n    var pay_buf: [1024]u8 = undefined;\n    const payload_b64 = jwt[dot1 + 1 .. dot2];\n    const pay_len = base64.Decoder.calcSizeForSlice(\n        payload_b64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..pay_len],\n        payload_b64,\n    ) catch unreachable;\n    const pay = pay_buf[0..pay_len];\n\n    try std.testing.expect(\n        std.mem.indexOf(\n            u8,\n            pay,\n            \"\\\"type\\\":\\\"operator\\\"\",\n        ) != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(\n            u8,\n            pay,\n            \"\\\"name\\\":\\\"test-operator\\\"\",\n        ) != null,\n    );\n\n    // Self-signed: verify with operator's own key\n    const sig_b64 = jwt[dot2 + 1 ..];\n    var sig_raw: [64]u8 = undefined;\n    base64.Decoder.decode(\n        &sig_raw,\n        sig_b64,\n    ) catch unreachable;\n    const sig = Ed25519.Signature.fromBytes(sig_raw);\n    sig.verify(\n        jwt[0..dot2],\n        op_kp.kp.public_key,\n    ) catch {\n        return error.WriteFailed;\n    };\n}\n\ntest \"JTI determinism - same input same output\" {\n    const op_seed = [_]u8{10} ** 32;\n    const op_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            op_seed,\n        ) catch unreachable;\n    const op_kp = nkey.KeyPair{\n        .kp = op_kp_inner,\n        .key_type = .operator,\n    };\n\n    const acct_seed = [_]u8{20} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const acct_pub = acct_kp.publicKey(&pk_buf);\n\n    var buf1: [2048]u8 = undefined;\n    const jwt1 = try encodeAccountClaims(\n        &buf1,\n        acct_pub,\n        \"det-test\",\n        op_kp,\n        1700000000,\n        .{},\n    );\n\n    var buf2: [2048]u8 = undefined;\n    const jwt2 = try encodeAccountClaims(\n        &buf2,\n        acct_pub,\n        \"det-test\",\n        op_kp,\n        1700000000,\n        .{},\n    );\n\n    try std.testing.expectEqualStrings(jwt1, jwt2);\n}\n\ntest \"JTI uniqueness - different names different JTI\" {\n    const op_seed = [_]u8{10} ** 32;\n    const op_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            op_seed,\n        ) catch unreachable;\n    const op_kp = nkey.KeyPair{\n        .kp = op_kp_inner,\n        .key_type = .operator,\n    };\n\n    const acct_seed = [_]u8{20} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const acct_pub = acct_kp.publicKey(&pk_buf);\n\n    var buf1: [2048]u8 = undefined;\n    const jwt1 = try encodeAccountClaims(\n        &buf1,\n        acct_pub,\n        \"account-alpha\",\n        op_kp,\n        1700000000,\n        .{},\n    );\n\n    var buf2: [2048]u8 = undefined;\n    const jwt2 = try encodeAccountClaims(\n        &buf2,\n        acct_pub,\n        \"account-beta\",\n        op_kp,\n        1700000000,\n        .{},\n    );\n\n    // JWTs must differ (different name -> different JTI)\n    try std.testing.expect(\n        !std.mem.eql(u8, jwt1, jwt2),\n    );\n}\n\ntest \"custom account limits in payload\" {\n    const op_seed = [_]u8{10} ** 32;\n    const op_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            op_seed,\n        ) catch unreachable;\n    const op_kp = nkey.KeyPair{\n        .kp = op_kp_inner,\n        .key_type = .operator,\n    };\n\n    const acct_seed = [_]u8{20} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const acct_pub = acct_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeAccountClaims(\n        &jwt_buf,\n        acct_pub,\n        \"limited-acct\",\n        op_kp,\n        1700000000,\n        .{\n            .subs = 100,\n            .conn = 50,\n            .wildcards = false,\n        },\n    );\n\n    // Decode payload\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n    var pay_buf: [1024]u8 = undefined;\n    const pb64 = jwt[dot1 + 1 .. dot2];\n    const plen = base64.Decoder.calcSizeForSlice(\n        pb64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..plen],\n        pb64,\n    ) catch unreachable;\n    const pay = pay_buf[0..plen];\n\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"subs\\\":100\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"conn\\\":50\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(\n            u8,\n            pay,\n            \"\\\"wildcards\\\":false\",\n        ) != null,\n    );\n}\n\ntest \"user JWT with empty permissions\" {\n    const acct_seed = [_]u8{30} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    const user_seed = [_]u8{40} ** 32;\n    const user_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            user_seed,\n        ) catch unreachable;\n    const user_kp = nkey.KeyPair{\n        .kp = user_kp_inner,\n        .key_type = .user,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeUserClaims(\n        &jwt_buf,\n        user_pub,\n        \"no-perms-user\",\n        acct_kp,\n        1700000000,\n        .{},\n    );\n\n    // Decode payload\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n    var pay_buf: [1024]u8 = undefined;\n    const pb64 = jwt[dot1 + 1 .. dot2];\n    const plen = base64.Decoder.calcSizeForSlice(\n        pb64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..plen],\n        pb64,\n    ) catch unreachable;\n    const pay = pay_buf[0..plen];\n\n    // No \"pub\": or \"sub\":{ permission blocks\n    // (only \"subs\":, \"sub\":\" for subject)\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"pub\\\":{\") == null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"sub\\\":{\") == null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"type\\\":\\\"user\\\"\") !=\n            null,\n    );\n}\n\ntest \"user JWT with single permission\" {\n    const acct_seed = [_]u8{30} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    const user_seed = [_]u8{40} ** 32;\n    const user_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            user_seed,\n        ) catch unreachable;\n    const user_kp = nkey.KeyPair{\n        .kp = user_kp_inner,\n        .key_type = .user,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeUserClaims(\n        &jwt_buf,\n        user_pub,\n        \"single-perm-user\",\n        acct_kp,\n        1700000000,\n        .{\n            .pub_allow = &.{\"single.subject\"},\n        },\n    );\n\n    // Decode payload\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n    var pay_buf: [1024]u8 = undefined;\n    const pb64 = jwt[dot1 + 1 .. dot2];\n    const plen = base64.Decoder.calcSizeForSlice(\n        pb64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..plen],\n        pb64,\n    ) catch unreachable;\n    const pay = pay_buf[0..plen];\n\n    try std.testing.expect(\n        std.mem.indexOf(\n            u8,\n            pay,\n            \"\\\"pub\\\":{\\\"allow\\\":[\\\"single.subject\\\"]}\",\n        ) != null,\n    );\n}\n\ntest \"custom user limits in payload\" {\n    const acct_seed = [_]u8{30} ** 32;\n    const acct_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            acct_seed,\n        ) catch unreachable;\n    const acct_kp = nkey.KeyPair{\n        .kp = acct_kp_inner,\n        .key_type = .account,\n    };\n\n    const user_seed = [_]u8{40} ** 32;\n    const user_kp_inner =\n        Ed25519.KeyPair.generateDeterministic(\n            user_seed,\n        ) catch unreachable;\n    const user_kp = nkey.KeyPair{\n        .kp = user_kp_inner,\n        .key_type = .user,\n    };\n\n    var pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&pk_buf);\n\n    var jwt_buf: [2048]u8 = undefined;\n    const jwt = try encodeUserClaims(\n        &jwt_buf,\n        user_pub,\n        \"limited-user\",\n        acct_kp,\n        1700000000,\n        .{\n            .subs = 10,\n            .data = 1024,\n            .payload = 512,\n        },\n    );\n\n    // Decode payload\n    const dot1 = std.mem.indexOf(u8, jwt, \".\") orelse\n        unreachable;\n    const dot2 = std.mem.indexOfPos(\n        u8,\n        jwt,\n        dot1 + 1,\n        \".\",\n    ) orelse unreachable;\n    var pay_buf: [1024]u8 = undefined;\n    const pb64 = jwt[dot1 + 1 .. dot2];\n    const plen = base64.Decoder.calcSizeForSlice(\n        pb64,\n    ) catch unreachable;\n    base64.Decoder.decode(\n        pay_buf[0..plen],\n        pb64,\n    ) catch unreachable;\n    const pay = pay_buf[0..plen];\n\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"subs\\\":10\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"data\\\":1024\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, pay, \"\\\"payload\\\":512\") !=\n            null,\n    );\n}\n"
  },
  {
    "path": "src/auth/nkey.zig",
    "content": "//! NKey authentication for NATS.\n//!\n//! Generates, parses, and encodes NKey seeds. Signs server nonces\n//! for authentication. NKeys use Ed25519 with base32-encoded keys.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\nconst Ed25519 = std.crypto.sign.Ed25519;\nconst base32 = @import(\"base32.zig\");\nconst crc16 = @import(\"crc16.zig\");\n\npub const Error = error{\n    InvalidSeed,\n    InvalidPrefix,\n    InvalidChecksum,\n    InvalidKeyType,\n    InvalidLength,\n    IdentityElement,\n};\n\n/// NKey entity types (encoded in seed prefix).\npub const KeyType = enum(u8) {\n    user = 160, // 20 << 3\n    account = 0, // 0 << 3\n    server = 104, // 13 << 3\n    cluster = 16, // 2 << 3\n    operator = 112, // 14 << 3\n\n    /// Converts byte value to KeyType enum.\n    pub fn fromByte(b: u8) ?KeyType {\n        return switch (b) {\n            160 => .user,\n            0 => .account,\n            104 => .server,\n            16 => .cluster,\n            112 => .operator,\n            else => null,\n        };\n    }\n};\n\n/// Seed prefix byte value (S = 18 << 3 = 144).\nconst SEED_PREFIX: u8 = 144;\n\n/// NKey keypair for signing and verification.\npub const KeyPair = struct {\n    kp: Ed25519.KeyPair,\n    key_type: KeyType,\n\n    /// Generates a random Ed25519 keypair for the given type.\n    pub fn generate(io: std.Io, key_type: KeyType) KeyPair {\n        assert(KeyType.fromByte(@intFromEnum(key_type)) != null);\n        const kp = Ed25519.KeyPair.generate(io);\n        return .{ .kp = kp, .key_type = key_type };\n    }\n\n    /// Encodes the keypair's seed in NKey format (base32).\n    ///\n    /// Reverse of `fromSeed`. Packs prefix + seed + CRC16,\n    /// then base32-encodes to 58-character string.\n    pub fn encodeSeed(\n        self: KeyPair,\n        out: *[58]u8,\n    ) []const u8 {\n        assert(KeyType.fromByte(\n            @intFromEnum(self.key_type),\n        ) != null);\n\n        var raw: [36]u8 = undefined;\n        const kt = @intFromEnum(self.key_type);\n        raw[0] = (SEED_PREFIX & 0xF8) | ((kt >> 5) & 0x07);\n        raw[1] = (kt & 0x1F) << 3;\n        raw[2..34].* = self.kp.secret_key.seed();\n\n        const crc = crc16.compute(raw[0..34]);\n        std.mem.writeInt(u16, raw[34..36], crc, .little);\n\n        defer std.crypto.secureZero(\n            u8,\n            @volatileCast(&raw),\n        );\n\n        return base32.encode(out, &raw) catch unreachable;\n    }\n\n    /// Parses an NKey seed and derives the Ed25519 keypair.\n    ///\n    /// Seed format (57 chars base32):\n    /// - Bytes 0-1: Packed prefix (seed prefix + key type)\n    /// - Bytes 2-33: 32-byte Ed25519 seed\n    /// - Bytes 34-35: CRC16 checksum (little-endian)\n    pub fn fromSeed(encoded_seed: []const u8) Error!KeyPair {\n        assert(encoded_seed.len > 0);\n\n        // Base32 decode the seed\n        var raw: [64]u8 = undefined;\n        defer std.crypto.secureZero(\n            u8,\n            @volatileCast(&raw),\n        );\n        const decoded = base32.decode(&raw, encoded_seed) catch {\n            return error.InvalidSeed;\n        };\n\n        // Expect 35 bytes: 2 prefix + 32 seed + 2 CRC (57 chars / 8 * 5 = 35)\n        if (decoded.len < 35) return error.InvalidLength;\n        assert(decoded.len >= 35);\n\n        // Extract prefix bytes\n        const b1 = decoded[0] & 0xF8;\n        const b2 = ((decoded[0] & 0x07) << 5) | ((decoded[1] & 0xF8) >> 3);\n\n        // Validate seed prefix\n        if (b1 != SEED_PREFIX) return error.InvalidPrefix;\n\n        // Validate key type\n        const key_type = KeyType.fromByte(b2) orelse {\n            return error.InvalidKeyType;\n        };\n\n        // Validate CRC16 checksum (last 2 bytes, little-endian)\n        const data_len = decoded.len - 2;\n        const stored_crc = std.mem.readInt(\n            u16,\n            decoded[data_len..][0..2],\n            .little,\n        );\n        if (!crc16.validate(decoded[0..data_len], stored_crc)) {\n            return error.InvalidChecksum;\n        }\n\n        // Extract 32-byte seed\n        const seed: [32]u8 = decoded[2..34].*;\n\n        // Derive Ed25519 keypair\n        const kp = Ed25519.KeyPair.generateDeterministic(seed) catch {\n            return error.IdentityElement;\n        };\n\n        return .{ .kp = kp, .key_type = key_type };\n    }\n\n    /// Signs data and returns raw 64-byte signature.\n    pub fn sign(self: KeyPair, data: []const u8) [64]u8 {\n        assert(data.len > 0);\n\n        // Deterministic signature (null noise)\n        const sig = self.kp.sign(data, null) catch unreachable;\n        return sig.toBytes();\n    }\n\n    /// Signs data and returns base64url-encoded signature (no padding).\n    /// Writes to provided buffer and returns slice.\n    pub fn signEncoded(\n        self: KeyPair,\n        data: []const u8,\n        out: *[86]u8,\n    ) []const u8 {\n        assert(data.len > 0);\n        assert(out.len >= 86);\n\n        const sig = self.sign(data);\n        return std.base64.url_safe_no_pad.Encoder.encode(out, &sig);\n    }\n\n    /// Returns base32-encoded public key.\n    /// Format: [key_type_prefix][32-byte-pubkey][crc16]\n    pub fn publicKey(self: KeyPair, out: *[56]u8) []const u8 {\n        assert(out.len >= 56);\n\n        // Build raw: 1 byte type + 32 bytes pubkey + 2 bytes CRC = 35 bytes\n        var raw: [35]u8 = undefined;\n        raw[0] = @intFromEnum(self.key_type);\n        raw[1..33].* = self.kp.public_key.toBytes();\n\n        const crc = crc16.compute(raw[0..33]);\n        std.mem.writeInt(u16, raw[33..35], crc, .little);\n\n        // Base32 encode: 35 bytes -> 56 chars\n        return base32.encode(out, &raw) catch unreachable;\n    }\n\n    /// Securely wipes keypair from memory.\n    pub fn wipe(self: *KeyPair) void {\n        std.crypto.secureZero(\n            u8,\n            @volatileCast(&self.kp.secret_key.bytes),\n        );\n    }\n};\n\n// Test vectors from NATS C client\ntest \"parse valid user seed\" {\n    const seed = \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\";\n    var kp = try KeyPair.fromSeed(seed);\n    defer kp.wipe();\n\n    try std.testing.expectEqual(KeyType.user, kp.key_type);\n}\n\ntest \"sign nonce matches test vector\" {\n    const seed = \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\";\n    var kp = try KeyPair.fromSeed(seed);\n    defer kp.wipe();\n\n    const sig = kp.sign(\"nonce\");\n    // First bytes from C client test\n    try std.testing.expectEqual(@as(u8, 155), sig[0]);\n    try std.testing.expectEqual(@as(u8, 157), sig[1]);\n}\n\ntest \"sign encoded\" {\n    const seed = \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\";\n    var kp = try KeyPair.fromSeed(seed);\n    defer kp.wipe();\n\n    var buf: [86]u8 = undefined;\n    const encoded = kp.signEncoded(\"nonce\", &buf);\n\n    // Base64url encoding of 64 bytes = 86 chars (no padding)\n    try std.testing.expectEqual(@as(usize, 86), encoded.len);\n\n    // First char should correspond to sig[0]=155\n    try std.testing.expect(encoded[0] == 'm');\n}\n\ntest \"public key format\" {\n    const seed = \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\";\n    var kp = try KeyPair.fromSeed(seed);\n    defer kp.wipe();\n\n    var buf: [56]u8 = undefined;\n    const pubkey = kp.publicKey(&buf);\n\n    // User public keys start with 'U'\n    try std.testing.expectEqual(@as(u8, 'U'), pubkey[0]);\n    try std.testing.expectEqual(@as(usize, 56), pubkey.len);\n}\n\ntest \"invalid seed - bad prefix\" {\n    // Valid 57-char base32 but wrong prefix (starts with 'N' instead of 'S')\n    // NAAA... decodes to prefix byte that is not SEED_PREFIX (144)\n    const bad = \"NAAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\";\n    try std.testing.expectError(error.InvalidPrefix, KeyPair.fromSeed(bad));\n}\n\ntest \"invalid seed - too short\" {\n    const bad = \"SUAMK2FG\";\n    try std.testing.expectError(error.InvalidLength, KeyPair.fromSeed(bad));\n}\n\ntest \"invalid seed - bad characters\" {\n    // Contains invalid base32 char '!'\n    const bad = \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4!Y\";\n    try std.testing.expectError(error.InvalidSeed, KeyPair.fromSeed(bad));\n}\n\ntest \"encodeSeed roundtrip with known seed\" {\n    const seed =\n        \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV\" ++\n        \"7NSWFFEW63UXMRLFM2XLAXK4GY\";\n    var kp = try KeyPair.fromSeed(seed);\n    defer kp.wipe();\n\n    var buf: [58]u8 = undefined;\n    const encoded = kp.encodeSeed(&buf);\n    try std.testing.expectEqualStrings(seed, encoded);\n}\n\ntest \"encodeSeed roundtrip with deterministic key\" {\n    const test_seed = [_]u8{1} ** 32;\n    const ed_kp = Ed25519.KeyPair.generateDeterministic(\n        test_seed,\n    ) catch unreachable;\n    const kp = KeyPair{ .kp = ed_kp, .key_type = .user };\n\n    var seed_buf: [58]u8 = undefined;\n    const encoded_seed = kp.encodeSeed(&seed_buf);\n\n    var kp2 = try KeyPair.fromSeed(encoded_seed);\n    defer kp2.wipe();\n\n    var pk1: [56]u8 = undefined;\n    var pk2: [56]u8 = undefined;\n    try std.testing.expectEqualStrings(\n        kp.publicKey(&pk1),\n        kp2.publicKey(&pk2),\n    );\n}\n\ntest \"seed prefix chars per key type\" {\n    const test_seed = [_]u8{42} ** 32;\n    const ed_kp = Ed25519.KeyPair.generateDeterministic(\n        test_seed,\n    ) catch unreachable;\n    var buf: [58]u8 = undefined;\n\n    // User seed starts with SU\n    const user_kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .user,\n    };\n    const user_enc = user_kp.encodeSeed(&buf);\n    try std.testing.expectEqual(@as(u8, 'S'), user_enc[0]);\n    try std.testing.expectEqual(@as(u8, 'U'), user_enc[1]);\n\n    // Account seed starts with SA\n    const acct_kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .account,\n    };\n    const acct_enc = acct_kp.encodeSeed(&buf);\n    try std.testing.expectEqual(@as(u8, 'S'), acct_enc[0]);\n    try std.testing.expectEqual(@as(u8, 'A'), acct_enc[1]);\n\n    // Operator seed starts with SO\n    const op_kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .operator,\n    };\n    const op_enc = op_kp.encodeSeed(&buf);\n    try std.testing.expectEqual(@as(u8, 'S'), op_enc[0]);\n    try std.testing.expectEqual(@as(u8, 'O'), op_enc[1]);\n}\n\ntest \"server and cluster seed prefix roundtrip\" {\n    const test_seed = [_]u8{99} ** 32;\n    const ed_kp = Ed25519.KeyPair.generateDeterministic(\n        test_seed,\n    ) catch unreachable;\n    var buf: [58]u8 = undefined;\n\n    // Server seed starts with SN\n    const srv_kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .server,\n    };\n    const srv_enc = srv_kp.encodeSeed(&buf);\n    try std.testing.expectEqual(@as(u8, 'S'), srv_enc[0]);\n    try std.testing.expectEqual(@as(u8, 'N'), srv_enc[1]);\n    var srv_kp2 = try KeyPair.fromSeed(srv_enc);\n    defer srv_kp2.wipe();\n    try std.testing.expectEqual(\n        KeyType.server,\n        srv_kp2.key_type,\n    );\n\n    // Cluster seed starts with SC\n    const cls_kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .cluster,\n    };\n    const cls_enc = cls_kp.encodeSeed(&buf);\n    try std.testing.expectEqual(@as(u8, 'S'), cls_enc[0]);\n    try std.testing.expectEqual(@as(u8, 'C'), cls_enc[1]);\n    var cls_kp2 = try KeyPair.fromSeed(cls_enc);\n    defer cls_kp2.wipe();\n    try std.testing.expectEqual(\n        KeyType.cluster,\n        cls_kp2.key_type,\n    );\n}\n\ntest \"public key prefix for all key types\" {\n    const test_seed = [_]u8{77} ** 32;\n    const ed_kp = Ed25519.KeyPair.generateDeterministic(\n        test_seed,\n    ) catch unreachable;\n    var pk_buf: [56]u8 = undefined;\n\n    const Expected = struct { kt: KeyType, ch: u8 };\n    const cases = [_]Expected{\n        .{ .kt = .user, .ch = 'U' },\n        .{ .kt = .account, .ch = 'A' },\n        .{ .kt = .operator, .ch = 'O' },\n        .{ .kt = .server, .ch = 'N' },\n        .{ .kt = .cluster, .ch = 'C' },\n    };\n\n    for (cases) |c| {\n        const kp = KeyPair{\n            .kp = ed_kp,\n            .key_type = c.kt,\n        };\n        const pk = kp.publicKey(&pk_buf);\n        try std.testing.expectEqual(c.ch, pk[0]);\n    }\n}\n\ntest \"full crypto chain: encode parse sign verify\" {\n    const test_seed = [_]u8{55} ** 32;\n    const ed_kp = Ed25519.KeyPair.generateDeterministic(\n        test_seed,\n    ) catch unreachable;\n    const kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .user,\n    };\n\n    // Encode seed\n    var seed_buf: [58]u8 = undefined;\n    const encoded_seed = kp.encodeSeed(&seed_buf);\n\n    // Parse back\n    var kp2 = try KeyPair.fromSeed(encoded_seed);\n    defer kp2.wipe();\n\n    // Public keys must match\n    var pk1: [56]u8 = undefined;\n    var pk2: [56]u8 = undefined;\n    try std.testing.expectEqualStrings(\n        kp.publicKey(&pk1),\n        kp2.publicKey(&pk2),\n    );\n\n    // Sign with parsed keypair, verify with original\n    const data = \"test payload for signing\";\n    const sig_bytes = kp2.sign(data);\n    const sig = Ed25519.Signature.fromBytes(sig_bytes);\n    try sig.verify(data, kp.kp.public_key);\n}\n\ntest \"CRC16 corruption detection\" {\n    const test_seed = [_]u8{88} ** 32;\n    const ed_kp = Ed25519.KeyPair.generateDeterministic(\n        test_seed,\n    ) catch unreachable;\n    const kp = KeyPair{\n        .kp = ed_kp,\n        .key_type = .user,\n    };\n\n    var seed_buf: [58]u8 = undefined;\n    const encoded = kp.encodeSeed(&seed_buf);\n\n    // Copy and corrupt one byte in the middle\n    var corrupt: [58]u8 = undefined;\n    @memcpy(corrupt[0..encoded.len], encoded);\n    corrupt[20] = if (corrupt[20] == 'A') 'B' else 'A';\n\n    try std.testing.expectError(\n        error.InvalidChecksum,\n        KeyPair.fromSeed(corrupt[0..encoded.len]),\n    );\n}\n"
  },
  {
    "path": "src/auth.zig",
    "content": "//! Authentication modules for NATS.\n//!\n//! Provides NKey authentication (Ed25519 signatures) and credentials\n//! file parsing for JWT authentication.\n\npub const nkey = @import(\"auth/nkey.zig\");\npub const creds = @import(\"auth/creds.zig\");\npub const jwt = @import(\"auth/jwt.zig\");\npub const base32 = @import(\"auth/base32.zig\");\npub const crc16 = @import(\"auth/crc16.zig\");\n\npub const KeyPair = nkey.KeyPair;\npub const KeyType = nkey.KeyType;\npub const Credentials = creds.Credentials;\npub const parseCredentials = creds.parse;\npub const loadCredentialsFile = creds.loadFile;\npub const formatCredentials = creds.format;\n\ntest {\n    _ = nkey;\n    _ = creds;\n    _ = jwt;\n    _ = base32;\n    _ = crc16;\n}\n"
  },
  {
    "path": "src/connection/errors.zig",
    "content": "//! Connection Errors\n//!\n//! Error types for connection-related failures including authentication,\n//! timeouts, and connection state issues.\n\nconst std = @import(\"std\");\n\n/// Connection-related errors.\npub const Error = error{\n    /// Connection to server was closed unexpectedly.\n    ConnectionClosed,\n    /// Connection attempt timed out.\n    ConnectionTimeout,\n    /// Server refused the connection.\n    ConnectionRefused,\n    /// Authentication with the server failed.\n    AuthenticationFailed,\n    /// Stale connection detected.\n    StaleConnection,\n    /// Server is in lame duck mode and will shut down soon.\n    LameDuckMode,\n    /// TCP_NODELAY socket option failed\n    TcpNoDelayFailed,\n    /// TCP receive buffer option failed\n    TcpRcvBufFailed,\n    /// URL too long\n    UrlTooLong,\n    /// Queue group too long\n    QueueGroupTooLong,\n    /// Subject too long\n    SubjectTooLong,\n};\n\n/// Parses auth-related errors from server -ERR message.\n/// Returns null if the message is not an auth error.\npub fn parseAuthError(msg: []const u8) ?Error {\n    if (std.mem.indexOf(u8, msg, \"Authentication\")) |_| {\n        return error.AuthenticationFailed;\n    }\n    if (std.mem.indexOf(u8, msg, \"Stale Connection\")) |_| {\n        return error.StaleConnection;\n    }\n    return null;\n}\n\n/// Returns true if the error is retryable (connection can be re-established).\npub fn isRetryable(err: Error) bool {\n    return switch (err) {\n        error.ConnectionClosed,\n        error.ConnectionTimeout,\n        error.StaleConnection,\n        => true,\n        else => false,\n    };\n}\n\ntest \"parseAuthError authentication\" {\n    const err = parseAuthError(\"Authentication Timeout\");\n    try std.testing.expectEqual(error.AuthenticationFailed, err.?);\n}\n\ntest \"parseAuthError stale connection\" {\n    const err = parseAuthError(\"Stale Connection\");\n    try std.testing.expectEqual(error.StaleConnection, err.?);\n}\n\ntest \"parseAuthError non-auth\" {\n    const err = parseAuthError(\"Some Other Error\");\n    try std.testing.expectEqual(@as(?Error, null), err);\n}\n\ntest \"isRetryable connection errors\" {\n    try std.testing.expect(isRetryable(error.ConnectionClosed));\n    try std.testing.expect(isRetryable(error.ConnectionTimeout));\n    try std.testing.expect(isRetryable(error.StaleConnection));\n}\n\ntest \"isRetryable non-retryable errors\" {\n    try std.testing.expect(!isRetryable(error.AuthenticationFailed));\n    try std.testing.expect(!isRetryable(error.ConnectionRefused));\n    try std.testing.expect(!isRetryable(error.LameDuckMode));\n}\n"
  },
  {
    "path": "src/connection/events.zig",
    "content": "//! Connection Events\n//!\n//! Event queue for connection lifecycle and message events.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\n\nconst commands = @import(\"../protocol/commands.zig\");\nconst ServerInfo = commands.ServerInfo;\n\n/// Connection events that can be polled by the user.\npub const Event = union(enum) {\n    /// Successfully connected to server.\n    connected: ConnectedInfo,\n\n    /// Disconnected from server.\n    disconnected: DisconnectedInfo,\n\n    /// Received a message.\n    message: MessageInfo,\n\n    /// Received an error from server.\n    server_error: []const u8,\n\n    /// Reconnecting to server.\n    reconnecting: ReconnectingInfo,\n\n    /// Lamport drain started.\n    drain_started,\n\n    /// Lamport drain completed.\n    drain_completed,\n};\n\n/// Information about successful connection.\npub const ConnectedInfo = struct {\n    /// Server information received during handshake.\n    server_id: []const u8,\n    server_name: []const u8,\n    version: []const u8,\n    /// True if this is a reconnection.\n    is_reconnect: bool,\n};\n\n/// Information about disconnection.\npub const DisconnectedInfo = struct {\n    /// Reason for disconnection.\n    reason: DisconnectReason,\n    /// Error message if applicable.\n    error_msg: ?[]const u8,\n};\n\n/// Reasons for disconnection.\npub const DisconnectReason = enum {\n    /// Normal close requested by user.\n    user_close,\n    /// Server closed connection.\n    server_close,\n    /// Network error occurred.\n    network_error,\n    /// Authentication failed.\n    auth_failed,\n    /// Protocol error.\n    protocol_error,\n    /// Connection timeout.\n    timeout,\n};\n\n/// Information about received message.\npub const MessageInfo = struct {\n    /// Message subject.\n    subject: []const u8,\n    /// Subscription ID that matched.\n    sid: u64,\n    /// Optional reply-to subject.\n    reply_to: ?[]const u8,\n    /// Message payload.\n    data: []const u8,\n    /// Header data if present (HMSG).\n    headers: ?[]const u8,\n};\n\n/// Information about reconnection attempt.\npub const ReconnectingInfo = struct {\n    /// Current attempt number.\n    attempt: u32,\n    /// Maximum attempts configured.\n    max_attempts: u32,\n    /// Server being connected to.\n    server: []const u8,\n};\n"
  },
  {
    "path": "src/connection/io_task.zig",
    "content": "//! Background I/O Task for NATS Client\n//!\n//! Async task that handles:\n//! - All socket reads (fillMore)\n//! - Message routing (MSG/HMSG to subscription queues)\n//! - PONG responses to server PING\n//! - Reconnection (including handshake writes)\n//!\n//! Caller context handles:\n//! - PUB, SUB, UNSUB writes\n//! - Client-initiated PING\n//! - Flush operations\n//!\n//! Both contexts share the socket writer via write_mutex.\n//! Runs as async task started by Client.connect().\n\nconst std = @import(\"std\");\nconst builtin = @import(\"builtin\");\nconst posix = std.posix;\nconst native_os = builtin.os.tag;\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\n\nconst Client = @import(\"../Client.zig\");\nconst State = @import(\"state.zig\").State;\nconst protocol = @import(\"../protocol.zig\");\nconst dbg = @import(\"../dbg.zig\");\nconst memory = @import(\"../memory.zig\");\nconst TieredSlab = memory.TieredSlab;\nconst defaults = @import(\"../defaults.zig\");\n\nconst Message = Client.Message;\n\n/// Poll timeout when buffer empty (milliseconds).\n/// Derived from defaults.Poll.timeout_us for configurability.\n/// 0 = busy poll (from timeout_us=0), >=1 otherwise.\nconst POLL_TIMEOUT_MS: i32 = if (defaults.Poll.timeout_us == 0)\n    0\nelse\n    @max(1, @divFloor(defaults.Poll.timeout_us + 999, 1000));\n\nconst Io = std.Io;\n\n/// Gets current monotonic time in nanoseconds.\nfn getNowNs(io: Io) u64 {\n    const ts = Io.Timestamp.now(io, .awake);\n    return @intCast(ts.nanoseconds);\n}\n\n/// Drain return queue - free returned buffers back to slab.\n/// Called periodically from read loop to reclaim memory.\n/// Uses batch pop to reduce atomic operations from N to ceil(N/64).\ninline fn drainReturnQueue(client: *Client) void {\n    const slab = &client.tiered_slab;\n    var batch_buf: [64][]u8 = undefined;\n    while (true) {\n        const count = client.return_queue.popBatch(&batch_buf);\n        if (count == 0) break;\n        for (batch_buf[0..count]) |buf| {\n            slab.free(buf);\n        }\n    }\n}\n\n/// Drain publish ring to socket. Returns false on write error\n/// (caller should continue :outer to retry). Also handles\n/// auto-flush for non-publish writes (SUB, UNSUB).\ninline fn drainPublishRing(client: *Client) bool {\n    if (!client.publish_ring.isEmpty()) {\n        if (State.atomicLoad(&client.state) == .connected) {\n            client.write_mutex.lock(\n                client.io,\n            ) catch return true;\n            if (State.atomicLoad(&client.state) ==\n                .connected)\n            {\n                while (client.publish_ring.peek()) |data| {\n                    client.active_writer.writeAll(\n                        data,\n                    ) catch {\n                        client.write_mutex.unlock(\n                            client.io,\n                        );\n                        return false;\n                    };\n                    client.publish_ring.advance();\n                }\n                // REVIEWED(2025-03): flush catch {} is intentional.\n                // Next write detects failure and triggers reconnect.\n                client.active_writer.flush() catch {};\n                if (client.use_tls) {\n                    client.writer.interface.flush() catch {};\n                }\n            }\n            client.write_mutex.unlock(client.io);\n            _ = client.flush_requested.swap(\n                false,\n                .acquire,\n            );\n        }\n    } else if (client.flush_requested.load(.monotonic)) {\n        // Auto-flush for non-publish writes (SUB, UNSUB)\n        if (client.flush_requested.swap(false, .acquire)) {\n            if (State.atomicLoad(&client.state) ==\n                .connected)\n            {\n                client.write_mutex.lock(\n                    client.io,\n                ) catch return true;\n                defer client.write_mutex.unlock(client.io);\n                if (State.atomicLoad(&client.state) ==\n                    .connected)\n                {\n                    client.active_writer.flush() catch {};\n                    if (client.use_tls) {\n                        client.writer.interface.flush() catch {};\n                    }\n                }\n            }\n        }\n    }\n    return true;\n}\n\n/// Main I/O task entry point. Called via io.async() from connect().\n/// Reader: reads socket, routes MSG, responds to PING with PONG.\n/// Exits cleanly when stream is closed (close then cancel).\npub fn run(client: *Client) void {\n    dbg.print(\"io_task[fd={d}]: STARTED\", .{client.stream.socket.handle});\n    var loop_count: u64 = 0;\n\n    // Health check throttling (100ms interval to avoid hot-loop impact)\n    // Use iteration counter to avoid syscall every loop (~10ms at 1M loops/sec)\n    const health_check_interval_ns: u64 = 100_000_000;\n    var last_health_check_ns: u64 = 0;\n    var health_check_counter: u32 = 0;\n\n    outer: while (true) {\n        if (dbg.enabled) loop_count += 1;\n        // HOT PATH: Exit check - intentionally non-atomic for performance.\n        // Safe because of \"close-then-cancel\" pattern (see module doc).\n        // Stale .closed read causes socket op failure, task exits anyway.\n        if (client.state == .closed) break :outer;\n\n        // Periodic health check (detects stale connections when server killed)\n        // Only check timestamp every N iterations to avoid syscall overhead\n        health_check_counter +%= 1;\n        if (health_check_counter >= defaults.Spin.health_check_iterations) {\n            health_check_counter = 0;\n\n            const now_ns = getNowNs(client.io);\n            if (now_ns - last_health_check_ns >= health_check_interval_ns) {\n                last_health_check_ns = now_ns;\n                if (client.checkHealthAndDetectStale()) {\n                    // Connection stale - trigger disconnect/reconnect\n                    const state = State.atomicLoad(&client.state);\n                    if (client.options.reconnect and state != .closed) {\n                        if (!handleDisconnect(client)) break :outer;\n                        continue :outer;\n                    }\n                    // Reconnect disabled - set closed state before exiting\n                    @atomicStore(State, &client.state, .closed, .release);\n                    client.pushEvent(.{ .closed = {} });\n                    break :outer;\n                }\n            }\n        }\n\n        // Drain ring BEFORE reads to minimize write latency.\n        // Without this, ring drains only after the inner\n        // loop's 1ms poll timeout, starving the producer.\n        if (!drainPublishRing(client)) continue :outer;\n\n        var made_progress = true;\n        while (made_progress) {\n            made_progress = false;\n\n            // Exit inner loop when ring has pending data\n            // so we drain it without blocking in poll().\n            if (!client.publish_ring.isEmpty()) break;\n\n            drainReturnQueue(client);\n\n            const route_result =\n                tryRouteBufferedMessages(client);\n            if (route_result == .progress) {\n                dbg.print(\n                    \"io_task[fd={d}]: routed messages\",\n                    .{client.stream.socket.handle},\n                );\n                made_progress = true;\n            }\n            if (route_result == .disconnected) {\n                const state = State.atomicLoad(\n                    &client.state,\n                );\n                if (client.options.reconnect and\n                    state != .closed)\n                {\n                    if (!handleDisconnect(client))\n                        break :outer;\n                    continue :outer;\n                }\n                @atomicStore(\n                    State,\n                    &client.state,\n                    .closed,\n                    .release,\n                );\n                client.pushEvent(.{ .closed = {} });\n                break :outer;\n            }\n\n            if (!made_progress) {\n                const read_result = tryFillBuffer(client);\n                if (read_result == .progress) {\n                    dbg.print(\n                        \"io_task[fd={d}]: read data\" ++\n                            \" from socket\",\n                        .{client.stream.socket.handle},\n                    );\n                }\n                if (read_result == .canceled)\n                    break :outer;\n                if (read_result == .disconnected) {\n                    const state = State.atomicLoad(\n                        &client.state,\n                    );\n                    if (client.options.reconnect and\n                        state != .closed)\n                    {\n                        if (!handleDisconnect(client))\n                            break :outer;\n                        continue :outer;\n                    }\n                    @atomicStore(\n                        State,\n                        &client.state,\n                        .closed,\n                        .release,\n                    );\n                    client.pushEvent(\n                        .{ .closed = {} },\n                    );\n                    break :outer;\n                }\n                if (read_result == .progress)\n                    made_progress = true;\n            }\n        }\n\n        // Drain ring AFTER reads (catches new entries\n        // produced during the inner loop).\n        if (!drainPublishRing(client)) continue :outer;\n\n        // No progress - yield to allow other threads\n        std.Thread.yield() catch {};\n    }\n    if (dbg.enabled) {\n        const stats = &client.io_task_stats;\n        dbg.print(\n            \"io_task: EXITED loops={d} fill_calls={d} buffered_hits={d} \" ++\n                \"poll_timeouts={d} read_ok={d}\",\n            .{\n                loop_count,\n                stats.fill_calls,\n                stats.fill_buffered_hits,\n                stats.fill_poll_timeouts,\n                stats.fill_read_success,\n            },\n        );\n    }\n}\n\n/// Result of read/route operations.\nconst ReadResult = enum {\n    progress,\n    no_progress,\n    disconnected,\n    canceled,\n};\n\n/// Result of poll() operation for disconnect detection.\nconst PollResult = enum {\n    has_data,\n    no_data,\n    disconnected,\n};\n\n/// Cross-platform socket poll.\n/// std.posix.poll has a Windows codepath that calls\n/// windows.poll which does not exist in this Zig version.\n/// Use WSAPoll directly on Windows.\nfn pollSockets(\n    fds: []posix.pollfd,\n    timeout: i32,\n) posix.PollError!usize {\n    if (native_os == .windows) {\n        const ws2 = std.os.windows.ws2_32;\n        const rc = ws2.WSAPoll(\n            fds.ptr,\n            @intCast(fds.len),\n            timeout,\n        );\n        if (rc == ws2.SOCKET_ERROR)\n            return error.NetworkDown;\n        return @intCast(rc);\n    }\n    return posix.poll(fds, timeout);\n}\n\n/// Poll socket for readable data with timeout (cross-platform).\n/// Also detects disconnect via POLLHUP/POLLERR.\n/// On Linux, POLLIN and POLLHUP can both be set when there's\n/// buffered data AND the connection is closing. POLLHUP is prioritized\n/// to detect dead connections even with buffered data.\ninline fn pollForData(\n    fd: posix.socket_t,\n    timeout_ms: i32,\n) PollResult {\n    var fds = [_]posix.pollfd{.{\n        .fd = fd,\n        .events = posix.POLL.IN,\n        .revents = 0,\n    }};\n    const ready = pollSockets(&fds, timeout_ms) catch return .no_data;\n    if (ready == 0) return .no_data;\n\n    // Single load, combined checks (avoid 3 separate loads)\n    const revents = fds[0].revents;\n    // REVIEWED(2025-03): POLLHUP prioritized over POLLIN intentionally.\n    // Dead connection detection is more important than draining\n    // partial data; reconnect recovers cleanly.\n    if ((revents & (posix.POLL.HUP | posix.POLL.ERR)) != 0)\n        return .disconnected;\n\n    if ((revents & posix.POLL.IN) != 0) return .has_data;\n    return .no_data;\n}\n\n/// Try to fill buffer without blocking forever.\n/// Uses poll() to check for data, then fillMore() to read.\n/// For TLS: loops until we get decrypted data or no more TCP data available.\n/// This handles TLS record fragmentation where a record spans multiple TCP segments.\ninline fn tryFillBuffer(client: *Client) ReadResult {\n    if (dbg.enabled) client.io_task_stats.fill_calls += 1;\n    // HOT PATH: Non-atomic read - see module doc \"State checks (hot path)\"\n    if (client.state == .closed) return .canceled;\n\n    const reader = client.active_reader;\n    const fd = client.stream.socket.handle;\n    const before = reader.buffered().len;\n    if (dbg.enabled) client.io_task_stats.fill_buffered_hits += before;\n\n    // TLS: loop until we decrypt data or truly no more data available.\n    // Key insight: encrypted data may be in TCP reader's buffer (not socket).\n    // After TLS decrypts one record, more encrypted records may remain in the\n    // TCP buffer. poll() only sees the socket, not the TCP reader's buffer!\n    // So we must: 1) check TCP buffer first, 2) only poll if TCP buffer empty.\n    if (client.use_tls) {\n        while (true) {\n            // Atomic read: race with deinit closing socket before fillMore()\n            if (State.atomicLoad(&client.state) == .closed) return .canceled;\n\n            // Check if TCP reader has buffered encrypted data (poll can't see this)\n            const tcp_buffered = client.reader.interface.buffered().len;\n\n            if (tcp_buffered == 0) {\n                // TCP buffer empty - poll socket for more encrypted data\n                const poll_result = pollForData(fd, POLL_TIMEOUT_MS);\n                if (poll_result == .disconnected) return .disconnected;\n                if (poll_result == .no_data) {\n                    if (dbg.enabled)\n                        client.io_task_stats.fill_poll_timeouts += 1;\n                    // No more data anywhere - return what we have\n                    return if (reader.buffered().len > before)\n                        .progress\n                    else\n                        .no_progress;\n                }\n            }\n            // Either TCP buffer has data, or poll said socket has data\n\n            reader.fillMore() catch |err| {\n                if (err == error.Canceled) return .canceled;\n                if (err == error.EndOfStream or\n                    err == error.ConnectionResetByPeer or\n                    err == error.BrokenPipe or\n                    err == error.NotOpenForReading)\n                {\n                    return .disconnected;\n                }\n                // Other errors: check if we made progress before failing\n                return if (reader.buffered().len > before)\n                    .progress\n                else\n                    .no_progress;\n            };\n\n            // Got decrypted data - success\n            if (reader.buffered().len > before) {\n                if (dbg.enabled) client.io_task_stats.fill_read_success += 1;\n                return .progress;\n            }\n            // No decrypted data yet - TLS needs more data, loop\n        }\n    }\n\n    // Non-TLS: simple poll + read\n    const poll_result = pollForData(fd, POLL_TIMEOUT_MS);\n\n    if (poll_result == .disconnected) {\n        return .disconnected;\n    }\n    if (poll_result == .no_data) {\n        if (dbg.enabled) client.io_task_stats.fill_poll_timeouts += 1;\n        return .no_progress;\n    }\n\n    // Atomic read: race with deinit closing socket before fillMore()\n    if (State.atomicLoad(&client.state) == .closed) return .canceled;\n\n    // Socket has data -> fillMore() will return immediately\n    reader.fillMore() catch |err| {\n        if (err == error.Canceled) return .canceled;\n        if (err == error.EndOfStream or\n            err == error.ConnectionResetByPeer or\n            err == error.BrokenPipe or\n            err == error.NotOpenForReading)\n        {\n            return .disconnected;\n        }\n        return .no_progress;\n    };\n\n    const after = reader.buffered().len;\n    if (after > before) {\n        if (dbg.enabled) client.io_task_stats.fill_read_success += 1;\n        return .progress;\n    }\n    return .no_progress;\n}\n\n/// Route buffered messages (no I/O, buffer processing only).\n/// Handles: MSG -> route to queue, PING -> write PONG.\n/// Uses lock-free SpscQueue - no yields needed.\ninline fn tryRouteBufferedMessages(\n    client: *Client,\n) ReadResult {\n    const allocator = client.allocator;\n    const reader = client.active_reader;\n    const slab = &client.tiered_slab;\n\n    // HOT PATH: Non-atomic read - see module doc \"State checks (hot path)\"\n    if (client.state == .closed) return .canceled;\n\n    const data = reader.buffered();\n    if (data.len == 0) return .no_progress;\n\n    var offset: usize = 0;\n    while (offset < data.len) {\n        var consumed: usize = 0;\n        const result = client.parser.parse(\n            allocator,\n            data[offset..],\n            &consumed,\n        ) catch {\n            // Scan to next CRLF for recovery (skip corrupted data)\n            // Uses SIMD on supported platforms\n            if (std.mem.indexOf(u8, data[offset..], \"\\r\\n\")) |crlf_pos| {\n                const bytes_skipped = crlf_pos + 2;\n                offset += bytes_skipped;\n\n                // Track and rate-limit protocol error notifications\n                client.protocol_errors += 1;\n                const msgs_since = client.statistics.msgs_in -|\n                    client.last_parse_error_notified_at;\n                const interval = client.options.error_notify_interval_msgs;\n                if (client.protocol_errors == 1 or msgs_since >= interval) {\n                    client.last_parse_error_notified_at = client.statistics.msgs_in;\n                    client.pushEvent(.{\n                        .protocol_error = .{\n                            .bytes_skipped = bytes_skipped,\n                            .count = client.protocol_errors,\n                        },\n                    });\n                }\n                dbg.print(\n                    \"parse error (#{d}, skipped {d} bytes, rate-limited)\",\n                    .{ client.protocol_errors, bytes_skipped },\n                );\n            } else {\n                break;\n            }\n            continue;\n        };\n\n        if (result) |cmd| {\n            switch (cmd) {\n                .msg => |args| {\n                    routeMessageToSub(client, slab, args);\n                    client.statistics.msgs_in += 1;\n                    client.statistics.bytes_in += args.payload.len;\n                },\n                .hmsg => |args| {\n                    routeHMessageToSub(client, slab, args);\n                    client.statistics.msgs_in += 1;\n                    client.statistics.bytes_in += args.total_len;\n                },\n                .ping => {\n                    client.write_mutex.lock(client.io) catch\n                        return .disconnected;\n                    defer client.write_mutex.unlock(client.io);\n                    client.active_writer.writeAll(\"PONG\\r\\n\") catch {\n                        return .disconnected;\n                    };\n                    client.active_writer.flush() catch return .disconnected;\n                },\n                .pong => {\n                    const now = getNowNs(client.io);\n                    dbg.print(\"Got PONG, storing timestamp={d}\", .{now});\n                    client.pings_outstanding.store(0, .monotonic);\n                    client.last_pong_received_ns.store(now, .release);\n                },\n                .info => |info| {\n                    // REVIEWED(2025-03): server_info replacement\n                    // races with user reads. Risk: user holding a\n                    // slice from serverInfo() getters gets dangling\n                    // pointer when old strings are freed. Window is\n                    // narrow (reconnect only). Locking would add\n                    // overhead to every getter for a rare event.\n                    // x86_64 only; aarch64 risk is strictly worse.\n                    if (client.server_info) |*old| {\n                        old.deinit(allocator);\n                    }\n                    client.server_info = info;\n                    client.max_payload = info.max_payload;\n                    client.parser.max_payload = info.max_payload;\n                },\n                .ok => {},\n                .err => |err_msg| {\n                    if (handleServerError(client, err_msg)) {\n                        return .disconnected;\n                    }\n                },\n            }\n            offset += consumed;\n        } else {\n            break;\n        }\n    }\n\n    if (offset > 0) {\n        reader.toss(offset);\n        return .progress;\n    }\n\n    return .no_progress;\n}\n\n/// Route MSG to subscription queue.\ninline fn routeMessageToSub(\n    client: *Client,\n    slab: *TieredSlab,\n    args: protocol.MsgArgs,\n) void {\n    client.read_mutex.lockUncancelable(client.io);\n    defer client.read_mutex.unlock(client.io);\n\n    dbg.print(\"routeMsg[fd={d}]: sid={d} subject={s}\", .{ client.stream.socket.handle, args.sid, args.subject });\n    const sub = client.getSubscriptionBySid(args.sid) orelse {\n        dbg.print(\"routeMsg[fd={d}]: NO SUB FOUND for sid={d}\", .{ client.stream.socket.handle, args.sid });\n        return;\n    };\n\n    const subj_len = args.subject.len;\n    const payload_len = args.payload.len;\n    const reply_len = if (args.reply_to) |rt| rt.len else 0;\n    const total_size = subj_len + payload_len + reply_len;\n\n    // Bounds verification - assert our arithmetic is correct\n    const subj_end = subj_len;\n    const payload_end = subj_end + payload_len;\n    const reply_end = payload_end + reply_len;\n    assert(reply_end == total_size);\n\n    const buf = slab.alloc(total_size) orelse {\n        sub.alloc_failed_msgs += 1;\n        // Rate-limit: push event on 1st failure OR after interval msgs\n        const msgs_since = client.statistics.msgs_in -| sub.last_alloc_notified_at;\n        const interval = client.options.error_notify_interval_msgs;\n        if (sub.alloc_failed_msgs == 1 or msgs_since >= interval) {\n            sub.last_alloc_notified_at = client.statistics.msgs_in;\n            client.pushEvent(.{\n                .alloc_failed = .{\n                    .sid = args.sid,\n                    .count = sub.alloc_failed_msgs,\n                },\n            });\n        }\n        dbg.print(\n            \"alloc failed sid={d} (#{d}, rate-limited every {d} msgs)\",\n            .{ args.sid, sub.alloc_failed_msgs, interval },\n        );\n        return;\n    };\n\n    @memcpy(buf[0..subj_end], args.subject);\n    @memcpy(buf[subj_end..payload_end], args.payload);\n    if (args.reply_to) |rt| {\n        @memcpy(buf[payload_end..reply_end], rt);\n    }\n\n    const subject = buf[0..subj_end];\n    const data_slice = buf[subj_end..payload_end];\n    const reply_to: ?[]const u8 = if (reply_len > 0)\n        buf[payload_end..reply_end]\n    else\n        null;\n\n    const msg = Message{\n        .subject = subject,\n        .sid = args.sid,\n        .reply_to = reply_to,\n        .data = data_slice,\n        .headers = null,\n        .owned = true,\n        .backing_buf = buf,\n        .return_queue = &client.return_queue,\n        .return_lock = &client.return_lock,\n    };\n\n    sub.pushMessage(msg) catch {\n        dbg.print(\"routeMsg: PUSH FAILED (slow consumer) sid={d}\", .{args.sid});\n        sub.dropped_msgs += 1;\n        slab.free(buf);\n        // REVIEWED(2025-03): Single notification is intentional.\n        // Avoids flooding event queue during slow consumer.\n        // Users monitor sub.dropped_msgs for ongoing counts.\n        if (sub.dropped_msgs == 1) {\n            client.pushEvent(.{ .slow_consumer = .{ .sid = args.sid } });\n        }\n        return;\n    };\n    // REVIEWED(2025-03): Non-atomic stats are safe here.\n    // io_task is the sole writer; user reads after drain.\n    dbg.print(\"routeMsg: pushed to queue, sid={d}\", .{args.sid});\n    sub.received_msgs += 1;\n}\n\n/// Route HMSG to subscription queue.\ninline fn routeHMessageToSub(\n    client: *Client,\n    slab: *TieredSlab,\n    args: protocol.HMsgArgs,\n) void {\n    client.read_mutex.lockUncancelable(client.io);\n    defer client.read_mutex.unlock(client.io);\n\n    const sub = client.getSubscriptionBySid(args.sid) orelse return;\n\n    const subj_len = args.subject.len;\n    const data_len = args.payload.len;\n    const hdr_len = args.headers.len;\n    const reply_len = if (args.reply_to) |rt| rt.len else 0;\n    const total_size = subj_len + data_len + hdr_len + reply_len;\n\n    // Bounds verification - assert our arithmetic is correct\n    const subj_end = subj_len;\n    const data_end = subj_end + data_len;\n    const hdr_end = data_end + hdr_len;\n    const reply_end = hdr_end + reply_len;\n    assert(reply_end == total_size);\n\n    const buf = slab.alloc(total_size) orelse {\n        sub.alloc_failed_msgs += 1;\n        // Rate-limit: push event on 1st failure OR after interval msgs\n        const msgs_since = client.statistics.msgs_in -| sub.last_alloc_notified_at;\n        const interval = client.options.error_notify_interval_msgs;\n        if (sub.alloc_failed_msgs == 1 or msgs_since >= interval) {\n            sub.last_alloc_notified_at = client.statistics.msgs_in;\n            client.pushEvent(.{\n                .alloc_failed = .{\n                    .sid = args.sid,\n                    .count = sub.alloc_failed_msgs,\n                },\n            });\n        }\n        dbg.print(\n            \"alloc failed sid={d} (#{d}, rate-limited every {d} msgs)\",\n            .{ args.sid, sub.alloc_failed_msgs, interval },\n        );\n        return;\n    };\n\n    @memcpy(buf[0..subj_end], args.subject);\n    @memcpy(buf[subj_end..data_end], args.payload);\n    @memcpy(buf[data_end..hdr_end], args.headers);\n    if (args.reply_to) |rt| {\n        @memcpy(buf[hdr_end..reply_end], rt);\n    }\n\n    const subject = buf[0..subj_end];\n    const data_slice = buf[subj_end..data_end];\n    const headers = buf[data_end..hdr_end];\n    const reply_to: ?[]const u8 = if (reply_len > 0)\n        buf[hdr_end..reply_end]\n    else\n        null;\n\n    const msg = Message{\n        .subject = subject,\n        .sid = args.sid,\n        .reply_to = reply_to,\n        .data = data_slice,\n        .headers = headers,\n        .owned = true,\n        .backing_buf = buf,\n        .return_queue = &client.return_queue,\n        .return_lock = &client.return_lock,\n    };\n\n    sub.pushMessage(msg) catch {\n        sub.dropped_msgs += 1;\n        slab.free(buf);\n        if (sub.dropped_msgs == 1) {\n            client.pushEvent(.{ .slow_consumer = .{ .sid = args.sid } });\n        }\n        return;\n    };\n    sub.received_msgs += 1;\n}\n\n/// Handle disconnect - backup subs, attempt reconnection, restore subs.\n/// Returns true if reconnected successfully, false if should exit task.\nfn handleDisconnect(client: *Client) bool {\n    @atomicStore(State, &client.state, .disconnected, .release);\n\n    client.pushEvent(.{ .disconnected = .{ .err = null } });\n\n    client.backupSubscriptions() catch |err| {\n        dbg.print(\"backupSubscriptions failed: {s}\", .{@errorName(err)});\n    };\n\n    // Close old stream before reconnect to prevent FD leak\n    // (matches reconnect() ordering: backup then cleanup)\n    client.cleanupForReconnect();\n\n    if (tryReconnectLoop(client)) {\n        client.restoreSubscriptions() catch {\n            dbg.print(\"Failed to restore subscriptions after reconnect\", .{});\n        };\n\n        client.pushEvent(.{ .reconnected = {} });\n        return true;\n    } else {\n        @atomicStore(State, &client.state, .closed, .release);\n        client.pushEvent(.{ .closed = {} });\n        return false;\n    }\n}\n\n/// Attempt reconnection loop with backoff.\n/// Returns true if reconnected, false if failed or canceled.\nfn tryReconnectLoop(client: *Client) bool {\n    @atomicStore(State, &client.state, .reconnecting, .release);\n    const max_attempts = if (client.options.max_reconnect_attempts == 0)\n        std.math.maxInt(u32)\n    else\n        client.options.max_reconnect_attempts;\n\n    var attempt: u32 = 0;\n    while (attempt < max_attempts) {\n        attempt += 1;\n        client.reconnect_attempt = attempt;\n\n        // Wait with backoff (except first attempt) - cancellation point\n        if (attempt > 1) {\n            const delay_ms = calculateReconnectDelay(client, attempt);\n            client.io.sleep(\n                .fromMilliseconds(delay_ms),\n                .awake,\n            ) catch |err| {\n                if (err == error.Canceled) return false;\n            };\n        }\n\n        for (client.server_pool.servers[0..client.server_pool.count]) |*server| {\n            client.tryConnect(server) catch continue;\n            @atomicStore(State, &client.state, .connected, .release);\n            _ = client.statistics.reconnects.fetchAdd(1, .monotonic);\n            client.reconnect_attempt = 0;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n/// Calculate reconnect delay with exponential backoff and jitter.\n/// If custom_reconnect_delay callback is set, uses that instead.\nfn calculateReconnectDelay(client: *Client, attempt: u32) u32 {\n    assert(attempt > 0);\n\n    // Use custom callback if provided\n    if (client.options.custom_reconnect_delay) |cb| {\n        return cb(attempt);\n    }\n\n    // Exponential backoff: base * 2^(attempt-1), capped at max\n    const base_ms = client.options.reconnect_wait_ms;\n    const max_ms = client.options.reconnect_wait_max_ms;\n    const jitter_pct = client.options.reconnect_jitter_percent;\n\n    // Calculate exponential delay: base * 2^(attempt-2) for attempt > 1\n    // attempt 2 -> base, attempt 3 -> base*2, attempt 4 -> base*4, etc.\n    const shift: u5 = @intCast(@min(attempt -| 2, 30));\n    const exp_delay: u64 = @as(u64, base_ms) << shift;\n    const capped_delay: u32 = @intCast(@min(exp_delay, max_ms));\n\n    // Apply jitter: delay +/- jitter_pct%\n    if (jitter_pct == 0) return capped_delay;\n\n    const jitter_range = (capped_delay * jitter_pct) / 100;\n    if (jitter_range == 0) return capped_delay;\n    var rand_buf: [4]u8 = undefined;\n    client.io.random(&rand_buf);\n    const rand_val = std.mem.readInt(\n        u32,\n        &rand_buf,\n        .little,\n    );\n    const jitter_offset = rand_val % (jitter_range * 2 + 1);\n    const jitter: i64 = @as(i64, jitter_offset) -\n        @as(i64, jitter_range);\n\n    const final_delay: i64 = @as(i64, capped_delay) + jitter;\n    return @intCast(@max(final_delay, 1));\n}\n\n/// Handle server -ERR message. Categorizes error and pushes event.\n/// Also stores as last_error for later retrieval via getLastError().\n/// Returns true if error is fatal (should disconnect), false otherwise.\nfn handleServerError(client: *Client, msg: []const u8) bool {\n    const events = @import(\"../events.zig\");\n\n    // Categorize error (case-insensitive matching like Go/C clients)\n    const err_type: anyerror = blk: {\n        if (containsIgnoreCase(msg, \"authorization\")) {\n            break :blk events.Error.AuthorizationViolation;\n        }\n        if (containsIgnoreCase(msg, \"permissions violation\")) {\n            break :blk events.Error.PermissionViolation;\n        }\n        if (containsIgnoreCase(msg, \"stale connection\")) {\n            break :blk events.Error.StaleConnection;\n        }\n        if (containsIgnoreCase(msg, \"maximum connections\")) {\n            break :blk events.Error.MaxConnectionsExceeded;\n        }\n        break :blk events.Error.ServerError;\n    };\n\n    // REVIEWED(2025-03): last_error written without sync.\n    // Acceptable: errors rare, x86_64 TSO ensures coherent\n    // reads, msg is copied into fixed buffer before use.\n    client.last_error = err_type;\n    if (msg.len < 256) {\n        const len: u8 = @intCast(msg.len);\n        @memcpy(client.last_error_msg[0..len], msg);\n        client.last_error_msg_len = len;\n    } else {\n        // Truncate to fit u8 length field\n        @memcpy(\n            client.last_error_msg[0..255],\n            msg[0..255],\n        );\n        client.last_error_msg_len = 255;\n    }\n\n    // Use already-copied last_error_msg to avoid\n    // dangling pointer into recycled parser buffer\n    const safe_msg = if (client.last_error_msg_len > 0)\n        client.last_error_msg[0..client.last_error_msg_len]\n    else\n        null;\n    client.pushEvent(.{ .err = .{ .err = err_type, .msg = safe_msg } });\n\n    // Fatal errors trigger disconnect/reconnect\n    return err_type == events.Error.AuthorizationViolation or\n        err_type == events.Error.StaleConnection or\n        err_type == events.Error.MaxConnectionsExceeded;\n}\n\n/// Case-insensitive substring search (no allocations).\nfn containsIgnoreCase(haystack: []const u8, needle: []const u8) bool {\n    if (needle.len > haystack.len) return false;\n    var i: usize = 0;\n    while (i <= haystack.len - needle.len) : (i += 1) {\n        var match = true;\n        for (0..needle.len) |j| {\n            const h = haystack[i + j];\n            const n = needle[j];\n            const hl = if (h >= 'A' and h <= 'Z') h + 32 else h;\n            const nl = if (n >= 'A' and n <= 'Z') n + 32 else n;\n            if (hl != nl) {\n                match = false;\n                break;\n            }\n        }\n        if (match) return true;\n    }\n    return false;\n}\n"
  },
  {
    "path": "src/connection/reconnect_test.zig",
    "content": "//! Reconnection Logic Unit Tests\n//!\n//! Tests for subscription backup/restore, backoff calculations,\n//! and pending buffer operations.\n\nconst std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst SubBackup = Client.SubBackup;\n\n// SubBackup Structure Tests\n\ntest \"SubBackup default initialization\" {\n    const backup: SubBackup = .{};\n    try std.testing.expectEqual(@as(u64, 0), backup.sid);\n    try std.testing.expectEqual(@as(u8, 0), backup.subject_len);\n    try std.testing.expectEqual(@as(u8, 0), backup.queue_group_len);\n    try std.testing.expect(backup.max_msgs == null);\n    try std.testing.expectEqual(@as(u64, 0), backup.received_msgs);\n}\n\ntest \"SubBackup getSubject returns correct slice\" {\n    var backup: SubBackup = .{};\n    const subject = \"test.subject.name\";\n    @memcpy(backup.subject_buf[0..subject.len], subject);\n    backup.subject_len = subject.len;\n\n    try std.testing.expectEqualStrings(subject, backup.getSubject());\n}\n\ntest \"SubBackup getSubject empty\" {\n    const backup: SubBackup = .{};\n    try std.testing.expectEqualStrings(\"\", backup.getSubject());\n}\n\ntest \"SubBackup getQueueGroup returns correct slice\" {\n    var backup: SubBackup = .{};\n    const qg = \"my-queue-group\";\n    @memcpy(backup.queue_group_buf[0..qg.len], qg);\n    backup.queue_group_len = qg.len;\n\n    try std.testing.expectEqualStrings(qg, backup.queueGroup().?);\n}\n\ntest \"SubBackup getQueueGroup returns null when empty\" {\n    const backup: SubBackup = .{};\n    try std.testing.expect(backup.queueGroup() == null);\n}\n\ntest \"SubBackup max subject length\" {\n    var backup: SubBackup = .{};\n    // Fill entire buffer\n    @memset(&backup.subject_buf, 'x');\n    backup.subject_len = 255;\n\n    try std.testing.expectEqual(@as(usize, 255), backup.getSubject().len);\n}\n\ntest \"SubBackup max queue group length\" {\n    var backup: SubBackup = .{};\n    @memset(&backup.queue_group_buf, 'q');\n    backup.queue_group_len = 64;\n\n    try std.testing.expectEqual(@as(usize, 64), backup.queueGroup().?.len);\n}\n\ntest \"SubBackup preserves SID\" {\n    var backup: SubBackup = .{};\n    backup.sid = 12345;\n    try std.testing.expectEqual(@as(u64, 12345), backup.sid);\n}\n\ntest \"SubBackup preserves max_msgs\" {\n    var backup: SubBackup = .{};\n    backup.max_msgs = 100;\n    try std.testing.expectEqual(@as(u64, 100), backup.max_msgs.?);\n}\n\ntest \"SubBackup preserves received_msgs\" {\n    var backup: SubBackup = .{};\n    backup.received_msgs = 42;\n    try std.testing.expectEqual(@as(u64, 42), backup.received_msgs);\n}\n\ntest \"SubBackup max SID value\" {\n    var backup: SubBackup = .{};\n    backup.sid = std.math.maxInt(u64);\n    try std.testing.expectEqual(std.math.maxInt(u64), backup.sid);\n}\n\n// Backoff Calculation Tests\n\n// These test the backoff calculation formula:\n// exp_wait = base << attempt (capped at 10)\n// capped = min(exp_wait, max_wait)\n// jitter = +/-(capped * jitter_percent / 100)\n\ntest \"backoff base case\" {\n    const base_ms: u64 = 2000;\n    const attempt: u4 = 0;\n    const exp_wait = base_ms << attempt;\n    try std.testing.expectEqual(@as(u64, 2000), exp_wait);\n}\n\ntest \"backoff exponential growth\" {\n    const base_ms: u64 = 2000;\n\n    try std.testing.expectEqual(@as(u64, 2000), base_ms << @as(u4, 0));\n    try std.testing.expectEqual(@as(u64, 4000), base_ms << @as(u4, 1));\n    try std.testing.expectEqual(@as(u64, 8000), base_ms << @as(u4, 2));\n    try std.testing.expectEqual(@as(u64, 16000), base_ms << @as(u4, 3));\n    try std.testing.expectEqual(@as(u64, 32000), base_ms << @as(u4, 4));\n}\n\ntest \"backoff capped at max\" {\n    const base_ms: u64 = 2000;\n    const max_ms: u64 = 30000;\n\n    // Attempt 5 would be 64000, capped to 30000\n    const exp_wait = base_ms << @as(u4, 5);\n    const capped = @min(exp_wait, max_ms);\n    try std.testing.expectEqual(@as(u64, 30000), capped);\n}\n\ntest \"backoff attempt capped at 10\" {\n    const base_ms: u64 = 2000;\n    const max_attempt: u4 = 10;\n\n    // Attempt 10 = 2000 << 10 = 2,048,000ms\n    const exp_wait = base_ms << max_attempt;\n    try std.testing.expectEqual(@as(u64, 2048000), exp_wait);\n}\n\ntest \"backoff jitter range calculation\" {\n    const capped: u64 = 30000;\n    const jitter_percent: u8 = 10;\n\n    const jitter_range = capped * jitter_percent / 100;\n    try std.testing.expectEqual(@as(u64, 3000), jitter_range);\n}\n\ntest \"backoff jitter bounds\" {\n    const capped: u64 = 30000;\n    const jitter_percent: u8 = 10;\n    const jitter_range = capped * jitter_percent / 100;\n\n    // Jitter should be in range [-3000, +3000]\n    // Min wait = 30000 - 3000 = 27000\n    // Max wait = 30000 + 3000 = 33000\n    const min_wait = capped - jitter_range;\n    const max_wait = capped + jitter_range;\n\n    try std.testing.expectEqual(@as(u64, 27000), min_wait);\n    try std.testing.expectEqual(@as(u64, 33000), max_wait);\n}\n\ntest \"backoff zero jitter\" {\n    const capped: u64 = 30000;\n    const jitter_percent: u8 = 0;\n    const jitter_range = capped * jitter_percent / 100;\n\n    try std.testing.expectEqual(@as(u64, 0), jitter_range);\n}\n\ntest \"backoff max jitter 50 percent\" {\n    const capped: u64 = 30000;\n    const jitter_percent: u8 = 50;\n    const jitter_range = capped * jitter_percent / 100;\n\n    try std.testing.expectEqual(@as(u64, 15000), jitter_range);\n}\n\n// Reconnection Options Tests\n\ntest \"default reconnection options\" {\n    const opts: Client.Options = .{};\n\n    try std.testing.expect(opts.reconnect);\n    try std.testing.expectEqual(@as(u32, 60), opts.max_reconnect_attempts);\n    try std.testing.expectEqual(@as(u32, 2000), opts.reconnect_wait_ms);\n    try std.testing.expectEqual(@as(u32, 30000), opts.reconnect_wait_max_ms);\n    try std.testing.expectEqual(@as(u8, 10), opts.reconnect_jitter_percent);\n    try std.testing.expect(opts.discover_servers);\n    try std.testing.expectEqual(@as(usize, 8 * 1024 * 1024), opts.pending_buffer_size);\n}\n\ntest \"disable reconnection\" {\n    const opts: Client.Options = .{ .reconnect = false };\n    try std.testing.expect(!opts.reconnect);\n}\n\ntest \"infinite reconnect attempts\" {\n    const opts: Client.Options = .{ .max_reconnect_attempts = 0 };\n    try std.testing.expectEqual(@as(u32, 0), opts.max_reconnect_attempts);\n}\n\ntest \"custom reconnect timing\" {\n    const opts: Client.Options = .{\n        .reconnect_wait_ms = 500,\n        .reconnect_wait_max_ms = 10000,\n        .reconnect_jitter_percent = 25,\n    };\n\n    try std.testing.expectEqual(@as(u32, 500), opts.reconnect_wait_ms);\n    try std.testing.expectEqual(@as(u32, 10000), opts.reconnect_wait_max_ms);\n    try std.testing.expectEqual(@as(u8, 25), opts.reconnect_jitter_percent);\n}\n\ntest \"disable pending buffer\" {\n    const opts: Client.Options = .{ .pending_buffer_size = 0 };\n    try std.testing.expectEqual(@as(usize, 0), opts.pending_buffer_size);\n}\n\ntest \"custom pending buffer size\" {\n    const opts: Client.Options = .{ .pending_buffer_size = 1024 * 1024 };\n    try std.testing.expectEqual(@as(usize, 1024 * 1024), opts.pending_buffer_size);\n}\n\n// Health Check Options Tests\n\ntest \"default health check options\" {\n    const opts: Client.Options = .{};\n\n    try std.testing.expectEqual(@as(u32, 120000), opts.ping_interval_ms);\n    try std.testing.expectEqual(@as(u8, 2), opts.max_pings_outstanding);\n}\n\ntest \"disable health check\" {\n    const opts: Client.Options = .{ .ping_interval_ms = 0 };\n    try std.testing.expectEqual(@as(u32, 0), opts.ping_interval_ms);\n}\n\ntest \"aggressive health check\" {\n    const opts: Client.Options = .{\n        .ping_interval_ms = 1000,\n        .max_pings_outstanding = 1,\n    };\n\n    try std.testing.expectEqual(@as(u32, 1000), opts.ping_interval_ms);\n    try std.testing.expectEqual(@as(u8, 1), opts.max_pings_outstanding);\n}\n\n// Stats Tests\n\ntest \"stats default reconnects zero\" {\n    const stats: Client.Statistics = .{};\n    try std.testing.expectEqual(@as(u32, 0), stats.reconnects);\n}\n\n// Subscription Remaining Messages Calculation Tests\n\ntest \"remaining messages calculation\" {\n    // Test the -| saturating subtraction pattern used in restoreSubscriptions\n    const max_msgs: u64 = 100;\n    const received: u64 = 30;\n    const remaining = max_msgs -| received;\n    try std.testing.expectEqual(@as(u64, 70), remaining);\n}\n\ntest \"remaining messages at zero\" {\n    const max_msgs: u64 = 100;\n    const received: u64 = 100;\n    const remaining = max_msgs -| received;\n    try std.testing.expectEqual(@as(u64, 0), remaining);\n}\n\ntest \"remaining messages saturates\" {\n    const max_msgs: u64 = 100;\n    const received: u64 = 150; // More than max (shouldn't happen but test anyway)\n    const remaining = max_msgs -| received;\n    try std.testing.expectEqual(@as(u64, 0), remaining);\n}\n\ntest \"remaining messages max values\" {\n    const max_msgs: u64 = std.math.maxInt(u64);\n    const received: u64 = 1;\n    const remaining = max_msgs -| received;\n    try std.testing.expectEqual(std.math.maxInt(u64) - 1, remaining);\n}\n\n// Pending Buffer Size Estimation Tests\n\n// Tests for the size estimation: \"PUB subject len\\r\\npayload\\r\\n\"\n// encoded_size = 4 + subject.len + 1 + 10 + 2 + payload.len + 2\n\ntest \"pending buffer size estimation minimal\" {\n    const subject = \"x\";\n    const payload = \"\";\n    // \"PUB x 0\\r\\n\\r\\n\" = 4 + 1 + 1 + 10 + 2 + 0 + 2 = 20 (estimate)\n    const estimate = 4 + subject.len + 1 + 10 + 2 + payload.len + 2;\n    try std.testing.expectEqual(@as(usize, 19), estimate);\n}\n\ntest \"pending buffer size estimation typical\" {\n    const subject = \"my.test.subject\";\n    const payload = \"hello world\";\n    const estimate = 4 + subject.len + 1 + 10 + 2 + payload.len + 2;\n    try std.testing.expectEqual(@as(usize, 45), estimate);\n}\n\ntest \"pending buffer size estimation large payload\" {\n    const subject = \"data.stream\";\n    const payload_size: usize = 1024 * 1024; // 1MB\n    const estimate = 4 + subject.len + 1 + 10 + 2 + payload_size + 2;\n    try std.testing.expectEqual(@as(usize, 1048606), estimate);\n}\n\n// Multiple Backup Array Tests\n\ntest \"backup array initialization\" {\n    const backups = [_]SubBackup{.{}} ** Client.MAX_SUBSCRIPTIONS;\n    try std.testing.expectEqual(Client.MAX_SUBSCRIPTIONS, backups.len);\n\n    // All should be zeroed\n    for (backups) |backup| {\n        try std.testing.expectEqual(@as(u64, 0), backup.sid);\n        try std.testing.expectEqual(@as(u8, 0), backup.subject_len);\n    }\n}\n\ntest \"backup array modification\" {\n    var backups = [_]SubBackup{.{}} ** 4;\n\n    backups[0].sid = 1;\n    backups[1].sid = 2;\n    backups[2].sid = 3;\n    backups[3].sid = 4;\n\n    try std.testing.expectEqual(@as(u64, 1), backups[0].sid);\n    try std.testing.expectEqual(@as(u64, 2), backups[1].sid);\n    try std.testing.expectEqual(@as(u64, 3), backups[2].sid);\n    try std.testing.expectEqual(@as(u64, 4), backups[3].sid);\n}\n\n// Edge Case Tests\n\ntest \"subject with special characters in backup\" {\n    var backup: SubBackup = .{};\n    const subject = \"test.*.>\";\n    @memcpy(backup.subject_buf[0..subject.len], subject);\n    backup.subject_len = subject.len;\n\n    try std.testing.expectEqualStrings(\"test.*.>\", backup.getSubject());\n}\n\ntest \"queue group with hyphens and numbers\" {\n    var backup: SubBackup = .{};\n    const qg = \"worker-group-123\";\n    @memcpy(backup.queue_group_buf[0..qg.len], qg);\n    backup.queue_group_len = qg.len;\n\n    try std.testing.expectEqualStrings(qg, backup.queueGroup().?);\n}\n\ntest \"backup with all fields populated\" {\n    var backup: SubBackup = .{};\n\n    backup.sid = 42;\n\n    const subject = \"orders.*.shipped\";\n    @memcpy(backup.subject_buf[0..subject.len], subject);\n    backup.subject_len = subject.len;\n\n    const qg = \"processors\";\n    @memcpy(backup.queue_group_buf[0..qg.len], qg);\n    backup.queue_group_len = qg.len;\n\n    backup.max_msgs = 1000;\n    backup.received_msgs = 500;\n\n    try std.testing.expectEqual(@as(u64, 42), backup.sid);\n    try std.testing.expectEqualStrings(subject, backup.getSubject());\n    try std.testing.expectEqualStrings(qg, backup.queueGroup().?);\n    try std.testing.expectEqual(@as(u64, 1000), backup.max_msgs.?);\n    try std.testing.expectEqual(@as(u64, 500), backup.received_msgs);\n}\n"
  },
  {
    "path": "src/connection/server_pool.zig",
    "content": "//! Server Pool for Reconnection\n//!\n//! Manages multiple servers for reconnection with round-robin rotation.\n//! Servers are discovered from initial URL and INFO connect_urls.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst defaults = @import(\"../defaults.zig\");\n\n/// Maximum number of servers in the pool.\npub const MAX_SERVERS: u8 = defaults.Server.max_pool_size;\n\n/// Maximum URL length.\npub const MAX_URL_LEN: u16 = defaults.Server.max_url_len;\n\n/// Cooldown period after failure before retry (ns).\nconst FAILURE_COOLDOWN_NS: u64 = defaults.Server.failure_cooldown_ns;\n\n/// Server entry in the pool.\npub const Server = struct {\n    url: [MAX_URL_LEN]u8 = undefined,\n    url_len: u8 = 0,\n    host_start: u8 = 0,\n    host_len: u8 = 0,\n    port: u16 = defaults.Protocol.port,\n    consecutive_failures: u8 = 0,\n    last_attempt_ns: u64 = 0,\n    /// Whether this server uses TLS (from tls:// scheme).\n    use_tls: bool = false,\n\n    /// Get the URL as a slice.\n    pub fn getUrl(self: *const Server) []const u8 {\n        return self.url[0..self.url_len];\n    }\n\n    /// Get the host as a slice.\n    pub fn getHost(self: *const Server) []const u8 {\n        return self.url[self.host_start..][0..self.host_len];\n    }\n};\n\n/// Server pool for reconnection rotation.\npub const ServerPool = struct {\n    servers: [MAX_SERVERS]Server = undefined,\n    count: u8 = 0,\n    current_idx: u8 = 0,\n    primary_idx: u8 = 0,\n\n    /// Initialize server pool with primary server URL.\n    pub fn init(primary_url: []const u8) error{InvalidUrl}!ServerPool {\n        var pool: ServerPool = .{};\n\n        if (primary_url.len == 0 or primary_url.len >= MAX_URL_LEN) {\n            return error.InvalidUrl;\n        }\n\n        pool.addServer(primary_url) catch return error.InvalidUrl;\n        pool.primary_idx = 0;\n\n        assert(pool.count > 0);\n        return pool;\n    }\n\n    /// Add a server to the pool. Returns false if pool is full or URL invalid.\n    pub fn addServer(self: *ServerPool, url: []const u8) !void {\n        if (self.count >= MAX_SERVERS) return error.PoolFull;\n        if (url.len == 0 or url.len >= MAX_URL_LEN) return error.InvalidUrl;\n\n        for (self.servers[0..self.count]) |*existing| {\n            if (std.mem.eql(u8, existing.getUrl(), url)) {\n                return;\n            }\n        }\n\n        var server: Server = .{};\n\n        const url_len: u8 = @intCast(url.len);\n        @memcpy(server.url[0..url_len], url);\n        server.url_len = url_len;\n\n        var remaining = url;\n\n        if (std.mem.startsWith(u8, remaining, \"tls://\")) {\n            remaining = remaining[6..];\n            server.host_start = 6;\n            server.use_tls = true;\n        } else if (std.mem.startsWith(u8, remaining, \"nats://\")) {\n            remaining = remaining[7..];\n            server.host_start = 7;\n        }\n\n        if (std.mem.indexOf(u8, remaining, \"@\")) |at_pos| {\n            remaining = remaining[at_pos + 1 ..];\n            server.host_start += @intCast(at_pos + 1);\n        }\n\n        if (remaining.len > 0 and remaining[0] == '[') {\n            // IPv6 literal: [::1]:port\n            if (std.mem.indexOf(\n                u8,\n                remaining,\n                \"]\",\n            )) |bracket_end| {\n                server.host_start += 1; // skip '['\n                server.host_len = @intCast(\n                    bracket_end - 1,\n                );\n                const after = remaining[bracket_end + 1 ..];\n                if (after.len > 1 and after[0] == ':') {\n                    server.port = std.fmt.parseInt(\n                        u16,\n                        after[1..],\n                        10,\n                    ) catch 4222;\n                }\n            } else {\n                // Malformed IPv6 — treat as host\n                server.host_len = @intCast(\n                    remaining.len,\n                );\n            }\n        } else if (std.mem.indexOf(\n            u8,\n            remaining,\n            \":\",\n        )) |colon_pos| {\n            if (colon_pos > 255) return error.InvalidUrl;\n            server.host_len = @intCast(colon_pos);\n            server.port = std.fmt.parseInt(\n                u16,\n                remaining[colon_pos + 1 ..],\n                10,\n            ) catch 4222;\n        } else {\n            if (remaining.len > 255)\n                return error.InvalidUrl;\n            server.host_len = @intCast(remaining.len);\n            server.port = 4222;\n        }\n\n        assert(server.host_len > 0);\n        assert(server.port > 0);\n\n        self.servers[self.count] = server;\n        self.count += 1;\n    }\n\n    /// Add servers from ServerInfo connect_urls.\n    /// Returns the number of new servers that were added (not duplicates).\n    pub fn addFromConnectUrls(\n        self: *ServerPool,\n        urls: []const [256]u8,\n        lens: []const u8,\n        count: u8,\n    ) u8 {\n        assert(urls.len >= count);\n        assert(lens.len >= count);\n\n        const before = self.count;\n        for (0..count) |i| {\n            const len = lens[i];\n            if (len == 0) continue;\n            const url = urls[i][0..len];\n            self.addServer(url) catch continue;\n        }\n        return self.count - before;\n    }\n\n    /// Get next server for connection attempt (round-robin).\n    /// Skips servers that failed recently (cooldown).\n    /// Returns null if all servers are on cooldown.\n    pub fn nextServer(self: *ServerPool, now_ns: u64) ?*Server {\n        if (self.count == 0) return null;\n\n        assert(self.count > 0);\n        assert(self.current_idx < self.count);\n\n        var attempts: u8 = 0;\n        while (attempts < self.count) : (attempts += 1) {\n            self.current_idx = (self.current_idx + 1) % self.count;\n            var server = &self.servers[self.current_idx];\n\n            if (server.consecutive_failures > 0) {\n                const cooldown = FAILURE_COOLDOWN_NS *\n                    @as(u64, server.consecutive_failures);\n                if (now_ns - server.last_attempt_ns < cooldown) {\n                    continue;\n                }\n            }\n\n            server.last_attempt_ns = now_ns;\n            return server;\n        }\n\n        return null;\n    }\n\n    /// Mark current server as failed.\n    pub fn markCurrentFailed(self: *ServerPool) void {\n        if (self.count == 0) return;\n        assert(self.current_idx < self.count);\n\n        var server = &self.servers[self.current_idx];\n        if (server.consecutive_failures < 255) {\n            server.consecutive_failures += 1;\n        }\n    }\n\n    /// Reset all failure counts (called on successful connect).\n    pub fn resetFailures(self: *ServerPool) void {\n        for (self.servers[0..self.count]) |*server| {\n            server.consecutive_failures = 0;\n        }\n    }\n\n    /// Get current server URL as slice.\n    pub fn currentUrl(self: *const ServerPool) []const u8 {\n        if (self.count == 0) return \"none\";\n        assert(self.current_idx < self.count);\n        return self.servers[self.current_idx].getUrl();\n    }\n\n    /// Get current server.\n    pub fn current(self: *ServerPool) ?*Server {\n        if (self.count == 0) return null;\n        assert(self.current_idx < self.count);\n        return &self.servers[self.current_idx];\n    }\n\n    /// Get server count.\n    pub fn serverCount(self: *const ServerPool) u8 {\n        return self.count;\n    }\n};\n\ntest \"server pool init\" {\n    const pool = try ServerPool.init(\"nats://localhost:4222\");\n    try std.testing.expectEqual(@as(u8, 1), pool.count);\n    try std.testing.expectEqualStrings(\n        \"nats://localhost:4222\",\n        pool.servers[0].getUrl(),\n    );\n    try std.testing.expectEqualStrings(\"localhost\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"server pool init with auth\" {\n    const pool = try ServerPool.init(\"nats://user:pass@localhost:4222\");\n    try std.testing.expectEqual(@as(u8, 1), pool.count);\n    try std.testing.expectEqualStrings(\"localhost\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"server pool init without port\" {\n    const pool = try ServerPool.init(\"nats://localhost\");\n    try std.testing.expectEqual(@as(u8, 1), pool.count);\n    try std.testing.expectEqualStrings(\"localhost\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"server pool init without scheme\" {\n    const pool = try ServerPool.init(\"localhost:4222\");\n    try std.testing.expectEqual(@as(u8, 1), pool.count);\n    try std.testing.expectEqualStrings(\"localhost\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"server pool add servers\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n    try pool.addServer(\"nats://server3:4222\");\n\n    try std.testing.expectEqual(@as(u8, 3), pool.count);\n}\n\ntest \"server pool deduplication\" {\n    var pool = try ServerPool.init(\"nats://localhost:4222\");\n    try pool.addServer(\"nats://localhost:4222\"); // Duplicate\n    try pool.addServer(\"nats://localhost:4222\"); // Duplicate\n\n    try std.testing.expectEqual(@as(u8, 1), pool.count);\n}\n\ntest \"server pool rotation\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n    try pool.addServer(\"nats://server3:4222\");\n\n    const now: u64 = 1000000000000;\n\n    // Should rotate through servers\n    const s1 = pool.nextServer(now).?;\n    try std.testing.expectEqualStrings(\"nats://server2:4222\", s1.getUrl());\n\n    const s2 = pool.nextServer(now).?;\n    try std.testing.expectEqualStrings(\"nats://server3:4222\", s2.getUrl());\n\n    const s3 = pool.nextServer(now).?;\n    try std.testing.expectEqualStrings(\"nats://server1:4222\", s3.getUrl());\n}\n\ntest \"server pool failure tracking\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n\n    var now: u64 = 1000000000000;\n\n    // Get server and mark as failed\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n\n    try std.testing.expectEqual(@as(u8, 1), pool.servers[0].consecutive_failures);\n\n    // Should be on cooldown\n    now += 1000000000; // +1 second (cooldown is 5 seconds)\n    try std.testing.expect(pool.nextServer(now) == null);\n\n    // After cooldown, should be available\n    now += 10000000000; // +10 seconds\n    try std.testing.expect(pool.nextServer(now) != null);\n}\n\ntest \"server pool reset failures\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n\n    _ = pool.nextServer(0);\n    pool.markCurrentFailed();\n    pool.markCurrentFailed();\n\n    try std.testing.expectEqual(@as(u8, 2), pool.servers[0].consecutive_failures);\n\n    pool.resetFailures();\n\n    try std.testing.expectEqual(@as(u8, 0), pool.servers[0].consecutive_failures);\n}\n\ntest \"server pool empty url\" {\n    const result = ServerPool.init(\"\");\n    try std.testing.expectError(error.InvalidUrl, result);\n}\n\ntest \"server pool tls scheme\" {\n    const pool = try ServerPool.init(\"tls://secure.example.com:4222\");\n    try std.testing.expectEqual(@as(u8, 1), pool.count);\n    try std.testing.expectEqualStrings(\"secure.example.com\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n    try std.testing.expect(pool.servers[0].use_tls);\n}\n\ntest \"server pool nats scheme not tls\" {\n    const pool = try ServerPool.init(\"nats://localhost:4222\");\n    try std.testing.expect(!pool.servers[0].use_tls);\n}\n\ntest \"server pool mixed schemes\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"tls://server2:4222\");\n\n    try std.testing.expectEqual(@as(u8, 2), pool.count);\n    try std.testing.expect(!pool.servers[0].use_tls);\n    try std.testing.expect(pool.servers[1].use_tls);\n}\n"
  },
  {
    "path": "src/connection/server_pool_test.zig",
    "content": "//! Server Pool Tests\n//!\n//! Unit tests for ServerPool including edge cases,\n//! failure tracking, cooldown behavior, and URL parsing.\n\nconst std = @import(\"std\");\nconst ServerPool = @import(\"server_pool.zig\").ServerPool;\nconst Server = @import(\"server_pool.zig\").Server;\nconst MAX_SERVERS = @import(\"server_pool.zig\").MAX_SERVERS;\nconst MAX_URL_LEN = @import(\"server_pool.zig\").MAX_URL_LEN;\n\n// URL Parsing Tests\n\ntest \"parse URL with IPv4 address\" {\n    const pool = try ServerPool.init(\"nats://192.168.1.100:4222\");\n    try std.testing.expectEqualStrings(\"192.168.1.100\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"parse URL with different port\" {\n    const pool = try ServerPool.init(\"nats://localhost:5222\");\n    try std.testing.expectEqual(@as(u16, 5222), pool.servers[0].port);\n}\n\ntest \"parse URL with max port\" {\n    const pool = try ServerPool.init(\"nats://localhost:65535\");\n    try std.testing.expectEqual(@as(u16, 65535), pool.servers[0].port);\n}\n\ntest \"parse URL with port 1\" {\n    const pool = try ServerPool.init(\"nats://localhost:1\");\n    try std.testing.expectEqual(@as(u16, 1), pool.servers[0].port);\n}\n\ntest \"parse URL with invalid port uses default\" {\n    const pool = try ServerPool.init(\"nats://localhost:invalid\");\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"parse URL with port overflow uses default\" {\n    const pool = try ServerPool.init(\"nats://localhost:99999\");\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"parse URL with user only\" {\n    const pool = try ServerPool.init(\"nats://user@localhost:4222\");\n    try std.testing.expectEqualStrings(\"localhost\", pool.servers[0].getHost());\n}\n\ntest \"parse URL with complex auth\" {\n    const pool = try ServerPool.init(\"nats://user:p@ss:word@localhost:4222\");\n    try std.testing.expectEqualStrings(\"localhost\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"parse URL with just host no scheme no port\" {\n    const pool = try ServerPool.init(\"myserver\");\n    try std.testing.expectEqualStrings(\"myserver\", pool.servers[0].getHost());\n    try std.testing.expectEqual(@as(u16, 4222), pool.servers[0].port);\n}\n\ntest \"parse URL preserves original\" {\n    const original = \"nats://demo.nats.io:4222\";\n    const pool = try ServerPool.init(original);\n    try std.testing.expectEqualStrings(original, pool.servers[0].getUrl());\n}\n\ntest \"pool starts empty after primary\" {\n    const pool = try ServerPool.init(\"nats://localhost:4222\");\n    try std.testing.expectEqual(@as(u8, 1), pool.serverCount());\n}\n\ntest \"pool can hold MAX_SERVERS\" {\n    var pool = try ServerPool.init(\"nats://server0:4222\");\n\n    var i: u8 = 1;\n    while (i < MAX_SERVERS) : (i += 1) {\n        var buf: [32]u8 = undefined;\n        const url = std.fmt.bufPrint(&buf, \"nats://server{d}:4222\", .{i}) catch\n            unreachable;\n        try pool.addServer(url);\n    }\n\n    try std.testing.expectEqual(MAX_SERVERS, pool.serverCount());\n}\n\ntest \"pool full returns error\" {\n    var pool = try ServerPool.init(\"nats://server0:4222\");\n\n    var i: u8 = 1;\n    while (i < MAX_SERVERS) : (i += 1) {\n        var buf: [32]u8 = undefined;\n        const url = std.fmt.bufPrint(&buf, \"nats://server{d}:4222\", .{i}) catch\n            unreachable;\n        try pool.addServer(url);\n    }\n\n    const result = pool.addServer(\"nats://overflow:4222\");\n    try std.testing.expectError(error.PoolFull, result);\n}\n\ntest \"URL too long returns error\" {\n    var long_url: [MAX_URL_LEN + 10]u8 = undefined;\n    @memset(&long_url, 'a');\n    const result = ServerPool.init(&long_url);\n    try std.testing.expectError(error.InvalidUrl, result);\n}\n\ntest \"URL exactly max length succeeds\" {\n    var url: [MAX_URL_LEN]u8 = undefined;\n    @memset(&url, 'a');\n    @memcpy(url[0..7], \"server:\");\n    const pool = try ServerPool.init(&url);\n    try std.testing.expectEqual(@as(u8, 1), pool.serverCount());\n}\n\ntest \"exact duplicate rejected\" {\n    var pool = try ServerPool.init(\"nats://localhost:4222\");\n    try pool.addServer(\"nats://localhost:4222\");\n    try std.testing.expectEqual(@as(u8, 1), pool.serverCount());\n}\n\ntest \"different port not duplicate\" {\n    var pool = try ServerPool.init(\"nats://localhost:4222\");\n    try pool.addServer(\"nats://localhost:4223\");\n    try std.testing.expectEqual(@as(u8, 2), pool.serverCount());\n}\n\ntest \"different host not duplicate\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n    try std.testing.expectEqual(@as(u8, 2), pool.serverCount());\n}\n\ntest \"case sensitive URLs\" {\n    var pool = try ServerPool.init(\"nats://Server1:4222\");\n    try pool.addServer(\"nats://server1:4222\");\n    try std.testing.expectEqual(@as(u8, 2), pool.serverCount());\n}\n\ntest \"rotation starts from second server\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n    try pool.addServer(\"nats://server3:4222\");\n\n    const now: u64 = 1_000_000_000_000;\n\n    const s1 = pool.nextServer(now).?;\n    try std.testing.expectEqualStrings(\"nats://server2:4222\", s1.getUrl());\n}\n\ntest \"rotation wraps around\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n\n    const now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now); // server2\n    _ = pool.nextServer(now); // server1\n    const s3 = pool.nextServer(now).?; // server2 again\n    try std.testing.expectEqualStrings(\"nats://server2:4222\", s3.getUrl());\n}\n\ntest \"single server rotation returns same\" {\n    var pool = try ServerPool.init(\"nats://only:4222\");\n\n    const now: u64 = 1_000_000_000_000;\n\n    const s1 = pool.nextServer(now).?;\n    const s2 = pool.nextServer(now).?;\n    const s3 = pool.nextServer(now).?;\n\n    try std.testing.expectEqualStrings(\"nats://only:4222\", s1.getUrl());\n    try std.testing.expectEqualStrings(\"nats://only:4222\", s2.getUrl());\n    try std.testing.expectEqualStrings(\"nats://only:4222\", s3.getUrl());\n}\n\n// Failure Tracking Tests\n\ntest \"failure count increments\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    const now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n    try std.testing.expectEqual(@as(u8, 0), pool.servers[0].consecutive_failures);\n\n    pool.markCurrentFailed();\n    try std.testing.expectEqual(@as(u8, 1), pool.servers[0].consecutive_failures);\n\n    pool.markCurrentFailed();\n    try std.testing.expectEqual(@as(u8, 2), pool.servers[0].consecutive_failures);\n}\n\ntest \"failure count saturates at 255\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    const now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n\n    var i: u16 = 0;\n    while (i < 300) : (i += 1) {\n        pool.markCurrentFailed();\n    }\n\n    try std.testing.expectEqual(@as(u8, 255), pool.servers[0].consecutive_failures);\n}\n\ntest \"reset failures clears all\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n\n    var now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n    pool.markCurrentFailed();\n\n    now += 100_000_000_000;\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n\n    pool.resetFailures();\n\n    try std.testing.expectEqual(@as(u8, 0), pool.servers[0].consecutive_failures);\n    try std.testing.expectEqual(@as(u8, 0), pool.servers[1].consecutive_failures);\n}\n\ntest \"cooldown increases with failures\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    var now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n\n    now += 4_000_000_000;\n    try std.testing.expect(pool.nextServer(now) == null);\n\n    now += 2_000_000_000;\n    try std.testing.expect(pool.nextServer(now) != null);\n    pool.markCurrentFailed();\n\n    now += 8_000_000_000;\n    try std.testing.expect(pool.nextServer(now) == null);\n\n    now += 4_000_000_000;\n    try std.testing.expect(pool.nextServer(now) != null);\n}\n\ntest \"all servers on cooldown returns null\" {\n    var pool = try ServerPool.init(\"nats://server1:4222\");\n    try pool.addServer(\"nats://server2:4222\");\n\n    var now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n\n    now += 1_000_000_000;\n    try std.testing.expect(pool.nextServer(now) == null);\n}\n\ntest \"cooldown expires allows retry\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    var now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n\n    now += 6_000_000_000;\n    try std.testing.expect(pool.nextServer(now) != null);\n}\n\ntest \"healthy server chosen over failed\" {\n    var pool = try ServerPool.init(\"nats://failed:4222\");\n    try pool.addServer(\"nats://healthy:4222\");\n\n    const now: u64 = 1_000_000_000_000;\n\n    _ = pool.nextServer(now);\n    pool.markCurrentFailed();\n\n    const server = pool.nextServer(now).?;\n    try std.testing.expectEqualStrings(\"nats://healthy:4222\", server.getUrl());\n}\n\ntest \"add from connect_urls\" {\n    var pool = try ServerPool.init(\"nats://primary:4222\");\n\n    var urls: [16][256]u8 = undefined;\n    var lens: [16]u8 = [_]u8{0} ** 16;\n\n    const url1 = \"nats://cluster1:4222\";\n    const url2 = \"nats://cluster2:4222\";\n\n    @memcpy(urls[0][0..url1.len], url1);\n    lens[0] = url1.len;\n\n    @memcpy(urls[1][0..url2.len], url2);\n    lens[1] = url2.len;\n\n    pool.addFromConnectUrls(&urls, &lens, 2);\n\n    try std.testing.expectEqual(@as(u8, 3), pool.serverCount());\n}\n\ntest \"add from connect_urls skips empty\" {\n    var pool = try ServerPool.init(\"nats://primary:4222\");\n\n    var urls: [16][256]u8 = undefined;\n    var lens: [16]u8 = [_]u8{0} ** 16;\n\n    const url1 = \"nats://cluster1:4222\";\n    @memcpy(urls[0][0..url1.len], url1);\n    lens[0] = url1.len;\n    lens[2] = 0;\n\n    pool.addFromConnectUrls(&urls, &lens, 3);\n\n    try std.testing.expectEqual(@as(u8, 2), pool.serverCount());\n}\n\ntest \"add from connect_urls deduplicates\" {\n    var pool = try ServerPool.init(\"nats://primary:4222\");\n\n    var urls: [16][256]u8 = undefined;\n    var lens: [16]u8 = [_]u8{0} ** 16;\n\n    const url1 = \"nats://primary:4222\";\n    @memcpy(urls[0][0..url1.len], url1);\n    lens[0] = url1.len;\n\n    pool.addFromConnectUrls(&urls, &lens, 1);\n\n    try std.testing.expectEqual(@as(u8, 1), pool.serverCount());\n}\n\n// Current Server Access Tests\n\ntest \"currentUrl on empty pool returns none\" {\n    // Can't create empty pool directly, but test the behavior\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    // Manually clear for testing (don't do this in production!)\n    pool.count = 0;\n    try std.testing.expectEqualStrings(\"none\", pool.currentUrl());\n}\n\ntest \"current returns server reference\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    const server = pool.current().?;\n    try std.testing.expectEqualStrings(\"nats://server:4222\", server.getUrl());\n}\n\ntest \"current allows modification\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    const server = pool.current().?;\n    server.consecutive_failures = 5;\n    try std.testing.expectEqual(@as(u8, 5), pool.servers[0].consecutive_failures);\n}\n\n// Server Struct Tests\n\ntest \"server default values\" {\n    const server: Server = .{};\n    try std.testing.expectEqual(@as(u8, 0), server.url_len);\n    try std.testing.expectEqual(@as(u16, 4222), server.port);\n    try std.testing.expectEqual(@as(u8, 0), server.consecutive_failures);\n    try std.testing.expectEqual(@as(u64, 0), server.last_attempt_ns);\n}\n\ntest \"server getUrl returns correct slice\" {\n    var server: Server = .{};\n    const url = \"nats://test:1234\";\n    @memcpy(server.url[0..url.len], url);\n    server.url_len = url.len;\n\n    try std.testing.expectEqualStrings(url, server.getUrl());\n}\n\ntest \"server getHost returns correct slice\" {\n    var server: Server = .{};\n    const url = \"nats://myhost:4222\";\n    @memcpy(server.url[0..url.len], url);\n    server.url_len = url.len;\n    server.host_start = 7;\n    server.host_len = 6;\n\n    try std.testing.expectEqualStrings(\"myhost\", server.getHost());\n}\n\ntest \"primary index preserved\" {\n    var pool = try ServerPool.init(\"nats://primary:4222\");\n    try pool.addServer(\"nats://secondary:4222\");\n\n    try std.testing.expectEqual(@as(u8, 0), pool.primary_idx);\n}\n\ntest \"timestamps updated on next_server\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n\n    const time1: u64 = 1_000_000_000_000;\n    _ = pool.nextServer(time1);\n    try std.testing.expectEqual(time1, pool.servers[0].last_attempt_ns);\n\n    const time2: u64 = 2_000_000_000_000;\n    _ = pool.nextServer(time2);\n    try std.testing.expectEqual(time2, pool.servers[0].last_attempt_ns);\n}\n\ntest \"zero time works\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    const server = pool.nextServer(0);\n    try std.testing.expect(server != null);\n}\n\ntest \"max time works\" {\n    var pool = try ServerPool.init(\"nats://server:4222\");\n    const server = pool.nextServer(std.math.maxInt(u64));\n    try std.testing.expect(server != null);\n}\n"
  },
  {
    "path": "src/connection/state.zig",
    "content": "//! Connection State Machine\n//!\n//! Manages the connection state transitions for NATS protocol.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// Connection states.\npub const State = enum {\n    /// Initial state, not connected.\n    disconnected,\n\n    /// TCP connection established, waiting for INFO.\n    connecting,\n\n    /// Received INFO, sent CONNECT, waiting for response.\n    authenticating,\n\n    /// Fully connected and ready.\n    connected,\n\n    /// Connection lost, attempting reconnect.\n    reconnecting,\n\n    /// Gracefully draining before close.\n    draining,\n\n    /// Permanently closed.\n    closed,\n\n    /// Thread-safe state read (use from io_task/callback_task).\n    pub inline fn atomicLoad(state_ptr: *const State) State {\n        return @atomicLoad(State, state_ptr, .acquire);\n    }\n\n    /// Thread-safe state write (use from io_task).\n    pub inline fn atomicStore(state_ptr: *State, new_state: State) void {\n        @atomicStore(State, state_ptr, new_state, .release);\n    }\n\n    /// Returns true if the connection can send messages.\n    pub fn canSend(self: State) bool {\n        return self == .connected or self == .draining;\n    }\n\n    /// Returns true if the connection can receive messages.\n    pub fn canReceive(self: State) bool {\n        return self == .connected or self == .draining;\n    }\n\n    /// Returns true if the connection is in a terminal state.\n    pub fn isTerminal(self: State) bool {\n        return self == .closed;\n    }\n\n    /// Returns true if the connection should attempt reconnection.\n    pub fn shouldReconnect(self: State) bool {\n        return self == .reconnecting;\n    }\n};\n\n/// State machine for connection lifecycle.\npub const StateMachine = struct {\n    state: State = .disconnected,\n    last_error: ?[]const u8 = null,\n\n    /// Transitions to connecting state.\n    pub fn startConnect(self: *StateMachine) !void {\n        switch (self.state) {\n            .disconnected, .reconnecting => {\n                self.state = .connecting;\n                self.last_error = null;\n                assert(self.state == .connecting);\n            },\n            .closed => return error.ConnectionClosed,\n            else => return error.InvalidState,\n        }\n    }\n\n    /// Called when INFO is received.\n    pub fn receivedInfo(self: *StateMachine) !void {\n        if (self.state != .connecting) return error.InvalidState;\n        self.state = .authenticating;\n        assert(self.state == .authenticating);\n    }\n\n    /// Called when CONNECT is acknowledged.\n    pub fn connectAcknowledged(self: *StateMachine) !void {\n        if (self.state != .authenticating) return error.InvalidState;\n        self.state = .connected;\n        assert(self.state == .connected);\n    }\n\n    /// Called when connection is lost.\n    pub fn connectionLost(self: *StateMachine, err: ?[]const u8) void {\n        self.last_error = err;\n        switch (self.state) {\n            .connected, .authenticating, .connecting => {\n                self.state = .reconnecting;\n            },\n            .draining => {\n                self.state = .closed;\n            },\n            else => {},\n        }\n    }\n\n    /// Starts graceful drain.\n    pub fn startDrain(self: *StateMachine) !void {\n        if (self.state != .connected) return error.InvalidState;\n        self.state = .draining;\n        assert(self.state == .draining);\n    }\n\n    /// Closes the connection permanently.\n    pub fn close(self: *StateMachine) void {\n        self.state = .closed;\n        assert(self.state.isTerminal());\n    }\n\n    /// Resets to disconnected for reconnection attempt.\n    pub fn resetForReconnect(self: *StateMachine) !void {\n        if (self.state != .reconnecting) return error.InvalidState;\n        self.state = .disconnected;\n        assert(self.state == .disconnected);\n    }\n};\n\ntest \"state machine happy path\" {\n    var sm: StateMachine = .{};\n\n    try std.testing.expectEqual(State.disconnected, sm.state);\n\n    try sm.startConnect();\n    try std.testing.expectEqual(State.connecting, sm.state);\n\n    try sm.receivedInfo();\n    try std.testing.expectEqual(State.authenticating, sm.state);\n\n    try sm.connectAcknowledged();\n    try std.testing.expectEqual(State.connected, sm.state);\n\n    try std.testing.expect(sm.state.canSend());\n    try std.testing.expect(sm.state.canReceive());\n}\n\ntest \"state machine reconnect\" {\n    var sm: StateMachine = .{};\n\n    try sm.startConnect();\n    try sm.receivedInfo();\n    try sm.connectAcknowledged();\n\n    sm.connectionLost(\"test error\");\n    try std.testing.expectEqual(State.reconnecting, sm.state);\n    try std.testing.expect(sm.state.shouldReconnect());\n\n    try sm.resetForReconnect();\n    try std.testing.expectEqual(State.disconnected, sm.state);\n}\n\ntest \"state machine drain\" {\n    var sm: StateMachine = .{};\n\n    try sm.startConnect();\n    try sm.receivedInfo();\n    try sm.connectAcknowledged();\n\n    try sm.startDrain();\n    try std.testing.expectEqual(State.draining, sm.state);\n    try std.testing.expect(sm.state.canSend());\n\n    sm.connectionLost(null);\n    try std.testing.expectEqual(State.closed, sm.state);\n    try std.testing.expect(sm.state.isTerminal());\n}\n\ntest \"state machine close\" {\n    var sm: StateMachine = .{};\n\n    try sm.startConnect();\n    sm.close();\n\n    try std.testing.expectEqual(State.closed, sm.state);\n    try std.testing.expectError(error.ConnectionClosed, sm.startConnect());\n}\n"
  },
  {
    "path": "src/connection.zig",
    "content": "//! Connection Module\n//!\n//! Provides connection state management and events for NATS.\n\nconst std = @import(\"std\");\n\npub const state = @import(\"connection/state.zig\");\npub const events = @import(\"connection/events.zig\");\npub const errors = @import(\"connection/errors.zig\");\npub const server_pool = @import(\"connection/server_pool.zig\");\npub const io_task = @import(\"connection/io_task.zig\");\n\npub const State = state.State;\npub const StateMachine = state.StateMachine;\n\npub const Event = events.Event;\npub const ConnectedInfo = events.ConnectedInfo;\npub const DisconnectedInfo = events.DisconnectedInfo;\npub const DisconnectReason = events.DisconnectReason;\npub const MessageInfo = events.MessageInfo;\npub const ReconnectingInfo = events.ReconnectingInfo;\n\npub const Error = errors.Error;\npub const parseAuthError = errors.parseAuthError;\npub const isRetryable = errors.isRetryable;\n\npub const ServerPool = server_pool.ServerPool;\n\ntest {\n    std.testing.refAllDecls(@This());\n}\n"
  },
  {
    "path": "src/dbg.zig",
    "content": "//! Debug printing utilities for NATS client.\n//!\n//! Compile with -DEnableDebug=true to enable debug output.\n//! When disabled, all debug calls are eliminated by dead code elimination.\n\nconst std = @import(\"std\");\nconst build_options = @import(\"build_options\");\n\n/// Debug printing enabled at compile time.\npub const enabled = build_options.enable_debug;\n\n/// Print debug message if debug is enabled.\n/// Dead code eliminated when disabled.\npub inline fn print(comptime fmt: []const u8, args: anytype) void {\n    if (enabled) {\n        std.debug.print(\"[NATS] \" ++ fmt ++ \"\\n\", args);\n    }\n}\n\n/// Print reconnection event.\npub inline fn reconnectEvent(\n    comptime event: []const u8,\n    attempt: u32,\n    server: []const u8,\n) void {\n    if (enabled) {\n        std.debug.print(\n            \"[NATS:RECONNECT] {s} attempt={d} server={s}\\n\",\n            .{ event, attempt, server },\n        );\n    }\n}\n\n/// Print connection state change.\npub inline fn stateChange(\n    comptime from: []const u8,\n    comptime to: []const u8,\n) void {\n    if (enabled) {\n        std.debug.print(\"[NATS:STATE] {s} -> {s}\\n\", .{ from, to });\n    }\n}\n\n/// Print PING/PONG event.\npub inline fn pingPong(comptime event: []const u8, outstanding: u8) void {\n    if (enabled) {\n        std.debug.print(\n            \"[NATS:HEALTH] {s} outstanding={d}\\n\",\n            .{ event, outstanding },\n        );\n    }\n}\n\n/// Print subscription event.\npub inline fn subscription(\n    comptime event: []const u8,\n    sid: u64,\n    subject: []const u8,\n) void {\n    if (enabled) {\n        std.debug.print(\n            \"[NATS:SUB] {s} sid={d} subject={s}\\n\",\n            .{ event, sid, subject },\n        );\n    }\n}\n\n/// Print pending buffer event.\npub inline fn pendingBuffer(\n    comptime event: []const u8,\n    pos: usize,\n    capacity: usize,\n) void {\n    if (enabled) {\n        std.debug.print(\n            \"[NATS:BUFFER] {s} pos={d} capacity={d}\\n\",\n            .{ event, pos, capacity },\n        );\n    }\n}\n"
  },
  {
    "path": "src/defaults.zig",
    "content": "//! Centralized Default Configuration\n//!\n//! Queue size is the master value. Slab tier counts derive from it.\n//! Change queue_size once, all memory allocations adjust automatically.\n\nconst builtin = @import(\"builtin\");\n\n/// Predefined queue size options (power-of-2, 1K to 512K).\npub const QueueSize = enum(u32) {\n    k1 = 1024,\n    k2 = 2048,\n    k4 = 4096,\n    k8 = 8192,\n    k16 = 16384,\n    k32 = 32768,\n    k64 = 65536,\n    k128 = 131072,\n    k256 = 262144,\n    k512 = 524288,\n\n    /// Returns the numeric value.\n    pub fn value(self: QueueSize) u32 {\n        return @intFromEnum(self);\n    }\n};\n\n/// Memory and slab configuration.\npub const Memory = struct {\n    /// Master queue size (slab tiers derive from this).\n    pub const queue_size: QueueSize = .k8;\n\n    /// Slab tier sizes (fixed, power-of-2).\n    pub const tier_sizes = [_]u32{ 256, 512, 1024, 4096, 16384 };\n    pub const tier_count: usize = tier_sizes.len;\n\n    /// Slab tier counts (derived from queue_size).\n    pub const tier_counts = blk: {\n        const q = queue_size.value();\n        break :blk [_]u32{\n            q, // Tier 0: 256B\n            q, // Tier 1: 512B\n            q / 2, // Tier 2: 1KB\n            q / 4, // Tier 3: 4KB\n            q / 16, // Tier 4: 16KB\n        };\n    };\n\n    /// Max slab slice size (larger uses fallback allocator).\n    pub const max_slice_size: usize = 16384;\n\n    /// Total pre-allocated slab memory (comptime computed).\n    pub const total_memory: usize = blk: {\n        var total: usize = 0;\n        for (tier_sizes, tier_counts) |size, count| {\n            total += @as(usize, size) * count;\n        }\n        break :blk total;\n    };\n};\n\n/// Connection settings.\npub const Connection = struct {\n    /// Connection timeout (5 seconds).\n    pub const timeout_ns: u64 = 5_000_000_000;\n    /// Read buffer size. Must be > max_payload + protocol overhead.\n    /// Derived from Protocol.max_payload + 8KB headroom for MSG/HMSG headers.\n    pub const reader_buffer_size: usize = Protocol.max_payload + 8 * 1024;\n    /// Write buffer size. Same default as read buffer.\n    pub const writer_buffer_size: usize = Protocol.max_payload + 8 * 1024;\n    /// TCP receive buffer hint (1 MB for high throughput).\n    pub const tcp_rcvbuf: u32 = 1024 * 1024;\n    /// Ping interval (2 minutes).\n    pub const ping_interval_ms: u32 = 120_000;\n    /// Max outstanding pings before stale.\n    pub const max_pings_outstanding: u8 = 2;\n};\n\n/// Reconnection strategy.\npub const Reconnection = struct {\n    /// Enable automatic reconnection.\n    pub const enabled: bool = true;\n    /// Maximum reconnection attempts (0 = infinite).\n    pub const max_attempts: u32 = 60;\n    /// Initial wait between attempts (2 seconds).\n    pub const wait_ms: u32 = 2_000;\n    /// Maximum wait with backoff (30 seconds).\n    pub const wait_max_ms: u32 = 30_000;\n    /// Jitter percentage (0-50).\n    pub const jitter_percent: u8 = 10;\n    /// Discover servers from INFO connect_urls.\n    pub const discover_servers: bool = true;\n    /// Buffer for publishes during reconnect (8 MB).\n    pub const pending_buffer_size: usize = 8 * 1024 * 1024;\n};\n\n/// Server pool limits.\npub const Server = struct {\n    /// Max servers in pool.\n    pub const max_pool_size: u8 = 16;\n    /// Max URL string length.\n    pub const max_url_len: u16 = 256;\n    /// Cooldown after failure (5 seconds).\n    pub const failure_cooldown_ns: u64 = 5_000_000_000;\n};\n\n/// Client limits.\npub const Client = struct {\n    /// Max concurrent subscriptions per client.\n    pub const max_subscriptions: u16 = 16384;\n    /// SidMap hash table capacity.\n    pub const sidmap_capacity: u32 = 32768;\n};\n\n/// Protocol constants.\npub const Protocol = struct {\n    /// Default NATS server port.\n    pub const port: u16 = 4222;\n    /// Default max payload (1 MB).\n    pub const max_payload: u32 = 1048576;\n    /// Client version string.\n    pub const version: []const u8 = \"0.1.0\";\n};\n\n/// Spin/yield loop tuning constants.\npub const Spin = struct {\n    /// Spin iterations before yielding in subscription next() loop.\n    /// After this many spins, yields to I/O runtime for cancellation support.\n    pub const max_spins: u32 = 4096;\n    /// Loop iterations between health check timestamp\n    /// reads in io_task.\n    pub const health_check_iterations: u32 = 1000000;\n    // timeout_check_iterations removed -- all spin loops\n    // now use io.sleep yield after max_spins instead.\n};\n\n/// Poll timeout configuration for io_task.\npub const Poll = struct {\n    /// Poll timeout in microseconds.\n    /// 0 = busy poll (max throughput, high CPU)\n    /// 100-500 = low latency with reduced CPU\n    /// 1000 = 1ms, balanced (default)\n    /// Values < 1000 require ppoll() on Linux for sub-ms precision.\n    /// poll() rounds up to 1ms minimum.\n    pub const timeout_us: i32 = 1000;\n};\n\n/// Protocol limits for subjects and queue groups.\n/// These are compile-time limits that define backup buffer sizes.\npub const Limits = struct {\n    /// Max subject length for backup buffers (reconnect support).\n    /// Subjects longer than this cannot be restored after reconnect.\n    pub const max_subject_len: u16 = 256;\n    /// Max queue group length for backup buffers.\n    pub const max_queue_group_len: u8 = 64;\n};\n\n/// Error reporting configuration.\npub const ErrorReporting = struct {\n    /// Messages between rate-limited error notifications.\n    /// After first error, subsequent errors only notify every N messages.\n    /// This prevents event queue flooding during sustained error conditions.\n    pub const notify_interval_msgs: u64 = 100_000;\n};\n\n/// TLS configuration.\npub const Tls = struct {\n    /// TLS read/write buffer size (must be >= tls.Client.min_buffer_len).\n    /// Using 32KB for good performance.\n    pub const buffer_size: usize = 32 * 1024;\n};\n"
  },
  {
    "path": "src/events.zig",
    "content": "//! Event Callbacks for NATS Client\n//!\n//! Type-erased event handler using comptime vtable pattern (like std.mem.Allocator).\n//! Enables callbacks without closures, maintaining Zig's no-hidden-allocation guarantee.\n//!\n//! ## Architecture\n//!\n//! io_task pushes events to SPSC queue (non-blocking).\n//! callback_task drains queue and dispatches to user handlers.\n//!\n//! ## Usage\n//!\n//! ```zig\n//! const MyHandler = struct {\n//!     counter: *u32,\n//!\n//!     pub fn onConnect(self: *@This()) void {\n//!         self.counter.* += 1;\n//!     }\n//! };\n//!\n//! var counter: u32 = 0;\n//! var handler = MyHandler{ .counter = &counter };\n//! const client = try nats.Client.connect(allocator, io, url, .{\n//!     .event_handler = nats.EventHandler.init(MyHandler, &handler),\n//! });\n//! ```\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// NATS-specific errors for event callbacks.\npub const Error = error{\n    /// Subscription queue full - messages being dropped (slow consumer).\n    SlowConsumer,\n    /// Server permission violation (publish/subscribe rejected).\n    PermissionViolation,\n    /// Connection is stale (ping timeout).\n    StaleConnection,\n    /// Server sent -ERR response.\n    ServerError,\n    /// Authorization failed (invalid credentials).\n    AuthorizationViolation,\n    /// Server connection limit reached.\n    MaxConnectionsExceeded,\n    /// Failed to restore subscriptions after reconnect.\n    /// User may need to re-subscribe manually.\n    SubscriptionRestoreFailed,\n    /// Message allocation failed (slab exhausted).\n    AllocationFailed,\n    /// Protocol parse error (malformed data skipped).\n    ProtocolParseError,\n    /// Subject too long for backup buffer (>256 bytes).\n    SubjectTooLong,\n    /// Queue group too long for backup buffer (>64 bytes).\n    QueueGroupTooLong,\n    /// Drain completed with failures (UNSUB or flush failed).\n    DrainIncomplete,\n    /// TCP_NODELAY socket option failed (performance impact).\n    TcpNoDelayFailed,\n    /// TCP receive buffer option failed (performance impact).\n    TcpRcvBufFailed,\n    /// URL too long (>256 bytes, would be truncated).\n    UrlTooLong,\n};\n\n/// Returns a human-readable description for NATS errors.\n/// For errors not in the NATS Error set, returns the error name.\npub fn statusText(err: anyerror) []const u8 {\n    return switch (err) {\n        Error.SlowConsumer => \"Slow consumer - subscription queue full\",\n        Error.PermissionViolation => \"Permission denied by server\",\n        Error.StaleConnection => \"Connection stale - ping timeout exceeded\",\n        Error.ServerError => \"Server error response\",\n        Error.AuthorizationViolation => \"Authorization failed\",\n        Error.MaxConnectionsExceeded => \"Server connection limit reached\",\n        Error.SubscriptionRestoreFailed => \"Failed to restore subscriptions\",\n        Error.AllocationFailed => \"Message allocation failed - slab exhausted\",\n        Error.ProtocolParseError => \"Protocol parse error - malformed data\",\n        Error.SubjectTooLong => \"Subject exceeds maximum length\",\n        Error.QueueGroupTooLong => \"Queue group exceeds maximum length\",\n        Error.DrainIncomplete => \"Drain completed with failures\",\n        Error.TcpNoDelayFailed => \"Failed to set TCP_NODELAY\",\n        Error.TcpRcvBufFailed => \"Failed to set TCP receive buffer\",\n        Error.UrlTooLong => \"URL exceeds maximum length\",\n        else => @errorName(err),\n    };\n}\n\n/// Events pushed from io_task to callback_task.\n/// These represent connection lifecycle changes and async errors.\npub const Event = union(enum) {\n    /// Initial connection established. Fired once, not on reconnect.\n    connected: void,\n\n    /// Connection lost. err is the I/O error that caused disconnect,\n    /// or null if clean close.\n    disconnected: struct { err: ?anyerror },\n\n    /// Successfully reconnected after disconnect.\n    /// Fired each time reconnection succeeds.\n    reconnected: void,\n\n    /// Connection permanently closed. No more events after this.\n    /// Fired exactly once when client becomes unusable.\n    closed: void,\n\n    /// Slow consumer - subscription queue full, message dropped.\n    /// sid identifies the affected subscription.\n    slow_consumer: struct { sid: u64 },\n\n    /// Async error that doesn't close connection.\n    /// Includes permission violations, server errors, stale connection.\n    err: struct { err: anyerror, msg: ?[]const u8 },\n\n    /// Server entering lame duck mode (graceful shutdown).\n    lame_duck: void,\n\n    /// Message allocation failed (slab exhausted). Rate-limited.\n    alloc_failed: struct { sid: u64, count: u64 },\n\n    /// Protocol parse error (malformed data recovered via CRLF skip).\n    /// Rate-limited: fires on first error, then every 100k messages.\n    protocol_error: struct { bytes_skipped: usize, count: u64 },\n\n    /// New servers discovered via cluster INFO (connect_urls).\n    /// count is the number of new servers added to the pool.\n    discovered_servers: struct { count: u8 },\n\n    /// Connection entering drain mode.\n    /// Fired when drain() is called on the client.\n    draining: void,\n\n    /// Subscription auto-unsubscribe limit reached.\n    /// Fired when a subscription hits its max messages limit.\n    subscription_complete: struct { sid: u64 },\n};\n\n/// Type-erased event handler using std.mem.Allocator vtable pattern.\n/// All callbacks are optional - only implement what you need.\n///\n/// Handler struct can contain references to external state:\n/// ```zig\n/// const MyHandler = struct {\n///     app_state: *AppState,  // Reference to your state\n///\n///     pub fn onConnect(self: *@This()) void {\n///         self.app_state.is_connected = true;\n///     }\n/// };\n/// ```\npub const EventHandler = struct {\n    ptr: *anyopaque,\n    vtable: *const VTable,\n\n    pub const VTable = struct {\n        onConnect: ?*const fn (*anyopaque) void = null,\n        onDisconnect: ?*const fn (*anyopaque, ?anyerror) void = null,\n        onReconnect: ?*const fn (*anyopaque) void = null,\n        onClose: ?*const fn (*anyopaque) void = null,\n        onError: ?*const fn (*anyopaque, anyerror) void = null,\n        onLameDuck: ?*const fn (*anyopaque) void = null,\n        onDiscoveredServers: ?*const fn (*anyopaque, u8) void = null,\n        onDraining: ?*const fn (*anyopaque) void = null,\n        onSubscriptionComplete: ?*const fn (*anyopaque, u64) void = null,\n    };\n\n    /// Create handler from concrete type using comptime.\n    /// Only generates vtable entries for methods that exist on T.\n    pub fn init(comptime T: type, ptr: *T) EventHandler {\n        const gen = struct {\n            fn onConnect(p: *anyopaque) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onConnect();\n            }\n            fn onDisconnect(p: *anyopaque, err: ?anyerror) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onDisconnect(err);\n            }\n            fn onReconnect(p: *anyopaque) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onReconnect();\n            }\n            fn onClose(p: *anyopaque) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onClose();\n            }\n            fn onError(p: *anyopaque, err: anyerror) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onError(err);\n            }\n            fn onLameDuck(p: *anyopaque) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onLameDuck();\n            }\n            fn onDiscoveredServers(p: *anyopaque, count: u8) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onDiscoveredServers(count);\n            }\n            fn onDraining(p: *anyopaque) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onDraining();\n            }\n            fn onSubscriptionComplete(p: *anyopaque, sid: u64) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onSubscriptionComplete(sid);\n            }\n        };\n\n        const vtable = comptime blk: {\n            break :blk VTable{\n                .onConnect = if (@hasDecl(T, \"onConnect\"))\n                    gen.onConnect\n                else\n                    null,\n                .onDisconnect = if (@hasDecl(T, \"onDisconnect\"))\n                    gen.onDisconnect\n                else\n                    null,\n                .onReconnect = if (@hasDecl(T, \"onReconnect\"))\n                    gen.onReconnect\n                else\n                    null,\n                .onClose = if (@hasDecl(T, \"onClose\"))\n                    gen.onClose\n                else\n                    null,\n                .onError = if (@hasDecl(T, \"onError\"))\n                    gen.onError\n                else\n                    null,\n                .onLameDuck = if (@hasDecl(T, \"onLameDuck\"))\n                    gen.onLameDuck\n                else\n                    null,\n                .onDiscoveredServers = if (@hasDecl(T, \"onDiscoveredServers\"))\n                    gen.onDiscoveredServers\n                else\n                    null,\n                .onDraining = if (@hasDecl(T, \"onDraining\"))\n                    gen.onDraining\n                else\n                    null,\n                .onSubscriptionComplete = if (@hasDecl(T, \"onSubscriptionComplete\"))\n                    gen.onSubscriptionComplete\n                else\n                    null,\n            };\n        };\n\n        return .{\n            .ptr = ptr,\n            .vtable = &vtable,\n        };\n    }\n\n    /// Dispatch connected event to handler.\n    pub fn dispatchConnect(self: EventHandler) void {\n        if (self.vtable.onConnect) |f| f(self.ptr);\n    }\n\n    /// Dispatch disconnected event to handler.\n    pub fn dispatchDisconnect(self: EventHandler, err: ?anyerror) void {\n        if (self.vtable.onDisconnect) |f| f(self.ptr, err);\n    }\n\n    /// Dispatch reconnected event to handler.\n    pub fn dispatchReconnect(self: EventHandler) void {\n        if (self.vtable.onReconnect) |f| f(self.ptr);\n    }\n\n    /// Dispatch closed event to handler.\n    pub fn dispatchClose(self: EventHandler) void {\n        if (self.vtable.onClose) |f| f(self.ptr);\n    }\n\n    /// Dispatch error event to handler.\n    pub fn dispatchError(self: EventHandler, err: anyerror) void {\n        if (self.vtable.onError) |f| f(self.ptr, err);\n    }\n\n    /// Dispatch lame duck event to handler.\n    pub fn dispatchLameDuck(self: EventHandler) void {\n        if (self.vtable.onLameDuck) |f| f(self.ptr);\n    }\n\n    /// Dispatch discovered servers event to handler.\n    pub fn dispatchDiscoveredServers(self: EventHandler, count: u8) void {\n        if (self.vtable.onDiscoveredServers) |f| f(self.ptr, count);\n    }\n\n    /// Dispatch draining event to handler.\n    pub fn dispatchDraining(self: EventHandler) void {\n        if (self.vtable.onDraining) |f| f(self.ptr);\n    }\n\n    /// Dispatch subscription complete event to handler.\n    pub fn dispatchSubscriptionComplete(self: EventHandler, sid: u64) void {\n        if (self.vtable.onSubscriptionComplete) |f| f(self.ptr, sid);\n    }\n};\n\ntest \"EventHandler vtable generation\" {\n    const FullHandler = struct {\n        connect_count: u32 = 0,\n        disconnect_count: u32 = 0,\n        last_error: ?anyerror = null,\n\n        pub fn onConnect(self: *@This()) void {\n            self.connect_count += 1;\n        }\n        pub fn onDisconnect(self: *@This(), err: ?anyerror) void {\n            self.disconnect_count += 1;\n            self.last_error = err;\n        }\n        pub fn onReconnect(self: *@This()) void {\n            self.connect_count += 1;\n        }\n        pub fn onClose(_: *@This()) void {}\n        pub fn onError(self: *@This(), err: anyerror) void {\n            self.last_error = err;\n        }\n        pub fn onLameDuck(_: *@This()) void {}\n    };\n\n    var handler = FullHandler{};\n    const eh = EventHandler.init(FullHandler, &handler);\n\n    try std.testing.expect(eh.vtable.onConnect != null);\n    try std.testing.expect(eh.vtable.onDisconnect != null);\n    try std.testing.expect(eh.vtable.onReconnect != null);\n    try std.testing.expect(eh.vtable.onClose != null);\n    try std.testing.expect(eh.vtable.onError != null);\n    try std.testing.expect(eh.vtable.onLameDuck != null);\n\n    eh.dispatchConnect();\n    try std.testing.expectEqual(@as(u32, 1), handler.connect_count);\n\n    eh.dispatchDisconnect(error.OutOfMemory);\n    try std.testing.expectEqual(@as(u32, 1), handler.disconnect_count);\n    try std.testing.expectEqual(error.OutOfMemory, handler.last_error.?);\n}\n\ntest \"EventHandler partial implementation\" {\n    const MinimalHandler = struct {\n        called: bool = false,\n\n        pub fn onConnect(self: *@This()) void {\n            self.called = true;\n        }\n    };\n\n    var handler = MinimalHandler{};\n    const eh = EventHandler.init(MinimalHandler, &handler);\n\n    try std.testing.expect(eh.vtable.onConnect != null);\n    try std.testing.expect(eh.vtable.onDisconnect == null);\n    try std.testing.expect(eh.vtable.onReconnect == null);\n    try std.testing.expect(eh.vtable.onClose == null);\n    try std.testing.expect(eh.vtable.onError == null);\n    try std.testing.expect(eh.vtable.onLameDuck == null);\n\n    eh.dispatchConnect();\n    try std.testing.expect(handler.called);\n\n    eh.dispatchDisconnect(null); // Should be no-op\n    eh.dispatchReconnect(); // Should be no-op\n    eh.dispatchClose(); // Should be no-op\n}\n\ntest \"EventHandler with external state\" {\n    const AppState = struct {\n        is_online: bool = false,\n        reconnect_count: u32 = 0,\n    };\n\n    const MyHandler = struct {\n        app: *AppState,\n\n        pub fn onConnect(self: *@This()) void {\n            self.app.is_online = true;\n        }\n\n        pub fn onDisconnect(self: *@This(), _: ?anyerror) void {\n            self.app.is_online = false;\n        }\n\n        pub fn onReconnect(self: *@This()) void {\n            self.app.is_online = true;\n            self.app.reconnect_count += 1;\n        }\n    };\n\n    var app_state = AppState{};\n    var handler = MyHandler{ .app = &app_state };\n    const eh = EventHandler.init(MyHandler, &handler);\n\n    try std.testing.expect(!app_state.is_online);\n    try std.testing.expectEqual(@as(u32, 0), app_state.reconnect_count);\n\n    eh.dispatchConnect();\n    try std.testing.expect(app_state.is_online);\n\n    eh.dispatchDisconnect(error.BrokenPipe);\n    try std.testing.expect(!app_state.is_online);\n\n    eh.dispatchReconnect();\n    try std.testing.expect(app_state.is_online);\n    try std.testing.expectEqual(@as(u32, 1), app_state.reconnect_count);\n}\n\ntest \"Event union\" {\n    const events = [_]Event{\n        .{ .connected = {} },\n        .{ .disconnected = .{ .err = error.BrokenPipe } },\n        .{ .disconnected = .{ .err = null } },\n        .{ .reconnected = {} },\n        .{ .closed = {} },\n        .{ .slow_consumer = .{ .sid = 42 } },\n        .{ .err = .{ .err = Error.SlowConsumer, .msg = null } },\n        .{ .err = .{ .err = Error.PermissionViolation, .msg = \"test\" } },\n        .{ .lame_duck = {} },\n        .{ .alloc_failed = .{ .sid = 1, .count = 5 } },\n        .{ .protocol_error = .{ .bytes_skipped = 128, .count = 3 } },\n        .{ .discovered_servers = .{ .count = 3 } },\n        .{ .draining = {} },\n        .{ .subscription_complete = .{ .sid = 42 } },\n    };\n\n    for (events) |event| {\n        switch (event) {\n            .connected => {},\n            .disconnected => |d| {\n                if (d.err) |err| {\n                    _ = @errorName(err);\n                }\n            },\n            .reconnected => {},\n            .closed => {},\n            .slow_consumer => |sc| try std.testing.expect(sc.sid >= 0),\n            .err => |e| {\n                _ = @errorName(e.err);\n            },\n            .lame_duck => {},\n            .alloc_failed => |af| {\n                try std.testing.expect(af.sid > 0);\n                try std.testing.expect(af.count > 0);\n            },\n            .protocol_error => |pe| {\n                try std.testing.expect(pe.bytes_skipped > 0);\n                try std.testing.expect(pe.count > 0);\n            },\n            .discovered_servers => |ds| {\n                try std.testing.expect(ds.count > 0);\n            },\n            .draining => {},\n            .subscription_complete => |sc| {\n                try std.testing.expect(sc.sid > 0);\n            },\n        }\n    }\n}\n\ntest \"statusText for known errors\" {\n    // Test known NATS errors\n    try std.testing.expectEqualStrings(\n        \"Slow consumer - subscription queue full\",\n        statusText(Error.SlowConsumer),\n    );\n    try std.testing.expectEqualStrings(\n        \"Permission denied by server\",\n        statusText(Error.PermissionViolation),\n    );\n    try std.testing.expectEqualStrings(\n        \"Authorization failed\",\n        statusText(Error.AuthorizationViolation),\n    );\n    try std.testing.expectEqualStrings(\n        \"Message allocation failed - slab exhausted\",\n        statusText(Error.AllocationFailed),\n    );\n}\n\ntest \"statusText for unknown errors\" {\n    // Unknown errors should return error name\n    try std.testing.expectEqualStrings(\n        \"OutOfMemory\",\n        statusText(error.OutOfMemory),\n    );\n}\n"
  },
  {
    "path": "src/examples/README.md",
    "content": "# Examples\n\nRun with `zig build run-<name>` (requires `nats-server` on localhost:4222).\n\n| Example | Run | Description |\n|---------|-----|-------------|\n| simple | `run-simple` | Basic pub/sub - connect, `subscribeSync`, publish, receive |\n| request_reply | `run-request-reply` | RPC pattern with automatic inbox handling |\n| headers | `run-headers` | Publish, receive, and parse NATS headers |\n| queue_groups | `run-queue-groups` | Load-balanced workers with `io.concurrent()` |\n| polling_loop | `run-polling-loop` | Non-blocking `tryNextMsg()` with priority scheduling |\n| select | `run-select` | Race subscription against timeout with `Io.Select` |\n| batch_receiving | `run-batch-receiving` | `nextMsgBatch()` for bulk receives, stats monitoring |\n| reconnection | `run-reconnection` | Auto-reconnect, backoff, buffer during disconnect |\n| events | `run-events` | EventHandler callbacks with external state |\n| callback | `run-callback` | `subscribe()` and `subscribeFn()` callback subscriptions |\n| request_reply_callback | `run-request-reply-callback` | Service responder via callback subscription |\n| graceful_shutdown | `run-graceful-shutdown` | `drain()` lifecycle, pre-shutdown health checks |\n| jetstream_publish | `run-jetstream-publish` | Create a stream and publish with ack confirmation |\n| jetstream_consume | `run-jetstream-consume` | Pull consumer fetch and acknowledgement |\n| jetstream_push | `run-jetstream-push` | Push consumer callback delivery |\n| jetstream_async_publish | `run-jetstream-async-publish` | Async JetStream publishing |\n| kv | `run-kv` | Key-Value bucket operations |\n| kv_watch | `run-kv-watch` | Watch Key-Value updates |\n| micro_echo | `run-micro-echo` | NATS service API echo service |\n"
  },
  {
    "path": "src/examples/batch_receiving.zig",
    "content": "//! Batch Receiving Patterns\n//!\n//! Demonstrates efficient batch message retrieval:\n//! - nextMsgBatch(): blocking batch receive (waits for at least 1 message)\n//! - tryNextMsgBatch(): non-blocking batch receive for polling\n//! - Stats monitoring: track messages and detect drops\n//!\n//! Run with: zig build run-batch-receiving\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Io = std.Io;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    // Connect with larger subscription queue for batch receiving\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{\n            .name = \"batch-receiving\",\n            .sub_queue_size = 512, // Larger queue for batch demos\n        },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    const sub = try client.subscribeSync(\"bench.>\");\n    defer sub.deinit();\n\n    std.debug.print(\"Subscribed to 'bench.>'\\n\\n\", .{});\n\n    // Publish test messages\n    const message_count: u32 = 100;\n    std.debug.print(\"Publishing {d} messages...\\n\", .{message_count});\n\n    for (0..message_count) |i| {\n        var buf: [64]u8 = undefined;\n        const payload = std.fmt.bufPrint(&buf, \"Message {d}\", .{i + 1}) catch \"Msg\";\n        try client.publish(\"bench.test\", payload);\n    }\n\n    // BATCH RECEIVING: Receive multiple messages at once\n    std.debug.print(\"Receiving messages (batch mode)...\\n\", .{});\n\n    var batch_buf: [32]nats.Message = undefined;\n    var total_received: u32 = 0;\n    var batch_count: u32 = 0;\n\n    const recv_start = Io.Timestamp.now(io, .awake);\n\n    while (total_received < message_count) {\n        // nextBatch waits for at least 1 message, returns up to 32\n        const count = sub.nextMsgBatch(io, &batch_buf) catch break;\n        batch_count += 1;\n\n        for (batch_buf[0..count]) |*msg| {\n            defer msg.deinit();\n            total_received += 1;\n        }\n\n        // Check for dropped messages\n        const dropped = sub.dropped();\n        if (dropped > 0) {\n            std.debug.print(\n                \"  Warning: {d} messages dropped (consumer too slow)\\n\",\n                .{dropped},\n            );\n        }\n    }\n\n    const recv_end = Io.Timestamp.now(io, .awake);\n    const elapsed = recv_start.durationTo(recv_end);\n    const recv_ns: u64 = @intCast(elapsed.nanoseconds);\n    const recv_ms = @as(f64, @floatFromInt(recv_ns)) /\n        1_000_000.0;\n\n    std.debug.print(\n        \"Received {d} messages in {d} batches ({d:.2}ms)\\n\",\n        .{ total_received, batch_count, recv_ms },\n    );\n    std.debug.print(\n        \"Throughput: {d:.0} msgs/sec\\n\\n\",\n        .{@as(f64, @floatFromInt(total_received)) / (recv_ms / 1000.0)},\n    );\n\n    // NON-BLOCKING BATCH: tryNextBatch for polling\n    std.debug.print(\"Demonstrating tryNextBatch (non-blocking)...\\n\", .{});\n\n    // Publish a few more messages\n    for (0..5) |i| {\n        var buf: [64]u8 = undefined;\n        const payload = std.fmt.bufPrint(&buf, \"Extra {d}\", .{i + 1}) catch \"Msg\";\n        try client.publish(\"bench.extra\", payload);\n    }\n\n    // Flush to ensure messages have been delivered\n    try client.flush(1_000_000_000);\n\n    // Non-blocking batch receive\n    const available = sub.tryNextMsgBatch(&batch_buf);\n    std.debug.print(\"  tryNextBatch returned {d} messages immediately\\n\", .{available});\n\n    for (batch_buf[0..available]) |*msg| {\n        defer msg.deinit();\n        std.debug.print(\"    {s}\\n\", .{msg.data});\n    }\n\n    // STATS SUMMARY\n    std.debug.print(\"\\nStats summary:\\n\", .{});\n    std.debug.print(\"  Messages received: {d}\\n\", .{sub.received_msgs});\n    std.debug.print(\"  Messages dropped: {d}\\n\", .{sub.dropped()});\n\n    const stats = client.stats();\n    std.debug.print(\"  Total bytes out: {d}\\n\", .{stats.bytes_out});\n    std.debug.print(\"  Total bytes in: {d}\\n\", .{stats.bytes_in});\n\n    std.debug.print(\"\\nDone!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/callback.zig",
    "content": "//! Callback Subscriptions\n//!\n//! Demonstrates callback-style message handling using MsgHandler\n//! (vtable pattern) and plain function pointers. Messages are\n//! dispatched automatically -- no manual next() loop needed.\n//!\n//! Run with: zig build run-callback\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server -DV\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\n// -- MsgHandler pattern: handler struct with state --\n\n/// Application state shared with the handler.\nconst AppState = struct {\n    count: u32 = 0,\n    last_subject: [64]u8 = undefined,\n    last_subject_len: usize = 0,\n};\n\n/// Handler struct -- implements onMessage to receive callbacks.\nconst MyHandler = struct {\n    app: *AppState,\n\n    pub fn onMessage(self: *@This(), msg: *const nats.Message) void {\n        self.app.count += 1;\n        const len = @min(msg.subject.len, 64);\n        @memcpy(self.app.last_subject[0..len], msg.subject[0..len]);\n        self.app.last_subject_len = len;\n\n        std.debug.print(\n            \"  [handler] #{d} {s}: {s}\\n\",\n            .{\n                self.app.count,\n                msg.subject,\n                msg.data,\n            },\n        );\n    }\n};\n\n// -- Plain fn pattern: no struct needed --\n\n/// Simple alert function -- stateless callback.\nfn alertFn(msg: *const nats.Message) void {\n    std.debug.print(\n        \"  [alert] {s}: {s}\\n\",\n        .{ msg.subject, msg.data },\n    );\n}\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{ .name = \"callback-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    // 1. MsgHandler callback subscription\n    var app = AppState{};\n    var handler = MyHandler{ .app = &app };\n\n    const sub1 = try client.subscribe(\n        \"demo.handler\",\n        nats.MsgHandler.init(MyHandler, &handler),\n    );\n    defer sub1.deinit();\n\n    // 2. Plain fn callback subscription\n    const sub2 = try client.subscribeFn(\n        \"demo.alert\",\n        alertFn,\n    );\n    defer sub2.deinit();\n\n    std.debug.print(\"Subscribed with callbacks.\\n\", .{});\n    std.debug.print(\n        \"Publishing messages...\\n\\n\",\n        .{},\n    );\n\n    // Publish messages\n    for (0..5) |i| {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"hello {d}\",\n            .{i + 1},\n        ) catch \"hello\";\n        try client.publish(\"demo.handler\", msg);\n    }\n    try client.publish(\"demo.alert\", \"fire!\");\n    try client.publish(\"demo.alert\", \"smoke!\");\n\n    // Flush to ensure messages have been delivered\n    try client.flush(1_000_000_000);\n\n    std.debug.print(\n        \"\\nHandler count: {d}\\n\",\n        .{app.count},\n    );\n    std.debug.print(\n        \"Last subject: {s}\\n\",\n        .{app.last_subject[0..app.last_subject_len]},\n    );\n\n    // Verify all messages delivered\n    std.debug.assert(app.count == 5);\n\n    std.debug.print(\"Done!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/events.zig",
    "content": "//! Event Callbacks Example\n//!\n//! Demonstrates how to handle connection lifecycle events using the\n//! EventHandler pattern. Shows how handlers can reference external state\n//! without closures.\n//!\n//! Run with: zig build run-events\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server -DV\n//!\n//! Try stopping/starting nats-server to see disconnect/reconnect events.\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\n/// Application state that callbacks will modify.\n/// This pattern allows event handlers to update shared state\n/// without closures.\nconst AppState = struct {\n    is_online: bool = false,\n    reconnect_count: u32 = 0,\n    last_error: ?anyerror = null,\n    should_shutdown: bool = false,\n};\n\n/// Event handler that references external AppState.\n/// All callback methods are optional - only implement what you need.\nconst MyEventHandler = struct {\n    app: *AppState,\n\n    pub fn onConnect(self: *@This()) void {\n        self.app.is_online = true;\n        std.debug.print(\"[EVENT] Connected to NATS server\\n\", .{});\n    }\n\n    pub fn onDisconnect(self: *@This(), err: ?anyerror) void {\n        self.app.is_online = false;\n        self.app.last_error = err;\n        if (err) |e| {\n            std.debug.print(\"[EVENT] Disconnected: {s}\\n\", .{@errorName(e)});\n        } else {\n            std.debug.print(\"[EVENT] Disconnected (clean)\\n\", .{});\n        }\n    }\n\n    pub fn onReconnect(self: *@This()) void {\n        self.app.is_online = true;\n        self.app.reconnect_count += 1;\n        std.debug.print(\n            \"[EVENT] Reconnected! (total reconnects: {})\\n\",\n            .{self.app.reconnect_count},\n        );\n    }\n\n    pub fn onClose(self: *@This()) void {\n        self.app.is_online = false;\n        self.app.should_shutdown = true;\n        std.debug.print(\"[EVENT] Connection closed permanently\\n\", .{});\n    }\n\n    pub fn onError(self: *@This(), err: anyerror) void {\n        self.app.last_error = err;\n        std.debug.print(\"[EVENT] Async error: {s}\\n\", .{@errorName(err)});\n    }\n\n    pub fn onLameDuck(_: *@This()) void {\n        std.debug.print(\n            \"[EVENT] Server entering lame duck mode - prepare for shutdown!\\n\",\n            .{},\n        );\n    }\n};\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    // External state that callbacks will modify\n    var app_state = AppState{};\n\n    // Handler with reference to external state\n    var handler = MyEventHandler{ .app = &app_state };\n\n    std.debug.print(\"Connecting to NATS with event callbacks...\\n\", .{});\n\n    // Connect with event handler\n    const client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{\n        .event_handler = nats.EventHandler.init(MyEventHandler, &handler),\n        .reconnect = true,\n    });\n    defer client.deinit();\n\n    // Subscribe to test subject\n    const sub = try client.subscribeSync(\"test.>\");\n    defer sub.deinit();\n\n    std.debug.print(\"\\nSubscribed to test.>\\n\", .{});\n    std.debug.print(\"Try: nats pub test.hello 'world'\\n\", .{});\n    std.debug.print(\"Try stopping/starting nats-server to see events!\\n\", .{});\n    std.debug.print(\"Press Ctrl+C to exit.\\n\\n\", .{});\n\n    // Main loop - processes messages and checks app_state\n    var msg_count: u32 = 0;\n    const max_msgs: u32 = 100;\n\n    while (!app_state.should_shutdown and msg_count < max_msgs) {\n        // Non-blocking message check with timeout\n        if (try sub.nextMsgTimeout(1000)) |msg| {\n            defer msg.deinit();\n            std.debug.print(\"Received: {s}\\n\", .{msg.data});\n            msg_count += 1;\n        }\n\n        // React to state changes from callbacks\n        if (!app_state.is_online) {\n            std.debug.print(\"(offline - waiting for reconnect...)\\n\", .{});\n            io.sleep(.fromMilliseconds(1000), .awake) catch {};\n        }\n    }\n\n    std.debug.print(\"\\n=== Final App State ===\\n\", .{});\n    std.debug.print(\"  Online: {}\\n\", .{app_state.is_online});\n    std.debug.print(\"  Reconnects: {}\\n\", .{app_state.reconnect_count});\n    if (app_state.last_error) |err| {\n        std.debug.print(\"  Last error: {s}\\n\", .{@errorName(err)});\n    } else {\n        std.debug.print(\"  Last error: none\\n\", .{});\n    }\n    std.debug.print(\"  Messages received: {}\\n\", .{msg_count});\n}\n"
  },
  {
    "path": "src/examples/graceful_shutdown.zig",
    "content": "//! Graceful Shutdown Pattern\n//!\n//! Demonstrates production-ready lifecycle management:\n//! - drain() for graceful subscription cleanup\n//! - Proper resource cleanup order\n//! - Monitoring dropped messages before shutdown\n//!\n//! Run with: zig build run-graceful-shutdown\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{ .name = \"graceful-shutdown-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    // Create multiple subscriptions\n    const orders = try client.subscribeSync(\"orders.*\");\n    defer orders.deinit();\n\n    const events = try client.subscribeSync(\"events.>\");\n    defer events.deinit();\n\n    std.debug.print(\"Subscriptions active:\\n\", .{});\n    std.debug.print(\"  - orders.* (sid={d})\\n\", .{orders.sid});\n    std.debug.print(\"  - events.> (sid={d})\\n\", .{events.sid});\n\n    // Simulate some activity\n    std.debug.print(\"\\nSimulating activity...\\n\", .{});\n\n    for (0..5) |i| {\n        var buf: [64]u8 = undefined;\n        const order = std.fmt.bufPrint(&buf, \"Order #{d}\", .{i + 1000}) catch \"Order\";\n        try client.publish(\"orders.new\", order);\n\n        const event = std.fmt.bufPrint(&buf, \"Event {d}\", .{i + 1}) catch \"Event\";\n        try client.publish(\"events.user.login\", event);\n    }\n\n    std.debug.print(\"Published 10 messages (5 orders, 5 events)\\n\", .{});\n\n    // Flush to ensure messages have been delivered\n    try client.flush(1_000_000_000);\n\n    var orders_count: u32 = 0;\n    while (orders.tryNextMsg()) |msg| {\n        defer msg.deinit();\n        orders_count += 1;\n    }\n\n    var events_count: u32 = 0;\n    while (events.tryNextMsg()) |msg| {\n        defer msg.deinit();\n        events_count += 1;\n    }\n\n    std.debug.print(\n        \"Processed: {d} orders, {d} events\\n\",\n        .{ orders_count, events_count },\n    );\n\n    // CHECK FOR DROPPED MESSAGES before shutdown\n    std.debug.print(\"\\nPre-shutdown health check:\\n\", .{});\n\n    const orders_dropped = orders.dropped();\n    const events_dropped = events.dropped();\n\n    if (orders_dropped > 0 or events_dropped > 0) {\n        std.debug.print(\"  WARNING: Messages were dropped!\\n\", .{});\n        std.debug.print(\"    orders: {d} dropped\\n\", .{orders_dropped});\n        std.debug.print(\"    events: {d} dropped\\n\", .{events_dropped});\n    } else {\n        std.debug.print(\"  No messages dropped - healthy!\\n\", .{});\n    }\n\n    // GRACEFUL SHUTDOWN with drain()\n    std.debug.print(\"\\nInitiating graceful shutdown...\\n\", .{});\n\n    // drain() does the following:\n    // 1. Unsubscribes all active subscriptions\n    // 2. Drains any remaining messages from queues (frees memory)\n    // 3. Flushes pending writes to server\n    // 4. Closes connection and transitions to closed state\n    const drain_result = client.drain() catch |err| {\n        std.debug.print(\"Drain failed: {}\\n\", .{err});\n        return err;\n    };\n\n    std.debug.print(\"Drain completed:\\n\", .{});\n    if (drain_result.unsub_failures > 0) {\n        std.debug.print(\"  WARNING: {d} unsub commands failed\\n\", .{\n            drain_result.unsub_failures,\n        });\n    } else {\n        std.debug.print(\"  All subscriptions unsubscribed successfully\\n\", .{});\n    }\n    if (drain_result.flush_failed) {\n        std.debug.print(\"  WARNING: Final flush failed\\n\", .{});\n    } else {\n        std.debug.print(\"  Final flush succeeded\\n\", .{});\n    }\n\n    // Final stats\n    const stats = client.stats();\n    std.debug.print(\"\\nFinal statistics:\\n\", .{});\n    std.debug.print(\"  Messages sent: {d}\\n\", .{stats.msgs_out});\n    std.debug.print(\"  Messages received: {d}\\n\", .{stats.msgs_in});\n    std.debug.print(\"  Bytes sent: {d}\\n\", .{stats.bytes_out});\n    std.debug.print(\"  Bytes received: {d}\\n\", .{stats.bytes_in});\n\n    std.debug.print(\"\\nGraceful shutdown complete!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/headers.zig",
    "content": "//! NATS Headers\n//!\n//! Demonstrates publishing messages with headers and parsing\n//! received header metadata.\n//!\n//! Run with: zig build run-headers\n//!   or:    zig build run-headers -Dio_backend=evented\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst io_backend = @import(\"io_backend\");\n\nconst headers = nats.protocol.headers;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    var backend: io_backend.Backend = undefined;\n    try io_backend.init(&backend, allocator);\n    defer backend.deinit();\n    const io = backend.io();\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{ .name = \"headers-example\" },\n    );\n    defer client.deinit();\n\n    const sub = try client.subscribeSync(\"headers.demo\");\n    defer sub.deinit();\n\n    try client.flush(std.time.ns_per_s);\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"Content-Type\", .value = \"application/json\" },\n        .{ .key = \"X-Request-Id\", .value = \"req-42\" },\n        .{ .key = \"X-Trace\", .value = \"alpha\" },\n        .{ .key = \"X-Trace\", .value = \"beta\" },\n    };\n\n    try client.publishWithHeaders(\n        \"headers.demo\",\n        &hdrs,\n        \"{\\\"message\\\":\\\"hello\\\"}\",\n    );\n\n    if (try sub.nextMsgTimeout(1000)) |msg| {\n        defer msg.deinit();\n\n        std.debug.print(\"Received payload: {s}\\n\", .{msg.data});\n\n        const raw = msg.headers orelse return error.MissingHeaders;\n        var parsed = headers.parse(allocator, raw);\n        defer parsed.deinit();\n\n        if (parsed.err) |err| {\n            std.debug.print(\"Header parse error: {}\\n\", .{err});\n            return error.InvalidHeaders;\n        }\n\n        if (parsed.get(\"content-type\")) |content_type| {\n            std.debug.print(\"Content-Type: {s}\\n\", .{content_type});\n        }\n\n        std.debug.print(\"Headers:\\n\", .{});\n        for (parsed.items()) |entry| {\n            std.debug.print(\"  {s}: {s}\\n\", .{\n                entry.key,\n                entry.value,\n            });\n        }\n    } else {\n        std.debug.print(\"Timed out waiting for message\\n\", .{});\n    }\n}\n"
  },
  {
    "path": "src/examples/jetstream_async_publish.zig",
    "content": "//! JetStream Async Publish -- non-blocking publish with\n//! futures.\n//!\n//! AsyncPublisher decouples publishing from ack waiting.\n//! Messages are sent immediately and acks are correlated\n//! in the background via a shared reply subscription.\n//! Use this when throughput matters more than per-message\n//! confirmation.\n//!\n//! Run with: zig build run-jetstream-async-publish\n//!\n//! Prerequisites: nats-server -js\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://127.0.0.1:4222\",\n        .{ .name = \"js-async-pub-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    var js = try js_mod.JetStream.init(client, .{});\n\n    var stream_resp = try js.createStream(.{\n        .name = \"DEMO_ASYNC\",\n        .subjects = &.{\"perf.>\"},\n        .storage = .memory,\n    });\n    stream_resp.deinit();\n\n    // AsyncPublisher manages a shared reply inbox and\n    // correlates incoming acks to pending futures.\n    // max_pending=64 means backpressure kicks in after\n    // 64 unacknowledged publishes (the caller blocks\n    // until acks drain below the threshold).\n    var ap = try js_mod.AsyncPublisher.init(\n        &js,\n        .{ .max_pending = 64 },\n    );\n    defer ap.deinit();\n\n    const msg_count: u32 = 100;\n\n    // Fire-and-forget: publish all messages without\n    // waiting for individual acks. The futures\n    // accumulate and resolve as acks arrive from the\n    // server in the background.\n    var futures: [100]*js_mod.PubAckFuture = undefined;\n    for (0..msg_count) |i| {\n        var buf: [64]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"measurement #{d}\",\n            .{i + 1},\n        ) catch \"data\";\n        futures[i] = try ap.publish(\n            \"perf.metrics\",\n            payload,\n        );\n    }\n\n    std.debug.print(\n        \"Published {d} messages.\\n\",\n        .{msg_count},\n    );\n    std.debug.print(\n        \"Pending acks: {d}\\n\\n\",\n        .{ap.publishAsyncPending()},\n    );\n\n    // waitComplete blocks until all pending futures\n    // resolve (acks received) or the timeout expires.\n    // This is the batch-level sync point.\n    try ap.waitComplete(10000);\n\n    std.debug.print(\n        \"All acks received (pending={d}).\\n\\n\",\n        .{ap.publishAsyncPending()},\n    );\n\n    // Verify a few individual futures to show the\n    // per-message API. Each future can be checked\n    // independently with wait() or result().\n    for (0..3) |i| {\n        const fut = futures[i];\n        defer fut.deinit();\n        if (fut.result()) |ack| {\n            std.debug.print(\n                \"  Future[{d}]: seq={d}\\n\",\n                .{ i, ack.seq },\n            );\n        }\n    }\n    // Deinit remaining futures\n    for (3..msg_count) |i| futures[i].deinit();\n\n    // Check stream info for final message count\n    var info = try js.streamInfo(\"DEMO_ASYNC\");\n    defer info.deinit();\n\n    if (info.value.state) |state| {\n        std.debug.print(\n            \"\\nStream has {d} messages.\\n\",\n            .{state.messages},\n        );\n    }\n\n    var del = try js.deleteStream(\"DEMO_ASYNC\");\n    del.deinit();\n\n    std.debug.print(\"Done!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/jetstream_consume.zig",
    "content": "//! JetStream Pull Consumer -- fetch, iterate, and consume\n//! patterns.\n//!\n//! Demonstrates three ways to receive messages from a pull\n//! consumer: batch fetch, single next(), and continuous\n//! MessagesContext iteration. Pull consumers are the\n//! recommended pattern for most workloads -- the client\n//! controls the pace.\n//!\n//! Run with: zig build run-jetstream-consume\n//!\n//! Prerequisites: nats-server -js\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://127.0.0.1:4222\",\n        .{ .name = \"js-consume-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    var js = try js_mod.JetStream.init(client, .{});\n\n    // Create a stream to hold our task messages\n    var stream_resp = try js.createStream(.{\n        .name = \"DEMO_CONSUME\",\n        .subjects = &.{\"tasks.>\"},\n        .storage = .memory,\n    });\n    stream_resp.deinit();\n\n    // Publish 10 task messages before consuming.\n    // In production, publishers and consumers run\n    // independently -- messages are persisted in the\n    // stream until acknowledged.\n    for (0..10) |i| {\n        var buf: [32]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"task {d}\",\n            .{i + 1},\n        ) catch \"task\";\n        var ack = try js.publish(\n            \"tasks.work\",\n            payload,\n        );\n        ack.deinit();\n    }\n    std.debug.print(\"Published 10 tasks.\\n\\n\", .{});\n\n    // Create a durable pull consumer named \"worker\".\n    // Explicit ack means the server waits for each\n    // message to be acknowledged before considering\n    // it delivered. Unacked messages are redelivered.\n    var cons_resp = try js.createConsumer(\n        \"DEMO_CONSUME\",\n        .{\n            .name = \"worker\",\n            .ack_policy = .explicit,\n        },\n    );\n    cons_resp.deinit();\n\n    // PullSubscription is a lightweight handle that\n    // binds to the stream + consumer pair. No heap\n    // allocation -- safe to copy/move.\n    var pull = js_mod.PullSubscription{\n        .js = &js,\n        .stream = \"DEMO_CONSUME\",\n    };\n    try pull.setConsumer(\"worker\");\n\n    // Pattern 1: Batch fetch -- get up to N messages\n    // in one round-trip. Efficient for bulk processing.\n    std.debug.print(\"-- Pattern 1: fetch --\\n\", .{});\n\n    var result = try pull.fetch(.{\n        .max_messages = 5,\n        .timeout_ms = 5000,\n    });\n    defer result.deinit();\n\n    var total: usize = 0;\n    for (result.messages) |*msg| {\n        std.debug.print(\n            \"  [{d}] {s}\\n\",\n            .{ total + 1, msg.data() },\n        );\n        // Always ack to tell the server we're done\n        // with this message. Without ack, the server\n        // will redeliver after ack_wait expires.\n        try msg.ack();\n        total += 1;\n    }\n    std.debug.print(\n        \"  Fetched {d} messages.\\n\\n\",\n        .{result.count()},\n    );\n\n    // Pattern 2: next() -- fetch a single message.\n    // Good for request-at-a-time processing or when\n    // you need fine-grained control.\n    std.debug.print(\n        \"-- Pattern 2: next --\\n\",\n        .{},\n    );\n\n    if (try pull.next(3000)) |*msg| {\n        var m = msg.*;\n        defer m.deinit();\n        std.debug.print(\n            \"  Got: {s}\\n\\n\",\n            .{m.data()},\n        );\n        try m.ack();\n        total += 1;\n    }\n\n    // Pattern 3: MessagesContext -- continuous iterator\n    // that auto-fetches new batches as needed. Best\n    // for long-running workers that process messages\n    // in a loop.\n    std.debug.print(\n        \"-- Pattern 3: messages --\\n\",\n        .{},\n    );\n\n    var msgs = try pull.messages(.{\n        .max_messages = 10,\n        .expires_ms = 5000,\n    });\n    defer msgs.deinit();\n\n    // Read remaining messages (4 left from our 10)\n    while (try msgs.next()) |*msg| {\n        var m = msg.*;\n        defer m.deinit();\n        std.debug.print(\n            \"  [{d}] {s}\\n\",\n            .{ total + 1, m.data() },\n        );\n        try m.ack();\n        total += 1;\n    }\n\n    std.debug.print(\n        \"\\nTotal processed: {d}\\n\",\n        .{total},\n    );\n\n    // Clean up stream\n    var del = try js.deleteStream(\"DEMO_CONSUME\");\n    del.deinit();\n\n    std.debug.print(\"Done!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/jetstream_publish.zig",
    "content": "//! JetStream Publish -- stream CRUD and publish with\n//! acknowledgment.\n//!\n//! Creates a stream, publishes messages with server-side\n//! acknowledgment, demonstrates deduplication via msg-id,\n//! and queries stream info.\n//!\n//! Run with: zig build run-jetstream-publish\n//!\n//! Prerequisites: nats-server -js\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\n// JetStream is accessed through the nats.jetstream module.\nconst js_mod = nats.jetstream;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    // Connect to NATS with JetStream enabled on the\n    // server. JetStream uses the same TCP connection\n    // as core NATS -- no extra ports needed.\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://127.0.0.1:4222\",\n        .{ .name = \"js-publish-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    // JetStream context is stack-allocated -- it holds\n    // a pointer to the client plus config (no heap).\n    var js = try js_mod.JetStream.init(client, .{});\n\n    // Create a memory-backed stream named DEMO_PUBLISH\n    // that captures all subjects matching \"demo.>\".\n    // Memory storage is fast but not persistent across\n    // server restarts.\n    var create_resp = try js.createStream(.{\n        .name = \"DEMO_PUBLISH\",\n        .subjects = &.{\"demo.>\"},\n        .storage = .memory,\n    });\n    create_resp.deinit();\n\n    std.debug.print(\n        \"Stream 'DEMO_PUBLISH' created.\\n\\n\",\n        .{},\n    );\n\n    // Publish 5 messages. Each publish returns a PubAck\n    // from the server confirming storage. The ack\n    // contains the stream name and sequence number.\n    for (0..5) |i| {\n        var buf: [64]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"order #{d}\",\n            .{i + 1},\n        ) catch \"order\";\n\n        var ack = try js.publish(\n            \"demo.orders\",\n            payload,\n        );\n        defer ack.deinit();\n\n        std.debug.print(\n            \"Published seq={d} stream={s}\\n\",\n            .{\n                ack.value.seq,\n                ack.value.stream orelse \"?\",\n            },\n        );\n    }\n\n    // Deduplication: publish with a msg-id header.\n    // If the same msg-id is sent within the stream's\n    // duplicate_window (default 2min), the server\n    // returns duplicate=true without storing again.\n    std.debug.print(\n        \"\\n-- Deduplication test --\\n\",\n        .{},\n    );\n\n    var ack1 = try js.publishWithOpts(\n        \"demo.orders\",\n        \"unique payload\",\n        .{ .msg_id = \"order-abc-123\" },\n    );\n    defer ack1.deinit();\n    std.debug.print(\"First:  seq={d} dup={}\\n\", .{\n        ack1.value.seq,\n        ack1.value.duplicate orelse false,\n    });\n\n    // Same msg-id again -- server detects duplicate\n    var ack2 = try js.publishWithOpts(\n        \"demo.orders\",\n        \"unique payload\",\n        .{ .msg_id = \"order-abc-123\" },\n    );\n    defer ack2.deinit();\n    std.debug.print(\"Second: seq={d} dup={}\\n\", .{\n        ack2.value.seq,\n        ack2.value.duplicate orelse false,\n    });\n\n    // Query stream info to see the message count\n    var info = try js.streamInfo(\"DEMO_PUBLISH\");\n    defer info.deinit();\n\n    if (info.value.state) |state| {\n        std.debug.print(\n            \"\\nStream has {d} messages\" ++\n                \" ({d} bytes)\\n\",\n            .{ state.messages, state.bytes },\n        );\n    }\n\n    // Clean up: delete the stream and all its data\n    var del = try js.deleteStream(\"DEMO_PUBLISH\");\n    del.deinit();\n\n    std.debug.print(\n        \"\\nStream deleted. Done!\\n\",\n        .{},\n    );\n}\n"
  },
  {
    "path": "src/examples/jetstream_push.zig",
    "content": "//! JetStream Push Consumer -- server-side message delivery.\n//!\n//! Push consumers have the server send messages to a\n//! deliver_subject. The client subscribes to that subject\n//! and processes messages via a callback handler.\n//!\n//! When to use push vs pull:\n//! - Pull: client controls pace, best for batch/worker\n//!   patterns, recommended for most use cases.\n//! - Push: server controls pace, good for real-time\n//!   fan-out, simpler for \"firehose\" scenarios.\n//!\n//! Run with: zig build run-jetstream-push\n//!\n//! Prerequisites: nats-server -js\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\n// Counter tracks how many messages the handler has\n// processed. Must implement onMessage for the\n// JsMsgHandler vtable interface. The JsMsg has\n// owned=false -- its slice fields are valid only\n// during this callback; do not save pointers past\n// the function return.\nconst Counter = struct {\n    received: u32 = 0,\n    target: u32 = 0,\n\n    pub fn onMessage(\n        self: *Counter,\n        msg: *js_mod.JsMsg,\n    ) void {\n        self.received += 1;\n        std.debug.print(\n            \"  [{d}/{d}] {s}: {s}\\n\",\n            .{\n                self.received,\n                self.target,\n                msg.subject(),\n                msg.data(),\n            },\n        );\n        // Push consumers with ack_policy=none don't\n        // need explicit acks. For explicit ack policy,\n        // call msg.ack() here.\n    }\n};\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://127.0.0.1:4222\",\n        .{ .name = \"js-push-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    var js = try js_mod.JetStream.init(client, .{});\n\n    var stream_resp = try js.createStream(.{\n        .name = \"DEMO_PUSH\",\n        .subjects = &.{\"events.>\"},\n        .storage = .memory,\n    });\n    stream_resp.deinit();\n\n    const msg_count: u32 = 5;\n\n    // Set up the push subscription BEFORE creating\n    // the consumer. The subscription must be active\n    // so it catches messages the server starts\n    // pushing immediately after consumer creation.\n    var push_sub = js_mod.PushSubscription{\n        .js = &js,\n        .stream = \"DEMO_PUSH\",\n    };\n    try push_sub.setConsumer(\"push-worker\");\n    try push_sub.setDeliverSubject(\n        \"_DELIVER.push-example\",\n    );\n\n    var counter = Counter{\n        .target = msg_count,\n    };\n\n    // consume() subscribes to the deliver subject\n    // and dispatches messages to our Counter handler\n    // on the IO thread.\n    var ctx = try push_sub.consume(\n        js_mod.JsMsgHandler.init(\n            Counter,\n            &counter,\n        ),\n        .{},\n    );\n    defer ctx.deinit();\n\n    // Now create the push consumer on the server.\n    // ack_policy=none means no ack required -- good\n    // for monitoring/logging where loss is acceptable.\n    var cons_resp = try js.createPushConsumer(\n        \"DEMO_PUSH\",\n        .{\n            .name = \"push-worker\",\n            .deliver_subject = \"_DELIVER.push-example\",\n            .ack_policy = .none,\n        },\n    );\n    cons_resp.deinit();\n\n    // Publish messages -- server pushes them to our\n    // deliver subject automatically.\n    for (0..msg_count) |i| {\n        var buf: [32]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"event {d}\",\n            .{i + 1},\n        ) catch \"event\";\n        var ack = try js.publish(\n            \"events.clicks\",\n            payload,\n        );\n        ack.deinit();\n    }\n\n    // Flush to ensure all publishes reach the server\n    try client.flush(2_000_000_000);\n\n    // Wait briefly for delivery to complete\n    var waited: u32 = 0;\n    while (counter.received < msg_count and\n        waited < 3000)\n    {\n        var ts: std.posix.timespec = .{\n            .sec = 0,\n            .nsec = 1_000_000,\n        };\n        _ = std.posix.system.nanosleep(\n            &ts,\n            &ts,\n        );\n        waited += 1;\n    }\n\n    std.debug.print(\n        \"\\nReceived {d}/{d} messages.\\n\",\n        .{ counter.received, msg_count },\n    );\n\n    var del = try js.deleteStream(\"DEMO_PUSH\");\n    del.deinit();\n\n    std.debug.print(\"Done!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/kv.zig",
    "content": "//! Key-Value Store -- CRUD, concurrency, listing.\n//!\n//! NATS KV is a distributed key-value store backed by\n//! JetStream. Keys are NATS subjects, values are message\n//! payloads. Supports history, optimistic concurrency\n//! (compare-and-swap via revision numbers), and TTL.\n//!\n//! Run with: zig build run-kv\n//!\n//! Prerequisites: nats-server -js\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://127.0.0.1:4222\",\n        .{ .name = \"kv-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    var js = try js_mod.JetStream.init(client, .{});\n\n    // Create a KV bucket with history=5. This means\n    // up to 5 revisions per key are kept. Backed by\n    // a JetStream stream named \"KV_demo-kv\".\n    var kv = try js.createKeyValue(.{\n        .bucket = \"demo-kv\",\n        .history = 5,\n        .storage = .memory,\n    });\n\n    std.debug.print(\n        \"Bucket 'demo-kv' created.\\n\\n\",\n        .{},\n    );\n\n    // -- Basic CRUD --\n\n    // Put stores a value and returns the revision\n    // (stream sequence number). Each put creates a\n    // new revision.\n    const rev1 = try kv.put(\"user.name\", \"Alice\");\n    std.debug.print(\n        \"Put 'user.name'='Alice' rev={d}\\n\",\n        .{rev1},\n    );\n\n    // Get returns the latest value for a key.\n    // Returns null if the key was never written.\n    if (try kv.get(\"user.name\")) |entry| {\n        var e = entry;\n        defer e.deinit();\n        std.debug.print(\n            \"Get 'user.name'='{s}' rev={d}\\n\",\n            .{ e.value, e.revision },\n        );\n    }\n\n    // Update overwrites the value. New revision\n    // returned.\n    const rev2 = try kv.put(\"user.name\", \"Bob\");\n    std.debug.print(\n        \"Put 'user.name'='Bob' rev={d}\\n\\n\",\n        .{rev2},\n    );\n\n    // -- Optimistic concurrency --\n    // update() takes a revision parameter: the write\n    // only succeeds if the key's current revision\n    // matches. This prevents lost updates when\n    // multiple clients write concurrently.\n\n    std.debug.print(\n        \"-- Optimistic concurrency --\\n\",\n        .{},\n    );\n\n    const rev3 = try kv.update(\n        \"user.name\",\n        \"Charlie\",\n        rev2,\n    );\n    std.debug.print(\n        \"Update with rev={d}: ok, new rev={d}\\n\",\n        .{ rev2, rev3 },\n    );\n\n    // Attempt to update with a stale revision.\n    // This simulates a concurrent writer that read\n    // an older value.\n    if (kv.update(\"user.name\", \"Dave\", rev1)) |_| {\n        std.debug.print(\"Unexpected success!\\n\", .{});\n    } else |_| {\n        std.debug.print(\n            \"Update with stale rev={d}: \" ++\n                \"rejected (expected)\\n\\n\",\n            .{rev1},\n        );\n    }\n\n    // -- Create (if not exists) --\n    // create() only succeeds if the key does not yet\n    // exist. Useful for distributed locks or\n    // one-time initialization.\n\n    std.debug.print(\n        \"-- Create if not exists --\\n\",\n        .{},\n    );\n\n    const email_rev = try kv.create(\n        \"user.email\",\n        \"alice@example.com\",\n    );\n    std.debug.print(\n        \"Created 'user.email' rev={d}\\n\",\n        .{email_rev},\n    );\n\n    // Second create fails because key already exists\n    if (kv.create(\"user.email\", \"bob@example.com\")) |_| {\n        std.debug.print(\"Unexpected success!\\n\", .{});\n    } else |_| {\n        std.debug.print(\n            \"Create duplicate: rejected\\n\\n\",\n            .{},\n        );\n    }\n\n    // -- Delete --\n    // Soft-delete publishes a delete marker. The key\n    // still appears in history but get() returns the\n    // delete marker with operation=.delete.\n\n    const del_rev = try kv.delete(\"user.email\");\n    std.debug.print(\n        \"Deleted 'user.email' rev={d}\\n\\n\",\n        .{del_rev},\n    );\n\n    // -- List keys --\n    // keys() returns all non-deleted keys in the\n    // bucket. Uses an ephemeral consumer under the\n    // hood.\n    std.debug.print(\"-- All keys --\\n\", .{});\n\n    // Add a few more keys for listing\n    _ = try kv.put(\"user.age\", \"30\");\n    _ = try kv.put(\"user.city\", \"Portland\");\n\n    const key_list = try kv.keys(allocator);\n    defer {\n        for (key_list) |k| allocator.free(k);\n        allocator.free(key_list);\n    }\n\n    for (key_list) |key| {\n        std.debug.print(\"  {s}\\n\", .{key});\n    }\n    std.debug.print(\n        \"  ({d} keys total)\\n\\n\",\n        .{key_list.len},\n    );\n\n    // -- History --\n    // history() returns all revisions for a key,\n    // including puts, updates, and deletes.\n    std.debug.print(\n        \"-- History for 'user.name' --\\n\",\n        .{},\n    );\n\n    const hist = try kv.history(\n        allocator,\n        \"user.name\",\n    );\n    defer {\n        for (hist) |*e| {\n            var entry = e.*;\n            entry.deinit();\n        }\n        allocator.free(hist);\n    }\n\n    for (hist) |entry| {\n        const op_str: []const u8 = switch (entry.operation) {\n            .put => \"PUT\",\n            .delete => \"DEL\",\n            .purge => \"PURGE\",\n        };\n        std.debug.print(\n            \"  rev={d} op={s} val='{s}'\\n\",\n            .{ entry.revision, op_str, entry.value },\n        );\n    }\n\n    // -- Bucket status --\n    // status() returns the underlying stream info\n    // for the KV bucket.\n    std.debug.print(\"\\n-- Bucket status --\\n\", .{});\n\n    var st = try kv.status();\n    defer st.deinit();\n\n    if (st.value.state) |state| {\n        std.debug.print(\n            \"  messages={d} bytes={d}\\n\",\n            .{ state.messages, state.bytes },\n        );\n    }\n\n    // -- Cleanup --\n    var del_resp = try js.deleteKeyValue(\"demo-kv\");\n    del_resp.deinit();\n\n    std.debug.print(\n        \"\\nBucket deleted. Done!\\n\",\n        .{},\n    );\n}\n"
  },
  {
    "path": "src/examples/kv_watch.zig",
    "content": "//! Key-Value Watch -- real-time change notifications.\n//!\n//! KV watch creates an ephemeral consumer that delivers\n//! change events as they happen. The watcher first\n//! delivers all existing matching keys (the \"initial\n//! values\"), then switches to live updates. A null from\n//! next() after the initial batch signals the transition\n//! to live mode.\n//!\n//! Run with: zig build run-kv-watch\n//!\n//! Prerequisites: nats-server -js\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst js_mod = nats.jetstream;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://127.0.0.1:4222\",\n        .{ .name = \"kv-watch-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    var js = try js_mod.JetStream.init(client, .{});\n\n    var kv = try js.createKeyValue(.{\n        .bucket = \"demo-watch\",\n        .storage = .memory,\n    });\n\n    // Seed an initial value before creating the\n    // watcher. This will appear as the first \"initial\n    // value\" delivered to the watcher.\n    _ = try kv.put(\"config.version\", \"1.0\");\n\n    std.debug.print(\n        \"Seeded 'config.version'='1.0'\\n\\n\",\n        .{},\n    );\n\n    // Watch all keys matching \"config.>\". The \">\"\n    // wildcard matches one or more tokens, so this\n    // catches config.version, config.debug, etc.\n    var watcher = try kv.watch(\"config.>\");\n    defer watcher.deinit();\n\n    // Read initial values. The watcher delivers all\n    // existing matching keys first. A null return\n    // signals that all existing keys have been\n    // delivered and we're now in live mode.\n    std.debug.print(\n        \"-- Initial values --\\n\",\n        .{},\n    );\n\n    while (try watcher.next(3000)) |entry| {\n        var e = entry;\n        defer e.deinit();\n        printEntry(&e);\n    }\n\n    std.debug.print(\n        \"  (initial sync complete)\\n\\n\",\n        .{},\n    );\n\n    // Now make some live changes. These will be\n    // delivered to the watcher in real time.\n    _ = try kv.put(\"config.version\", \"2.0\");\n    _ = try kv.put(\"config.debug\", \"true\");\n    _ = try kv.put(\"config.log_level\", \"info\");\n\n    // Flush to ensure all puts reach the server\n    try client.flush(2_000_000_000);\n\n    // Read live updates. Each put above generates\n    // one watcher event.\n    std.debug.print(\"-- Live updates --\\n\", .{});\n\n    var live_count: u32 = 0;\n    while (live_count < 3) {\n        if (try watcher.next(3000)) |entry| {\n            var e = entry;\n            defer e.deinit();\n            printEntry(&e);\n            live_count += 1;\n        } else break;\n    }\n\n    // Delete a key and watch the delete marker\n    _ = try kv.delete(\"config.debug\");\n    try client.flush(2_000_000_000);\n\n    std.debug.print(\n        \"\\n-- Delete event --\\n\",\n        .{},\n    );\n\n    if (try watcher.next(3000)) |entry| {\n        var e = entry;\n        defer e.deinit();\n        printEntry(&e);\n    }\n\n    // Cleanup\n    var del = try js.deleteKeyValue(\"demo-watch\");\n    del.deinit();\n\n    std.debug.print(\"\\nBucket deleted. Done!\\n\", .{});\n}\n\n/// Prints a KV entry showing key, value, operation,\n/// and revision. Handles all three operations: put,\n/// delete, and purge.\nfn printEntry(\n    entry: *const js_mod.KeyValueEntry,\n) void {\n    const op_str: []const u8 = switch (entry.operation) {\n        .put => \"PUT\",\n        .delete => \"DEL\",\n        .purge => \"PURGE\",\n    };\n    if (entry.operation == .put) {\n        std.debug.print(\n            \"  {s} '{s}'='{s}' rev={d}\\n\",\n            .{\n                op_str,\n                entry.key,\n                entry.value,\n                entry.revision,\n            },\n        );\n    } else {\n        std.debug.print(\n            \"  {s} '{s}' rev={d}\\n\",\n            .{\n                op_str,\n                entry.key,\n                entry.revision,\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "src/examples/micro_echo.zig",
    "content": "const std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Echo = struct {\n    pub fn onRequest(_: *@This(), req: *nats.micro.Request) void {\n        req.respond(req.data()) catch {};\n    }\n};\n\npub fn main(init: std.process.Init) !void {\n    const client = try nats.Client.connect(\n        init.gpa,\n        init.io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    var echo = Echo{};\n    const service = try nats.micro.addService(client, .{\n        .name = \"echo\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"echo\",\n            .handler = nats.micro.Handler.init(Echo, &echo),\n        },\n    });\n    defer service.deinit();\n\n    std.debug.print(\"micro echo service running on 'echo'\\n\", .{});\n    while (true) {\n        init.io.sleep(.fromSeconds(1), .awake) catch {};\n    }\n}\n"
  },
  {
    "path": "src/examples/polling_loop.zig",
    "content": "//! Non-Blocking Polling Pattern\n//!\n//! Demonstrates non-blocking message processing with tryNextMsg():\n//! - Event loop integration (check messages, do other work)\n//! - Multiple subscriptions with round-robin polling\n//! - Mixed workloads (NATS + other tasks)\n//!\n//! Use this pattern when you need to:\n//! - Integrate NATS into an existing event loop\n//! - Handle multiple subscriptions without threads\n//! - Do other work between message processing\n//!\n//! Run with: zig build run-polling-loop\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{ .name = \"polling-loop-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    // Subscribe to multiple subjects\n    const high_priority = try client.subscribeSync(\"priority.high\");\n    defer high_priority.deinit();\n\n    const normal = try client.subscribeSync(\"priority.normal\");\n    defer normal.deinit();\n\n    const low_priority = try client.subscribeSync(\"priority.low\");\n    defer low_priority.deinit();\n\n    std.debug.print(\"Subscribed to: priority.high, priority.normal, priority.low\\n\\n\", .{});\n\n    // Publish test messages with different priorities\n    try client.publish(\"priority.high\", \"URGENT: System alert!\");\n    try client.publish(\"priority.normal\", \"Info: User logged in\");\n    try client.publish(\"priority.low\", \"Debug: Cache refreshed\");\n    try client.publish(\"priority.high\", \"URGENT: Disk space low!\");\n    try client.publish(\"priority.normal\", \"Info: Report generated\");\n    try client.publish(\"priority.low\", \"Debug: Metrics collected\");\n\n    std.debug.print(\"Published 6 messages (2 high, 2 normal, 2 low)\\n\\n\", .{});\n\n    // Flush to ensure messages have been delivered\n    try client.flush(1_000_000_000);\n\n    // PRIORITY POLLING: Check high priority first, then others\n    std.debug.print(\"Priority polling (high -> normal -> low):\\n\", .{});\n\n    var high_count: u32 = 0;\n    var normal_count: u32 = 0;\n    var low_count: u32 = 0;\n    var iterations: u32 = 0;\n    const max_iterations: u32 = 20;\n\n    while (iterations < max_iterations) : (iterations += 1) {\n        var processed_any = false;\n\n        // Always check high priority first (drain completely)\n        while (high_priority.tryNextMsg()) |msg| {\n            defer msg.deinit();\n            high_count += 1;\n            processed_any = true;\n            std.debug.print(\"  [HIGH] {s}\\n\", .{msg.data});\n        }\n\n        // Then check normal priority (one at a time)\n        if (normal.tryNextMsg()) |msg| {\n            defer msg.deinit();\n            normal_count += 1;\n            processed_any = true;\n            std.debug.print(\"  [NORMAL] {s}\\n\", .{msg.data});\n        }\n\n        // Finally check low priority (one at a time)\n        if (low_priority.tryNextMsg()) |msg| {\n            defer msg.deinit();\n            low_count += 1;\n            processed_any = true;\n            std.debug.print(\"  [LOW] {s}\\n\", .{msg.data});\n        }\n\n        // Do other work if no messages\n        if (!processed_any) {\n            // In a real app, this is where you'd do other event loop work\n            if (iterations < 5) {\n                std.debug.print(\"  (no messages - doing other work...)\\n\", .{});\n            }\n            io.sleep(.fromMilliseconds(10), .awake) catch {};\n        }\n\n        // Exit when all messages processed\n        if (high_count >= 2 and normal_count >= 2 and low_count >= 2) {\n            break;\n        }\n    }\n\n    std.debug.print(\"\\nProcessed: {d} high, {d} normal, {d} low\\n\", .{\n        high_count,\n        normal_count,\n        low_count,\n    });\n\n    // ROUND-ROBIN POLLING: Fair scheduling across subscriptions\n    std.debug.print(\"\\n--- Round-Robin Polling Demo ---\\n\\n\", .{});\n\n    // Publish more messages\n    for (0..3) |i| {\n        var buf: [64]u8 = undefined;\n        const high_msg = std.fmt.bufPrint(&buf, \"High {d}\", .{i + 1}) catch \"High\";\n        try client.publish(\"priority.high\", high_msg);\n\n        const norm_msg = std.fmt.bufPrint(&buf, \"Normal {d}\", .{i + 1}) catch \"Normal\";\n        try client.publish(\"priority.normal\", norm_msg);\n\n        const low_msg = std.fmt.bufPrint(&buf, \"Low {d}\", .{i + 1}) catch \"Low\";\n        try client.publish(\"priority.low\", low_msg);\n    }\n    try client.flush(1_000_000_000);\n\n    std.debug.print(\"Published 9 more messages (3 each)\\n\", .{});\n    std.debug.print(\"Round-robin processing:\\n\", .{});\n\n    const subs = [_]*nats.Client.Sub{ high_priority, normal, low_priority };\n    const names = [_][]const u8{ \"HIGH\", \"NORMAL\", \"LOW\" };\n    var idx: usize = 0;\n    var total: u32 = 0;\n\n    while (total < 9) {\n        if (subs[idx].tryNextMsg()) |msg| {\n            defer msg.deinit();\n            total += 1;\n            std.debug.print(\"  [{s}] {s}\\n\", .{ names[idx], msg.data });\n        }\n        idx = (idx + 1) % 3; // Round-robin to next subscription\n\n        // Safety: prevent infinite loop if messages don't arrive\n        if (idx == 0) {\n            io.sleep(.fromMilliseconds(10), .awake) catch {};\n        }\n    }\n\n    std.debug.print(\"\\nTotal processed: {d}\\n\", .{total});\n    std.debug.print(\"\\nDone!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/queue_groups.zig",
    "content": "//! Queue Groups - Load-Balanced Workers\n//!\n//! Demonstrates horizontal scaling with NATS queue groups. Multiple workers\n//! subscribe to the same subject with the same queue group name - NATS\n//! distributes messages round-robin among them.\n//!\n//! This example uses io.concurrent() to run workers in parallel threads,\n//! pushing results to a shared Io.Queue for the main loop to consume.\n//!\n//! Run with: zig build run-queue-groups\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Io = std.Io;\n\nconst WorkerResult = struct {\n    worker_id: u8,\n    data: []const u8,\n    msg: nats.Message,\n\n    fn deinit(self: WorkerResult) void {\n        self.msg.deinit();\n    }\n};\n\nfn workerTask(\n    io: Io,\n    worker_id: u8,\n    sub: *nats.Client.Sub,\n    queue: *Io.Queue(WorkerResult),\n    done: *std.atomic.Value(bool),\n) void {\n    while (!done.load(.acquire)) {\n        const msg = sub.nextMsgTimeout(100) catch return orelse continue;\n        queue.putOne(io, .{\n            .worker_id = worker_id,\n            .data = msg.data,\n            .msg = msg,\n        }) catch {\n            msg.deinit();\n            return;\n        };\n    }\n}\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{ .name = \"queue-groups-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    // Create 3 workers in queue group\n    const worker1 = try client.queueSubscribeSync(\"tasks\", \"workers\");\n    defer worker1.deinit();\n\n    const worker2 = try client.queueSubscribeSync(\"tasks\", \"workers\");\n    defer worker2.deinit();\n\n    const worker3 = try client.queueSubscribeSync(\"tasks\", \"workers\");\n    defer worker3.deinit();\n\n    std.debug.print(\"Created 3 workers in queue group 'workers'\\n\", .{});\n\n    // Shared queue for results\n    var queue_buf: [32]WorkerResult = undefined;\n    var queue: Io.Queue(WorkerResult) = .init(&queue_buf);\n    var done: std.atomic.Value(bool) = .init(false);\n\n    // Launch workers in TRUE parallel threads (return void, so no catch)\n    var w1 = try io.concurrent(workerTask, .{\n        io, 1, worker1, &queue, &done,\n    });\n    defer w1.cancel(io);\n\n    var w2 = try io.concurrent(workerTask, .{\n        io, 2, worker2, &queue, &done,\n    });\n    defer w2.cancel(io);\n\n    var w3 = try io.concurrent(workerTask, .{\n        io, 3, worker3, &queue, &done,\n    });\n    defer w3.cancel(io);\n\n    // Publish messages\n    const message_count: u32 = 9;\n    std.debug.print(\"\\nPublishing {d} messages...\\n\\n\", .{message_count});\n\n    for (0..message_count) |i| {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(&buf, \"Task {d}\", .{i + 1}) catch \"Task\";\n        try client.publish(\"tasks\", msg);\n    }\n\n    // Consume results from queue\n    var counts = [3]u32{ 0, 0, 0 };\n    var total_received: u32 = 0;\n\n    std.debug.print(\"Receiving from concurrent workers:\\n\", .{});\n\n    while (total_received < message_count) {\n        const result = queue.getOne(io) catch break;\n        defer result.deinit();\n        counts[result.worker_id - 1] += 1;\n        total_received += 1;\n        std.debug.print(\n            \"  Worker {d} received: {s}\\n\",\n            .{ result.worker_id, result.data },\n        );\n    }\n\n    // Signal workers to stop\n    done.store(true, .release);\n\n    std.debug.print(\"\\nDistribution summary:\\n\", .{});\n    for (counts, 0..) |count, idx| {\n        std.debug.print(\"  Worker {d}: {d} messages\\n\", .{ idx + 1, count });\n    }\n\n    std.debug.print(\"\\nDone!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/reconnection.zig",
    "content": "//! Reconnection and Resilience\n//!\n//! Demonstrates NATS client resilience features:\n//! - Reconnection configuration options\n//! - Connection state monitoring\n//! - Handling publish during disconnect\n//!\n//! Run with: zig build run-reconnection\n//!\n//! To test reconnection:\n//! 1. Start nats-server\n//! 2. Run this example\n//! 3. Restart nats-server while example is running\n//! 4. Watch the client reconnect automatically\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    std.debug.print(\"Connecting with reconnection enabled...\\n\", .{});\n\n    // Connect with explicit reconnection settings\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{\n            .name = \"reconnection-example\",\n\n            // Reconnection settings\n            .reconnect = true, // Enable auto-reconnect (default)\n            .max_reconnect_attempts = 10, // Max attempts (0 = infinite)\n            .reconnect_wait_ms = 1000, // Initial backoff: 1 second\n            .reconnect_wait_max_ms = 10_000, // Max backoff: 10 seconds\n            .reconnect_jitter_percent = 10, // Add 10% jitter to backoff\n\n            // Keepalive settings (detect stale connections)\n            .ping_interval_ms = 30_000, // PING every 30 seconds\n            .max_pings_outstanding = 2, // Disconnect after 2 missed PONGs\n\n            // Buffer publishes during reconnect (8MB default)\n            .pending_buffer_size = 8 * 1024 * 1024,\n        },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected!\\n\", .{});\n    printConnectionInfo(client);\n\n    // Subscribe\n    const sub = try client.subscribeSync(\"demo.reconnect\");\n    defer sub.deinit();\n\n    std.debug.print(\"\\nSubscribed to 'demo.reconnect'\\n\", .{});\n    std.debug.print(\"Monitoring connection for 10 seconds...\\n\", .{});\n    std.debug.print(\"(Try restarting nats-server to see reconnection)\\n\\n\", .{});\n\n    // Monitor connection and publish periodically\n    var iteration: u32 = 0;\n    const max_iterations: u32 = 20;\n\n    while (iteration < max_iterations) : (iteration += 1) {\n        io.sleep(.fromMilliseconds(500), .awake) catch {};\n\n        // Check connection state\n        const connected = client.isConnected();\n        const state_str = if (connected) \"CONNECTED\" else \"DISCONNECTED\";\n\n        // Try to publish\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(&buf, \"Ping {d}\", .{iteration + 1}) catch \"Ping\";\n\n        if (client.publish(\"demo.reconnect\", msg)) {\n            std.debug.print(\n                \"[{d:2}] {s} - Published: {s}\\n\",\n                .{ iteration + 1, state_str, msg },\n            );\n        } else |pub_err| {\n            std.debug.print(\n                \"[{d:2}] {s} - Publish failed: {}\\n\",\n                .{ iteration + 1, state_str, pub_err },\n            );\n        }\n\n        // Try to receive any messages\n        while (sub.tryNextMsg()) |recv_msg| {\n            defer recv_msg.deinit();\n            std.debug.print(\"      Received: {s}\\n\", .{recv_msg.data});\n        }\n\n        // Print reconnection stats periodically\n        if (iteration > 0 and (iteration + 1) % 5 == 0) {\n            printStats(client);\n        }\n    }\n\n    std.debug.print(\"\\nFinal connection state:\\n\", .{});\n    printConnectionInfo(client);\n    printStats(client);\n\n    std.debug.print(\"\\nDone!\\n\", .{});\n}\n\nfn printConnectionInfo(client: *nats.Client) void {\n    std.debug.print(\"Connection info:\\n\", .{});\n    std.debug.print(\"  Connected: {}\\n\", .{client.isConnected()});\n\n    if (client.serverInfo()) |info| {\n        if (info.server_name.len > 0) {\n            std.debug.print(\"  Server: {s}\\n\", .{info.server_name});\n        }\n        if (info.version.len > 0) {\n            std.debug.print(\"  Version: {s}\\n\", .{info.version});\n        }\n        std.debug.print(\"  Max payload: {d} bytes\\n\", .{info.max_payload});\n    }\n}\n\nfn printStats(client: *nats.Client) void {\n    const stats = client.stats();\n    std.debug.print(\"  Stats: {d} msgs out, {d} msgs in, {d} reconnects\\n\", .{\n        stats.msgs_out,\n        stats.msgs_in,\n        stats.reconnects,\n    });\n}\n"
  },
  {
    "path": "src/examples/request_reply.zig",
    "content": "//! Request/Reply Pattern\n//!\n//! Demonstrates RPC-style request/reply communication.\n//! Run with: zig build run-request-reply\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst io_backend = @import(\"io_backend\");\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    var service_backend: io_backend.Backend = undefined;\n    try io_backend.init(&service_backend, allocator);\n    defer service_backend.deinit();\n    const service_io = service_backend.io();\n\n    var requester_backend: io_backend.Backend = undefined;\n    try io_backend.init(&requester_backend, allocator);\n    defer requester_backend.deinit();\n    const requester_io = requester_backend.io();\n\n    // Service client\n    const service_client = try nats.Client.connect(\n        allocator,\n        service_io,\n        \"nats://localhost:4222\",\n        .{ .name = \"service\" },\n    );\n    defer service_client.deinit();\n\n    // Requester client\n    const requester = try nats.Client.connect(\n        allocator,\n        requester_io,\n        \"nats://localhost:4222\",\n        .{ .name = \"requester\" },\n    );\n    defer requester.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    // Service subscribes to handle requests\n    const service = try service_client.subscribeSync(\"math.double\");\n    defer service.deinit();\n\n    std.debug.print(\"Service listening on 'math.double'\\n\", .{});\n\n    // Run service handler in background (returns void, so no catch)\n    var service_future = service_io.async(handleService, .{\n        service_client,\n        service,\n    });\n    defer service_future.cancel(service_io);\n\n    // Flush to ensure server has registered the subscription\n    try service_client.flush(1_000_000_000);\n\n    // Send request using client.request() - handles inbox automatically\n    std.debug.print(\"\\nRequester: What is 21 * 2?\\n\", .{});\n\n    if (try requester.request(\"math.double\", \"21\", 1000)) |reply| {\n        defer reply.deinit();\n        std.debug.print(\"Reply: {s}\\n\", .{reply.data});\n    } else {\n        std.debug.print(\"Request timed out\\n\", .{});\n    }\n\n    std.debug.print(\"\\nDone!\\n\", .{});\n}\n\nfn handleService(\n    client: *nats.Client,\n    service: *nats.Client.Sub,\n) void {\n    const req = service.nextMsgTimeout(2000) catch return;\n    if (req) |r| {\n        defer r.deinit();\n\n        const num = std.fmt.parseInt(i32, r.data, 10) catch 0;\n        var buf: [32]u8 = undefined;\n        const result = std.fmt.bufPrint(&buf, \"{d}\", .{num * 2}) catch \"error\";\n\n        std.debug.print(\"Service: {d} * 2 = {s}\\n\", .{ num, result });\n\n        if (r.reply_to) |reply_to| {\n            client.publish(reply_to, result) catch {};\n        }\n    }\n}\n"
  },
  {
    "path": "src/examples/request_reply_callback.zig",
    "content": "//! Request/Reply with Callback Subscription\n//!\n//! Demonstrates building a service responder using callback-style\n//! subscriptions. The service handler receives requests via\n//! onMessage and sends replies using msg.respond().\n//!\n//! Run with: zig build run-request-reply-callback\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server -DV\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst io_backend = @import(\"io_backend\");\n\n/// Doubler service -- doubles any number sent to it.\nconst DoublerService = struct {\n    client: *nats.Client,\n    handled: u32 = 0,\n\n    pub fn onMessage(\n        self: *@This(),\n        msg: *const nats.Message,\n    ) void {\n        self.handled += 1;\n\n        const num = std.fmt.parseInt(\n            i32,\n            msg.data,\n            10,\n        ) catch 0;\n        var buf: [32]u8 = undefined;\n        const result = std.fmt.bufPrint(\n            &buf,\n            \"{d}\",\n            .{num * 2},\n        ) catch \"error\";\n\n        std.debug.print(\n            \"  [service] {d} * 2 = {s}\\n\",\n            .{ num, result },\n        );\n\n        msg.respond(self.client, result) catch |err| {\n            std.debug.print(\n                \"  [service] respond failed: {s}\\n\",\n                .{@errorName(err)},\n            );\n        };\n    }\n};\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    var service_backend: io_backend.Backend = undefined;\n    try io_backend.init(&service_backend, allocator);\n    defer service_backend.deinit();\n    const service_io = service_backend.io();\n\n    var requester_backend: io_backend.Backend = undefined;\n    try io_backend.init(&requester_backend, allocator);\n    defer requester_backend.deinit();\n    const requester_io = requester_backend.io();\n\n    // Service client\n    const service_client = try nats.Client.connect(\n        allocator,\n        service_io,\n        \"nats://localhost:4222\",\n        .{ .name = \"doubler-service\" },\n    );\n    defer service_client.deinit();\n\n    // Requester client\n    const requester = try nats.Client.connect(\n        allocator,\n        requester_io,\n        \"nats://localhost:4222\",\n        .{ .name = \"requester\" },\n    );\n    defer requester.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\\n\", .{});\n\n    // Start service with callback subscription\n    var svc = DoublerService{ .client = service_client };\n\n    const sub = try service_client.subscribe(\n        \"math.double\",\n        nats.MsgHandler.init(DoublerService, &svc),\n    );\n    defer sub.deinit();\n\n    std.debug.print(\"Service listening on 'math.double'\\n\\n\", .{});\n\n    // Flush to ensure server has registered the subscription\n    try service_client.flush(1_000_000_000);\n\n    // Send requests\n    const numbers = [_][]const u8{ \"21\", \"50\", \"100\" };\n    for (numbers) |n| {\n        std.debug.print(\"Requesting: {s} * 2\\n\", .{n});\n        if (try requester.request(\"math.double\", n, 1000)) |reply| {\n            defer reply.deinit();\n            std.debug.print(\"  Reply: {s}\\n\\n\", .{reply.data});\n        } else {\n            std.debug.print(\"  Timed out\\n\\n\", .{});\n        }\n    }\n\n    std.debug.print(\n        \"Service handled {d} requests.\\n\",\n        .{svc.handled},\n    );\n\n    // Verify all requests were handled\n    std.debug.assert(svc.handled == 3);\n\n    std.debug.print(\"Done!\\n\", .{});\n}\n"
  },
  {
    "path": "src/examples/select.zig",
    "content": "//! Io.Select Pattern - Subscription with Timeout\n//!\n//! Demonstrates Io.Select to race a subscription receive against a\n//! timeout. This is the correct use case for Io.Select with NATS -\n//! racing ONE subscription against a non-resource operation like sleep.\n//!\n//! NOTE: Do NOT use Io.Select to race multiple subscriptions -\n//! cancelling a subscription task discards any message it received.\n//! Use polling or io.concurrent() + Io.Queue instead (see\n//! polling_loop.zig and queue_groups.zig).\n//!\n//! Run with: zig build run-select\n//!\n//! Prerequisites: nats-server running on localhost:4222\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst Io = std.Io;\nconst Sub = nats.Client.Sub;\nconst Message = nats.Message;\n\n/// Sleep function compatible with Io.Select.async()\nfn sleepMs(io: Io, ms: i64) void {\n    io.sleep(.fromMilliseconds(ms), .awake) catch {};\n}\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    const io = init.io;\n\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{ .name = \"select-example\" },\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    const sub = try client.subscribeSync(\"demo.select\");\n    defer sub.deinit();\n\n    std.debug.print(\"Subscribed to 'demo.select'\\n\", .{});\n    std.debug.print(\n        \"\\nPublishing 3 messages with 200ms gaps...\\n\",\n        .{},\n    );\n    std.debug.print(\n        \"Using 500ms timeout - should receive all 3.\\n\\n\",\n        .{},\n    );\n\n    // Spawn publisher in background\n    var publisher = io.async(publishMessages, .{ client, io });\n    defer publisher.cancel(io);\n\n    // Receive with timeout using Io.Select\n    var received: u32 = 0;\n    const max_attempts = 5;\n\n    const Sel = Io.Select(union(enum) {\n        message: anyerror!Message,\n        timeout: void,\n    });\n\n    for (0..max_attempts) |attempt| {\n        var buf: [2]Sel.Union = undefined;\n        var sel = Sel.init(io, &buf);\n        sel.async(.message, Sub.nextMsg, .{sub});\n        sel.async(.timeout, sleepMs, .{ io, 500 });\n\n        // Wait for EITHER message OR timeout\n        const result = sel.await() catch {\n            // Cancel remaining tasks, deinit any messages\n            while (sel.cancel()) |remaining| {\n                switch (remaining) {\n                    .message => |r| {\n                        if (r) |m| m.deinit() else |_| {}\n                    },\n                    .timeout => {},\n                }\n            }\n            break;\n        };\n        // Cancel the loser task\n        while (sel.cancel()) |remaining| {\n            switch (remaining) {\n                .message => |r| {\n                    if (r) |m| m.deinit() else |_| {}\n                },\n                .timeout => {},\n            }\n        }\n\n        switch (result) {\n            .message => |msg_result| {\n                const msg = msg_result catch continue;\n                defer msg.deinit();\n                received += 1;\n                std.debug.print(\n                    \"  [{d}] Received: {s}\\n\",\n                    .{ attempt + 1, msg.data },\n                );\n            },\n            .timeout => {\n                std.debug.print(\n                    \"  [{d}] Timeout - no message\\n\",\n                    .{attempt + 1},\n                );\n            },\n        }\n    }\n\n    std.debug.print(\"\\nReceived {d} messages in {d} attempts.\\n\", .{\n        received,\n        max_attempts,\n    });\n    std.debug.print(\"Done!\\n\", .{});\n}\n\nfn publishMessages(\n    client: *nats.Client,\n    io: Io,\n) void {\n    io.sleep(.fromMilliseconds(100), .awake) catch {};\n\n    for (1..4) |i| {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"Message {d}\",\n            .{i},\n        ) catch \"Msg\";\n        client.publish(\"demo.select\", msg) catch return;\n        io.sleep(.fromMilliseconds(200), .awake) catch {};\n    }\n}\n"
  },
  {
    "path": "src/examples/simple.zig",
    "content": "//! Simple NATS Example\n//!\n//! Minimal \"hello world\" - connect, subscribe, publish, receive one message.\n//! A starting point for learning the NATS Zig client.\n//! Run with: zig build run-simple\n//!   or:    zig build run-simple -Dio_backend=evented\n//!\n//! Prerequisites: nats-server running on localhost:4222\n//!   nats-server -DV\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\nconst io_backend = @import(\"io_backend\");\n\n/// Main entry point using Zig 0.16's std.process.Init.\n/// Init provides: gpa (allocator), io (async I/O), arena, args, environ.\n///\n/// IMPORTANT: each Client needs its own Io. We create the backend\n/// here next to the Client so it owns its own execution context.\n/// We deliberately do NOT reuse `init.io` so the build option\n/// `-Dio_backend=...` can pick between Threaded and Evented.\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    var backend: io_backend.Backend = undefined;\n    try io_backend.init(&backend, allocator);\n    defer backend.deinit();\n    const io = backend.io();\n\n    // Connect to NATS server\n    const client = try nats.Client.connect(\n        allocator,\n        io,\n        \"nats://localhost:4222\",\n        .{},\n    );\n    defer client.deinit();\n\n    std.debug.print(\"Connected to NATS!\\n\", .{});\n\n    // Subscribe to a subject\n    const sub = try client.subscribeSync(\"hello\");\n    defer sub.deinit();\n\n    // Publish a message\n    try client.publish(\"hello\", \"Hello, NATS!\");\n\n    // Receive the message\n    if (try sub.nextMsgTimeout(1000)) |msg| {\n        defer msg.deinit();\n        std.debug.print(\"Received: {s}\\n\", .{msg.data});\n    }\n\n    std.debug.print(\"Done!\\n\", .{});\n}\n"
  },
  {
    "path": "src/io_backend.zig",
    "content": "//! Comptime selector for the std.Io backend used by entry points.\n//!\n//! Picks between `std.Io.Threaded` (default) and `std.Io.Evented`\n//! at compile time, based on the `-Dio_backend=threaded|evented`\n//! build option. The library itself is backend-agnostic and\n//! accepts any `std.Io` via `Client.connect`; this module exists\n//! purely so applications, examples, and integration tests can\n//! flip backends without code changes.\n//!\n//! Usage:\n//! ```\n//! const io_backend = @import(\"io_backend\");\n//! var backend: io_backend.Backend = undefined;\n//! try io_backend.init(&backend, gpa);\n//! defer backend.deinit();\n//! const io = backend.io();\n//! var client = try nats.Client.connect(gpa, io, url, .{});\n//! defer client.deinit();\n//! ```\n\nconst std = @import(\"std\");\nconst build_options = @import(\"build_options\");\n\nconst want_evented = std.mem.eql(\n    u8,\n    build_options.io_backend,\n    \"evented\",\n);\n\n/// The selected Io backend type, chosen at compile time from the\n/// `-Dio_backend=...` build option. Defaults to `std.Io.Threaded`.\npub const Backend = if (want_evented) blk: {\n    if (std.Io.Evented == void) @compileError(\n        \"std.Io.Evented is not supported on this target. \" ++\n            \"Build with -Dio_backend=threaded.\",\n    );\n    break :blk std.Io.Evented;\n} else std.Io.Threaded;\n\ncomptime {\n    std.debug.assert(@sizeOf(Backend) > 0);\n}\n\n/// Initialize the selected backend in place with default options.\n/// Caller owns the result and must call `Backend.deinit()`.\n///\n/// `out` may be undefined on entry; it is fully initialized on\n/// successful return.\n///\n/// Threaded init cannot fail and Uring/Kqueue/Dispatch init can,\n/// so the wrapper is uniformly errorable.\npub fn init(out: *Backend, gpa: std.mem.Allocator) !void {\n    return initWithEnviron(out, gpa, .empty);\n}\n\n/// Initialize the selected backend with a process environment.\n///\n/// This matters for entry points that spawn child processes: std.Io resolves\n/// `argv[0]` through the environment stored in the Io backend, not through\n/// later shell state.\npub fn initWithEnviron(\n    out: *Backend,\n    gpa: std.mem.Allocator,\n    environ: std.process.Environ,\n) !void {\n    std.debug.assert(@sizeOf(Backend) > 0);\n    if (Backend == std.Io.Threaded) {\n        out.* = std.Io.Threaded.init(gpa, .{ .environ = environ });\n    } else {\n        try Backend.init(out, gpa, .{ .environ = environ });\n    }\n}\n\ntest \"Backend type is selectable at comptime\" {\n    try std.testing.expect(@sizeOf(Backend) > 0);\n    try std.testing.expect(@hasDecl(Backend, \"io\"));\n    try std.testing.expect(@hasDecl(Backend, \"deinit\"));\n}\n"
  },
  {
    "path": "src/jetstream/JetStream.zig",
    "content": "//! JetStream context providing stream/consumer CRUD, publish,\n//! and pull subscription operations over core NATS request/reply.\n\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\nconst types = @import(\"types.zig\");\nconst errors = @import(\"errors.zig\");\nconst publish_headers = @import(\"publish_headers.zig\");\n\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\nconst headers = nats.protocol.headers;\nconst pubsub = @import(\"../pubsub.zig\");\n\npub const Response = types.Response;\npub const StreamConfig = types.StreamConfig;\npub const StreamInfo = types.StreamInfo;\npub const ConsumerConfig = types.ConsumerConfig;\npub const ConsumerInfo = types.ConsumerInfo;\npub const CreateConsumerRequest = types.CreateConsumerRequest;\npub const DeleteResponse = types.DeleteResponse;\npub const PurgeResponse = types.PurgeResponse;\npub const ConsumerPauseResponse = types.ConsumerPauseResponse;\npub const PubAck = types.PubAck;\npub const PublishOpts = types.PublishOpts;\npub const StreamNamesResponse = types.StreamNamesResponse;\npub const StreamListResponse = types.StreamListResponse;\npub const ConsumerNamesResponse = types.ConsumerNamesResponse;\npub const ConsumerListResponse = types.ConsumerListResponse;\npub const ListRequest = types.ListRequest;\npub const AccountInfo = types.AccountInfo;\nconst StorageType = types.StorageType;\nconst PushSubscription = @import(\n    \"push.zig\",\n).PushSubscription;\npub const ApiError = errors.ApiError;\npub const ApiErrorJson = errors.ApiErrorJson;\n\nconst JetStream = @This();\n\nfn returnsErrorUnion(comptime f: anytype) bool {\n    const ret = @typeInfo(@TypeOf(f)).@\"fn\".return_type orelse return false;\n    return switch (@typeInfo(ret)) {\n        .error_union => true,\n        else => false,\n    };\n}\n\nclient: *Client,\nallocator: Allocator,\napi_prefix_buf: [128]u8 = undefined,\napi_prefix_len: u8 = 0,\ntimeout_ms: u32 = 5000,\nlast_api_err: ?ApiError = null,\n\n/// JetStream context options for API prefix, timeout, and\n/// multi-tenant domain configuration.\npub const Options = struct {\n    api_prefix: []const u8 = \"$JS.API.\",\n    timeout_ms: u32 = 5000,\n    domain: ?[]const u8 = null,\n};\n\npub fn validateName(name: []const u8) errors.Error!void {\n    if (name.len == 0) return errors.Error.InvalidName;\n    for (name) |c| {\n        if (c <= 0x20 or c == 0x7f or\n            c == '.' or c == '*' or c == '>' or\n            c == '/' or c == '\\\\')\n        {\n            return errors.Error.InvalidName;\n        }\n    }\n}\n\npub fn validateBucketName(bucket: []const u8) errors.Error!void {\n    if (bucket.len == 0) return errors.Error.InvalidBucket;\n    if (bucket.len > 64) return errors.Error.NameTooLong;\n    for (bucket) |c| {\n        if (c <= 0x20 or c == 0x7f or\n            c == '.' or c == '*' or c == '>' or\n            c == '/' or c == '\\\\')\n        {\n            return errors.Error.InvalidBucket;\n        }\n    }\n}\n\nfn validateApiPrefix(prefix: []const u8) errors.Error!void {\n    if (prefix.len == 0) return errors.Error.InvalidApiPrefix;\n    if (prefix.len > 128) return errors.Error.NameTooLong;\n    for (prefix) |c| {\n        if (c <= 0x20 or c == 0x7f or c == '*' or c == '>') {\n            return errors.Error.InvalidApiPrefix;\n        }\n    }\n}\n\n/// Initializes a JetStream context bound to the given client.\npub fn init(client: *Client, opts: Options) !JetStream {\n    std.debug.assert(client.isConnected());\n    var js = JetStream{\n        .client = client,\n        .allocator = client.allocator,\n        .timeout_ms = opts.timeout_ms,\n    };\n    if (opts.domain) |d| {\n        try validateName(d);\n        // \"$JS.\" + domain + \".API.\" = 9 overhead\n        if (d.len > 119) return errors.Error.NameTooLong;\n        var buf: [128]u8 = undefined;\n        const p = std.fmt.bufPrint(\n            &buf,\n            \"$JS.{s}.API.\",\n            .{d},\n        ) catch unreachable;\n        @memcpy(\n            js.api_prefix_buf[0..p.len],\n            p,\n        );\n        js.api_prefix_len = @intCast(p.len);\n    } else {\n        const p = opts.api_prefix;\n        try validateApiPrefix(p);\n        if (p.len > js.api_prefix_buf.len) return errors.Error.NameTooLong;\n        @memcpy(js.api_prefix_buf[0..p.len], p);\n        js.api_prefix_len = @intCast(p.len);\n    }\n    return js;\n}\n\n/// Returns the last API error from the server, if any.\npub fn lastApiError(self: *const JetStream) ?ApiError {\n    return self.last_api_err;\n}\n\n// -- Stream CRUD --\n\n/// Creates a stream with the given configuration.\npub fn createStream(\n    self: *JetStream,\n    config: StreamConfig,\n) !Response(StreamInfo) {\n    try validateName(config.name);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.CREATE.{s}\",\n        .{config.name},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(StreamInfo, subj, config);\n}\n\n/// Updates a stream with the given configuration.\npub fn updateStream(\n    self: *JetStream,\n    config: StreamConfig,\n) !Response(StreamInfo) {\n    try validateName(config.name);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.UPDATE.{s}\",\n        .{config.name},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(StreamInfo, subj, config);\n}\n\n/// Deletes a stream by name.\npub fn deleteStream(\n    self: *JetStream,\n    name: []const u8,\n) !Response(DeleteResponse) {\n    try validateName(name);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.DELETE.{s}\",\n        .{name},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequestNoPayload(\n        DeleteResponse,\n        subj,\n    );\n}\n\n/// Creates a stream or updates it if it already exists.\n/// Tries update first; falls back to create on\n/// stream_not_found (matches Go client behavior).\npub fn createOrUpdateStream(\n    self: *JetStream,\n    config: StreamConfig,\n) !Response(StreamInfo) {\n    try validateName(config.name);\n    std.debug.assert(self.timeout_ms > 0);\n    return self.updateStream(config) catch |err| {\n        if (err == error.ApiError) {\n            if (self.lastApiError()) |ae| {\n                if (ae.err_code ==\n                    errors.ErrCode.stream_not_found)\n                    return self.createStream(config);\n            }\n        }\n        return err;\n    };\n}\n\n/// Gets stream info by name.\npub fn streamInfo(\n    self: *JetStream,\n    name: []const u8,\n) !Response(StreamInfo) {\n    try validateName(name);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.INFO.{s}\",\n        .{name},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequestNoPayload(StreamInfo, subj);\n}\n\n/// Purges a stream by name. Optionally filter by\n/// subject to only purge matching messages.\npub fn purgeStream(\n    self: *JetStream,\n    name: []const u8,\n) !Response(PurgeResponse) {\n    return self.purgeStreamFiltered(name, null);\n}\n\n/// Purges messages matching a specific subject.\npub fn purgeStreamSubject(\n    self: *JetStream,\n    name: []const u8,\n    subject: []const u8,\n) !Response(PurgeResponse) {\n    try pubsub.validateSubscribe(subject);\n    return self.purgeStreamFiltered(name, subject);\n}\n\nfn purgeStreamFiltered(\n    self: *JetStream,\n    name: []const u8,\n    subject: ?[]const u8,\n) !Response(PurgeResponse) {\n    try validateName(name);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.PURGE.{s}\",\n        .{name},\n    ) catch return errors.Error.SubjectTooLong;\n    if (subject) |s| {\n        return self.apiRequest(\n            PurgeResponse,\n            subj,\n            types.PurgeRequest{ .filter = s },\n        );\n    }\n    return self.apiRequestNoPayload(\n        PurgeResponse,\n        subj,\n    );\n}\n\n// -- Stream message operations --\n\n/// Gets a raw message from a stream by sequence number.\npub fn getMsg(\n    self: *JetStream,\n    stream: []const u8,\n    seq: u64,\n) !Response(types.MsgGetResponse) {\n    try validateName(stream);\n    std.debug.assert(seq > 0);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.MSG.GET.{s}\",\n        .{stream},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        types.MsgGetResponse,\n        subj,\n        types.MsgGetRequest{ .seq = seq },\n    );\n}\n\n/// Gets the last message on a specific subject.\npub fn getLastMsgForSubject(\n    self: *JetStream,\n    stream: []const u8,\n    subject: []const u8,\n) !Response(types.MsgGetResponse) {\n    try validateName(stream);\n    try pubsub.validateSubscribe(subject);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.MSG.GET.{s}\",\n        .{stream},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        types.MsgGetResponse,\n        subj,\n        types.MsgGetRequest{ .last_by_subj = subject },\n    );\n}\n\n/// Deletes a message from a stream by sequence.\n/// The message is marked as erased but not overwritten.\npub fn deleteMsg(\n    self: *JetStream,\n    stream: []const u8,\n    seq: u64,\n) !Response(DeleteResponse) {\n    try validateName(stream);\n    std.debug.assert(seq > 0);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.MSG.DELETE.{s}\",\n        .{stream},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        DeleteResponse,\n        subj,\n        types.MsgDeleteRequest{\n            .seq = seq,\n            .no_erase = true,\n        },\n    );\n}\n\n/// Securely deletes a message by overwriting it with\n/// random data. Slower than deleteMsg.\npub fn secureDeleteMsg(\n    self: *JetStream,\n    stream: []const u8,\n    seq: u64,\n) !Response(DeleteResponse) {\n    try validateName(stream);\n    std.debug.assert(seq > 0);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"STREAM.MSG.DELETE.{s}\",\n        .{stream},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        DeleteResponse,\n        subj,\n        types.MsgDeleteRequest{ .seq = seq },\n    );\n}\n\n// -- Consumer CRUD --\n\n/// Creates a consumer on the given stream. Returns\n/// error if consumer already exists with different\n/// config. The filter_subject (if any) is in the body.\npub fn createConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    config: ConsumerConfig,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [512]u8 = undefined;\n    const name = config.name orelse\n        config.durable_name orelse \"\";\n    try validateName(name);\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.CREATE.{s}.{s}\",\n        .{ stream, name },\n    ) catch return errors.Error.SubjectTooLong;\n    const req = CreateConsumerRequest{\n        .stream_name = stream,\n        .config = config,\n        .action = \"create\",\n    };\n    return self.apiRequest(ConsumerInfo, subj, req);\n}\n\n/// Creates or updates a consumer on the given stream.\n/// If the consumer exists, it will be updated if the\n/// config change is compatible.\npub fn createOrUpdateConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    config: ConsumerConfig,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [512]u8 = undefined;\n    const name = config.name orelse\n        config.durable_name orelse \"\";\n    try validateName(name);\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.CREATE.{s}.{s}\",\n        .{ stream, name },\n    ) catch return errors.Error.SubjectTooLong;\n    const req = CreateConsumerRequest{\n        .stream_name = stream,\n        .config = config,\n    };\n    return self.apiRequest(ConsumerInfo, subj, req);\n}\n\n/// Updates a consumer on the given stream.\npub fn updateConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    config: ConsumerConfig,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [512]u8 = undefined;\n    const name = config.name orelse\n        config.durable_name orelse \"\";\n    try validateName(name);\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.CREATE.{s}.{s}\",\n        .{ stream, name },\n    ) catch return errors.Error.SubjectTooLong;\n    const req = CreateConsumerRequest{\n        .stream_name = stream,\n        .config = config,\n        .action = \"update\",\n    };\n    return self.apiRequest(ConsumerInfo, subj, req);\n}\n\n/// Deletes a consumer from a stream.\npub fn deleteConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    consumer: []const u8,\n) !Response(DeleteResponse) {\n    try validateName(stream);\n    try validateName(consumer);\n    var buf: [512]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.DELETE.{s}.{s}\",\n        .{ stream, consumer },\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequestNoPayload(\n        DeleteResponse,\n        subj,\n    );\n}\n\n/// Gets consumer info.\npub fn consumerInfo(\n    self: *JetStream,\n    stream: []const u8,\n    consumer: []const u8,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    try validateName(consumer);\n    var buf: [512]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.INFO.{s}.{s}\",\n        .{ stream, consumer },\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequestNoPayload(\n        ConsumerInfo,\n        subj,\n    );\n}\n\n/// Creates a push consumer on the given stream.\n/// The config must have deliver_subject set.\npub fn createPushConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    config: ConsumerConfig,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    std.debug.assert(config.deliver_subject != null);\n    std.debug.assert(self.timeout_ms > 0);\n    return self.createConsumer(stream, config);\n}\n\n/// Creates or updates a push consumer.\n/// The config must have deliver_subject set.\npub fn createOrUpdatePushConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    config: ConsumerConfig,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    std.debug.assert(config.deliver_subject != null);\n    std.debug.assert(self.timeout_ms > 0);\n    return self.createOrUpdateConsumer(stream, config);\n}\n\n/// Pauses a consumer until the given time (RFC 3339).\n/// The consumer will not deliver messages until resumed\n/// or the pause_until time is reached.\npub fn pauseConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    consumer: []const u8,\n    pause_until: []const u8,\n) !Response(ConsumerPauseResponse) {\n    try validateName(stream);\n    try validateName(consumer);\n    std.debug.assert(pause_until.len > 0);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [512]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.PAUSE.{s}.{s}\",\n        .{ stream, consumer },\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        ConsumerPauseResponse,\n        subj,\n        types.ConsumerPauseRequest{\n            .pause_until = pause_until,\n        },\n    );\n}\n\n/// Resumes a paused consumer immediately.\npub fn resumeConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    consumer: []const u8,\n) !Response(ConsumerPauseResponse) {\n    try validateName(stream);\n    try validateName(consumer);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [512]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.PAUSE.{s}.{s}\",\n        .{ stream, consumer },\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        ConsumerPauseResponse,\n        subj,\n        types.ConsumerPauseRequest{},\n    );\n}\n\n/// Updates an existing push consumer.\n/// Config must have deliver_subject set.\npub fn updatePushConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    config: ConsumerConfig,\n) !Response(ConsumerInfo) {\n    try validateName(stream);\n    std.debug.assert(config.deliver_subject != null);\n    std.debug.assert(self.timeout_ms > 0);\n    return self.updateConsumer(stream, config);\n}\n\n/// Binds to an existing push consumer by name.\n/// Returns a PushSubscription with deliver_subject\n/// populated from the server-side config.\npub fn pushConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    consumer_name: []const u8,\n) !PushSubscription {\n    try validateName(stream);\n    try validateName(consumer_name);\n    var resp = try self.consumerInfo(\n        stream,\n        consumer_name,\n    );\n    defer resp.deinit();\n\n    const cfg = resp.value.config orelse\n        return errors.Error.ApiError;\n    const ds = cfg.deliver_subject orelse\n        return errors.Error.ApiError;\n\n    var ps = PushSubscription{\n        .js = self,\n        .stream = stream,\n    };\n    try ps.setConsumer(consumer_name);\n    try ps.setDeliverSubject(ds);\n    if (cfg.deliver_group) |dg| {\n        try ps.setDeliverGroup(dg);\n    }\n    return ps;\n}\n\n/// Unpins the currently pinned client for a consumer\n/// in the given delivery group.\npub fn unpinConsumer(\n    self: *JetStream,\n    stream: []const u8,\n    consumer: []const u8,\n    group: []const u8,\n) !Response(DeleteResponse) {\n    try validateName(stream);\n    try validateName(consumer);\n    std.debug.assert(group.len > 0);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [512]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.UNPIN.{s}.{s}\",\n        .{ stream, consumer },\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        DeleteResponse,\n        subj,\n        types.ConsumerUnpinRequest{\n            .group = group,\n        },\n    );\n}\n\n/// Returns the underlying client connection.\npub fn conn(self: *const JetStream) *Client {\n    return self.client;\n}\n\n/// Returns the JetStream context options.\npub fn options(self: *const JetStream) Options {\n    return .{\n        .api_prefix = self.apiPrefix(),\n        .timeout_ms = self.timeout_ms,\n    };\n}\n\n// -- Listing & Account Info --\n\n/// Returns stream names. Pass offset=0 for first\n/// page. Check response total/offset/limit for\n/// pagination. Returns one page per call.\npub fn streamNames(\n    self: *JetStream,\n) !Response(StreamNamesResponse) {\n    return self.streamNamesOffset(0);\n}\n\n/// Returns stream names starting at offset.\npub fn streamNamesOffset(\n    self: *JetStream,\n    offset: u64,\n) !Response(StreamNamesResponse) {\n    std.debug.assert(self.timeout_ms > 0);\n    return self.apiRequest(\n        StreamNamesResponse,\n        \"STREAM.NAMES\",\n        ListRequest{ .offset = offset },\n    );\n}\n\n/// Returns all stream names across all pages.\n/// Caller owns the returned slice; free each\n/// string and the slice with allocator.\npub fn allStreamNames(\n    self: *JetStream,\n    allocator: Allocator,\n) ![][]const u8 {\n    std.debug.assert(self.timeout_ms > 0);\n    var result: std.ArrayList([]const u8) = .empty;\n    errdefer {\n        for (result.items) |n|\n            allocator.free(n);\n        result.deinit(allocator);\n    }\n    var offset: u64 = 0;\n    while (true) {\n        var resp = try self.streamNamesOffset(\n            offset,\n        );\n        defer resp.deinit();\n        const names = resp.value.streams orelse\n            break;\n        for (names) |n| {\n            const owned = try allocator.dupe(u8, n);\n            result.append(allocator, owned) catch |e| {\n                allocator.free(owned);\n                return e;\n            };\n        }\n        offset += names.len;\n        if (offset >= resp.value.total) break;\n    }\n    return result.toOwnedSlice(allocator);\n}\n\n/// Returns stream info list (one page).\npub fn streams(\n    self: *JetStream,\n) !Response(StreamListResponse) {\n    std.debug.assert(self.timeout_ms > 0);\n    return self.apiRequest(\n        StreamListResponse,\n        \"STREAM.LIST\",\n        ListRequest{},\n    );\n}\n\n/// Finds the stream name that captures a subject.\npub fn streamNameBySubject(\n    self: *JetStream,\n    subject: []const u8,\n) !Response(StreamNamesResponse) {\n    try pubsub.validateSubscribe(subject);\n    std.debug.assert(self.timeout_ms > 0);\n    return self.apiRequest(\n        StreamNamesResponse,\n        \"STREAM.NAMES\",\n        ListRequest{ .subject = subject },\n    );\n}\n\n/// Returns consumer names (one page).\npub fn consumerNames(\n    self: *JetStream,\n    stream: []const u8,\n) !Response(ConsumerNamesResponse) {\n    return self.consumerNamesOffset(stream, 0);\n}\n\n/// Returns consumer names at offset.\npub fn consumerNamesOffset(\n    self: *JetStream,\n    stream: []const u8,\n    offset: u64,\n) !Response(ConsumerNamesResponse) {\n    try validateName(stream);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.NAMES.{s}\",\n        .{stream},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        ConsumerNamesResponse,\n        subj,\n        ListRequest{ .offset = offset },\n    );\n}\n\n/// Returns consumer info list (one page).\npub fn consumers(\n    self: *JetStream,\n    stream: []const u8,\n) !Response(ConsumerListResponse) {\n    try validateName(stream);\n    std.debug.assert(self.timeout_ms > 0);\n    var buf: [256]u8 = undefined;\n    const subj = std.fmt.bufPrint(\n        &buf,\n        \"CONSUMER.LIST.{s}\",\n        .{stream},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.apiRequest(\n        ConsumerListResponse,\n        subj,\n        ListRequest{},\n    );\n}\n\n/// Returns JetStream account information including\n/// usage stats and limits.\npub fn accountInfo(\n    self: *JetStream,\n) !Response(AccountInfo) {\n    std.debug.assert(self.timeout_ms > 0);\n    return self.apiRequestNoPayload(\n        AccountInfo,\n        \"INFO\",\n    );\n}\n\n// -- Key-Value Store --\n\nconst kv_mod = @import(\"kv.zig\");\npub const KeyValue = kv_mod.KeyValue;\npub const KvWatcher = kv_mod.KvWatcher;\nconst KeyValueConfig = types.KeyValueConfig;\n\n/// Creates a new key-value bucket backed by a\n/// JetStream stream. Returns a KeyValue handle.\npub fn createKeyValue(\n    self: *JetStream,\n    cfg: KeyValueConfig,\n) !KeyValue {\n    try validateBucketName(cfg.bucket);\n    std.debug.assert(self.timeout_ms > 0);\n    const sc = self.kvStreamConfig(cfg);\n    var subjects = [_][]const u8{sc.subject()};\n    var resp = try self.createStream(sc.config(\n        sc.stream_name(),\n        &subjects,\n    ));\n    resp.deinit();\n    try self.confirmServerRoundTrip();\n    return try self.initKeyValue(cfg.bucket);\n}\n\n/// Updates an existing key-value bucket config.\n/// Returns error if the bucket doesn't exist.\npub fn updateKeyValue(\n    self: *JetStream,\n    cfg: KeyValueConfig,\n) !KeyValue {\n    try validateBucketName(cfg.bucket);\n    std.debug.assert(self.timeout_ms > 0);\n    const sc = self.kvStreamConfig(cfg);\n    var subjects = [_][]const u8{sc.subject()};\n    var resp = try self.updateStream(sc.config(\n        sc.stream_name(),\n        &subjects,\n    ));\n    resp.deinit();\n    try self.confirmServerRoundTrip();\n    return try self.initKeyValue(cfg.bucket);\n}\n\n/// Creates or updates a key-value bucket.\npub fn createOrUpdateKeyValue(\n    self: *JetStream,\n    cfg: KeyValueConfig,\n) !KeyValue {\n    try validateBucketName(cfg.bucket);\n    std.debug.assert(self.timeout_ms > 0);\n    const sc = self.kvStreamConfig(cfg);\n    var subjects = [_][]const u8{sc.subject()};\n    var resp = try self.createOrUpdateStream(\n        sc.config(sc.stream_name(), &subjects),\n    );\n    resp.deinit();\n    try self.confirmServerRoundTrip();\n    return try self.initKeyValue(cfg.bucket);\n}\n\n/// Binds to an existing key-value bucket.\n/// Returns error if the stream doesn't exist.\npub fn keyValue(\n    self: *JetStream,\n    bucket_name: []const u8,\n) !KeyValue {\n    try validateBucketName(bucket_name);\n\n    var stream_buf: [68]u8 = undefined;\n    const stream_name = std.fmt.bufPrint(\n        &stream_buf,\n        \"KV_{s}\",\n        .{bucket_name},\n    ) catch return errors.Error.SubjectTooLong;\n\n    // Verify stream exists\n    var resp = try self.streamInfo(stream_name);\n    resp.deinit();\n\n    return try self.initKeyValue(bucket_name);\n}\n\n/// Deletes a key-value bucket and its backing stream.\npub fn deleteKeyValue(\n    self: *JetStream,\n    bucket_name: []const u8,\n) !Response(DeleteResponse) {\n    try validateBucketName(bucket_name);\n    var stream_buf: [68]u8 = undefined;\n    const stream_name = std.fmt.bufPrint(\n        &stream_buf,\n        \"KV_{s}\",\n        .{bucket_name},\n    ) catch return errors.Error.SubjectTooLong;\n    return self.deleteStream(stream_name);\n}\n\nfn initKeyValue(\n    self: *JetStream,\n    bucket_name: []const u8,\n) !KeyValue {\n    try validateBucketName(bucket_name);\n    var kv = KeyValue{ .js = self };\n\n    @memcpy(\n        kv.bucket_buf[0..bucket_name.len],\n        bucket_name,\n    );\n    kv.bucket_len = @intCast(bucket_name.len);\n\n    var stream_buf: [68]u8 = undefined;\n    const sn = std.fmt.bufPrint(\n        &stream_buf,\n        \"KV_{s}\",\n        .{bucket_name},\n    ) catch unreachable;\n    @memcpy(kv.stream_buf[0..sn.len], sn);\n    kv.stream_len = @intCast(sn.len);\n\n    return kv;\n}\n\nfn confirmServerRoundTrip(self: *JetStream) !void {\n    // Make newly created/updated KV streams immediately publishable on\n    // constrained runners where stream interest can lag the API response.\n    const timeout_ns = @as(u64, self.timeout_ms) *\n        std.time.ns_per_ms;\n    try self.client.flush(timeout_ns);\n}\n\n/// Builds the stream config for a KV bucket without\n/// executing the API call. Shared by create/update/\n/// createOrUpdate.\nconst KvStreamCfg = struct {\n    stream_name_buf: [68]u8 = undefined,\n    stream_name_len: u8 = 0,\n    subj_buf: [128]u8 = undefined,\n    subj_len: u8 = 0,\n    hist: i64 = 1,\n    dup_window: ?i64 = null,\n    max_bytes: ?i64 = null,\n    max_age: ?i64 = null,\n    max_msg_size: ?i32 = null,\n    storage: StorageType = .file,\n    replicas: ?i32 = null,\n    desc: ?[]const u8 = null,\n\n    fn stream_name(\n        self: *const KvStreamCfg,\n    ) []const u8 {\n        std.debug.assert(self.stream_name_len > 0);\n        return self.stream_name_buf[0..self.stream_name_len];\n    }\n\n    fn subject(\n        self: *const KvStreamCfg,\n    ) []const u8 {\n        std.debug.assert(self.subj_len > 0);\n        return self.subj_buf[0..self.subj_len];\n    }\n\n    fn config(\n        self: *const KvStreamCfg,\n        name: []const u8,\n        subjects: *const [1][]const u8,\n    ) StreamConfig {\n        return .{\n            .name = name,\n            .subjects = subjects,\n            .max_msgs_per_subject = self.hist,\n            .max_bytes = self.max_bytes,\n            .max_age = self.max_age,\n            .max_msg_size = self.max_msg_size,\n            .storage = self.storage,\n            .num_replicas = self.replicas,\n            .discard = .new,\n            .duplicate_window = self.dup_window,\n            .max_msgs = -1,\n            .max_consumers = -1,\n            .allow_rollup_hdrs = true,\n            .deny_delete = true,\n            .deny_purge = false,\n            .allow_direct = true,\n            .mirror_direct = false,\n            .description = self.desc,\n        };\n    }\n};\n\nfn kvStreamConfig(\n    _: *const JetStream,\n    cfg: KeyValueConfig,\n) KvStreamCfg {\n    std.debug.assert(cfg.bucket.len > 0);\n    std.debug.assert(cfg.bucket.len <= 64);\n    var sc = KvStreamCfg{};\n\n    const sn = std.fmt.bufPrint(\n        &sc.stream_name_buf,\n        \"KV_{s}\",\n        .{cfg.bucket},\n    ) catch unreachable;\n    sc.stream_name_len = @intCast(sn.len);\n\n    const sp = std.fmt.bufPrint(\n        &sc.subj_buf,\n        \"$KV.{s}.>\",\n        .{cfg.bucket},\n    ) catch unreachable;\n    sc.subj_len = @intCast(sp.len);\n\n    sc.hist = if (cfg.history) |h|\n        @intCast(h)\n    else\n        1;\n\n    const two_min: i64 = 120_000_000_000;\n    sc.dup_window = if (cfg.ttl) |ttl|\n        @min(ttl, two_min)\n    else\n        two_min;\n\n    sc.max_bytes = cfg.max_bytes;\n    sc.max_age = cfg.ttl;\n    sc.max_msg_size = cfg.max_value_size;\n    sc.storage = cfg.storage orelse .file;\n    sc.replicas = cfg.replicas;\n    sc.desc = cfg.description;\n\n    return sc;\n}\n\n/// Returns names of all key-value buckets. Queries\n/// stream names with $KV subject filter, strips the\n/// KV_ prefix. Caller owns the returned slice.\npub fn keyValueStoreNames(\n    self: *JetStream,\n    alloc: std.mem.Allocator,\n) ![][]const u8 {\n    std.debug.assert(self.timeout_ms > 0);\n    var result: std.ArrayList([]const u8) = .empty;\n    errdefer {\n        for (result.items) |n| alloc.free(n);\n        result.deinit(alloc);\n    }\n    var offset: u64 = 0;\n    while (true) {\n        var resp = try self.apiRequest(\n            StreamNamesResponse,\n            \"STREAM.NAMES\",\n            ListRequest{\n                .offset = offset,\n                .subject = \"$KV.*.>\",\n            },\n        );\n        defer resp.deinit();\n        const names = resp.value.streams orelse break;\n        for (names) |n| {\n            // Strip \"KV_\" prefix\n            const bucket = if (n.len > 3 and\n                std.mem.startsWith(u8, n, \"KV_\"))\n                n[3..]\n            else\n                n;\n            const owned = try alloc.dupe(u8, bucket);\n            result.append(alloc, owned) catch |e| {\n                alloc.free(owned);\n                return e;\n            };\n        }\n        offset += names.len;\n        if (offset >= resp.value.total) break;\n    }\n    return result.toOwnedSlice(alloc);\n}\n\n/// Returns all KV buckets with status info. Caller\n/// owns the returned slice.\npub fn keyValueStores(\n    self: *JetStream,\n    alloc: std.mem.Allocator,\n) ![]types.KeyValueStatus {\n    std.debug.assert(self.timeout_ms > 0);\n    const names = try self.keyValueStoreNames(alloc);\n    defer {\n        for (names) |n| alloc.free(n);\n        alloc.free(names);\n    }\n\n    var result: std.ArrayList(\n        types.KeyValueStatus,\n    ) = .empty;\n    errdefer result.deinit(alloc);\n\n    for (names) |bucket| {\n        var stream_buf: [68]u8 = undefined;\n        const sn = std.fmt.bufPrint(\n            &stream_buf,\n            \"KV_{s}\",\n            .{bucket},\n        ) catch continue;\n        var resp = self.streamInfo(sn) catch continue;\n        defer resp.deinit();\n\n        const cfg = resp.value.config orelse continue;\n        const state = resp.value.state orelse\n            types.StreamState{};\n\n        try result.append(alloc, .{\n            .bucket = bucket,\n            .values = state.messages,\n            .history = cfg.max_msgs_per_subject orelse\n                1,\n            .ttl = cfg.max_age orelse 0,\n            .bytes = state.bytes,\n            .backing_store = cfg.storage orelse .file,\n            .is_compressed = if (cfg.compression) |c|\n                c == .s2\n            else\n                false,\n        });\n    }\n    return result.toOwnedSlice(alloc);\n}\n\n// -- JetStream Publish --\n\n/// Publishes a message to a JetStream stream subject\n/// and waits for a PubAck. Retries NoResponders within\n/// the configured JetStream timeout to tolerate transient\n/// stream or leadership readiness after stream creation.\npub fn publish(\n    self: *JetStream,\n    subject: []const u8,\n    payload: []const u8,\n) !Response(PubAck) {\n    std.debug.assert(subject.len > 0);\n    std.debug.assert(payload.len <= 1048576);\n    return self.publishRetry(subject, payload, null);\n}\n\npub fn publishRetry(\n    self: *JetStream,\n    subject: []const u8,\n    payload: []const u8,\n    hdrs: ?[]const headers.Entry,\n) !Response(PubAck) {\n    const retry_wait_ms: u32 = 250;\n    const max_retries: u32 = @max(2, self.timeout_ms / retry_wait_ms);\n    const retry_wait_ns: u64 =\n        @as(u64, retry_wait_ms) * std.time.ns_per_ms;\n    var attempt: u32 = 0;\n\n    while (true) {\n        const resp = if (hdrs) |h|\n            self.client.requestWithHeaders(\n                subject,\n                h,\n                payload,\n                self.timeout_ms,\n            ) catch |err| return err\n        else\n            self.client.request(\n                subject,\n                payload,\n                self.timeout_ms,\n            ) catch |err| return err;\n\n        var msg = resp orelse\n            return errors.Error.Timeout;\n\n        if (msg.isNoResponders()) {\n            msg.deinit();\n            attempt += 1;\n            if (attempt > max_retries)\n                return errors.Error.NoResponders;\n            sleepNs(retry_wait_ns);\n            continue;\n        }\n\n        defer msg.deinit();\n        return self.parsePubAckResponse(&msg);\n    }\n}\n\nfn sleepNs(ns: u64) void {\n    var ts: std.posix.timespec = .{\n        .sec = @intCast(ns / 1_000_000_000),\n        .nsec = @intCast(ns % 1_000_000_000),\n    };\n    _ = std.posix.system.nanosleep(&ts, &ts);\n}\n\n/// Publishes with header-based options (msg-id, expected\n/// stream/seq) and waits for a PubAck.\npub fn publishWithOpts(\n    self: *JetStream,\n    subject: []const u8,\n    payload: []const u8,\n    opts: PublishOpts,\n) !Response(PubAck) {\n    std.debug.assert(subject.len > 0);\n    std.debug.assert(payload.len <= 1048576);\n\n    var hdrs: publish_headers.PublishHeaderSet = undefined;\n    hdrs.populate(opts);\n\n    // Pass null when opts produced no headers -- the protocol\n    // layer asserts entries.len > 0, and publishRetry uses the\n    // null/non-null distinction to select the correct publish\n    // path.\n    const slice = hdrs.slice();\n    const hdr_slice: ?[]const headers.Entry =\n        if (slice.len == 0) null else slice;\n    return self.publishRetry(\n        subject,\n        payload,\n        hdr_slice,\n    );\n}\n\n/// Publishes a pre-built JetStream message with user-supplied\n/// headers and optional PublishOpts. User headers are merged\n/// with JetStream-generated headers (msg_id, expected_stream,\n/// etc.); on key collision, JetStream headers from `msg.opts`\n/// override the user-supplied value (matches Go client\n/// PublishMsg semantics).\n///\n/// Header key comparison is case-insensitive per NATS header\n/// conventions.\n///\n/// The allocator is used only for a temporary merge buffer\n/// that is freed before return. No allocations escape.\npub fn publishMsg(\n    self: *JetStream,\n    allocator: Allocator,\n    msg: types.JsPublishMsg,\n) !Response(PubAck) {\n    std.debug.assert(msg.subject.len > 0);\n    std.debug.assert(msg.payload.len <= 1048576);\n\n    var js_hdrs: publish_headers.PublishHeaderSet = undefined;\n    js_hdrs.populate(msg.opts);\n\n    // Fast path: no user headers. Pass null to publishRetry\n    // when opts also produced no JS headers -- the protocol\n    // layer asserts entries.len > 0 in encodedSize() and the\n    // null/non-null distinction is how publishRetry chooses\n    // between the header and no-header publish paths.\n    if (msg.headers == null or msg.headers.?.len == 0) {\n        const js_slice = js_hdrs.slice();\n        const hdr_slice: ?[]const headers.Entry =\n            if (js_slice.len == 0) null else js_slice;\n        return self.publishRetry(\n            msg.subject,\n            msg.payload,\n            hdr_slice,\n        );\n    }\n\n    // Merge: user headers first, JS headers override on\n    // case-insensitive key collision.\n    var merged: std.ArrayList(headers.Entry) = .empty;\n    defer merged.deinit(allocator);\n\n    try merged.appendSlice(allocator, msg.headers.?);\n\n    for (js_hdrs.slice()) |js_entry| {\n        var replaced = false;\n        for (merged.items) |*existing| {\n            if (std.ascii.eqlIgnoreCase(\n                existing.key,\n                js_entry.key,\n            )) {\n                existing.value = js_entry.value;\n                replaced = true;\n                break;\n            }\n        }\n        if (!replaced) try merged.append(\n            allocator,\n            js_entry,\n        );\n    }\n\n    const hdr_slice: ?[]const headers.Entry =\n        if (merged.items.len == 0) null else merged.items;\n    return self.publishRetry(\n        msg.subject,\n        msg.payload,\n        hdr_slice,\n    );\n}\n\n// -- Internal helpers --\n\n/// Builds the full API subject and sends a request with\n/// JSON payload, parsing the response.\npub fn apiRequest(\n    self: *JetStream,\n    comptime T: type,\n    api_subject: []const u8,\n    payload: anytype,\n) !Response(T) {\n    std.debug.assert(api_subject.len > 0);\n    const prefix = self.apiPrefix();\n    std.debug.assert(prefix.len > 0);\n\n    var full_buf: [512]u8 = undefined;\n    const full_subj = std.fmt.bufPrint(\n        &full_buf,\n        \"{s}{s}\",\n        .{ prefix, api_subject },\n    ) catch return errors.Error.SubjectTooLong;\n\n    const json_payload = try types.jsonStringify(\n        self.allocator,\n        payload,\n    );\n    defer self.allocator.free(json_payload);\n\n    const resp = self.client.request(\n        full_subj,\n        json_payload,\n        self.timeout_ms,\n    ) catch |err| return err;\n    var msg = resp orelse\n        return errors.Error.Timeout;\n    defer msg.deinit();\n\n    if (msg.isNoResponders())\n        return errors.Error.NoResponders;\n\n    return self.parseResponse(T, msg.data);\n}\n\n/// Sends a request with no payload body.\nfn apiRequestNoPayload(\n    self: *JetStream,\n    comptime T: type,\n    api_subject: []const u8,\n) !Response(T) {\n    std.debug.assert(api_subject.len > 0);\n    const prefix = self.apiPrefix();\n    std.debug.assert(prefix.len > 0);\n\n    var full_buf: [512]u8 = undefined;\n    const full_subj = std.fmt.bufPrint(\n        &full_buf,\n        \"{s}{s}\",\n        .{ prefix, api_subject },\n    ) catch return errors.Error.SubjectTooLong;\n\n    const resp = self.client.request(\n        full_subj,\n        \"\",\n        self.timeout_ms,\n    ) catch |err| return err;\n    var msg = resp orelse\n        return errors.Error.Timeout;\n    defer msg.deinit();\n\n    if (msg.isNoResponders())\n        return errors.Error.NoResponders;\n\n    return self.parseResponse(T, msg.data);\n}\n\n/// Parses JSON response, checks for API error envelope.\nfn parseResponse(\n    self: *JetStream,\n    comptime T: type,\n    data: []const u8,\n) !Response(T) {\n    std.debug.assert(data.len > 0);\n    var parsed = types.jsonParse(\n        T,\n        self.allocator,\n        data,\n    ) catch return errors.Error.JsonParseError;\n\n    if (checkApiError(T, &parsed.value)) |api_err| {\n        self.last_api_err = ApiError.fromJson(api_err);\n        parsed.deinit();\n        return errors.Error.ApiError;\n    }\n\n    return Response(T){\n        .value = parsed.value,\n        ._parsed = parsed,\n    };\n}\n\n/// Parses PubAck from a message response (publish goes\n/// directly to stream subject, not through $JS.API).\nfn parsePubAckResponse(\n    self: *JetStream,\n    msg: *Client.Message,\n) !Response(PubAck) {\n    if (msg.isNoResponders())\n        return errors.Error.NoResponders;\n    std.debug.assert(msg.data.len > 0);\n    return self.parseResponse(PubAck, msg.data);\n}\n\n/// Checks if a parsed response contains an API error.\nfn checkApiError(\n    comptime T: type,\n    value: *const T,\n) ?ApiErrorJson {\n    if (@hasField(T, \"error\")) {\n        if (value.@\"error\") |err| {\n            if (err.code > 0) return err;\n        }\n    }\n    return null;\n}\n\n/// Returns the API prefix slice.\npub fn apiPrefix(self: *const JetStream) []const u8 {\n    std.debug.assert(self.api_prefix_len > 0);\n    return self.api_prefix_buf[0..self.api_prefix_len];\n}\n\n// -- Tests --\n\ntest \"subject building\" {\n    // Test apiPrefix format for default\n    var js = JetStream{\n        .client = undefined,\n        .allocator = std.testing.allocator,\n    };\n    const default_prefix = \"$JS.API.\";\n    @memcpy(\n        js.api_prefix_buf[0..default_prefix.len],\n        default_prefix,\n    );\n    js.api_prefix_len = @intCast(default_prefix.len);\n    try std.testing.expectEqualStrings(\n        \"$JS.API.\",\n        js.apiPrefix(),\n    );\n}\n\ntest \"JetStream init reports invalid prefix or domain at runtime\" {\n    try std.testing.expect(returnsErrorUnion(JetStream.init));\n}\n"
  },
  {
    "path": "src/jetstream/async_publish.zig",
    "content": "//! Async JetStream publish with PubAckFuture.\n//!\n//! Non-blocking publish that returns a future for the ack.\n//! Uses a shared reply subscription and correlation map to\n//! match incoming acks to pending futures. Supports max\n//! in-flight backpressure and per-ack timeouts.\n\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\n\nconst types = @import(\"types.zig\");\nconst errors = @import(\"errors.zig\");\nconst JetStream = @import(\"JetStream.zig\");\nconst Io = std.Io;\nconst publish_headers = @import(\"publish_headers.zig\");\n\n/// Published ack future. Returned by publishAsync().\n/// Call wait() to block until the ack arrives.\npub const PubAckFuture = struct {\n    _done: std.atomic.Value(bool) =\n        std.atomic.Value(bool).init(false),\n    _ack: ?types.PubAck = null,\n    _err: ?anyerror = null,\n    _id_buf: [token_size]u8 = undefined,\n    _allocator: Allocator,\n    _stream_owned: ?[]u8 = null,\n    _domain_owned: ?[]u8 = null,\n\n    /// Blocks until the ack is received or timeout.\n    pub fn wait(\n        self: *PubAckFuture,\n        timeout_ms: u32,\n    ) !types.PubAck {\n        std.debug.assert(timeout_ms > 0);\n        var elapsed: u32 = 0;\n        while (!self._done.load(.acquire)) {\n            if (elapsed >= timeout_ms)\n                return errors.Error.Timeout;\n            sleepNs(1_000_000); // 1ms poll\n            elapsed += 1;\n        }\n\n        if (self._err) |e| return e;\n        return self._ack orelse\n            errors.Error.Timeout;\n    }\n\n    /// Non-blocking check. Returns ack if ready.\n    pub fn result(\n        self: *const PubAckFuture,\n    ) ?types.PubAck {\n        if (!self._done.load(.acquire)) return null;\n        return self._ack;\n    }\n\n    /// Returns the error if the ack failed.\n    pub fn err(self: *const PubAckFuture) ?anyerror {\n        if (!self._done.load(.acquire)) return null;\n        return self._err;\n    }\n\n    /// Frees the future.\n    pub fn deinit(self: *PubAckFuture) void {\n        if (self._stream_owned) |stream| {\n            self._allocator.free(stream);\n            self._stream_owned = null;\n        }\n        if (self._domain_owned) |domain| {\n            self._allocator.free(domain);\n            self._domain_owned = null;\n        }\n        self._allocator.destroy(self);\n    }\n};\n\nconst token_size = 6;\nconst alphabet =\n    \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\" ++\n    \"abcdefghijklmnopqrstuvwxyz\";\n\n/// Async publisher for JetStream. Created from a\n/// JetStream context. Manages a shared reply\n/// subscription and correlates incoming acks to\n/// pending publish futures.\npub const AsyncPublisher = struct {\n    js: *JetStream,\n    client: *Client,\n    allocator: Allocator,\n\n    // Reply inbox: \"<inbox_prefix>.<unique>.\"\n    reply_prefix_buf: [128]u8 = undefined,\n    reply_prefix_len: u8 = 0,\n\n    // Callback subscription on reply_prefix + \"*\"\n    sub: ?*Client.Sub = null,\n\n    // Pending acks: id_str → *PubAckFuture\n    mu: Io.Mutex = .init,\n    pending: PendingMap = .empty,\n    pending_count: std.atomic.Value(u32) =\n        std.atomic.Value(u32).init(0),\n\n    // Config\n    max_pending: u32 = 256,\n    ack_timeout_ms: u32 = 5000,\n\n    const PendingMap = std.StringHashMapUnmanaged(\n        *PubAckFuture,\n    );\n\n    /// Options for the async publisher.\n    pub const Options = struct {\n        max_pending: u32 = 256,\n        ack_timeout_ms: u32 = 5000,\n    };\n\n    /// Creates an async publisher bound to the given\n    /// JetStream context. The reply subscription is\n    /// created lazily on first publish (the struct\n    /// must have a stable address by then).\n    pub fn init(\n        js: *JetStream,\n        opts: Options,\n    ) !AsyncPublisher {\n        std.debug.assert(js.timeout_ms > 0);\n        const client = js.client;\n        const allocator = js.allocator;\n\n        var ap = AsyncPublisher{\n            .js = js,\n            .client = client,\n            .allocator = allocator,\n            .max_pending = opts.max_pending,\n            .ack_timeout_ms = opts.ack_timeout_ms,\n        };\n\n        // Build reply prefix: _INBOX.<random>.\n        const inbox = try client.newInbox();\n        defer allocator.free(inbox);\n        std.debug.assert(inbox.len > 0);\n        const plen = @min(\n            inbox.len,\n            ap.reply_prefix_buf.len - 1,\n        );\n        @memcpy(\n            ap.reply_prefix_buf[0..plen],\n            inbox[0..plen],\n        );\n        ap.reply_prefix_buf[plen] = '.';\n        ap.reply_prefix_len = @intCast(plen + 1);\n\n        return ap;\n    }\n\n    /// Ensures the reply subscription exists. Called\n    /// lazily on first publish when the struct has a\n    /// stable address.\n    fn ensureSubscribed(\n        self: *AsyncPublisher,\n    ) !void {\n        if (self.sub != null) return;\n        var sub_buf: [130]u8 = undefined;\n        const sub_subj = std.fmt.bufPrint(\n            &sub_buf,\n            \"{s}*\",\n            .{self.replyPrefix()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        self.sub = try self.client.subscribe(\n            sub_subj,\n            Client.MsgHandler.init(\n                AsyncPublisher,\n                self,\n            ),\n        );\n    }\n\n    /// Returns the reply prefix slice.\n    fn replyPrefix(\n        self: *const AsyncPublisher,\n    ) []const u8 {\n        std.debug.assert(self.reply_prefix_len > 0);\n        return self.reply_prefix_buf[0..self.reply_prefix_len];\n    }\n\n    /// Publishes a message asynchronously. Returns a\n    /// future that resolves when the server acks.\n    /// Blocks if max_pending is reached (backpressure).\n    pub fn publish(\n        self: *AsyncPublisher,\n        subject: []const u8,\n        payload: []const u8,\n    ) !*PubAckFuture {\n        std.debug.assert(subject.len > 0);\n        return self.publishWithOpts(\n            subject,\n            payload,\n            .{},\n        );\n    }\n\n    /// Publishes with header options (msg-id, etc).\n    pub fn publishWithOpts(\n        self: *AsyncPublisher,\n        subject: []const u8,\n        payload: []const u8,\n        opts: types.PublishOpts,\n    ) !*PubAckFuture {\n        std.debug.assert(subject.len > 0);\n\n        try self.ensureSubscribed();\n\n        // Backpressure: wait if too many pending\n        var bp_elapsed: u32 = 0;\n        while (self.pending_count.load(.acquire) >=\n            self.max_pending)\n        {\n            if (bp_elapsed >= self.ack_timeout_ms)\n                return errors.Error.Timeout;\n            sleepNs(1_000_000);\n            bp_elapsed += 1;\n        }\n\n        // Generate unique token\n        var id: [token_size]u8 = undefined;\n        self.client.io.random(&id);\n        for (&id) |*b| {\n            b.* = alphabet[@mod(b.*, alphabet.len)];\n        }\n\n        // Build reply subject: prefix + token\n        var reply_buf: [140]u8 = undefined;\n        const prefix = self.replyPrefix();\n        @memcpy(\n            reply_buf[0..prefix.len],\n            prefix,\n        );\n        @memcpy(\n            reply_buf[prefix.len..][0..token_size],\n            &id,\n        );\n        const reply = reply_buf[0 .. prefix.len +\n            token_size];\n\n        // Create future\n        const fut = try self.allocator.create(\n            PubAckFuture,\n        );\n        fut.* = .{ ._allocator = self.allocator };\n        @memcpy(&fut._id_buf, &id);\n\n        // Register in pending map\n        const io = self.client.io;\n        const id_key = fut._id_buf[0..token_size];\n        {\n            try self.mu.lock(io);\n            defer self.mu.unlock(io);\n            self.pending.put(\n                self.allocator,\n                id_key,\n                fut,\n            ) catch {\n                self.allocator.destroy(fut);\n                return error.OutOfMemory;\n            };\n        }\n        _ = self.pending_count.fetchAdd(1, .release);\n        errdefer {\n            self.mu.lock(io) catch {};\n            const removed = self.pending.fetchRemove(id_key);\n            self.mu.unlock(io);\n            if (removed) |entry| {\n                _ = self.pending_count.fetchSub(1, .release);\n                self.allocator.destroy(entry.value);\n            }\n        }\n\n        var hdrs: publish_headers.PublishHeaderSet = undefined;\n        hdrs.populate(opts);\n\n        // Publish with reply-to\n        if (hdrs.count > 0) {\n            try self.client\n                .publishRequestWithHeaders(\n                subject,\n                reply,\n                hdrs.slice(),\n                payload,\n            );\n        } else {\n            try self.client.publishRequest(\n                subject,\n                reply,\n                payload,\n            );\n        }\n\n        return fut;\n    }\n\n    /// Returns the number of pending acks.\n    pub fn publishAsyncPending(\n        self: *const AsyncPublisher,\n    ) u32 {\n        return self.pending_count.load(.acquire);\n    }\n\n    /// Blocks until all pending acks are resolved\n    /// or timeout.\n    pub fn waitComplete(\n        self: *const AsyncPublisher,\n        timeout_ms: u32,\n    ) !void {\n        std.debug.assert(timeout_ms > 0);\n        var elapsed: u32 = 0;\n        while (self.pending_count.load(.acquire) > 0) {\n            if (elapsed >= timeout_ms)\n                return errors.Error.Timeout;\n            sleepNs(1_000_000);\n            elapsed += 1;\n        }\n    }\n\n    /// Callback handler for incoming ack messages.\n    /// Routes acks to the correct PubAckFuture by\n    /// extracting the token from the reply subject.\n    pub fn onMessage(\n        self: *AsyncPublisher,\n        msg: *const Client.Message,\n    ) void {\n        const subj = msg.subject;\n        const plen = self.reply_prefix_len;\n        if (subj.len <= plen) return;\n        const id = subj[plen..];\n\n        const io = self.client.io;\n        self.mu.lock(io) catch return;\n        const entry = self.pending.fetchRemove(id);\n        self.mu.unlock(io);\n\n        const fut = if (entry) |e| e.value else return;\n        defer _ = self.pending_count.fetchSub(1, .release);\n\n        // Parse PubAck from message data\n        if (msg.data.len == 0) {\n            fut._err = errors.Error.NoResponders;\n            fut._done.store(true, .release);\n            return;\n        }\n\n        var parsed = types.jsonParse(\n            types.PubAck,\n            self.allocator,\n            msg.data,\n        ) catch {\n            fut._err = errors.Error.JsonParseError;\n            fut._done.store(true, .release);\n            return;\n        };\n        defer parsed.deinit();\n\n        if (parsed.value.@\"error\") |api_err| {\n            if (api_err.code > 0) {\n                fut._err = errors.Error.ApiError;\n                fut._done.store(true, .release);\n                return;\n            }\n        }\n\n        const stream_owned: ?[]u8 = if (parsed.value.stream) |stream|\n            self.allocator.dupe(u8, stream) catch {\n                fut._err = error.OutOfMemory;\n                fut._done.store(true, .release);\n                return;\n            }\n        else\n            null;\n\n        const domain_owned: ?[]u8 = if (parsed.value.domain) |domain|\n            self.allocator.dupe(u8, domain) catch {\n                if (stream_owned) |stream| self.allocator.free(stream);\n                fut._err = error.OutOfMemory;\n                fut._done.store(true, .release);\n                return;\n            }\n        else\n            null;\n\n        fut._stream_owned = stream_owned;\n        fut._domain_owned = domain_owned;\n        fut._ack = .{\n            .stream = if (stream_owned) |stream| stream else null,\n            .seq = parsed.value.seq,\n            .duplicate = parsed.value.duplicate,\n            .domain = if (domain_owned) |domain| domain else null,\n        };\n        fut._done.store(true, .release);\n    }\n\n    /// Cleans up the publisher. Unsubscribes and\n    /// fails all pending futures.\n    pub fn cleanup(self: *AsyncPublisher) void {\n        if (self.sub) |s| {\n            s.deinit();\n            self.sub = null;\n        }\n\n        // Fail all pending futures\n        const io = self.client.io;\n        self.mu.lock(io) catch {};\n        var it = self.pending.iterator();\n        while (it.next()) |entry| {\n            const fut = entry.value_ptr.*;\n            fut._err = errors.Error.Timeout;\n            fut._done.store(true, .release);\n        }\n        self.pending.clearAndFree(self.allocator);\n        self.mu.unlock(io);\n        self.pending_count.store(0, .release);\n    }\n\n    /// Alias for cleanup.\n    pub fn deinit(self: *AsyncPublisher) void {\n        self.cleanup();\n    }\n};\n\nfn sleepNs(ns: u64) void {\n    var ts: std.posix.timespec = .{\n        .sec = @intCast(ns / 1_000_000_000),\n        .nsec = @intCast(ns % 1_000_000_000),\n    };\n    _ = std.posix.system.nanosleep(&ts, &ts);\n}\n"
  },
  {
    "path": "src/jetstream/consumer.zig",
    "content": "//! Shared consumer abstractions for JetStream pull and push.\n//!\n//! Types here are designed for reuse across consumption modes.\n//! Pull consumers (pull.zig) use them now; push consumers\n//! (push.zig) will import them in v1.1 without changes.\n\nconst std = @import(\"std\");\nconst JsMsg = @import(\"message.zig\").JsMsg;\n\n/// Callback handler for JetStream message consumption.\n/// Comptime vtable pattern matching Client.MsgHandler but\n/// taking `*JsMsg` instead of `*const Message`.\npub const JsMsgHandler = struct {\n    ptr: *anyopaque,\n    vtable: *const VTable,\n\n    pub const VTable = struct {\n        onMessage: *const fn (\n            *anyopaque,\n            *JsMsg,\n        ) void,\n    };\n\n    /// Creates a handler from a concrete type via comptime.\n    /// The type must have `onMessage(self, *JsMsg) void`.\n    pub fn init(\n        comptime T: type,\n        ptr: *T,\n    ) JsMsgHandler {\n        const gen = struct {\n            fn onMessage(\n                p: *anyopaque,\n                msg: *JsMsg,\n            ) void {\n                const self: *T = @ptrCast(\n                    @alignCast(p),\n                );\n                self.onMessage(msg);\n            }\n        };\n        return .{\n            .ptr = ptr,\n            .vtable = &.{\n                .onMessage = gen.onMessage,\n            },\n        };\n    }\n\n    /// Creates a handler from a plain function pointer.\n    pub fn initFn(\n        func: *const fn (*JsMsg) void,\n    ) JsMsgHandler {\n        const gen = struct {\n            fn onMessage(\n                p: *anyopaque,\n                msg: *JsMsg,\n            ) void {\n                const f: *const fn (*JsMsg) void =\n                    @ptrCast(@alignCast(p));\n                f(msg);\n            }\n        };\n        return .{\n            .ptr = @ptrCast(@constCast(func)),\n            .vtable = &.{\n                .onMessage = gen.onMessage,\n            },\n        };\n    }\n\n    /// Dispatches a message to the handler.\n    pub fn dispatch(\n        self: JsMsgHandler,\n        msg: *JsMsg,\n    ) void {\n        std.debug.assert(self.ptr != undefined_ptr);\n        self.vtable.onMessage(self.ptr, msg);\n    }\n\n    const undefined_ptr: *anyopaque = @ptrFromInt(\n        std.math.maxInt(usize),\n    );\n};\n\n/// Options for continuous message consumption.\n/// Shared by pull and (future) push consumers.\npub const ConsumeOpts = struct {\n    max_messages: u32 = 500,\n    max_bytes: ?u32 = null,\n    /// Idle heartbeat interval in ms. 0 = disabled.\n    /// When enabled, must be less than expires_ms.\n    /// Server sends status 100 at this interval when\n    /// idle. Client detects stale connection after 2\n    /// consecutive misses.\n    heartbeat_ms: u32 = 0,\n    threshold_pct: u8 = 50,\n    expires_ms: u32 = 30000,\n    err_handler: ?ErrHandler = null,\n};\n\n/// Error callback for consume operations.\npub const ErrHandler = *const fn (anyerror) void;\n\n/// Controls an active consume() operation.\n/// Standalone struct -- NOT coupled to pull or push\n/// specifics. Both modes reuse the same context type.\npub const ConsumeContext = struct {\n    _state: std.atomic.Value(State) =\n        std.atomic.Value(State).init(.running),\n    _shared_state: ?*std.atomic.Value(State) = null,\n    _allocator: ?std.mem.Allocator = null,\n    _task: ?std.Io.Future(void) = null,\n    _io: std.Io = undefined,\n    _thread: ?std.Thread = null,\n\n    pub const State = enum(u8) {\n        running = 0,\n        draining = 1,\n        stopped = 2,\n    };\n\n    /// Reads the current state atomically.\n    pub fn state(self: *const ConsumeContext) State {\n        const state_ptr = self._shared_state orelse\n            @constCast(&self._state);\n        return state_ptr.load(.acquire);\n    }\n\n    /// Stops consumption immediately. Buffered messages\n    /// that have not been dispatched are discarded.\n    pub fn stop(self: *ConsumeContext) void {\n        std.debug.assert(self.state() != .stopped);\n        const state_ptr = self._shared_state orelse &self._state;\n        state_ptr.store(.stopped, .release);\n    }\n\n    /// Signals the consumer to process remaining buffered\n    /// messages and then stop.\n    pub fn drain(self: *ConsumeContext) void {\n        std.debug.assert(self.state() == .running);\n        const state_ptr = self._shared_state orelse &self._state;\n        state_ptr.store(.draining, .release);\n    }\n\n    /// Returns true if consumption has fully stopped.\n    pub fn closed(self: *const ConsumeContext) bool {\n        return self.state() == .stopped;\n    }\n\n    /// Stops the background task and cleans up. The\n    /// background task handles its own sub cleanup.\n    pub fn deinit(self: *ConsumeContext) void {\n        const state_ptr = self._shared_state orelse &self._state;\n        state_ptr.store(.stopped, .release);\n        if (self._thread) |t| {\n            t.join();\n            self._thread = null;\n        } else if (self._task) |*t| {\n            t.cancel(self._io);\n            self._task = null;\n        }\n        if (self._shared_state) |shared| {\n            if (self._allocator) |allocator| {\n                allocator.destroy(shared);\n            }\n            self._shared_state = null;\n            self._allocator = null;\n        }\n    }\n};\n\n/// Tracks idle heartbeat health for pull/push consumers.\n/// The server sends status 100 heartbeats at the configured\n/// interval. If we miss `max_misses` consecutive heartbeats\n/// (each window = 2x interval), the connection is stale.\n///\n/// Usage: set receive timeout to `timeoutMs()`, call\n/// `recordActivity()` on any message, `recordTimeout()`\n/// on receive timeout. Reusable by pull, push, and\n/// ordered consumers.\npub const HeartbeatMonitor = struct {\n    heartbeat_ms: u32,\n    misses: u32 = 0,\n    max_misses: u32 = 2,\n\n    /// Creates a monitor for the given heartbeat interval.\n    pub fn init(heartbeat_ms: u32) HeartbeatMonitor {\n        std.debug.assert(heartbeat_ms > 0);\n        return .{ .heartbeat_ms = heartbeat_ms };\n    }\n\n    /// Returns the timeout (ms) to use for receive ops.\n    /// Set to 2x heartbeat interval so one missed heartbeat\n    /// doesn't immediately trigger an error.\n    pub fn timeoutMs(\n        self: *const HeartbeatMonitor,\n    ) u32 {\n        std.debug.assert(self.heartbeat_ms > 0);\n        return self.heartbeat_ms *| 2;\n    }\n\n    /// Call when any message or heartbeat is received.\n    /// Resets the miss counter.\n    pub fn recordActivity(\n        self: *HeartbeatMonitor,\n    ) void {\n        self.misses = 0;\n    }\n\n    /// Call when a receive times out with no data.\n    /// Returns true if heartbeat is considered lost.\n    pub fn recordTimeout(\n        self: *HeartbeatMonitor,\n    ) bool {\n        self.misses += 1;\n        return self.misses >= self.max_misses;\n    }\n\n    /// Returns true if heartbeat is currently healthy.\n    pub fn isHealthy(\n        self: *const HeartbeatMonitor,\n    ) bool {\n        return self.misses < self.max_misses;\n    }\n};\n\n// -- Tests --\n\ntest \"JsMsgHandler dispatch with struct\" {\n    const Counter = struct {\n        count: u32 = 0,\n        pub fn onMessage(self: *@This(), _: *JsMsg) void {\n            self.count += 1;\n        }\n    };\n\n    var counter = Counter{};\n    const handler = JsMsgHandler.init(Counter, &counter);\n\n    // Create a minimal JsMsg for testing dispatch\n    var msg = JsMsg{\n        .msg = undefined,\n        .client = undefined,\n    };\n    handler.dispatch(&msg);\n    handler.dispatch(&msg);\n\n    try std.testing.expectEqual(@as(u32, 2), counter.count);\n}\n\ntest \"ConsumeContext state transitions\" {\n    var ctx = ConsumeContext{};\n    try std.testing.expect(!ctx.closed());\n    try std.testing.expectEqual(\n        ConsumeContext.State.running,\n        ctx.state(),\n    );\n\n    ctx.drain();\n    try std.testing.expectEqual(\n        ConsumeContext.State.draining,\n        ctx.state(),\n    );\n    try std.testing.expect(!ctx.closed());\n\n    ctx._state.store(.running, .release);\n    ctx.stop();\n    try std.testing.expect(ctx.closed());\n    try std.testing.expectEqual(\n        ConsumeContext.State.stopped,\n        ctx.state(),\n    );\n}\n\ntest \"HeartbeatMonitor timeout detection\" {\n    var hb = HeartbeatMonitor.init(5000);\n    try std.testing.expectEqual(\n        @as(u32, 10000),\n        hb.timeoutMs(),\n    );\n    try std.testing.expect(hb.isHealthy());\n\n    // First timeout: not yet lost\n    try std.testing.expect(!hb.recordTimeout());\n    try std.testing.expect(hb.isHealthy());\n\n    // Second timeout: heartbeat lost\n    try std.testing.expect(hb.recordTimeout());\n    try std.testing.expect(!hb.isHealthy());\n\n    // Activity resets\n    hb.recordActivity();\n    try std.testing.expect(hb.isHealthy());\n    try std.testing.expectEqual(@as(u32, 0), hb.misses);\n}\n"
  },
  {
    "path": "src/jetstream/errors.zig",
    "content": "//! JetStream error types and error codes.\n//!\n//! Two-layer error handling: Zig error unions for transport/protocol\n//! failures, and ApiError struct for JetStream-specific API errors\n//! returned by the server.\n\nconst std = @import(\"std\");\n\n/// JetStream API error returned by the server.\n/// Stored inline on the JetStream context (no allocation).\npub const ApiError = struct {\n    code: u16,\n    err_code: u16,\n    description_buf: [255]u8 = undefined,\n    description_len: u8 = 0,\n\n    /// Returns the error description string.\n    pub fn description(self: *const ApiError) []const u8 {\n        return self.description_buf[0..self.description_len];\n    }\n\n    /// Creates an ApiError from a parsed JSON error object.\n    pub fn fromJson(json_err: ApiErrorJson) ApiError {\n        std.debug.assert(json_err.code > 0);\n        var result = ApiError{\n            .code = json_err.code,\n            .err_code = json_err.err_code,\n        };\n        if (json_err.description) |desc| {\n            const len: u8 = @intCast(@min(\n                desc.len,\n                result.description_buf.len,\n            ));\n            @memcpy(\n                result.description_buf[0..len],\n                desc[0..len],\n            );\n            result.description_len = len;\n        }\n        return result;\n    }\n};\n\n/// JSON-deserializable error object from JetStream API responses.\npub const ApiErrorJson = struct {\n    code: u16 = 0,\n    err_code: u16 = 0,\n    description: ?[]const u8 = null,\n};\n\n/// Well-known JetStream error codes (from Go's errors.go).\npub const ErrCode = struct {\n    pub const bad_request: u16 = 10003;\n    pub const consumer_create: u16 = 10012;\n    pub const consumer_not_found: u16 = 10014;\n    pub const max_consumers_limit: u16 = 10026;\n    pub const message_not_found: u16 = 10037;\n    pub const js_not_enabled_for_account: u16 = 10039;\n    pub const stream_name_in_use: u16 = 10058;\n    pub const stream_not_found: u16 = 10059;\n    pub const stream_wrong_last_seq: u16 = 10071;\n    pub const js_not_enabled: u16 = 10076;\n    pub const consumer_already_exists: u16 = 10105;\n    pub const duplicate_filter_subjects: u16 = 10136;\n    pub const overlapping_filter_subjects: u16 = 10138;\n    pub const consumer_empty_filter: u16 = 10139;\n    pub const consumer_exists: u16 = 10148;\n    pub const consumer_does_not_exist: u16 = 10149;\n};\n\n/// JetStream error set for Zig error unions.\npub const Error = error{\n    Timeout,\n    NoResponders,\n    ApiError,\n    JsonParseError,\n    SubjectTooLong,\n    NoHeartbeat,\n    ConsumerDeleted,\n    OrderedReset,\n    InvalidName,\n    InvalidApiPrefix,\n    InvalidBucket,\n    NameTooLong,\n    InvalidKey,\n    InvalidData,\n    KeyNotFound,\n    WrongLastRevision,\n    ThreadSpawnFailed,\n};\n\ntest \"ApiError.fromJson\" {\n    const json_err = ApiErrorJson{\n        .code = 404,\n        .err_code = ErrCode.stream_not_found,\n        .description = \"stream not found\",\n    };\n    const api_err = ApiError.fromJson(json_err);\n    try std.testing.expectEqual(@as(u16, 404), api_err.code);\n    try std.testing.expectEqual(\n        ErrCode.stream_not_found,\n        api_err.err_code,\n    );\n    try std.testing.expectEqualStrings(\n        \"stream not found\",\n        api_err.description(),\n    );\n}\n\ntest \"ApiError.fromJson truncates long description\" {\n    const long = \"x\" ** 300;\n    const json_err = ApiErrorJson{\n        .code = 400,\n        .err_code = ErrCode.bad_request,\n        .description = long,\n    };\n    const api_err = ApiError.fromJson(json_err);\n    try std.testing.expectEqual(@as(u8, 255), api_err.description_len);\n    try std.testing.expectEqual(@as(usize, 255), api_err.description().len);\n}\n\ntest \"ApiError.fromJson null description\" {\n    const json_err = ApiErrorJson{\n        .code = 500,\n        .err_code = 0,\n        .description = null,\n    };\n    const api_err = ApiError.fromJson(json_err);\n    try std.testing.expectEqual(@as(u8, 0), api_err.description_len);\n    try std.testing.expectEqualStrings(\"\", api_err.description());\n}\n"
  },
  {
    "path": "src/jetstream/kv.zig",
    "content": "//! JetStream Key-Value Store.\n//!\n//! A key-value store backed by a JetStream stream. Keys are\n//! NATS subjects under `$KV.{bucket}.{key}`, values are\n//! message payloads. Supports history, delete markers, watch,\n//! and optimistic concurrency via revision numbers.\n\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\nconst headers_mod = nats.protocol.headers;\n\nconst types = @import(\"types.zig\");\nconst errors = @import(\"errors.zig\");\nconst JetStream = @import(\"JetStream.zig\");\nconst PullSubscription = @import(\n    \"pull.zig\",\n).PullSubscription;\nconst JsMsg = @import(\"message.zig\").JsMsg;\n\nvar ephemeral_counter: std.atomic.Value(u32) =\n    std.atomic.Value(u32).init(0);\n\nfn validateKeyToken(token: []const u8, allow_wildcards: bool, last: bool) !void {\n    if (token.len == 0) return errors.Error.InvalidKey;\n    if (std.mem.eql(u8, token, \"*\")) {\n        if (!allow_wildcards) return errors.Error.InvalidKey;\n        return;\n    }\n    if (std.mem.eql(u8, token, \">\")) {\n        if (!allow_wildcards or !last) return errors.Error.InvalidKey;\n        return;\n    }\n    for (token) |c| {\n        if (c <= 0x20 or c == 0x7f or c == '*' or c == '>') {\n            return errors.Error.InvalidKey;\n        }\n    }\n}\n\nfn validateKeyLike(value: []const u8, allow_wildcards: bool) !void {\n    if (value.len == 0) return errors.Error.InvalidKey;\n    var start: usize = 0;\n    var i: usize = 0;\n    while (i <= value.len) : (i += 1) {\n        if (i == value.len or value[i] == '.') {\n            try validateKeyToken(\n                value[start..i],\n                allow_wildcards,\n                i == value.len,\n            );\n            start = i + 1;\n        }\n    }\n}\n\nfn validateKey(key: []const u8) !void {\n    try validateKeyLike(key, false);\n}\n\nfn validateKeyPattern(pattern: []const u8) !void {\n    try validateKeyLike(pattern, true);\n}\n\n/// Key-value store bound to a specific bucket.\n/// Created via `JetStream.createKeyValue()` or\n/// `JetStream.keyValue()`.\npub const KeyValue = struct {\n    js: *JetStream,\n    bucket_buf: [64]u8 = undefined,\n    bucket_len: u8 = 0,\n    stream_buf: [68]u8 = undefined,\n    stream_len: u8 = 0,\n    // Stable storage for ephemeral consumer names\n    _eph_name_buf: [48]u8 = undefined,\n    _eph_name_len: u8 = 0,\n\n    /// Returns the bucket name.\n    pub fn bucket(self: *const KeyValue) []const u8 {\n        std.debug.assert(self.bucket_len > 0);\n        return self.bucket_buf[0..self.bucket_len];\n    }\n\n    /// Returns the underlying stream name.\n    fn streamName(\n        self: *const KeyValue,\n    ) []const u8 {\n        std.debug.assert(self.stream_len > 0);\n        return self.stream_buf[0..self.stream_len];\n    }\n\n    /// Builds the KV subject for a key. Validates key\n    /// contains no wildcards or control characters.\n    fn kvSubject(\n        self: *const KeyValue,\n        key: []const u8,\n        buf: []u8,\n    ) ![]const u8 {\n        try validateKey(key);\n        return std.fmt.bufPrint(\n            buf,\n            \"$KV.{s}.{s}\",\n            .{ self.bucket(), key },\n        ) catch return errors.Error.SubjectTooLong;\n    }\n\n    // -- Get --\n\n    /// Gets the latest value for a key. Returns null\n    /// if the key does not exist. Returns the entry\n    /// even if it's a delete/purge marker (check\n    /// entry.operation).\n    pub fn get(\n        self: *KeyValue,\n        key: []const u8,\n    ) !?types.KeyValueEntry {\n        std.debug.assert(key.len > 0);\n        std.debug.assert(self.bucket_len > 0);\n        return self.getBySubject(key);\n    }\n\n    /// Gets a specific revision of a key.\n    pub fn getRevision(\n        self: *KeyValue,\n        key: []const u8,\n        revision: u64,\n    ) !?types.KeyValueEntry {\n        std.debug.assert(key.len > 0);\n        std.debug.assert(revision > 0);\n        return self.getBySeq(key, revision);\n    }\n\n    fn getBySubject(\n        self: *KeyValue,\n        key: []const u8,\n    ) !?types.KeyValueEntry {\n        var subj_buf: [256]u8 = undefined;\n        const kv_subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n\n        var api_buf: [512]u8 = undefined;\n        const api_subj = std.fmt.bufPrint(\n            &api_buf,\n            \"STREAM.MSG.GET.{s}\",\n            .{self.streamName()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        const req = types.MsgGetRequest{\n            .last_by_subj = kv_subj,\n        };\n        return self.fetchAndParse(api_subj, req, key);\n    }\n\n    fn getBySeq(\n        self: *KeyValue,\n        key: []const u8,\n        seq: u64,\n    ) !?types.KeyValueEntry {\n        var api_buf: [512]u8 = undefined;\n        const api_subj = std.fmt.bufPrint(\n            &api_buf,\n            \"STREAM.MSG.GET.{s}\",\n            .{self.streamName()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        const req = types.MsgGetRequest{ .seq = seq };\n        return self.fetchAndParse(api_subj, req, key);\n    }\n\n    fn fetchAndParse(\n        self: *KeyValue,\n        api_subj: []const u8,\n        req: types.MsgGetRequest,\n        key: []const u8,\n    ) !?types.KeyValueEntry {\n        var resp = self.js.apiRequest(\n            types.MsgGetResponse,\n            api_subj,\n            req,\n        ) catch |err| {\n            if (err == error.ApiError) {\n                if (self.js.lastApiError()) |ae| {\n                    if (ae.err_code == 10037)\n                        return null;\n                }\n            }\n            return err;\n        };\n        defer resp.deinit();\n\n        const msg = resp.value.message orelse\n            return null;\n        const seq = msg.seq;\n\n        // Validate subject matches expected key\n        if (msg.subject) |subj| {\n            var exp_buf: [256]u8 = undefined;\n            const expected = std.fmt.bufPrint(\n                &exp_buf,\n                \"$KV.{s}.{s}\",\n                .{ self.bucket(), key },\n            ) catch return errors.Error.SubjectTooLong;\n            if (!std.mem.eql(u8, subj, expected))\n                return null;\n        }\n\n        // Determine operation from stored headers\n        var op: types.KeyValueOp = .put;\n        if (msg.hdrs) |hdr_b64| {\n            // hdrs is base64-encoded\n            var decode_buf: [1024]u8 = undefined;\n            const decoded = decodeBase64(\n                hdr_b64,\n                &decode_buf,\n            ) orelse return types.KeyValueEntry{\n                .bucket = self.bucket(),\n                .key = key,\n                .value = \"\",\n                .revision = seq,\n                .operation = .put,\n            };\n            op = parseKvOp(decoded);\n        }\n\n        // Data is base64-encoded in JSON response —\n        // decode before returning to caller\n        const allocator = self.js.allocator;\n        var val: []const u8 = \"\";\n        var val_alloc: ?Allocator = null;\n        if (msg.data) |data_b64| {\n            if (data_b64.len > 0 and op == .put) {\n                // Decode base64 into allocated buffer\n                const decoder = std.base64.standard\n                    .Decoder;\n                const dec_len = decoder\n                    .calcSizeForSlice(data_b64) catch {\n                    return error.InvalidData;\n                };\n                const decoded_val = try allocator.alloc(\n                    u8,\n                    dec_len,\n                );\n                decoder.decode(\n                    decoded_val[0..dec_len],\n                    data_b64,\n                ) catch {\n                    allocator.free(decoded_val);\n                    return error.InvalidData;\n                };\n                val = decoded_val[0..dec_len];\n                val_alloc = allocator;\n            }\n        }\n\n        return types.KeyValueEntry{\n            .bucket = self.bucket(),\n            .key = key,\n            .value = val,\n            .revision = seq,\n            .operation = op,\n            .value_allocator = val_alloc,\n        };\n    }\n\n    // -- Put --\n\n    /// Puts a value for a key. Returns the revision\n    /// (sequence number).\n    pub fn put(\n        self: *KeyValue,\n        key: []const u8,\n        value: []const u8,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        std.debug.assert(self.bucket_len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        var resp = try self.js.publish(subj, value);\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Puts a string value for a key. Convenience\n    /// wrapper around put() -- in Zig, strings are\n    /// already []const u8.\n    pub fn putString(\n        self: *KeyValue,\n        key: []const u8,\n        value: []const u8,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        return self.put(key, value);\n    }\n\n    /// Creates a key only if it does not already exist.\n    /// Returns the revision, or error.ApiError if the\n    /// key already exists (check lastApiError for\n    /// stream_wrong_last_seq).\n    pub fn create(\n        self: *KeyValue,\n        key: []const u8,\n        value: []const u8,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        var resp = try self.js.publishWithOpts(\n            subj,\n            value,\n            .{ .expected_last_subj_seq = 0 },\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Updates a key only if the current revision matches.\n    /// Returns the new revision, or error.ApiError on\n    /// revision mismatch.\n    pub fn update(\n        self: *KeyValue,\n        key: []const u8,\n        value: []const u8,\n        revision: u64,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        std.debug.assert(revision > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        var resp = try self.js.publishWithOpts(\n            subj,\n            value,\n            .{ .expected_last_subj_seq = revision },\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Options for KV create operations.\n    pub const CreateOpts = struct {\n        /// Per-key TTL (e.g., \"5s\", \"1m\"). Requires\n        /// the bucket to have allow_msg_ttl enabled.\n        ttl: ?[]const u8 = null,\n    };\n\n    /// Creates a key with options (e.g., per-key TTL).\n    pub fn createWithOpts(\n        self: *KeyValue,\n        key: []const u8,\n        value: []const u8,\n        opts: CreateOpts,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        var resp = try self.js.publishWithOpts(\n            subj,\n            value,\n            .{\n                .expected_last_subj_seq = 0,\n                .ttl = opts.ttl,\n            },\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Options for conditional delete/purge.\n    pub const KvDeleteOpts = struct {\n        /// Only delete if latest revision matches.\n        last_revision: ?u64 = null,\n    };\n\n    // -- Delete / Purge --\n\n    /// Soft-deletes a key by publishing a delete marker.\n    /// Returns the revision number. The key can still\n    /// appear in history.\n    pub fn delete(\n        self: *KeyValue,\n        key: []const u8,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        const hdrs = [_]nats.protocol.headers.Entry{\n            .{\n                .key = \"KV-Operation\",\n                .value = \"DEL\",\n            },\n        };\n        var resp = try self.js.publishRetry(\n            subj,\n            \"\",\n            &hdrs,\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Purges a key and all its history.\n    /// Returns the revision number.\n    pub fn purge(\n        self: *KeyValue,\n        key: []const u8,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        const hdrs = [_]nats.protocol.headers.Entry{\n            .{\n                .key = \"KV-Operation\",\n                .value = \"PURGE\",\n            },\n            .{\n                .key = \"Nats-Rollup\",\n                .value = \"sub\",\n            },\n        };\n        var resp = try self.js.publishRetry(\n            subj,\n            \"\",\n            &hdrs,\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Deletes a key only if latest revision matches.\n    pub fn deleteWithOpts(\n        self: *KeyValue,\n        key: []const u8,\n        opts: KvDeleteOpts,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        var hdr_entries: [2]nats.protocol.headers.Entry =\n            undefined;\n        hdr_entries[0] = .{\n            .key = \"KV-Operation\",\n            .value = \"DEL\",\n        };\n        var hdr_count: usize = 1;\n        var rev_buf: [20]u8 = undefined;\n        if (opts.last_revision) |rev| {\n            const s = std.fmt.bufPrint(\n                &rev_buf,\n                \"{d}\",\n                .{rev},\n            ) catch unreachable;\n            hdr_entries[1] = .{\n                .key = headers_mod.HeaderName\n                    .expected_last_subj_seq,\n                .value = s,\n            };\n            hdr_count = 2;\n        }\n        var resp = try self.js.publishRetry(\n            subj,\n            \"\",\n            hdr_entries[0..hdr_count],\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    /// Purges a key only if latest revision matches.\n    pub fn purgeWithOpts(\n        self: *KeyValue,\n        key: []const u8,\n        opts: KvDeleteOpts,\n    ) !u64 {\n        std.debug.assert(key.len > 0);\n        var subj_buf: [256]u8 = undefined;\n        const subj = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n        var hdr_entries: [3]nats.protocol.headers.Entry =\n            undefined;\n        hdr_entries[0] = .{\n            .key = \"KV-Operation\",\n            .value = \"PURGE\",\n        };\n        hdr_entries[1] = .{\n            .key = \"Nats-Rollup\",\n            .value = \"sub\",\n        };\n        var hdr_count: usize = 2;\n        var rev_buf: [20]u8 = undefined;\n        if (opts.last_revision) |rev| {\n            const s = std.fmt.bufPrint(\n                &rev_buf,\n                \"{d}\",\n                .{rev},\n            ) catch unreachable;\n            hdr_entries[2] = .{\n                .key = headers_mod.HeaderName\n                    .expected_last_subj_seq,\n                .value = s,\n            };\n            hdr_count = 3;\n        }\n        var resp = try self.js.publishRetry(\n            subj,\n            \"\",\n            hdr_entries[0..hdr_count],\n        );\n        defer resp.deinit();\n        return resp.value.seq;\n    }\n\n    // -- Keys --\n\n    /// Returns all current (non-deleted) keys in the\n    /// bucket. Creates an ephemeral consumer with\n    /// last_per_subject deliver policy. Caller owns\n    /// the slice; free each key + slice with allocator.\n    pub fn keys(\n        self: *KeyValue,\n        allocator: Allocator,\n    ) ![][]const u8 {\n        std.debug.assert(self.bucket_len > 0);\n\n        var subj_buf: [256]u8 = undefined;\n        const filter = std.fmt.bufPrint(\n            &subj_buf,\n            \"$KV.{s}.>\",\n            .{self.bucket()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        var pull = try self.createEphemeralPull(\n            filter,\n            .last_per_subject,\n            null,\n        );\n        defer self.deleteEphemeralPull(&pull);\n\n        var result: std.ArrayList([]const u8) = .empty;\n        errdefer {\n            for (result.items) |k| allocator.free(k);\n            result.deinit(allocator);\n        }\n\n        while (true) {\n            var msg = (try pull.next(3000)) orelse break;\n            defer msg.deinit();\n\n            const subj = msg.subject();\n            const plen: usize = 4 +\n                @as(usize, self.bucket_len) + 1;\n            if (subj.len > plen) {\n                const k = subj[plen..];\n                if (msg.headers()) |h| {\n                    if (isDeleteOp(h)) continue;\n                }\n                const owned = try allocator.dupe(\n                    u8,\n                    k,\n                );\n                result.append(allocator, owned) catch |e| {\n                    allocator.free(owned);\n                    return e;\n                };\n            }\n        }\n\n        return result.toOwnedSlice(allocator);\n    }\n\n    // -- History --\n\n    /// Returns all revisions for a key (up to max\n    /// history). Caller owns the returned slice.\n    pub fn history(\n        self: *KeyValue,\n        allocator: Allocator,\n        key: []const u8,\n    ) ![]types.KeyValueEntry {\n        std.debug.assert(key.len > 0);\n\n        var subj_buf: [256]u8 = undefined;\n        const filter = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n\n        var pull = try self.createEphemeralPull(\n            filter,\n            .all,\n            null,\n        );\n        defer self.deleteEphemeralPull(&pull);\n\n        var result: std.ArrayList(\n            types.KeyValueEntry,\n        ) = .empty;\n        errdefer {\n            for (result.items) |*entry| entry.deinit();\n            result.deinit(allocator);\n        }\n\n        while (true) {\n            var msg = (try pull.next(3000)) orelse break;\n            defer msg.deinit();\n\n            var op: types.KeyValueOp = .put;\n            if (msg.headers()) |h| {\n                op = parseKvOp(h);\n            }\n\n            const md = msg.metadata();\n            const seq = if (md) |m|\n                m.stream_seq\n            else\n                0;\n\n            // Extract value before defer deinit frees msg\n            const data = msg.data();\n            var val: []const u8 = \"\";\n            var val_alloc: ?Allocator = null;\n            if (data.len > 0 and op == .put) {\n                val = try allocator.dupe(\n                    u8,\n                    data,\n                );\n                val_alloc = allocator;\n            }\n\n            result.append(allocator, .{\n                .bucket = self.bucket(),\n                .key = key,\n                .value = val,\n                .revision = seq,\n                .operation = op,\n                .value_allocator = val_alloc,\n            }) catch |err| {\n                if (val_alloc) |a| a.free(val);\n                return err;\n            };\n        }\n\n        return result.toOwnedSlice(allocator);\n    }\n\n    /// Returns all revisions with watch options.\n    pub fn historyWithOpts(\n        self: *KeyValue,\n        allocator: Allocator,\n        key: []const u8,\n        opts: types.WatchOpts,\n    ) ![]types.KeyValueEntry {\n        std.debug.assert(key.len > 0);\n\n        var subj_buf: [256]u8 = undefined;\n        const filter = try self.kvSubject(\n            key,\n            &subj_buf,\n        );\n\n        const dp: types.DeliverPolicy = if (opts\n            .include_history) .all else .all;\n        var pull = try self.createEphemeralPull(\n            filter,\n            dp,\n            if (opts.resume_from_revision) |r| r else null,\n        );\n        defer self.deleteEphemeralPull(&pull);\n\n        var result: std.ArrayList(\n            types.KeyValueEntry,\n        ) = .empty;\n        errdefer {\n            for (result.items) |*entry| entry.deinit();\n            result.deinit(allocator);\n        }\n\n        while (true) {\n            var msg = (try pull.next(3000)) orelse break;\n            defer msg.deinit();\n\n            var op: types.KeyValueOp = .put;\n            if (msg.headers()) |h| {\n                op = parseKvOp(h);\n            }\n\n            if (opts.ignore_deletes and\n                (op == .delete or op == .purge))\n                continue;\n\n            const md = msg.metadata();\n            const seq = if (md) |m|\n                m.stream_seq\n            else\n                0;\n\n            if (opts.meta_only) {\n                try result.append(allocator, .{\n                    .bucket = self.bucket(),\n                    .key = key,\n                    .value = \"\",\n                    .revision = seq,\n                    .operation = op,\n                });\n                continue;\n            }\n\n            const data = msg.data();\n            var val: []const u8 = \"\";\n            var val_alloc: ?Allocator = null;\n            if (data.len > 0 and op == .put) {\n                val = try allocator.dupe(u8, data);\n                val_alloc = allocator;\n            }\n\n            result.append(allocator, .{\n                .bucket = self.bucket(),\n                .key = key,\n                .value = val,\n                .revision = seq,\n                .operation = op,\n                .value_allocator = val_alloc,\n            }) catch |err| {\n                if (val_alloc) |a| a.free(val);\n                return err;\n            };\n        }\n\n        return result.toOwnedSlice(allocator);\n    }\n\n    /// Returns a streaming key lister with filters.\n    /// Only returns keys matching the given patterns.\n    pub fn listKeysFiltered(\n        self: *KeyValue,\n        patterns: []const []const u8,\n    ) !KeyLister {\n        std.debug.assert(self.bucket_len > 0);\n        std.debug.assert(patterns.len > 0);\n        std.debug.assert(patterns.len <= 16);\n\n        var filters: [16][]const u8 = undefined;\n        var filter_bufs: [16][256]u8 = undefined;\n\n        for (patterns, 0..) |p, i| {\n            try validateKeyPattern(p);\n            const f = std.fmt.bufPrint(\n                &filter_bufs[i],\n                \"$KV.{s}.{s}\",\n                .{ self.bucket(), p },\n            ) catch return errors.Error.SubjectTooLong;\n            filters[i] = filter_bufs[i][0..f.len];\n        }\n\n        const seq = ephemeral_counter.fetchAdd(\n            1,\n            .monotonic,\n        );\n        const name = std.fmt.bufPrint(\n            &self._eph_name_buf,\n            \"kv{d}x{d}\",\n            .{ seq, @intFromPtr(self) % 99999 },\n        ) catch unreachable;\n        self._eph_name_len = @intCast(name.len);\n\n        const fs = filters[0..patterns.len];\n        var resp = try self.js.createConsumer(\n            self.streamName(),\n            .{\n                .name = name,\n                .ack_policy = .none,\n                .deliver_policy = .last_per_subject,\n                .filter_subjects = fs,\n                .mem_storage = true,\n                .inactive_threshold = 60_000_000_000,\n            },\n        );\n        resp.deinit();\n\n        var pull = PullSubscription{\n            .js = self.js,\n            .stream = self.streamName(),\n        };\n        try pull.setConsumer(name);\n\n        return KeyLister{ .kv = self, .pull = pull };\n    }\n\n    // -- Watch --\n\n    /// Watches a key pattern for real-time updates.\n    /// Delivers current values (last per subject)\n    /// first, then continues with live updates.\n    pub fn watch(\n        self: *KeyValue,\n        key_pattern: []const u8,\n    ) !KvWatcher {\n        return self.watchWithOpts(key_pattern, .{});\n    }\n\n    /// Watches with configurable options.\n    pub fn watchWithOpts(\n        self: *KeyValue,\n        key_pattern: []const u8,\n        opts: types.WatchOpts,\n    ) !KvWatcher {\n        try validateKeyPattern(key_pattern);\n        var subj_buf: [256]u8 = undefined;\n        const filter = std.fmt.bufPrint(\n            &subj_buf,\n            \"$KV.{s}.{s}\",\n            .{ self.bucket(), key_pattern },\n        ) catch return errors.Error.SubjectTooLong;\n\n        const dp = watchDeliverPolicy(opts);\n        const start = if (opts.resume_from_revision) |r|\n            r\n        else\n            null;\n\n        const pull = try self.createEphemeralPull(\n            filter,\n            dp,\n            start,\n        );\n\n        return KvWatcher{\n            .kv = self,\n            .pull = pull,\n            .opts = opts,\n        };\n    }\n\n    /// Watches all keys in the bucket.\n    pub fn watchAll(self: *KeyValue) !KvWatcher {\n        return self.watchAllWithOpts(.{});\n    }\n\n    /// Watches all keys with configurable options.\n    pub fn watchAllWithOpts(\n        self: *KeyValue,\n        opts: types.WatchOpts,\n    ) !KvWatcher {\n        var subj_buf: [256]u8 = undefined;\n        const filter = std.fmt.bufPrint(\n            &subj_buf,\n            \"$KV.{s}.>\",\n            .{self.bucket()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        const dp = watchDeliverPolicy(opts);\n        const start = if (opts.resume_from_revision) |r|\n            r\n        else\n            null;\n\n        const pull = try self.createEphemeralPull(\n            filter,\n            dp,\n            start,\n        );\n\n        return KvWatcher{\n            .kv = self,\n            .pull = pull,\n            .opts = opts,\n        };\n    }\n\n    /// Watches multiple key patterns simultaneously.\n    /// Uses filter_subjects on the consumer config.\n    pub fn watchFiltered(\n        self: *KeyValue,\n        patterns: []const []const u8,\n        opts: types.WatchOpts,\n    ) !KvWatcher {\n        std.debug.assert(patterns.len > 0);\n        std.debug.assert(patterns.len <= 16);\n\n        var filters: [16][]const u8 = undefined;\n        var filter_bufs: [16][256]u8 = undefined;\n        var filter_lens: [16]u8 = undefined;\n\n        for (patterns, 0..) |p, i| {\n            try validateKeyPattern(p);\n            const f = std.fmt.bufPrint(\n                &filter_bufs[i],\n                \"$KV.{s}.{s}\",\n                .{ self.bucket(), p },\n            ) catch return errors.Error.SubjectTooLong;\n            filter_lens[i] = @intCast(f.len);\n            filters[i] = filter_bufs[i][0..f.len];\n        }\n\n        const dp = watchDeliverPolicy(opts);\n        const start = opts.resume_from_revision;\n\n        const seq = ephemeral_counter.fetchAdd(\n            1,\n            .monotonic,\n        );\n        const name = std.fmt.bufPrint(\n            &self._eph_name_buf,\n            \"kv{d}x{d}\",\n            .{ seq, @intFromPtr(self) % 99999 },\n        ) catch unreachable;\n        self._eph_name_len = @intCast(name.len);\n\n        const fs = filters[0..patterns.len];\n        var resp = try self.js.createConsumer(\n            self.streamName(),\n            .{\n                .name = name,\n                .ack_policy = .none,\n                .deliver_policy = dp,\n                .opt_start_seq = start,\n                .filter_subjects = fs,\n                .mem_storage = true,\n                .inactive_threshold = 60_000_000_000,\n                .headers_only = if (opts.meta_only)\n                    true\n                else\n                    null,\n            },\n        );\n        resp.deinit();\n\n        var pull = PullSubscription{\n            .js = self.js,\n            .stream = self.streamName(),\n        };\n        try pull.setConsumer(name);\n\n        return KvWatcher{\n            .kv = self,\n            .pull = pull,\n            .opts = opts,\n        };\n    }\n\n    // -- Key listing (streaming) --\n\n    /// Streaming key lister. More memory-efficient\n    /// than keys() for large buckets.\n    pub const KeyLister = struct {\n        kv: *KeyValue,\n        pull: PullSubscription,\n        done: bool = false,\n        current_msg: ?JsMsg = null,\n\n        fn clearCurrent(self: *KeyLister) void {\n            if (self.current_msg) |*msg| {\n                msg.deinit();\n                self.current_msg = null;\n            }\n        }\n\n        /// Returns the next key, or null when done.\n        /// Caller does NOT own the returned slice --\n        /// it points into the message buffer and is\n        /// valid until the next call to next().\n        pub fn next(\n            self: *KeyLister,\n        ) !?[]const u8 {\n            if (self.done) return null;\n            self.clearCurrent();\n            while (true) {\n                var msg = (try self.pull.next(3000)) orelse {\n                    self.done = true;\n                    return null;\n                };\n\n                const subj = msg.subject();\n                const plen: usize = 4 +\n                    @as(usize, self.kv.bucket_len) + 1;\n                if (subj.len <= plen) {\n                    msg.deinit();\n                    continue;\n                }\n\n                if (msg.headers()) |h| {\n                    if (KeyValue.isDeleteOp(h)) {\n                        msg.deinit();\n                        continue;\n                    }\n                }\n\n                self.current_msg = msg;\n                const stored = self.current_msg.?;\n                return stored.subject()[plen..];\n            }\n        }\n\n        /// Cleans up the lister and its consumer.\n        pub fn deinit(self: *KeyLister) void {\n            self.clearCurrent();\n            self.kv.deleteEphemeralPull(&self.pull);\n        }\n    };\n\n    /// Returns a streaming key lister. Caller must\n    /// call deinit() when done.\n    pub fn listKeys(self: *KeyValue) !KeyLister {\n        std.debug.assert(self.bucket_len > 0);\n\n        var subj_buf: [256]u8 = undefined;\n        const filter = std.fmt.bufPrint(\n            &subj_buf,\n            \"$KV.{s}.>\",\n            .{self.bucket()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        const pull = try self.createEphemeralPull(\n            filter,\n            .last_per_subject,\n            null,\n        );\n\n        return KeyLister{ .kv = self, .pull = pull };\n    }\n\n    fn watchDeliverPolicy(\n        opts: types.WatchOpts,\n    ) types.DeliverPolicy {\n        if (opts.resume_from_revision != null)\n            return .by_start_sequence;\n        if (opts.updates_only)\n            return .new;\n        if (opts.include_history)\n            return .all;\n        return .last_per_subject;\n    }\n\n    // -- Purge Deletes --\n\n    /// Options for purging delete markers.\n    pub const PurgeDeletesOpts = struct {\n        /// Only purge markers older than this (ns).\n        /// Default 0 = purge all markers.\n        older_than_ns: i64 = 0,\n    };\n\n    /// Removes all delete/purge markers from the\n    /// bucket. Optionally filters by marker age.\n    pub fn purgeDeletes(\n        self: *KeyValue,\n        opts: PurgeDeletesOpts,\n    ) !u64 {\n        std.debug.assert(self.bucket_len > 0);\n        const cutoff_ns: i64 = if (opts.older_than_ns > 0) blk: {\n            const now = std.Io.Clock.real.now(self.js.client.io);\n            const now_ns: i64 = @intCast(now.nanoseconds);\n            break :blk if (now_ns > opts.older_than_ns)\n                now_ns - opts.older_than_ns\n            else\n                0;\n        } else 0;\n        var subj_buf: [256]u8 = undefined;\n        const filter = std.fmt.bufPrint(\n            &subj_buf,\n            \"$KV.{s}.>\",\n            .{self.bucket()},\n        ) catch return errors.Error.SubjectTooLong;\n\n        var pull = try self.createEphemeralPull(\n            filter,\n            .last_per_subject,\n            null,\n        );\n        defer self.deleteEphemeralPull(&pull);\n\n        var purged: u64 = 0;\n        while (true) {\n            var msg = (try pull.next(3000)) orelse break;\n            defer msg.deinit();\n\n            if (msg.headers()) |h| {\n                if (isDeleteOp(h)) {\n                    if (opts.older_than_ns > 0) {\n                        const md = msg.metadata() orelse\n                            continue;\n                        if (md.timestamp <= 0 or\n                            md.timestamp > cutoff_ns)\n                            continue;\n                    }\n                    const subj = msg.subject();\n                    var pr = try self.js.purgeStreamSubject(\n                        self.streamName(),\n                        subj,\n                    );\n                    pr.deinit();\n                    purged += 1;\n                }\n            }\n        }\n        return purged;\n    }\n\n    /// Creates an ephemeral consumer with the given\n    /// deliver policy and returns a PullSubscription.\n    fn createEphemeralPull(\n        self: *KeyValue,\n        filter: []const u8,\n        deliver_policy: types.DeliverPolicy,\n        opt_start_seq: ?u64,\n    ) !PullSubscription {\n        std.debug.assert(filter.len > 0);\n        // Generate unique name into stable storage\n        const seq = ephemeral_counter.fetchAdd(\n            1,\n            .monotonic,\n        );\n        const name = std.fmt.bufPrint(\n            &self._eph_name_buf,\n            \"kv{d}x{d}\",\n            .{ seq, @intFromPtr(self) % 99999 },\n        ) catch unreachable;\n        self._eph_name_len = @intCast(name.len);\n\n        var resp = try self.js.createConsumer(\n            self.streamName(),\n            .{\n                .name = name,\n                .ack_policy = .none,\n                .deliver_policy = deliver_policy,\n                .opt_start_seq = opt_start_seq,\n                .filter_subject = filter,\n                .mem_storage = true,\n                .inactive_threshold = 60_000_000_000,\n            },\n        );\n        resp.deinit();\n\n        var pull = PullSubscription{\n            .js = self.js,\n            .stream = self.streamName(),\n        };\n        try pull.setConsumer(name);\n        return pull;\n    }\n\n    fn ephName(self: *const KeyValue) []const u8 {\n        std.debug.assert(self._eph_name_len > 0);\n        return self._eph_name_buf[0..self._eph_name_len];\n    }\n\n    fn deleteEphemeralPull(\n        self: *KeyValue,\n        pull: *PullSubscription,\n    ) void {\n        var resp = self.js.deleteConsumer(\n            self.streamName(),\n            pull.consumerName(),\n        ) catch return;\n        resp.deinit();\n    }\n\n    // -- Status --\n\n    /// Returns bucket status information.\n    pub fn status(\n        self: *KeyValue,\n    ) !types.Response(types.StreamInfo) {\n        return self.js.streamInfo(self.streamName());\n    }\n\n    // -- Helpers --\n\n    fn isDeleteOp(raw_headers: []const u8) bool {\n        const op = parseKvOp(raw_headers);\n        return op == .delete or op == .purge;\n    }\n\n    /// Matches exact KV-Operation header values to\n    /// avoid substring false positives.\n    fn parseKvOp(raw_headers: []const u8) types.KeyValueOp {\n        if (std.mem.indexOf(\n            u8,\n            raw_headers,\n            \"KV-Operation: PURGE\\r\\n\",\n        ) != null) return .purge;\n        if (std.mem.indexOf(\n            u8,\n            raw_headers,\n            \"KV-Operation: DEL\\r\\n\",\n        ) != null) return .delete;\n        return .put;\n    }\n\n    fn decodeBase64(\n        encoded: []const u8,\n        buf: []u8,\n    ) ?[]const u8 {\n        if (encoded.len == 0) return null;\n        const decoder = std.base64.standard.Decoder;\n        const len = decoder.calcSizeForSlice(\n            encoded,\n        ) catch return null;\n        if (len > buf.len) return null;\n        decoder.decode(\n            buf[0..len],\n            encoded,\n        ) catch return null;\n        return buf[0..len];\n    }\n};\n\n/// Watcher for real-time KV updates using an ephemeral\n/// consumer with last_per_subject. Call `next()` to\n/// receive entries.\npub const KvWatcher = struct {\n    kv: *KeyValue,\n    pull: PullSubscription,\n    opts: types.WatchOpts = .{},\n    initial_done: bool = false,\n\n    /// Returns the next entry update. Returns null\n    /// when no more updates within timeout. First\n    /// null after creation means all existing keys\n    /// have been delivered.\n    pub fn next(\n        self: *KvWatcher,\n        timeout_ms: u32,\n    ) !?types.KeyValueEntry {\n        std.debug.assert(timeout_ms > 0);\n        while (true) {\n            var msg = (try self.pull.next(\n                timeout_ms,\n            )) orelse {\n                if (!self.initial_done) {\n                    self.initial_done = true;\n                }\n                return null;\n            };\n            defer msg.deinit();\n\n            var op: types.KeyValueOp = .put;\n            if (msg.headers()) |h| {\n                op = KeyValue.parseKvOp(h);\n            }\n\n            // Skip deletes if configured\n            if (self.opts.ignore_deletes and\n                (op == .delete or op == .purge))\n                continue;\n\n            const subj = msg.subject();\n            const plen: usize = 4 +\n                @as(usize, self.kv.bucket_len) + 1;\n            const key = if (subj.len > plen)\n                subj[plen..]\n            else\n                \"\";\n            const allocator = self.kv.js.allocator;\n            const owned_key = try allocator.dupe(u8, key);\n            errdefer allocator.free(owned_key);\n\n            const md = msg.metadata();\n            const seq = if (md) |m|\n                m.stream_seq\n            else\n                0;\n\n            // meta_only: skip value extraction\n            if (self.opts.meta_only) {\n                return types.KeyValueEntry{\n                    .bucket = self.kv.bucket(),\n                    .key = owned_key,\n                    .value = \"\",\n                    .revision = seq,\n                    .operation = op,\n                    .key_allocator = allocator,\n                };\n            }\n\n            const data = msg.data();\n            var val: []const u8 = \"\";\n            var val_alloc: ?Allocator = null;\n            if (data.len > 0 and op == .put) {\n                val = try allocator.dupe(u8, data);\n                val_alloc = allocator;\n            }\n\n            return types.KeyValueEntry{\n                .bucket = self.kv.bucket(),\n                .key = owned_key,\n                .value = val,\n                .revision = seq,\n                .operation = op,\n                .key_allocator = allocator,\n                .value_allocator = val_alloc,\n            };\n        }\n    }\n\n    /// Cleans up the watcher and its consumer.\n    pub fn deinit(self: *KvWatcher) void {\n        self.kv.deleteEphemeralPull(&self.pull);\n    }\n};\n\ntest \"KV keys reject spaces and DEL\" {\n    var kv = KeyValue{ .js = undefined };\n    @memcpy(kv.bucket_buf[0..1], \"B\");\n    kv.bucket_len = 1;\n\n    var buf: [256]u8 = undefined;\n    try std.testing.expectError(\n        errors.Error.InvalidKey,\n        kv.kvSubject(\"bad key\", &buf),\n    );\n    try std.testing.expectError(\n        errors.Error.InvalidKey,\n        kv.kvSubject(\"bad\\x7fkey\", &buf),\n    );\n}\n"
  },
  {
    "path": "src/jetstream/message.zig",
    "content": "//! JetStream message wrapper with acknowledgment protocol.\n//!\n//! Wraps a core NATS Message and adds JetStream ack/nak/wpi/term\n//! methods that publish to the message's reply_to subject.\n\nconst std = @import(\"std\");\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\n\n/// JetStream message wrapper with ack protocol support.\n///\n/// Ownership model (mirrors Client.Message.owned):\n/// - Pull/fetch path: `owned = true`. The caller receives a\n///   JsMsg by value and MUST call `deinit()` when finished to\n///   free the underlying backing buffer.\n/// - Push callback path: `owned = false`. The subscription\n///   passes a stack-local JsMsg to the handler; `deinit()`\n///   is a no-op. Slice fields (subject, data, headers,\n///   reply_to via the inner Client.Message) are valid ONLY\n///   during the callback invocation. Do NOT copy the struct\n///   out of the callback scope or save pointers past return\n///   -- the backing buffer is reclaimed by the subscription\n///   right after the handler returns.\n///\n/// This matches the existing contract for `*const\n/// Client.Message` in core NATS callbacks.\npub const JsMsg = struct {\n    msg: Client.Message,\n    client: *Client,\n    acked: bool = false,\n    /// See the type-level doc comment for the lifetime\n    /// contract. Default is `true` (owned) so pull-path\n    /// constructions do not need to specify it.\n    owned: bool = true,\n\n    /// Acknowledges the message (+ACK).\n    pub fn ack(self: *JsMsg) !void {\n        std.debug.assert(!self.acked);\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        try self.client.publish(reply, \"+ACK\");\n        self.acked = true;\n    }\n\n    /// Acknowledges and waits for server confirmation.\n    /// Slower than ack() but guarantees the server\n    /// processed the acknowledgment.\n    pub fn doubleAck(\n        self: *JsMsg,\n        timeout_ms: u32,\n    ) !void {\n        std.debug.assert(!self.acked);\n        std.debug.assert(timeout_ms > 0);\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        const resp = self.client.request(\n            reply,\n            \"+ACK\",\n            timeout_ms,\n        ) catch |err| return err;\n        if (resp) |r| {\n            var m = r;\n            m.deinit();\n        }\n        self.acked = true;\n    }\n\n    /// Negatively acknowledges -- triggers redelivery (-NAK).\n    pub fn nak(self: *JsMsg) !void {\n        std.debug.assert(!self.acked);\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        try self.client.publish(reply, \"-NAK\");\n        self.acked = true;\n    }\n\n    /// Negatively acknowledges with a redelivery delay.\n    pub fn nakWithDelay(\n        self: *JsMsg,\n        delay_ns: i64,\n    ) !void {\n        std.debug.assert(!self.acked);\n        std.debug.assert(delay_ns > 0);\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        var buf: [64]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"-NAK {{\\\"delay\\\":{d}}}\",\n            .{delay_ns},\n        ) catch unreachable;\n        try self.client.publish(reply, payload);\n        self.acked = true;\n    }\n\n    /// Signals work in progress (+WPI). Can be called\n    /// repeatedly to extend the ack deadline.\n    pub fn inProgress(self: *JsMsg) !void {\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        try self.client.publish(reply, \"+WPI\");\n    }\n\n    /// Terminates message processing (+TERM).\n    pub fn term(self: *JsMsg) !void {\n        std.debug.assert(!self.acked);\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        try self.client.publish(reply, \"+TERM\");\n        self.acked = true;\n    }\n\n    /// Terminates with a reason string.\n    pub fn termWithReason(\n        self: *JsMsg,\n        reason: []const u8,\n    ) !void {\n        std.debug.assert(!self.acked);\n        std.debug.assert(reason.len > 0);\n        // \"+TERM \" = 6 overhead, 512 - 6 = 506 max\n        std.debug.assert(reason.len <= 506);\n        const reply = self.msg.reply_to orelse\n            return;\n        std.debug.assert(reply.len > 0);\n        var buf: [512]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"+TERM {s}\",\n            .{reason},\n        ) catch unreachable;\n        try self.client.publish(reply, payload);\n        self.acked = true;\n    }\n\n    /// Returns the message data payload.\n    pub fn data(self: *const JsMsg) []const u8 {\n        return self.msg.data;\n    }\n\n    /// Returns the message subject.\n    pub fn subject(self: *const JsMsg) []const u8 {\n        return self.msg.subject;\n    }\n\n    /// Returns raw headers if present.\n    pub fn headers(self: *const JsMsg) ?[]const u8 {\n        return self.msg.headers;\n    }\n\n    /// Returns the reply-to subject (ack subject).\n    pub fn replyTo(self: *const JsMsg) ?[]const u8 {\n        return self.msg.reply_to;\n    }\n\n    /// Parses JetStream metadata from the reply subject.\n    /// Returns null if the reply subject is missing or\n    /// not in the expected `$JS.ACK.*` format.\n    /// Returned slices point into the reply subject\n    /// string (owned by the underlying Message).\n    pub fn metadata(\n        self: *const JsMsg,\n    ) ?MsgMetadata {\n        const reply = self.msg.reply_to orelse\n            return null;\n        std.debug.assert(reply.len > 0);\n        return parseMsgMetadata(reply);\n    }\n\n    /// Frees the underlying message. No-op when `owned` is\n    /// false (push-callback path -- subscription handles it).\n    pub fn deinit(self: *JsMsg) void {\n        if (!self.owned) return;\n        self.msg.deinit();\n    }\n};\n\n/// Metadata parsed from a JetStream message reply subject.\n/// Format: `$JS.ACK.<stream>.<consumer>.<nDel>.<sSeq>\n///          .<cSeq>.<timestamp>.<nPending>`\n/// With domain: `$JS.<domain>.ACK.<stream>.<consumer>\n///              .<nDel>.<sSeq>.<cSeq>.<timestamp>.<nPending>`\npub const MsgMetadata = struct {\n    stream: []const u8,\n    consumer: []const u8,\n    num_delivered: u64,\n    stream_seq: u64,\n    consumer_seq: u64,\n    timestamp: i64,\n    num_pending: u64,\n    domain: ?[]const u8,\n};\n\n/// Parses JetStream metadata from a reply subject string.\n/// Returns null if the format is invalid.\nfn parseMsgMetadata(\n    reply: []const u8,\n) ?MsgMetadata {\n    std.debug.assert(reply.len > 0);\n    var it = std.mem.splitScalar(u8, reply, '.');\n\n    // Token 0: must be \"$JS\"\n    const t0 = it.next() orelse return null;\n    if (!std.mem.eql(u8, t0, \"$JS\")) return null;\n\n    // Token 1: \"ACK\" or domain name\n    const t1 = it.next() orelse return null;\n\n    var domain: ?[]const u8 = null;\n    if (!std.mem.eql(u8, t1, \"ACK\")) {\n        domain = t1;\n        const ack_tok = it.next() orelse return null;\n        if (!std.mem.eql(u8, ack_tok, \"ACK\"))\n            return null;\n    }\n\n    const stream = it.next() orelse return null;\n    const consumer = it.next() orelse return null;\n    const n_del = it.next() orelse return null;\n    const s_seq = it.next() orelse return null;\n    const c_seq = it.next() orelse return null;\n    const ts = it.next() orelse return null;\n    const n_pend = it.next() orelse return null;\n\n    return MsgMetadata{\n        .stream = stream,\n        .consumer = consumer,\n        .num_delivered = parseU64(n_del) orelse\n            return null,\n        .stream_seq = parseU64(s_seq) orelse\n            return null,\n        .consumer_seq = parseU64(c_seq) orelse\n            return null,\n        .timestamp = parseI64(ts) orelse return null,\n        .num_pending = parseU64(n_pend) orelse\n            return null,\n        .domain = domain,\n    };\n}\n\nfn parseU64(s: []const u8) ?u64 {\n    return std.fmt.parseInt(u64, s, 10) catch null;\n}\n\nfn parseI64(s: []const u8) ?i64 {\n    return std.fmt.parseInt(i64, s, 10) catch null;\n}\n\n// -- Tests --\n\ntest \"parse standard reply subject\" {\n    const reply =\n        \"$JS.ACK.ORDERS.worker.1.42.42.1710000000.5\";\n    const md = parseMsgMetadata(reply).?;\n    try std.testing.expectEqualStrings(\n        \"ORDERS\",\n        md.stream,\n    );\n    try std.testing.expectEqualStrings(\n        \"worker\",\n        md.consumer,\n    );\n    try std.testing.expectEqual(@as(u64, 1), md.num_delivered);\n    try std.testing.expectEqual(@as(u64, 42), md.stream_seq);\n    try std.testing.expectEqual(@as(u64, 42), md.consumer_seq);\n    try std.testing.expectEqual(\n        @as(i64, 1710000000),\n        md.timestamp,\n    );\n    try std.testing.expectEqual(@as(u64, 5), md.num_pending);\n    try std.testing.expect(md.domain == null);\n}\n\ntest \"parse reply with domain\" {\n    const reply =\n        \"$JS.hub.ACK.ORDERS.worker.1.42.42.1710000000.5\";\n    const md = parseMsgMetadata(reply).?;\n    try std.testing.expectEqualStrings(\"hub\", md.domain.?);\n    try std.testing.expectEqualStrings(\n        \"ORDERS\",\n        md.stream,\n    );\n    try std.testing.expectEqualStrings(\n        \"worker\",\n        md.consumer,\n    );\n    try std.testing.expectEqual(@as(u64, 42), md.stream_seq);\n}\n\ntest \"invalid reply returns null\" {\n    try std.testing.expect(\n        parseMsgMetadata(\"_INBOX.abc.def\") == null,\n    );\n    try std.testing.expect(\n        parseMsgMetadata(\"$JS.ACK.STREAM\") == null,\n    );\n    try std.testing.expect(\n        parseMsgMetadata(\"$JS.ACK\") == null,\n    );\n    try std.testing.expect(\n        parseMsgMetadata(\"NATS.something\") == null,\n    );\n}\n\ntest \"edge cases: zero values\" {\n    const reply =\n        \"$JS.ACK.S.C.0.0.0.0.0\";\n    const md = parseMsgMetadata(reply).?;\n    try std.testing.expectEqual(@as(u64, 0), md.stream_seq);\n    try std.testing.expectEqual(@as(u64, 0), md.consumer_seq);\n    try std.testing.expectEqual(@as(u64, 0), md.num_pending);\n    try std.testing.expectEqual(@as(u64, 0), md.num_delivered);\n    try std.testing.expectEqual(@as(i64, 0), md.timestamp);\n}\n"
  },
  {
    "path": "src/jetstream/ordered.zig",
    "content": "//! Ordered consumer for gap-free, in-order delivery.\n//!\n//! Internal type used by KV Watch. Automatically recreates\n//! the ephemeral consumer on sequence gaps or heartbeat\n//! failures, resuming from the last known stream position.\n\nconst std = @import(\"std\");\n\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\n\nconst types = @import(\"types.zig\");\nconst errors = @import(\"errors.zig\");\nconst consumer_mod = @import(\"consumer.zig\");\nconst msg_mod = @import(\"message.zig\");\nconst JsMsg = msg_mod.JsMsg;\nconst MsgMetadata = msg_mod.MsgMetadata;\nconst JetStream = @import(\"JetStream.zig\");\nconst PullSubscription = @import(\"pull.zig\").PullSubscription;\nconst HeartbeatMonitor = consumer_mod.HeartbeatMonitor;\n\n/// Auto-recreating ephemeral pull consumer ensuring\n/// gap-free, in-order delivery. Not public API --\n/// used internally by KV Watch.\npub const OrderedConsumer = struct {\n    js: *JetStream,\n    stream: []const u8,\n    config: OrderedConfig,\n    consumer_name_buf: [64]u8 = undefined,\n    consumer_name_len: u8 = 0,\n    stream_seq: u64 = 0,\n    consumer_seq: u64 = 0,\n    serial: u32 = 0,\n    pull: ?PullSubscription = null,\n    hb: ?HeartbeatMonitor = null,\n    reset_count: u32 = 0,\n\n    /// Configuration for ordered consumers.\n    /// Restricted subset of full ConsumerConfig.\n    pub const OrderedConfig = struct {\n        filter_subject: ?[]const u8 = null,\n        deliver_policy: ?types.DeliverPolicy = null,\n        opt_start_seq: ?u64 = null,\n        headers_only: ?bool = null,\n        heartbeat_ms: u32 = 0,\n    };\n\n    /// Creates an ordered consumer. Does NOT create\n    /// the server-side consumer yet (lazy on first\n    /// next() call).\n    pub fn init(\n        js: *JetStream,\n        stream: []const u8,\n        config: OrderedConfig,\n    ) OrderedConsumer {\n        std.debug.assert(stream.len > 0);\n        std.debug.assert(js.timeout_ms > 0);\n        return OrderedConsumer{\n            .js = js,\n            .stream = stream,\n            .config = config,\n            .hb = if (config.heartbeat_ms > 0)\n                HeartbeatMonitor.init(\n                    config.heartbeat_ms,\n                )\n            else\n                null,\n        };\n    }\n\n    /// Fetches the next message in order. Creates or\n    /// recreates the consumer as needed. Returns null\n    /// when no messages are available within timeout.\n    pub fn next(\n        self: *OrderedConsumer,\n        timeout_ms: u32,\n    ) !?JsMsg {\n        std.debug.assert(timeout_ms > 0);\n\n        while (true) {\n            // Ensure consumer exists\n            if (self.pull == null) {\n                try self.createOrReset();\n            }\n\n            const recv_ms = if (self.hb) |hb|\n                hb.timeoutMs()\n            else\n                timeout_ms;\n\n            var pull = &self.pull.?;\n            const maybe = pull.next(\n                recv_ms,\n            ) catch |err| {\n                if (err == error.Timeout or\n                    err == error.NoResponders)\n                {\n                    self.deleteConsumer();\n                    self.pull = null;\n                    return null;\n                }\n                return err;\n            };\n\n            const msg = maybe orelse {\n                if (self.hb) |*hb| {\n                    if (hb.recordTimeout()) {\n                        try self.createOrReset();\n                    }\n                }\n                return null;\n            };\n\n            if (self.hb) |*hb| hb.recordActivity();\n\n            // Check for sequence gap\n            const md = msg.metadata();\n            if (md) |m| {\n                const expected = self.consumer_seq + 1;\n                if (expected > 1 and\n                    m.consumer_seq != expected)\n                {\n                    // REVIEWED(2025-03): Setting stream_seq to\n                    // gap message's seq is correct per NATS\n                    // ordered consumer protocol. Gaps mean\n                    // messages were lost; restart from gap\n                    // point is the documented recovery.\n                    var m2 = msg;\n                    m2.deinit();\n                    self.stream_seq = m.stream_seq;\n                    try self.createOrReset();\n                    continue;\n                }\n                self.stream_seq = m.stream_seq;\n                self.consumer_seq = m.consumer_seq;\n            }\n\n            return msg;\n        }\n    }\n\n    /// Creates or recreates the server-side consumer,\n    /// resuming from last known stream position.\n    fn createOrReset(self: *OrderedConsumer) !void {\n        // Delete old consumer (ignore errors)\n        if (self.consumer_name_len > 0) {\n            self.deleteConsumer();\n            self.backoffSleep();\n        }\n\n        self.serial += 1;\n        self.consumer_seq = 0;\n\n        // Generate unique name (avoid _ prefix which\n        // NATS reserves for internal use)\n        const name = std.fmt.bufPrint(\n            &self.consumer_name_buf,\n            \"oc{d}x{d}\",\n            .{ self.serial, @intFromPtr(self) % 99999 },\n        ) catch unreachable;\n        self.consumer_name_len = @intCast(name.len);\n\n        // Go's ordered consumer ALWAYS uses\n        // DeliverByStartSequencePolicy (ordered.go:626)\n        var next_seq: u64 = 1;\n        if (self.stream_seq > 0) {\n            next_seq = self.stream_seq + 1;\n        } else if (self.config.opt_start_seq) |s| {\n            next_seq = s;\n        }\n\n        const cfg = types.ConsumerConfig{\n            .name = name,\n            .deliver_policy = .by_start_sequence,\n            .opt_start_seq = next_seq,\n            .ack_policy = .none,\n            .max_deliver = 1,\n            .mem_storage = true,\n            .inactive_threshold = 300_000_000_000,\n            .num_replicas = 1,\n            .headers_only = self.config.headers_only,\n            .filter_subject = self.config.filter_subject,\n        };\n\n        var resp = try self.js.createConsumer(\n            self.stream,\n            cfg,\n        );\n        resp.deinit();\n\n        // Ensure server has processed the consumer\n        self.js.client.flush(5_000_000_000) catch {};\n\n        var p = PullSubscription{\n            .js = self.js,\n            .stream = self.stream,\n        };\n        try p.setConsumer(self.consumerName());\n        self.pull = p;\n\n        self.reset_count += 1;\n    }\n\n    /// Backoff sleep between resets.\n    fn backoffSleep(self: *const OrderedConsumer) void {\n        const delays = [_]u64{\n            250_000_000,\n            500_000_000,\n            1_000_000_000,\n            2_000_000_000,\n            5_000_000_000,\n            10_000_000_000,\n        };\n        const idx = @min(\n            self.reset_count,\n            delays.len - 1,\n        );\n        var ts: std.posix.timespec = .{\n            .sec = @intCast(delays[idx] / 1_000_000_000),\n            .nsec = @intCast(\n                delays[idx] % 1_000_000_000,\n            ),\n        };\n        _ = std.posix.system.nanosleep(&ts, &ts);\n    }\n\n    /// Deletes the current server-side consumer.\n    fn deleteConsumer(self: *OrderedConsumer) void {\n        if (self.consumer_name_len == 0) return;\n        var resp = self.js.deleteConsumer(\n            self.stream,\n            self.consumerName(),\n        ) catch return;\n        resp.deinit();\n    }\n\n    /// Returns the current consumer name slice.\n    fn consumerName(self: *const OrderedConsumer) []const u8 {\n        std.debug.assert(self.consumer_name_len > 0);\n        return self.consumer_name_buf[0..self.consumer_name_len];\n    }\n\n    /// Cleans up the server-side consumer.\n    pub fn deinit(self: *OrderedConsumer) void {\n        self.deleteConsumer();\n        self.pull = null;\n        self.consumer_name_len = 0;\n    }\n};\n\n// -- Tests --\n\ntest \"OrderedConsumer config restrictions\" {\n    // Verify the config we build matches restrictions\n    const cfg = types.ConsumerConfig{\n        .name = \"_oc_test\",\n        .ack_policy = .none,\n        .max_deliver = 1,\n        .inactive_threshold = 300_000_000_000,\n        .num_replicas = 1,\n        .filter_subject = \"$KV.mybucket.>\",\n    };\n    try std.testing.expectEqual(\n        types.AckPolicy.none,\n        cfg.ack_policy.?,\n    );\n    try std.testing.expectEqual(\n        @as(i64, 1),\n        cfg.max_deliver.?,\n    );\n    try std.testing.expectEqual(\n        @as(i64, 300_000_000_000),\n        cfg.inactive_threshold.?,\n    );\n}\n\ntest \"backoff delays increase\" {\n    const delays = [_]u64{\n        250_000_000,\n        500_000_000,\n        1_000_000_000,\n        2_000_000_000,\n        5_000_000_000,\n        10_000_000_000,\n    };\n    // Verify delays are monotonically increasing\n    var prev: u64 = 0;\n    for (delays) |d| {\n        try std.testing.expect(d > prev);\n        prev = d;\n    }\n    // Verify cap at 10s\n    try std.testing.expectEqual(\n        @as(u64, 10_000_000_000),\n        delays[delays.len - 1],\n    );\n}\n"
  },
  {
    "path": "src/jetstream/publish_headers.zig",
    "content": "const std = @import(\"std\");\n\nconst nats = @import(\"../nats.zig\");\nconst headers = nats.protocol.headers;\nconst types = @import(\"types.zig\");\n\npub const PublishHeaderSet = struct {\n    entries: [6]headers.Entry = undefined,\n    count: usize = 0,\n    expected_last_seq_buf: [20]u8 = undefined,\n    expected_last_subj_seq_buf: [20]u8 = undefined,\n\n    pub fn slice(\n        self: *const PublishHeaderSet,\n    ) []const headers.Entry {\n        return self.entries[0..self.count];\n    }\n\n    pub fn populate(\n        self: *PublishHeaderSet,\n        opts: types.PublishOpts,\n    ) void {\n        self.* = .{};\n\n        if (opts.msg_id) |v| {\n            self.entries[self.count] = .{\n                .key = headers.HeaderName.msg_id,\n                .value = v,\n            };\n            self.count += 1;\n        }\n        if (opts.expected_stream) |v| {\n            self.entries[self.count] = .{\n                .key = headers.HeaderName.expected_stream,\n                .value = v,\n            };\n            self.count += 1;\n        }\n        if (opts.expected_last_msg_id) |v| {\n            self.entries[self.count] = .{\n                .key = headers.HeaderName.expected_last_msg_id,\n                .value = v,\n            };\n            self.count += 1;\n        }\n        if (opts.expected_last_seq) |v| {\n            const s = std.fmt.bufPrint(\n                &self.expected_last_seq_buf,\n                \"{d}\",\n                .{v},\n            ) catch unreachable;\n            self.entries[self.count] = .{\n                .key = headers.HeaderName.expected_last_seq,\n                .value = s,\n            };\n            self.count += 1;\n        }\n        if (opts.expected_last_subj_seq) |v| {\n            const s = std.fmt.bufPrint(\n                &self.expected_last_subj_seq_buf,\n                \"{d}\",\n                .{v},\n            ) catch unreachable;\n            self.entries[self.count] = .{\n                .key = headers.HeaderName.expected_last_subj_seq,\n                .value = s,\n            };\n            self.count += 1;\n        }\n        if (opts.ttl) |v| {\n            self.entries[self.count] = .{\n                .key = headers.HeaderName.msg_ttl,\n                .value = v,\n            };\n            self.count += 1;\n        }\n    }\n};\n"
  },
  {
    "path": "src/jetstream/pull.zig",
    "content": "//! JetStream pull-based subscription.\n//!\n//! Implements fetch-based message consumption: subscribe to a\n//! temporary inbox, publish a pull request, collect messages\n//! until batch complete or timeout/status signals.\n\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\n\nconst types = @import(\"types.zig\");\nconst errors = @import(\"errors.zig\");\nconst consumer_mod = @import(\"consumer.zig\");\nconst JsMsg = @import(\"message.zig\").JsMsg;\nconst JetStream = @import(\"JetStream.zig\");\n\nconst JsMsgHandler = consumer_mod.JsMsgHandler;\nconst ConsumeContext = consumer_mod.ConsumeContext;\nconst ConsumeOpts = consumer_mod.ConsumeOpts;\nconst HeartbeatMonitor = consumer_mod.HeartbeatMonitor;\n\nfn returnsErrorUnion(comptime f: anytype) bool {\n    const ret = @typeInfo(@TypeOf(f)).@\"fn\".return_type orelse return false;\n    return switch (@typeInfo(ret)) {\n        .error_union => true,\n        else => false,\n    };\n}\n\n/// Pull-based consumer subscription.\npub const PullSubscription = struct {\n    js: *JetStream,\n    stream: []const u8,\n    /// Inline consumer name buffer (avoids dangling\n    /// slices from external buffers).\n    consumer_buf: [48]u8 = undefined,\n    consumer_len: u8 = 0,\n\n    /// Returns consumer name as a slice into the\n    /// inline buffer. Safe after move/copy.\n    pub fn consumerName(\n        self: *const PullSubscription,\n    ) []const u8 {\n        std.debug.assert(self.consumer_len > 0);\n        return self.consumer_buf[0..self.consumer_len];\n    }\n\n    /// Sets the consumer name from a source slice.\n    pub fn setConsumer(\n        self: *PullSubscription,\n        name: []const u8,\n    ) errors.Error!void {\n        try JetStream.validateName(name);\n        if (name.len > self.consumer_buf.len) {\n            return errors.Error.NameTooLong;\n        }\n        @memcpy(\n            self.consumer_buf[0..name.len],\n            name,\n        );\n        self.consumer_len = @intCast(name.len);\n    }\n\n    /// Options for pull-based message fetching.\n    pub const FetchOpts = struct {\n        max_messages: u32 = 1,\n        timeout_ms: u32 = 5000,\n    };\n\n    /// Fetches messages from the consumer. Returns a\n    /// FetchResult that owns the messages. Caller must\n    /// call `deinit()` on the result when done.\n    /// Auto-configures 5s heartbeat for requests > 10s\n    /// (matching Go client behavior).\n    pub fn fetch(\n        self: *PullSubscription,\n        opts: FetchOpts,\n    ) !FetchResult {\n        std.debug.assert(opts.max_messages > 0);\n        std.debug.assert(self.stream.len > 0);\n        std.debug.assert(self.consumer_len > 0);\n        // Auto-heartbeat for long requests (Go default)\n        const hb: ?i64 = if (opts.timeout_ms > 10000)\n            5_000_000_000\n        else\n            null;\n        return self.fetchInternal(.{\n            .batch = @intCast(opts.max_messages),\n            .expires = msToNs(opts.timeout_ms),\n            .idle_heartbeat = hb,\n        }, opts.timeout_ms);\n    }\n\n    /// Fetches with no_wait: returns immediately with\n    /// whatever is available (may be 0 messages).\n    pub fn fetchNoWait(\n        self: *PullSubscription,\n        max_messages: u32,\n    ) !FetchResult {\n        std.debug.assert(max_messages > 0);\n        std.debug.assert(self.stream.len > 0);\n        std.debug.assert(self.consumer_len > 0);\n        return self.fetchInternal(.{\n            .batch = @intCast(max_messages),\n            .no_wait = true,\n        }, 2000);\n    }\n\n    /// Fetches up to max_bytes worth of messages.\n    pub fn fetchBytes(\n        self: *PullSubscription,\n        max_bytes: u32,\n        opts: FetchOpts,\n    ) !FetchResult {\n        std.debug.assert(max_bytes > 0);\n        std.debug.assert(self.stream.len > 0);\n        return self.fetchInternal(.{\n            .batch = @intCast(opts.max_messages),\n            .max_bytes = @intCast(max_bytes),\n            .expires = msToNs(opts.timeout_ms),\n        }, opts.timeout_ms);\n    }\n\n    /// Fetches a single message. Returns null on timeout.\n    pub fn next(\n        self: *PullSubscription,\n        timeout_ms: u32,\n    ) !?JsMsg {\n        std.debug.assert(timeout_ms > 0);\n        std.debug.assert(self.stream.len > 0);\n        var result = try self.fetchInternal(.{\n            .batch = 1,\n            .expires = msToNs(timeout_ms),\n        }, timeout_ms);\n        if (result.messages.len == 0) {\n            result.deinit();\n            return null;\n        }\n        std.debug.assert(result.messages.len == 1);\n        const msg = result.messages[0];\n        result.allocator.free(result.messages);\n        return msg;\n    }\n\n    /// Internal fetch with arbitrary PullRequest params.\n    fn fetchInternal(\n        self: *PullSubscription,\n        pull_req: types.PullRequest,\n        timeout_ms: u32,\n    ) !FetchResult {\n        const client = self.js.client;\n        const allocator = self.js.allocator;\n\n        const inbox = try client.newInbox();\n        defer allocator.free(inbox);\n\n        var sub = try client.subscribeSync(inbox);\n        defer sub.deinit();\n\n        var subj_buf: [512]u8 = undefined;\n        const prefix = self.js.apiPrefix();\n        const pull_subj = std.fmt.bufPrint(\n            &subj_buf,\n            \"{s}CONSUMER.MSG.NEXT.{s}.{s}\",\n            .{ prefix, self.stream, self.consumerName() },\n        ) catch return errors.Error.SubjectTooLong;\n\n        const payload = try types.jsonStringify(\n            allocator,\n            pull_req,\n        );\n        defer allocator.free(payload);\n\n        try client.publishRequest(\n            pull_subj,\n            inbox,\n            payload,\n        );\n        try client.flush(5_000_000_000);\n\n        const batch: u32 = if (pull_req.batch) |b|\n            @intCast(b)\n        else\n            1;\n\n        var msgs: std.ArrayList(JsMsg) = .empty;\n        errdefer {\n            for (msgs.items) |*m| m.deinit();\n            msgs.deinit(allocator);\n        }\n\n        var collected: u32 = 0;\n        while (collected < batch) {\n            const maybe_msg = sub.nextMsgTimeout(\n                timeout_ms,\n            ) catch |err| {\n                if (collected > 0) break;\n                return err;\n            };\n            const msg = maybe_msg orelse break;\n\n            if (msg.status()) |code| {\n                msg.deinit();\n                switch (code) {\n                    404, 408, 409 => break,\n                    100 => continue,\n                    else => break,\n                }\n            }\n\n            try msgs.append(allocator, JsMsg{\n                .msg = msg,\n                .client = client,\n            });\n            collected += 1;\n        }\n\n        return FetchResult{\n            .messages = try msgs.toOwnedSlice(\n                allocator,\n            ),\n            .allocator = allocator,\n        };\n    }\n\n    fn msToNs(ms: u32) i64 {\n        return @as(i64, @intCast(ms)) * 1_000_000;\n    }\n\n    /// Creates a message iterator for continuous pull\n    /// consumption. Returns a MessagesContext whose\n    /// `next()` method yields one JsMsg at a time.\n    /// Caller must call `deinit()` when done.\n    pub fn messages(\n        self: *PullSubscription,\n        opts: ConsumeOpts,\n    ) !MessagesContext {\n        std.debug.assert(self.stream.len > 0);\n        std.debug.assert(self.consumer_len > 0);\n        std.debug.assert(opts.max_messages > 0);\n        std.debug.assert(opts.expires_ms > 0);\n        // heartbeat must be less than expires\n        std.debug.assert(\n            opts.heartbeat_ms == 0 or\n                opts.heartbeat_ms < opts.expires_ms,\n        );\n\n        const client = self.js.client;\n        const inbox = try client.newInbox();\n        defer client.allocator.free(inbox);\n\n        const sub = try client.subscribeSync(inbox);\n\n        return MessagesContext{\n            .pull = self,\n            .sub = sub,\n            .opts = opts,\n            .hb = if (opts.heartbeat_ms > 0)\n                HeartbeatMonitor.init(opts.heartbeat_ms)\n            else\n                null,\n        };\n    }\n\n    /// Starts continuous callback-based consumption.\n    /// Messages are dispatched to the handler in a\n    /// background task. Returns a ConsumeContext for\n    /// stop/drain control. Caller must call `deinit()`\n    /// on the returned context when done.\n    pub fn consume(\n        self: *PullSubscription,\n        handler: JsMsgHandler,\n        opts: ConsumeOpts,\n    ) !ConsumeContext {\n        std.debug.assert(self.stream.len > 0);\n        std.debug.assert(self.consumer_len > 0);\n        std.debug.assert(opts.max_messages > 0);\n        std.debug.assert(opts.expires_ms > 0);\n        std.debug.assert(\n            opts.heartbeat_ms == 0 or\n                opts.heartbeat_ms < opts.expires_ms,\n        );\n\n        const client = self.js.client;\n        const inbox = try client.newInbox();\n        defer client.allocator.free(inbox);\n\n        const sub = try client.subscribeSync(inbox);\n        errdefer sub.deinit();\n\n        // Issue initial pull request\n        try issuePull(\n            self.js,\n            client,\n            sub.subject,\n            self.stream,\n            self.consumerName(),\n            opts,\n        );\n\n        const io = client.io;\n        const state = try client.allocator.create(\n            std.atomic.Value(ConsumeContext.State),\n        );\n        errdefer client.allocator.destroy(state);\n        state.* = std.atomic.Value(ConsumeContext.State).init(.running);\n\n        var ctx = ConsumeContext{\n            ._io = io,\n            ._shared_state = state,\n            ._allocator = client.allocator,\n        };\n\n        ctx._task = io.async(\n            consumeDrainTask,\n            .{\n                self.js,\n                client,\n                sub,\n                handler,\n                opts,\n                state,\n                self.stream,\n                self.consumerName(),\n            },\n        );\n\n        return ctx;\n    }\n\n    /// Result of a fetch operation.\n    pub const FetchResult = struct {\n        messages: []JsMsg,\n        allocator: Allocator,\n\n        /// Returns the number of messages fetched.\n        pub fn count(self: *const FetchResult) usize {\n            return self.messages.len;\n        }\n\n        /// Frees all messages and the backing slice.\n        pub fn deinit(self: *FetchResult) void {\n            for (self.messages) |*m| m.deinit();\n            self.allocator.free(self.messages);\n        }\n    };\n};\n\ntest \"PullSubscription setConsumer reports invalid input at runtime\" {\n    try std.testing.expect(returnsErrorUnion(PullSubscription.setConsumer));\n}\n\n/// Iterator for continuous pull-based consumption.\n/// Each call to `next()` returns a single JsMsg.\n/// Automatically issues new pull requests when the\n/// current batch is exhausted. Monitors heartbeats\n/// when configured (heartbeat_ms > 0 in ConsumeOpts).\npub const MessagesContext = struct {\n    pull: *PullSubscription,\n    sub: *Client.Sub,\n    opts: ConsumeOpts,\n    hb: ?HeartbeatMonitor = null,\n    active: bool = true,\n    delivered: u32 = 0,\n    batch_pending: bool = false,\n\n    /// Returns the next message, or null on timeout.\n    /// Issues pull requests automatically. Caller owns\n    /// the returned JsMsg and must call ack + deinit.\n    /// Returns error.NoHeartbeat if heartbeats stop.\n    pub fn next(self: *MessagesContext) !?JsMsg {\n        std.debug.assert(self.active);\n        const client = self.pull.js.client;\n        const recv_ms = if (self.hb) |hb|\n            hb.timeoutMs()\n        else\n            self.opts.expires_ms;\n\n        // Issue pull if needed\n        if (!self.batch_pending) {\n            try issuePull(\n                self.pull.js,\n                client,\n                self.sub.subject,\n                self.pull.stream,\n                self.pull.consumerName(),\n                self.opts,\n            );\n            self.batch_pending = true;\n            self.delivered = 0;\n        }\n\n        while (self.active) {\n            const maybe = self.sub.nextMsgTimeout(\n                recv_ms,\n            ) catch |err| {\n                self.batch_pending = false;\n                return err;\n            };\n            const msg = maybe orelse {\n                // Receive timed out -- check heartbeat\n                if (self.hb) |*hb| {\n                    if (hb.recordTimeout())\n                        return errors.Error.NoHeartbeat;\n                }\n                self.batch_pending = false;\n                return null;\n            };\n\n            // Any message resets heartbeat monitor\n            if (self.hb) |*hb| hb.recordActivity();\n\n            if (msg.status()) |code| {\n                if (code == 100) {\n                    if (msg.reply_to) |reply| {\n                        client.publish(\n                            reply,\n                            \"\",\n                        ) catch {};\n                    }\n                    msg.deinit();\n                    continue;\n                }\n                msg.deinit();\n                switch (code) {\n                    404, 408 => {\n                        self.batch_pending = false;\n                        return null;\n                    },\n                    409 => {\n                        self.batch_pending = false;\n                        return null;\n                    },\n                    else => {\n                        self.batch_pending = false;\n                        return null;\n                    },\n                }\n            }\n\n            self.delivered += 1;\n\n            const threshold = self.opts.max_messages *\n                self.opts.threshold_pct / 100;\n            if (self.delivered >= threshold) {\n                issuePull(\n                    self.pull.js,\n                    client,\n                    self.sub.subject,\n                    self.pull.stream,\n                    self.pull.consumerName(),\n                    self.opts,\n                ) catch {};\n                self.delivered = 0;\n            }\n\n            return JsMsg{\n                .msg = msg,\n                .client = client,\n            };\n        }\n\n        return null;\n    }\n\n    /// Stops the iterator. No more messages after this.\n    pub fn stop(self: *MessagesContext) void {\n        std.debug.assert(self.active);\n        self.active = false;\n    }\n\n    /// Frees the underlying subscription.\n    pub fn deinit(self: *MessagesContext) void {\n        self.active = false;\n        self.sub.deinit();\n    }\n};\n\n/// Issues a pull request to the server.\nfn issuePull(\n    js: *JetStream,\n    client: *Client,\n    inbox: []const u8,\n    stream: []const u8,\n    consumer_name: []const u8,\n    opts: ConsumeOpts,\n) !void {\n    std.debug.assert(inbox.len > 0);\n    std.debug.assert(stream.len > 0);\n    std.debug.assert(\n        opts.heartbeat_ms == 0 or\n            opts.heartbeat_ms < opts.expires_ms,\n    );\n\n    var subj_buf: [512]u8 = undefined;\n    const prefix = js.apiPrefix();\n    const pull_subj = std.fmt.bufPrint(\n        &subj_buf,\n        \"{s}CONSUMER.MSG.NEXT.{s}.{s}\",\n        .{ prefix, stream, consumer_name },\n    ) catch return errors.Error.SubjectTooLong;\n\n    const hb_ns: ?i64 = if (opts.heartbeat_ms > 0)\n        PullSubscription.msToNs(opts.heartbeat_ms)\n    else\n        null;\n    const pull_req = types.PullRequest{\n        .batch = @intCast(opts.max_messages),\n        .expires = PullSubscription.msToNs(\n            opts.expires_ms,\n        ),\n        .idle_heartbeat = hb_ns,\n    };\n    const payload = try types.jsonStringify(\n        js.allocator,\n        pull_req,\n    );\n    defer js.allocator.free(payload);\n\n    try client.publishRequest(\n        pull_subj,\n        inbox,\n        payload,\n    );\n    try client.flush(5_000_000_000);\n}\n\n/// Background task for callback-based consume().\nfn consumeDrainTask(\n    js: *JetStream,\n    client: *Client,\n    sub: *Client.Sub,\n    handler: JsMsgHandler,\n    opts: ConsumeOpts,\n    state: *std.atomic.Value(ConsumeContext.State),\n    stream: []const u8,\n    consumer_name: []const u8,\n) void {\n    defer {\n        sub.deinit();\n        state.store(.stopped, .release);\n    }\n\n    var hb: ?HeartbeatMonitor = if (opts.heartbeat_ms > 0)\n        HeartbeatMonitor.init(opts.heartbeat_ms)\n    else\n        null;\n    const recv_ms = if (hb) |h|\n        h.timeoutMs()\n    else\n        opts.expires_ms;\n\n    var delivered: u32 = 0;\n\n    while (state.load(.acquire) == .running or\n        state.load(.acquire) == .draining)\n    {\n        const maybe = sub.nextMsgTimeout(\n            recv_ms,\n        ) catch |err| {\n            if (opts.err_handler) |eh| eh(err);\n            if (state.load(.acquire) == .draining) break;\n            issuePull(\n                js,\n                client,\n                sub.subject,\n                stream,\n                consumer_name,\n                opts,\n            ) catch break;\n            continue;\n        };\n        const msg = maybe orelse {\n            if (state.load(.acquire) == .draining) break;\n            // Check heartbeat\n            if (hb) |*h| {\n                if (h.recordTimeout()) {\n                    if (opts.err_handler) |eh|\n                        eh(errors.Error.NoHeartbeat);\n                    break;\n                }\n            }\n            issuePull(\n                js,\n                client,\n                sub.subject,\n                stream,\n                consumer_name,\n                opts,\n            ) catch break;\n            delivered = 0;\n            continue;\n        };\n\n        if (hb) |*h| h.recordActivity();\n\n        if (msg.status()) |code| {\n            if (code == 100) {\n                if (msg.reply_to) |reply| {\n                    client.publish(\n                        reply,\n                        \"\",\n                    ) catch {};\n                }\n                msg.deinit();\n                continue;\n            }\n            msg.deinit();\n            switch (code) {\n                404, 408 => {\n                    if (state.load(.acquire) == .draining) break;\n                    // Re-issue pull (batch expired)\n                    issuePull(\n                        js,\n                        client,\n                        sub.subject,\n                        stream,\n                        consumer_name,\n                        opts,\n                    ) catch break;\n                    delivered = 0;\n                    continue;\n                },\n                409 => break,\n                else => continue,\n            }\n        }\n\n        // REVIEWED(2025-03): Stack-local JsMsg is intentional.\n        // handler.dispatch() is synchronous — handler must\n        // process before return. Avoids allocation per msg.\n        var js_msg = JsMsg{\n            .msg = msg,\n            .client = client,\n        };\n        handler.dispatch(&js_msg);\n        delivered += 1;\n\n        const threshold = opts.max_messages *\n            opts.threshold_pct / 100;\n        if (delivered >= threshold) {\n            issuePull(\n                js,\n                client,\n                sub.subject,\n                stream,\n                consumer_name,\n                opts,\n            ) catch {};\n            delivered = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "src/jetstream/push.zig",
    "content": "//! JetStream push-based consumer subscription.\n//!\n//! Push consumers have a deliver_subject configured and the\n//! server pushes messages to that subject. The client\n//! subscribes using a callback and processes messages as\n//! they arrive on the IO thread.\n\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\nconst nats = @import(\"../nats.zig\");\nconst Client = nats.Client;\n\nconst types = @import(\"types.zig\");\nconst errors = @import(\"errors.zig\");\nconst consumer_mod = @import(\"consumer.zig\");\nconst JsMsg = @import(\"message.zig\").JsMsg;\nconst JsMsgHandler = consumer_mod.JsMsgHandler;\nconst JetStream = @import(\"JetStream.zig\");\nconst pubsub = @import(\"../pubsub.zig\");\n\nfn returnsErrorUnion(comptime f: anytype) bool {\n    const ret = @typeInfo(@TypeOf(f)).@\"fn\".return_type orelse return false;\n    return switch (@typeInfo(ret)) {\n        .error_union => true,\n        else => false,\n    };\n}\n\n/// Push-based consumer subscription. Created after a\n/// push consumer exists on the server. Subscribe to\n/// the deliver_subject and process messages via\n/// consume().\npub const PushSubscription = struct {\n    js: *JetStream,\n    stream: []const u8,\n    consumer_buf: [48]u8 = undefined,\n    consumer_len: u8 = 0,\n    deliver_buf: [256]u8 = undefined,\n    deliver_len: u16 = 0,\n    deliver_group_buf: [64]u8 = undefined,\n    deliver_group_len: u8 = 0,\n\n    /// Returns consumer name.\n    pub fn consumerName(\n        self: *const PushSubscription,\n    ) []const u8 {\n        std.debug.assert(self.consumer_len > 0);\n        return self.consumer_buf[0..self.consumer_len];\n    }\n\n    /// Returns the deliver subject.\n    pub fn deliverSubject(\n        self: *const PushSubscription,\n    ) []const u8 {\n        std.debug.assert(self.deliver_len > 0);\n        return self.deliver_buf[0..self.deliver_len];\n    }\n\n    /// Sets consumer name.\n    pub fn setConsumer(\n        self: *PushSubscription,\n        name: []const u8,\n    ) errors.Error!void {\n        try JetStream.validateName(name);\n        if (name.len > self.consumer_buf.len) {\n            return errors.Error.NameTooLong;\n        }\n        @memcpy(\n            self.consumer_buf[0..name.len],\n            name,\n        );\n        self.consumer_len = @intCast(name.len);\n    }\n\n    /// Sets the deliver subject.\n    pub fn setDeliverSubject(\n        self: *PushSubscription,\n        subj: []const u8,\n    ) !void {\n        try pubsub.validatePublish(subj);\n        if (subj.len > self.deliver_buf.len) {\n            return errors.Error.SubjectTooLong;\n        }\n        @memcpy(\n            self.deliver_buf[0..subj.len],\n            subj,\n        );\n        self.deliver_len = @intCast(subj.len);\n    }\n\n    /// Sets the deliver group (queue group).\n    pub fn setDeliverGroup(\n        self: *PushSubscription,\n        group: []const u8,\n    ) !void {\n        try pubsub.validateQueueGroup(group);\n        if (group.len > self.deliver_group_buf.len) {\n            return errors.Error.NameTooLong;\n        }\n        @memcpy(\n            self.deliver_group_buf[0..group.len],\n            group,\n        );\n        self.deliver_group_len = @intCast(group.len);\n    }\n\n    /// Options for push consumption.\n    pub const ConsumeOpts = struct {\n        /// Expected idle-heartbeat interval in ms.\n        /// If no message or heartbeat arrives within\n        /// 2x this interval, err_handler is called\n        /// with error.NoHeartbeat. In normal use this\n        /// should match the consumer's server-side\n        /// idle_heartbeat configuration to avoid false\n        /// positives during idle periods.\n        heartbeat_ms: u32 = 0,\n        err_handler: ?consumer_mod.ErrHandler = null,\n    };\n\n    /// Starts callback-based consumption on the\n    /// deliver subject. Uses the client's native\n    /// callback subscription (runs on IO thread).\n    /// To stop: call the returned context's deinit().\n    ///\n    /// The handler receives `*JsMsg` with `owned = false`.\n    /// Slice fields (data, subject, headers, reply_to)\n    /// are valid ONLY during the callback; do not copy\n    /// the struct out or save pointers past return.\n    pub fn consume(\n        self: *PushSubscription,\n        handler: JsMsgHandler,\n        opts: ConsumeOpts,\n    ) !PushConsumeContext {\n        std.debug.assert(self.deliver_len > 0);\n        std.debug.assert(self.consumer_len > 0);\n\n        const client = self.js.client;\n        const subj = self.deliverSubject();\n\n        // Allocate wrapper on heap so it outlives\n        // this function. Stores the JsMsgHandler\n        // and a pointer back to the client.\n        const wrapper = try client.allocator.create(\n            PushCallbackWrapper,\n        );\n        errdefer client.allocator.destroy(wrapper);\n        wrapper.* = .{\n            .handler = handler,\n            .client = client,\n            .allocator = client.allocator,\n            .err_handler = opts.err_handler,\n            .last_activity_ns = std.atomic.Value(u64).init(\n                getNowNs(client.io),\n            ),\n        };\n\n        const qg: ?[]const u8 =\n            if (self.deliver_group_len > 0)\n                self.deliver_group_buf[0..self.deliver_group_len]\n            else\n                null;\n\n        // Use client.subscribe (callback mode).\n        // This runs callbackDrainFn on the IO thread.\n        // Messages are dispatched via the wrapper.\n        const sub = try client.queueSubscribe(\n            subj,\n            qg,\n            Client.MsgHandler.init(\n                PushCallbackWrapper,\n                wrapper,\n            ),\n        );\n        errdefer sub.deinit();\n\n        // Flush to ensure SUB reaches the server\n        // before the caller creates the push consumer.\n        try client.flush(5_000_000_000);\n\n        var ctx = PushConsumeContext{\n            .sub = sub,\n            .wrapper = wrapper,\n            .io = client.io,\n        };\n        if (opts.heartbeat_ms > 0) {\n            ctx.monitor_future = client.io.concurrent(\n                pushHeartbeatMonitorTask,\n                .{ wrapper, opts.heartbeat_ms },\n            ) catch client.io.async(\n                pushHeartbeatMonitorTask,\n                .{ wrapper, opts.heartbeat_ms },\n            );\n        }\n        return ctx;\n    }\n};\n\ntest \"PushSubscription setters report invalid input at runtime\" {\n    try std.testing.expect(returnsErrorUnion(PushSubscription.setConsumer));\n    try std.testing.expect(returnsErrorUnion(PushSubscription.setDeliverSubject));\n    try std.testing.expect(returnsErrorUnion(PushSubscription.setDeliverGroup));\n}\n\n/// Heap-allocated wrapper that bridges Client.MsgHandler\n/// (receives *const Message) to JsMsgHandler (receives\n/// *JsMsg with owned=false). Lives on the heap because the\n/// callback subscription outlives the consume() call.\nconst PushCallbackWrapper = struct {\n    handler: JsMsgHandler,\n    client: *Client,\n    allocator: Allocator,\n    err_handler: ?consumer_mod.ErrHandler = null,\n    last_activity_ns: std.atomic.Value(u64) =\n        std.atomic.Value(u64).init(0),\n\n    pub fn onMessage(\n        self: *PushCallbackWrapper,\n        msg: *const Client.Message,\n    ) void {\n        self.last_activity_ns.store(\n            getNowNs(self.client.io),\n            .release,\n        );\n\n        if (msg.status()) |code| {\n            if (code == 100) {\n                if (msg.reply_to) |reply| {\n                    self.client.publish(reply, \"\") catch |err| {\n                        if (self.err_handler) |eh| eh(err);\n                    };\n                }\n                return;\n            }\n        }\n\n        // Borrowed message. The underlying Client.Message\n        // backing buffer is reclaimed by callbackDrainFn\n        // after handler.dispatch() returns. owned=false\n        // makes JsMsg.deinit() a no-op.\n        var js_msg = JsMsg{\n            .msg = msg.*,\n            .client = self.client,\n            .owned = false,\n        };\n        self.handler.dispatch(&js_msg);\n    }\n};\n\n/// Context for controlling an active push consume.\n/// Simpler than ConsumeContext -- just wraps the\n/// subscription. Stopping = unsubscribing.\npub const PushConsumeContext = struct {\n    sub: ?*Client.Sub,\n    wrapper: *PushCallbackWrapper,\n    monitor_future: ?std.Io.Future(void) = null,\n    io: std.Io = undefined,\n\n    /// Stops consumption. Safe to call before deinit.\n    pub fn stop(self: *PushConsumeContext) void {\n        if (self.monitor_future) |*future| {\n            _ = future.cancel(self.io);\n            self.monitor_future = null;\n        }\n        if (self.sub) |s| {\n            s.deinit();\n            self.sub = null;\n        }\n    }\n\n    /// Stops (if not already) and frees resources.\n    pub fn deinit(self: *PushConsumeContext) void {\n        self.stop();\n        self.wrapper.allocator.destroy(\n            self.wrapper,\n        );\n    }\n};\n\nfn getNowNs(io: std.Io) u64 {\n    const ts = std.Io.Timestamp.now(io, .awake);\n    return @intCast(ts.nanoseconds);\n}\n\nfn pushHeartbeatMonitorTask(\n    wrapper: *PushCallbackWrapper,\n    heartbeat_ms: u32,\n) void {\n    std.debug.assert(heartbeat_ms > 0);\n    const io = wrapper.client.io;\n    const timeout_ns =\n        @as(u64, heartbeat_ms) * 2 * std.time.ns_per_ms;\n    var notified = false;\n\n    while (true) {\n        io.sleep(\n            .fromMilliseconds(heartbeat_ms),\n            .awake,\n        ) catch |err| {\n            if (err == error.Canceled) return;\n            return;\n        };\n\n        const last =\n            wrapper.last_activity_ns.load(.acquire);\n        const now = getNowNs(io);\n        if (now -| last >= timeout_ns) {\n            if (!notified) {\n                if (wrapper.err_handler) |eh| {\n                    eh(errors.Error.NoHeartbeat);\n                }\n                notified = true;\n            }\n        } else {\n            notified = false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/jetstream/types.zig",
    "content": "//! JetStream type definitions for stream/consumer configuration,\n//! API responses, and request payloads.\n//!\n//! All structs use optional fields with null defaults for\n//! forward-compatible JSON serialization (omit nulls) and\n//! parsing (ignore unknown fields).\n\nconst std = @import(\"std\");\nconst errors = @import(\"errors.zig\");\nconst headers = @import(\"../protocol/headers.zig\");\n\npub const ApiErrorJson = errors.ApiErrorJson;\n\n// -- Enums (lowercase tags match NATS JSON wire format) --\n\npub const RetentionPolicy = enum {\n    limits,\n    interest,\n    workqueue,\n};\n\npub const StorageType = enum { file, memory };\npub const DiscardPolicy = enum { old, new };\npub const StoreCompression = enum { none, s2 };\n\npub const DeliverPolicy = enum {\n    all,\n    last,\n    new,\n    by_start_sequence,\n    by_start_time,\n    last_per_subject,\n};\n\npub const AckPolicy = enum { none, all, explicit };\npub const ReplayPolicy = enum { instant, original };\n\npub const SubjectTransform = struct {\n    src: ?[]const u8 = null,\n    dest: ?[]const u8 = null,\n};\n\n// -- Stream types --\n\npub const StreamConfig = struct {\n    name: []const u8,\n    description: ?[]const u8 = null,\n    subjects: ?[]const []const u8 = null,\n    retention: ?RetentionPolicy = null,\n    max_consumers: ?i64 = null,\n    max_msgs: ?i64 = null,\n    max_bytes: ?i64 = null,\n    max_age: ?i64 = null,\n    max_msgs_per_subject: ?i64 = null,\n    max_msg_size: ?i32 = null,\n    storage: ?StorageType = null,\n    num_replicas: ?i32 = null,\n    no_ack: ?bool = null,\n    duplicate_window: ?i64 = null,\n    discard: ?DiscardPolicy = null,\n    discard_new_per_subject: ?bool = null,\n    sealed: ?bool = null,\n    deny_delete: ?bool = null,\n    deny_purge: ?bool = null,\n    allow_rollup_hdrs: ?bool = null,\n    allow_direct: ?bool = null,\n    mirror_direct: ?bool = null,\n    compression: ?StoreCompression = null,\n    first_seq: ?u64 = null,\n    allow_msg_ttl: ?bool = null,\n    metadata: ?std.json.Value = null,\n    subject_transform: ?SubjectTransform = null,\n};\n\npub const StreamState = struct {\n    messages: u64 = 0,\n    bytes: u64 = 0,\n    first_seq: u64 = 0,\n    last_seq: u64 = 0,\n    consumer_count: i64 = 0,\n    num_deleted: i64 = 0,\n    num_subjects: u64 = 0,\n};\n\npub const StreamInfo = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    config: ?StreamConfig = null,\n    state: ?StreamState = null,\n    created: ?[]const u8 = null,\n    ts: ?[]const u8 = null,\n};\n\n// -- Consumer types --\n\npub const ConsumerConfig = struct {\n    name: ?[]const u8 = null,\n    durable_name: ?[]const u8 = null,\n    description: ?[]const u8 = null,\n    deliver_policy: ?DeliverPolicy = null,\n    opt_start_seq: ?u64 = null,\n    ack_policy: ?AckPolicy = null,\n    ack_wait: ?i64 = null,\n    max_deliver: ?i64 = null,\n    filter_subject: ?[]const u8 = null,\n    filter_subjects: ?[]const []const u8 = null,\n    replay_policy: ?ReplayPolicy = null,\n    max_waiting: ?i64 = null,\n    max_ack_pending: ?i64 = null,\n    inactive_threshold: ?i64 = null,\n    num_replicas: ?i32 = null,\n    headers_only: ?bool = null,\n    mem_storage: ?bool = null,\n    // Push consumer fields (v1.1 ready, harmless as null)\n    deliver_subject: ?[]const u8 = null,\n    deliver_group: ?[]const u8 = null,\n    flow_control: ?bool = null,\n    idle_heartbeat: ?i64 = null,\n};\n\npub const SequenceInfo = struct {\n    consumer_seq: u64 = 0,\n    stream_seq: u64 = 0,\n};\n\npub const ConsumerInfo = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    stream_name: ?[]const u8 = null,\n    name: ?[]const u8 = null,\n    config: ?ConsumerConfig = null,\n    delivered: ?SequenceInfo = null,\n    ack_floor: ?SequenceInfo = null,\n    num_ack_pending: i64 = 0,\n    num_redelivered: i64 = 0,\n    num_waiting: i64 = 0,\n    num_pending: u64 = 0,\n    created: ?[]const u8 = null,\n    ts: ?[]const u8 = null,\n};\n\npub const CreateConsumerRequest = struct {\n    stream_name: []const u8,\n    config: ConsumerConfig,\n    action: ?[]const u8 = null,\n};\n\n// -- Publish types --\n\npub const PubAck = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    stream: ?[]const u8 = null,\n    seq: u64 = 0,\n    duplicate: ?bool = null,\n    domain: ?[]const u8 = null,\n};\n\npub const PublishOpts = struct {\n    msg_id: ?[]const u8 = null,\n    expected_stream: ?[]const u8 = null,\n    expected_last_seq: ?u64 = null,\n    expected_last_msg_id: ?[]const u8 = null,\n    expected_last_subj_seq: ?u64 = null,\n    ttl: ?[]const u8 = null,\n};\n\n/// Pre-built JetStream publish message with user headers.\n///\n/// Passed to `JetStream.publishMsg()` for publishing messages\n/// with arbitrary user-supplied headers alongside JetStream-\n/// specific headers from `opts`. On header-key collision\n/// (case-insensitive per NATS convention), JetStream headers\n/// from `opts` override the user-supplied value -- matching\n/// Go client `PublishMsg` semantics.\npub const JsPublishMsg = struct {\n    subject: []const u8,\n    payload: []const u8,\n    headers: ?[]const headers.Entry = null,\n    opts: PublishOpts = .{},\n};\n\n// -- Pull types --\n\npub const PullRequest = struct {\n    batch: ?i64 = null,\n    expires: ?i64 = null,\n    no_wait: ?bool = null,\n    max_bytes: ?i64 = null,\n    idle_heartbeat: ?i64 = null,\n};\n\n// -- Delete response --\n\npub const DeleteResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    success: bool = false,\n};\n\n// -- Purge response --\n\npub const PurgeRequest = struct {\n    filter: ?[]const u8 = null,\n    seq: ?u64 = null,\n    keep: ?u64 = null,\n};\n\npub const PurgeResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    success: bool = false,\n    purged: u64 = 0,\n};\n\n// -- Listing responses (paginated) --\n\npub const StreamNamesResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    total: u64 = 0,\n    offset: u64 = 0,\n    limit: u64 = 0,\n    streams: ?[]const []const u8 = null,\n};\n\npub const StreamListResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    total: u64 = 0,\n    offset: u64 = 0,\n    limit: u64 = 0,\n    streams: ?[]const StreamInfo = null,\n};\n\npub const ConsumerNamesResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    total: u64 = 0,\n    offset: u64 = 0,\n    limit: u64 = 0,\n    consumers: ?[]const []const u8 = null,\n};\n\npub const ConsumerListResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    total: u64 = 0,\n    offset: u64 = 0,\n    limit: u64 = 0,\n    consumers: ?[]const ConsumerInfo = null,\n};\n\n/// Request body for paginated listing APIs.\npub const ListRequest = struct {\n    offset: u64 = 0,\n    subject: ?[]const u8 = null,\n};\n\n// -- Key-Value types --\n\npub const KeyValueConfig = struct {\n    bucket: []const u8,\n    description: ?[]const u8 = null,\n    max_value_size: ?i32 = null,\n    history: ?u8 = null,\n    ttl: ?i64 = null,\n    max_bytes: ?i64 = null,\n    storage: ?StorageType = null,\n    replicas: ?i32 = null,\n};\n\npub const KeyValueOp = enum { put, delete, purge };\n\n/// Options for KV watch operations.\npub const WatchOpts = struct {\n    /// Deliver all historical values, not just latest.\n    include_history: bool = false,\n    /// Skip entries with delete/purge markers.\n    ignore_deletes: bool = false,\n    /// Only deliver new updates, skip initial values.\n    updates_only: bool = false,\n    /// Only deliver metadata, not values.\n    meta_only: bool = false,\n    /// Resume watching from a specific revision.\n    resume_from_revision: ?u64 = null,\n};\n\npub const KeyValueEntry = struct {\n    bucket: []const u8,\n    key: []const u8,\n    value: []const u8,\n    revision: u64,\n    operation: KeyValueOp,\n    /// Allocator used for owned key (null = not owned).\n    key_allocator: ?std.mem.Allocator = null,\n    /// Allocator used for owned value (null = not owned).\n    value_allocator: ?std.mem.Allocator = null,\n\n    /// Frees owned key/value buffers if allocated.\n    pub fn deinit(self: *KeyValueEntry) void {\n        if (self.key_allocator) |a| {\n            if (self.key.len > 0) a.free(self.key);\n            self.key_allocator = null;\n        }\n        if (self.value_allocator) |a| {\n            if (self.value.len > 0) a.free(self.value);\n            self.value_allocator = null;\n        }\n    }\n};\n\n// -- Stream MSG.GET types --\n\npub const MsgGetRequest = struct {\n    last_by_subj: ?[]const u8 = null,\n    seq: ?u64 = null,\n};\n\npub const MsgGetResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    message: ?StoredMsg = null,\n};\n\npub const StoredMsg = struct {\n    subject: ?[]const u8 = null,\n    seq: u64 = 0,\n    data: ?[]const u8 = null,\n    hdrs: ?[]const u8 = null,\n    time: ?[]const u8 = null,\n};\n\n// -- Key-Value status --\n\npub const KeyValueStatus = struct {\n    bucket: []const u8 = \"\",\n    values: u64 = 0,\n    history: i64 = 1,\n    ttl: i64 = 0,\n    bytes: u64 = 0,\n    backing_store: StorageType = .file,\n    is_compressed: bool = false,\n};\n\n// -- Stream MSG.DELETE types --\n\npub const MsgDeleteRequest = struct {\n    seq: u64,\n    no_erase: ?bool = null,\n};\n\n// -- Consumer pause types --\n\npub const ConsumerPauseRequest = struct {\n    pause_until: ?[]const u8 = null,\n};\n\npub const ConsumerPauseResponse = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    paused: bool = false,\n    pause_until: ?[]const u8 = null,\n    pause_remaining: ?i64 = null,\n};\n\n// -- Consumer unpin types --\n\npub const ConsumerUnpinRequest = struct {\n    group: []const u8,\n};\n\n// -- Account info --\n\npub const AccountInfo = struct {\n    type: ?[]const u8 = null,\n    @\"error\": ?ApiErrorJson = null,\n    memory: u64 = 0,\n    storage: u64 = 0,\n    streams: u64 = 0,\n    consumers: u64 = 0,\n    limits: ?AccountLimits = null,\n    api: ?APIStats = null,\n    domain: ?[]const u8 = null,\n};\n\npub const AccountLimits = struct {\n    max_memory: i64 = 0,\n    max_storage: i64 = 0,\n    max_streams: i64 = 0,\n    max_consumers: i64 = 0,\n};\n\npub const APIStats = struct {\n    total: u64 = 0,\n    errors: u64 = 0,\n};\n\n// -- Generic response wrapper --\n\n/// Wraps a parsed JSON response. All string slices in\n/// `value` point into the parsed arena -- they become\n/// invalid after `deinit()`. Copy any strings you need\n/// to keep: `const s = try alloc.dupe(u8, val.name);`\n/// Caller MUST call `deinit()`.\npub fn Response(comptime T: type) type {\n    return struct {\n        const Self = @This();\n        value: T,\n        _parsed: std.json.Parsed(T),\n\n        pub fn deinit(self: *Self) void {\n            self._parsed.deinit();\n        }\n    };\n}\n\n// -- JSON helpers --\n\nconst json_stringify_opts: std.json.Stringify.Options = .{\n    .emit_null_optional_fields = false,\n};\n\nconst json_parse_opts: std.json.ParseOptions = .{\n    .ignore_unknown_fields = true,\n    .allocate = .alloc_always,\n};\n\n/// Serializes a value to JSON, omitting null optional fields.\npub fn jsonStringify(\n    allocator: std.mem.Allocator,\n    value: anytype,\n) error{OutOfMemory}![]u8 {\n    return std.json.Stringify.valueAlloc(\n        allocator,\n        value,\n        json_stringify_opts,\n    );\n}\n\n/// Parses JSON into type T, ignoring unknown fields.\npub fn jsonParse(\n    comptime T: type,\n    allocator: std.mem.Allocator,\n    data: []const u8,\n) std.json.ParseError(std.json.Scanner)!std.json.Parsed(T) {\n    return std.json.parseFromSlice(\n        T,\n        allocator,\n        data,\n        json_parse_opts,\n    );\n}\n\n// -- Tests --\n\ntest \"StreamConfig JSON round-trip\" {\n    const alloc = std.testing.allocator;\n    const config = StreamConfig{\n        .name = \"TEST\",\n        .subjects = &.{\"test.>\"},\n        .retention = .limits,\n        .storage = .file,\n        .max_msgs = 1000,\n    };\n\n    const json = try jsonStringify(alloc, config);\n    defer alloc.free(json);\n\n    var parsed = try jsonParse(StreamConfig, alloc, json);\n    defer parsed.deinit();\n\n    const v = parsed.value;\n    try std.testing.expectEqualStrings(\"TEST\", v.name);\n    try std.testing.expectEqual(RetentionPolicy.limits, v.retention.?);\n    try std.testing.expectEqual(StorageType.file, v.storage.?);\n    try std.testing.expectEqual(@as(i64, 1000), v.max_msgs.?);\n    try std.testing.expect(v.subjects != null);\n    try std.testing.expectEqual(@as(usize, 1), v.subjects.?.len);\n}\n\ntest \"ConsumerConfig JSON round-trip\" {\n    const alloc = std.testing.allocator;\n    const config = ConsumerConfig{\n        .name = \"my-consumer\",\n        .durable_name = \"my-consumer\",\n        .ack_policy = .explicit,\n        .deliver_policy = .all,\n        .max_ack_pending = 1000,\n    };\n\n    const json = try jsonStringify(alloc, config);\n    defer alloc.free(json);\n\n    var parsed = try jsonParse(ConsumerConfig, alloc, json);\n    defer parsed.deinit();\n\n    const v = parsed.value;\n    try std.testing.expectEqualStrings(\"my-consumer\", v.name.?);\n    try std.testing.expectEqual(AckPolicy.explicit, v.ack_policy.?);\n    try std.testing.expectEqual(\n        DeliverPolicy.all,\n        v.deliver_policy.?,\n    );\n}\n\ntest \"PubAck parse with error\" {\n    const alloc = std.testing.allocator;\n    const json =\n        \\\\{\"type\":\"io.nats.jetstream.api.v1.pub_ack\",\n        \\\\\"error\":{\"code\":503,\"err_code\":10076,\n        \\\\\"description\":\"jetstream not enabled\"}}\n    ;\n\n    var parsed = try jsonParse(PubAck, alloc, json);\n    defer parsed.deinit();\n\n    const v = parsed.value;\n    try std.testing.expect(v.@\"error\" != null);\n    try std.testing.expectEqual(@as(u16, 503), v.@\"error\".?.code);\n    try std.testing.expectEqual(\n        @as(u16, 10076),\n        v.@\"error\".?.err_code,\n    );\n}\n\ntest \"DeleteResponse parse success\" {\n    const alloc = std.testing.allocator;\n    const json = \"{\\\"success\\\":true}\";\n\n    var parsed = try jsonParse(DeleteResponse, alloc, json);\n    defer parsed.deinit();\n\n    try std.testing.expect(parsed.value.success);\n}\n\ntest \"null optional fields omitted in JSON\" {\n    const alloc = std.testing.allocator;\n    const config = StreamConfig{ .name = \"TEST\" };\n\n    const json = try jsonStringify(alloc, config);\n    defer alloc.free(json);\n\n    // Should not contain \"retention\" since it's null\n    try std.testing.expect(\n        std.mem.indexOf(u8, json, \"retention\") == null,\n    );\n    // Should contain \"name\"\n    try std.testing.expect(\n        std.mem.indexOf(u8, json, \"name\") != null,\n    );\n}\n\ntest \"StreamNamesResponse JSON round-trip\" {\n    const alloc = std.testing.allocator;\n    const json =\n        \\\\{\"total\":3,\"offset\":0,\"limit\":1024,\n        \\\\\"streams\":[\"S1\",\"S2\",\"S3\"]}\n    ;\n    var parsed = try jsonParse(\n        StreamNamesResponse,\n        alloc,\n        json,\n    );\n    defer parsed.deinit();\n\n    const v = parsed.value;\n    try std.testing.expectEqual(@as(u64, 3), v.total);\n    try std.testing.expectEqual(@as(u64, 0), v.offset);\n    try std.testing.expect(v.streams != null);\n    try std.testing.expectEqual(\n        @as(usize, 3),\n        v.streams.?.len,\n    );\n    try std.testing.expectEqualStrings(\n        \"S1\",\n        v.streams.?[0],\n    );\n}\n\ntest \"AccountInfo JSON round-trip\" {\n    const alloc = std.testing.allocator;\n    const json =\n        \\\\{\"memory\":1024,\"storage\":4096,\"streams\":2,\n        \\\\\"consumers\":5,\"limits\":{\"max_memory\":-1,\n        \\\\\"max_storage\":-1,\"max_streams\":-1,\n        \\\\\"max_consumers\":-1},\"api\":{\"total\":42,\n        \\\\\"errors\":1}}\n    ;\n    var parsed = try jsonParse(AccountInfo, alloc, json);\n    defer parsed.deinit();\n\n    const v = parsed.value;\n    try std.testing.expectEqual(@as(u64, 1024), v.memory);\n    try std.testing.expectEqual(@as(u64, 4096), v.storage);\n    try std.testing.expectEqual(@as(u64, 2), v.streams);\n    try std.testing.expectEqual(@as(u64, 5), v.consumers);\n    try std.testing.expect(v.limits != null);\n    try std.testing.expect(v.api != null);\n    try std.testing.expectEqual(\n        @as(u64, 42),\n        v.api.?.total,\n    );\n}\n\ntest \"ConsumerConfig with push fields serializes\" {\n    const alloc = std.testing.allocator;\n    const config = ConsumerConfig{\n        .name = \"push-test\",\n        .deliver_subject = \"deliver.test\",\n        .deliver_group = \"grp\",\n    };\n\n    const json = try jsonStringify(alloc, config);\n    defer alloc.free(json);\n\n    try std.testing.expect(\n        std.mem.indexOf(u8, json, \"deliver_subject\") !=\n            null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, json, \"deliver_group\") !=\n            null,\n    );\n\n    var parsed = try jsonParse(\n        ConsumerConfig,\n        alloc,\n        json,\n    );\n    defer parsed.deinit();\n    try std.testing.expectEqualStrings(\n        \"deliver.test\",\n        parsed.value.deliver_subject.?,\n    );\n}\n\ntest \"MsgDeleteRequest serialization\" {\n    const alloc = std.testing.allocator;\n    // With no_erase\n    const json1 = try jsonStringify(alloc, MsgDeleteRequest{\n        .seq = 42,\n        .no_erase = true,\n    });\n    defer alloc.free(json1);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json1, \"\\\"seq\\\":42\") != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, json1, \"no_erase\") != null,\n    );\n\n    // Without no_erase (null omitted)\n    const json2 = try jsonStringify(\n        alloc,\n        MsgDeleteRequest{ .seq = 7 },\n    );\n    defer alloc.free(json2);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json2, \"no_erase\") == null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, json2, \"\\\"seq\\\":7\") != null,\n    );\n}\n\ntest \"ConsumerPauseRequest serialization\" {\n    const alloc = std.testing.allocator;\n    const json1 = try jsonStringify(\n        alloc,\n        ConsumerPauseRequest{\n            .pause_until = \"2026-04-01T00:00:00Z\",\n        },\n    );\n    defer alloc.free(json1);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json1, \"pause_until\") !=\n            null,\n    );\n\n    const json2 = try jsonStringify(\n        alloc,\n        ConsumerPauseRequest{},\n    );\n    defer alloc.free(json2);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json2, \"pause_until\") ==\n            null,\n    );\n}\n\ntest \"ConsumerPauseResponse parsing\" {\n    const alloc = std.testing.allocator;\n    const json =\n        \\\\{\"paused\":true,\n        \\\\\"pause_until\":\"2026-04-01T00:00:00Z\",\n        \\\\\"pause_remaining\":3600000000000}\n    ;\n    var parsed = try jsonParse(\n        ConsumerPauseResponse,\n        alloc,\n        json,\n    );\n    defer parsed.deinit();\n    try std.testing.expect(parsed.value.paused);\n    try std.testing.expect(\n        parsed.value.pause_until != null,\n    );\n    try std.testing.expectEqual(\n        @as(i64, 3600000000000),\n        parsed.value.pause_remaining.?,\n    );\n}\n\ntest \"PublishOpts TTL field\" {\n    const alloc = std.testing.allocator;\n    const json = try jsonStringify(\n        alloc,\n        PublishOpts{ .ttl = \"5s\" },\n    );\n    defer alloc.free(json);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json, \"\\\"ttl\\\":\\\"5s\\\"\") !=\n            null,\n    );\n}\n\ntest \"StreamConfig allow_msg_ttl\" {\n    const alloc = std.testing.allocator;\n    const json = try jsonStringify(alloc, StreamConfig{\n        .name = \"TTL_TEST\",\n        .allow_msg_ttl = true,\n    });\n    defer alloc.free(json);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json, \"allow_msg_ttl\") !=\n            null,\n    );\n}\n\ntest \"CreateConsumerRequest action field\" {\n    const alloc = std.testing.allocator;\n    // With action = \"create\"\n    const json1 = try jsonStringify(\n        alloc,\n        CreateConsumerRequest{\n            .stream_name = \"S\",\n            .config = .{ .name = \"C\" },\n            .action = \"create\",\n        },\n    );\n    defer alloc.free(json1);\n    try std.testing.expect(\n        std.mem.indexOf(\n            u8,\n            json1,\n            \"\\\"action\\\":\\\"create\\\"\",\n        ) != null,\n    );\n\n    // With action = null (omitted for createOrUpdate)\n    const json2 = try jsonStringify(\n        alloc,\n        CreateConsumerRequest{\n            .stream_name = \"S\",\n            .config = .{ .name = \"C\" },\n        },\n    );\n    defer alloc.free(json2);\n    try std.testing.expect(\n        std.mem.indexOf(u8, json2, \"action\") == null,\n    );\n}\n"
  },
  {
    "path": "src/jetstream.zig",
    "content": "//! JetStream -- NATS persistence and streaming layer.\n//!\n//! Provides stream/consumer CRUD, publish with ack, pull-based\n//! message consumption, and message acknowledgment protocol over\n//! core NATS request/reply.\n\nconst std = @import(\"std\");\n\npub const JetStream = @import(\"jetstream/JetStream.zig\");\npub const types = @import(\"jetstream/types.zig\");\npub const errors = @import(\"jetstream/errors.zig\");\npub const consumer = @import(\"jetstream/consumer.zig\");\nconst message = @import(\"jetstream/message.zig\");\npub const JsMsg = message.JsMsg;\npub const MsgMetadata = message.MsgMetadata;\nconst pull_mod = @import(\"jetstream/pull.zig\");\npub const PullSubscription = pull_mod.PullSubscription;\npub const MessagesContext = pull_mod.MessagesContext;\nconst push_mod = @import(\"jetstream/push.zig\");\npub const PushSubscription = push_mod.PushSubscription;\nconst ordered_mod = @import(\"jetstream/ordered.zig\");\npub const OrderedConsumer = ordered_mod.OrderedConsumer;\nconst kv_mod = @import(\"jetstream/kv.zig\");\npub const KeyValue = kv_mod.KeyValue;\npub const KvWatcher = kv_mod.KvWatcher;\npub const KeyLister = kv_mod.KeyValue.KeyLister;\npub const KeyValueConfig = types.KeyValueConfig;\npub const KeyValueEntry = types.KeyValueEntry;\npub const KeyValueOp = types.KeyValueOp;\npub const WatchOpts = types.WatchOpts;\n\n// Consumer abstractions\npub const JsMsgHandler = consumer.JsMsgHandler;\npub const ConsumeContext = consumer.ConsumeContext;\npub const ConsumeOpts = consumer.ConsumeOpts;\npub const HeartbeatMonitor = consumer.HeartbeatMonitor;\n\n// Convenience re-exports\npub const StreamConfig = types.StreamConfig;\npub const ConsumerConfig = types.ConsumerConfig;\npub const StreamInfo = types.StreamInfo;\npub const ConsumerInfo = types.ConsumerInfo;\npub const PubAck = types.PubAck;\npub const ApiError = errors.ApiError;\npub const Response = types.Response;\npub const AccountInfo = types.AccountInfo;\npub const ConsumerPauseResponse = types.ConsumerPauseResponse;\npub const PublishOpts = types.PublishOpts;\npub const MsgGetResponse = types.MsgGetResponse;\npub const KeyValueStatus = types.KeyValueStatus;\nconst async_pub = @import(\"jetstream/async_publish.zig\");\npub const AsyncPublisher = async_pub.AsyncPublisher;\npub const PubAckFuture = async_pub.PubAckFuture;\n\ntest {\n    std.testing.refAllDecls(@This());\n}\n"
  },
  {
    "path": "src/memory/sidmap.zig",
    "content": "//! SidMap - Zero-Alloc Subscription ID Router\n//!\n//! Pre-allocated open-addressing hash map optimized for O(1) subscription\n//! routing. Uses splitmix64 hash and power-of-two capacity for fast lookups.\n//! Inspired by io_uring NATS client design.\n//!\n//! Zero allocations - caller provides pre-allocated arrays.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// Sentinel values for slot state.\npub const EMPTY: u16 = 0xFFFF;\npub const TOMB: u16 = 0xFFFE;\n\n/// Maximum valid slot index (leaves room for EMPTY/TOMB sentinels).\npub const MAX_SLOT: u16 = 0xFFFD;\n\n/// Zero-allocation subscription ID to slot index map.\n///\n/// Uses open-addressing with linear probing and splitmix64 hash.\n/// Caller provides pre-allocated keys/vals arrays at init.\n/// Maximum 70% load factor enforced to maintain O(1) performance.\npub const SidMap = struct {\n    keys: []u64,\n    vals: []u16,\n    cap: u32,\n    len: u32,\n\n    /// Initialize SidMap with pre-allocated arrays.\n    /// Capacity must be power of 2 and match array lengths.\n    pub fn init(keys: []u64, vals: []u16) SidMap {\n        assert(keys.len == vals.len);\n        assert(keys.len > 0);\n        assert(isPowerOfTwo(keys.len));\n        assert(keys.len <= std.math.maxInt(u32));\n\n        @memset(vals, EMPTY);\n\n        return .{\n            .keys = keys,\n            .vals = vals,\n            .cap = @intCast(keys.len),\n            .len = 0,\n        };\n    }\n\n    /// O(1) lookup - returns slot index for SID or null if not found.\n    /// Marked inline for hot path performance.\n    pub inline fn get(self: *const SidMap, sid: u64) ?u16 {\n        assert(self.cap > 0);\n        assert(isPowerOfTwo(self.cap));\n\n        const mask = self.cap - 1;\n        var idx: u32 = @intCast(mix64(sid) & mask);\n        var probes: u32 = 0;\n\n        while (probes < self.cap) : (probes += 1) {\n            const v = self.vals[idx];\n\n            if (v == EMPTY) {\n                return null;\n            }\n\n            if (v != TOMB and self.keys[idx] == sid) {\n                return v;\n            }\n\n            idx = (idx + 1) & mask;\n        }\n\n        return null;\n    }\n\n    /// Insert or update SID -> slot mapping.\n    /// Returns error if map is full (load > 70%).\n    pub fn put(self: *SidMap, sid: u64, slot: u16) error{MapFull}!void {\n        assert(slot <= MAX_SLOT);\n        assert(self.cap > 0);\n\n        const max_load = self.cap * 7 / 10;\n        if (self.len >= max_load) {\n            return error.MapFull;\n        }\n\n        const mask = self.cap - 1;\n        var idx: u32 = @intCast(mix64(sid) & mask);\n        var tomb_idx: ?u32 = null;\n        var probes: u32 = 0;\n\n        while (probes < self.cap) : (probes += 1) {\n            const v = self.vals[idx];\n\n            if (v == EMPTY) {\n                const insert_idx = tomb_idx orelse idx;\n                self.keys[insert_idx] = sid;\n                self.vals[insert_idx] = slot;\n                self.len += 1;\n                return;\n            }\n\n            if (v == TOMB) {\n                if (tomb_idx == null) {\n                    tomb_idx = idx;\n                }\n            } else if (self.keys[idx] == sid) {\n                self.vals[idx] = slot;\n                return;\n            }\n\n            idx = (idx + 1) & mask;\n        }\n\n        // REVIEWED(2025-03): Load factor (70%) guarantees at\n        // least one EMPTY slot exists within probing distance.\n        // Tombstones don't count toward len, so len < max_load\n        // ensures the loop always finds an EMPTY or matching\n        // slot before exhausting cap probes.\n        unreachable;\n    }\n\n    /// Remove SID from map. Returns true if found and removed.\n    pub fn remove(self: *SidMap, sid: u64) bool {\n        assert(self.cap > 0);\n\n        const mask = self.cap - 1;\n        var idx: u32 = @intCast(mix64(sid) & mask);\n        var probes: u32 = 0;\n\n        while (probes < self.cap) : (probes += 1) {\n            const v = self.vals[idx];\n\n            if (v == EMPTY) {\n                return false;\n            }\n\n            if (v != TOMB and self.keys[idx] == sid) {\n                self.vals[idx] = TOMB;\n                self.len -= 1;\n                return true;\n            }\n\n            idx = (idx + 1) & mask;\n        }\n\n        return false;\n    }\n\n    /// Returns current number of entries.\n    pub fn count(self: *const SidMap) u32 {\n        return self.len;\n    }\n\n    /// Returns true if map is empty.\n    pub fn isEmpty(self: *const SidMap) bool {\n        return self.len == 0;\n    }\n\n    /// Clear all entries (reset to initial state).\n    pub fn clear(self: *SidMap) void {\n        @memset(self.vals, EMPTY);\n        self.len = 0;\n    }\n};\n\n/// splitmix64 hash function - fast 64-bit mixer.\n/// 5 operations, good avalanche properties.\ninline fn mix64(x0: u64) u64 {\n    var x = x0 +% 0x9E3779B97F4A7C15;\n    x = (x ^ (x >> 30)) *% 0xBF58476D1CE4E5B9;\n    x = (x ^ (x >> 27)) *% 0x94D049BB133111EB;\n    return x ^ (x >> 31);\n}\n\n/// Check if n is a power of two.\ninline fn isPowerOfTwo(n: usize) bool {\n    return n > 0 and (n & (n - 1)) == 0;\n}\n\ntest {\n    _ = @import(\"sidmap_test.zig\");\n}\n"
  },
  {
    "path": "src/memory/sidmap_test.zig",
    "content": "//! SidMap Edge Case Tests\n//!\n//! - Sentinel value handling (EMPTY/TOMB corruption)\n//! - Hash collision stress testing\n//! - Tombstone accumulation and reuse\n//! - Load factor boundary conditions\n//! - Edge values (SID=0, slot=0, max values)\n\nconst std = @import(\"std\");\nconst sidmap = @import(\"sidmap.zig\");\nconst SidMap = sidmap.SidMap;\nconst EMPTY = sidmap.EMPTY;\nconst TOMB = sidmap.TOMB;\nconst MAX_SLOT = sidmap.MAX_SLOT;\n\ntest \"SidMap basic operations\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n\n    var map: SidMap = .init(&keys, &vals);\n\n    try std.testing.expect(map.isEmpty());\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n\n    try map.put(100, 0);\n    try map.put(200, 1);\n    try map.put(300, 2);\n\n    try std.testing.expectEqual(@as(u32, 3), map.count());\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(100).?);\n    try std.testing.expectEqual(@as(u16, 1), map.get(200).?);\n    try std.testing.expectEqual(@as(u16, 2), map.get(300).?);\n    try std.testing.expect(map.get(999) == null);\n\n    try map.put(200, 42);\n    try std.testing.expectEqual(@as(u16, 42), map.get(200).?);\n    try std.testing.expectEqual(@as(u32, 3), map.count());\n\n    try std.testing.expect(map.remove(200));\n    try std.testing.expect(map.get(200) == null);\n    try std.testing.expectEqual(@as(u32, 2), map.count());\n\n    try std.testing.expect(!map.remove(999));\n}\n\ntest \"SidMap tombstone reuse\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try map.put(3, 2);\n\n    try std.testing.expect(map.remove(2));\n\n    try map.put(4, 3);\n    try std.testing.expectEqual(@as(u32, 3), map.count());\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(1).?);\n    try std.testing.expect(map.get(2) == null);\n    try std.testing.expectEqual(@as(u16, 2), map.get(3).?);\n    try std.testing.expectEqual(@as(u16, 3), map.get(4).?);\n}\n\ntest \"SidMap load factor limit\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 8 = 5 entries max\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try map.put(3, 2);\n    try map.put(4, 3);\n    try map.put(5, 4);\n\n    // 6th should fail\n    try std.testing.expectError(error.MapFull, map.put(6, 5));\n}\n\ntest \"SidMap clear\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try std.testing.expectEqual(@as(u32, 2), map.count());\n\n    map.clear();\n    try std.testing.expect(map.isEmpty());\n    try std.testing.expect(map.get(1) == null);\n    try std.testing.expect(map.get(2) == null);\n}\n\ntest \"SidMap large capacity\" {\n    var keys: [512]u64 = undefined;\n    var vals: [512]u16 = undefined;\n\n    var map: SidMap = .init(&keys, &vals);\n\n    // Insert many entries\n    var i: u64 = 0;\n    while (i < 300) : (i += 1) {\n        try map.put(i * 1000, @intCast(i));\n    }\n\n    try std.testing.expectEqual(@as(u32, 300), map.count());\n\n    i = 0;\n    while (i < 300) : (i += 1) {\n        try std.testing.expectEqual(@as(u16, @intCast(i)), map.get(i * 1000).?);\n    }\n}\n\ntest \"SidMap SID zero\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(0, 42);\n    try std.testing.expectEqual(@as(u16, 42), map.get(0).?);\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try std.testing.expect(map.remove(0));\n    try std.testing.expect(map.get(0) == null);\n}\n\ntest \"SidMap SID u64 max\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    const max_sid: u64 = std.math.maxInt(u64);\n    try map.put(max_sid, 99);\n    try std.testing.expectEqual(@as(u16, 99), map.get(max_sid).?);\n}\n\ntest \"SidMap SID one\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try std.testing.expectEqual(@as(u16, 0), map.get(1).?);\n}\n\ntest \"SidMap consecutive SIDs\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try map.put(3, 2);\n    try map.put(4, 3);\n    try map.put(5, 4);\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(1).?);\n    try std.testing.expectEqual(@as(u16, 1), map.get(2).?);\n    try std.testing.expectEqual(@as(u16, 2), map.get(3).?);\n    try std.testing.expectEqual(@as(u16, 3), map.get(4).?);\n    try std.testing.expectEqual(@as(u16, 4), map.get(5).?);\n}\n\ntest \"SidMap slot zero\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(100, 0);\n    try std.testing.expectEqual(@as(u16, 0), map.get(100).?);\n}\n\ntest \"SidMap slot MAX_SLOT\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(100, MAX_SLOT);\n    try std.testing.expectEqual(MAX_SLOT, map.get(100).?);\n}\n\n// Sentinel protection: put() asserts slot <= MAX_SLOT to prevent TOMB/EMPTY\n// corruption. Debug builds fail assertion; release builds strip assert.\n\ntest \"SidMap sentinel values documented\" {\n    try std.testing.expectEqual(@as(u16, 0xFFFF), EMPTY);\n    try std.testing.expectEqual(@as(u16, 0xFFFE), TOMB);\n    try std.testing.expectEqual(@as(u16, 0xFFFD), MAX_SLOT);\n}\n\ntest \"SidMap valid slot range\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // Verify full valid range: 0 to MAX_SLOT (0xFFFD)\n    try map.put(1, 0); // Minimum valid\n    try map.put(2, MAX_SLOT); // Maximum valid\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(1).?);\n    try std.testing.expectEqual(MAX_SLOT, map.get(2).?);\n}\n\ntest \"SidMap exact load factor boundary\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 8 = 5.6, truncated to 5\n    // So max_load = 5, inserts allowed while len < 5\n    try map.put(1, 0); // len=1\n    try map.put(2, 1); // len=2\n    try map.put(3, 2); // len=3\n    try map.put(4, 3); // len=4\n    try map.put(5, 4); // len=5\n\n    try std.testing.expectEqual(@as(u32, 5), map.count());\n\n    try std.testing.expectError(error.MapFull, map.put(6, 5));\n}\n\ntest \"SidMap load factor after removes\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try map.put(3, 2);\n    try map.put(4, 3);\n    try map.put(5, 4);\n\n    try std.testing.expect(map.remove(1));\n    try std.testing.expect(map.remove(2));\n    try std.testing.expectEqual(@as(u32, 3), map.count());\n\n    try map.put(6, 5);\n    try map.put(7, 6);\n    try std.testing.expectEqual(@as(u32, 5), map.count());\n\n    try std.testing.expectError(error.MapFull, map.put(8, 7));\n}\n\ntest \"SidMap load factor 16 capacity\" {\n    var keys: [16]u64 = undefined;\n    var vals: [16]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 16 = 11.2, truncated to 11\n    var i: u64 = 0;\n    while (i < 11) : (i += 1) {\n        try map.put(i, @intCast(i));\n    }\n\n    try std.testing.expectEqual(@as(u32, 11), map.count());\n\n    // 12th should fail\n    try std.testing.expectError(error.MapFull, map.put(11, 11));\n}\n\ntest \"SidMap load factor 256 capacity\" {\n    var keys: [256]u64 = undefined;\n    var vals: [256]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 256 = 179.2, truncated to 179\n    var i: u64 = 0;\n    while (i < 179) : (i += 1) {\n        try map.put(i * 7, @intCast(i)); // Spread out SIDs\n    }\n\n    try std.testing.expectEqual(@as(u32, 179), map.count());\n\n    // 180th should fail\n    try std.testing.expectError(error.MapFull, map.put(9999, 179));\n}\n\ntest \"SidMap tombstone lookup continues probing\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // Insert entries that will cluster (hash not controllable, insert many)\n    try map.put(100, 0);\n    try map.put(200, 1);\n    try map.put(300, 2);\n\n    try std.testing.expect(map.remove(200));\n\n    try std.testing.expectEqual(@as(u16, 2), map.get(300).?);\n}\n\ntest \"SidMap tombstone reuse on insert\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try map.put(3, 2);\n    try map.put(4, 3);\n    try map.put(5, 4);\n\n    try std.testing.expect(map.remove(1));\n    try std.testing.expect(map.remove(2));\n    try std.testing.expect(map.remove(3));\n    try std.testing.expect(map.remove(4));\n    try std.testing.expect(map.remove(5));\n\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n\n    try map.put(10, 0);\n    try map.put(20, 1);\n    try map.put(30, 2);\n    try map.put(40, 3);\n    try map.put(50, 4);\n\n    try std.testing.expectEqual(@as(u32, 5), map.count());\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(10).?);\n    try std.testing.expectEqual(@as(u16, 4), map.get(50).?);\n}\n\ntest \"SidMap many insert remove cycles\" {\n    var keys: [64]u64 = undefined;\n    var vals: [64]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    const max_entries = 44;\n\n    var i: u64 = 0;\n    while (i < max_entries) : (i += 1) {\n        try map.put(i, @intCast(i));\n    }\n    i = 0;\n    while (i < max_entries) : (i += 1) {\n        try std.testing.expect(map.remove(i));\n    }\n\n    i = 1000;\n    while (i < 1000 + max_entries) : (i += 1) {\n        try map.put(i, @intCast(i - 1000));\n    }\n\n    try std.testing.expectEqual(@as(u32, max_entries), map.count());\n    try std.testing.expectEqual(@as(u16, 0), map.get(1000).?);\n    try std.testing.expectEqual(@as(u16, 43), map.get(1043).?);\n\n    try std.testing.expect(map.get(0) == null);\n    try std.testing.expect(map.get(43) == null);\n}\n\ntest \"SidMap tombstone does not affect count\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try std.testing.expectEqual(@as(u32, 2), map.count());\n\n    try std.testing.expect(map.remove(1));\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try std.testing.expect(map.get(1) == null);\n    try std.testing.expectEqual(@as(u16, 1), map.get(2).?);\n}\n\ntest \"SidMap double remove same SID\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(100, 42);\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try std.testing.expect(map.remove(100));\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n\n    try std.testing.expect(!map.remove(100));\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n}\n\ntest \"SidMap update existing does not change count\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(100, 0);\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try map.put(100, 1);\n    try map.put(100, 2);\n    try map.put(100, 42);\n\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n    try std.testing.expectEqual(@as(u16, 42), map.get(100).?);\n}\n\ntest \"SidMap put after remove same SID\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(100, 0);\n    try std.testing.expect(map.remove(100));\n    try std.testing.expect(map.get(100) == null);\n\n    try map.put(100, 99);\n    try std.testing.expectEqual(@as(u16, 99), map.get(100).?);\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n}\n\ntest \"SidMap get on empty\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try std.testing.expect(map.get(0) == null);\n    try std.testing.expect(map.get(1) == null);\n    try std.testing.expect(map.get(std.math.maxInt(u64)) == null);\n}\n\ntest \"SidMap remove on empty\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try std.testing.expect(!map.remove(0));\n    try std.testing.expect(!map.remove(100));\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n}\n\ntest \"SidMap clear on empty\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    map.clear();\n    try std.testing.expect(map.isEmpty());\n}\n\ntest \"SidMap clear after operations\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try std.testing.expect(map.remove(1));\n\n    map.clear();\n\n    try std.testing.expect(map.isEmpty());\n    try std.testing.expect(map.get(1) == null);\n    try std.testing.expect(map.get(2) == null);\n\n    try map.put(10, 0);\n    try map.put(20, 1);\n    try map.put(30, 2);\n    try map.put(40, 3);\n    try map.put(50, 4);\n    try std.testing.expectEqual(@as(u32, 5), map.count());\n}\n\ntest \"SidMap remove does not break probe chain\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(10, 0);\n    try map.put(20, 1);\n    try map.put(30, 2);\n    try map.put(40, 3);\n    try map.put(50, 4);\n\n    try std.testing.expect(map.remove(20));\n    try std.testing.expect(map.remove(40));\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(10).?);\n    try std.testing.expectEqual(@as(u16, 2), map.get(30).?);\n    try std.testing.expectEqual(@as(u16, 4), map.get(50).?);\n\n    try std.testing.expect(map.get(20) == null);\n    try std.testing.expect(map.get(40) == null);\n}\n\ntest \"SidMap insert after remove maintains integrity\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try map.put(3, 2);\n    try map.put(4, 3);\n    try map.put(5, 4);\n\n    try std.testing.expect(map.remove(2));\n    try std.testing.expect(map.remove(4));\n\n    try map.put(6, 5);\n    try map.put(7, 6);\n\n    try std.testing.expectEqual(@as(u16, 0), map.get(1).?);\n    try std.testing.expectEqual(@as(u16, 2), map.get(3).?);\n    try std.testing.expectEqual(@as(u16, 4), map.get(5).?);\n    try std.testing.expectEqual(@as(u16, 5), map.get(6).?);\n    try std.testing.expectEqual(@as(u16, 6), map.get(7).?);\n\n    try std.testing.expect(map.get(2) == null);\n    try std.testing.expect(map.get(4) == null);\n}\n\ntest \"SidMap minimum capacity 2\" {\n    var keys: [2]u64 = undefined;\n    var vals: [2]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 2 = 1.4, truncated to 1\n    try map.put(100, 0);\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    // 2nd should fail\n    try std.testing.expectError(error.MapFull, map.put(200, 1));\n}\n\ntest \"SidMap capacity 4\" {\n    var keys: [4]u64 = undefined;\n    var vals: [4]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 4 = 2.8, truncated to 2\n    try map.put(1, 0);\n    try map.put(2, 1);\n    try std.testing.expectEqual(@as(u32, 2), map.count());\n\n    // 3rd should fail\n    try std.testing.expectError(error.MapFull, map.put(3, 2));\n}\n\ntest \"SidMap capacity 1024\" {\n    var keys: [1024]u64 = undefined;\n    var vals: [1024]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    // 70% of 1024 = 716.8, truncated to 716\n    var i: u64 = 0;\n    while (i < 716) : (i += 1) {\n        try map.put(i, @intCast(i & 0xFFFF));\n    }\n    try std.testing.expectEqual(@as(u32, 716), map.count());\n\n    // 717th should fail\n    try std.testing.expectError(error.MapFull, map.put(99999, 0));\n}\n\ntest \"SidMap lookup non-existent after many tombstones\" {\n    var keys: [64]u64 = undefined;\n    var vals: [64]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    var i: u64 = 0;\n    while (i < 44) : (i += 1) {\n        try map.put(i, @intCast(i));\n    }\n\n    i = 0;\n    while (i < 44) : (i += 1) {\n        try std.testing.expect(map.remove(i));\n    }\n\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n\n    try std.testing.expect(map.get(100) == null);\n    try std.testing.expect(map.get(999) == null);\n    try std.testing.expect(map.get(0) == null);\n\n    try map.put(1000, 42);\n    try std.testing.expectEqual(@as(u16, 42), map.get(1000).?);\n}\n\ntest \"SidMap alternating insert remove stress\" {\n    var keys: [32]u64 = undefined;\n    var vals: [32]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    var cycle: u32 = 0;\n    while (cycle < 100) : (cycle += 1) {\n        const sid = @as(u64, cycle) * 1000;\n        try map.put(sid, @intCast(cycle & 0xFFFF));\n        try std.testing.expect(map.remove(sid));\n    }\n\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n\n    try map.put(1, 0);\n    try std.testing.expectEqual(@as(u16, 0), map.get(1).?);\n}\n\ntest \"SidMap sequential SIDs distribute well\" {\n    var keys: [256]u64 = undefined;\n    var vals: [256]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    var i: u64 = 1;\n    while (i <= 170) : (i += 1) {\n        try map.put(i, @intCast(i));\n    }\n\n    i = 1;\n    while (i <= 170) : (i += 1) {\n        try std.testing.expectEqual(@as(u16, @intCast(i)), map.get(i).?);\n    }\n}\n\ntest \"SidMap sparse SIDs\" {\n    var keys: [64]u64 = undefined;\n    var vals: [64]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    const sids = [_]u64{\n        1,\n        1000,\n        1000000,\n        1000000000,\n        std.math.maxInt(u64),\n        std.math.maxInt(u64) - 1,\n        std.math.maxInt(u64) / 2,\n    };\n\n    for (sids, 0..) |sid, idx| {\n        try map.put(sid, @intCast(idx));\n    }\n\n    for (sids, 0..) |sid, idx| {\n        try std.testing.expectEqual(@as(u16, @intCast(idx)), map.get(sid).?);\n    }\n}\n\ntest \"SidMap isEmpty after fill and empty\" {\n    var keys: [8]u64 = undefined;\n    var vals: [8]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try std.testing.expect(map.isEmpty());\n\n    try map.put(1, 0);\n    try std.testing.expect(!map.isEmpty());\n\n    try std.testing.expect(map.remove(1));\n    try std.testing.expect(map.isEmpty());\n}\n\ntest \"SidMap count accuracy through operations\" {\n    var keys: [16]u64 = undefined;\n    var vals: [16]u16 = undefined;\n    var map: SidMap = .init(&keys, &vals);\n\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n\n    try map.put(1, 0);\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try map.put(2, 1);\n    try std.testing.expectEqual(@as(u32, 2), map.count());\n\n    try map.put(1, 99); // Update, not insert\n    try std.testing.expectEqual(@as(u32, 2), map.count());\n\n    try std.testing.expect(map.remove(1));\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try std.testing.expect(!map.remove(1)); // Already removed\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try std.testing.expect(!map.remove(999)); // Never existed\n    try std.testing.expectEqual(@as(u32, 1), map.count());\n\n    try std.testing.expect(map.remove(2));\n    try std.testing.expectEqual(@as(u32, 0), map.count());\n}\n"
  },
  {
    "path": "src/memory/slab.zig",
    "content": "//! Tiered Slab Allocator\n//!\n//! High-performance message buffer allocator with O(1) alloc/free.\n//! Uses tiered slabs with embedded free lists for zero-overhead tracking.\n//! Falls back to provided allocator for oversized allocations.\n\nconst std = @import(\"std\");\nconst builtin = @import(\"builtin\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\nconst defaults = @import(\"../defaults.zig\");\n\n/// Configuration for tiered slab allocator (derived from defaults.Memory).\npub const Config = struct {\n    pub const TIER_COUNT = defaults.Memory.tier_count;\n\n    /// Slice sizes per tier (power-of-2 for efficient selection).\n    pub const tier_sizes = defaults.Memory.tier_sizes;\n\n    /// Slice counts per tier (derived from queue_size).\n    pub const tier_counts = defaults.Memory.tier_counts;\n\n    /// Maximum slice size handled by slab (larger uses fallback).\n    pub const max_slice_size: usize = defaults.Memory.max_slice_size;\n\n    /// Total pre-allocated memory.\n    pub const total_memory: usize = defaults.Memory.total_memory;\n};\n\n/// Single-tier slab with embedded free list.\n///\n/// Each free slice stores the index of the next free slice in its first\n/// 4 bytes. This eliminates separate tracking overhead.\npub const Slab = struct {\n    memory: []align(4096) u8,\n    slice_size: u32,\n    slice_count: u32,\n    free_head: u32,\n    alloc_count: u32,\n\n    const NONE: u32 = 0xFFFF_FFFF;\n\n    /// Initialize slab with page-aligned memory.\n    pub fn init(slice_size: u32, slice_count: u32) !Slab {\n        assert(slice_size >= 4);\n        assert(slice_count > 0);\n        assert(slice_size <= Config.max_slice_size);\n\n        const total = @as(usize, slice_size) * slice_count;\n\n        const raw = std.heap.page_allocator.alloc(\n            u8,\n            total,\n        ) catch return error.MmapFailed;\n        const memory: []align(4096) u8 = @alignCast(raw);\n\n        var slab = Slab{\n            .memory = @alignCast(memory),\n            .slice_size = slice_size,\n            .slice_count = slice_count,\n            .free_head = 0,\n            .alloc_count = 0,\n        };\n\n        var i: u32 = 0;\n        while (i < slice_count) : (i += 1) {\n            const slice = slab.getSliceByIndex(i);\n            const next: u32 = if (i + 1 < slice_count) i + 1 else NONE;\n            @as(*u32, @ptrCast(@alignCast(slice.ptr))).* = next;\n        }\n\n        return slab;\n    }\n\n    /// Release page-allocated memory.\n    pub fn deinit(self: *Slab) void {\n        std.heap.page_allocator.free(self.memory);\n        self.* = undefined;\n    }\n\n    /// O(1) allocation - pop from embedded free list.\n    pub inline fn alloc(self: *Slab) ?[]u8 {\n        if (self.free_head == NONE) return null;\n\n        const idx = self.free_head;\n        const slice = self.getSliceByIndex(idx);\n\n        self.free_head = @as(*u32, @ptrCast(@alignCast(slice.ptr))).*;\n        self.alloc_count += 1;\n\n        return slice;\n    }\n\n    /// O(1) deallocation - push to embedded free list.\n    /// Debug builds detect double-free by walking the free list.\n    pub inline fn free(self: *Slab, ptr: [*]u8) void {\n        const idx = self.ptrToIndex(ptr);\n        assert(idx < self.slice_count);\n\n        if (builtin.mode == .Debug) {\n            assert(!self.isInFreeList(idx));\n        }\n\n        @as(*u32, @ptrCast(@alignCast(ptr))).* = self.free_head;\n        self.free_head = idx;\n        self.alloc_count -= 1;\n    }\n\n    /// Debug helper: check if index is already in free list (O(n)).\n    fn isInFreeList(self: *Slab, target_idx: u32) bool {\n        var current = self.free_head;\n        while (current != NONE) {\n            if (current == target_idx) return true;\n            const slice = self.getSliceByIndex(current);\n            current = @as(*u32, @ptrCast(@alignCast(slice.ptr))).*;\n        }\n        return false;\n    }\n\n    inline fn getSliceByIndex(self: *Slab, idx: u32) []u8 {\n        const offset = @as(usize, idx) * self.slice_size;\n        return self.memory[offset..][0..self.slice_size];\n    }\n\n    inline fn ptrToIndex(self: *Slab, ptr: [*]u8) u32 {\n        const ptr_addr = @intFromPtr(ptr);\n        const mem_start = @intFromPtr(self.memory.ptr);\n        const mem_end = mem_start + self.memory.len;\n\n        assert(ptr_addr >= mem_start);\n        assert(ptr_addr < mem_end);\n\n        const offset = ptr_addr - mem_start;\n        assert(offset % self.slice_size == 0);\n\n        return @intCast(offset / self.slice_size);\n    }\n\n    /// Check if pointer belongs to this slab.\n    pub inline fn contains(self: *const Slab, ptr: [*]u8) bool {\n        const addr = @intFromPtr(ptr);\n        const base = @intFromPtr(self.memory.ptr);\n        return addr >= base and addr < base + self.memory.len;\n    }\n\n    /// Returns number of currently allocated slices.\n    pub fn getAllocCount(self: *const Slab) u32 {\n        return self.alloc_count;\n    }\n\n    /// Returns total capacity in slices.\n    pub fn getCapacity(self: *const Slab) u32 {\n        return self.slice_count;\n    }\n};\n\n/// Multi-tier slab allocator with fallback.\n///\n/// Selects appropriate tier based on requested size. Falls back to\n/// provided allocator for sizes exceeding max tier.\npub const TieredSlab = struct {\n    tiers: [Config.TIER_COUNT]Slab,\n    fallback: Allocator,\n    fallback_count: u32,\n\n    /// Initialize all tiers with mmap'd memory.\n    pub fn init(fallback_allocator: Allocator) !TieredSlab {\n        var ts: TieredSlab = undefined;\n        ts.fallback = fallback_allocator;\n        ts.fallback_count = 0;\n\n        var initialized: usize = 0;\n        errdefer {\n            for (ts.tiers[0..initialized]) |*tier| {\n                tier.deinit();\n            }\n        }\n\n        for (Config.tier_sizes, Config.tier_counts, 0..) |size, count, i| {\n            ts.tiers[i] = try Slab.init(size, count);\n            initialized += 1;\n        }\n\n        return ts;\n    }\n\n    /// Release all mmap'd memory.\n    pub fn deinit(self: *TieredSlab) void {\n        for (&self.tiers) |*tier| {\n            tier.deinit();\n        }\n    }\n\n    /// O(1) tier selection based on size.\n    inline fn selectTier(size: usize) ?usize {\n        if (size <= 256) return 0;\n        if (size <= 512) return 1;\n        if (size <= 1024) return 2;\n        if (size <= 4096) return 3;\n        if (size <= 16384) return 4;\n        return null;\n    }\n\n    /// Allocate from appropriate tier or fallback.\n    pub fn alloc(self: *TieredSlab, size: usize) ?[]u8 {\n        assert(size > 0);\n\n        if (selectTier(size)) |tier_idx| {\n            if (self.tiers[tier_idx].alloc()) |slice| {\n                return slice[0..size];\n            }\n        }\n\n        self.fallback_count += 1;\n        return self.fallback.alloc(u8, size) catch null;\n    }\n\n    /// Free to appropriate tier or fallback.\n    pub fn free(self: *TieredSlab, buf: []u8) void {\n        assert(buf.len > 0);\n\n        const ptr = buf.ptr;\n\n        inline for (&self.tiers) |*tier| {\n            if (tier.contains(ptr)) {\n                tier.free(ptr);\n                return;\n            }\n        }\n\n        self.fallback_count -= 1;\n        self.fallback.free(buf);\n    }\n\n    /// Check if pointer belongs to any slab tier.\n    pub fn containsPtr(self: *const TieredSlab, ptr: [*]u8) bool {\n        inline for (&self.tiers) |*tier| {\n            if (tier.contains(ptr)) return true;\n        }\n        return false;\n    }\n\n    /// Get diagnostic statistics.\n    pub fn getStats(self: *const TieredSlab) Stats {\n        var stats = Stats{};\n        for (self.tiers, 0..) |tier, i| {\n            stats.tier_alloc_counts[i] = tier.alloc_count;\n            stats.tier_capacities[i] = tier.slice_count;\n        }\n        stats.fallback_count = self.fallback_count;\n        return stats;\n    }\n\n    pub const Stats = struct {\n        tier_alloc_counts: [Config.TIER_COUNT]u32 = .{0} ** Config.TIER_COUNT,\n        tier_capacities: [Config.TIER_COUNT]u32 = .{0} ** Config.TIER_COUNT,\n        fallback_count: u32 = 0,\n\n        /// Total allocated across all tiers.\n        pub fn totalAllocated(self: Stats) u32 {\n            var total: u32 = 0;\n            for (self.tier_alloc_counts) |count| {\n                total += count;\n            }\n            return total + self.fallback_count;\n        }\n\n        /// Total capacity across all tiers.\n        pub fn totalCapacity(self: Stats) u32 {\n            var total: u32 = 0;\n            for (self.tier_capacities) |cap| {\n                total += cap;\n            }\n            return total;\n        }\n    };\n};\n\n/// Wrapper that implements std.mem.Allocator interface.\n///\n/// This allows TieredSlab to be used transparently with code expecting\n/// an Allocator, such as Message.deinit().\npub const SlabAllocator = struct {\n    slab: *TieredSlab,\n\n    /// Return std.mem.Allocator interface.\n    pub fn allocator(self: *SlabAllocator) Allocator {\n        return .{\n            .ptr = self,\n            .vtable = &vtable,\n        };\n    }\n\n    const vtable: Allocator.VTable = .{\n        .alloc = allocFn,\n        .resize = resizeFn,\n        .remap = remapFn,\n        .free = freeFn,\n    };\n\n    fn allocFn(\n        ctx: *anyopaque,\n        len: usize,\n        alignment: std.mem.Alignment,\n        ret_addr: usize,\n    ) ?[*]u8 {\n        _ = alignment;\n        _ = ret_addr;\n        const self: *SlabAllocator = @ptrCast(@alignCast(ctx));\n        const buf = self.slab.alloc(len) orelse return null;\n        return buf.ptr;\n    }\n\n    fn resizeFn(\n        ctx: *anyopaque,\n        buf: []u8,\n        alignment: std.mem.Alignment,\n        new_len: usize,\n        ret_addr: usize,\n    ) bool {\n        _ = ctx;\n        _ = alignment;\n        _ = ret_addr;\n        if (new_len <= buf.len) return true;\n        return false;\n    }\n\n    fn remapFn(\n        ctx: *anyopaque,\n        memory: []u8,\n        alignment: std.mem.Alignment,\n        new_len: usize,\n        ret_addr: usize,\n    ) ?[*]u8 {\n        _ = ctx;\n        _ = memory;\n        _ = alignment;\n        _ = new_len;\n        _ = ret_addr;\n        return null;\n    }\n\n    fn freeFn(\n        ctx: *anyopaque,\n        buf: []u8,\n        alignment: std.mem.Alignment,\n        ret_addr: usize,\n    ) void {\n        _ = alignment;\n        _ = ret_addr;\n        const self: *SlabAllocator = @ptrCast(@alignCast(ctx));\n        self.slab.free(buf);\n    }\n};\n\ntest \"Slab basic alloc/free\" {\n    var slab = try Slab.init(256, 16);\n    defer slab.deinit();\n\n    var ptrs: [16][]u8 = undefined;\n    for (&ptrs) |*p| {\n        p.* = slab.alloc() orelse unreachable;\n    }\n\n    try std.testing.expect(slab.alloc() == null);\n    try std.testing.expectEqual(@as(u32, 16), slab.getAllocCount());\n\n    for (ptrs) |p| {\n        slab.free(p.ptr);\n    }\n\n    try std.testing.expectEqual(@as(u32, 0), slab.getAllocCount());\n\n    const p = slab.alloc() orelse unreachable;\n    try std.testing.expect(p.len == 256);\n}\n\ntest \"TieredSlab tier selection\" {\n    const fallback = std.testing.allocator;\n    var ts = try TieredSlab.init(fallback);\n    defer ts.deinit();\n\n    const small = ts.alloc(100) orelse unreachable;\n    try std.testing.expect(ts.containsPtr(small.ptr));\n\n    const medium = ts.alloc(800) orelse unreachable;\n    try std.testing.expect(ts.containsPtr(medium.ptr));\n\n    const large = ts.alloc(20000) orelse unreachable;\n    try std.testing.expect(!ts.containsPtr(large.ptr));\n\n    ts.free(small);\n    ts.free(medium);\n    ts.free(large);\n\n    const stats = ts.getStats();\n    try std.testing.expectEqual(@as(u32, 0), stats.totalAllocated());\n}\n\ntest \"SlabAllocator interface\" {\n    const fallback = std.testing.allocator;\n    var ts = try TieredSlab.init(fallback);\n    defer ts.deinit();\n\n    var sa = SlabAllocator{ .slab = &ts };\n    const alloc = sa.allocator();\n\n    const buf = try alloc.alloc(u8, 200);\n    try std.testing.expect(buf.len == 200);\n\n    alloc.free(buf);\n}\n"
  },
  {
    "path": "src/memory.zig",
    "content": "//! Memory Management\n//!\n//! Provides SidMap for O(1) subscription routing and TieredSlab for\n//! high-performance message buffer allocation.\n\npub const sidmap = @import(\"memory/sidmap.zig\");\npub const SidMap = sidmap.SidMap;\n\npub const slab = @import(\"memory/slab.zig\");\npub const TieredSlab = slab.TieredSlab;\npub const SlabConfig = slab.Config;\n\ntest {\n    _ = sidmap;\n    _ = slab;\n}\n"
  },
  {
    "path": "src/micro/Service.zig",
    "content": "const std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst pubsub = @import(\"../pubsub.zig\");\nconst endpoint_mod = @import(\"endpoint.zig\");\nconst protocol = @import(\"protocol.zig\");\nconst validation = @import(\"validation.zig\");\nconst json_util = @import(\"json_util.zig\");\nconst timeutil = @import(\"timeutil.zig\");\n\npub const Error = anyerror;\n\npub const Config = struct {\n    name: []const u8,\n    version: []const u8,\n    description: ?[]const u8 = null,\n    metadata: []const protocol.MetadataPair = &.{},\n    service_prefix: []const u8 = \"$SRV\",\n    queue_policy: endpoint_mod.QueuePolicy = .{ .queue = \"q\" },\n    endpoint: ?endpoint_mod.EndpointConfig = null,\n};\n\npub const Service = @This();\n\nclient: *Client,\nallocator: std.mem.Allocator,\nname: []const u8,\nversion: []const u8,\ndescription: ?[]const u8,\nid: []const u8,\nservice_prefix: []const u8,\nstarted: []const u8,\nmetadata: []protocol.MetadataPair,\nqueue_policy: endpoint_mod.QueuePolicy,\nendpoints: std.ArrayList(*endpoint_mod.Endpoint) = .empty,\ngroup_prefixes: std.ArrayList([]u8) = .empty,\ngroup_queues: std.ArrayList([]u8) = .empty,\nmonitor_subs: std.ArrayList(*Client.Subscription) = .empty,\nmutex: std.Io.Mutex = .init,\nin_flight: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),\nstopping: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),\nstopped_flag: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),\nstop_error: ?anyerror = null,\n\npub fn addService(client: *Client, config: Config) !*Service {\n    try validation.validateName(config.name);\n    try validation.validateVersion(config.version);\n    try validation.validatePrefix(config.service_prefix);\n\n    const service = try client.allocator.create(Service);\n    errdefer client.allocator.destroy(service);\n\n    const name = try client.allocator.dupe(u8, config.name);\n    errdefer client.allocator.free(name);\n    const version = try client.allocator.dupe(u8, config.version);\n    errdefer client.allocator.free(version);\n    const description = if (config.description) |d|\n        try client.allocator.dupe(u8, d)\n    else\n        null;\n    errdefer if (description) |d| client.allocator.free(d);\n    const id = try generateId(client.allocator, client.io);\n    errdefer client.allocator.free(id);\n    const service_prefix = try client.allocator.dupe(u8, config.service_prefix);\n    errdefer client.allocator.free(service_prefix);\n    const metadata = try endpoint_mod.dupMetadata(client.allocator, config.metadata);\n    errdefer endpoint_mod.freeMetadata(client.allocator, metadata);\n    const queue_policy = try dupQueuePolicy(client.allocator, config.queue_policy);\n    errdefer freeQueuePolicy(client.allocator, queue_policy);\n\n    service.* = .{\n        .client = client,\n        .allocator = client.allocator,\n        .name = name,\n        .version = version,\n        .description = description,\n        .id = id,\n        .service_prefix = service_prefix,\n        .started = undefined,\n        .metadata = metadata,\n        .queue_policy = queue_policy,\n    };\n\n    var started_buf: [32]u8 = undefined;\n    const started = try timeutil.nowRfc3339(client.io, &started_buf);\n    service.started = try client.allocator.dupe(u8, started);\n    errdefer client.allocator.free(service.started);\n    errdefer service.cleanupRuntimeResources();\n\n    try service.initMonitorSubs();\n\n    if (config.endpoint) |ep_cfg| {\n        _ = try service.addEndpoint(ep_cfg);\n    }\n\n    // Make service discovery and endpoint subscriptions visible to the\n    // server before returning so immediate requests do not race setup.\n    try client.flush(5 * std.time.ns_per_s);\n\n    return service;\n}\n\npub fn addEndpoint(self: *Service, cfg: endpoint_mod.EndpointConfig) !*endpoint_mod.Endpoint {\n    return self.addEndpointWithPrefix(\"\", .inherit, cfg);\n}\n\npub fn addGroup(self: *Service, prefix: []const u8) !endpoint_mod.Group {\n    try validation.validateGroup(prefix);\n    const full = try self.allocGroupPrefix(\"\", prefix);\n    return .{\n        .service = self,\n        .prefix = full,\n        .queue_policy = .inherit,\n    };\n}\n\npub fn addGroupWithQueue(\n    self: *Service,\n    prefix: []const u8,\n    queue: []const u8,\n) !endpoint_mod.Group {\n    try validation.validateGroup(prefix);\n    try pubsub.validateQueueGroup(queue);\n    const full = try self.allocGroupPrefix(\"\", prefix);\n    const owned_queue = try self.allocGroupQueue(queue);\n    return .{\n        .service = self,\n        .prefix = full,\n        .queue_policy = .{ .queue = owned_queue },\n    };\n}\n\npub fn info(self: *Service, allocator: std.mem.Allocator) !protocol.Info {\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n\n    const endpoints = try allocator.alloc(protocol.EndpointInfo, self.endpoints.items.len);\n    for (self.endpoints.items, 0..) |ep, i| {\n        endpoints[i] = .{\n            .name = ep.name,\n            .subject = ep.subject,\n            .queue_group = ep.queue_group,\n            .metadata = if (ep.metadata.len == 0) null else ep.metadata,\n        };\n    }\n    return .{\n        .name = self.name,\n        .id = self.id,\n        .version = self.version,\n        .description = self.description,\n        .metadata = if (self.metadata.len == 0) null else self.metadata,\n        .endpoints = endpoints,\n    };\n}\n\npub fn stats(self: *Service, allocator: std.mem.Allocator) !protocol.StatsResponse {\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n\n    const endpoints = try allocator.alloc(protocol.EndpointStatsJson, self.endpoints.items.len);\n    for (self.endpoints.items, 0..) |ep, i| {\n        const snap = ep.stats.snapshot();\n        endpoints[i] = .{\n            .name = ep.name,\n            .subject = ep.subject,\n            .queue_group = ep.queue_group,\n            .metadata = if (ep.metadata.len == 0) null else ep.metadata,\n            .num_requests = snap.num_requests,\n            .num_errors = snap.num_errors,\n            .last_error = snap.last_error,\n            .processing_time = snap.processing_time,\n            .average_processing_time = snap.average_processing_time,\n        };\n    }\n    return .{\n        .name = self.name,\n        .id = self.id,\n        .version = self.version,\n        .started = self.started,\n        .metadata = if (self.metadata.len == 0) null else self.metadata,\n        .endpoints = endpoints,\n    };\n}\n\npub fn reset(self: *Service) void {\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n    for (self.endpoints.items) |ep| {\n        ep.stats.reset();\n    }\n}\n\npub fn stop(self: *Service, stop_error: ?anyerror) !void {\n    if (self.stopped_flag.load(.acquire)) return;\n    if (self.stopping.swap(true, .acq_rel)) {\n        return self.waitStopped();\n    }\n    self.stop_error = stop_error;\n\n    self.mutex.lockUncancelable(self.client.io);\n    for (self.monitor_subs.items) |sub| {\n        sub.deinit();\n    }\n    self.monitor_subs.clearRetainingCapacity();\n\n    for (self.endpoints.items) |ep| {\n        ep.sub.drain() catch {};\n    }\n    self.mutex.unlock(self.client.io);\n\n    // Ensure UNSUB frames reach the server before stop() returns so no\n    // new requests are accepted after shutdown completes.\n    self.client.flush(5 * std.time.ns_per_s) catch {};\n\n    const drain_timeout_ms = self.client.options.drain_timeout_ms;\n    var drain_err: ?anyerror = null;\n    for (self.endpoints.items) |ep| {\n        ep.sub.waitDrained(drain_timeout_ms) catch |err| {\n            if (drain_err == null) drain_err = err;\n        };\n    }\n\n    const start = std.Io.Timestamp.now(self.client.io, .awake);\n    const timeout_ns =\n        @as(i128, drain_timeout_ms) * std.time.ns_per_ms;\n    var spins: u32 = 0;\n    while (self.in_flight.load(.acquire) != 0) {\n        const now = std.Io.Timestamp.now(self.client.io, .awake);\n        if (now.nanoseconds - start.nanoseconds >= timeout_ns) {\n            drain_err = drain_err orelse error.Timeout;\n            break;\n        }\n        spins += 1;\n        if (spins < 100) {\n            std.atomic.spinLoopHint();\n        } else {\n            self.client.io.sleep(.fromNanoseconds(0), .awake) catch {};\n            spins = 0;\n        }\n    }\n\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n    for (self.endpoints.items) |ep| {\n        ep.sub.deinit();\n    }\n\n    // `deinit()` sends the final unsubscribe/cancel path for callback\n    // subscriptions. Confirm it has reached the server before reporting\n    // the service as stopped.\n    self.client.flush(5 * std.time.ns_per_s) catch {};\n\n    self.stopped_flag.store(true, .release);\n    if (drain_err) |err| return err;\n}\n\npub fn waitStopped(self: *Service) !void {\n    while (!self.stopped_flag.load(.acquire)) {\n        self.client.io.sleep(.fromNanoseconds(0), .awake) catch {};\n    }\n    if (self.stop_error) |err| return err;\n}\n\npub fn stopped(self: *const Service) bool {\n    return self.stopped_flag.load(.acquire);\n}\n\nfn cleanupRuntimeResources(self: *Service) void {\n    for (self.monitor_subs.items) |sub| {\n        sub.deinit();\n    }\n    self.monitor_subs.deinit(self.allocator);\n\n    for (self.endpoints.items) |ep| {\n        ep.sub.deinit();\n        ep.deinit(self.allocator);\n    }\n    self.endpoints.deinit(self.allocator);\n\n    for (self.group_prefixes.items) |prefix| self.allocator.free(prefix);\n    self.group_prefixes.deinit(self.allocator);\n\n    for (self.group_queues.items) |queue| self.allocator.free(queue);\n    self.group_queues.deinit(self.allocator);\n}\n\npub fn deinit(self: *Service) void {\n    self.stop(null) catch {};\n\n    for (self.endpoints.items) |ep| ep.deinit(self.allocator);\n    self.endpoints.deinit(self.allocator);\n\n    for (self.group_prefixes.items) |prefix| self.allocator.free(prefix);\n    self.group_prefixes.deinit(self.allocator);\n    for (self.group_queues.items) |queue| self.allocator.free(queue);\n    self.group_queues.deinit(self.allocator);\n    self.monitor_subs.deinit(self.allocator);\n\n    endpoint_mod.freeMetadata(self.allocator, self.metadata);\n    self.allocator.free(self.name);\n    self.allocator.free(self.version);\n    if (self.description) |d| self.allocator.free(d);\n    self.allocator.free(self.id);\n    self.allocator.free(self.service_prefix);\n    self.allocator.free(self.started);\n    freeQueuePolicy(self.allocator, self.queue_policy);\n    self.allocator.destroy(self);\n}\n\npub fn addEndpointWithPrefix(\n    self: *Service,\n    prefix: []const u8,\n    inherited_policy: endpoint_mod.QueuePolicy,\n    cfg: endpoint_mod.EndpointConfig,\n) !*endpoint_mod.Endpoint {\n    if (self.stopping.load(.acquire)) return error.InvalidState;\n\n    const full_subject = try joinSubject(self.allocator, prefix, cfg.subject);\n    errdefer self.allocator.free(full_subject);\n    try pubsub.validatePublish(full_subject);\n\n    const name = try self.allocator.dupe(u8, cfg.name orelse cfg.subject);\n    errdefer self.allocator.free(name);\n    const queue_group = try resolveQueuePolicy(self.allocator, cfg.queue_policy, inherited_policy, self.queue_policy);\n    errdefer if (queue_group) |q| self.allocator.free(q);\n    const metadata = try endpoint_mod.dupMetadata(self.allocator, cfg.metadata);\n    errdefer endpoint_mod.freeMetadata(self.allocator, metadata);\n\n    const ep = try self.allocator.create(endpoint_mod.Endpoint);\n    errdefer self.allocator.destroy(ep);\n\n    ep.* = .{\n        .service = self,\n        .sub = undefined,\n        .name = name,\n        .subject = full_subject,\n        .queue_group = queue_group,\n        .metadata = metadata,\n        .handler = cfg.handler,\n    };\n    ep.callback = .{ .endpoint = ep };\n\n    ep.sub = if (queue_group) |q|\n        try self.client.queueSubscribe(full_subject, q, Client.MsgHandler.init(endpoint_mod.EndpointCallback, &ep.callback))\n    else\n        try self.client.subscribe(full_subject, Client.MsgHandler.init(endpoint_mod.EndpointCallback, &ep.callback));\n    errdefer ep.sub.deinit();\n\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n    try self.endpoints.append(self.allocator, ep);\n\n    // Make the new endpoint visible to the server before returning.\n    self.client.flush(5 * std.time.ns_per_s) catch {};\n    return ep;\n}\n\npub fn allocGroupPrefix(self: *Service, base: []const u8, next: []const u8) ![]const u8 {\n    const full = try joinSubject(self.allocator, base, next);\n    errdefer self.allocator.free(full);\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n    try self.group_prefixes.append(self.allocator, full);\n    return full;\n}\n\npub fn allocGroupQueue(self: *Service, queue: []const u8) ![]const u8 {\n    try pubsub.validateQueueGroup(queue);\n    const owned = try self.allocator.dupe(u8, queue);\n    errdefer self.allocator.free(owned);\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n    try self.group_queues.append(self.allocator, owned);\n    return owned;\n}\n\npub fn onMessage(self: *Service, msg: *const Client.Message) void {\n    const reply_to = msg.reply_to orelse return;\n    if (self.stopped_flag.load(.acquire)) return;\n\n    var payload: ?[]u8 = null;\n    defer if (payload) |buf| self.allocator.free(buf);\n\n    const subject = msg.subject;\n    const prefix = self.service_prefix;\n    if (!std.mem.startsWith(u8, subject, prefix)) return;\n    if (subject.len <= prefix.len or subject[prefix.len] != '.') return;\n    const rest = subject[prefix.len + 1 ..];\n\n    if (matchVerb(rest, \"PING\")) {\n        const ping = protocol.Ping{\n            .name = self.name,\n            .id = self.id,\n            .version = self.version,\n            .metadata = if (self.metadata.len == 0) null else self.metadata,\n        };\n        payload = json_util.jsonStringify(self.allocator, ping) catch return;\n    } else if (matchVerb(rest, \"INFO\")) {\n        var info_resp = self.info(self.allocator) catch return;\n        defer info_resp.deinit(self.allocator);\n        payload = json_util.jsonStringify(self.allocator, info_resp) catch return;\n    } else if (matchVerb(rest, \"STATS\")) {\n        var stats_resp = self.stats(self.allocator) catch return;\n        defer stats_resp.deinit(self.allocator);\n        payload = json_util.jsonStringify(self.allocator, stats_resp) catch return;\n    } else return;\n\n    self.client.publish(reply_to, payload.?) catch {};\n}\n\nfn initMonitorSubs(self: *Service) !void {\n    const prefixes = [_][]const u8{\n        \"PING\", \"INFO\", \"STATS\",\n    };\n    for (prefixes) |verb| {\n        try self.subscribeMonitor(verb);\n        const name_subject = try std.fmt.allocPrint(\n            self.allocator,\n            \"{s}.{s}\",\n            .{ verb, self.name },\n        );\n        defer self.allocator.free(name_subject);\n        try self.subscribeMonitor(name_subject);\n\n        const id_subject = try std.fmt.allocPrint(\n            self.allocator,\n            \"{s}.{s}.{s}\",\n            .{ verb, self.name, self.id },\n        );\n        defer self.allocator.free(id_subject);\n        try self.subscribeMonitor(id_subject);\n    }\n}\n\nfn subscribeMonitor(self: *Service, suffix: []const u8) !void {\n    const subject = try std.fmt.allocPrint(\n        self.allocator,\n        \"{s}.{s}\",\n        .{ self.service_prefix, suffix },\n    );\n    defer self.allocator.free(subject);\n    const sub = try self.client.subscribe(subject, Client.MsgHandler.init(Service, self));\n    errdefer sub.deinit();\n\n    self.mutex.lockUncancelable(self.client.io);\n    defer self.mutex.unlock(self.client.io);\n    try self.monitor_subs.append(self.allocator, sub);\n}\n\nfn joinSubject(\n    allocator: std.mem.Allocator,\n    prefix: []const u8,\n    leaf: []const u8,\n) ![]u8 {\n    if (prefix.len == 0) return allocator.dupe(u8, leaf);\n    return std.fmt.allocPrint(allocator, \"{s}.{s}\", .{ prefix, leaf });\n}\n\nfn generateId(allocator: std.mem.Allocator, io: std.Io) ![]u8 {\n    const alphabet = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n    const out = try allocator.alloc(u8, 16);\n    var random: [16]u8 = undefined;\n    io.random(&random);\n    for (out, random) |*dst, src| {\n        dst.* = alphabet[@mod(src, alphabet.len)];\n    }\n    return out;\n}\n\nfn dupQueuePolicy(\n    allocator: std.mem.Allocator,\n    policy: endpoint_mod.QueuePolicy,\n) !endpoint_mod.QueuePolicy {\n    return switch (policy) {\n        .inherit => .inherit,\n        .no_queue => .no_queue,\n        .queue => |q| .{ .queue = try allocator.dupe(u8, q) },\n    };\n}\n\nfn freeQueuePolicy(\n    allocator: std.mem.Allocator,\n    policy: endpoint_mod.QueuePolicy,\n) void {\n    switch (policy) {\n        .queue => |q| allocator.free(q),\n        else => {},\n    }\n}\n\nfn resolveQueuePolicy(\n    allocator: std.mem.Allocator,\n    endpoint_policy: endpoint_mod.QueuePolicy,\n    group_policy: endpoint_mod.QueuePolicy,\n    service_policy: endpoint_mod.QueuePolicy,\n) !?[]u8 {\n    const resolved = switch (endpoint_policy) {\n        .inherit => switch (group_policy) {\n            .inherit => service_policy,\n            else => group_policy,\n        },\n        else => endpoint_policy,\n    };\n\n    return switch (resolved) {\n        .inherit => try allocator.dupe(u8, \"q\"),\n        .no_queue => null,\n        .queue => |q| try allocator.dupe(u8, q),\n    };\n}\n\nfn matchVerb(rest: []const u8, verb: []const u8) bool {\n    if (!std.mem.startsWith(u8, rest, verb)) return false;\n    return rest.len == verb.len or rest[verb.len] == '.';\n}\n"
  },
  {
    "path": "src/micro/endpoint.zig",
    "content": "const std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst protocol = @import(\"protocol.zig\");\nconst request_mod = @import(\"request.zig\");\nconst stats_mod = @import(\"stats.zig\");\nconst validation = @import(\"validation.zig\");\nconst pubsub = @import(\"../pubsub.zig\");\n\npub const QueuePolicy = union(enum) {\n    inherit,\n    queue: []const u8,\n    no_queue,\n};\n\npub const EndpointConfig = struct {\n    subject: []const u8,\n    name: ?[]const u8 = null,\n    handler: request_mod.Handler,\n    metadata: []const protocol.MetadataPair = &.{},\n    queue_policy: QueuePolicy = .inherit,\n};\n\npub const Endpoint = struct {\n    service: *anyopaque,\n    sub: *Client.Subscription,\n    name: []const u8,\n    subject: []const u8,\n    queue_group: ?[]const u8,\n    metadata: []protocol.MetadataPair,\n    handler: request_mod.Handler,\n    stats: stats_mod.EndpointStats = .{},\n    callback: EndpointCallback = undefined,\n\n    pub fn deinit(self: *Endpoint, allocator: std.mem.Allocator) void {\n        allocator.free(self.name);\n        allocator.free(self.subject);\n        freeMetadata(allocator, self.metadata);\n        if (self.queue_group) |q| allocator.free(q);\n        allocator.destroy(self);\n    }\n};\n\npub const Group = struct {\n    service: *anyopaque,\n    prefix: []const u8,\n    queue_policy: QueuePolicy,\n\n    pub fn addEndpoint(self: *Group, cfg: EndpointConfig) !*Endpoint {\n        const service = servicePtr(self.service);\n        return service.addEndpointWithPrefix(self.prefix, self.queue_policy, cfg);\n    }\n\n    pub fn group(self: *Group, prefix: []const u8) !Group {\n        const service = servicePtr(self.service);\n        try validation.validateGroup(prefix);\n        const full = try service.allocGroupPrefix(self.prefix, prefix);\n        return .{\n            .service = self.service,\n            .prefix = full,\n            .queue_policy = self.queue_policy,\n        };\n    }\n\n    pub fn groupWithQueue(self: *Group, prefix: []const u8, queue: []const u8) !Group {\n        const service = servicePtr(self.service);\n        try validation.validateGroup(prefix);\n        try pubsub.validateQueueGroup(queue);\n        const full = try service.allocGroupPrefix(self.prefix, prefix);\n        const owned_queue = try service.allocGroupQueue(queue);\n        return .{\n            .service = self.service,\n            .prefix = full,\n            .queue_policy = .{ .queue = owned_queue },\n        };\n    }\n};\n\npub const EndpointCallback = struct {\n    endpoint: *Endpoint,\n\n    pub fn onMessage(self: *@This(), msg: *const Client.Message) void {\n        const service = servicePtr(self.endpoint.service);\n\n        // Belt-and-suspenders: skip dispatch once stop() has begun.\n        // sub.drain() + flush already prevent new messages from reaching\n        // here, but this guard ensures any racing in-flight delivery does\n        // not re-enter the user handler after stop() observed in_flight==0.\n        if (service.stopping.load(.acquire)) return;\n\n        const start = std.Io.Timestamp.now(service.client.io, .awake);\n        _ = service.in_flight.fetchAdd(1, .acq_rel);\n        defer _ = service.in_flight.fetchSub(1, .acq_rel);\n\n        var req = request_mod.Request{\n            .client = service.client,\n            .msg = msg,\n        };\n        self.endpoint.handler.dispatch(&req);\n\n        const end = std.Io.Timestamp.now(service.client.io, .awake);\n        const elapsed: u64 = @intCast(end.nanoseconds - start.nanoseconds);\n        if (req.errored) {\n            self.endpoint.stats.recordError(\n                elapsed,\n                req.error_code,\n                req.errorDescription(),\n            );\n        } else {\n            self.endpoint.stats.recordSuccess(elapsed);\n        }\n    }\n};\n\npub fn dupMetadata(\n    allocator: std.mem.Allocator,\n    metadata: []const protocol.MetadataPair,\n) ![]protocol.MetadataPair {\n    const out = try allocator.alloc(protocol.MetadataPair, metadata.len);\n    errdefer allocator.free(out);\n    for (metadata, 0..) |pair, i| {\n        out[i].key = try allocator.dupe(u8, pair.key);\n        errdefer allocator.free(out[i].key);\n        errdefer {\n            for (out[0..i]) |prev| {\n                allocator.free(prev.key);\n                allocator.free(prev.value);\n            }\n        }\n        out[i].value = try allocator.dupe(u8, pair.value);\n    }\n    return out;\n}\n\npub fn freeMetadata(\n    allocator: std.mem.Allocator,\n    metadata: []const protocol.MetadataPair,\n) void {\n    for (metadata) |pair| {\n        allocator.free(pair.key);\n        allocator.free(pair.value);\n    }\n    if (metadata.len > 0) allocator.free(metadata);\n}\n\nfn servicePtr(ptr: *anyopaque) *@import(\"Service.zig\").Service {\n    return @ptrCast(@alignCast(ptr));\n}\n\ntest \"dupMetadata frees current key if value allocation fails\" {\n    const pairs = [_]protocol.MetadataPair{\n        .{ .key = \"role\", .value = \"primary\" },\n    };\n\n    var failing = std.testing.FailingAllocator.init(\n        std.testing.allocator,\n        .{ .fail_index = 2 },\n    );\n\n    try std.testing.expectError(\n        error.OutOfMemory,\n        dupMetadata(failing.allocator(), &pairs),\n    );\n}\n"
  },
  {
    "path": "src/micro/json_util.zig",
    "content": "const std = @import(\"std\");\n\nconst json_stringify_opts: std.json.Stringify.Options = .{\n    .emit_null_optional_fields = false,\n};\n\nconst json_parse_opts: std.json.ParseOptions = .{\n    .ignore_unknown_fields = true,\n};\n\npub fn jsonStringify(\n    allocator: std.mem.Allocator,\n    value: anytype,\n) error{OutOfMemory}![]u8 {\n    return std.json.Stringify.valueAlloc(\n        allocator,\n        value,\n        json_stringify_opts,\n    );\n}\n\npub fn jsonParse(\n    comptime T: type,\n    allocator: std.mem.Allocator,\n    data: []const u8,\n) std.json.ParseError(std.json.Scanner)!std.json.Parsed(T) {\n    return std.json.parseFromSlice(\n        T,\n        allocator,\n        data,\n        json_parse_opts,\n    );\n}\n"
  },
  {
    "path": "src/micro/protocol.zig",
    "content": "const std = @import(\"std\");\n\npub const Type = struct {\n    pub const ping = \"io.nats.micro.v1.ping_response\";\n    pub const info = \"io.nats.micro.v1.info_response\";\n    pub const stats = \"io.nats.micro.v1.stats_response\";\n};\n\npub const MetadataPair = struct {\n    key: []const u8,\n    value: []const u8,\n};\n\npub const Error = struct {\n    code: u16,\n    description: []const u8,\n};\n\npub const Ping = struct {\n    type: []const u8 = Type.ping,\n    name: []const u8,\n    id: []const u8,\n    version: []const u8,\n    metadata: ?[]const MetadataPair = null,\n};\n\npub const EndpointInfo = struct {\n    name: []const u8,\n    subject: []const u8,\n    queue_group: ?[]const u8 = null,\n    metadata: ?[]const MetadataPair = null,\n};\n\npub const Info = struct {\n    type: []const u8 = Type.info,\n    name: []const u8,\n    id: []const u8,\n    version: []const u8,\n    description: ?[]const u8 = null,\n    metadata: ?[]const MetadataPair = null,\n    endpoints: []const EndpointInfo = &.{},\n\n    pub fn deinit(self: *Info, allocator: std.mem.Allocator) void {\n        if (self.endpoints.len > 0) allocator.free(self.endpoints);\n        self.endpoints = &.{};\n    }\n};\n\npub const EndpointStatsJson = struct {\n    name: []const u8,\n    subject: []const u8,\n    queue_group: ?[]const u8 = null,\n    metadata: ?[]const MetadataPair = null,\n    num_requests: u64,\n    num_errors: u64,\n    last_error: ?Error = null,\n    processing_time: u64,\n    average_processing_time: u64,\n};\n\npub const StatsResponse = struct {\n    type: []const u8 = Type.stats,\n    name: []const u8,\n    id: []const u8,\n    version: []const u8,\n    started: []const u8,\n    metadata: ?[]const MetadataPair = null,\n    endpoints: []const EndpointStatsJson = &.{},\n\n    pub fn deinit(self: *StatsResponse, allocator: std.mem.Allocator) void {\n        if (self.endpoints.len > 0) allocator.free(self.endpoints);\n        self.endpoints = &.{};\n    }\n};\n"
  },
  {
    "path": "src/micro/request.zig",
    "content": "const std = @import(\"std\");\nconst Client = @import(\"../Client.zig\");\nconst headers_mod = @import(\"../protocol/headers.zig\");\nconst json_util = @import(\"json_util.zig\");\n\npub const HandlerFn = *const fn (*Request) void;\n\npub const Request = struct {\n    client: *Client,\n    msg: *const Client.Message,\n    errored: bool = false,\n    error_code: u16 = 0,\n    error_desc_len: usize = 0,\n    error_desc_buf: [128]u8 = undefined,\n\n    pub fn data(self: *const Request) []const u8 {\n        return self.msg.data;\n    }\n\n    pub fn subject(self: *const Request) []const u8 {\n        return self.msg.subject;\n    }\n\n    pub fn reply(self: *const Request) ?[]const u8 {\n        return self.msg.reply_to;\n    }\n\n    pub fn headers(self: *const Request) ?[]const u8 {\n        return self.msg.headers;\n    }\n\n    pub fn respond(self: *Request, payload: []const u8) !void {\n        const reply_to = self.msg.reply_to orelse return error.NoReplyTo;\n        try self.client.publish(reply_to, payload);\n    }\n\n    pub fn respondJson(self: *Request, value: anytype) !void {\n        const payload = try json_util.jsonStringify(self.client.allocator, value);\n        defer self.client.allocator.free(payload);\n        try self.respond(payload);\n    }\n\n    pub fn respondError(\n        self: *Request,\n        code: u16,\n        description: []const u8,\n        payload: []const u8,\n    ) !void {\n        const reply_to = self.msg.reply_to orelse return error.NoReplyTo;\n        var code_buf: [16]u8 = undefined;\n        const code_str = try std.fmt.bufPrint(&code_buf, \"{d}\", .{code});\n        const hdrs = [_]headers_mod.Entry{\n            .{ .key = \"Nats-Service-Error\", .value = description },\n            .{ .key = \"Nats-Service-Error-Code\", .value = code_str },\n        };\n        try self.client.publishWithHeaders(reply_to, &hdrs, payload);\n        self.errored = true;\n        self.error_code = code;\n        self.error_desc_len = @min(description.len, self.error_desc_buf.len);\n        @memcpy(\n            self.error_desc_buf[0..self.error_desc_len],\n            description[0..self.error_desc_len],\n        );\n    }\n\n    pub fn errorDescription(self: *const Request) []const u8 {\n        return self.error_desc_buf[0..self.error_desc_len];\n    }\n};\n\npub const Handler = struct {\n    impl: Impl,\n\n    pub const Impl = union(enum) {\n        vtable: VTableImpl,\n        bare_fn: HandlerFn,\n    };\n\n    pub const VTableImpl = struct {\n        ptr: *anyopaque,\n        call: *const fn (*anyopaque, *Request) void,\n    };\n\n    pub fn init(comptime T: type, ptr: *T) Handler {\n        const gen = struct {\n            fn call(p: *anyopaque, req: *Request) void {\n                const self: *T = @ptrCast(@alignCast(p));\n                self.onRequest(req);\n            }\n        };\n        return .{ .impl = .{ .vtable = .{\n            .ptr = ptr,\n            .call = gen.call,\n        } } };\n    }\n\n    pub fn fromFn(f: HandlerFn) Handler {\n        return .{ .impl = .{ .bare_fn = f } };\n    }\n\n    pub fn dispatch(self: Handler, req: *Request) void {\n        switch (self.impl) {\n            .vtable => |v| v.call(v.ptr, req),\n            .bare_fn => |f| f(req),\n        }\n    }\n};\n"
  },
  {
    "path": "src/micro/stats.zig",
    "content": "const std = @import(\"std\");\nconst protocol = @import(\"protocol.zig\");\nconst SpinLock = @import(\"../sync/spin_lock.zig\").SpinLock;\n\npub const EndpointStats = struct {\n    num_requests: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),\n    num_errors: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),\n    processing_time: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),\n    last_error_code: std.atomic.Value(u16) = std.atomic.Value(u16).init(0),\n    last_error_lock: SpinLock = .{},\n    last_error_len: usize = 0,\n    last_error_desc: [128]u8 = undefined,\n\n    pub fn recordSuccess(self: *EndpointStats, elapsed_ns: u64) void {\n        _ = self.num_requests.fetchAdd(1, .monotonic);\n        _ = self.processing_time.fetchAdd(elapsed_ns, .monotonic);\n    }\n\n    pub fn recordError(\n        self: *EndpointStats,\n        elapsed_ns: u64,\n        code: u16,\n        description: []const u8,\n    ) void {\n        _ = self.num_requests.fetchAdd(1, .monotonic);\n        _ = self.num_errors.fetchAdd(1, .monotonic);\n        _ = self.processing_time.fetchAdd(elapsed_ns, .monotonic);\n        self.last_error_lock.lock();\n        defer self.last_error_lock.unlock();\n\n        const copy_len = @min(description.len, self.last_error_desc.len);\n        @memcpy(self.last_error_desc[0..copy_len], description[0..copy_len]);\n        self.last_error_len = copy_len;\n        self.last_error_code.store(code, .release);\n    }\n\n    pub fn reset(self: *EndpointStats) void {\n        self.num_requests.store(0, .release);\n        self.num_errors.store(0, .release);\n        self.processing_time.store(0, .release);\n        self.last_error_lock.lock();\n        defer self.last_error_lock.unlock();\n        self.last_error_len = 0;\n        self.last_error_code.store(0, .release);\n    }\n\n    pub fn snapshot(\n        self: *EndpointStats,\n    ) struct {\n        num_requests: u64,\n        num_errors: u64,\n        processing_time: u64,\n        average_processing_time: u64,\n        last_error: ?protocol.Error,\n    } {\n        const num_requests = self.num_requests.load(.acquire);\n        const num_errors = self.num_errors.load(.acquire);\n        const processing_time = self.processing_time.load(.acquire);\n        const average_processing_time = if (num_requests == 0)\n            0\n        else\n            @divTrunc(processing_time, num_requests);\n\n        self.last_error_lock.lock();\n        defer self.last_error_lock.unlock();\n\n        const last_error = if (self.last_error_len == 0)\n            null\n        else\n            protocol.Error{\n                .code = self.last_error_code.load(.acquire),\n                .description = self.last_error_desc[0..self.last_error_len],\n            };\n\n        return .{\n            .num_requests = num_requests,\n            .num_errors = num_errors,\n            .processing_time = processing_time,\n            .average_processing_time = average_processing_time,\n            .last_error = last_error,\n        };\n    }\n};\n\ntest \"stats basic\" {\n    var stats: EndpointStats = .{};\n    stats.recordSuccess(10);\n    stats.recordError(20, 503, \"down\");\n    const snap = stats.snapshot();\n    try std.testing.expectEqual(@as(u64, 2), snap.num_requests);\n    try std.testing.expectEqual(@as(u64, 1), snap.num_errors);\n    try std.testing.expectEqual(@as(u64, 30), snap.processing_time);\n    try std.testing.expect(snap.last_error != null);\n}\n"
  },
  {
    "path": "src/micro/timeutil.zig",
    "content": "const std = @import(\"std\");\n\npub fn nowRfc3339(io: std.Io, buf: []u8) ![]const u8 {\n    const ts = std.Io.Timestamp.now(io, .real);\n    const total_ns: u64 = @intCast(ts.nanoseconds);\n    const epoch_secs = total_ns / std.time.ns_per_s;\n    const ms = @as(u32, @intCast((total_ns % std.time.ns_per_s) / std.time.ns_per_ms));\n\n    const epoch = std.time.epoch.EpochSeconds{\n        .secs = epoch_secs,\n    };\n    const day_secs = epoch.getDaySeconds();\n    const year_day = epoch.getEpochDay().calculateYearDay();\n    const month_day = year_day.calculateMonthDay();\n\n    return std.fmt.bufPrint(\n        buf,\n        \"{d:0>4}-{d:0>2}-{d:0>2}T{d:0>2}:{d:0>2}:{d:0>2}.{d:0>3}Z\",\n        .{\n            year_day.year,\n            @intFromEnum(month_day.month),\n            month_day.day_index + 1,\n            day_secs.getHoursIntoDay(),\n            day_secs.getMinutesIntoHour(),\n            day_secs.getSecondsIntoMinute(),\n            ms,\n        },\n    );\n}\n"
  },
  {
    "path": "src/micro/validation.zig",
    "content": "const std = @import(\"std\");\nconst pubsub = @import(\"../pubsub.zig\");\n\npub const Error = error{\n    InvalidName,\n    InvalidVersion,\n    InvalidPrefix,\n    InvalidGroup,\n} || pubsub.subject.ValidationError;\n\npub fn validateName(name: []const u8) Error!void {\n    if (name.len == 0) return error.InvalidName;\n    for (name) |c| {\n        switch (c) {\n            'A'...'Z', 'a'...'z', '0'...'9', '-', '_' => {},\n            else => return error.InvalidName,\n        }\n    }\n}\n\npub fn validateVersion(version: []const u8) Error!void {\n    _ = std.SemanticVersion.parse(version) catch {\n        return error.InvalidVersion;\n    };\n}\n\npub fn validatePrefix(prefix: []const u8) Error!void {\n    pubsub.validatePublish(prefix) catch {\n        return error.InvalidPrefix;\n    };\n}\n\npub fn validateGroup(prefix: []const u8) Error!void {\n    pubsub.validatePublish(prefix) catch {\n        return error.InvalidGroup;\n    };\n}\n\ntest \"validate name\" {\n    try validateName(\"svc_1\");\n    try std.testing.expectError(error.InvalidName, validateName(\"\"));\n    try std.testing.expectError(error.InvalidName, validateName(\"bad name\"));\n    try std.testing.expectError(error.InvalidName, validateName(\"bad/name\"));\n}\n\ntest \"validate version\" {\n    try validateVersion(\"1.0.0\");\n    try validateVersion(\"1.0.0-rc1+build.5\");\n    try std.testing.expectError(error.InvalidVersion, validateVersion(\"1.0\"));\n}\n"
  },
  {
    "path": "src/micro.zig",
    "content": "const std = @import(\"std\");\n\npub const Service = @import(\"micro/Service.zig\");\npub const Config = Service.Config;\npub const Error = Service.Error;\npub const addService = Service.addService;\n\nconst request_mod = @import(\"micro/request.zig\");\npub const Request = request_mod.Request;\npub const Handler = request_mod.Handler;\npub const HandlerFn = request_mod.HandlerFn;\n\npub const endpoint = @import(\"micro/endpoint.zig\");\npub const Endpoint = endpoint.Endpoint;\npub const EndpointConfig = endpoint.EndpointConfig;\npub const Group = endpoint.Group;\npub const QueuePolicy = endpoint.QueuePolicy;\n\npub const protocol = @import(\"micro/protocol.zig\");\npub const Info = protocol.Info;\npub const Ping = protocol.Ping;\npub const StatsResponse = protocol.StatsResponse;\n\ntest {\n    std.testing.refAllDecls(@This());\n}\n"
  },
  {
    "path": "src/nats.zig",
    "content": "//! NATS Client Library for Zig\n//!\n//! A Zig implementation of the NATS messaging protocol with native\n//! async I/O using std.Io, zero external C dependencies.\n//!\n//! ## Quick Start\n//!\n//! ```zig\n//! const nats = @import(\"nats\");\n//! const std = @import(\"std\");\n//!\n//! pub fn main(init: std.process.Init) !void {\n//!     const allocator = init.gpa;\n//!     const io = init.io;\n//!\n//!     const client = try nats.Client.connect(allocator, io, \"nats://localhost:4222\", .{});\n//!     defer client.deinit();\n//!\n//!     try client.publish(\"hello\", \"world\");\n//!     try client.flush(std.time.ns_per_s * 10);\n//! }\n//! ```\n\nconst std = @import(\"std\");\n\n// Module exports\npub const defaults = @import(\"defaults.zig\");\npub const protocol = @import(\"protocol.zig\");\npub const connection = @import(\"connection.zig\");\npub const pubsub = @import(\"pubsub.zig\");\npub const memory = @import(\"memory.zig\");\npub const auth = @import(\"auth.zig\");\npub const jetstream = @import(\"jetstream.zig\");\npub const micro = @import(\"micro.zig\");\n\n// Configuration types\npub const QueueSize = defaults.QueueSize;\n\n// Client module\npub const Client = @import(\"Client.zig\");\n\n// Primary types (nested in Client)\npub const Subscription = Client.Subscription;\npub const Message = Client.Message;\npub const Options = Client.Options;\npub const Statistics = Client.Statistics;\n\n// Event callback types (nested in Client)\npub const Event = Client.Event;\npub const EventHandler = Client.EventHandler;\n\n// Message callback type (nested in Client)\npub const MsgHandler = Client.MsgHandler;\n\n// Events module exports\nconst events = @import(\"events.zig\");\npub const EventError = events.Error;\npub const statusText = events.statusText;\n\n// Convenience exports\npub const newInbox = pubsub.newInbox;\npub const validateSubject = pubsub.validatePublish;\n\n// Protocol types\npub const ServerInfo = protocol.ServerInfo;\npub const ConnectOptions = protocol.ConnectOptions;\n\n/// Library version\npub const version = defaults.Protocol.version;\n\n/// Default NATS port\npub const default_port: u16 = defaults.Protocol.port;\n\n/// Default maximum payload size (1MB)\npub const default_max_payload: u32 = defaults.Protocol.max_payload;\n\ntest {\n    std.testing.refAllDecls(@This());\n}\n"
  },
  {
    "path": "src/protocol/commands.zig",
    "content": "//! NATS Protocol Command Definitions\n//!\n//! Defines the structure of all NATS protocol commands for both\n//! server-to-client and client-to-server communication.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\nconst defaults = @import(\"../defaults.zig\");\n\n/// Commands sent from server to client.\npub const ServerCommand = union(enum) {\n    info: ServerInfo,\n    msg: MsgArgs,\n    hmsg: HMsgArgs,\n    ping,\n    pong,\n    ok,\n    err: []const u8,\n};\n\n/// Commands sent from client to server.\npub const ClientCommand = union(enum) {\n    connect: ConnectOptions,\n    pub_cmd: PubArgs,\n    hpub: HPubArgs,\n    sub: SubArgs,\n    unsub: UnsubArgs,\n    ping,\n    pong,\n};\n\n/// Server info with owned string copies.\n/// All strings are allocated and owned by this struct.\npub const ServerInfo = struct {\n    /// Maximum allowed max_payload value (1GB).\n    pub const MAX_PAYLOAD_LIMIT = 1024 * 1024 * 1024;\n\n    /// Validation errors for ServerInfo.\n    pub const ValidationError = error{InvalidServerInfo};\n\n    server_id: []const u8,\n    server_name: []const u8,\n    version: []const u8,\n    host: []const u8,\n    proto: u32,\n    port: u16,\n    headers: bool,\n    max_payload: u32,\n    jetstream: bool,\n    tls_required: bool,\n    tls_available: bool,\n    auth_required: bool,\n    nonce: ?[]const u8,\n    client_id: ?u64,\n    client_ip: ?[]const u8,\n    cluster: ?[]const u8,\n\n    /// Discovered server URLs from cluster (inline storage, no allocation).\n    connect_urls: [16][256]u8 = undefined,\n    connect_urls_lens: [16]u8 = [_]u8{0} ** 16,\n    connect_urls_count: u8 = 0,\n\n    /// Creates an owned copy from parsed JSON RawServerInfo.\n    /// Copies all strings so they outlive the JSON arena.\n    /// Returns InvalidServerInfo if required fields are missing or invalid.\n    pub fn fromParsed(\n        allocator: Allocator,\n        parsed: std.json.Parsed(RawServerInfo),\n    ) (ValidationError || Allocator.Error)!ServerInfo {\n        const info = parsed.value;\n\n        // Must have at least server_id or version for identification\n        if (info.server_id.len == 0 and info.version.len == 0) {\n            return error.InvalidServerInfo;\n        }\n\n        // max_payload must be > 0 and <= 1GB\n        if (info.max_payload == 0 or info.max_payload > MAX_PAYLOAD_LIMIT) {\n            return error.InvalidServerInfo;\n        }\n\n        const sid = try allocator.dupe(u8, info.server_id);\n        errdefer allocator.free(sid);\n        const sname = try allocator.dupe(u8, info.server_name);\n        errdefer allocator.free(sname);\n        const ver = try allocator.dupe(u8, info.version);\n        errdefer allocator.free(ver);\n        const host = try allocator.dupe(u8, info.host);\n        errdefer allocator.free(host);\n        const nonce = if (info.nonce) |n|\n            try allocator.dupe(u8, n)\n        else\n            null;\n        errdefer if (nonce) |n| allocator.free(n);\n        const cip = if (info.client_ip) |ip|\n            try allocator.dupe(u8, ip)\n        else\n            null;\n        errdefer if (cip) |c| allocator.free(c);\n        const cluster = if (info.cluster) |c|\n            try allocator.dupe(u8, c)\n        else\n            null;\n\n        var owned = ServerInfo{\n            .server_id = sid,\n            .server_name = sname,\n            .version = ver,\n            .host = host,\n            .proto = info.proto,\n            .port = info.port,\n            .headers = info.headers,\n            .max_payload = info.max_payload,\n            .jetstream = info.jetstream,\n            .tls_required = info.tls_required,\n            .tls_available = info.tls_available,\n            .auth_required = info.auth_required,\n            .nonce = nonce,\n            .client_id = info.client_id,\n            .client_ip = cip,\n            .cluster = cluster,\n        };\n\n        // Copy connect_urls (inline, no allocation)\n        // Skip URLs > max_url_len (truncated URL would be invalid anyway)\n        if (info.connect_urls) |urls| {\n            for (urls) |url| {\n                if (owned.connect_urls_count >= 16) break;\n                if (url.len > defaults.Server.max_url_len) continue;\n                const len: u8 = @intCast(url.len);\n                const idx = owned.connect_urls_count;\n                @memcpy(owned.connect_urls[idx][0..len], url);\n                owned.connect_urls_lens[idx] = len;\n                owned.connect_urls_count += 1;\n            }\n        }\n\n        return owned;\n    }\n\n    /// Frees all owned strings.\n    pub fn deinit(self: *ServerInfo, allocator: Allocator) void {\n        assert(self.port > 0);\n        allocator.free(self.server_id);\n        allocator.free(self.server_name);\n        allocator.free(self.version);\n        allocator.free(self.host);\n        if (self.nonce) |n| allocator.free(n);\n        if (self.client_ip) |ip| allocator.free(ip);\n        if (self.cluster) |c| allocator.free(c);\n        self.* = undefined;\n    }\n\n    /// Get connect URL at index. Returns null if index out of bounds.\n    pub fn getConnectUrl(self: *const ServerInfo, idx: u8) ?[]const u8 {\n        if (idx >= self.connect_urls_count) return null;\n        const len = self.connect_urls_lens[idx];\n        if (len == 0) return null;\n        return self.connect_urls[idx][0..len];\n    }\n};\n\n/// Raw server INFO payload parsed from JSON.\n/// Internal use only - strings borrow from JSON arena.\npub const RawServerInfo = struct {\n    server_id: []const u8 = \"\",\n    server_name: []const u8 = \"\",\n    version: []const u8 = \"\",\n    proto: u32 = 1,\n    host: []const u8 = \"\",\n    port: u16 = 4222,\n    headers: bool = false,\n    max_payload: u32 = 1048576,\n    jetstream: bool = false,\n    tls_required: bool = false,\n    tls_available: bool = false,\n    auth_required: bool = false,\n    connect_urls: ?[]const []const u8 = null,\n    nonce: ?[]const u8 = null,\n    client_id: ?u64 = null,\n    client_ip: ?[]const u8 = null,\n    cluster: ?[]const u8 = null,\n\n    /// Parses RawServerInfo from JSON data.\n    pub fn parse(\n        allocator: Allocator,\n        json_data: []const u8,\n    ) std.json.ParseError(std.json.Scanner)!std.json.Parsed(RawServerInfo) {\n        return std.json.parseFromSlice(\n            RawServerInfo,\n            allocator,\n            json_data,\n            .{ .ignore_unknown_fields = true },\n        );\n    }\n\n    /// Frees a parsed RawServerInfo.\n    pub fn deinit(parsed: *std.json.Parsed(RawServerInfo)) void {\n        parsed.deinit();\n    }\n};\n\n/// Client CONNECT command options.\npub const ConnectOptions = struct {\n    verbose: bool = false,\n    pedantic: bool = false,\n    tls_required: bool = false,\n    auth_token: ?[]const u8 = null,\n    user: ?[]const u8 = null,\n    pass: ?[]const u8 = null,\n    name: ?[]const u8 = null,\n    lang: []const u8 = \"zig\",\n    version: []const u8 = \"0.1.0\",\n    protocol: u32 = 1,\n    echo: bool = true,\n    sig: ?[]const u8 = null,\n    jwt: ?[]const u8 = null,\n    nkey: ?[]const u8 = null,\n    headers: bool = true,\n    no_responders: bool = true,\n};\n\n/// Arguments for PUB command.\npub const PubArgs = struct {\n    subject: []const u8,\n    reply_to: ?[]const u8 = null,\n    payload: []const u8,\n};\n\n/// Arguments for HPUB command (publish with headers).\npub const HPubArgs = struct {\n    subject: []const u8,\n    reply_to: ?[]const u8 = null,\n    headers: []const u8,\n    payload: []const u8,\n};\n\n/// Arguments for HPUB command with structured header entries.\n/// Preferred over HPubArgs for type-safe header construction.\npub const HPubWithEntriesArgs = struct {\n    subject: []const u8,\n    reply_to: ?[]const u8 = null,\n    headers: []const headers_mod.Entry,\n    payload: []const u8,\n};\n\nconst headers_mod = @import(\"headers.zig\");\n\n/// Arguments for SUB command.\npub const SubArgs = struct {\n    subject: []const u8,\n    queue_group: ?[]const u8 = null,\n    sid: u64,\n};\n\n/// Arguments for UNSUB command.\npub const UnsubArgs = struct {\n    sid: u64,\n    max_msgs: ?u64 = null,\n};\n\n/// Arguments parsed from MSG command.\npub const MsgArgs = struct {\n    subject: []const u8,\n    sid: u64,\n    reply_to: ?[]const u8 = null,\n    payload_len: usize,\n    payload: []const u8 = \"\",\n    /// Length of header line including \\r\\n (for partial message parsing).\n    header_line_len: usize = 0,\n};\n\n/// Arguments parsed from HMSG command (message with headers).\npub const HMsgArgs = struct {\n    subject: []const u8,\n    sid: u64,\n    reply_to: ?[]const u8 = null,\n    header_len: usize,\n    total_len: usize,\n    headers: []const u8 = \"\",\n    payload: []const u8 = \"\",\n    /// Length of header line including \\r\\n (for partial message parsing).\n    header_line_len: usize = 0,\n};\n\ntest \"server info parse\" {\n    const allocator = std.testing.allocator;\n    const json = \"{\\\"server_id\\\":\\\"test\\\",\\\"version\\\":\\\"2.10.0\\\",\" ++\n        \"\\\"proto\\\":1,\\\"max_payload\\\":1048576}\";\n\n    var parsed = try RawServerInfo.parse(allocator, json);\n    defer parsed.deinit();\n\n    try std.testing.expectEqualSlices(u8, \"test\", parsed.value.server_id);\n    try std.testing.expectEqualSlices(u8, \"2.10.0\", parsed.value.version);\n    try std.testing.expectEqual(@as(u32, 1), parsed.value.proto);\n    try std.testing.expectEqual(@as(u32, 1048576), parsed.value.max_payload);\n}\n\ntest \"server info parse with unknown fields\" {\n    const allocator = std.testing.allocator;\n    const json =\n        \\\\{\"server_id\":\"x\",\"unknown_field\":\"ignored\",\"version\":\"1.0\"}\n    ;\n\n    var parsed = try RawServerInfo.parse(allocator, json);\n    defer parsed.deinit();\n\n    try std.testing.expectEqualSlices(u8, \"x\", parsed.value.server_id);\n}\n\ntest \"connect options defaults\" {\n    const opts: ConnectOptions = .{};\n    try std.testing.expect(!opts.verbose);\n    try std.testing.expect(opts.echo);\n    try std.testing.expect(opts.headers);\n    try std.testing.expectEqualSlices(u8, \"zig\", opts.lang);\n}\n\ntest \"pub args\" {\n    const args: PubArgs = .{\n        .subject = \"test.subject\",\n        .payload = \"hello\",\n    };\n    try std.testing.expectEqualSlices(u8, \"test.subject\", args.subject);\n    try std.testing.expectEqual(@as(?[]const u8, null), args.reply_to);\n}\n\ntest \"sub args with queue\" {\n    const args: SubArgs = .{\n        .subject = \"orders.>\",\n        .queue_group = \"workers\",\n        .sid = 42,\n    };\n    try std.testing.expectEqualSlices(u8, \"workers\", args.queue_group.?);\n}\n"
  },
  {
    "path": "src/protocol/encoder.zig",
    "content": "//! NATS Protocol Encoder\n//!\n//! Encodes client commands into NATS wire protocol format.\n//! All string fields are validated against CRLF injection and control chars.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\nconst Io = std.Io;\n\nconst commands = @import(\"commands.zig\");\nconst ConnectOptions = commands.ConnectOptions;\nconst PubArgs = commands.PubArgs;\nconst HPubArgs = commands.HPubArgs;\nconst HPubWithEntriesArgs = commands.HPubWithEntriesArgs;\nconst SubArgs = commands.SubArgs;\nconst UnsubArgs = commands.UnsubArgs;\n\nconst headers = @import(\"headers.zig\");\n\nconst subject = @import(\"../pubsub/subject.zig\");\nconst ValidationError = subject.ValidationError;\n\n/// Fast integer-to-string conversion (avoids std.fmt overhead).\n/// Writes digits directly to buffer, returns slice of written digits.\nfn writeUsizeToBuffer(buf: *[20]u8, value: usize) []const u8 {\n    if (value == 0) {\n        buf[19] = '0';\n        return buf[19..20];\n    }\n\n    var v = value;\n    var i: usize = 20;\n    while (v > 0) : (v /= 10) {\n        i -= 1;\n        buf[i] = @intCast((v % 10) + '0');\n    }\n    return buf[i..20];\n}\n\n/// Protocol encoder for client commands.\npub const Encoder = struct {\n    /// Encoding validation errors.\n    /// Includes ValidationError for subject/reply-to/queue-group validation.\n    pub const Error = error{\n        EmptyHeaders,\n        InvalidHeader,\n        InvalidSid,\n    } || ValidationError;\n\n    /// Encodes CONNECT command with JSON options.\n    pub fn encodeConnect(\n        writer: *Io.Writer,\n        opts: ConnectOptions,\n    ) Io.Writer.Error!void {\n        try writer.writeAll(\"CONNECT \");\n        try std.json.Stringify.value(opts, .{}, writer);\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    /// Encodes PUB command.\n    /// Validates subject and reply_to for CRLF injection and control chars.\n    pub fn encodePub(\n        writer: *Io.Writer,\n        args: PubArgs,\n    ) (Error || Io.Writer.Error)!void {\n        try subject.validatePublish(args.subject);\n        assert(args.subject.len > 0);\n\n        try writer.writeAll(\"PUB \");\n        try writer.writeAll(args.subject);\n\n        if (args.reply_to) |reply| {\n            if (reply.len > 0) {\n                try subject.validateReplyTo(reply);\n                try writer.writeByte(' ');\n                try writer.writeAll(reply);\n            }\n        }\n\n        var num_buf: [20]u8 = undefined;\n        try writer.writeByte(' ');\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, args.payload.len));\n        try writer.writeAll(\"\\r\\n\");\n        try writer.writeAll(args.payload);\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    /// Encodes HPUB command (publish with headers).\n    /// Validates subject and reply_to for CRLF injection and control chars.\n    pub fn encodeHPub(\n        writer: *Io.Writer,\n        args: HPubArgs,\n    ) (Error || Io.Writer.Error)!void {\n        try subject.validatePublish(args.subject);\n        if (args.headers.len == 0) return Error.EmptyHeaders;\n        assert(args.subject.len > 0);\n        assert(args.headers.len > 0);\n\n        try writer.writeAll(\"HPUB \");\n        try writer.writeAll(args.subject);\n\n        if (args.reply_to) |reply| {\n            if (reply.len > 0) {\n                try subject.validateReplyTo(reply);\n                try writer.writeByte(' ');\n                try writer.writeAll(reply);\n            }\n        }\n\n        const total_len = args.headers.len + args.payload.len;\n        var num_buf: [20]u8 = undefined;\n        try writer.writeByte(' ');\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, args.headers.len));\n        try writer.writeByte(' ');\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, total_len));\n        try writer.writeAll(\"\\r\\n\");\n        try writer.writeAll(args.headers);\n        try writer.writeAll(args.payload);\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    /// Encodes HPUB command with structured header entries.\n    /// Calculates header size and encodes headers inline.\n    pub fn encodeHPubWithEntries(\n        writer: *Io.Writer,\n        args: HPubWithEntriesArgs,\n    ) (Error || Io.Writer.Error)!void {\n        try subject.validatePublish(args.subject);\n        headers.validateEntries(args.headers) catch |err| switch (err) {\n            error.EmptyHeaders => return Error.EmptyHeaders,\n            error.InvalidHeader => return Error.InvalidHeader,\n        };\n        assert(args.subject.len > 0);\n        assert(args.headers.len > 0);\n        assert(args.headers.len <= 1024);\n\n        try writer.writeAll(\"HPUB \");\n        try writer.writeAll(args.subject);\n\n        if (args.reply_to) |reply| {\n            if (reply.len > 0) {\n                try subject.validateReplyTo(reply);\n                try writer.writeByte(' ');\n                try writer.writeAll(reply);\n            }\n        }\n\n        const hdr_len = headers.encodedSize(args.headers);\n        const total_len = hdr_len + args.payload.len;\n        var num_buf: [20]u8 = undefined;\n        try writer.writeByte(' ');\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, hdr_len));\n        try writer.writeByte(' ');\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, total_len));\n        try writer.writeAll(\"\\r\\n\");\n        try headers.encode(writer, args.headers);\n        try writer.writeAll(args.payload);\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    /// Encodes SUB command.\n    /// Validates subject and queue_group for CRLF injection and control chars.\n    pub fn encodeSub(\n        writer: *Io.Writer,\n        args: SubArgs,\n    ) (Error || Io.Writer.Error)!void {\n        try subject.validateSubscribe(args.subject);\n        if (args.sid == 0) return Error.InvalidSid;\n        assert(args.subject.len > 0);\n        assert(args.sid > 0);\n\n        try writer.writeAll(\"SUB \");\n        try writer.writeAll(args.subject);\n\n        if (args.queue_group) |queue| {\n            if (queue.len > 0) {\n                try subject.validateQueueGroup(queue);\n                try writer.writeByte(' ');\n                try writer.writeAll(queue);\n            }\n        }\n\n        var num_buf: [20]u8 = undefined;\n        try writer.writeByte(' ');\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, args.sid));\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    /// Encodes UNSUB command.\n    pub fn encodeUnsub(\n        writer: *Io.Writer,\n        args: UnsubArgs,\n    ) (Error || Io.Writer.Error)!void {\n        if (args.sid == 0) return Error.InvalidSid;\n        assert(args.sid > 0);\n        var num_buf: [20]u8 = undefined;\n        try writer.writeAll(\"UNSUB \");\n        try writer.writeAll(writeUsizeToBuffer(&num_buf, args.sid));\n\n        if (args.max_msgs) |max| {\n            try writer.writeByte(' ');\n            try writer.writeAll(writeUsizeToBuffer(&num_buf, max));\n        }\n\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    /// Encodes PING command.\n    pub fn encodePing(writer: *Io.Writer) Io.Writer.Error!void {\n        try writer.writeAll(\"PING\\r\\n\");\n    }\n\n    /// Encodes PONG command.\n    pub fn encodePong(writer: *Io.Writer) Io.Writer.Error!void {\n        try writer.writeAll(\"PONG\\r\\n\");\n    }\n};\n\ntest {\n    _ = @import(\"encoder_test.zig\");\n}\n"
  },
  {
    "path": "src/protocol/encoder_test.zig",
    "content": "//! Protocol Encoder Edge Case Tests\n//!\n//! - Integer conversion edge cases\n//! - CRLF injection attacks (SECURITY)\n//! - Empty/optional field handling\n//! - SID boundary values\n//! - Payload size edge cases\n\nconst std = @import(\"std\");\nconst Io = std.Io;\n\nconst encoder = @import(\"encoder.zig\");\nconst Encoder = encoder.Encoder;\n\n// Section 1: Existing Tests (moved from encoder.zig)\n\ntest \"encode PING\" {\n    var buf: [64]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    try Encoder.encodePing(&writer);\n    try std.testing.expectEqualSlices(u8, \"PING\\r\\n\", writer.buffered());\n}\n\ntest \"encode PONG\" {\n    var buf: [64]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    try Encoder.encodePong(&writer);\n    try std.testing.expectEqualSlices(u8, \"PONG\\r\\n\", writer.buffered());\n}\n\ntest \"encode PUB\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"test.subject\",\n        .payload = \"hello\",\n    });\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB test.subject 5\\r\\nhello\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encode PUB with reply\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"request\",\n        .reply_to = \"_INBOX.123\",\n        .payload = \"data\",\n    });\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB request _INBOX.123 4\\r\\ndata\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encode SUB\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{\n        .subject = \"events.>\",\n        .sid = 42,\n    });\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"SUB events.> 42\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encode SUB with queue\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{\n        .subject = \"orders.*\",\n        .queue_group = \"workers\",\n        .sid = 1,\n    });\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"SUB orders.* workers 1\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encode UNSUB\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeUnsub(&writer, .{ .sid = 5 });\n\n    try std.testing.expectEqualSlices(u8, \"UNSUB 5\\r\\n\", writer.buffered());\n}\n\ntest \"encode UNSUB with max\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeUnsub(&writer, .{ .sid = 5, .max_msgs = 10 });\n\n    try std.testing.expectEqualSlices(u8, \"UNSUB 5 10\\r\\n\", writer.buffered());\n}\n\ntest \"encode CONNECT\" {\n    var buf: [1024]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeConnect(&writer, .{\n        .verbose = false,\n        .name = \"test-client\",\n    });\n\n    const written = writer.buffered();\n    try std.testing.expect(std.mem.startsWith(u8, written, \"CONNECT {\"));\n    try std.testing.expect(std.mem.endsWith(u8, written, \"}\\r\\n\"));\n    try std.testing.expect(\n        std.mem.indexOf(u8, written, \"\\\"name\\\":\\\"test-client\\\"\") != null,\n    );\n}\n\ntest \"encodePub empty subject rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"\",\n        .payload = \"hello\",\n    });\n    try std.testing.expectError(Encoder.Error.EmptySubject, result);\n}\n\ntest \"encodeHPub empty subject rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    const result = Encoder.encodeHPub(&writer, .{\n        .subject = \"\",\n        .headers = \"NATS/1.0\\r\\n\\r\\n\",\n        .payload = \"hello\",\n    });\n    try std.testing.expectError(Encoder.Error.EmptySubject, result);\n}\n\ntest \"encodeHPub empty headers rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    const result = Encoder.encodeHPub(&writer, .{\n        .subject = \"test\",\n        .headers = \"\",\n        .payload = \"hello\",\n    });\n    try std.testing.expectError(Encoder.Error.EmptyHeaders, result);\n}\n\ntest \"encodeSub empty subject rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    const result = Encoder.encodeSub(&writer, .{\n        .subject = \"\",\n        .sid = 1,\n    });\n    try std.testing.expectError(Encoder.Error.EmptySubject, result);\n}\n\ntest \"encodeSub invalid SID rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    const result = Encoder.encodeSub(&writer, .{\n        .subject = \"test\",\n        .sid = 0,\n    });\n    try std.testing.expectError(Encoder.Error.InvalidSid, result);\n}\n\ntest \"encodeUnsub invalid SID rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    const result = Encoder.encodeUnsub(&writer, .{ .sid = 0 });\n    try std.testing.expectError(Encoder.Error.InvalidSid, result);\n}\n\n// Section 2: SID Boundary Value Tests\n\ntest \"encodeSub SID one\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{ .subject = \"test\", .sid = 1 });\n    try std.testing.expectEqualSlices(u8, \"SUB test 1\\r\\n\", writer.buffered());\n}\n\ntest \"encodeSub SID large value\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{ .subject = \"test\", .sid = 999999999 });\n    try std.testing.expectEqualSlices(\n        u8,\n        \"SUB test 999999999\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeSub SID u64 max\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const max_sid: u64 = std.math.maxInt(u64);\n    try Encoder.encodeSub(&writer, .{ .subject = \"t\", .sid = max_sid });\n\n    const expected = \"SUB t 18446744073709551615\\r\\n\";\n    try std.testing.expectEqualSlices(u8, expected, writer.buffered());\n}\n\ntest \"encodeUnsub SID one\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeUnsub(&writer, .{ .sid = 1 });\n    try std.testing.expectEqualSlices(u8, \"UNSUB 1\\r\\n\", writer.buffered());\n}\n\ntest \"encodeUnsub SID u64 max\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const max_sid: u64 = std.math.maxInt(u64);\n    try Encoder.encodeUnsub(&writer, .{ .sid = max_sid });\n\n    const expected = \"UNSUB 18446744073709551615\\r\\n\";\n    try std.testing.expectEqualSlices(u8, expected, writer.buffered());\n}\n\ntest \"encodeUnsub max_msgs u64 max\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const max_val: u64 = std.math.maxInt(u64);\n    try Encoder.encodeUnsub(&writer, .{ .sid = 1, .max_msgs = max_val });\n\n    const expected = \"UNSUB 1 18446744073709551615\\r\\n\";\n    try std.testing.expectEqualSlices(u8, expected, writer.buffered());\n}\n\n// Section 3: Payload Size Edge Cases\n\ntest \"encodePub empty payload\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"test\",\n        .payload = \"\",\n    });\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB test 0\\r\\n\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodePub single byte payload\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"test\",\n        .payload = \"X\",\n    });\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB test 1\\r\\nX\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodePub payload length 9\" {\n    var buf: [512]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    var payload_buf: [9]u8 = undefined;\n    @memset(&payload_buf, 'X');\n    try Encoder.encodePub(&writer, .{ .subject = \"s\", .payload = &payload_buf });\n    try std.testing.expect(std.mem.startsWith(u8, writer.buffered(), \"PUB s 9\\r\\n\"));\n}\n\ntest \"encodePub payload length 10\" {\n    var buf: [512]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    var payload_buf: [10]u8 = undefined;\n    @memset(&payload_buf, 'X');\n    try Encoder.encodePub(&writer, .{ .subject = \"s\", .payload = &payload_buf });\n    try std.testing.expect(std.mem.startsWith(u8, writer.buffered(), \"PUB s 10\\r\\n\"));\n}\n\ntest \"encodePub payload length 99\" {\n    var buf: [512]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    var payload_buf: [99]u8 = undefined;\n    @memset(&payload_buf, 'X');\n    try Encoder.encodePub(&writer, .{ .subject = \"s\", .payload = &payload_buf });\n    try std.testing.expect(std.mem.startsWith(u8, writer.buffered(), \"PUB s 99\\r\\n\"));\n}\n\ntest \"encodePub payload length 100\" {\n    var buf: [512]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n    var payload_buf: [100]u8 = undefined;\n    @memset(&payload_buf, 'X');\n    try Encoder.encodePub(&writer, .{ .subject = \"s\", .payload = &payload_buf });\n    try std.testing.expect(std.mem.startsWith(u8, writer.buffered(), \"PUB s 100\\r\\n\"));\n}\n\ntest \"encodeHPub empty payload with headers\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeHPub(&writer, .{\n        .subject = \"test\",\n        .headers = \"NATS/1.0\\r\\n\\r\\n\",\n        .payload = \"\",\n    });\n\n    // headers.len = 12, total_len = 12 (headers only)\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB test 12 12\\r\\nNATS/1.0\\r\\n\\r\\n\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeHPub headers and payload lengths\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeHPub(&writer, .{\n        .subject = \"test\",\n        .headers = \"NATS/1.0\\r\\nX:Y\\r\\n\\r\\n\", // 17 bytes\n        .payload = \"hello\", // 5 bytes\n    });\n\n    // headers.len = 17, total_len = 22\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB test 17 22\\r\\nNATS/1.0\\r\\nX:Y\\r\\n\\r\\nhello\\r\\n\",\n        writer.buffered(),\n    );\n}\n\n// Section 4: Subject Edge Cases\n\ntest \"encodePub single char subject\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{ .subject = \"x\", .payload = \"y\" });\n    try std.testing.expectEqualSlices(u8, \"PUB x 1\\r\\ny\\r\\n\", writer.buffered());\n}\n\ntest \"encodePub subject with dots\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"foo.bar.baz\",\n        .payload = \"\",\n    });\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB foo.bar.baz 0\\r\\n\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeSub subject with wildcards\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{ .subject = \"foo.*.bar\", .sid = 1 });\n    try std.testing.expectEqualSlices(\n        u8,\n        \"SUB foo.*.bar 1\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeSub subject with full wildcard\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{ .subject = \"foo.>\", .sid = 1 });\n    try std.testing.expectEqualSlices(\n        u8,\n        \"SUB foo.> 1\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeSub subject only wildcard\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{ .subject = \">\", .sid = 1 });\n    try std.testing.expectEqualSlices(u8, \"SUB > 1\\r\\n\", writer.buffered());\n}\n\n// Section 5: CRLF Injection Tests (SECURITY) - FIXED\n\ntest \"encodePub subject with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // CRLF injection attempt - must be rejected\n    const malicious_subject = \"test\\r\\nUNSUB 1\\r\\nPUB foo\";\n    const result = Encoder.encodePub(&writer, .{\n        .subject = malicious_subject,\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodePub reply_to with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const malicious_reply = \"_INBOX\\r\\nUNSUB 1\\r\\nPUB foo\";\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\",\n        .reply_to = malicious_reply,\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeSub subject with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const malicious_subject = \"test\\r\\nUNSUB 1\";\n    const result = Encoder.encodeSub(&writer, .{\n        .subject = malicious_subject,\n        .sid = 1,\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeSub queue_group with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const malicious_queue = \"workers\\r\\nUNSUB 1\";\n    const result = Encoder.encodeSub(&writer, .{\n        .subject = \"test\",\n        .queue_group = malicious_queue,\n        .sid = 1,\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeHPub subject with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const malicious_subject = \"test\\r\\nUNSUB 1\";\n    const result = Encoder.encodeHPub(&writer, .{\n        .subject = malicious_subject,\n        .headers = \"NATS/1.0\\r\\n\\r\\n\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeHPub reply_to with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const malicious_reply = \"_INBOX\\r\\nUNSUB 1\";\n    const result = Encoder.encodeHPub(&writer, .{\n        .subject = \"test\",\n        .reply_to = malicious_reply,\n        .headers = \"NATS/1.0\\r\\n\\r\\n\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\n// Section 6: Space in Fields Tests - FIXED\n\ntest \"encodePub subject with space rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Space in subject must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test subject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.SpaceInSubject, result);\n}\n\ntest \"encodeSub queue_group with space rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Space in queue_group must be rejected\n    const result = Encoder.encodeSub(&writer, .{\n        .subject = \"test\",\n        .queue_group = \"worker group\",\n        .sid = 1,\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\n// Section 7: Empty Optional Field Tests - FIXED\n\ntest \"encodePub empty reply_to treated as null\" {\n    // Empty string reply_to is now treated as null (skipped)\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"test\",\n        .reply_to = \"\", // Empty string treated as null\n        .payload = \"x\",\n    });\n\n    // Should produce same output as no reply_to\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB test 1\\r\\nx\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeSub empty queue_group treated as null\" {\n    // Empty string queue_group is now treated as null (skipped)\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeSub(&writer, .{\n        .subject = \"test\",\n        .queue_group = \"\", // Empty string treated as null\n        .sid = 1,\n    });\n\n    // Should produce same output as no queue_group\n    try std.testing.expectEqualSlices(\n        u8,\n        \"SUB test 1\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeHPub empty reply_to treated as null\" {\n    // Empty string reply_to is now treated as null (skipped)\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeHPub(&writer, .{\n        .subject = \"test\",\n        .reply_to = \"\",\n        .headers = \"NATS/1.0\\r\\n\\r\\n\",\n        .payload = \"x\",\n    });\n\n    // Should produce same output as no reply_to\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB test 12 13\\r\\nNATS/1.0\\r\\n\\r\\nx\\r\\n\",\n        writer.buffered(),\n    );\n}\n\n// Section 8: Null Byte Tests - FIXED\n\ntest \"encodePub subject with null byte rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Null byte in subject must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\\x00inject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodePub payload with null byte allowed\" {\n    // Payload CAN contain null bytes (binary data)\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"test\",\n        .payload = \"hel\\x00lo\",\n    });\n\n    const written = writer.buffered();\n    // Null byte should be in payload\n    try std.testing.expectEqualSlices(\n        u8,\n        \"PUB test 6\\r\\nhel\\x00lo\\r\\n\",\n        written,\n    );\n}\n\n// Section 9: Control Character Tests - FIXED\n\ntest \"encodePub subject with tab rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Tab in subject must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\\tsubject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.SpaceInSubject, result);\n}\n\ntest \"encodePub subject with CR only rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // CR alone must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\\rsubject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodePub subject with LF only rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // LF alone must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\\nsubject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodePub subject with DEL char rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // DEL (0x7F) must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\\x7fsubject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodePub reply_to with control char rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Control char in reply_to must be rejected\n    const result = Encoder.encodePub(&writer, .{\n        .subject = \"test\",\n        .reply_to = \"_INBOX\\x01inject\",\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeSub queue_group with control char rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Control char in queue_group must be rejected\n    const result = Encoder.encodeSub(&writer, .{\n        .subject = \"test\",\n        .queue_group = \"workers\\x01group\",\n        .sid = 1,\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\n// Section 10: UNSUB max_msgs Edge Cases\n\ntest \"encodeUnsub max_msgs zero\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // max_msgs = 0 might have special meaning or be invalid\n    try Encoder.encodeUnsub(&writer, .{ .sid = 1, .max_msgs = 0 });\n    try std.testing.expectEqualSlices(\n        u8,\n        \"UNSUB 1 0\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeUnsub max_msgs one\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeUnsub(&writer, .{ .sid = 1, .max_msgs = 1 });\n    try std.testing.expectEqualSlices(\n        u8,\n        \"UNSUB 1 1\\r\\n\",\n        writer.buffered(),\n    );\n}\n\n// Section 11: CONNECT Edge Cases\n\ntest \"encodeConnect minimal options\" {\n    var buf: [1024]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeConnect(&writer, .{});\n\n    const written = writer.buffered();\n    try std.testing.expect(std.mem.startsWith(u8, written, \"CONNECT {\"));\n    try std.testing.expect(std.mem.endsWith(u8, written, \"}\\r\\n\"));\n}\n\ntest \"encodeConnect with all options\" {\n    var buf: [2048]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    try Encoder.encodeConnect(&writer, .{\n        .verbose = true,\n        .pedantic = true,\n        .name = \"full-client\",\n        .lang = \"zig\",\n        .version = \"1.0.0\",\n        .protocol = 1,\n        .echo = false,\n        .headers = true,\n        .no_responders = true,\n    });\n\n    const written = writer.buffered();\n    try std.testing.expect(std.mem.startsWith(u8, written, \"CONNECT {\"));\n    try std.testing.expect(std.mem.endsWith(u8, written, \"}\\r\\n\"));\n    try std.testing.expect(\n        std.mem.indexOf(u8, written, \"\\\"verbose\\\":true\") != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, written, \"\\\"echo\\\":false\") != null,\n    );\n}\n\n// Section 12: Long Subject/Payload Tests\n\ntest \"encodePub long subject\" {\n    var buf: [4096]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Create 200-char subject\n    var subject_buf: [200]u8 = undefined;\n    @memset(&subject_buf, 'x');\n\n    try Encoder.encodePub(&writer, .{\n        .subject = &subject_buf,\n        .payload = \"y\",\n    });\n\n    const written = writer.buffered();\n    try std.testing.expect(std.mem.startsWith(u8, written, \"PUB \"));\n    try std.testing.expect(written.len > 200);\n}\n\ntest \"encodeSub long queue_group\" {\n    var buf: [4096]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    var queue_buf: [100]u8 = undefined;\n    @memset(&queue_buf, 'q');\n\n    try Encoder.encodeSub(&writer, .{\n        .subject = \"test\",\n        .queue_group = &queue_buf,\n        .sid = 1,\n    });\n\n    const written = writer.buffered();\n    try std.testing.expect(written.len > 100);\n}\n\n// Section 13: Binary Payload Tests\n\ntest \"encodePub payload with all byte values\" {\n    var buf: [1024]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    // Payload containing all 256 byte values\n    var payload: [256]u8 = undefined;\n    for (&payload, 0..) |*p, i| {\n        p.* = @intCast(i);\n    }\n\n    try Encoder.encodePub(&writer, .{\n        .subject = \"binary\",\n        .payload = &payload,\n    });\n\n    const written = writer.buffered();\n    try std.testing.expect(\n        std.mem.startsWith(u8, written, \"PUB binary 256\\r\\n\"),\n    );\n    // Verify payload is intact\n    const payload_start = std.mem.indexOf(u8, written, \"\\r\\n\").? + 2;\n    const payload_end = written.len - 2; // Exclude trailing \\r\\n\n    try std.testing.expectEqualSlices(\n        u8,\n        &payload,\n        written[payload_start..payload_end],\n    );\n}\n\ntest \"encodeHPub binary headers and payload\" {\n    var buf: [1024]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const headers = \"NATS/1.0\\r\\nBin: \\x00\\x01\\x02\\r\\n\\r\\n\";\n    const payload = \"\\xFF\\xFE\\xFD\";\n\n    try Encoder.encodeHPub(&writer, .{\n        .subject = \"binary\",\n        .headers = headers,\n        .payload = payload,\n    });\n\n    const written = writer.buffered();\n    // Verify headers and payload are in output\n    try std.testing.expect(std.mem.indexOf(u8, written, headers) != null);\n}\n\n// Section 14: HPUB with Entries Tests\n\nconst headers_mod = @import(\"headers.zig\");\n\ntest \"encodeHPubWithEntries basic\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"Foo\", .value = \"bar\" },\n    };\n\n    try Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"test\",\n        .headers = &entries,\n        .payload = \"hello\",\n    });\n\n    // NATS/1.0\\r\\nFoo: bar\\r\\n\\r\\n = 22 bytes\n    // total = 22 + 5 = 27 bytes\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB test 22 27\\r\\nNATS/1.0\\r\\nFoo: bar\\r\\n\\r\\nhello\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeHPubWithEntries with reply_to\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"X\", .value = \"Y\" },\n    };\n\n    try Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"request\",\n        .reply_to = \"_INBOX.123\",\n        .headers = &entries,\n        .payload = \"data\",\n    });\n\n    // NATS/1.0\\r\\nX: Y\\r\\n\\r\\n = 18 bytes\n    // total = 18 + 4 = 22 bytes\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB request _INBOX.123 18 22\\r\\nNATS/1.0\\r\\nX: Y\\r\\n\\r\\ndata\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeHPubWithEntries multiple headers\" {\n    var buf: [512]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"Content-Type\", .value = \"application/json\" },\n        .{ .key = \"Nats-Msg-Id\", .value = \"abc123\" },\n    };\n\n    try Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"api.request\",\n        .headers = &entries,\n        .payload = \"{}\",\n    });\n\n    const written = writer.buffered();\n    try std.testing.expect(std.mem.startsWith(u8, written, \"HPUB api.request \"));\n    try std.testing.expect(std.mem.indexOf(\n        u8,\n        written,\n        \"Content-Type: application/json\",\n    ) != null);\n    try std.testing.expect(std.mem.indexOf(\n        u8,\n        written,\n        \"Nats-Msg-Id: abc123\",\n    ) != null);\n}\n\ntest \"encodeHPubWithEntries empty payload\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"Status\", .value = \"100\" },\n    };\n\n    try Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"notify\",\n        .headers = &entries,\n        .payload = \"\",\n    });\n\n    // NATS/1.0\\r\\n (10) + Status: 100\\r\\n (13) + \\r\\n (2) = 25 bytes\n    // total = 25 + 0 = 25 bytes\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB notify 25 25\\r\\nNATS/1.0\\r\\nStatus: 100\\r\\n\\r\\n\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encodeHPubWithEntries empty headers rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{};\n\n    const result = Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"test\",\n        .headers = &entries,\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(Encoder.Error.EmptyHeaders, result);\n}\n\ntest \"encodeHPubWithEntries empty subject rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"X\", .value = \"Y\" },\n    };\n\n    const result = Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"\",\n        .headers = &entries,\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(Encoder.Error.EmptySubject, result);\n}\n\ntest \"encodeHPubWithEntries subject with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"X\", .value = \"Y\" },\n    };\n\n    const result = Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"test\\r\\nUNSUB 1\",\n        .headers = &entries,\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeHPubWithEntries reply_to with CRLF rejected\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"X\", .value = \"Y\" },\n    };\n\n    const result = Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"test\",\n        .reply_to = \"_INBOX\\r\\nUNSUB 1\",\n        .headers = &entries,\n        .payload = \"x\",\n    });\n\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"encodeHPubWithEntries empty reply_to treated as null\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]headers_mod.Entry{\n        .{ .key = \"X\", .value = \"Y\" },\n    };\n\n    try Encoder.encodeHPubWithEntries(&writer, .{\n        .subject = \"test\",\n        .reply_to = \"\",\n        .headers = &entries,\n        .payload = \"x\",\n    });\n\n    // Should produce same as no reply_to\n    try std.testing.expectEqualSlices(\n        u8,\n        \"HPUB test 18 19\\r\\nNATS/1.0\\r\\nX: Y\\r\\n\\r\\nx\\r\\n\",\n        writer.buffered(),\n    );\n}\n"
  },
  {
    "path": "src/protocol/errors.zig",
    "content": "//! Protocol Errors\n//!\n//! Error types for protocol-related failures including parsing errors,\n//! server errors, and authorization violations.\n\nconst std = @import(\"std\");\n\n/// Protocol-related errors.\npub const Error = error{\n    /// Server sent an invalid protocol message.\n    ProtocolError,\n    /// Server sent an error response.\n    ServerError,\n    /// Authorization for the requested operation was denied.\n    AuthorizationViolation,\n    /// Message payload exceeds server's maximum allowed size.\n    MaxPayloadExceeded,\n};\n\n/// Parses a NATS server error message into a protocol Error.\n/// Server errors come in the form: -ERR 'message'\npub fn parseServerError(msg: []const u8) Error {\n    if (std.mem.indexOf(u8, msg, \"Authorization Violation\")) |_| {\n        return error.AuthorizationViolation;\n    }\n    if (std.mem.indexOf(u8, msg, \"Maximum Payload\")) |_| {\n        return error.MaxPayloadExceeded;\n    }\n    return error.ServerError;\n}\n\n/// Returns true if the error is a permissions error.\npub fn isAuthError(err: Error) bool {\n    return err == error.AuthorizationViolation;\n}\n\ntest \"parseServerError authorization\" {\n    const err = parseServerError(\"Authorization Violation\");\n    try std.testing.expectEqual(error.AuthorizationViolation, err);\n}\n\ntest \"parseServerError max payload\" {\n    const err = parseServerError(\"Maximum Payload Exceeded\");\n    try std.testing.expectEqual(error.MaxPayloadExceeded, err);\n}\n\ntest \"parseServerError unknown\" {\n    const err = parseServerError(\"Some Unknown Error\");\n    try std.testing.expectEqual(error.ServerError, err);\n}\n\ntest \"isAuthError\" {\n    try std.testing.expect(isAuthError(error.AuthorizationViolation));\n    try std.testing.expect(!isAuthError(error.ServerError));\n    try std.testing.expect(!isAuthError(error.ProtocolError));\n}\n"
  },
  {
    "path": "src/protocol/header_map.zig",
    "content": "//! Header Map Builder\n//!\n//! Programmatic builder for NATS message headers.\n//! Supports multi-value headers (same key, multiple values).\n//!\n//! Example:\n//! ```zig\n//! var headers = HeaderMap.init(allocator);\n//! defer headers.deinit();\n//! try headers.set(\"Content-Type\", \"application/json\");\n//! try headers.add(\"X-Tag\", \"important\");\n//! try headers.add(\"X-Tag\", \"urgent\");  // Multiple values\n//!\n//! // Encode to NATS format\n//! const encoded = try headers.encode();\n//! defer allocator.free(encoded);\n//! ```\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\nconst headers = @import(\"headers.zig\");\n\n/// Builder for NATS message headers.\n/// Supports multiple values per key.\n/// Stores allocator\npub const HeaderMap = struct {\n    allocator: Allocator,\n    /// Keys (owned, case-preserved).\n    keys: std.ArrayList([]u8) = .empty,\n    /// Values (owned). Same index as key.\n    values: std.ArrayList([]u8) = .empty,\n\n    /// Creates a new HeaderMap with the given allocator.\n    pub fn init(allocator: Allocator) HeaderMap {\n        return .{ .allocator = allocator };\n    }\n\n    /// Frees all memory.\n    pub fn deinit(self: *HeaderMap) void {\n        for (self.keys.items) |key| {\n            self.allocator.free(key);\n        }\n        self.keys.deinit(self.allocator);\n\n        for (self.values.items) |value| {\n            self.allocator.free(value);\n        }\n        self.values.deinit(self.allocator);\n    }\n\n    /// Sets a header, replacing any existing values for this key.\n    /// Key comparison is case-insensitive.\n    pub fn set(\n        self: *HeaderMap,\n        key: []const u8,\n        value: []const u8,\n    ) error{ InvalidHeader, OutOfMemory }!void {\n        headers.validateKeyValue(key, value) catch return error.InvalidHeader;\n\n        // Remove existing values for this key\n        self.deleteInternal(key);\n\n        // Add new entry\n        const owned_key = try self.allocator.dupe(u8, key);\n        errdefer self.allocator.free(owned_key);\n\n        const owned_value = try self.allocator.dupe(u8, value);\n        errdefer self.allocator.free(owned_value);\n\n        try self.keys.append(self.allocator, owned_key);\n        try self.values.append(self.allocator, owned_value);\n    }\n\n    /// Adds a value to a header (allows multiple values for same key).\n    /// Key comparison is case-insensitive for grouping.\n    pub fn add(\n        self: *HeaderMap,\n        key: []const u8,\n        value: []const u8,\n    ) error{ InvalidHeader, OutOfMemory }!void {\n        headers.validateKeyValue(key, value) catch return error.InvalidHeader;\n\n        const owned_key = try self.allocator.dupe(u8, key);\n        errdefer self.allocator.free(owned_key);\n\n        const owned_value = try self.allocator.dupe(u8, value);\n        errdefer self.allocator.free(owned_value);\n\n        try self.keys.append(self.allocator, owned_key);\n        try self.values.append(self.allocator, owned_value);\n    }\n\n    /// Gets the first value for a header (case-insensitive lookup).\n    /// Returns null if header not found.\n    pub fn get(self: *const HeaderMap, key: []const u8) ?[]const u8 {\n        assert(key.len > 0);\n        for (self.keys.items, 0..) |k, i| {\n            if (std.ascii.eqlIgnoreCase(k, key)) {\n                return self.values.items[i];\n            }\n        }\n        return null;\n    }\n\n    /// Gets the last value for a header (case-insensitive lookup).\n    /// Useful when multiple values exist and you want the most recent.\n    /// Returns null if header not found.\n    pub fn getLast(self: *const HeaderMap, key: []const u8) ?[]const u8 {\n        assert(key.len > 0);\n        var last: ?[]const u8 = null;\n        for (self.keys.items, 0..) |k, i| {\n            if (std.ascii.eqlIgnoreCase(k, key)) {\n                last = self.values.items[i];\n            }\n        }\n        return last;\n    }\n\n    /// Gets all values for a header (case-insensitive lookup).\n    /// Caller owns returned slice, must free with allocator.\n    pub fn getAll(\n        self: *const HeaderMap,\n        key: []const u8,\n    ) Allocator.Error!?[]const []const u8 {\n        assert(key.len > 0);\n\n        var match_count: usize = 0;\n        for (self.keys.items) |k| {\n            if (std.ascii.eqlIgnoreCase(k, key)) {\n                match_count += 1;\n            }\n        }\n\n        if (match_count == 0) return null;\n\n        const result = try self.allocator.alloc(\n            []const u8,\n            match_count,\n        );\n        var idx: usize = 0;\n        for (self.keys.items, 0..) |k, i| {\n            if (std.ascii.eqlIgnoreCase(k, key)) {\n                result[idx] = self.values.items[i];\n                idx += 1;\n            }\n        }\n\n        return result;\n    }\n\n    /// Deletes all values for a header (case-insensitive).\n    pub fn delete(self: *HeaderMap, key: []const u8) void {\n        assert(key.len > 0);\n        self.deleteInternal(key);\n    }\n\n    fn deleteInternal(self: *HeaderMap, key: []const u8) void {\n        var i: usize = 0;\n        while (i < self.keys.items.len) {\n            if (std.ascii.eqlIgnoreCase(\n                self.keys.items[i],\n                key,\n            )) {\n                self.allocator.free(\n                    self.keys.orderedRemove(i),\n                );\n                self.allocator.free(\n                    self.values.orderedRemove(i),\n                );\n            } else {\n                i += 1;\n            }\n        }\n    }\n\n    /// Returns slice of all keys (for iteration).\n    /// Note: May contain duplicate keys if add() was used.\n    pub fn keys_slice(self: *const HeaderMap) []const []const u8 {\n        return @ptrCast(self.keys.items);\n    }\n\n    /// Returns the number of header entries.\n    /// Note: Same key may appear multiple times.\n    pub fn count(self: *const HeaderMap) usize {\n        return self.keys.items.len;\n    }\n\n    /// Returns true if the map is empty.\n    pub fn isEmpty(self: *const HeaderMap) bool {\n        return self.keys.items.len == 0;\n    }\n\n    /// Encodes headers to NATS/1.0 format.\n    /// Caller owns returned memory.\n    pub fn encode(self: *const HeaderMap) ![]u8 {\n        if (self.keys.items.len == 0) {\n            return error.EmptyHeaders;\n        }\n\n        // Calculate size\n        var size: usize = 10; // \"NATS/1.0\\r\\n\"\n        for (self.keys.items, 0..) |key, i| {\n            // \"Key: Value\\r\\n\"\n            size += key.len + 2 + self.values.items[i].len + 2;\n        }\n        size += 2; // final \"\\r\\n\"\n\n        const buf = try self.allocator.alloc(u8, size);\n        errdefer self.allocator.free(buf);\n\n        var pos: usize = 0;\n        @memcpy(buf[pos..][0..10], \"NATS/1.0\\r\\n\");\n        pos += 10;\n\n        for (self.keys.items, 0..) |key, i| {\n            const value = self.values.items[i];\n            @memcpy(buf[pos..][0..key.len], key);\n            pos += key.len;\n            @memcpy(buf[pos..][0..2], \": \");\n            pos += 2;\n            @memcpy(buf[pos..][0..value.len], value);\n            pos += value.len;\n            @memcpy(buf[pos..][0..2], \"\\r\\n\");\n            pos += 2;\n        }\n\n        @memcpy(buf[pos..][0..2], \"\\r\\n\");\n        pos += 2;\n\n        assert(pos == size);\n        return buf;\n    }\n\n    /// Returns the encoded size in bytes.\n    pub fn encodedSize(self: *const HeaderMap) usize {\n        if (self.keys.items.len == 0) return 0;\n\n        var size: usize = 10; // \"NATS/1.0\\r\\n\"\n        for (self.keys.items, 0..) |key, i| {\n            size += key.len + 2 + self.values.items[i].len + 2;\n        }\n        size += 2; // final \"\\r\\n\"\n        return size;\n    }\n};\n\n// Tests\n\ntest \"header map set and get\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.set(\"Content-Type\", \"application/json\");\n    try hm.set(\"X-Request-Id\", \"abc123\");\n\n    try std.testing.expectEqualStrings(\n        \"application/json\",\n        hm.get(\"Content-Type\").?,\n    );\n    try std.testing.expectEqualStrings(\n        \"abc123\",\n        hm.get(\"X-Request-Id\").?,\n    );\n    try std.testing.expectEqual(\n        @as(?[]const u8, null),\n        hm.get(\"Not-Found\"),\n    );\n}\n\ntest \"header map case insensitive get\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.set(\"Content-Type\", \"text/plain\");\n\n    try std.testing.expect(hm.get(\"content-type\") != null);\n    try std.testing.expect(hm.get(\"CONTENT-TYPE\") != null);\n    try std.testing.expect(hm.get(\"Content-Type\") != null);\n}\n\ntest \"header map set replaces existing\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.set(\"Key\", \"value1\");\n    try hm.set(\"Key\", \"value2\");\n\n    try std.testing.expectEqualStrings(\n        \"value2\",\n        hm.get(\"Key\").?,\n    );\n    try std.testing.expectEqual(@as(usize, 1), hm.count());\n}\n\ntest \"header map add multiple values\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.add(\"X-Tag\", \"important\");\n    try hm.add(\"X-Tag\", \"urgent\");\n    try hm.add(\"X-Tag\", \"review\");\n\n    try std.testing.expectEqual(@as(usize, 3), hm.count());\n\n    const all = try hm.getAll(\"X-Tag\");\n    defer allocator.free(all.?);\n\n    try std.testing.expectEqual(@as(usize, 3), all.?.len);\n    try std.testing.expectEqualStrings(\"important\", all.?[0]);\n    try std.testing.expectEqualStrings(\"urgent\", all.?[1]);\n    try std.testing.expectEqualStrings(\"review\", all.?[2]);\n}\n\ntest \"header map getLast\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.add(\"X-Tag\", \"first\");\n    try hm.add(\"X-Tag\", \"second\");\n    try hm.add(\"X-Tag\", \"third\");\n\n    // get() returns first, getLast() returns last\n    try std.testing.expectEqualStrings(\n        \"first\",\n        hm.get(\"X-Tag\").?,\n    );\n    try std.testing.expectEqualStrings(\n        \"third\",\n        hm.getLast(\"X-Tag\").?,\n    );\n\n    // Case insensitive\n    try std.testing.expectEqualStrings(\n        \"third\",\n        hm.getLast(\"x-tag\").?,\n    );\n\n    // Non-existent key returns null\n    try std.testing.expectEqual(\n        @as(?[]const u8, null),\n        hm.getLast(\"Not-Found\"),\n    );\n}\n\ntest \"header map delete\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.add(\"X-Tag\", \"value1\");\n    try hm.add(\"X-Tag\", \"value2\");\n    try hm.add(\"Other\", \"keep\");\n\n    hm.delete(\"X-Tag\");\n\n    try std.testing.expectEqual(\n        @as(?[]const u8, null),\n        hm.get(\"X-Tag\"),\n    );\n    try std.testing.expectEqualStrings(\n        \"keep\",\n        hm.get(\"Other\").?,\n    );\n    try std.testing.expectEqual(@as(usize, 1), hm.count());\n}\n\ntest \"header map encode\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.set(\"Foo\", \"bar\");\n    try hm.set(\"Baz\", \"123\");\n\n    const encoded = try hm.encode();\n    defer allocator.free(encoded);\n\n    try std.testing.expect(\n        std.mem.startsWith(u8, encoded, \"NATS/1.0\\r\\n\"),\n    );\n    try std.testing.expect(\n        std.mem.endsWith(u8, encoded, \"\\r\\n\\r\\n\"),\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, encoded, \"Foo: bar\\r\\n\") != null,\n    );\n    try std.testing.expect(\n        std.mem.indexOf(u8, encoded, \"Baz: 123\\r\\n\") != null,\n    );\n}\n\ntest \"header map encoded size\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.set(\"Foo\", \"bar\");\n\n    // \"NATS/1.0\\r\\n\" (10) + \"Foo: bar\\r\\n\" (10) + \"\\r\\n\" (2) = 22\n    try std.testing.expectEqual(\n        @as(usize, 22),\n        hm.encodedSize(),\n    );\n\n    const encoded = try hm.encode();\n    defer allocator.free(encoded);\n\n    try std.testing.expectEqual(\n        hm.encodedSize(),\n        encoded.len,\n    );\n}\n\ntest \"header map empty\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try std.testing.expect(hm.isEmpty());\n    try std.testing.expectEqual(\n        @as(usize, 0),\n        hm.count(),\n    );\n    try std.testing.expectEqual(\n        @as(usize, 0),\n        hm.encodedSize(),\n    );\n}\n\ntest \"header map with empty value\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try hm.set(\"Empty\", \"\");\n\n    try std.testing.expectEqualStrings(\n        \"\",\n        hm.get(\"Empty\").?,\n    );\n\n    const encoded = try hm.encode();\n    defer allocator.free(encoded);\n\n    try std.testing.expect(\n        std.mem.indexOf(u8, encoded, \"Empty: \\r\\n\") != null,\n    );\n}\n\ntest \"header map rejects injection-prone names and values\" {\n    const allocator = std.testing.allocator;\n    var hm = HeaderMap.init(allocator);\n    defer hm.deinit();\n\n    try std.testing.expectError(\n        error.InvalidHeader,\n        hm.set(\"Bad:Name\", \"value\"),\n    );\n    try std.testing.expectError(\n        error.InvalidHeader,\n        hm.add(\"Bad\\x7fName\", \"value\"),\n    );\n    try std.testing.expectError(\n        error.InvalidHeader,\n        hm.add(\"Good\", \"bad\\x7fvalue\"),\n    );\n}\n"
  },
  {
    "path": "src/protocol/headers.zig",
    "content": "//! NATS Protocol Headers\n//!\n//! Handles NATS message headers in the NATS/1.0 format.\n//! Headers are used with HPUB/HMSG commands for metadata.\n//!\n//! Features:\n//! - Full ownership: ParseResult copies all strings, safe after source freed\n//! - Case-insensitive header lookup\n//! - API: parse() function, always call deinit()\n//!\n//! Format:\n//! ```\n//! NATS/1.0\\r\\n\n//! Header-Name: value\\r\\n\n//! Another-Header: value\\r\\n\n//! \\r\\n\n//! ```\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\nconst Io = std.Io;\n\n/// Well-known NATS header names.\npub const HeaderName = struct {\n    pub const msg_id = \"Nats-Msg-Id\";\n    pub const expected_stream = \"Nats-Expected-Stream\";\n    pub const expected_last_msg_id = \"Nats-Expected-Last-Msg-Id\";\n    pub const expected_last_seq = \"Nats-Expected-Last-Sequence\";\n    pub const expected_last_subj_seq = \"Nats-Expected-Last-Subject-Sequence\";\n    pub const last_consumer = \"Nats-Last-Consumer\";\n    pub const last_stream = \"Nats-Last-Stream\";\n    pub const consumer_stalled = \"Nats-Consumer-Stalled\";\n    pub const rollup = \"Nats-Rollup\";\n    pub const msg_ttl = \"Nats-TTL\";\n    pub const no_responders = \"Status\";\n    pub const description = \"Description\";\n};\n\n/// Status codes returned in headers.\npub const Status = struct {\n    pub const no_responders = \"503\";\n    pub const request_timeout = \"408\";\n    pub const no_messages = \"404\";\n    pub const control_message = \"100\";\n};\n\n/// Extracts status code from raw header bytes without allocation.\n/// Returns null if no status code present or invalid format.\n/// Format expected: \"NATS/1.0 503 Description\\r\\n...\" or \"NATS/1.0\\r\\n...\"\npub fn extractStatus(header_data: []const u8) ?u16 {\n    // Minimum: \"NATS/1.0\\r\\n\" = 10 chars\n    if (header_data.len < 10) return null;\n\n    // Verify NATS/1.0 prefix\n    if (!std.mem.startsWith(u8, header_data, \"NATS/1.0\")) return null;\n\n    // Skip \"NATS/1.0\"\n    const after_version = header_data[8..];\n\n    // If next char is \\r, no status code\n    if (after_version.len == 0 or after_version[0] == '\\r') return null;\n\n    // Expect space before status code\n    if (after_version[0] != ' ') return null;\n\n    // Find end of status code (space or \\r)\n    const status_start = 1; // skip space\n    var status_end: usize = status_start;\n    while (status_end < after_version.len) : (status_end += 1) {\n        const c = after_version[status_end];\n        if (c == ' ' or c == '\\r') break;\n    }\n\n    if (status_end == status_start) return null;\n\n    const status_str = after_version[status_start..status_end];\n    return std.fmt.parseInt(u16, status_str, 10) catch null;\n}\n\n/// Header entry (key-value pair).\npub const Entry = struct {\n    key: []const u8,\n    value: []const u8,\n};\n\npub const ValidationError = error{\n    EmptyHeaders,\n    InvalidHeader,\n};\n\n/// Validates a header field name.\n/// Field names must be non-empty and cannot contain\n/// whitespace, control characters, DEL, or ':'.\npub fn validateKey(key: []const u8) ValidationError!void {\n    if (key.len == 0) return error.InvalidHeader;\n    for (key) |c| {\n        if (c <= 0x20 or c == 0x7f or c == ':') {\n            return error.InvalidHeader;\n        }\n    }\n}\n\n/// Validates a header field value.\n/// Values cannot contain control characters or DEL; this prevents\n/// CRLF injection when headers are serialized to the NATS wire format.\npub fn validateValue(value: []const u8) ValidationError!void {\n    for (value) |c| {\n        if (c < 0x20 or c == 0x7f) {\n            return error.InvalidHeader;\n        }\n    }\n}\n\npub fn validateKeyValue(\n    key: []const u8,\n    value: []const u8,\n) ValidationError!void {\n    try validateKey(key);\n    try validateValue(value);\n}\n\npub fn validateEntries(entries: []const Entry) ValidationError!void {\n    if (entries.len == 0) return error.EmptyHeaders;\n    for (entries) |entry| {\n        try validateKeyValue(entry.key, entry.value);\n    }\n}\n\n/// Result of header parsing.\n///\n/// Owns all its data - copies strings to heap. Safe to use after source\n/// data is freed. Caller MUST call deinit() to free memory.\npub const ParseResult = struct {\n    /// Heap-allocated entry array (owns this memory).\n    entries: []Entry = &.{},\n\n    /// Heap-allocated string buffer (all key/value strings copied here).\n    string_buf: []u8 = &.{},\n\n    /// Number of valid entries.\n    count: usize = 0,\n\n    /// Allocator used (needed for deinit).\n    allocator: Allocator,\n\n    /// Status code from header line (e.g., \"503\") - slice into string_buf.\n    status: ?[]const u8 = null,\n\n    /// Description from header line - slice into string_buf.\n    description: ?[]const u8 = null,\n\n    /// Byte offset where headers end in original data.\n    header_end: usize = 0,\n\n    /// Parse error if any.\n    err: ?ParseError = null,\n\n    pub const ParseError = enum {\n        invalid_version,\n        invalid_header,\n        incomplete,\n        out_of_memory,\n    };\n\n    /// Returns all parsed entries. Empty slice if error occurred.\n    pub fn items(self: *const ParseResult) []const Entry {\n        if (self.err != null) return &.{};\n        assert(self.count <= self.entries.len);\n        return self.entries[0..self.count];\n    }\n\n    /// Gets first value for header name (case-insensitive).\n    /// Returns null if error occurred or header not found.\n    pub fn get(self: *const ParseResult, name: []const u8) ?[]const u8 {\n        if (self.err != null) return null;\n        for (self.items()) |entry| {\n            if (std.ascii.eqlIgnoreCase(entry.key, name)) {\n                return entry.value;\n            }\n        }\n        return null;\n    }\n\n    /// Returns true if this is a no-responders status (503).\n    pub fn isNoResponders(self: *const ParseResult) bool {\n        if (self.err != null) return false;\n        if (self.status) |s| {\n            return std.mem.eql(u8, s, Status.no_responders);\n        }\n        return false;\n    }\n\n    /// Frees all heap-allocated memory.\n    /// Safe to call multiple times. MUST be called after parse().\n    pub fn deinit(self: *ParseResult) void {\n        if (self.entries.len > 0) {\n            self.allocator.free(self.entries);\n            self.entries = &.{};\n        }\n        if (self.string_buf.len > 0) {\n            self.allocator.free(self.string_buf);\n            self.string_buf = &.{};\n        }\n        self.count = 0;\n        self.status = null;\n        self.description = null;\n    }\n};\n\n/// Parses headers from NATS/1.0 format.\n///\n/// Allocates memory and copies all header data. ParseResult owns its data\n/// and is safe to use after the source data is freed.\n///\n/// Caller MUST call result.deinit() to free memory.\n///\n/// On error: result.err is set, items() returns empty, get() returns null.\npub fn parse(allocator: Allocator, data: []const u8) ParseResult {\n    assert(data.len > 0);\n    return parseImpl(allocator, data);\n}\n\nfn parseImpl(allocator: Allocator, data: []const u8) ParseResult {\n    var result: ParseResult = .{ .allocator = allocator };\n\n    if (!std.mem.startsWith(u8, data, \"NATS/1.0\")) {\n        result.err = .invalid_version;\n        return result;\n    }\n\n    // Pass 1: count headers and string bytes\n    var header_count: usize = 0;\n    var total_string_bytes: usize = 0;\n    var status_len: usize = 0;\n    var desc_len: usize = 0;\n\n    var pos: usize = 8;\n\n    // Parse optional status and description on first line\n    if (pos < data.len and data[pos] == ' ') {\n        pos += 1;\n        const status_end = std.mem.indexOfPos(u8, data, pos, \" \") orelse\n            std.mem.indexOfPos(u8, data, pos, \"\\r\\n\") orelse {\n            result.err = .incomplete;\n            return result;\n        };\n        status_len = status_end - pos;\n        total_string_bytes += status_len;\n        pos = status_end;\n\n        // Skip description if present\n        if (pos < data.len and data[pos] == ' ') {\n            pos += 1;\n            const desc_end = std.mem.indexOfPos(u8, data, pos, \"\\r\\n\") orelse {\n                result.err = .incomplete;\n                return result;\n            };\n            desc_len = desc_end - pos;\n            total_string_bytes += desc_len;\n            pos = desc_end;\n        }\n    }\n\n    // Skip \\r\\n after version line\n    if (pos + 2 > data.len or !std.mem.eql(u8, data[pos..][0..2], \"\\r\\n\")) {\n        result.err = .incomplete;\n        return result;\n    }\n    pos += 2;\n\n    // Count header entries and their string sizes\n    while (pos < data.len) {\n        // Empty line marks end of headers\n        if (std.mem.startsWith(u8, data[pos..], \"\\r\\n\")) {\n            result.header_end = pos + 2;\n            break;\n        }\n\n        // Find colon separator\n        const colon = std.mem.indexOfPos(u8, data, pos, \":\") orelse {\n            result.err = .invalid_header;\n            return result;\n        };\n\n        const key_len = colon - pos;\n\n        // Skip colon and optional space\n        var value_start = colon + 1;\n        if (value_start < data.len and data[value_start] == ' ') {\n            value_start += 1;\n        }\n\n        // Find end of line\n        const line_end = std.mem.indexOfPos(u8, data, value_start, \"\\r\\n\") orelse {\n            result.err = .incomplete;\n            return result;\n        };\n\n        const value_len = line_end - value_start;\n\n        header_count += 1;\n        // Guard against unbounded header allocation\n        if (header_count > 1024) {\n            result.err = .invalid_header;\n            return result;\n        }\n        total_string_bytes += key_len + value_len;\n\n        pos = line_end + 2;\n    } else {\n        // Didn't find terminating \\r\\n\\r\\n\n        result.err = .incomplete;\n        return result;\n    }\n\n    // Pass 2: allocate and copy\n    if (header_count > 0 or status_len > 0 or desc_len > 0) {\n        // Allocate entries array\n        const entries = allocator.alloc(Entry, header_count) catch {\n            result.err = .out_of_memory;\n            return result;\n        };\n\n        // Allocate string buffer\n        const string_buf = allocator.alloc(u8, total_string_bytes) catch {\n            allocator.free(entries);\n            result.err = .out_of_memory;\n            return result;\n        };\n\n        result.entries = entries;\n        result.string_buf = string_buf;\n\n        // Copy strings into buffer\n        var buf_pos: usize = 0;\n        var entry_idx: usize = 0;\n\n        pos = 8;\n\n        // Copy status and description\n        if (status_len > 0) {\n            pos += 1; // skip space\n            @memcpy(string_buf[buf_pos..][0..status_len], data[pos..][0..status_len]);\n            result.status = string_buf[buf_pos..][0..status_len];\n            buf_pos += status_len;\n            pos += status_len;\n\n            if (desc_len > 0) {\n                pos += 1; // skip space\n                @memcpy(\n                    string_buf[buf_pos..][0..desc_len],\n                    data[pos..][0..desc_len],\n                );\n                result.description = string_buf[buf_pos..][0..desc_len];\n                buf_pos += desc_len;\n                pos += desc_len;\n            }\n        }\n\n        // Skip \\r\\n after version line\n        pos = std.mem.indexOfPos(u8, data, 8, \"\\r\\n\").? + 2;\n\n        // Copy header entries\n        while (pos < data.len) {\n            if (std.mem.startsWith(u8, data[pos..], \"\\r\\n\")) {\n                break;\n            }\n\n            const colon = std.mem.indexOfPos(u8, data, pos, \":\").?;\n            const key_len = colon - pos;\n\n            // Copy key\n            @memcpy(string_buf[buf_pos..][0..key_len], data[pos..][0..key_len]);\n            const key_slice = string_buf[buf_pos..][0..key_len];\n            buf_pos += key_len;\n\n            // Skip colon and optional space\n            var value_start = colon + 1;\n            if (value_start < data.len and data[value_start] == ' ') {\n                value_start += 1;\n            }\n\n            const line_end = std.mem.indexOfPos(u8, data, value_start, \"\\r\\n\").?;\n            const value_len = line_end - value_start;\n\n            // Copy value\n            @memcpy(\n                string_buf[buf_pos..][0..value_len],\n                data[value_start..][0..value_len],\n            );\n            const value_slice = string_buf[buf_pos..][0..value_len];\n            buf_pos += value_len;\n\n            entries[entry_idx] = .{ .key = key_slice, .value = value_slice };\n            entry_idx += 1;\n\n            pos = line_end + 2;\n        }\n\n        result.count = entry_idx;\n        assert(entry_idx == header_count);\n        assert(buf_pos == total_string_bytes);\n    }\n\n    return result;\n}\n\n/// Encodes headers to NATS/1.0 format.\npub fn encode(\n    writer: *Io.Writer,\n    entries: []const Entry,\n) Io.Writer.Error!void {\n    assert(entries.len > 0);\n    try writer.writeAll(\"NATS/1.0\\r\\n\");\n\n    for (entries) |entry| {\n        try writer.writeAll(entry.key);\n        try writer.writeAll(\": \");\n        try writer.writeAll(entry.value);\n        try writer.writeAll(\"\\r\\n\");\n    }\n\n    try writer.writeAll(\"\\r\\n\");\n}\n\n/// Encodes headers directly into a pre-sized byte buffer.\n/// Buffer must be exactly encodedSize(entries) bytes.\npub fn encodeToBuf(\n    buf: []u8,\n    entries: []const Entry,\n) void {\n    assert(entries.len > 0);\n    var pos: usize = 0;\n\n    @memcpy(buf[pos..][0..10], \"NATS/1.0\\r\\n\");\n    pos += 10;\n\n    for (entries) |entry| {\n        @memcpy(buf[pos..][0..entry.key.len], entry.key);\n        pos += entry.key.len;\n        @memcpy(buf[pos..][0..2], \": \");\n        pos += 2;\n        @memcpy(\n            buf[pos..][0..entry.value.len],\n            entry.value,\n        );\n        pos += entry.value.len;\n        @memcpy(buf[pos..][0..2], \"\\r\\n\");\n        pos += 2;\n    }\n\n    @memcpy(buf[pos..][0..2], \"\\r\\n\");\n}\n\n/// Calculates the encoded size of headers.\npub fn encodedSize(entries: []const Entry) usize {\n    assert(entries.len > 0);\n    var size: usize = 10; // \"NATS/1.0\\r\\n\"\n\n    for (entries) |entry| {\n        size += entry.key.len + 2 + entry.value.len + 2;\n    }\n\n    size += 2; // final \\r\\n\n    return size;\n}\n\n// Tests\n\ntest \"parse simple headers\" {\n    const data = \"NATS/1.0\\r\\nFoo: bar\\r\\nBaz: qux\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqual(@as(usize, 2), result.count);\n    try std.testing.expectEqualSlices(u8, \"Foo\", result.items()[0].key);\n    try std.testing.expectEqualSlices(u8, \"bar\", result.items()[0].value);\n    try std.testing.expectEqualSlices(u8, \"Baz\", result.items()[1].key);\n    try std.testing.expectEqualSlices(u8, \"qux\", result.items()[1].value);\n}\n\ntest \"parse with status\" {\n    const data = \"NATS/1.0 503 No Responders\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqualSlices(u8, \"503\", result.status.?);\n    try std.testing.expectEqualSlices(u8, \"No Responders\", result.description.?);\n    try std.testing.expect(result.isNoResponders());\n}\n\ntest \"parse no headers\" {\n    const data = \"NATS/1.0\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqual(@as(usize, 0), result.count);\n}\n\ntest \"get header case insensitive\" {\n    const data = \"NATS/1.0\\r\\nContent-Type: application/json\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expect(result.get(\"content-type\") != null);\n    try std.testing.expect(result.get(\"CONTENT-TYPE\") != null);\n    try std.testing.expect(result.get(\"Content-Type\") != null);\n}\n\ntest \"parse many headers\" {\n    // Build 100 headers dynamically\n    var data_buf: [4096]u8 = undefined;\n    var pos: usize = 0;\n\n    const prefix = \"NATS/1.0\\r\\n\";\n    @memcpy(data_buf[pos..][0..prefix.len], prefix);\n    pos += prefix.len;\n\n    for (0..100) |i| {\n        const written = std.fmt.bufPrint(\n            data_buf[pos..],\n            \"H{d:0>3}: value{d}\\r\\n\",\n            .{ i, i },\n        ) catch unreachable;\n        pos += written.len;\n    }\n    @memcpy(data_buf[pos..][0..2], \"\\r\\n\");\n    pos += 2;\n\n    var result = parse(std.testing.allocator, data_buf[0..pos]);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqual(@as(usize, 100), result.count);\n    try std.testing.expect(result.get(\"H000\") != null);\n    try std.testing.expect(result.get(\"H099\") != null);\n}\n\ntest \"parsed data survives after source freed\" {\n    // This test verifies ownership - parsed data is independent\n    const data = try std.testing.allocator.dupe(u8, \"NATS/1.0\\r\\nKey: value\\r\\n\\r\\n\");\n\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    // Free source data\n    std.testing.allocator.free(data);\n\n    // ParseResult should still work (owns copies)\n    try std.testing.expectEqualSlices(u8, \"Key\", result.items()[0].key);\n    try std.testing.expectEqualSlices(u8, \"value\", result.items()[0].value);\n}\n\ntest \"error returns empty items\" {\n    const data = \"INVALID\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expect(result.err != null);\n    try std.testing.expectEqual(@as(usize, 0), result.items().len);\n    try std.testing.expect(result.get(\"anything\") == null);\n}\n\ntest \"deinit is safe to call multiple times\" {\n    const data = \"NATS/1.0\\r\\nFoo: bar\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n\n    result.deinit();\n    result.deinit();\n    result.deinit();\n}\n\ntest \"encode headers\" {\n    var buf: [256]u8 = undefined;\n    var writer = Io.Writer.fixed(&buf);\n\n    const entries = [_]Entry{\n        .{ .key = \"Foo\", .value = \"bar\" },\n        .{ .key = \"Baz\", .value = \"123\" },\n    };\n\n    try encode(&writer, &entries);\n    try std.testing.expectEqualSlices(\n        u8,\n        \"NATS/1.0\\r\\nFoo: bar\\r\\nBaz: 123\\r\\n\\r\\n\",\n        writer.buffered(),\n    );\n}\n\ntest \"encoded size\" {\n    const entries = [_]Entry{\n        .{ .key = \"Foo\", .value = \"bar\" },\n    };\n\n    const size = encodedSize(&entries);\n    try std.testing.expectEqual(@as(usize, 22), size);\n}\n\ntest \"parse status only no description\" {\n    const data = \"NATS/1.0 503\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqualSlices(u8, \"503\", result.status.?);\n    try std.testing.expect(result.description == null);\n    try std.testing.expect(result.isNoResponders());\n}\n\ntest \"parse status with headers\" {\n    const data = \"NATS/1.0 100 Idle Heartbeat\\r\\nNats-Last-Consumer: 42\\r\\n\\r\\n\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqualSlices(u8, \"100\", result.status.?);\n    try std.testing.expectEqualSlices(u8, \"Idle Heartbeat\", result.description.?);\n    try std.testing.expectEqual(@as(usize, 1), result.count);\n    try std.testing.expectEqualSlices(u8, \"42\", result.get(\"Nats-Last-Consumer\").?);\n}\n\ntest \"header_end is set correctly\" {\n    // \"NATS/1.0\\r\\nFoo: bar\\r\\n\\r\\n\" = 8 + 2 + 8 + 2 + 2 = 22 bytes\n    const data = \"NATS/1.0\\r\\nFoo: bar\\r\\n\\r\\npayload here\";\n    var result = parse(std.testing.allocator, data);\n    defer result.deinit();\n\n    try std.testing.expectEqual(@as(?ParseResult.ParseError, null), result.err);\n    try std.testing.expectEqual(@as(usize, 22), result.header_end);\n    try std.testing.expectEqualSlices(u8, \"payload here\", data[result.header_end..]);\n}\n\ntest \"extractStatus returns 503\" {\n    const data = \"NATS/1.0 503 No Responders\\r\\n\\r\\n\";\n    try std.testing.expectEqual(@as(?u16, 503), extractStatus(data));\n}\n\ntest \"extractStatus returns 408\" {\n    const data = \"NATS/1.0 408 Request Timeout\\r\\n\\r\\n\";\n    try std.testing.expectEqual(@as(?u16, 408), extractStatus(data));\n}\n\ntest \"extractStatus returns 100\" {\n    const data = \"NATS/1.0 100 Idle Heartbeat\\r\\nHeader: value\\r\\n\\r\\n\";\n    try std.testing.expectEqual(@as(?u16, 100), extractStatus(data));\n}\n\ntest \"extractStatus returns null for no status\" {\n    const data = \"NATS/1.0\\r\\nFoo: bar\\r\\n\\r\\n\";\n    try std.testing.expectEqual(@as(?u16, null), extractStatus(data));\n}\n\ntest \"extractStatus returns null for invalid prefix\" {\n    const data = \"HTTP/1.0 200 OK\\r\\n\\r\\n\";\n    try std.testing.expectEqual(@as(?u16, null), extractStatus(data));\n}\n\ntest \"extractStatus returns null for short data\" {\n    const data = \"NATS\";\n    try std.testing.expectEqual(@as(?u16, null), extractStatus(data));\n}\n\ntest \"extractStatus handles status without description\" {\n    const data = \"NATS/1.0 503\\r\\n\\r\\n\";\n    try std.testing.expectEqual(@as(?u16, 503), extractStatus(data));\n}\n"
  },
  {
    "path": "src/protocol/parser.zig",
    "content": "//! NATS Protocol Parser\n//!\n//! Parses incoming data from NATS server into structured commands.\n//! Handles streaming data that may arrive in partial chunks.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\n\nconst commands = @import(\"commands.zig\");\nconst ServerCommand = commands.ServerCommand;\nconst RawServerInfo = commands.RawServerInfo;\nconst ServerInfo = commands.ServerInfo;\nconst MsgArgs = commands.MsgArgs;\nconst HMsgArgs = commands.HMsgArgs;\n\n/// Fast decimal parser for u64. Inlined for hot path performance.\n/// Uses wrapping math with length guard to prevent overflow.\npub inline fn parseU64Fast(s: []const u8) error{\n    InvalidCharacter,\n    Overflow,\n}!u64 {\n    assert(s.len > 0);\n    if (s.len >= 20) return error.Overflow;\n    var v: u64 = 0;\n    for (s) |c| {\n        if (c < '0' or c > '9') return error.InvalidCharacter;\n        v = v *% 10 +% @as(u64, c - '0');\n    }\n    return v;\n}\n\n/// Fast decimal parser for usize. Inlined for hot path performance.\n/// Uses wrapping math with length guard to prevent overflow.\npub inline fn parseUsizeFast(s: []const u8) error{\n    InvalidCharacter,\n    Overflow,\n}!usize {\n    assert(s.len > 0);\n    // REVIEWED(2025-03): Guard is correct for 64-bit targets.\n    // On 32-bit usize, 10-digit values could wrap, but this\n    // library targets 64-bit only. max_payload check catches\n    // any practical overflow.\n    if (s.len >= 20) return error.Overflow;\n    var v: usize = 0;\n    for (s) |c| {\n        if (c < '0' or c > '9') return error.InvalidCharacter;\n        v = v *% 10 +% @as(usize, c - '0');\n    }\n    return v;\n}\n\n/// Fast \\r\\n finder optimized for short NATS lines (~30 bytes).\n/// Scans for '\\r' then checks next byte, avoiding 2-byte pattern overhead.\ninline fn findCRLF(data: []const u8) ?usize {\n    if (data.len < 2) return null;\n    const end = data.len - 1;\n    var i: usize = 0;\n    while (i < end) : (i += 1) {\n        if (data[i] == '\\r' and data[i + 1] == '\\n') return i;\n    }\n    return null;\n}\n\n/// Protocol parser for NATS server commands.\n///\n/// Stateless single-pass parser - no multi-stage state machine needed.\n/// Handles partial data by returning null (need more bytes).\n/// Allocates only for INFO command (ServerInfo string copies).\npub const Parser = struct {\n    /// Max payload size from server INFO (DoS guard).\n    max_payload: usize = 1048576,\n\n    /// Parse error types.\n    pub const Error = error{\n        InvalidCommand,\n        InvalidArguments,\n        PayloadTooLarge,\n        InvalidJson,\n        InvalidServerInfo,\n    };\n\n    /// Creates a new parser.\n    pub fn init() Parser {\n        return .{};\n    }\n\n    /// Resets the parser to initial state (no-op for stateless parser).\n    pub fn reset(self: *Parser) void {\n        _ = self;\n    }\n\n    /// Parses data and returns a command if complete.\n    /// Returns null if no complete command is available (need more data).\n    /// Sets consumed to the number of bytes consumed (0 if need more data).\n    pub fn parse(\n        self: *Parser,\n        allocator: Allocator,\n        data: []const u8,\n        consumed: *usize,\n    ) (Error || Allocator.Error)!?ServerCommand {\n        consumed.* = 0;\n\n        if (data.len == 0) return null;\n\n        const line_end = findCRLF(data) orelse return null;\n        const line = data[0..line_end];\n        const header_len = line_end + 2;\n\n        if (line.len == 0) return Parser.Error.InvalidCommand;\n\n        // u32 word comparison for dispatch\n        const CMD_MSG: u32 = 0x2047534D; // \"MSG \" in little-endian\n        const CMD_PING: u32 = 0x474E4950; // \"PING\" in little-endian\n        const CMD_PONG: u32 = 0x474E4F50; // \"PONG\" in little-endian\n        const CMD_INFO: u32 = 0x4F464E49; // \"INFO\" in little-endian\n        const CMD_HMSG: u32 = 0x47534D48; // \"HMSG\" in little-endian\n        const CMD_ERR: u32 = 0x5252452D; // \"-ERR\" in little-endian\n\n        if (line.len >= 4) {\n            const cmd = std.mem.readInt(u32, line[0..4], .little);\n\n            // MSG <subject> <sid> [reply-to] <size>\n            if (cmd == CMD_MSG) {\n                return parseFullMsgFast(data, line[4..], header_len, consumed, self.max_payload);\n            }\n            // PING (exact 4 chars)\n            if (cmd == CMD_PING and line.len == 4) {\n                consumed.* = header_len;\n                return .ping;\n            }\n            // PONG (exact 4 chars)\n            if (cmd == CMD_PONG and line.len == 4) {\n                consumed.* = header_len;\n                return .pong;\n            }\n            // INFO <json> (need 5th char to be space)\n            if (cmd == CMD_INFO and line.len >= 5 and line[4] == ' ') {\n                const json_data = line[5..];\n                var parsed = std.json.parseFromSlice(\n                    RawServerInfo,\n                    allocator,\n                    json_data,\n                    .{ .ignore_unknown_fields = true },\n                ) catch return Error.InvalidJson;\n                defer parsed.deinit();\n\n                const owned = ServerInfo.fromParsed(\n                    allocator,\n                    parsed,\n                ) catch |err| return switch (err) {\n                    error.OutOfMemory => error.OutOfMemory,\n                    error.InvalidServerInfo => Error.InvalidServerInfo,\n                };\n                consumed.* = header_len;\n                return .{ .info = owned };\n            }\n            // HMSG <subject> <sid> [reply-to] <hdr_len> <total_len>\n            if (cmd == CMD_HMSG and line.len >= 5 and line[4] == ' ') {\n                return parseFullHMsgFast(data, line[5..], header_len, consumed, self.max_payload);\n            }\n            // -ERR <message>\n            if (cmd == CMD_ERR and line.len >= 5 and line[4] == ' ') {\n                consumed.* = header_len;\n                return .{ .err = line[5..] };\n            }\n        }\n\n        if (line.len == 3 and line[0] == '+' and line[1] == 'O' and\n            line[2] == 'K')\n        {\n            consumed.* = header_len;\n            return .ok;\n        }\n\n        return Error.InvalidCommand;\n    }\n};\n\n/// Verify trailing CRLF using u16 comparison (little-endian).\ninline fn verifyCRLF(data: []const u8, offset: usize) bool {\n    if (offset + 2 > data.len) return false;\n    const word = @as(u16, @bitCast([2]u8{ data[offset], data[offset + 1] }));\n    return word == 0x0A0D;\n}\n\n/// Parse complete MSG using manual byte scanning (no iterator allocation).\n/// Returns null if payload not yet available.\ninline fn parseFullMsgFast(\n    data: []const u8,\n    args_line: []const u8,\n    header_len: usize,\n    consumed: *usize,\n    max_payload: usize,\n) Parser.Error!?ServerCommand {\n    if (args_line.len == 0)\n        return Parser.Error.InvalidArguments;\n    assert(header_len > 0);\n\n    var i: usize = 0;\n\n    // Parse subject (scan to first space)\n    const subj_start = i;\n    while (i < args_line.len and args_line[i] != ' ') : (i += 1) {}\n    if (i == subj_start or i >= args_line.len) {\n        return Parser.Error.InvalidArguments;\n    }\n    const subject = args_line[subj_start..i];\n    i += 1; // skip space\n\n    // Parse SID inline (avoids separate function call overhead)\n    var sid: u64 = 0;\n    var sid_digits: u8 = 0;\n    while (i < args_line.len and args_line[i] != ' ') : (i += 1) {\n        const c = args_line[i];\n        if (c < '0' or c > '9') return Parser.Error.InvalidArguments;\n        sid_digits += 1;\n        // u64 max is 20 digits; reject at >= 20 to prevent overflow\n        if (sid_digits >= 20) return Parser.Error.InvalidArguments;\n        sid = sid *% 10 +% @as(u64, c - '0');\n    }\n    if (sid_digits == 0 or sid == 0) return Parser.Error.InvalidArguments;\n\n    // Check if there's more to parse\n    if (i >= args_line.len) return Parser.Error.InvalidArguments;\n    i += 1; // skip space\n\n    // Parse third token\n    const t3_start = i;\n    while (i < args_line.len and args_line[i] != ' ') : (i += 1) {}\n    if (i == t3_start) return Parser.Error.InvalidArguments;\n    const third = args_line[t3_start..i];\n\n    // Check for optional fourth token (reply-to case)\n    var reply_to: ?[]const u8 = null;\n    var payload_len_slice: []const u8 = undefined;\n\n    if (i < args_line.len and args_line[i] == ' ') {\n        i += 1; // skip space\n        reply_to = third;\n        const t4_start = i;\n        while (i < args_line.len and args_line[i] != ' ') : (i += 1) {}\n        if (i == t4_start) return Parser.Error.InvalidArguments;\n        payload_len_slice = args_line[t4_start..i];\n    } else {\n        payload_len_slice = third;\n    }\n\n    // Parse payload length inline (with overflow guard)\n    if (payload_len_slice.len >= 20) return Parser.Error.InvalidArguments;\n    var payload_len: usize = 0;\n    for (payload_len_slice) |c| {\n        if (c < '0' or c > '9') return Parser.Error.InvalidArguments;\n        payload_len = payload_len *% 10 +% @as(usize, c - '0');\n    }\n\n    if (payload_len > max_payload)\n        return Parser.Error.PayloadTooLarge;\n\n    // Calculate total message size: header + payload + trailing \\r\\n\n    const total_len = header_len + payload_len + 2;\n\n    // Check for complete message\n    if (data.len < total_len) return null;\n\n    // Verify trailing CRLF with u16 comparison\n    if (!verifyCRLF(data, header_len + payload_len)) {\n        return Parser.Error.InvalidArguments;\n    }\n\n    // Extract payload - it's right after the header\n    const payload = data[header_len..][0..payload_len];\n\n    consumed.* = total_len;\n    assert(consumed.* <= data.len);\n    assert(subject.len > 0);\n    assert(sid > 0);\n\n    return .{ .msg = .{\n        .subject = subject,\n        .sid = sid,\n        .reply_to = reply_to,\n        .payload_len = payload_len,\n        .payload = payload,\n    } };\n}\n\n/// Parse complete MSG in single pass (legacy, kept for test compatibility).\n/// Returns null if payload not yet available.\ninline fn parseFullMsg(\n    data: []const u8,\n    args_line: []const u8,\n    header_len: usize,\n    consumed: *usize,\n) Parser.Error!?ServerCommand {\n    return parseFullMsgFast(data, args_line, header_len, consumed, 1048576);\n}\n\n/// Parse complete HMSG using manual byte scanning (no iterator allocation).\n/// Returns null if headers/payload not yet available.\ninline fn parseFullHMsgFast(\n    data: []const u8,\n    args_line: []const u8,\n    header_len: usize,\n    consumed: *usize,\n    max_payload: usize,\n) Parser.Error!?ServerCommand {\n    if (args_line.len == 0)\n        return Parser.Error.InvalidArguments;\n    assert(header_len > 0);\n\n    var i: usize = 0;\n\n    // Parse subject (scan to first space)\n    const subj_start = i;\n    while (i < args_line.len and args_line[i] != ' ') : (i += 1) {}\n    if (i == subj_start or i >= args_line.len) {\n        return Parser.Error.InvalidArguments;\n    }\n    const subject = args_line[subj_start..i];\n    i += 1; // skip space\n\n    // Parse SID inline (with overflow guard)\n    var sid: u64 = 0;\n    var sid_digits: u8 = 0;\n    while (i < args_line.len and args_line[i] != ' ') : (i += 1) {\n        const c = args_line[i];\n        if (c < '0' or c > '9') return Parser.Error.InvalidArguments;\n        sid_digits += 1;\n        // u64 max is 20 digits; reject at >= 20 to prevent overflow\n        if (sid_digits >= 20) return Parser.Error.InvalidArguments;\n        sid = sid *% 10 +% @as(u64, c - '0');\n    }\n    if (sid_digits == 0 or sid == 0 or i >= args_line.len)\n        return Parser.Error.InvalidArguments;\n    i += 1; // skip space\n\n    // Collect remaining tokens (2 or 3: [reply-to] hdr_len total_len)\n    var tokens: [3][]const u8 = undefined;\n    var token_count: usize = 0;\n\n    while (i < args_line.len and token_count < 3) {\n        const t_start = i;\n        while (i < args_line.len and args_line[i] != ' ') : (i += 1) {}\n        if (i == t_start) break;\n        tokens[token_count] = args_line[t_start..i];\n        token_count += 1;\n        if (i < args_line.len and args_line[i] == ' ') i += 1;\n    }\n\n    if (token_count < 2) return Parser.Error.InvalidArguments;\n\n    var reply_to: ?[]const u8 = null;\n    var hdr_len_slice: []const u8 = undefined;\n    var total_len_slice: []const u8 = undefined;\n\n    if (token_count == 3) {\n        reply_to = tokens[0];\n        hdr_len_slice = tokens[1];\n        total_len_slice = tokens[2];\n    } else {\n        hdr_len_slice = tokens[0];\n        total_len_slice = tokens[1];\n    }\n\n    // Parse hdr_len inline (with overflow guard)\n    if (hdr_len_slice.len >= 20) return Parser.Error.InvalidArguments;\n    var hdr_len: usize = 0;\n    for (hdr_len_slice) |c| {\n        if (c < '0' or c > '9') return Parser.Error.InvalidArguments;\n        hdr_len = hdr_len *% 10 +% @as(usize, c - '0');\n    }\n\n    // Parse total_content_len inline (with overflow guard)\n    if (total_len_slice.len >= 20) return Parser.Error.InvalidArguments;\n    var total_content_len: usize = 0;\n    for (total_len_slice) |c| {\n        if (c < '0' or c > '9') return Parser.Error.InvalidArguments;\n        total_content_len = total_content_len *% 10 +% @as(usize, c - '0');\n    }\n\n    if (hdr_len > total_content_len) return Parser.Error.InvalidArguments;\n    if (total_content_len > max_payload)\n        return Parser.Error.PayloadTooLarge;\n\n    // Calculate total message size: header line + content + trailing \\r\\n\n    const total_len = header_len + total_content_len + 2;\n\n    // Check for complete message\n    if (data.len < total_len) return null;\n\n    // Verify trailing CRLF with u16 comparison\n    if (!verifyCRLF(data, header_len + total_content_len)) {\n        return Parser.Error.InvalidArguments;\n    }\n\n    // Extract headers and payload - they're right after the header line\n    const headers = data[header_len..][0..hdr_len];\n    const payload_len = total_content_len - hdr_len;\n    const payload = data[header_len + hdr_len ..][0..payload_len];\n\n    consumed.* = total_len;\n    assert(consumed.* <= data.len);\n    assert(subject.len > 0);\n    assert(sid > 0);\n    assert(hdr_len <= total_content_len);\n\n    return .{ .hmsg = .{\n        .subject = subject,\n        .sid = sid,\n        .reply_to = reply_to,\n        .header_len = hdr_len,\n        .total_len = total_content_len,\n        .headers = headers,\n        .payload = payload,\n    } };\n}\n\n/// Parse complete HMSG in single pass (legacy, kept for test compatibility).\n/// Returns null if headers/payload not yet available.\ninline fn parseFullHMsg(\n    data: []const u8,\n    args_line: []const u8,\n    header_len: usize,\n    consumed: *usize,\n) Parser.Error!?ServerCommand {\n    return parseFullHMsgFast(data, args_line, header_len, consumed, 1048576);\n}\n\ntest {\n    _ = @import(\"parser_test.zig\");\n}\n"
  },
  {
    "path": "src/protocol/parser_test.zig",
    "content": "//! Parser Edge Case Tests\n//!\n//! - Integer parsing edge cases (overflow, boundaries, invalid chars)\n//! - MSG/HMSG parsing (truncated, malformed, edge values)\n//! - INFO JSON parsing (invalid JSON, type mismatches, overflow)\n//! - Command dispatch (case sensitivity, prefix validation)\n//! - CRLF verification and buffer boundaries\n\nconst std = @import(\"std\");\nconst parser = @import(\"parser.zig\");\nconst Parser = parser.Parser;\nconst parseU64Fast = parser.parseU64Fast;\nconst parseUsizeFast = parser.parseUsizeFast;\nconst ServerCommand = @import(\"commands.zig\").ServerCommand;\n\n// Section 1: Existing Tests (moved from parser.zig)\n\ntest \"parse PING\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"PING\\r\\n\",\n        &consumed,\n    );\n\n    try std.testing.expectEqual(@as(usize, 6), consumed);\n    try std.testing.expectEqual(ServerCommand.ping, result.?);\n}\n\ntest \"parse PONG\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"PONG\\r\\n\",\n        &consumed,\n    );\n\n    try std.testing.expectEqual(@as(usize, 6), consumed);\n    try std.testing.expectEqual(ServerCommand.pong, result.?);\n}\n\ntest \"parse +OK\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"+OK\\r\\n\",\n        &consumed,\n    );\n\n    try std.testing.expectEqual(ServerCommand.ok, result.?);\n}\n\ntest \"parse -ERR\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"-ERR 'Authorization Violation'\\r\\n\",\n        &consumed,\n    );\n\n    try std.testing.expectEqualSlices(\n        u8,\n        \"'Authorization Violation'\",\n        result.?.err,\n    );\n}\n\ntest \"parse MSG without payload\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG test.subject 1 0\\r\\n\\r\\n\",\n        &consumed,\n    );\n\n    const msg = result.?.msg;\n    try std.testing.expectEqualSlices(u8, \"test.subject\", msg.subject);\n    try std.testing.expectEqual(@as(u64, 1), msg.sid);\n    try std.testing.expectEqual(@as(usize, 0), msg.payload_len);\n}\n\ntest \"parse MSG with payload\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"MSG test.subject 42 5\\r\\nhello\\r\\n\";\n\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n    try std.testing.expect(result != null);\n\n    const msg = result.?.msg;\n    try std.testing.expectEqualSlices(u8, \"test.subject\", msg.subject);\n    try std.testing.expectEqual(@as(u64, 42), msg.sid);\n    try std.testing.expectEqualSlices(u8, \"hello\", msg.payload);\n    try std.testing.expectEqual(@as(usize, 30), consumed);\n}\n\ntest \"parse MSG with payload - partial data\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const partial = \"MSG test.subject 42 5\\r\\nhel\";\n    var result = try p.parse(std.testing.allocator, partial, &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n\n    const full = \"MSG test.subject 42 5\\r\\nhello\\r\\n\";\n    result = try p.parse(std.testing.allocator, full, &consumed);\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"hello\", result.?.msg.payload);\n}\n\ntest \"parse MSG with reply-to\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"MSG test.subject 1 _INBOX.123 5\\r\\nworld\\r\\n\";\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n\n    const msg = result.?.msg;\n    try std.testing.expectEqualSlices(u8, \"_INBOX.123\", msg.reply_to.?);\n    try std.testing.expectEqualSlices(u8, \"world\", msg.payload);\n}\n\ntest \"parse incomplete data returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(std.testing.allocator, \"PIN\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n}\n\ntest \"parse INFO\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const info_json = \"INFO {\\\"server_id\\\":\\\"test\\\",\" ++\n        \"\\\"version\\\":\\\"2.10.0\\\",\\\"proto\\\":1,\\\"max_payload\\\":1048576}\\r\\n\";\n\n    const alloc = std.testing.allocator;\n    const result = try p.parse(alloc, info_json, &consumed);\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n\n    try std.testing.expect(result != null);\n    const info = result.?.info;\n    try std.testing.expectEqualSlices(u8, \"test\", info.server_id);\n    try std.testing.expectEqualSlices(u8, \"2.10.0\", info.version);\n    try std.testing.expectEqual(@as(u32, 1048576), info.max_payload);\n}\n\ntest \"parse HMSG without payload\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"HMSG test.subject 1 12 12\\r\\nNATS/1.0\\r\\n\\r\\n\\r\\n\";\n\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n    try std.testing.expect(result != null);\n\n    const hmsg = result.?.hmsg;\n    try std.testing.expectEqualSlices(u8, \"test.subject\", hmsg.subject);\n    try std.testing.expectEqual(@as(u64, 1), hmsg.sid);\n    try std.testing.expectEqual(@as(usize, 12), hmsg.header_len);\n    try std.testing.expectEqual(@as(usize, 12), hmsg.total_len);\n}\n\ntest \"parse HMSG with payload\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"HMSG test.subject 42 12 17\\r\\nNATS/1.0\\r\\n\\r\\nhello\\r\\n\";\n\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n    try std.testing.expect(result != null);\n\n    const hmsg = result.?.hmsg;\n    try std.testing.expectEqualSlices(u8, \"test.subject\", hmsg.subject);\n    try std.testing.expectEqual(@as(u64, 42), hmsg.sid);\n    try std.testing.expectEqualSlices(u8, \"NATS/1.0\\r\\n\\r\\n\", hmsg.headers);\n    try std.testing.expectEqualSlices(u8, \"hello\", hmsg.payload);\n}\n\ntest \"parse invalid command\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const alloc = std.testing.allocator;\n    const result = p.parse(alloc, \"INVALID\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parseU64Fast valid numbers\" {\n    try std.testing.expectEqual(@as(u64, 0), try parseU64Fast(\"0\"));\n    try std.testing.expectEqual(@as(u64, 1), try parseU64Fast(\"1\"));\n    try std.testing.expectEqual(@as(u64, 123), try parseU64Fast(\"123\"));\n    try std.testing.expectEqual(@as(u64, 999999), try parseU64Fast(\"999999\"));\n}\n\ntest \"parseU64Fast invalid input\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"abc\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12a3\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"-1\"));\n}\n\ntest \"parseUsizeFast valid numbers\" {\n    try std.testing.expectEqual(@as(usize, 0), try parseUsizeFast(\"0\"));\n    try std.testing.expectEqual(@as(usize, 42), try parseUsizeFast(\"42\"));\n    const large: usize = 1048576;\n    try std.testing.expectEqual(large, try parseUsizeFast(\"1048576\"));\n}\n\ntest \"parseUsizeFast invalid input\" {\n    try std.testing.expectError(error.InvalidCharacter, parseUsizeFast(\"xyz\"));\n    try std.testing.expectError(error.InvalidCharacter, parseUsizeFast(\"1 2\"));\n}\n\ntest \"parseU64Fast overflow protection\" {\n    // 21 digits - guaranteed overflow, should be rejected\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"123456789012345678901\"),\n    );\n    // 20 digits now rejected (overflow guard)\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"18446744073709551615\"),\n    );\n    // 20 zeros also rejected\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"00000000000000000000\"),\n    );\n}\n\ntest \"parseUsizeFast overflow protection\" {\n    // 21 digits - guaranteed overflow, should be rejected\n    try std.testing.expectError(\n        error.Overflow,\n        parseUsizeFast(\"123456789012345678901\"),\n    );\n    // 20 digits now rejected (overflow guard)\n    try std.testing.expectError(\n        error.Overflow,\n        parseUsizeFast(\"18446744073709551615\"),\n    );\n}\n\ntest \"parse MSG with SID=0 rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG test.subject 0 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"parse HMSG with SID=0 rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG test.subject 0 12 12\\r\\nNATS/1.0\\r\\n\\r\\n\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\n// Section 2: Integer Parsing Edge Cases (parseU64Fast / parseUsizeFast)\n\ntest \"parseU64Fast u64 max value rejected\" {\n    // u64 max = 18446744073709551615 (20 digits, now rejected)\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"18446744073709551615\"),\n    );\n}\n\ntest \"parseU64Fast u64 max plus one rejected\" {\n    // 20 digits rejected by guard\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"18446744073709551616\"),\n    );\n}\n\ntest \"parseU64Fast 19 digit large value\" {\n    const result = try parseU64Fast(\"9999999999999999999\");\n    try std.testing.expectEqual(@as(u64, 9999999999999999999), result);\n}\n\ntest \"parseU64Fast leading zeros preserved value\" {\n    // Leading zeros should parse correctly\n    try std.testing.expectEqual(@as(u64, 1), try parseU64Fast(\"0000000000000000001\"));\n    try std.testing.expectEqual(@as(u64, 42), try parseU64Fast(\"0000000000000000042\"));\n    try std.testing.expectEqual(@as(u64, 0), try parseU64Fast(\"0000000000000000000\"));\n}\n\ntest \"parseU64Fast 20 chars overflow\" {\n    // 20+ chars always rejected (prevents wrapping)\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"00000000000000000000\"),\n    );\n}\n\ntest \"parseU64Fast 21 zeros overflow\" {\n    // 21 characters should error regardless of value\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"000000000000000000000\"),\n    );\n}\n\ntest \"parseU64Fast invalid first char\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"a123\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"x999\"));\n}\n\ntest \"parseU64Fast invalid middle char\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12a34\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"99x99\"));\n}\n\ntest \"parseU64Fast invalid last char\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"1234a\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"9999z\"));\n}\n\ntest \"parseU64Fast space in middle\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12 34\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"1 2\"));\n}\n\ntest \"parseU64Fast tab character\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12\\t34\"));\n}\n\ntest \"parseU64Fast newline character\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12\\n34\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"123\\r\\n\"));\n}\n\ntest \"parseU64Fast null byte\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12\\x0034\"));\n}\n\ntest \"parseU64Fast negative sign\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"-123\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"-1\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"-0\"));\n}\n\ntest \"parseU64Fast positive sign\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"+123\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"+1\"));\n}\n\ntest \"parseU64Fast minus in middle\" {\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"12-34\"));\n}\n\ntest \"parseU64Fast exactly 20 chars boundary\" {\n    // 20-char numbers now rejected (overflow guard)\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"10000000000000000000\"),\n    );\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"12345678901234567890\"),\n    );\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"99999999999999999999\"),\n    );\n}\n\ntest \"parseU64Fast exactly 21 chars overflow\" {\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"100000000000000000000\"),\n    );\n    try std.testing.expectError(\n        error.Overflow,\n        parseU64Fast(\"999999999999999999999\"),\n    );\n}\n\ntest \"parseU64Fast single digit all values\" {\n    try std.testing.expectEqual(@as(u64, 0), try parseU64Fast(\"0\"));\n    try std.testing.expectEqual(@as(u64, 1), try parseU64Fast(\"1\"));\n    try std.testing.expectEqual(@as(u64, 5), try parseU64Fast(\"5\"));\n    try std.testing.expectEqual(@as(u64, 9), try parseU64Fast(\"9\"));\n}\n\ntest \"parseUsizeFast boundaries\" {\n    // 20-char numbers now rejected (overflow guard)\n    try std.testing.expectError(\n        error.Overflow,\n        parseUsizeFast(\"18446744073709551615\"),\n    );\n    try std.testing.expectEqual(\n        @as(usize, 0),\n        try parseUsizeFast(\"0\"),\n    );\n    try std.testing.expectEqual(\n        @as(usize, 1),\n        try parseUsizeFast(\"1\"),\n    );\n}\n\ntest \"parseU64Fast special ASCII near digits\" {\n    // Characters just before '0' (ASCII 48) and after '9' (ASCII 57)\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"/\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\":\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"1/2\"));\n    try std.testing.expectError(error.InvalidCharacter, parseU64Fast(\"1:2\"));\n}\n\n// Section 3: MSG Parsing Edge Cases\n\ntest \"MSG header only no CRLF returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // No \\r\\n means incomplete - should return null\n    const result = try p.parse(std.testing.allocator, \"MSG subject 1 5\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n}\n\ntest \"MSG header with CRLF but no payload returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Header complete but payload not yet arrived\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n}\n\ntest \"MSG partial payload returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Only 3 of 5 payload bytes\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\r\\nhel\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n}\n\ntest \"MSG payload without trailing CRLF returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Payload complete but missing trailing \\r\\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\r\\nhello\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"MSG one byte short of complete returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Missing final \\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\r\\nhello\\r\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"MSG empty subject rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Double space = empty subject\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG  1 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG non-numeric SID rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject abc 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG non-numeric payload length rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 abc\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG float SID rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 1.5 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG float payload length rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5.5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG negative SID rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject -1 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG negative payload length rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 -5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG SID one is valid\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(u64, 1), result.?.msg.sid);\n}\n\ntest \"MSG SID large value\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Large but valid SID\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 999999999999 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(u64, 999999999999), result.?.msg.sid);\n}\n\ntest \"MSG SID overflow 21 digits rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 123456789012345678901 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG zero payload valid\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 0\\r\\n\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 0), result.?.msg.payload_len);\n    try std.testing.expectEqualSlices(u8, \"\", result.?.msg.payload);\n}\n\ntest \"MSG payload length overflow 21 digits rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 123456789012345678901\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"MSG subject with dots\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG foo.bar.baz 1 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"foo.bar.baz\", result.?.msg.subject);\n}\n\ntest \"MSG single char subject\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG x 1 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"x\", result.?.msg.subject);\n}\n\ntest \"MSG long subject\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // 256 character subject\n    const long_subject = \"a\" ** 256;\n    const data = \"MSG \" ++ long_subject ++ \" 1 5\\r\\nhello\\r\\n\";\n\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 256), result.?.msg.subject.len);\n}\n\ntest \"MSG reply-to with dots\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 _INBOX.abc.def 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(\n        u8,\n        \"_INBOX.abc.def\",\n        result.?.msg.reply_to.?,\n    );\n}\n\ntest \"MSG tab instead of space rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Tab is not a valid delimiter\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG\\tsubject\\t1\\t5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"MSG missing SID field rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Only subject and length, no SID\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    // This parses \"5\" as SID and then fails with missing length\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\n// Section 4: HMSG Parsing Edge Cases\n\ntest \"HMSG header length zero\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // hdr_len=0 means no headers, only payload\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 0 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 0), result.?.hmsg.header_len);\n    try std.testing.expectEqualSlices(u8, \"\", result.?.hmsg.headers);\n    try std.testing.expectEqualSlices(u8, \"hello\", result.?.hmsg.payload);\n}\n\ntest \"HMSG header length exceeds total rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // hdr_len > total_len is invalid\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 100 50\\r\\n\" ++ \"x\" ** 50 ++ \"\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"HMSG header length equals total\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // hdr_len == total_len means headers only, no payload\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 12 12\\r\\nNATS/1.0\\r\\n\\r\\n\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 12), result.?.hmsg.header_len);\n    try std.testing.expectEqual(@as(usize, 12), result.?.hmsg.total_len);\n    try std.testing.expectEqualSlices(u8, \"\", result.?.hmsg.payload);\n}\n\ntest \"HMSG header length one less than total\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // 1 byte payload\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 12 13\\r\\nNATS/1.0\\r\\n\\r\\nX\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"X\", result.?.hmsg.payload);\n}\n\ntest \"HMSG zero total length\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Both hdr_len and total_len are 0\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 0 0\\r\\n\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 0), result.?.hmsg.header_len);\n    try std.testing.expectEqual(@as(usize, 0), result.?.hmsg.total_len);\n}\n\ntest \"HMSG total length overflow rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 0 123456789012345678901\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"HMSG header length overflow rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 123456789012345678901 100\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"HMSG partial headers returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Need 12 bytes of headers but only have 5\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 12 12\\r\\nNATS/\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"HMSG headers complete payload partial returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Headers complete (12 bytes) but payload (5 bytes) incomplete\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 12 17\\r\\nNATS/1.0\\r\\n\\r\\nhel\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"HMSG with reply-to\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 _INBOX.reply 12 17\\r\\nNATS/1.0\\r\\n\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"_INBOX.reply\", result.?.hmsg.reply_to.?);\n}\n\ntest \"HMSG SID overflow rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG subject 123456789012345678901 12 12\\r\\nNATS/1.0\\r\\n\\r\\n\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"HMSG non-numeric header length rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 abc 12\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\ntest \"HMSG non-numeric total length rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"HMSG subject 1 12 abc\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidArguments, result);\n}\n\n// Section 5: INFO Parsing Edge Cases\n\ntest \"INFO minimal valid json\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Minimal valid JSON - needs at least server_id or version\n    const result = try p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\\"test\\\"}\\r\\n\",\n        &consumed,\n    );\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"test\", result.?.info.server_id);\n}\n\ntest \"INFO malformed json rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"INFO {invalid}\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO truncated json rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO array instead of object rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"INFO []\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO null rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"INFO null\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO string instead of object rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"INFO \\\"test\\\"\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO max_payload zero rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // max_payload = 0 is invalid\n    const result = p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\\"test\\\",\\\"max_payload\\\":0}\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidServerInfo, result);\n}\n\ntest \"INFO max_payload exceeds limit rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // max_payload > 1GB is invalid\n    const result = p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\\"test\\\",\\\"max_payload\\\":2000000000}\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidServerInfo, result);\n}\n\ntest \"INFO empty json rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Empty JSON has no server_id or version\n    const result = p.parse(std.testing.allocator, \"INFO {}\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidServerInfo, result);\n}\n\ntest \"INFO empty server_id with version valid\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Empty server_id is valid if version is present\n    const result = try p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\\"\\\",\\\"version\\\":\\\"2.10.0\\\"}\\r\\n\",\n        &consumed,\n    );\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"\", result.?.info.server_id);\n    try std.testing.expectEqualSlices(u8, \"2.10.0\", result.?.info.version);\n}\n\ntest \"INFO with unknown fields ignored\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\\"test\\\",\\\"unknown_field\\\":\\\"value\\\",\" ++\n            \"\\\"another_unknown\\\":123}\\r\\n\",\n        &consumed,\n    );\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"test\", result.?.info.server_id);\n}\n\ntest \"INFO boolean as string type mismatch\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // headers should be bool, not string\n    const result = p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"headers\\\":\\\"true\\\"}\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO number as string type mismatch\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // server_id should be string, not number\n    const result = p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":123}\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidJson, result);\n}\n\ntest \"INFO string as number coerced\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Zig JSON parser coerces string \"1000\" to number 1000\n    const result = try p.parse(\n        std.testing.allocator,\n        \"INFO {\\\"server_id\\\":\\\"test\\\",\\\"max_payload\\\":\\\"1000\\\"}\\r\\n\",\n        &consumed,\n    );\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(u32, 1000), result.?.info.max_payload);\n}\n\ntest \"INFO with all valid fields\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const json = \"INFO {\" ++\n        \"\\\"server_id\\\":\\\"NATS123\\\",\" ++\n        \"\\\"server_name\\\":\\\"my-nats\\\",\" ++\n        \"\\\"version\\\":\\\"2.10.0\\\",\" ++\n        \"\\\"proto\\\":1,\" ++\n        \"\\\"host\\\":\\\"localhost\\\",\" ++\n        \"\\\"port\\\":4222,\" ++\n        \"\\\"max_payload\\\":1048576,\" ++\n        \"\\\"headers\\\":true,\" ++\n        \"\\\"jetstream\\\":true\" ++\n        \"}\\r\\n\";\n\n    const result = try p.parse(std.testing.allocator, json, &consumed);\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n    try std.testing.expect(result != null);\n    const info = result.?.info;\n    try std.testing.expectEqualSlices(u8, \"NATS123\", info.server_id);\n    try std.testing.expectEqualSlices(u8, \"my-nats\", info.server_name);\n    try std.testing.expectEqual(@as(u16, 4222), info.port);\n    try std.testing.expectEqual(true, info.headers);\n    try std.testing.expectEqual(true, info.jetstream);\n}\n\n// Section 6: Command Dispatch Edge Cases\n\ntest \"parse MSG without space rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"MSG123\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse HMSG without space rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"HMSG123\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse INFO without space rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"INFO{}\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse lowercase msg rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(\n        std.testing.allocator,\n        \"msg subject 1 5\\r\\nhello\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse lowercase ping rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"ping\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse mixed case Ping rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"Ping\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse PING with trailing data rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"PING extra\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse PONG with trailing data rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"PONG extra\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse +OK with trailing data rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"+OK extra\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse -ERR empty message\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // -ERR with just a space and nothing after\n    const result = try p.parse(std.testing.allocator, \"-ERR \\r\\n\", &consumed);\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"\", result.?.err);\n}\n\ntest \"parse -ERR no space rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"-ERRmessage\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse -ERR long message\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const long_msg = \"x\" ** 1000;\n    const data = \"-ERR \" ++ long_msg ++ \"\\r\\n\";\n\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 1000), result.?.err.len);\n}\n\ntest \"parse empty buffer returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(std.testing.allocator, \"\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n}\n\ntest \"parse single byte returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(std.testing.allocator, \"M\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"parse CR only returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(std.testing.allocator, \"\\r\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"parse LF only returns null\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = try p.parse(std.testing.allocator, \"\\n\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"parse unknown command rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"UNKNOWN\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse binary garbage rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"\\x00\\x01\\x02\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\ntest \"parse high ASCII rejected\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const result = p.parse(std.testing.allocator, \"\\xFF\\xFE\\r\\n\", &consumed);\n    try std.testing.expectError(Parser.Error.InvalidCommand, result);\n}\n\n// Section 7: CRLF Verification Edge Cases\n\ntest \"MSG with LF only line ending incomplete\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // \\n alone is not recognized as line ending, returns null\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\nhello\\n\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"MSG with CR only line ending incomplete\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // \\r alone is not recognized as line ending\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\rhello\\r\",\n        &consumed,\n    );\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n}\n\ntest \"MSG payload contains CRLF\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // CRLF in payload is valid - payload is binary\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 7\\r\\nhel\\r\\nlo\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"hel\\r\\nlo\", result.?.msg.payload);\n}\n\ntest \"MSG payload is all CRLF\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Payload of just \\r\\n\\r\\n (4 bytes)\n    const result = try p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 4\\r\\n\\r\\n\\r\\n\\r\\n\",\n        &consumed,\n    );\n    try std.testing.expect(result != null);\n    try std.testing.expectEqualSlices(u8, \"\\r\\n\\r\\n\", result.?.msg.payload);\n}\n\ntest \"MSG wrong CRLF order in payload trailing\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // \\n\\r instead of \\r\\n at end - should fail verification\n    const result = p.parse(\n        std.testing.allocator,\n        \"MSG subject 1 5\\r\\nhello\\n\\r\",\n        &consumed,\n    );\n    // Either returns null (incomplete) or InvalidArguments\n    if (result) |r| {\n        try std.testing.expectEqual(@as(?ServerCommand, null), r);\n    } else |err| {\n        try std.testing.expectEqual(Parser.Error.InvalidArguments, err);\n    }\n}\n\n// Section 8: Buffer Boundary Edge Cases\n\ntest \"MSG exactly fills buffer\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"MSG subject 1 5\\r\\nhello\\r\\n\";\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(data.len, consumed);\n}\n\ntest \"multiple commands in buffer first only\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Two commands: MSG then PING\n    const data = \"MSG subject 1 5\\r\\nhello\\r\\nPING\\r\\n\";\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n\n    try std.testing.expect(result != null);\n    // Should only consume the MSG, not the PING\n    try std.testing.expectEqual(@as(usize, 24), consumed);\n    try std.testing.expectEqualSlices(u8, \"hello\", result.?.msg.payload);\n\n    // Parse again for PING\n    const remaining = data[consumed..];\n    var consumed2: usize = 0;\n    const result2 = try p.parse(std.testing.allocator, remaining, &consumed2);\n    try std.testing.expect(result2 != null);\n    try std.testing.expectEqual(ServerCommand.ping, result2.?);\n}\n\ntest \"partial parse consumed is zero\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // Incomplete command\n    const result = try p.parse(std.testing.allocator, \"MSG subject 1 5\\r\\nhel\", &consumed);\n    try std.testing.expectEqual(@as(?ServerCommand, null), result);\n    try std.testing.expectEqual(@as(usize, 0), consumed);\n}\n\ntest \"consumed never exceeds data length\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"PING\\r\\n\";\n    _ = try p.parse(std.testing.allocator, data, &consumed);\n\n    try std.testing.expect(consumed <= data.len);\n}\n\ntest \"MSG with extra data after\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    // MSG followed by garbage\n    const data = \"MSG subject 1 5\\r\\nhello\\r\\ngarbage\";\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(@as(usize, 24), consumed);\n    // Garbage should remain unparsed\n    try std.testing.expectEqualSlices(u8, \"garbage\", data[consumed..]);\n}\n\ntest \"INFO consumed includes CRLF\" {\n    var p: Parser = .{};\n    var consumed: usize = 0;\n\n    const data = \"INFO {\\\"server_id\\\":\\\"test\\\"}\\r\\n\";\n    const result = try p.parse(std.testing.allocator, data, &consumed);\n    defer {\n        if (result) |cmd| {\n            switch (cmd) {\n                .info => |*info| {\n                    var info_mut = info.*;\n                    info_mut.deinit(std.testing.allocator);\n                },\n                else => {},\n            }\n        }\n    }\n\n    try std.testing.expect(result != null);\n    try std.testing.expectEqual(data.len, consumed);\n}\n"
  },
  {
    "path": "src/protocol.zig",
    "content": "//! NATS Protocol Implementation\n//!\n//! This module handles the NATS wire protocol including parsing server\n//! commands and encoding client commands.\n//!\n//! - Server commands: INFO, MSG, HMSG, PING, PONG, +OK, -ERR\n//! - Client commands: CONNECT, PUB, HPUB, SUB, UNSUB, PING, PONG\n\nconst std = @import(\"std\");\n\npub const commands = @import(\"protocol/commands.zig\");\npub const parser = @import(\"protocol/parser.zig\");\npub const encoder = @import(\"protocol/encoder.zig\");\npub const headers = @import(\"protocol/headers.zig\");\npub const header_map = @import(\"protocol/header_map.zig\");\npub const errors = @import(\"protocol/errors.zig\");\n\n// Re-export common types\npub const ServerInfo = commands.ServerInfo;\npub const RawServerInfo = commands.RawServerInfo;\npub const ConnectOptions = commands.ConnectOptions;\npub const ServerCommand = commands.ServerCommand;\npub const ClientCommand = commands.ClientCommand;\npub const MsgArgs = commands.MsgArgs;\npub const HMsgArgs = commands.HMsgArgs;\npub const PubArgs = commands.PubArgs;\npub const SubArgs = commands.SubArgs;\n\npub const Parser = parser.Parser;\npub const Encoder = encoder.Encoder;\npub const HeaderMap = header_map.HeaderMap;\n\n// Protocol errors\npub const Error = errors.Error;\npub const parseServerError = errors.parseServerError;\npub const isAuthError = errors.isAuthError;\n\ntest {\n    std.testing.refAllDecls(@This());\n}\n"
  },
  {
    "path": "src/pubsub/inbox.zig",
    "content": "//! Inbox Generation\n//!\n//! Generates unique inbox subjects for request/reply patterns.\n//! Inbox format: _INBOX.<random-22-chars>\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\nconst Io = std.Io;\n\n/// Inbox prefix used by NATS.\npub const prefix = \"_INBOX.\";\n\n/// Length of the random portion of inbox.\npub const random_len = 22;\n\n/// Total length of a generated inbox.\npub const total_len = prefix.len + random_len;\n\n/// Characters used in inbox generation (base62).\nconst alphabet = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\" ++\n    \"abcdefghijklmnopqrstuvwxyz\";\n\n/// Generates a new unique inbox subject.\n/// Caller owns returned memory.\npub fn newInbox(allocator: Allocator, io: Io) Allocator.Error![]u8 {\n    const result = try allocator.alloc(u8, total_len);\n    @memcpy(result[0..prefix.len], prefix);\n    fillRandom(io, result[prefix.len..]);\n    assert(result.len == total_len);\n    return result;\n}\n\n/// Generates inbox into provided buffer.\n/// Buffer must be at least total_len bytes.\npub fn newInboxBuf(io: Io, buf: []u8) error{BufferTooSmall}![]u8 {\n    if (buf.len < total_len) return error.BufferTooSmall;\n    assert(buf.len >= total_len);\n    @memcpy(buf[0..prefix.len], prefix);\n    fillRandom(io, buf[prefix.len..][0..random_len]);\n    return buf[0..total_len];\n}\n\n/// Fills buffer with random base62 characters.\nfn fillRandom(io: Io, buf: []u8) void {\n    assert(buf.len > 0);\n    io.random(buf);\n    for (buf) |*b| {\n        b.* = alphabet[@mod(b.*, alphabet.len)];\n    }\n}\n\n/// Checks if a subject is an inbox.\npub fn isInbox(subject: []const u8) bool {\n    return std.mem.startsWith(u8, subject, prefix);\n}\n\n/// Generates inbox with custom prefix for wildcards.\n/// Format: _INBOX.<prefix>.<random>\n/// Caller owns returned memory.\npub fn newInboxWithPrefix(\n    allocator: Allocator,\n    io: Io,\n    custom_prefix: []const u8,\n) Allocator.Error![]u8 {\n    assert(custom_prefix.len > 0);\n    const len = prefix.len + custom_prefix.len + 1 + random_len;\n    const result = try allocator.alloc(u8, len);\n\n    var pos: usize = 0;\n    @memcpy(result[pos..][0..prefix.len], prefix);\n    pos += prefix.len;\n\n    @memcpy(result[pos..][0..custom_prefix.len], custom_prefix);\n    pos += custom_prefix.len;\n\n    result[pos] = '.';\n    pos += 1;\n\n    fillRandom(io, result[pos..][0..random_len]);\n\n    return result;\n}\n\ntest \"new inbox\" {\n    const allocator = std.testing.allocator;\n    var io: Io.Threaded = .init(allocator, .{ .environ = .empty });\n    defer io.deinit();\n\n    const inbox = try newInbox(allocator, io.io());\n    defer allocator.free(inbox);\n\n    try std.testing.expectEqual(total_len, inbox.len);\n    try std.testing.expect(std.mem.startsWith(u8, inbox, prefix));\n    try std.testing.expect(isInbox(inbox));\n}\n\ntest \"new inbox buf\" {\n    const allocator = std.testing.allocator;\n    var io: Io.Threaded = .init(allocator, .{ .environ = .empty });\n    defer io.deinit();\n\n    var buf: [64]u8 = undefined;\n    const inbox = try newInboxBuf(io.io(), &buf);\n\n    try std.testing.expectEqual(total_len, inbox.len);\n    try std.testing.expect(std.mem.startsWith(u8, inbox, prefix));\n}\n\ntest \"new inbox buf too small\" {\n    const allocator = std.testing.allocator;\n    var io: Io.Threaded = .init(allocator, .{ .environ = .empty });\n    defer io.deinit();\n\n    var buf: [10]u8 = undefined;\n    try std.testing.expectError(\n        error.BufferTooSmall,\n        newInboxBuf(io.io(), &buf),\n    );\n}\n\ntest \"inbox uniqueness\" {\n    const allocator = std.testing.allocator;\n    var io: Io.Threaded = .init(allocator, .{ .environ = .empty });\n    defer io.deinit();\n\n    const inbox1 = try newInbox(allocator, io.io());\n    defer allocator.free(inbox1);\n\n    const inbox2 = try newInbox(allocator, io.io());\n    defer allocator.free(inbox2);\n\n    try std.testing.expect(!std.mem.eql(u8, inbox1, inbox2));\n}\n\ntest \"is inbox\" {\n    try std.testing.expect(isInbox(\"_INBOX.abc123\"));\n    try std.testing.expect(isInbox(\"_INBOX.\"));\n    try std.testing.expect(!isInbox(\"foo.bar\"));\n    try std.testing.expect(!isInbox(\"_INBOX\"));\n}\n\ntest \"inbox with prefix\" {\n    const allocator = std.testing.allocator;\n    var io: Io.Threaded = .init(allocator, .{ .environ = .empty });\n    defer io.deinit();\n\n    const inbox = try newInboxWithPrefix(allocator, io.io(), \"myprefix\");\n    defer allocator.free(inbox);\n\n    try std.testing.expect(std.mem.startsWith(u8, inbox, \"_INBOX.myprefix.\"));\n    try std.testing.expect(isInbox(inbox));\n}\n"
  },
  {
    "path": "src/pubsub/subject.zig",
    "content": "//! Subject Validation and Matching\n//!\n//! NATS subjects are dot-separated tokens. Wildcards:\n//! - `*` matches exactly one token\n//! - `>` matches one or more tokens (must be last)\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// Errors during subject validation.\npub const ValidationError = error{\n    EmptySubject,\n    EmptyToken,\n    InvalidCharacter,\n    WildcardNotLast,\n    SpaceInSubject,\n};\n\n/// Validates a subject for publishing (no wildcards allowed).\npub fn validatePublish(subject: []const u8) ValidationError!void {\n    if (subject.len == 0) return error.EmptySubject;\n\n    var token_start: usize = 0;\n    for (subject, 0..) |c, i| {\n        if (c == '.') {\n            if (i == token_start) return error.EmptyToken;\n            token_start = i + 1;\n        } else if (c == ' ' or c == '\\t') {\n            return error.SpaceInSubject;\n        } else if (c == '*' or c == '>' or c < 0x20 or c == 0x7f) {\n            // Wildcards and control chars (includes CR/LF/null)\n            return error.InvalidCharacter;\n        }\n    }\n\n    // Check last token isn't empty\n    if (token_start >= subject.len) return error.EmptyToken;\n}\n\n/// Validates a subject for subscribing (wildcards allowed).\npub fn validateSubscribe(subject: []const u8) ValidationError!void {\n    if (subject.len == 0) return error.EmptySubject;\n\n    var token_start: usize = 0;\n    var has_full_wildcard = false;\n\n    for (subject, 0..) |c, i| {\n        if (c == '.') {\n            if (i == token_start) return error.EmptyToken;\n            if (has_full_wildcard) return error.WildcardNotLast;\n            token_start = i + 1;\n        } else if (c == ' ' or c == '\\t') {\n            return error.SpaceInSubject;\n        } else if (c == '>') {\n            // > must be alone in its token (at start AND next is . or end)\n            if (i != token_start) return error.InvalidCharacter;\n            if (i + 1 < subject.len and subject[i + 1] != '.') {\n                return error.InvalidCharacter;\n            }\n            has_full_wildcard = true;\n        } else if (c == '*') {\n            // * must be alone in its token (at start AND next is . or end)\n            if (i != token_start) return error.InvalidCharacter;\n            if (i + 1 < subject.len and subject[i + 1] != '.') {\n                return error.InvalidCharacter;\n            }\n        } else if (c < 0x20 or c == 0x7f) {\n            // Control chars (includes CR/LF/null)\n            return error.InvalidCharacter;\n        }\n    }\n\n    // Check last token isn't empty\n    if (token_start >= subject.len) return error.EmptyToken;\n}\n\n/// Validates a reply-to address for protocol safety.\npub fn validateReplyTo(reply_to: []const u8) ValidationError!void {\n    if (reply_to.len == 0) return error.EmptySubject;\n    for (reply_to) |c| {\n        if (c <= 0x20 or c == 0x7f) return error.InvalidCharacter;\n    }\n}\n\n/// Validates a queue group name for protocol safety.\npub fn validateQueueGroup(queue: []const u8) ValidationError!void {\n    if (queue.len == 0) return error.EmptySubject;\n    for (queue) |c| {\n        if (c <= 0x20 or c == 0x7f) return error.InvalidCharacter;\n    }\n}\n\n/// Checks if a subject matches a pattern (with wildcards).\npub fn matches(pattern: []const u8, subject: []const u8) bool {\n    // Empty inputs are invalid subjects - return false\n    if (pattern.len == 0 or subject.len == 0) return false;\n    var pat_iter = std.mem.tokenizeScalar(u8, pattern, '.');\n    var subj_iter = std.mem.tokenizeScalar(u8, subject, '.');\n\n    while (pat_iter.next()) |pat_token| {\n        if (std.mem.eql(u8, pat_token, \">\")) {\n            // > matches rest of subject (one or more tokens)\n            return subj_iter.next() != null;\n        }\n\n        const subj_token = subj_iter.next() orelse return false;\n\n        if (std.mem.eql(u8, pat_token, \"*\")) {\n            // * matches any single token\n            continue;\n        }\n\n        if (!std.mem.eql(u8, pat_token, subj_token)) {\n            return false;\n        }\n    }\n\n    // Both must be exhausted for exact match\n    return subj_iter.next() == null;\n}\n\n/// Counts the number of tokens in a subject.\npub fn tokenCount(subject: []const u8) usize {\n    if (subject.len == 0) return 0;\n\n    var count: usize = 1;\n    for (subject) |c| {\n        if (c == '.') count += 1;\n    }\n    return count;\n}\n\n/// Extracts a specific token from a subject (0-indexed).\npub fn getToken(subject: []const u8, index: usize) ?[]const u8 {\n    var iter = std.mem.tokenizeScalar(u8, subject, '.');\n    var i: usize = 0;\n    while (iter.next()) |token| {\n        if (i == index) return token;\n        i += 1;\n    }\n    return null;\n}\n\ntest {\n    _ = @import(\"subject_test.zig\");\n}\n"
  },
  {
    "path": "src/pubsub/subject_test.zig",
    "content": "//! Subject Validation Edge Case Tests\n//!\n//! - Empty/boundary inputs\n//! - Null bytes and control characters\n//! - Wildcard position validation\n//! - Pattern matching edge cases\n//! - Token counting edge cases\n\nconst std = @import(\"std\");\nconst subject = @import(\"subject.zig\");\nconst validatePublish = subject.validatePublish;\nconst validateSubscribe = subject.validateSubscribe;\nconst validateReplyTo = subject.validateReplyTo;\nconst validateQueueGroup = subject.validateQueueGroup;\nconst matches = subject.matches;\nconst tokenCount = subject.tokenCount;\nconst getToken = subject.getToken;\nconst ValidationError = subject.ValidationError;\n\n// Section 1: Existing Tests (moved from subject.zig)\n\ntest \"validate publish subject\" {\n    try validatePublish(\"foo\");\n    try validatePublish(\"foo.bar\");\n    try validatePublish(\"foo.bar.baz\");\n    try validatePublish(\"_INBOX.abc123\");\n\n    try std.testing.expectError(error.EmptySubject, validatePublish(\"\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\"foo.\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\".foo\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\"foo..bar\"));\n    const inv_char = error.InvalidCharacter;\n    try std.testing.expectError(inv_char, validatePublish(\"foo.*\"));\n    try std.testing.expectError(inv_char, validatePublish(\"foo.>\"));\n    const space_err = error.SpaceInSubject;\n    try std.testing.expectError(space_err, validatePublish(\"foo bar\"));\n\n    // CR/LF injection protection\n    try std.testing.expectError(inv_char, validatePublish(\"test\\r\\nINFO\"));\n    try std.testing.expectError(inv_char, validatePublish(\"test\\nfoo\"));\n    try std.testing.expectError(inv_char, validatePublish(\"test\\rfoo\"));\n}\n\ntest \"validate subscribe subject\" {\n    try validateSubscribe(\"foo\");\n    try validateSubscribe(\"foo.bar\");\n    try validateSubscribe(\"foo.*\");\n    try validateSubscribe(\"*.bar\");\n    try validateSubscribe(\"foo.>\");\n    try validateSubscribe(\">\");\n\n    try std.testing.expectError(error.EmptySubject, validateSubscribe(\"\"));\n    try std.testing.expectError(error.EmptyToken, validateSubscribe(\"foo.\"));\n    const wc_err = error.WildcardNotLast;\n    try std.testing.expectError(wc_err, validateSubscribe(\"foo.>.bar\"));\n    const inv_char = error.InvalidCharacter;\n    try std.testing.expectError(inv_char, validateSubscribe(\"foo.bar>\"));\n    try std.testing.expectError(inv_char, validateSubscribe(\"foo.bar*\"));\n\n    // CR/LF injection protection\n    try std.testing.expectError(inv_char, validateSubscribe(\"test\\r\\nUNSUB\"));\n    try std.testing.expectError(inv_char, validateSubscribe(\"test\\nfoo\"));\n}\n\ntest \"subject matching\" {\n    // Exact match\n    try std.testing.expect(matches(\"foo.bar\", \"foo.bar\"));\n    try std.testing.expect(!matches(\"foo.bar\", \"foo.baz\"));\n\n    // Single token wildcard\n    try std.testing.expect(matches(\"foo.*\", \"foo.bar\"));\n    try std.testing.expect(matches(\"foo.*\", \"foo.baz\"));\n    try std.testing.expect(!matches(\"foo.*\", \"foo.bar.baz\"));\n    try std.testing.expect(matches(\"*.bar\", \"foo.bar\"));\n\n    // Full wildcard\n    try std.testing.expect(matches(\"foo.>\", \"foo.bar\"));\n    try std.testing.expect(matches(\"foo.>\", \"foo.bar.baz\"));\n    try std.testing.expect(!matches(\"foo.>\", \"foo\"));\n    try std.testing.expect(matches(\">\", \"foo\"));\n    try std.testing.expect(matches(\">\", \"foo.bar.baz\"));\n}\n\ntest \"token count\" {\n    try std.testing.expectEqual(@as(usize, 0), tokenCount(\"\"));\n    try std.testing.expectEqual(@as(usize, 1), tokenCount(\"foo\"));\n    try std.testing.expectEqual(@as(usize, 2), tokenCount(\"foo.bar\"));\n    try std.testing.expectEqual(@as(usize, 3), tokenCount(\"foo.bar.baz\"));\n}\n\ntest \"get token\" {\n    try std.testing.expectEqualSlices(u8, \"foo\", getToken(\"foo.bar.baz\", 0).?);\n    try std.testing.expectEqualSlices(u8, \"bar\", getToken(\"foo.bar.baz\", 1).?);\n    try std.testing.expectEqualSlices(u8, \"baz\", getToken(\"foo.bar.baz\", 2).?);\n    try std.testing.expect(getToken(\"foo.bar.baz\", 3) == null);\n}\n\ntest \"validateReplyTo rejects injection\" {\n    // Valid reply-to addresses\n    try validateReplyTo(\"_INBOX.abc123\");\n    try validateReplyTo(\"reply.to.subject\");\n\n    // CR/LF injection\n    const inv_char = error.InvalidCharacter;\n    try std.testing.expectError(inv_char, validateReplyTo(\"inbox\\r\\nUNSUB\"));\n    try std.testing.expectError(inv_char, validateReplyTo(\"inbox\\nfoo\"));\n    try std.testing.expectError(inv_char, validateReplyTo(\"inbox\\rfoo\"));\n\n    // Spaces and tabs\n    try std.testing.expectError(inv_char, validateReplyTo(\"inbox foo\"));\n    try std.testing.expectError(inv_char, validateReplyTo(\"inbox\\tfoo\"));\n}\n\ntest \"validateQueueGroup rejects injection\" {\n    // Valid queue groups\n    try validateQueueGroup(\"workers\");\n    try validateQueueGroup(\"queue-1\");\n\n    // CR/LF injection\n    const inv_char = error.InvalidCharacter;\n    try std.testing.expectError(inv_char, validateQueueGroup(\"workers\\r\\n\"));\n    try std.testing.expectError(inv_char, validateQueueGroup(\"workers\\nfoo\"));\n    try std.testing.expectError(inv_char, validateQueueGroup(\"workers\\rfoo\"));\n\n    // Spaces and tabs\n    try std.testing.expectError(inv_char, validateQueueGroup(\"workers foo\"));\n    try std.testing.expectError(inv_char, validateQueueGroup(\"workers\\tfoo\"));\n}\n\n// Section 2: validatePublish Edge Cases\n\ntest \"validatePublish single character subject\" {\n    try validatePublish(\"a\");\n    try validatePublish(\"x\");\n    try validatePublish(\"1\");\n    try validatePublish(\"_\");\n}\n\ntest \"validatePublish single dot rejected\" {\n    // Single dot = two empty tokens\n    try std.testing.expectError(error.EmptyToken, validatePublish(\".\"));\n}\n\ntest \"validatePublish multiple consecutive dots rejected\" {\n    try std.testing.expectError(error.EmptyToken, validatePublish(\"..\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\"...\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\"foo...bar\"));\n}\n\ntest \"validatePublish null byte rejected\" {\n    // Null bytes could be used for injection - should be rejected\n    const result = validatePublish(\"foo\\x00bar\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validatePublish control characters rejected\" {\n    // Various control characters that should be rejected\n    try std.testing.expectError(error.InvalidCharacter, validatePublish(\"foo\\x01bar\"));\n    try std.testing.expectError(error.InvalidCharacter, validatePublish(\"foo\\x7fbar\"));\n}\n\ntest \"validatePublish unicode characters\" {\n    // Unicode should probably be allowed (common in international use)\n    try validatePublish(\"日本語\");\n    try validatePublish(\"foo.émoji.bar\");\n}\n\ntest \"validatePublish very long subject\" {\n    // Very long subject - should there be a limit?\n    const long_subject = \"a\" ** 10000;\n    try validatePublish(long_subject);\n}\n\ntest \"validatePublish subject with numbers and special chars\" {\n    try validatePublish(\"foo-bar\");\n    try validatePublish(\"foo_bar\");\n    try validatePublish(\"foo123\");\n    try validatePublish(\"123\");\n    try validatePublish(\"foo-bar_baz.123\");\n}\n\ntest \"validatePublish leading/trailing dots\" {\n    try std.testing.expectError(error.EmptyToken, validatePublish(\".foo.bar\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\"foo.bar.\"));\n    try std.testing.expectError(error.EmptyToken, validatePublish(\".\"));\n}\n\n// Section 3: validateSubscribe Edge Cases\n\ntest \"validateSubscribe single wildcard tokens\" {\n    try validateSubscribe(\"*\");\n    try validateSubscribe(\">\");\n}\n\ntest \"validateSubscribe multiple single wildcards\" {\n    try validateSubscribe(\"*.*\");\n    try validateSubscribe(\"*.*.*\");\n    try validateSubscribe(\"foo.*.*\");\n    try validateSubscribe(\"*.*.bar\");\n}\n\ntest \"validateSubscribe wildcard in middle of token rejected\" {\n    // \"*abc\" or \"abc*\" should be rejected\n    try std.testing.expectError(error.InvalidCharacter, validateSubscribe(\"*abc\"));\n    try std.testing.expectError(error.InvalidCharacter, validateSubscribe(\"abc*\"));\n    try std.testing.expectError(error.InvalidCharacter, validateSubscribe(\"a*c\"));\n}\n\ntest \"validateSubscribe > in middle of token rejected\" {\n    try std.testing.expectError(error.InvalidCharacter, validateSubscribe(\">abc\"));\n    try std.testing.expectError(error.InvalidCharacter, validateSubscribe(\"abc>\"));\n    try std.testing.expectError(error.InvalidCharacter, validateSubscribe(\"a>c\"));\n}\n\ntest \"validateSubscribe > not at end rejected\" {\n    try std.testing.expectError(error.WildcardNotLast, validateSubscribe(\">.bar\"));\n    try std.testing.expectError(error.WildcardNotLast, validateSubscribe(\"foo.>.bar\"));\n    try std.testing.expectError(error.WildcardNotLast, validateSubscribe(\">.*\"));\n}\n\ntest \"validateSubscribe null byte rejected\" {\n    const result = validateSubscribe(\"foo\\x00bar\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateSubscribe single dot rejected\" {\n    try std.testing.expectError(error.EmptyToken, validateSubscribe(\".\"));\n}\n\ntest \"validateSubscribe empty token before wildcard\" {\n    try std.testing.expectError(error.EmptyToken, validateSubscribe(\".*\"));\n    try std.testing.expectError(error.EmptyToken, validateSubscribe(\".>\"));\n}\n\n// Section 4: validateReplyTo Edge Cases\n\ntest \"validateReplyTo empty string\" {\n    const result = validateReplyTo(\"\");\n    try std.testing.expectError(error.EmptySubject, result);\n}\n\ntest \"validateReplyTo null byte rejected\" {\n    const result = validateReplyTo(\"inbox\\x00inject\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo allows dots and special chars\" {\n    // Reply-to can contain dots (for inbox subjects)\n    try validateReplyTo(\"_INBOX.abc.123.def\");\n    try validateReplyTo(\"reply-to\");\n    try validateReplyTo(\"reply_to\");\n}\n\ntest \"validateReplyTo tab rejected\" {\n    const result = validateReplyTo(\"inbox\\treply\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo CR only rejected\" {\n    const result = validateReplyTo(\"inbox\\rreply\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo LF only rejected\" {\n    const result = validateReplyTo(\"inbox\\nreply\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo space rejected\" {\n    const result = validateReplyTo(\"inbox reply\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo DEL char rejected\" {\n    const result = validateReplyTo(\"inbox\\x7freply\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo control char 0x01 rejected\" {\n    const result = validateReplyTo(\"inbox\\x01reply\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateReplyTo very long string\" {\n    // 10000 character reply-to should be valid (no length limit)\n    const long_reply = \"a\" ** 10000;\n    try validateReplyTo(long_reply);\n}\n\n// Section 5: validateQueueGroup Edge Cases\n\ntest \"validateQueueGroup empty string\" {\n    const result = validateQueueGroup(\"\");\n    try std.testing.expectError(error.EmptySubject, result);\n}\n\ntest \"validateQueueGroup null byte rejected\" {\n    const result = validateQueueGroup(\"workers\\x00inject\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup allows dots\" {\n    // Queue groups can contain dots\n    try validateQueueGroup(\"worker.group.1\");\n}\n\ntest \"validateQueueGroup tab rejected\" {\n    const result = validateQueueGroup(\"workers\\tgroup\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup CR only rejected\" {\n    const result = validateQueueGroup(\"workers\\rgroup\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup LF only rejected\" {\n    const result = validateQueueGroup(\"workers\\ngroup\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup space rejected\" {\n    const result = validateQueueGroup(\"workers group\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup DEL char rejected\" {\n    const result = validateQueueGroup(\"workers\\x7fgroup\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup control char 0x01 rejected\" {\n    const result = validateQueueGroup(\"workers\\x01group\");\n    try std.testing.expectError(error.InvalidCharacter, result);\n}\n\ntest \"validateQueueGroup very long string\" {\n    // 10000 character queue group should be valid (no length limit)\n    const long_qg = \"w\" ** 10000;\n    try validateQueueGroup(long_qg);\n}\n\ntest \"validateQueueGroup allows special chars\" {\n    try validateQueueGroup(\"worker-pool_1\");\n    try validateQueueGroup(\"worker.pool.1\");\n    try validateQueueGroup(\"WORKERS\");\n}\n\n// Section 6: matches() Edge Cases\n\ntest \"matches empty pattern\" {\n    const result = matches(\"\", \"foo\");\n    try std.testing.expect(!result);\n}\n\ntest \"matches empty subject\" {\n    const result = matches(\"foo\", \"\");\n    try std.testing.expect(!result);\n}\n\ntest \"matches both empty\" {\n    // Both empty = both invalid, return false\n    const result = matches(\"\", \"\");\n    try std.testing.expect(!result);\n}\n\ntest \"matches single token exact\" {\n    try std.testing.expect(matches(\"foo\", \"foo\"));\n    try std.testing.expect(!matches(\"foo\", \"bar\"));\n}\n\ntest \"matches pattern longer than subject\" {\n    try std.testing.expect(!matches(\"foo.bar.baz\", \"foo.bar\"));\n    try std.testing.expect(!matches(\"foo.bar\", \"foo\"));\n}\n\ntest \"matches subject longer than pattern\" {\n    try std.testing.expect(!matches(\"foo\", \"foo.bar\"));\n    try std.testing.expect(!matches(\"foo.bar\", \"foo.bar.baz\"));\n}\n\ntest \"matches * matches empty token behavior\" {\n    // What happens with \"foo.*\" matching \"foo.\" (empty last token)?\n    // tokenizeScalar skips empty tokens, so this might have unexpected behavior\n    try std.testing.expect(!matches(\"foo.*\", \"foo.\"));\n}\n\ntest \"matches > requires at least one token\" {\n    // \">\" should require at least one token to match\n    try std.testing.expect(matches(\">\", \"a\"));\n    try std.testing.expect(matches(\">\", \"a.b.c\"));\n    // \"foo.>\" requires at least one token after foo\n    try std.testing.expect(!matches(\"foo.>\", \"foo\"));\n    try std.testing.expect(matches(\"foo.>\", \"foo.x\"));\n}\n\ntest \"matches multiple * wildcards\" {\n    try std.testing.expect(matches(\"*.*\", \"a.b\"));\n    try std.testing.expect(!matches(\"*.*\", \"a\"));\n    try std.testing.expect(!matches(\"*.*\", \"a.b.c\"));\n    try std.testing.expect(matches(\"*.*.*\", \"a.b.c\"));\n}\n\ntest \"matches * and > combination\" {\n    try std.testing.expect(matches(\"*.>\", \"a.b\"));\n    try std.testing.expect(matches(\"*.>\", \"a.b.c\"));\n    try std.testing.expect(!matches(\"*.>\", \"a\"));\n}\n\ntest \"matches with dots in pattern edge cases\" {\n    // Pattern and subject with trailing/leading dots\n    // tokenizeScalar skips empty tokens, so \"foo.\" -> [\"foo\"] and \".foo\" -> [\"foo\"]\n    // Both become equivalent, so they match (garbage in, garbage out for invalid subjects)\n    try std.testing.expect(matches(\"foo.\", \"foo.\"));\n    try std.testing.expect(matches(\".foo\", \".foo\"));\n}\n\n// Section 7: tokenCount Edge Cases\n\ntest \"tokenCount single dot\" {\n    // \".\" has two empty tokens\n    const count = tokenCount(\".\");\n    try std.testing.expectEqual(@as(usize, 2), count);\n}\n\ntest \"tokenCount multiple dots\" {\n    // \"..\" has three empty tokens\n    try std.testing.expectEqual(@as(usize, 3), tokenCount(\"..\"));\n    try std.testing.expectEqual(@as(usize, 4), tokenCount(\"...\"));\n}\n\ntest \"tokenCount trailing dot\" {\n    // \"foo.\" counts as 2 tokens even though second is empty\n    try std.testing.expectEqual(@as(usize, 2), tokenCount(\"foo.\"));\n}\n\ntest \"tokenCount leading dot\" {\n    // \".foo\" counts as 2 tokens even though first is empty\n    try std.testing.expectEqual(@as(usize, 2), tokenCount(\".foo\"));\n}\n\ntest \"tokenCount very long subject\" {\n    const long_subject = \"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z\";\n    try std.testing.expectEqual(@as(usize, 26), tokenCount(long_subject));\n}\n\n// Section 8: getToken Edge Cases\n\ntest \"getToken empty subject\" {\n    // Empty subject should return null for any index\n    try std.testing.expect(getToken(\"\", 0) == null);\n    try std.testing.expect(getToken(\"\", 1) == null);\n}\n\ntest \"getToken single dot\" {\n    // \".\" splits into empty tokens - tokenizeScalar skips them\n    try std.testing.expect(getToken(\".\", 0) == null);\n}\n\ntest \"getToken trailing dot\" {\n    // \"foo.\" - what does getToken return for index 1?\n    const token0 = getToken(\"foo.\", 0);\n    try std.testing.expectEqualSlices(u8, \"foo\", token0.?);\n    // Index 1 should be null (empty token skipped by tokenizeScalar)\n    try std.testing.expect(getToken(\"foo.\", 1) == null);\n}\n\ntest \"getToken leading dot\" {\n    // \".foo\" - what does getToken return for index 0?\n    const token0 = getToken(\".foo\", 0);\n    try std.testing.expectEqualSlices(u8, \"foo\", token0.?);\n    try std.testing.expect(getToken(\".foo\", 1) == null);\n}\n\ntest \"getToken very large index\" {\n    try std.testing.expect(getToken(\"foo.bar\", 1000) == null);\n}\n"
  },
  {
    "path": "src/pubsub/subscription.zig",
    "content": "//! Subscription Types (for embedded/zero-allocation use)\n//!\n//! This module contains types for embedded/no-alloc scenarios.\n//! For normal use, see client.zig Subscription type.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\nconst subject_mod = @import(\"subject.zig\");\n\n/// Subscription state.\npub const State = enum {\n    active,\n    draining,\n    unsubscribed,\n};\n\n/// Subscription-related errors.\npub const Error = error{\n    InvalidSubject,\n    InvalidSubscription,\n    SubscriptionClosed,\n};\n\n/// Fixed-size ring buffer queue (zero allocations).\n/// For embedded use cases where dynamic allocation is not allowed.\npub fn FixedQueue(comptime T: type, comptime capacity: u16) type {\n    return struct {\n        items: [capacity]T = undefined,\n        head: u16 = 0,\n        tail: u16 = 0,\n        count: u16 = 0,\n\n        const Self = @This();\n\n        pub fn push(self: *Self, item: T) !void {\n            if (self.count >= capacity) return error.QueueFull;\n            self.items[self.tail] = item;\n            self.tail = (self.tail + 1) % capacity;\n            self.count += 1;\n        }\n\n        pub fn tryPop(self: *Self) ?T {\n            if (self.count == 0) return null;\n            const item = self.items[self.head];\n            self.head = (self.head + 1) % capacity;\n            self.count -= 1;\n            return item;\n        }\n\n        pub fn clear(self: *Self) void {\n            self.head = 0;\n            self.tail = 0;\n            self.count = 0;\n        }\n    };\n}\n\n/// FixedSubscription slot configuration.\npub const FixedSubConfig = struct {\n    max_subject_len: u16 = 256,\n    max_queue_group_len: u16 = 64,\n    queue_capacity: u16 = 256,\n};\n\n/// Zero-allocation subscription slot (for embedded use).\n/// Uses fixed buffers for subject, queue_group, and message queue.\n/// Designed for embedding in fixed arrays (no heap allocation).\n/// Message type must be provided by user as it's defined in client.zig.\npub fn FixedSubscription(\n    comptime ClientType: type,\n    comptime MessageType: type,\n    comptime config: FixedSubConfig,\n) type {\n    return struct {\n        client: *ClientType,\n        sid: u64,\n        subject_buf: [config.max_subject_len]u8,\n        subject_len: u16,\n        queue_group_buf: [config.max_queue_group_len]u8,\n        queue_group_len: u16,\n        messages: FixedQueue(MessageType, config.queue_capacity),\n        state: State,\n        max_msgs: u64,\n        received_msgs: u64,\n        active: bool,\n\n        const Self = @This();\n\n        /// Initialize an inactive slot.\n        pub fn initEmpty() Self {\n            return .{\n                .client = undefined,\n                .sid = 0,\n                .subject_buf = undefined,\n                .subject_len = 0,\n                .queue_group_buf = undefined,\n                .queue_group_len = 0,\n                .messages = .{},\n                .state = .unsubscribed,\n                .max_msgs = 0,\n                .received_msgs = 0,\n                .active = false,\n            };\n        }\n\n        /// Activation errors.\n        pub const ActivateError = error{\n            EmptySubject,\n            SubjectTooLong,\n            QueueGroupTooLong,\n        };\n\n        /// Activate slot with subscription data.\n        pub fn activate(\n            self: *Self,\n            client_ptr: *ClientType,\n            sid_val: u64,\n            subj: []const u8,\n            queue_grp: ?[]const u8,\n        ) ActivateError!void {\n            if (subj.len == 0) return error.EmptySubject;\n            if (subj.len >= config.max_subject_len) {\n                return error.SubjectTooLong;\n            }\n            assert(subj.len > 0);\n            assert(subj.len <= config.max_subject_len);\n\n            self.client = client_ptr;\n            self.sid = sid_val;\n            self.subject_len = @intCast(subj.len);\n            @memcpy(self.subject_buf[0..subj.len], subj);\n\n            if (queue_grp) |qg| {\n                if (qg.len > config.max_queue_group_len) {\n                    return error.QueueGroupTooLong;\n                }\n                self.queue_group_len = @intCast(qg.len);\n                @memcpy(self.queue_group_buf[0..qg.len], qg);\n            } else {\n                self.queue_group_len = 0;\n            }\n\n            self.messages.clear();\n            self.state = .active;\n            self.max_msgs = 0;\n            self.received_msgs = 0;\n            self.active = true;\n        }\n\n        /// Deactivate slot (returns to pool).\n        pub fn deactivate(self: *Self) void {\n            self.active = false;\n            self.state = .unsubscribed;\n            self.sid = 0;\n        }\n\n        /// Get subject slice.\n        pub fn subject(self: *const Self) []const u8 {\n            return self.subject_buf[0..self.subject_len];\n        }\n\n        /// Get queue group slice (null if not set).\n        pub fn queueGroup(self: *const Self) ?[]const u8 {\n            if (self.queue_group_len == 0) return null;\n            return self.queue_group_buf[0..self.queue_group_len];\n        }\n\n        /// Returns pending message count.\n        pub fn pending(self: *const Self) u16 {\n            return self.messages.count;\n        }\n\n        /// Start draining.\n        pub fn drain(self: *Self) void {\n            if (self.state == .active) {\n                self.state = .draining;\n            }\n        }\n\n        /// Check if active.\n        pub fn isActive(self: *const Self) bool {\n            return self.state == .active and self.active;\n        }\n\n        /// Match subject pattern.\n        pub fn matches(self: *const Self, msg_subject: []const u8) bool {\n            return subject_mod.matches(self.subject(), msg_subject);\n        }\n    };\n}\n\ntest {\n    _ = @import(\"subscription_test.zig\");\n}\n"
  },
  {
    "path": "src/pubsub/subscription_test.zig",
    "content": "//! Subscription Module Tests\n//!\n//! Tests ring buffer wraparound, capacity limits, state transitions,\n//! and subject validation edge cases.\n\nconst std = @import(\"std\");\nconst testing = std.testing;\nconst subscription = @import(\"subscription.zig\");\nconst FixedQueue = subscription.FixedQueue;\nconst FixedSubscription = subscription.FixedSubscription;\nconst State = subscription.State;\n\n// Test types for FixedSubscription testing\nconst TestClient = struct {\n    dummy: u32 = 0,\n};\n\nconst TestMessage = struct {\n    data: u32,\n};\n\nconst TestConfig = subscription.FixedSubConfig{\n    .max_subject_len = 64,\n    .max_queue_group_len = 32,\n    .queue_capacity = 8,\n};\n\nconst TestSub = FixedSubscription(TestClient, TestMessage, TestConfig);\n\n// Section 1: FixedQueue Basic Operations\n\ntest \"FixedQueue push and pop basic\" {\n    var q: FixedQueue(u32, 4) = .{};\n    try testing.expectEqual(@as(u16, 0), q.count);\n\n    try q.push(1);\n    try q.push(2);\n    try q.push(3);\n\n    try testing.expectEqual(@as(u16, 3), q.count);\n    try testing.expectEqual(@as(?u32, 1), q.tryPop());\n    try testing.expectEqual(@as(?u32, 2), q.tryPop());\n    try testing.expectEqual(@as(?u32, 3), q.tryPop());\n    try testing.expectEqual(@as(u16, 0), q.count);\n}\n\ntest \"FixedQueue full returns error\" {\n    var q: FixedQueue(u32, 2) = .{};\n\n    try q.push(1);\n    try q.push(2);\n    try testing.expectError(error.QueueFull, q.push(3));\n    try testing.expectEqual(@as(u16, 2), q.count);\n}\n\ntest \"FixedQueue pop from empty returns null\" {\n    var q: FixedQueue(u32, 4) = .{};\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n    try testing.expectEqual(@as(u16, 0), q.count);\n}\n\n// Section 2: FixedQueue Wraparound Behavior\n\ntest \"FixedQueue wraparound single cycle\" {\n    // Capacity 4: fill, empty, refill to test wraparound\n    var q: FixedQueue(u32, 4) = .{};\n\n    // Fill completely\n    try q.push(1);\n    try q.push(2);\n    try q.push(3);\n    try q.push(4);\n    try testing.expectError(error.QueueFull, q.push(5));\n\n    // Empty completely\n    try testing.expectEqual(@as(?u32, 1), q.tryPop());\n    try testing.expectEqual(@as(?u32, 2), q.tryPop());\n    try testing.expectEqual(@as(?u32, 3), q.tryPop());\n    try testing.expectEqual(@as(?u32, 4), q.tryPop());\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n\n    // Refill - now head/tail have wrapped\n    try q.push(10);\n    try q.push(20);\n    try q.push(30);\n    try q.push(40);\n\n    // Verify FIFO order maintained\n    try testing.expectEqual(@as(?u32, 10), q.tryPop());\n    try testing.expectEqual(@as(?u32, 20), q.tryPop());\n    try testing.expectEqual(@as(?u32, 30), q.tryPop());\n    try testing.expectEqual(@as(?u32, 40), q.tryPop());\n}\n\ntest \"FixedQueue wraparound interleaved push pop\" {\n    var q: FixedQueue(u32, 4) = .{};\n\n    // Push 2, pop 1, repeat - causes gradual wraparound\n    try q.push(1);\n    try q.push(2);\n    try testing.expectEqual(@as(?u32, 1), q.tryPop());\n\n    try q.push(3);\n    try q.push(4);\n    try testing.expectEqual(@as(?u32, 2), q.tryPop());\n\n    try q.push(5);\n    try q.push(6);\n    try testing.expectEqual(@as(?u32, 3), q.tryPop());\n\n    // Continue until wrapped multiple times\n    try q.push(7);\n    try testing.expectEqual(@as(?u32, 4), q.tryPop());\n    try q.push(8);\n    try testing.expectEqual(@as(?u32, 5), q.tryPop());\n\n    // Verify remaining\n    try testing.expectEqual(@as(?u32, 6), q.tryPop());\n    try testing.expectEqual(@as(?u32, 7), q.tryPop());\n    try testing.expectEqual(@as(?u32, 8), q.tryPop());\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n}\n\ntest \"FixedQueue wraparound stress\" {\n    var q: FixedQueue(u32, 8) = .{};\n\n    // 100 push/pop cycles to stress wraparound\n    var i: u32 = 0;\n    while (i < 100) : (i += 1) {\n        try q.push(i);\n        const val = q.tryPop();\n        try testing.expectEqual(@as(?u32, i), val);\n    }\n\n    try testing.expectEqual(@as(u16, 0), q.count);\n}\n\n// Section 3: FixedQueue Clear Operations\n\ntest \"FixedQueue clear non-empty\" {\n    var q: FixedQueue(u32, 4) = .{};\n\n    try q.push(1);\n    try q.push(2);\n    try q.push(3);\n    try testing.expectEqual(@as(u16, 3), q.count);\n\n    q.clear();\n\n    try testing.expectEqual(@as(u16, 0), q.count);\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n\n    // Should be able to push again\n    try q.push(100);\n    try testing.expectEqual(@as(?u32, 100), q.tryPop());\n}\n\ntest \"FixedQueue clear empty\" {\n    var q: FixedQueue(u32, 4) = .{};\n\n    q.clear();\n    q.clear(); // Double clear\n    q.clear();\n\n    try testing.expectEqual(@as(u16, 0), q.count);\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n}\n\ntest \"FixedQueue clear after wraparound\" {\n    var q: FixedQueue(u32, 4) = .{};\n\n    // Cause wraparound\n    try q.push(1);\n    try q.push(2);\n    _ = q.tryPop();\n    _ = q.tryPop();\n    try q.push(3);\n    try q.push(4);\n\n    q.clear();\n\n    try testing.expectEqual(@as(u16, 0), q.count);\n    try testing.expectEqual(@as(u16, 0), q.head);\n    try testing.expectEqual(@as(u16, 0), q.tail);\n}\n\n// Section 4: FixedQueue Edge Capacities\n\ntest \"FixedQueue capacity 1\" {\n    var q: FixedQueue(u32, 1) = .{};\n\n    try q.push(42);\n    try testing.expectError(error.QueueFull, q.push(43));\n    try testing.expectEqual(@as(?u32, 42), q.tryPop());\n    try testing.expectEqual(@as(?u32, null), q.tryPop());\n\n    // Can push again\n    try q.push(100);\n    try testing.expectEqual(@as(?u32, 100), q.tryPop());\n}\n\ntest \"FixedQueue capacity 2\" {\n    var q: FixedQueue(u32, 2) = .{};\n\n    try q.push(1);\n    try q.push(2);\n    try testing.expectError(error.QueueFull, q.push(3));\n\n    try testing.expectEqual(@as(?u32, 1), q.tryPop());\n    try q.push(3);\n    try testing.expectEqual(@as(?u32, 2), q.tryPop());\n    try testing.expectEqual(@as(?u32, 3), q.tryPop());\n}\n\ntest \"FixedQueue capacity 256\" {\n    var q: FixedQueue(u32, 256) = .{};\n\n    // Fill to capacity\n    var i: u32 = 0;\n    while (i < 256) : (i += 1) {\n        try q.push(i);\n    }\n\n    try testing.expectError(error.QueueFull, q.push(256));\n    try testing.expectEqual(@as(u16, 256), q.count);\n\n    // Verify FIFO order\n    i = 0;\n    while (i < 256) : (i += 1) {\n        try testing.expectEqual(@as(?u32, i), q.tryPop());\n    }\n}\n\n// Section 5: FixedQueue Count Accuracy\n\ntest \"FixedQueue count accuracy through operations\" {\n    var q: FixedQueue(u32, 8) = .{};\n\n    try testing.expectEqual(@as(u16, 0), q.count);\n\n    try q.push(1);\n    try testing.expectEqual(@as(u16, 1), q.count);\n\n    try q.push(2);\n    try q.push(3);\n    try testing.expectEqual(@as(u16, 3), q.count);\n\n    _ = q.tryPop();\n    try testing.expectEqual(@as(u16, 2), q.count);\n\n    _ = q.tryPop();\n    _ = q.tryPop();\n    try testing.expectEqual(@as(u16, 0), q.count);\n\n    // Pop from empty doesn't affect count\n    _ = q.tryPop();\n    try testing.expectEqual(@as(u16, 0), q.count);\n}\n\ntest \"FixedQueue count with failed push\" {\n    var q: FixedQueue(u32, 2) = .{};\n\n    try q.push(1);\n    try q.push(2);\n    try testing.expectEqual(@as(u16, 2), q.count);\n\n    // Failed push shouldn't change count\n    try testing.expectError(error.QueueFull, q.push(3));\n    try testing.expectEqual(@as(u16, 2), q.count);\n}\n\n// Section 6: FixedQueue FIFO Order\n\ntest \"FixedQueue strict FIFO order\" {\n    var q: FixedQueue(u32, 16) = .{};\n\n    const values = [_]u32{ 100, 200, 300, 400, 500, 600, 700, 800 };\n    for (values) |v| {\n        try q.push(v);\n    }\n\n    for (values) |expected| {\n        try testing.expectEqual(@as(?u32, expected), q.tryPop());\n    }\n}\n\ntest \"FixedQueue FIFO with interleaved operations\" {\n    var q: FixedQueue(u32, 4) = .{};\n\n    try q.push(1);\n    try q.push(2);\n    try testing.expectEqual(@as(?u32, 1), q.tryPop());\n    try q.push(3);\n    try testing.expectEqual(@as(?u32, 2), q.tryPop());\n    try q.push(4);\n    try q.push(5);\n    try testing.expectEqual(@as(?u32, 3), q.tryPop());\n    try testing.expectEqual(@as(?u32, 4), q.tryPop());\n    try testing.expectEqual(@as(?u32, 5), q.tryPop());\n}\n\n// Section 7: FixedQueue Different Types\n\ntest \"FixedQueue with struct type\" {\n    const Item = struct { id: u32, value: u64 };\n    var q: FixedQueue(Item, 4) = .{};\n\n    try q.push(.{ .id = 1, .value = 100 });\n    try q.push(.{ .id = 2, .value = 200 });\n\n    const item1 = q.tryPop().?;\n    try testing.expectEqual(@as(u32, 1), item1.id);\n    try testing.expectEqual(@as(u64, 100), item1.value);\n\n    const item2 = q.tryPop().?;\n    try testing.expectEqual(@as(u32, 2), item2.id);\n    try testing.expectEqual(@as(u64, 200), item2.value);\n}\n\ntest \"FixedQueue with pointer type\" {\n    var values = [_]u32{ 10, 20, 30 };\n    var q: FixedQueue(*u32, 4) = .{};\n\n    try q.push(&values[0]);\n    try q.push(&values[1]);\n    try q.push(&values[2]);\n\n    const p1 = q.tryPop().?;\n    try testing.expectEqual(@as(u32, 10), p1.*);\n\n    const p2 = q.tryPop().?;\n    try testing.expectEqual(@as(u32, 20), p2.*);\n}\n\n// Section 8: FixedSubscription initEmpty\n\ntest \"FixedSubscription initEmpty defaults\" {\n    const sub = TestSub.initEmpty();\n\n    try testing.expectEqual(@as(u64, 0), sub.sid);\n    try testing.expectEqual(@as(u8, 0), sub.subject_len);\n    try testing.expectEqual(@as(u8, 0), sub.queue_group_len);\n    try testing.expectEqual(State.unsubscribed, sub.state);\n    try testing.expectEqual(@as(u64, 0), sub.max_msgs);\n    try testing.expectEqual(@as(u64, 0), sub.received_msgs);\n    try testing.expectEqual(false, sub.active);\n}\n\ntest \"FixedSubscription initEmpty subject accessor\" {\n    const sub = TestSub.initEmpty();\n\n    // subject() returns empty slice when inactive\n    const s = sub.subject();\n    try testing.expectEqual(@as(usize, 0), s.len);\n}\n\ntest \"FixedSubscription initEmpty queueGroup accessor\" {\n    const sub = TestSub.initEmpty();\n\n    // queueGroup() returns null when not set\n    try testing.expect(sub.queueGroup() == null);\n}\n\n// Section 9: FixedSubscription activate Valid Cases\n\ntest \"FixedSubscription activate basic\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 123, \"test.subject\", null);\n\n    try testing.expectEqual(@as(u64, 123), sub.sid);\n    try testing.expectEqualStrings(\"test.subject\", sub.subject());\n    try testing.expect(sub.queueGroup() == null);\n    try testing.expectEqual(State.active, sub.state);\n    try testing.expectEqual(true, sub.active);\n    try testing.expectEqual(true, sub.isActive());\n}\n\ntest \"FixedSubscription activate with queue group\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 456, \"orders\", \"workers\");\n\n    try testing.expectEqualStrings(\"orders\", sub.subject());\n    try testing.expectEqualStrings(\"workers\", sub.queueGroup().?);\n    try testing.expectEqual(true, sub.isActive());\n}\n\ntest \"FixedSubscription activate subject at max length\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // max_subject_len is exclusive (>= rejects it)\n    const max_subject = \"a\" ** 63;\n    try sub.activate(&client, 1, max_subject, null);\n\n    try testing.expectEqual(@as(usize, 63), sub.subject().len);\n    try testing.expectEqualStrings(max_subject, sub.subject());\n}\n\ntest \"FixedSubscription activate queue_group at max length\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // Config has max_queue_group_len = 32\n    const max_qg = \"q\" ** 32;\n    try sub.activate(&client, 1, \"test\", max_qg);\n\n    try testing.expectEqual(@as(usize, 32), sub.queueGroup().?.len);\n    try testing.expectEqualStrings(max_qg, sub.queueGroup().?);\n}\n\n// Section 10: FixedSubscription activate Error Cases\n\ntest \"FixedSubscription activate subject too long\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // Config has max_subject_len = 64\n    const long_subject = \"a\" ** 65;\n    try testing.expectError(\n        error.SubjectTooLong,\n        sub.activate(&client, 1, long_subject, null),\n    );\n\n    // Should remain inactive\n    try testing.expectEqual(false, sub.active);\n}\n\ntest \"FixedSubscription activate queue_group too long\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // Config has max_queue_group_len = 32\n    const long_qg = \"q\" ** 33;\n    try testing.expectError(\n        error.QueueGroupTooLong,\n        sub.activate(&client, 1, \"test\", long_qg),\n    );\n\n    try testing.expectEqual(false, sub.active);\n}\n\ntest \"FixedSubscription activate empty subject returns error\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try testing.expectError(\n        error.EmptySubject,\n        sub.activate(&client, 1, \"\", null),\n    );\n\n    try testing.expectEqual(false, sub.active);\n}\n\n// Section 11: FixedSubscription deactivate\n\ntest \"FixedSubscription deactivate resets state\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 123, \"test\", null);\n    try testing.expectEqual(true, sub.isActive());\n\n    sub.deactivate();\n\n    try testing.expectEqual(false, sub.active);\n    try testing.expectEqual(State.unsubscribed, sub.state);\n    try testing.expectEqual(@as(u64, 0), sub.sid);\n    try testing.expectEqual(false, sub.isActive());\n}\n\ntest \"FixedSubscription deactivate on inactive\" {\n    var sub = TestSub.initEmpty();\n\n    // Should be safe to deactivate an inactive slot\n    sub.deactivate();\n    sub.deactivate();\n\n    try testing.expectEqual(false, sub.active);\n}\n\ntest \"FixedSubscription reactivate after deactivate\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"first.subject\", null);\n    sub.deactivate();\n\n    try sub.activate(&client, 2, \"second.subject\", \"queue\");\n\n    try testing.expectEqual(@as(u64, 2), sub.sid);\n    try testing.expectEqualStrings(\"second.subject\", sub.subject());\n    try testing.expectEqualStrings(\"queue\", sub.queueGroup().?);\n    try testing.expectEqual(true, sub.isActive());\n}\n\n// Section 12: FixedSubscription State Transitions\n\ntest \"FixedSubscription drain from active\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"test\", null);\n    try testing.expectEqual(State.active, sub.state);\n\n    sub.drain();\n\n    try testing.expectEqual(State.draining, sub.state);\n    // Note: isActive() returns false when draining\n    try testing.expectEqual(false, sub.isActive());\n}\n\ntest \"FixedSubscription drain from non-active is noop\" {\n    var sub = TestSub.initEmpty();\n\n    sub.drain();\n    try testing.expectEqual(State.unsubscribed, sub.state);\n\n    // Set to draining, drain again should be noop\n    var client = TestClient{};\n    try sub.activate(&client, 1, \"test\", null);\n    sub.drain();\n    try testing.expectEqual(State.draining, sub.state);\n\n    sub.drain(); // Should not change state\n    try testing.expectEqual(State.draining, sub.state);\n}\n\ntest \"FixedSubscription isActive requires both flags\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // Initially: state=unsubscribed, active=false -> isActive=false\n    try testing.expectEqual(false, sub.isActive());\n\n    // After activate: state=active, active=true -> isActive=true\n    try sub.activate(&client, 1, \"test\", null);\n    try testing.expectEqual(true, sub.isActive());\n\n    // After drain: state=draining, active=true -> isActive=false\n    sub.drain();\n    try testing.expectEqual(false, sub.isActive());\n\n    // After deactivate: state=unsubscribed, active=false -> isActive=false\n    sub.deactivate();\n    try testing.expectEqual(false, sub.isActive());\n}\n\n// Section 13: FixedSubscription pending count\n\ntest \"FixedSubscription pending initial\" {\n    const sub = TestSub.initEmpty();\n    try testing.expectEqual(@as(u16, 0), sub.pending());\n}\n\ntest \"FixedSubscription pending after activate\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"test\", null);\n    try testing.expectEqual(@as(u16, 0), sub.pending());\n}\n\ntest \"FixedSubscription pending after message push\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"test\", null);\n\n    try sub.messages.push(.{ .data = 1 });\n    try testing.expectEqual(@as(u16, 1), sub.pending());\n\n    try sub.messages.push(.{ .data = 2 });\n    try testing.expectEqual(@as(u16, 2), sub.pending());\n\n    _ = sub.messages.tryPop();\n    try testing.expectEqual(@as(u16, 1), sub.pending());\n}\n\n// Section 14: FixedSubscription matches\n\ntest \"FixedSubscription matches exact\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"foo.bar\", null);\n\n    try testing.expect(sub.matches(\"foo.bar\"));\n    try testing.expect(!sub.matches(\"foo.baz\"));\n    try testing.expect(!sub.matches(\"foo\"));\n    try testing.expect(!sub.matches(\"foo.bar.baz\"));\n}\n\ntest \"FixedSubscription matches wildcard single\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"foo.*\", null);\n\n    try testing.expect(sub.matches(\"foo.bar\"));\n    try testing.expect(sub.matches(\"foo.baz\"));\n    try testing.expect(!sub.matches(\"foo.bar.baz\"));\n    try testing.expect(!sub.matches(\"foo\"));\n}\n\ntest \"FixedSubscription matches wildcard full\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"foo.>\", null);\n\n    try testing.expect(sub.matches(\"foo.bar\"));\n    try testing.expect(sub.matches(\"foo.bar.baz\"));\n    try testing.expect(sub.matches(\"foo.a.b.c.d\"));\n    try testing.expect(!sub.matches(\"foo\"));\n    try testing.expect(!sub.matches(\"bar.foo\"));\n}\n\n// Section 15: FixedSubscription Multiple Cycles\n\ntest \"FixedSubscription multiple activate deactivate cycles\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    var i: u64 = 0;\n    while (i < 10) : (i += 1) {\n        try sub.activate(&client, i, \"test\", null);\n        try testing.expectEqual(i, sub.sid);\n        try testing.expectEqual(true, sub.isActive());\n\n        sub.deactivate();\n        try testing.expectEqual(false, sub.isActive());\n    }\n}\n\ntest \"FixedSubscription activate clears message queue\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"test\", null);\n\n    // Push some messages\n    try sub.messages.push(.{ .data = 1 });\n    try sub.messages.push(.{ .data = 2 });\n    try testing.expectEqual(@as(u16, 2), sub.pending());\n\n    // Deactivate and reactivate\n    sub.deactivate();\n    try sub.activate(&client, 2, \"other\", null);\n\n    // Queue should be cleared\n    try testing.expectEqual(@as(u16, 0), sub.pending());\n}\n\n// Section 16: State Enum\n\ntest \"State enum values distinct\" {\n    try testing.expect(State.active != State.draining);\n    try testing.expect(State.active != State.unsubscribed);\n    try testing.expect(State.draining != State.unsubscribed);\n}\n\n// Section 17: Edge Case Configs\n\ntest \"FixedSubscription minimal config\" {\n    const MinConfig = subscription.FixedSubConfig{\n        .max_subject_len = 2,\n        .max_queue_group_len = 2,\n        .queue_capacity = 1,\n    };\n\n    const MinSub = FixedSubscription(TestClient, TestMessage, MinConfig);\n    var client = TestClient{};\n    var sub = MinSub.initEmpty();\n\n    try sub.activate(&client, 1, \"a\", \"b\");\n    try testing.expectEqualStrings(\"a\", sub.subject());\n    try testing.expectEqualStrings(\"b\", sub.queueGroup().?);\n\n    // Queue capacity 1\n    try sub.messages.push(.{ .data = 1 });\n    try testing.expectError(error.QueueFull, sub.messages.push(.{ .data = 2 }));\n}\n\ntest \"FixedSubscription large config\" {\n    const LargeConfig = subscription.FixedSubConfig{\n        .max_subject_len = 1024,\n        .max_queue_group_len = 512,\n        .queue_capacity = 1024,\n    };\n\n    const LargeSub = FixedSubscription(TestClient, TestMessage, LargeConfig);\n    var client = TestClient{};\n    var sub = LargeSub.initEmpty();\n\n    const long_subject = \"a\" ** 1023;\n    try sub.activate(&client, 1, long_subject, null);\n    try testing.expectEqual(@as(usize, 1023), sub.subject().len);\n}\n\ntest \"FixedSubscription large queue group\" {\n    const LargeConfig = subscription.FixedSubConfig{\n        .max_subject_len = 256,\n        .max_queue_group_len = 512,\n        .queue_capacity = 256,\n    };\n\n    const LargeSub = FixedSubscription(TestClient, TestMessage, LargeConfig);\n    var client = TestClient{};\n    var sub = LargeSub.initEmpty();\n\n    const long_qg = \"q\" ** 512;\n    try sub.activate(&client, 1, \"test\", long_qg);\n    try testing.expectEqual(@as(usize, 512), sub.queueGroup().?.len);\n}\n\n// Section 18: SID Edge Values\n\ntest \"FixedSubscription SID zero\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 0, \"test\", null);\n    try testing.expectEqual(@as(u64, 0), sub.sid);\n}\n\ntest \"FixedSubscription SID max u64\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, std.math.maxInt(u64), \"test\", null);\n    try testing.expectEqual(std.math.maxInt(u64), sub.sid);\n}\n\n// Section 19: Received/Max Messages\n\ntest \"FixedSubscription received msgs initial\" {\n    const sub = TestSub.initEmpty();\n    try testing.expectEqual(@as(u64, 0), sub.received_msgs);\n}\n\ntest \"FixedSubscription max msgs initial\" {\n    const sub = TestSub.initEmpty();\n    try testing.expectEqual(@as(u64, 0), sub.max_msgs);\n}\n\ntest \"FixedSubscription activate resets counters\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // Manually set counters\n    sub.received_msgs = 100;\n    sub.max_msgs = 50;\n\n    try sub.activate(&client, 1, \"test\", null);\n\n    try testing.expectEqual(@as(u64, 0), sub.received_msgs);\n    try testing.expectEqual(@as(u64, 0), sub.max_msgs);\n}\n\n// Section 20: Subject/QueueGroup with Dots\n\ntest \"FixedSubscription subject with dots\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"a.b.c.d.e\", null);\n    try testing.expectEqualStrings(\"a.b.c.d.e\", sub.subject());\n}\n\ntest \"FixedSubscription subject single char\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"x\", null);\n    try testing.expectEqualStrings(\"x\", sub.subject());\n}\n\ntest \"FixedSubscription queue group with dots\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // NATS queue groups typically don't have dots but are accepted\n    try sub.activate(&client, 1, \"test\", \"group.name\");\n    try testing.expectEqualStrings(\"group.name\", sub.queueGroup().?);\n}\n\n// Section 21: Subject/QueueGroup Special Characters\n\ntest \"FixedSubscription subject with hyphens and underscores\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"my-subject_name\", null);\n    try testing.expectEqualStrings(\"my-subject_name\", sub.subject());\n}\n\ntest \"FixedSubscription subject with numbers\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"topic123.sub456\", null);\n    try testing.expectEqualStrings(\"topic123.sub456\", sub.subject());\n}\n\ntest \"FixedSubscription queue group with hyphens\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    try sub.activate(&client, 1, \"test\", \"worker-pool-1\");\n    try testing.expectEqualStrings(\"worker-pool-1\", sub.queueGroup().?);\n}\n\ntest \"FixedSubscription subject all printable ASCII\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // Most printable ASCII chars (excluding space and control chars)\n    try sub.activate(&client, 1, \"!#$%&'()+,-./0123456789:;<=>?@ABC\", null);\n    try testing.expectEqualStrings(\"!#$%&'()+,-./0123456789:;<=>?@ABC\", sub.subject());\n}\n\n// Section 22: Subject/QueueGroup Unicode\n// NOTE: FixedSubscription stores raw bytes - it does NOT validate subject content.\n// Unicode validation (if needed) should be done at the NATS protocol encoder level.\n// These tests verify that arbitrary bytes are stored/retrieved correctly.\n\ntest \"FixedSubscription subject with unicode bytes\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // UTF-8 encoded Japanese: \"テスト\" (tesuto = test)\n    const unicode_subject = \"\\xe3\\x83\\x86\\xe3\\x82\\xb9\\xe3\\x83\\x88\";\n    try sub.activate(&client, 1, unicode_subject, null);\n    try testing.expectEqualStrings(unicode_subject, sub.subject());\n}\n\ntest \"FixedSubscription subject with emoji bytes\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // UTF-8 encoded emoji:  (rocket)\n    const emoji_subject = \"\\xf0\\x9f\\x9a\\x80.launch\";\n    try sub.activate(&client, 1, emoji_subject, null);\n    try testing.expectEqualStrings(emoji_subject, sub.subject());\n}\n\ntest \"FixedSubscription queue group with unicode bytes\" {\n    var client = TestClient{};\n    var sub = TestSub.initEmpty();\n\n    // UTF-8 encoded: \"队列\" (duìliè = queue in Chinese)\n    const unicode_qg = \"\\xe9\\x98\\x9f\\xe5\\x88\\x97\";\n    try sub.activate(&client, 1, \"test\", unicode_qg);\n    try testing.expectEqualStrings(unicode_qg, sub.queueGroup().?);\n}\n"
  },
  {
    "path": "src/pubsub.zig",
    "content": "//! Pub/Sub Module\n//!\n//! Provides publish/subscribe utilities including inbox generation\n//! and subject validation. For Message and Subscription types, see client.zig.\n\nconst std = @import(\"std\");\n\npub const inbox = @import(\"pubsub/inbox.zig\");\npub const subject = @import(\"pubsub/subject.zig\");\npub const subscription = @import(\"pubsub/subscription.zig\");\n\n// Subscription state enum (for embedded/fixed use)\npub const SubscriptionState = subscription.State;\npub const SubscriptionError = subscription.Error;\n\n// Fixed types for embedded use (no allocations)\npub const FixedQueue = subscription.FixedQueue;\npub const FixedSubscription = subscription.FixedSubscription;\npub const FixedSubConfig = subscription.FixedSubConfig;\n\npub const newInbox = inbox.newInbox;\npub const newInboxBuf = inbox.newInboxBuf;\npub const isInbox = inbox.isInbox;\n\npub const validatePublish = subject.validatePublish;\npub const validateSubscribe = subject.validateSubscribe;\npub const validateQueueGroup = subject.validateQueueGroup;\npub const validateReplyTo = subject.validateReplyTo;\npub const subjectMatches = subject.matches;\n\ntest {\n    std.testing.refAllDecls(@This());\n}\n"
  },
  {
    "path": "src/sync/byte_ring.zig",
    "content": "//! Lock-free SPSC Byte Ring Buffer\n//!\n//! Variable-length message passing between producer and consumer threads.\n//! Each entry is: [4-byte little-endian length][payload bytes].\n//! Length=0 is a padding sentinel meaning \"skip to ring start\".\n//!\n//! ## Memory Ordering\n//!\n//! Same release-acquire pattern as SpscQueue (see spsc_queue.zig):\n//! - `head`: written by producer (.release), read by consumer (.acquire)\n//! - `tail`: written by consumer (.release), read by producer (.acquire)\n//! - Producer's buffer writes are visible to consumer via head release-acquire.\n//! - Consumer's consumption is visible to producer via tail release-acquire.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// Length header size (4 bytes, little-endian u32).\npub const HDR_SIZE: usize = 4;\n\n/// Lock-free SPSC ring buffer for variable-length byte messages.\n/// Producer reserves space, writes data, commits. Consumer peeks, reads, advances.\npub const ByteRing = struct {\n    buffer: []u8,\n    capacity: usize,\n    head: std.atomic.Value(usize),\n    tail: std.atomic.Value(usize),\n\n    const Self = @This();\n\n    /// Initialize with pre-allocated buffer.\n    /// Capacity must be a power of 2 and >= 64.\n    pub fn init(buffer: []u8) Self {\n        assert(buffer.len >= 64);\n        assert(std.math.isPowerOfTwo(buffer.len));\n        return .{\n            .buffer = buffer,\n            .capacity = buffer.len,\n            .head = std.atomic.Value(usize).init(0),\n            .tail = std.atomic.Value(usize).init(0),\n        };\n    }\n\n    /// Returns available space in the ring (approximate).\n    pub fn available(self: *const Self) usize {\n        const head = self.head.load(.monotonic);\n        const tail = self.tail.load(.acquire);\n        return self.capacity -% (head -% tail);\n    }\n\n    /// Producer: calculate encoded size for a message.\n    /// Returns total ring bytes needed (header + payload).\n    pub fn entrySize(data_len: usize) usize {\n        return HDR_SIZE + data_len;\n    }\n\n    /// Producer: reserve contiguous space for `data_len` bytes.\n    /// Returns writable slice (header + payload area) or null if full.\n    /// If the entry doesn't fit before wrap, inserts a padding\n    /// sentinel and wraps to ring start.\n    pub fn reserve(\n        self: *Self,\n        data_len: usize,\n    ) ?[]u8 {\n        const total = entrySize(data_len);\n        if (total > self.capacity / 2) return null;\n\n        const head = self.head.load(.monotonic);\n        const tail = self.tail.load(.acquire);\n        const used = head -% tail;\n\n        if (used + total > self.capacity) return null;\n\n        const offset = head % self.capacity;\n        const remaining = self.capacity - offset;\n\n        if (remaining >= total) {\n            // Fits before wrap\n            return self.buffer[offset .. offset + total];\n        }\n\n        // Doesn't fit — insert padding sentinel at wrap point\n        if (remaining >= HDR_SIZE) {\n            // Write zero-length marker\n            const pad = self.buffer[offset..][0..HDR_SIZE];\n            std.mem.writeInt(u32, pad, 0, .little);\n        }\n\n        // Check if wrapping still fits\n        const new_used = used + remaining + total;\n        if (new_used > self.capacity) return null;\n\n        // REVIEWED(2025-03): Head advance here is safe.\n        // Consumer sees padding sentinel (zero-length header)\n        // and skips to offset 0. Actual data at offset 0 is\n        // only visible after commit() does a second .release\n        // store. The release-acquire chain prevents reading\n        // uncommitted data.\n        self.head.store(head +% remaining, .release);\n\n        // Now at ring start\n        return self.buffer[0..total];\n    }\n\n    /// Producer: commit an entry. Writes the length header and\n    /// advances head atomically (.release).\n    pub fn commit(self: *Self, entry: []u8, data_len: usize) void {\n        assert(entry.len >= HDR_SIZE + data_len);\n        assert(data_len > 0);\n\n        // Write length header\n        const hdr = entry[0..HDR_SIZE];\n        std.mem.writeInt(u32, hdr, @intCast(data_len), .little);\n\n        // Advance head past this entry\n        const head = self.head.load(.monotonic);\n        self.head.store(\n            head +% entrySize(data_len),\n            .release,\n        );\n    }\n\n    /// Consumer: peek at the next entry's data.\n    /// Returns the data slice (after the 4-byte header).\n    /// Skips padding sentinels (length=0) automatically.\n    /// Returns null if ring is empty.\n    pub fn peek(self: *Self) ?[]const u8 {\n        var tail = self.tail.load(.monotonic);\n        const head = self.head.load(.acquire);\n\n        while (tail != head) {\n            const offset = tail % self.capacity;\n\n            // Need at least HDR_SIZE bytes to read length\n            if (self.capacity - offset < HDR_SIZE) {\n                // Not enough space for a header — skip to start\n                tail = tail +% (self.capacity - offset);\n                self.tail.store(tail, .release);\n                continue;\n            }\n\n            const hdr = self.buffer[offset..][0..HDR_SIZE];\n            const entry_len = std.mem.readInt(\n                u32,\n                hdr,\n                .little,\n            );\n\n            if (entry_len == 0) {\n                // Padding sentinel — skip to wrap\n                const remaining = self.capacity - offset;\n                tail = tail +% remaining;\n                self.tail.store(tail, .release);\n                continue;\n            }\n\n            const data_start = offset + HDR_SIZE;\n            return self.buffer[data_start .. data_start + entry_len];\n        }\n\n        return null;\n    }\n\n    /// Consumer: advance past the current entry.\n    /// Must be called after peek() returned non-null.\n    pub fn advance(self: *Self) void {\n        const tail = self.tail.load(.monotonic);\n        const offset = tail % self.capacity;\n        const hdr = self.buffer[offset..][0..HDR_SIZE];\n        const entry_len = std.mem.readInt(\n            u32,\n            hdr,\n            .little,\n        );\n        assert(entry_len > 0);\n        self.tail.store(\n            tail +% entrySize(entry_len),\n            .release,\n        );\n    }\n\n    /// Consumer: drain all entries, writing each to the writer.\n    /// Returns number of entries drained.\n    pub fn drainToWriter(\n        self: *Self,\n        writer: anytype,\n    ) !usize {\n        var count: usize = 0;\n        while (self.peek()) |data| {\n            try writer.writeAll(data);\n            self.advance();\n            count += 1;\n        }\n        return count;\n    }\n\n    /// Returns true if ring appears empty.\n    pub fn isEmpty(self: *const Self) bool {\n        const head = self.head.load(.acquire);\n        const tail = self.tail.load(.acquire);\n        return head == tail;\n    }\n\n    /// Returns approximate number of bytes used.\n    pub fn len(self: *const Self) usize {\n        const head = self.head.load(.acquire);\n        const tail = self.tail.load(.acquire);\n        return head -% tail;\n    }\n\n    /// Reset ring to empty state (not thread-safe).\n    pub fn clear(self: *Self) void {\n        self.head.store(0, .release);\n        self.tail.store(0, .release);\n    }\n};\n\n// --- Tests ---\n\ntest \"ByteRing basic write/read\" {\n    var buf: [256]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n\n    // Reserve and write\n    const entry = ring.reserve(5).?;\n    @memcpy(entry[HDR_SIZE..][0..5], \"hello\");\n    ring.commit(entry, 5);\n\n    // Read back\n    const data = ring.peek().?;\n    try std.testing.expectEqualStrings(\"hello\", data);\n    ring.advance();\n\n    // Now empty\n    try std.testing.expect(ring.peek() == null);\n    try std.testing.expect(ring.isEmpty());\n}\n\ntest \"ByteRing multiple entries\" {\n    var buf: [1024]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n\n    const messages = [_][]const u8{\n        \"one\", \"two\",   \"three\", \"four\", \"five\",\n        \"six\", \"seven\", \"eight\", \"nine\", \"ten\",\n    };\n\n    for (messages) |msg| {\n        const entry = ring.reserve(msg.len).?;\n        @memcpy(entry[HDR_SIZE..][0..msg.len], msg);\n        ring.commit(entry, msg.len);\n    }\n\n    for (messages) |msg| {\n        const data = ring.peek().?;\n        try std.testing.expectEqualStrings(msg, data);\n        ring.advance();\n    }\n\n    try std.testing.expect(ring.peek() == null);\n}\n\ntest \"ByteRing wraparound\" {\n    var buf: [128]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n\n    // Fill to ~90% (each entry = HDR + 20 = 24 bytes, ~5 fit)\n    for (0..5) |i| {\n        const entry = ring.reserve(20).?;\n        @memset(entry[HDR_SIZE..][0..20], @intCast(i + 1));\n        ring.commit(entry, 20);\n    }\n\n    // Drain all\n    for (0..5) |i| {\n        const data = ring.peek().?;\n        try std.testing.expectEqual(\n            @as(u8, @intCast(i + 1)),\n            data[0],\n        );\n        ring.advance();\n    }\n\n    // Fill again — wraps around\n    for (0..5) |i| {\n        const entry = ring.reserve(20).?;\n        @memset(entry[HDR_SIZE..][0..20], @intCast(i + 10));\n        ring.commit(entry, 20);\n    }\n\n    for (0..5) |i| {\n        const data = ring.peek().?;\n        try std.testing.expectEqual(\n            @as(u8, @intCast(i + 10)),\n            data[0],\n        );\n        ring.advance();\n    }\n\n    try std.testing.expect(ring.isEmpty());\n}\n\ntest \"ByteRing padding sentinel\" {\n    // 64-byte ring. Write entries until one forces a wrap.\n    var buf: [64]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n\n    // Entry of 20 bytes = 24 with header. Fits twice (48/64 used).\n    const e1 = ring.reserve(20).?;\n    @memset(e1[HDR_SIZE..][0..20], 'A');\n    ring.commit(e1, 20);\n\n    const e2 = ring.reserve(20).?;\n    @memset(e2[HDR_SIZE..][0..20], 'B');\n    ring.commit(e2, 20);\n\n    // Consume first to free space\n    const d1 = ring.peek().?;\n    try std.testing.expectEqual(@as(u8, 'A'), d1[0]);\n    ring.advance();\n\n    // Now 24 bytes free at start, 16 bytes free at end.\n    // An entry of 20 (24 total) won't fit in the 16 bytes at end.\n    // Should pad and wrap to start.\n    const e3 = ring.reserve(20).?;\n    @memset(e3[HDR_SIZE..][0..20], 'C');\n    ring.commit(e3, 20);\n\n    // Read B then C\n    const d2 = ring.peek().?;\n    try std.testing.expectEqual(@as(u8, 'B'), d2[0]);\n    ring.advance();\n\n    const d3 = ring.peek().?;\n    try std.testing.expectEqual(@as(u8, 'C'), d3[0]);\n    ring.advance();\n\n    try std.testing.expect(ring.isEmpty());\n}\n\ntest \"ByteRing full returns null\" {\n    var buf: [64]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n\n    // Fill: 2 entries of 20 = 48 bytes used\n    const e1 = ring.reserve(20).?;\n    ring.commit(e1, 20);\n    const e2 = ring.reserve(20).?;\n    ring.commit(e2, 20);\n\n    // Third should fail (48 + 24 = 72 > 64)\n    try std.testing.expect(ring.reserve(20) == null);\n\n    // Drain one, now should fit\n    _ = ring.peek().?;\n    ring.advance();\n    try std.testing.expect(ring.reserve(20) != null);\n}\n\ntest \"ByteRing empty returns null\" {\n    var buf: [64]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n    try std.testing.expect(ring.peek() == null);\n    try std.testing.expect(ring.isEmpty());\n}\n\ntest \"ByteRing concurrent stress\" {\n    const NUM_MSGS = 100_000;\n    const allocator = std.testing.allocator;\n\n    const ring_buf = try allocator.alloc(u8, 65536);\n    defer allocator.free(ring_buf);\n\n    var ring = ByteRing.init(ring_buf);\n\n    var received: usize = 0;\n    var corrupt: bool = false;\n\n    const consumer = try std.Thread.spawn(.{}, struct {\n        fn run(\n            r: *ByteRing,\n            recv: *usize,\n            bad: *bool,\n        ) void {\n            var count: usize = 0;\n            while (count < NUM_MSGS) {\n                if (r.peek()) |data| {\n                    // Verify pattern: first byte = count & 0xFF\n                    const expected: u8 = @truncate(count);\n                    if (data.len < 1 or data[0] != expected) {\n                        bad.* = true;\n                    }\n                    r.advance();\n                    count += 1;\n                } else {\n                    std.atomic.spinLoopHint();\n                }\n            }\n            recv.* = count;\n        }\n    }.run, .{ &ring, &received, &corrupt });\n\n    // Producer\n    for (0..NUM_MSGS) |i| {\n        while (true) {\n            if (ring.reserve(16)) |entry| {\n                const pattern: u8 = @truncate(i);\n                @memset(entry[HDR_SIZE..][0..16], pattern);\n                ring.commit(entry, 16);\n                break;\n            } else {\n                std.atomic.spinLoopHint();\n            }\n        }\n    }\n\n    consumer.join();\n\n    try std.testing.expectEqual(NUM_MSGS, received);\n    try std.testing.expect(!corrupt);\n}\n\ntest \"ByteRing max message size\" {\n    const allocator = std.testing.allocator;\n    // Ring of 4096, message of ~2000 bytes (< capacity/2)\n    const ring_buf = try allocator.alloc(u8, 4096);\n    defer allocator.free(ring_buf);\n\n    var ring = ByteRing.init(ring_buf);\n\n    const msg_len = 2000;\n    const entry = ring.reserve(msg_len).?;\n    for (0..msg_len) |j| {\n        entry[HDR_SIZE + j] = @truncate(j);\n    }\n    ring.commit(entry, msg_len);\n\n    const data = ring.peek().?;\n    try std.testing.expectEqual(msg_len, data.len);\n    for (0..msg_len) |j| {\n        const expected: u8 = @truncate(j);\n        try std.testing.expectEqual(expected, data[j]);\n    }\n    ring.advance();\n    try std.testing.expect(ring.isEmpty());\n}\n\ntest \"ByteRing alternating sizes\" {\n    const allocator = std.testing.allocator;\n    const ring_buf = try allocator.alloc(u8, 131072);\n    defer allocator.free(ring_buf);\n\n    var ring = ByteRing.init(ring_buf);\n\n    // Alternate tiny (10B) and large (1000B) entries\n    const sizes = [_]usize{ 10, 1000 };\n    const COUNT = 200;\n\n    for (0..COUNT) |i| {\n        const sz = sizes[i % 2];\n        const entry = ring.reserve(sz).?;\n        @memset(entry[HDR_SIZE..][0..sz], @truncate(i));\n        ring.commit(entry, sz);\n    }\n\n    for (0..COUNT) |i| {\n        const sz = sizes[i % 2];\n        const data = ring.peek().?;\n        try std.testing.expectEqual(sz, data.len);\n        try std.testing.expectEqual(@as(u8, @truncate(i)), data[0]);\n        ring.advance();\n    }\n\n    try std.testing.expect(ring.isEmpty());\n}\n\ntest \"ByteRing drainToWriter\" {\n    var buf: [256]u8 = undefined;\n    var ring = ByteRing.init(&buf);\n\n    const e1 = ring.reserve(3).?;\n    @memcpy(e1[HDR_SIZE..][0..3], \"abc\");\n    ring.commit(e1, 3);\n\n    const e2 = ring.reserve(3).?;\n    @memcpy(e2[HDR_SIZE..][0..3], \"def\");\n    ring.commit(e2, 3);\n\n    var out_buf: [64]u8 = undefined;\n    var writer = std.Io.Writer.fixed(&out_buf);\n\n    const count = try ring.drainToWriter(&writer);\n    try std.testing.expectEqual(@as(usize, 2), count);\n    try std.testing.expectEqualStrings(\n        \"abcdef\",\n        out_buf[0..writer.end],\n    );\n    try std.testing.expect(ring.isEmpty());\n}\n"
  },
  {
    "path": "src/sync/spin_lock.zig",
    "content": "//! Lightweight atomic spinlock for short critical sections.\n//!\n//! Used where Io.Mutex is unavailable (e.g., Message.deinit()\n//! which has no io parameter). Lock hold time must be very\n//! short (nanoseconds) — only for single slot writes.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// Atomic spinlock using compare-and-swap.\npub const SpinLock = struct {\n    locked: std.atomic.Value(u8) =\n        std.atomic.Value(u8).init(0),\n\n    /// Acquire the lock. Spins until successful.\n    pub fn lock(self: *SpinLock) void {\n        while (self.locked.cmpxchgWeak(\n            0,\n            1,\n            .acquire,\n            .monotonic,\n        ) != null) {\n            std.atomic.spinLoopHint();\n        }\n    }\n\n    /// Release the lock.\n    pub fn unlock(self: *SpinLock) void {\n        assert(self.locked.load(.monotonic) == 1);\n        self.locked.store(0, .release);\n    }\n};\n\ntest \"SpinLock basic\" {\n    var sl: SpinLock = .{};\n    sl.lock();\n    sl.unlock();\n}\n\ntest \"SpinLock concurrent\" {\n    const NUM_THREADS = 4;\n    const ITERS = 100_000;\n\n    var sl: SpinLock = .{};\n    var counter: usize = 0;\n\n    const threads = blk: {\n        var t: [NUM_THREADS]std.Thread = undefined;\n        for (&t) |*thread| {\n            thread.* = try std.Thread.spawn(.{}, struct {\n                fn run(\n                    s: *SpinLock,\n                    c: *usize,\n                ) void {\n                    for (0..ITERS) |_| {\n                        s.lock();\n                        c.* += 1;\n                        s.unlock();\n                    }\n                }\n            }.run, .{ &sl, &counter });\n        }\n        break :blk t;\n    };\n\n    for (&threads) |*t| t.join();\n\n    try std.testing.expectEqual(\n        NUM_THREADS * ITERS,\n        counter,\n    );\n}\n"
  },
  {
    "path": "src/sync/spsc_queue.zig",
    "content": "//! Lock-free Single Producer Single Consumer Queue\n//!\n//! Zero syscalls, zero mutex, maximum throughput.\n//! Designed for cross-thread message passing between io_task and subscriber.\n//!\n//! ## Memory Ordering Rationale\n//!\n//! This SPSC queue uses a carefully chosen memory ordering strategy that is\n//! both correct on weakly-ordered architectures (ARM) and optimal for\n//! strongly-ordered architectures (x86_64).\n//!\n//! **Key insight**: Each index (head/tail) has exactly ONE writer thread.\n//! - `head`: written only by producer, read by both\n//! - `tail`: written only by consumer, read by both\n//!\n//! **Ordering rules applied**:\n//! - Reading your OWN index: `.monotonic` (you're the only writer, no sync needed)\n//! - Reading OTHER's index: `.acquire` (must see their prior writes to buffer)\n//! - Writing your index: `.release` (your buffer writes must be visible first)\n//!\n//! **The release-acquire pairing**:\n//! ```\n//! Producer:                          Consumer:\n//!   buffer[head] = item;               head = head.load(.acquire);  // sees data\n//!   head.store(new, .release);  ---->  item = buffer[tail];\n//!                                      tail.store(new, .release);\n//!                               <----  tail.load(.acquire);  // sees consumption\n//! ```\n//!\n//! This ensures: when consumer sees updated head, the data write is visible.\n//! When producer sees updated tail, the slot is safe to reuse.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\n\n/// Lock-free SPSC queue with runtime-sized buffer.\n/// Producer (io_task) and consumer (subscriber) can run on different threads.\n/// See module doc comment for memory ordering rationale.\npub fn SpscQueue(comptime T: type) type {\n    return struct {\n        buffer: []T,\n        capacity: usize,\n        head: std.atomic.Value(usize),\n        tail: std.atomic.Value(usize),\n        closed: std.atomic.Value(bool),\n\n        const Self = @This();\n\n        /// Initialize queue with pre-allocated buffer.\n        /// Buffer length MUST be a power of 2 for bitwise AND optimization.\n        pub fn init(buffer: []T) Self {\n            assert(buffer.len > 0);\n            assert(std.math.isPowerOfTwo(buffer.len));\n            return .{\n                .buffer = buffer,\n                .capacity = buffer.len,\n                .head = std.atomic.Value(usize).init(0),\n                .tail = std.atomic.Value(usize).init(0),\n                .closed = std.atomic.Value(bool).init(false),\n            };\n        }\n\n        /// Push item (producer only). Returns false if full.\n        /// O(1), lock-free, never blocks.\n        pub fn push(self: *Self, item: T) bool {\n            if (self.closed.load(.acquire)) return false;\n            // .monotonic: single head writer, no sync needed for own read\n            const head = self.head.load(.monotonic);\n            // .acquire: must see consumer's tail updates to know slots are free\n            const tail = self.tail.load(.acquire);\n\n            if (head -% tail >= self.capacity) return false;\n\n            const mask = self.capacity - 1;\n            self.buffer[head & mask] = item;\n            // .release: ensures item write is visible BEFORE head increment\n            // Consumer's .acquire on head will see this data\n            self.head.store(head +% 1, .release);\n            return true;\n        }\n\n        /// Pop item (consumer only). Returns null if empty.\n        /// O(1), lock-free, never blocks.\n        pub fn pop(self: *Self) ?T {\n            // .monotonic: single tail writer, no sync needed for own read\n            const tail = self.tail.load(.monotonic);\n            // .acquire: must see producer's buffer writes that happened before\n            // their .release store to head\n            const head = self.head.load(.acquire);\n\n            if (tail == head) return null;\n\n            const mask = self.capacity - 1;\n            const item = self.buffer[tail & mask];\n            // .release: ensures item read completes BEFORE tail increment\n            // Producer's .acquire on tail will see slot is now free\n            self.tail.store(tail +% 1, .release);\n            return item;\n        }\n\n        /// Pop multiple items into output buffer. Returns count popped.\n        /// O(n), lock-free, never blocks.\n        /// Same memory ordering rationale as pop() - see module doc.\n        pub fn popBatch(self: *Self, out: []T) usize {\n            // .monotonic: single tail writer\n            const tail = self.tail.load(.monotonic);\n            // .acquire: must see producer's buffer writes\n            const head = self.head.load(.acquire);\n\n            const available = head -% tail;\n            if (available == 0) return 0;\n\n            const mask = self.capacity - 1;\n            const count = @min(available, out.len);\n            for (0..count) |i| {\n                out[i] = self.buffer[(tail +% i) & mask];\n            }\n            // .release: ensures all reads complete before tail update\n            self.tail.store(tail +% count, .release);\n            return count;\n        }\n\n        /// Number of items in queue (approximate, may be stale).\n        pub fn len(self: *const Self) usize {\n            const head = self.head.load(.acquire);\n            const tail = self.tail.load(.acquire);\n            return head -% tail;\n        }\n\n        /// True if queue appears empty (may be stale).\n        pub fn isEmpty(self: *const Self) bool {\n            return self.len() == 0;\n        }\n\n        /// Returns true once the queue has been closed.\n        pub fn isClosed(self: *const Self) bool {\n            return self.closed.load(.acquire);\n        }\n\n        /// Close queue and wake receivers polling for shutdown.\n        pub fn close(self: *Self, io: anytype) void {\n            _ = io;\n            self.closed.store(true, .release);\n        }\n    };\n}\n\ntest \"SpscQueue push/pop\" {\n    var buffer: [4]u32 = undefined;\n    var q = SpscQueue(u32).init(&buffer);\n\n    try std.testing.expect(q.isEmpty());\n    try std.testing.expectEqual(@as(usize, 0), q.len());\n\n    try std.testing.expect(q.push(1));\n    try std.testing.expect(q.push(2));\n    try std.testing.expect(q.push(3));\n    try std.testing.expect(q.push(4));\n    try std.testing.expect(!q.push(5));\n\n    try std.testing.expectEqual(@as(usize, 4), q.len());\n\n    try std.testing.expectEqual(@as(?u32, 1), q.pop());\n    try std.testing.expectEqual(@as(?u32, 2), q.pop());\n    try std.testing.expectEqual(@as(?u32, 3), q.pop());\n    try std.testing.expectEqual(@as(?u32, 4), q.pop());\n    try std.testing.expectEqual(@as(?u32, null), q.pop());\n\n    try std.testing.expect(q.isEmpty());\n}\n\ntest \"SpscQueue popBatch\" {\n    var buffer: [8]u32 = undefined;\n    var q = SpscQueue(u32).init(&buffer);\n\n    _ = q.push(1);\n    _ = q.push(2);\n    _ = q.push(3);\n    _ = q.push(4);\n    _ = q.push(5);\n\n    var out: [3]u32 = undefined;\n    const count = q.popBatch(&out);\n    try std.testing.expectEqual(@as(usize, 3), count);\n    try std.testing.expectEqual(@as(u32, 1), out[0]);\n    try std.testing.expectEqual(@as(u32, 2), out[1]);\n    try std.testing.expectEqual(@as(u32, 3), out[2]);\n\n    try std.testing.expectEqual(@as(usize, 2), q.len());\n}\n\ntest \"SpscQueue wraparound\" {\n    var buffer: [4]u32 = undefined;\n    var q = SpscQueue(u32).init(&buffer);\n\n    for (0..10) |cycle| {\n        const base: u32 = @intCast(cycle * 4);\n        try std.testing.expect(q.push(base + 1));\n        try std.testing.expect(q.push(base + 2));\n        try std.testing.expect(q.push(base + 3));\n        try std.testing.expect(q.push(base + 4));\n        try std.testing.expect(!q.push(base + 5));\n\n        try std.testing.expectEqual(@as(?u32, base + 1), q.pop());\n        try std.testing.expectEqual(@as(?u32, base + 2), q.pop());\n        try std.testing.expectEqual(@as(?u32, base + 3), q.pop());\n        try std.testing.expectEqual(@as(?u32, base + 4), q.pop());\n        try std.testing.expectEqual(@as(?u32, null), q.pop());\n    }\n}\n"
  },
  {
    "path": "src/testing/README.md",
    "content": "# Integration Tests\n\nThis directory contains the integration test harness and fixtures for the\nNATS Zig client. These tests exercise the client against real `nats-server`\nprocesses instead of mocks.\n\n## Layout\n\n- `integration_test.zig` runs the main end-to-end integration suite.\n- `micro_integration_test.zig` runs the micro service integration suite.\n- `tls_integration_test.zig` runs the focused JWT/TLS integration suite.\n- `client/` contains the grouped test cases used by the runners.\n- `configs/` contains NATS server configurations used by the harness.\n- `certs/` contains TLS certificates used only by the test servers.\n- `server_manager.zig` and `test_utils.zig` provide shared test\n  infrastructure.\n\n## Commands\n\nRun from the repository root:\n\n```sh\nzig build test-integration\nzig build test-integration-micro\nzig build test-integration-tls\n```\n\nThe tests require `nats-server` to be available on `PATH`. The main\nintegration suite also uses the `nats` CLI for JetStream/KV\ncross-verification tests.\n\n## Scope\n\nThis directory is for correctness and interoperability tests. Performance\nbenchmarking is intentionally kept out of the main client repository; use the\nseparate benchmark repository for cross-client performance comparisons.\n"
  },
  {
    "path": "src/testing/certs/client-all.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCznnzpbfhgAUBe\nNtyUVTdHJZ93vWx7WeuO3q8zMePpxFhmCGmdtaO/rkdunQk5WGZpnO6wB3Ricn/O\nk9CZs+6RU3X4b17mUjljO+k/CHjXHbiZsh3KzE9bHVlcMIBru5dv6gKhbp5NsYaC\n5iOjpUtiFY2fkYdMbgtWlCEkvQJboQUwtKuNl5rbYX6blIYGjwq8w8ZqyGsiTzqs\nTw5mNVh4q0rW963LGjGJzsUBfw6YQ2bKEzysatDJMRRk2jJHrCQOzWdrxEh+wxle\nY4HTg2pdK/E0THhoIpFza4lAC05Ql5SQlzzLyWa9CJwUa+eDMUqJVbpMt2kyq0Bd\n9txX54YxAgMBAAECggEAQ9rdqXmH2Qzf+jeTgN3ochI+egevUbIYkPKDET4Jsagh\nFPqcm52g7Kq0BY+Bio5gsgk9CnbmesJykeG5bjdRKslyyZWZLj1lvJ1Hci6LKAjs\nUfO92XzxhaRCu9b+zLQjc33d3Ipjd0pXXGAAmrO5FKa7x8o8aJ0x31U6aByXJXG+\nCtMJXuCdk4a7FnsiBIQxhC2Ihb+1whBH1mdv14+FqWPo9Vb+ioSIEAVO4thbD4jy\nsKmDSnjrEDtjzKAEIrvZf9Hq56HJdu0RrpREIv9lmUOKze8GkAzIkDj55R2G10db\nvRsWsjB/iqI5an3+LDfUtNadR0h21udCRaZOJX+/GQKBgQDAy34fMvIQhRl63f9A\nGOT/bQ5HjOrXpmYFZw6062pGgfywj4ln5BYH+3s3orPeiExHjF5Lw4SG6u44nNmA\n6RhPRsLfIfhg7OHfa8VYhQP+hexz0JlMwGMmid30aySnyIrXn/WsDpICCbPZR4Lz\nOUIr23p/20WqbmPYdf2cW+PvfwKBgQDugTPwxyfcubVddZfVFIpxzGuZGLgL8s0K\n7lxZE5B4d0FTMDUX9MHq7j9hMiHhSuyn0SeHyuP03FqZWzoSjv1r+keCwCmoPWAC\nocJC9SoGz7AdBnRmqlbqEHpY+3RvLtVYkwDcmfxVYq5I8gQu5O6X4PbQf+cFb/xI\nhmC8Vh5iTwKBgQC6pYbxf2nX0nOLftY5YKB6JEM5w9QreH22Z0JWpr6ZighvilaV\nTLyDd9SfVRXbr4phjiRQJvXrhA+ioT70zTVqsm/Ag2upskst+HDytLvcMh1rNhzj\nsDGNQtWtZfjzsnOwMr0tmGGENY53IQNGoz1Lpkze8RJt4DcrfXdMY6200wKBgHv5\naRhVTWEsnxuvjnbSMIyqp5ty/+gmE3MFJ7edtdEInEozmsWTEmGd6hAJ0RacrZsl\n2xh43DlheS6R/wO6k/xWomlSndS34no7vxCzA197AZ50xni/PmJ4okAypPlOLNPX\nxfDlkgaIPvPn6Ui+8067P1Bty5ZF+atxPkNnuG99AoGAO++bI6twiF2yFrqp8bJ5\n6zjkwVYVDds/acnierTnAVF3yM+eqyq1BYjSCTSupqnd7KT0EMX77BBbGHAQuUZM\nfO3L7vgo10S18VWNiAt5yyZUKhN0lLwQeQeP0mIDcRKiLn7BwnNz8Wxo4p0KoKLS\nQflLqbILbuspjBXQvQ1Asb0=\n-----END PRIVATE KEY-----\n\n\n-----BEGIN CERTIFICATE-----\nMIIEnjCCAwagAwIBAgIQciuy77HHsdMG7UD/WPT1gzANBgkqhkiG9w0BAQsFADCB\ngzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSwwKgYDVQQLDCNzdGpl\ncGFuQGxvY2FsaG9zdCAoU3RqZXBhbiBHbGF2aW5hKTEzMDEGA1UEAwwqbWtjZXJ0\nIHN0amVwYW5AbG9jYWxob3N0IChTdGplcGFuIEdsYXZpbmEpMB4XDTE5MDYwMTAw\nMDAwMFoXDTMwMTEwNTEyMDk1N1owYTEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3Bt\nZW50IGNlcnRpZmljYXRlMTYwNAYDVQQLDC1zdGplcGFuQE1hY0Jvb2stUHJvLTIu\nbG9jYWwgKFN0amVwYW4gR2xhdmluYSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQCznnzpbfhgAUBeNtyUVTdHJZ93vWx7WeuO3q8zMePpxFhmCGmdtaO/\nrkdunQk5WGZpnO6wB3Ricn/Ok9CZs+6RU3X4b17mUjljO+k/CHjXHbiZsh3KzE9b\nHVlcMIBru5dv6gKhbp5NsYaC5iOjpUtiFY2fkYdMbgtWlCEkvQJboQUwtKuNl5rb\nYX6blIYGjwq8w8ZqyGsiTzqsTw5mNVh4q0rW963LGjGJzsUBfw6YQ2bKEzysatDJ\nMRRk2jJHrCQOzWdrxEh+wxleY4HTg2pdK/E0THhoIpFza4lAC05Ql5SQlzzLyWa9\nCJwUa+eDMUqJVbpMt2kyq0Bd9txX54YxAgMBAAGjga4wgaswDgYDVR0PAQH/BAQD\nAgWgMDEGA1UdJQQqMCgGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsG\nAQUFBwMEMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaDvaviAqw6RnyuBf3BI7\nS2uUmfIwNwYDVR0RBDAwLoIJbG9jYWxob3N0gQ9lbWFpbEBsb2NhbGhvc3SHEAAA\nAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAFc5p6LYY6zIBvJbVNXL\npofEM/ky999eHmLacKAAs08bBEpRLvho7Maf/YsRfFEB9tzE/Nhrc/9yC2hM1Jsy\n9I3jM5OPErEHAqzCpi5L0ykuKcmvLCAVLrBiEmBcAvY4snirIGZ0YMW5fqcrcy4B\n84Ptg9efUnV/XC3VQTLRX0pWp8t1T98P1ZFYK7dK1ejkC61/kO5WjTlIOkT+oVXf\nA/juQubq2QKkROTUze5pmA0jBTgFtszXfryuh++NsBb9vHWHV3JFgW2k5brw0ozs\n0F3snFOafLrw3xJcI8j2gYi+4sI6mmZTRWKhWF6TbWPl4Ysk+K/BkcEcDebYFCJz\nBNmqVjzKLv3qEDV+XaTYa96qlEJRTI96rofL3zzXC381XYhNRLFS5ExHchuETUKW\nS70tKNsyzpd3V0X4RTgopFuXzSfvaQycu3OwM74Ks0xmTgZL396ZlbtJfuT8v/2d\nc+t8Ei6H4a0r/nyip1df3keRL/otvkP4sby7D4xi0CJ2dw==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIRAIW/i8Ryvk+oZGg+/FvDpW8wDQYJKoZIhvcNAQELBQAw\ngYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjc3Rq\nZXBhbkBsb2NhbGhvc3QgKFN0amVwYW4gR2xhdmluYSkxMzAxBgNVBAMMKm1rY2Vy\ndCBzdGplcGFuQGxvY2FsaG9zdCAoU3RqZXBhbiBHbGF2aW5hKTAeFw0yMDA3MDcx\nNTU4NTlaFw0zMDA3MDcxNTU4NTlaMIGDMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxv\ncG1lbnQgQ0ExLDAqBgNVBAsMI3N0amVwYW5AbG9jYWxob3N0IChTdGplcGFuIEds\nYXZpbmEpMTMwMQYDVQQDDCpta2NlcnQgc3RqZXBhbkBsb2NhbGhvc3QgKFN0amVw\nYW4gR2xhdmluYSkwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCyRbze\nlNj5QkxgMxYIr4yEqLtTz/jSiX1W6uUymI+e1GQzl3BpP3fS/SyB9HZv1bn4PoiW\nB+BggsFrOsmVF4aXbikRz74DQ7FCqVMU/QGvlIbHkH5TShcLGzngG8DR3+Url3S8\nrlvYQBf2AXYXWcmSl1bFYzVxWSt67NzS29mvus40aAPXTpB7vq6nNs6F+7sARhDi\nOPRqniPqV29khMmxndwExAEMJBVZbeTNuwfehCud8dOj0kzk5ESX+3upIwOrnoye\n2tRNW34WWaxQrV65KOeGxdgIN+PeO7WL1jbCitCaGnGitTbtPGMCdf6LmRhA30Wy\nIZ3ZkxOmvXGkpAR6mxz3pqQWTZYieTA92s63LeVSNeYUof0tNu0SMTYWuAas0Ob3\nA/lu7PTCjTag5vVU5RwkfBmcNrbNNy9NbKgQB7TafZn7sfPpZT4EpAcFMgRb4KfR\nHfPiaxlDu2LKmBS9+i7x79nYAlB5IGgLyQ1cldwjDYqAAizBoigM2PI3tEMCAwEA\nAaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0O\nBBYEFGg72r4gKsOkZ8rgX9wSO0trlJnyMA0GCSqGSIb3DQEBCwUAA4IBgQBzxbH6\ns7dhvyOGK5HnQmc/vPiTbOk4QhXX12PPekL+UrhMyiMzWET2wkpHJXxtes0dGNU3\ntmQwVQTvg/BhyxxGcafvQe/o/5rS9lrrpdQriZg3gqqeUoi8UX+5/ef9EB7Z32Ho\nqUSOd6hPLLI+3UrnmWP+u5PRDmsHQYKc2YqyqQisjRVwLtGtCmGYfuBncP145Yyi\nqNlwI6jeZTAtRSkcKy6fnyJcjOCYKFWHpTGmBTMtO4LiTGadxnmbAq9mRBiKJJp6\nwrSz1JvbVXVY4caxpbDfkaT8RiP+k1Fbd6uMWnZTJLHPTNbzCl4aXcuHgoRhCLeq\nSdF3L7m0tM7lsTP3tddRY6zb+1u0II0Gu6umDsdyL6JOV4vv9Qb7xdy2jTU231+o\nTXLHaypw4Amp267EyvvWmU3VOl8BeUkJ/7LOqzZfKxTECwnxWywx6NV9ONQt8mNC\nATAQAyYXklJsZkX6VLMPE0Lv4Qbt/GnGUejER09zQi433e9jUF+vwQGwj/g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/testing/certs/client-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEnjCCAwagAwIBAgIQciuy77HHsdMG7UD/WPT1gzANBgkqhkiG9w0BAQsFADCB\ngzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSwwKgYDVQQLDCNzdGpl\ncGFuQGxvY2FsaG9zdCAoU3RqZXBhbiBHbGF2aW5hKTEzMDEGA1UEAwwqbWtjZXJ0\nIHN0amVwYW5AbG9jYWxob3N0IChTdGplcGFuIEdsYXZpbmEpMB4XDTE5MDYwMTAw\nMDAwMFoXDTMwMTEwNTEyMDk1N1owYTEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3Bt\nZW50IGNlcnRpZmljYXRlMTYwNAYDVQQLDC1zdGplcGFuQE1hY0Jvb2stUHJvLTIu\nbG9jYWwgKFN0amVwYW4gR2xhdmluYSkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQCznnzpbfhgAUBeNtyUVTdHJZ93vWx7WeuO3q8zMePpxFhmCGmdtaO/\nrkdunQk5WGZpnO6wB3Ricn/Ok9CZs+6RU3X4b17mUjljO+k/CHjXHbiZsh3KzE9b\nHVlcMIBru5dv6gKhbp5NsYaC5iOjpUtiFY2fkYdMbgtWlCEkvQJboQUwtKuNl5rb\nYX6blIYGjwq8w8ZqyGsiTzqsTw5mNVh4q0rW963LGjGJzsUBfw6YQ2bKEzysatDJ\nMRRk2jJHrCQOzWdrxEh+wxleY4HTg2pdK/E0THhoIpFza4lAC05Ql5SQlzzLyWa9\nCJwUa+eDMUqJVbpMt2kyq0Bd9txX54YxAgMBAAGjga4wgaswDgYDVR0PAQH/BAQD\nAgWgMDEGA1UdJQQqMCgGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsG\nAQUFBwMEMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaDvaviAqw6RnyuBf3BI7\nS2uUmfIwNwYDVR0RBDAwLoIJbG9jYWxob3N0gQ9lbWFpbEBsb2NhbGhvc3SHEAAA\nAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAFc5p6LYY6zIBvJbVNXL\npofEM/ky999eHmLacKAAs08bBEpRLvho7Maf/YsRfFEB9tzE/Nhrc/9yC2hM1Jsy\n9I3jM5OPErEHAqzCpi5L0ykuKcmvLCAVLrBiEmBcAvY4snirIGZ0YMW5fqcrcy4B\n84Ptg9efUnV/XC3VQTLRX0pWp8t1T98P1ZFYK7dK1ejkC61/kO5WjTlIOkT+oVXf\nA/juQubq2QKkROTUze5pmA0jBTgFtszXfryuh++NsBb9vHWHV3JFgW2k5brw0ozs\n0F3snFOafLrw3xJcI8j2gYi+4sI6mmZTRWKhWF6TbWPl4Ysk+K/BkcEcDebYFCJz\nBNmqVjzKLv3qEDV+XaTYa96qlEJRTI96rofL3zzXC381XYhNRLFS5ExHchuETUKW\nS70tKNsyzpd3V0X4RTgopFuXzSfvaQycu3OwM74Ks0xmTgZL396ZlbtJfuT8v/2d\nc+t8Ei6H4a0r/nyip1df3keRL/otvkP4sby7D4xi0CJ2dw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/testing/certs/client-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCznnzpbfhgAUBe\nNtyUVTdHJZ93vWx7WeuO3q8zMePpxFhmCGmdtaO/rkdunQk5WGZpnO6wB3Ricn/O\nk9CZs+6RU3X4b17mUjljO+k/CHjXHbiZsh3KzE9bHVlcMIBru5dv6gKhbp5NsYaC\n5iOjpUtiFY2fkYdMbgtWlCEkvQJboQUwtKuNl5rbYX6blIYGjwq8w8ZqyGsiTzqs\nTw5mNVh4q0rW963LGjGJzsUBfw6YQ2bKEzysatDJMRRk2jJHrCQOzWdrxEh+wxle\nY4HTg2pdK/E0THhoIpFza4lAC05Ql5SQlzzLyWa9CJwUa+eDMUqJVbpMt2kyq0Bd\n9txX54YxAgMBAAECggEAQ9rdqXmH2Qzf+jeTgN3ochI+egevUbIYkPKDET4Jsagh\nFPqcm52g7Kq0BY+Bio5gsgk9CnbmesJykeG5bjdRKslyyZWZLj1lvJ1Hci6LKAjs\nUfO92XzxhaRCu9b+zLQjc33d3Ipjd0pXXGAAmrO5FKa7x8o8aJ0x31U6aByXJXG+\nCtMJXuCdk4a7FnsiBIQxhC2Ihb+1whBH1mdv14+FqWPo9Vb+ioSIEAVO4thbD4jy\nsKmDSnjrEDtjzKAEIrvZf9Hq56HJdu0RrpREIv9lmUOKze8GkAzIkDj55R2G10db\nvRsWsjB/iqI5an3+LDfUtNadR0h21udCRaZOJX+/GQKBgQDAy34fMvIQhRl63f9A\nGOT/bQ5HjOrXpmYFZw6062pGgfywj4ln5BYH+3s3orPeiExHjF5Lw4SG6u44nNmA\n6RhPRsLfIfhg7OHfa8VYhQP+hexz0JlMwGMmid30aySnyIrXn/WsDpICCbPZR4Lz\nOUIr23p/20WqbmPYdf2cW+PvfwKBgQDugTPwxyfcubVddZfVFIpxzGuZGLgL8s0K\n7lxZE5B4d0FTMDUX9MHq7j9hMiHhSuyn0SeHyuP03FqZWzoSjv1r+keCwCmoPWAC\nocJC9SoGz7AdBnRmqlbqEHpY+3RvLtVYkwDcmfxVYq5I8gQu5O6X4PbQf+cFb/xI\nhmC8Vh5iTwKBgQC6pYbxf2nX0nOLftY5YKB6JEM5w9QreH22Z0JWpr6ZighvilaV\nTLyDd9SfVRXbr4phjiRQJvXrhA+ioT70zTVqsm/Ag2upskst+HDytLvcMh1rNhzj\nsDGNQtWtZfjzsnOwMr0tmGGENY53IQNGoz1Lpkze8RJt4DcrfXdMY6200wKBgHv5\naRhVTWEsnxuvjnbSMIyqp5ty/+gmE3MFJ7edtdEInEozmsWTEmGd6hAJ0RacrZsl\n2xh43DlheS6R/wO6k/xWomlSndS34no7vxCzA197AZ50xni/PmJ4okAypPlOLNPX\nxfDlkgaIPvPn6Ui+8067P1Bty5ZF+atxPkNnuG99AoGAO++bI6twiF2yFrqp8bJ5\n6zjkwVYVDds/acnierTnAVF3yM+eqyq1BYjSCTSupqnd7KT0EMX77BBbGHAQuUZM\nfO3L7vgo10S18VWNiAt5yyZUKhN0lLwQeQeP0mIDcRKiLn7BwnNz8Wxo4p0KoKLS\nQflLqbILbuspjBXQvQ1Asb0=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/testing/certs/ip-ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL\nBQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM\nB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl\nIEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw\nMjMwMlowcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV\nBAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmlj\nYXRlIEF1dGhvcml0eSAyMDIyLTA4LTI3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAqilVqyY8rmCpTwAsLF7DEtWEq37KbljBWVjmlp2Wo6TgMd3b537t\n6iO8+SbI8KH75i63RcxV3Uzt1/L9Yb6enDXF52A/U5ugmDhaa+Vsoo2HBTbCczmp\nqndp7znllQqn7wNLv6aGSvaeIUeYS5Dmlh3kt7Vqbn4YRANkOUTDYGSpMv7jYKSu\n1ee05Rco3H674zdwToYto8L8V7nVMrky42qZnGrJTaze+Cm9tmaIyHCwUq362CxS\ndkmaEuWx11MOIFZvL80n7ci6pveDxe5MIfwMC3/oGn7mbsSqidPMcTtjw6ey5NEu\nZ0UrC/2lL1FtF4gnVMKUSaEhU2oKjj0ZAQIDAQABo4IBHjCCARowHQYDVR0OBBYE\nFP7Pfz4u7sSt6ltviEVsx4hIFIs6MIGuBgNVHSMEgaYwgaOAFP7Pfz4u7sSt6ltv\niEVsx4hIFIs6oXWkczBxMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p\nYTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEpMCcGA1UEAwwg\nQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjItMDgtMjeCFEmcFu27XPRFG6zFrYx6\nWzNAtm0hMAwGA1UdEwQFMAMBAf8wOgYJYIZIAYb4QgENBC0WK25hdHMuaW8gbmF0\ncy1zZXJ2ZXIgdGVzdC1zdWl0ZSB0cmFuc2llbnQgQ0EwDQYJKoZIhvcNAQELBQAD\nggEBAHDCHLQklYZlnzHDaSwxgGSiPUrCf2zhk2DNIYSDyBgdzrIapmaVYQRrCBtA\nj/4jVFesgw5WDoe4TKsyha0QeVwJDIN8qg2pvpbmD8nOtLApfl0P966vcucxDwqO\ndQWrIgNsaUdHdwdo0OfvAlTfG0v/y2X0kbL7h/el5W9kWpxM/rfbX4IHseZL2sLq\nFH69SN3FhMbdIm1ldrcLBQVz8vJAGI+6B9hSSFQWljssE0JfAX+8VW/foJgMSx7A\nvBTq58rLkAko56Jlzqh/4QT+ckayg9I73v1Q5/44jP1mHw35s5ZrzpDQt2sVv4l5\nlwRPJFXMwe64flUs9sM+/vqJaIY=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/testing/certs/ip-cert.pem",
    "content": "Certificate:\n    Data:\n        Version: 3 (0x2)\n        Serial Number:\n            1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:01:4b:31:e6:d2:49:31:1f\n        Signature Algorithm: sha256WithRSAEncryption\n        Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27\n        Validity\n            Not Before: Aug 27 20:23:02 2022 GMT\n            Not After : Aug 24 20:23:02 2032 GMT\n        Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost\n        Subject Public Key Info:\n            Public Key Algorithm: rsaEncryption\n                RSA Public-Key: (2048 bit)\n                Modulus:\n                    00:e6:fb:47:65:cd:c9:a2:2d:af:8b:cd:d5:6a:79:\n                    54:3c:07:5f:eb:5a:71:2b:2b:e5:6f:be:31:fb:16:\n                    65:68:76:0e:59:e7:e4:57:ca:88:e9:77:d6:41:ad:\n                    57:7a:42:b2:d2:54:c4:0f:7c:5b:c1:bc:61:97:e3:\n                    22:3a:3e:1e:4a:5d:47:9f:6b:7d:6f:34:e3:8c:86:\n                    9d:85:19:29:9a:11:58:44:4c:a1:90:d3:14:61:e1:\n                    57:da:01:ea:ce:3f:90:ae:9e:5d:13:6d:2c:89:ca:\n                    39:15:6b:b6:9e:32:d7:2a:4c:48:85:2f:b0:1e:d8:\n                    4b:62:32:14:eb:32:b6:29:04:34:3c:af:39:b6:8b:\n                    52:32:4d:bf:43:5f:9b:fb:0d:43:a6:ad:2c:a7:41:\n                    29:55:c9:70:b3:b5:15:46:34:bf:e4:1e:52:2d:a4:\n                    49:2e:d5:21:ed:fc:00:f7:a2:0b:bc:12:0a:90:64:\n                    50:7c:c5:14:70:f5:fb:9b:62:08:78:43:49:31:f3:\n                    47:b8:93:d4:2d:4c:a9:dc:17:70:76:34:66:ff:65:\n                    c1:39:67:e9:a6:1c:80:6a:f0:9d:b3:28:c8:a3:3a:\n                    b7:5d:de:6e:53:6d:09:b3:0d:b1:13:10:e8:ec:e0:\n                    bd:5e:a1:94:4b:70:bf:dc:bd:8b:b9:82:65:dd:af:\n                    81:7b\n                Exponent: 65537 (0x10001)\n        X509v3 extensions:\n            X509v3 Basic Constraints: \n                CA:FALSE\n            Netscape Comment: \n                nats.io nats-server test-suite certificate\n            X509v3 Subject Key Identifier: \n                2B:8C:A3:8B:DB:DB:5C:CE:18:DB:F6:A8:31:4E:C2:3E:EE:D3:40:7E\n            X509v3 Authority Key Identifier: \n                keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A\n                DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27\n                serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21\n\n            X509v3 Subject Alternative Name: \n                DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1\n            Netscape Cert Type: \n                SSL Client, SSL Server\n            X509v3 Key Usage: \n                Digital Signature, Key Encipherment\n            X509v3 Extended Key Usage: \n                TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication\n    Signature Algorithm: sha256WithRSAEncryption\n         54:49:34:2b:38:d1:aa:3b:43:60:4c:3f:6a:f8:74:ca:49:53:\n         a1:af:12:d3:a8:17:90:7b:9d:a3:69:13:6e:da:2c:b7:61:31:\n         ac:eb:00:93:92:fc:0c:10:d4:18:a0:16:61:94:4b:42:cb:eb:\n         7a:f6:80:c6:45:c0:9c:09:aa:a9:48:e8:36:e3:c5:be:36:e0:\n         e9:78:2a:bb:ab:64:9b:20:eb:e6:0f:63:2b:59:c3:58:0b:3a:\n         84:15:04:c1:7e:12:03:1b:09:25:8d:4c:03:e8:18:26:c0:6c:\n         b7:90:b1:fd:bc:f1:cf:d0:d5:4a:03:15:71:0c:7d:c1:76:87:\n         92:f1:3e:bc:75:51:5a:c4:36:a4:ff:91:98:df:33:5d:a7:38:\n         de:50:29:fd:0f:c8:55:e6:8f:24:c2:2e:98:ab:d9:5d:65:2f:\n         50:cc:25:f6:84:f2:21:2e:5e:76:d0:86:1e:69:8b:cb:8a:3a:\n         2d:79:21:5e:e7:f7:2d:06:18:a1:13:cb:01:c3:46:91:2a:de:\n         b4:82:d7:c3:62:6f:08:a1:d5:90:19:30:9d:64:8e:e4:f8:ba:\n         4f:2f:ba:13:b4:a3:9f:d1:d5:77:64:8a:3e:eb:53:c5:47:ac:\n         ab:3e:0e:7a:9b:a6:f4:48:25:66:eb:c7:4c:f9:50:24:eb:71:\n         e0:75:ae:e6\n-----BEGIN CERTIFICATE-----\nMIIE+TCCA+GgAwIBAgIUHdkfBt39kCZOJ+ouAUsx5tJJMR8wDQYJKoZIhvcNAQEL\nBQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM\nB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl\nIEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw\nMjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV\nBAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z\ndDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOb7R2XNyaItr4vN1Wp5\nVDwHX+tacSsr5W++MfsWZWh2Dlnn5FfKiOl31kGtV3pCstJUxA98W8G8YZfjIjo+\nHkpdR59rfW8044yGnYUZKZoRWERMoZDTFGHhV9oB6s4/kK6eXRNtLInKORVrtp4y\n1ypMSIUvsB7YS2IyFOsytikENDyvObaLUjJNv0Nfm/sNQ6atLKdBKVXJcLO1FUY0\nv+QeUi2kSS7VIe38APeiC7wSCpBkUHzFFHD1+5tiCHhDSTHzR7iT1C1MqdwXcHY0\nZv9lwTln6aYcgGrwnbMoyKM6t13eblNtCbMNsRMQ6OzgvV6hlEtwv9y9i7mCZd2v\ngXsCAwEAAaOCAZ4wggGaMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu\naW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU\nK4yji9vbXM4Y2/aoMU7CPu7TQH4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I\nRWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh\nMRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD\nZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb\nM0C2bSEwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA\nAAABMBEGCWCGSAGG+EIBAQQEAwIGwDALBgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYI\nKwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYBBAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZI\nhvcNAQELBQADggEBAFRJNCs40ao7Q2BMP2r4dMpJU6GvEtOoF5B7naNpE27aLLdh\nMazrAJOS/AwQ1BigFmGUS0LL63r2gMZFwJwJqqlI6Dbjxb424Ol4KrurZJsg6+YP\nYytZw1gLOoQVBMF+EgMbCSWNTAPoGCbAbLeQsf288c/Q1UoDFXEMfcF2h5LxPrx1\nUVrENqT/kZjfM12nON5QKf0PyFXmjyTCLpir2V1lL1DMJfaE8iEuXnbQhh5pi8uK\nOi15IV7n9y0GGKETywHDRpEq3rSC18Nibwih1ZAZMJ1kjuT4uk8vuhO0o5/R1Xdk\nij7rU8VHrKs+DnqbpvRIJWbrx0z5UCTrceB1ruY=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/testing/certs/ip-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L\nzdVqeVQ8B1/rWnErK+VvvjH7FmVodg5Z5+RXyojpd9ZBrVd6QrLSVMQPfFvBvGGX\n4yI6Ph5KXUefa31vNOOMhp2FGSmaEVhETKGQ0xRh4VfaAerOP5Cunl0TbSyJyjkV\na7aeMtcqTEiFL7Ae2EtiMhTrMrYpBDQ8rzm2i1IyTb9DX5v7DUOmrSynQSlVyXCz\ntRVGNL/kHlItpEku1SHt/AD3ogu8EgqQZFB8xRRw9fubYgh4Q0kx80e4k9QtTKnc\nF3B2NGb/ZcE5Z+mmHIBq8J2zKMijOrdd3m5TbQmzDbETEOjs4L1eoZRLcL/cvYu5\ngmXdr4F7AgMBAAECggEBAK4sr3MiEbjcsHJAvXyzjwRRH1Bu+8VtLW7swe2vvrpd\nw4aiKXrV/BXpSsRtvPgxkXyvdMSkpuBZeFI7cVTwAJFc86RQPt77x9bwr5ltFwTZ\nrXCbRH3b3ZPNhByds3zhS+2Q92itu5cPyanQdn2mor9/lHPyOOGZgobCcynELL6R\nwRElkeDyf5ODuWEd7ADC5IFyZuwb3azNVexIK+0yqnMmv+QzEW3hsycFmFGAeB7v\nMIMjb2BhLrRr6Y5Nh+k58yM5DCf9h/OJhDpeXwLkxyK4BFg+aZffEbUX0wHDMR7f\n/nMv1g6cKvDWiLU8xLzez4t2qNIBNdxw5ZSLyQRRolECgYEA+ySTKrBAqI0Uwn8H\nsUFH95WhWUXryeRyGyQsnWAjZGF1+d67sSY2un2W6gfZrxRgiNLWEFq9AaUs0MuH\n6syF4Xwx/aZgU/gvsGtkgzuKw1bgvekT9pS/+opmHRCZyQAFEHj0IEpzyB6rW1u/\nLdlR3ShEENnmXilFv/uF/uXP5tMCgYEA63LiT0w46aGPA/E+aLRWU10c1eZ7KdhR\nc3En6zfgIxgFs8J38oLdkOR0CF6T53DSuvGR/OprVKdlnUhhDxBgT1oQjK2GlhPx\nJV5uMvarJDJxAwsF+7T4H2QtZ00BtEfpyp790+TlypSG1jo/BnSMmX2uEbV722lY\nhzINLY49obkCgYBEpN2YyG4T4+PtuXznxRkfogVk+kiVeVx68KtFJLbnw//UGT4i\nEHjbBmLOevDT+vTb0QzzkWmh3nzeYRM4aUiatjCPzP79VJPsW54whIDMHZ32KpPr\nTQMgPt3kSdpO5zN7KiRIAzGcXE2n/e7GYGUQ1uWr2XMu/4byD5SzdCscQwJ/Ymii\nLoKtRvk/zWYHr7uwWSeR5dVvpQ3E/XtONAImrIRd3cRqXfJUqTrTRKxDJXkCmyBc\n5FkWg0t0LUkTSDiQCJqcUDA3EINFR1kwthxja72pfpwc5Be/nV9BmuuUysVD8myB\nqw8A/KsXsHKn5QrRuVXOa5hvLEXbuqYw29mX6QKBgDGDzIzpR9uPtBCqzWJmc+IJ\nz4m/1NFlEz0N0QNwZ/TlhyT60ytJNcmW8qkgOSTHG7RDueEIzjQ8LKJYH7kXjfcF\n6AJczUG5PQo9cdJKo9JP3e1037P/58JpLcLe8xxQ4ce03zZpzhsxR2G/tz8DstJs\nb8jpnLyqfGrcV2feUtIZ\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/testing/certs/rootCA-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCyRbzelNj5Qkxg\nMxYIr4yEqLtTz/jSiX1W6uUymI+e1GQzl3BpP3fS/SyB9HZv1bn4PoiWB+BggsFr\nOsmVF4aXbikRz74DQ7FCqVMU/QGvlIbHkH5TShcLGzngG8DR3+Url3S8rlvYQBf2\nAXYXWcmSl1bFYzVxWSt67NzS29mvus40aAPXTpB7vq6nNs6F+7sARhDiOPRqniPq\nV29khMmxndwExAEMJBVZbeTNuwfehCud8dOj0kzk5ESX+3upIwOrnoye2tRNW34W\nWaxQrV65KOeGxdgIN+PeO7WL1jbCitCaGnGitTbtPGMCdf6LmRhA30WyIZ3ZkxOm\nvXGkpAR6mxz3pqQWTZYieTA92s63LeVSNeYUof0tNu0SMTYWuAas0Ob3A/lu7PTC\njTag5vVU5RwkfBmcNrbNNy9NbKgQB7TafZn7sfPpZT4EpAcFMgRb4KfRHfPiaxlD\nu2LKmBS9+i7x79nYAlB5IGgLyQ1cldwjDYqAAizBoigM2PI3tEMCAwEAAQKCAYBp\nK5kj2r4yNrmmGx1JnH8SmBSDenL5ieEm0MbMVZKNChHfGd1YSfgfwfpq5FSm33iq\nCgI8OINXjGwdHX5k9Y8ScQvLlTos5NeDUy9Pd39yHPZybz0HV/NGOxamrtjPN/4T\n/HMDCP3oEs/P8sa/OdogICYxpriVmRx8lZYk00yWTmduJVr2v0OfrTuOLFgkVQDa\nRXuaai1PZOIdUt3FeE0g+tcc/KD9j6AEtT9BW7BlxqWQtWS9BckVU9FftB4dBykc\nEIqo4wqWAVNulV6m6zSlzG1YioN5ZYvW3T5IMsRf2s0t6hmkRU56t5Uqp1MYSzZY\nbiDHyAkHLPFvmnJIIbcj7oBVdE0iM8VEmlYChiPHsEP3WhQKyN+AELTx+pr3DTuR\n4y7r6eAH7kJldo09xanRBE9aBDmblEBIvPCV0SKapWe41y2NGUfrDVvc8lVVXLe8\n1DVo+VOJGMXS9fSx+KOFDho7gNyQYs7K+9lhXa9nRov44+GXL4VdzAEs8msi0yEC\ngcEA2Yu56/fYZ1bvkw+bxE8cafs7Q0e8vpf/GRubaCgK0kRHh8Ssf+reqN1laCOj\nKgZd7b6V0UJsH62tLPzoqccWK3JHLRTUfSCsfR/uygp5oc6azpasyEzqWFLoUnkB\nA4q3aABau0s9At8OJs//lAXyI1QE4bjwgOeDRPp/ynPfYVcGHDoyyd8lAyDUG7cc\nf8G+o64oPxMEnIYNmLkag36k7fP0szx6kEbrp3H+5h/c7uy0JFW9PwZAHp+fgRWU\nallxAoHBANHI1OadRzqbEB5+iyxXFZok/BqMHE7QyKxNMTtKqaWUS8RH9f+vPSBK\nhK7I8kiTXaloLQ4VW54qjJ4NCd3eR6PGlGLecTHj4F3wu+84jncHnSaV5f1akvD2\nteC61Gmd8oeje/9G5xdVJpDEl9hfXqtTSPiy96IsXhFNr97q0sm35Alc6ZYeICXA\nJ0i9wdk3vtimELH/ul9daI25asFi4kA36ymawqNXk4b3/bOaBXFqVpECpV3YfXMR\nsJQpYxqu8wKBwQCdtNOFotj4oWdwPwJ3H7rDgeOGdLz5lorSEtdofI7Lu7/3Rrae\nzQ+5bzaSdjNUxeTV8zH8z6A+ntNKJ9YrLi5+NIwwvEcGpucklj+vrERc7r//P+/m\nDQxeF0xgbWQ0wx0OgiNEX9jM+hLyRBtNnbnZrpETadTAPhVFrityAupPUJ0XXYFw\nIxpb2DKsHOTGIRgo5Jo8j3bqWawFqTr1VJwP/KjKPu/DJAa2Dsfw3+x0MJivNpDI\n3akiCinBlHlRV6ECgcEApG3tsfSE6AKyV7SIEXEQlYl3sLcxWPV81NCMThTvc8EQ\nwgBFaOtJ1g2Sgg0vGoOnXikxZ2CGNyrSnO9LVIPtUwlLNVN1Fc2vBvKx24dQ4yss\nmhnT8wkTM5usY0ENTNtoRbh2cFh6uWccm0v8WLQn19Gn2IcuYga0lIt31hnorgNc\n0Znp3KgwOmaqY/GYB1ISXG2NmHcA9c6ZLLywWHPRMtShljKfbLgwAhJO4H9Q1Nys\njWytgSk26wJqjTcDXt7RAoHAd0geRKlPbumvnHqs6e9Aq+0q63ofskEoCDDmE1nT\nGTlcjLfEe3PB6KBlU/f2UGT5JwAeBMxlB0+XzS9CQnHKU2f6N4qn4nXGK0ZhXzu9\n+WqQhKzfQb3D1x0BTpRfVNtFSHvCjWjhZgXgAZDCtArVZ1dnZZPiOpnym+CrBun+\ncw4IOH2QbK6TNGpFW2Rtcs/CginusGcXUIAjQZGjrEIkkX73QjLQ5hU39CCe/PvI\n4ptoUsi0eFG0TZN3kB99ERCJ\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/testing/certs/rootCA.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIRAIW/i8Ryvk+oZGg+/FvDpW8wDQYJKoZIhvcNAQELBQAw\ngYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjc3Rq\nZXBhbkBsb2NhbGhvc3QgKFN0amVwYW4gR2xhdmluYSkxMzAxBgNVBAMMKm1rY2Vy\ndCBzdGplcGFuQGxvY2FsaG9zdCAoU3RqZXBhbiBHbGF2aW5hKTAeFw0yMDA3MDcx\nNTU4NTlaFw0zMDA3MDcxNTU4NTlaMIGDMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxv\ncG1lbnQgQ0ExLDAqBgNVBAsMI3N0amVwYW5AbG9jYWxob3N0IChTdGplcGFuIEds\nYXZpbmEpMTMwMQYDVQQDDCpta2NlcnQgc3RqZXBhbkBsb2NhbGhvc3QgKFN0amVw\nYW4gR2xhdmluYSkwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCyRbze\nlNj5QkxgMxYIr4yEqLtTz/jSiX1W6uUymI+e1GQzl3BpP3fS/SyB9HZv1bn4PoiW\nB+BggsFrOsmVF4aXbikRz74DQ7FCqVMU/QGvlIbHkH5TShcLGzngG8DR3+Url3S8\nrlvYQBf2AXYXWcmSl1bFYzVxWSt67NzS29mvus40aAPXTpB7vq6nNs6F+7sARhDi\nOPRqniPqV29khMmxndwExAEMJBVZbeTNuwfehCud8dOj0kzk5ESX+3upIwOrnoye\n2tRNW34WWaxQrV65KOeGxdgIN+PeO7WL1jbCitCaGnGitTbtPGMCdf6LmRhA30Wy\nIZ3ZkxOmvXGkpAR6mxz3pqQWTZYieTA92s63LeVSNeYUof0tNu0SMTYWuAas0Ob3\nA/lu7PTCjTag5vVU5RwkfBmcNrbNNy9NbKgQB7TafZn7sfPpZT4EpAcFMgRb4KfR\nHfPiaxlDu2LKmBS9+i7x79nYAlB5IGgLyQ1cldwjDYqAAizBoigM2PI3tEMCAwEA\nAaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0O\nBBYEFGg72r4gKsOkZ8rgX9wSO0trlJnyMA0GCSqGSIb3DQEBCwUAA4IBgQBzxbH6\ns7dhvyOGK5HnQmc/vPiTbOk4QhXX12PPekL+UrhMyiMzWET2wkpHJXxtes0dGNU3\ntmQwVQTvg/BhyxxGcafvQe/o/5rS9lrrpdQriZg3gqqeUoi8UX+5/ef9EB7Z32Ho\nqUSOd6hPLLI+3UrnmWP+u5PRDmsHQYKc2YqyqQisjRVwLtGtCmGYfuBncP145Yyi\nqNlwI6jeZTAtRSkcKy6fnyJcjOCYKFWHpTGmBTMtO4LiTGadxnmbAq9mRBiKJJp6\nwrSz1JvbVXVY4caxpbDfkaT8RiP+k1Fbd6uMWnZTJLHPTNbzCl4aXcuHgoRhCLeq\nSdF3L7m0tM7lsTP3tddRY6zb+1u0II0Gu6umDsdyL6JOV4vv9Qb7xdy2jTU231+o\nTXLHaypw4Amp267EyvvWmU3VOl8BeUkJ/7LOqzZfKxTECwnxWywx6NV9ONQt8mNC\nATAQAyYXklJsZkX6VLMPE0Lv4Qbt/GnGUejER09zQi433e9jUF+vwQGwj/g=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/testing/certs/server-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEbjCCAtagAwIBAgIRAI5awGA99MSpuYlAyXOE32AwDQYJKoZIhvcNAQELBQAw\ngYMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEsMCoGA1UECwwjc3Rq\nZXBhbkBsb2NhbGhvc3QgKFN0amVwYW4gR2xhdmluYSkxMzAxBgNVBAMMKm1rY2Vy\ndCBzdGplcGFuQGxvY2FsaG9zdCAoU3RqZXBhbiBHbGF2aW5hKTAeFw0xOTA2MDEw\nMDAwMDBaFw0zMDExMDUxMjA5NDRaMGExJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w\nbWVudCBjZXJ0aWZpY2F0ZTE2MDQGA1UECwwtc3RqZXBhbkBNYWNCb29rLVByby0y\nLmxvY2FsIChTdGplcGFuIEdsYXZpbmEpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAvH8A6SClj/Vwhp3QImnI5mUg5cQKZy6/2Gz+5abTRNW4Je4RKK66\n3zfIDBdDerXBtArRjMLrou1K6XxB0/yz8fQAkvgRR+xpWap30GjMgd7qIewIex79\nyW2U3BrKLa0FZC7b5H3NK07Bwz/RVlILycZA/hJ1/ApBDJ+mA30D7jMHMopFMSvG\nFHuAY6TZvdQyDdin3Yw4ciIsDHf8JUNYrMsqGjYukj49B3g0n7161lLn/Fo6s0Pv\nltqUfhwXyXdGU+2OELTvVCLlPLj8CBxTcfpENnXQYGUI1l/B+TnKlu+Iz3D/k3Nm\nvmSJMGPnL37B9X3RqNnkw7TDMsmRk8WduwIDAQABo34wfDAOBgNVHQ8BAf8EBAMC\nBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW\ngBRoO9q+ICrDpGfK4F/cEjtLa5SZ8jAmBgNVHREEHzAdgglsb2NhbGhvc3SHEAAA\nAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAG6T+h8wRElEzt8NOC7A\nSFUTT60RaImz6/nu1LYG0uxY5hSITUSEAUUXpeeP+o133CWrhIpNkUr/bmN5tpQX\nvyCv33zJHuaRZ+icwrNu0MmSZ2QVc2ovW0lDr6rWJHrsnH4vk7YBnSN8u2J2Q5u3\nnJLX7NkU0KPoDyCpolci0wgrCEgwR1CSak0M25cch2X2TQylsoeROfsIpWLT1VKf\nH9cZzasj1TzSoo02YuyHhuZP28NGFZvZZy0/aVHmwo6s2fbHVBvaAyDmjRIJI3Ep\n0OVwv/FFmubygoLdI0EU40bfkxNoCpf+MR5rwu1lJFfbNuY2ojjeHWjXxj48Y6fN\nwc6OztjNO9Wjj+l4XKP/tUhuCX/aXVVZP6DVB74pAlOODoEu/4XKCyJSrmRIuzfA\nxPyNDb/ABXF2JMV9zbVsDmp5/QrBIovvM25pWS4oG8L9wBgKF7fbenCR3iy+rgUe\nwN0bqTGWrB9k0mFtBbDDg+VbMPXo7mOcCmNYz8uJMPnrFA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/testing/certs/server-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8fwDpIKWP9XCG\nndAiacjmZSDlxApnLr/YbP7lptNE1bgl7hEorrrfN8gMF0N6tcG0CtGMwuui7Urp\nfEHT/LPx9ACS+BFH7GlZqnfQaMyB3uoh7Ah7Hv3JbZTcGsotrQVkLtvkfc0rTsHD\nP9FWUgvJxkD+EnX8CkEMn6YDfQPuMwcyikUxK8YUe4BjpNm91DIN2KfdjDhyIiwM\nd/wlQ1isyyoaNi6SPj0HeDSfvXrWUuf8WjqzQ++W2pR+HBfJd0ZT7Y4QtO9UIuU8\nuPwIHFNx+kQ2ddBgZQjWX8H5OcqW74jPcP+Tc2a+ZIkwY+cvfsH1fdGo2eTDtMMy\nyZGTxZ27AgMBAAECggEAXJZJnTlC+Y5Ggmj79htd6gVcfl+n+HzXEPigz6788T/F\nHyRr2z7QXZppsb6vj5O9nLD/sxN/aN0DweId94GV5c/DhG1DF8ABE2EPTxha86PJ\n/3WPyOI1KH6h8udZzcvB7S6zJe3BHHen5z7ulWbhkW/HNsVcnLtwrkGw6t+6UYJ3\na1yceXFAKFlv8f9M8g/PaP0zKyuprKtGupdTUhoekZ4M87jjsKQQ33oNdF5GBl56\nnIEAXK+rrR3ep2xeFtQ2/tdzAGfwB0NfrX4q7BEjPwpnt7lzLx/pIY4+ZSZc4GsZ\nfoxzTsAZ+5SYkhu2m5hLGUPrD4FIDedv2UEEMv74oQKBgQD6bDSuNqzRZNQC/8Yk\n6BLpiMTJVqHQi8etGsFy/608ahRz6BHdmtf3Jz63OYdxOL3nSa4Eh0lKzCyU9Ryw\nqvQIGdMnT6Bqpp7gNI596jzlLDhw1XD16XS+lFCvJ4DFjn9ZqxGo7dw62RA3LV/L\nC75zdrUqyO5XlfxmKghEC2YnawKBgQDAsbibqYBl/TPfn9J3x87m97t90j5OatG5\nnHAFdRmp7+QfxH5cCkHESPfNbiJL1tIDxIuNNvon3ffa7WPpKlf3fybE0WQq2jKi\nR0VD+U56OUZA0hpHOhG2aKZIix02oniVLQZ0Qq59jAiiiJWkvu4FLSirC+EGnL/h\nah54nIoG8QKBgQCMNv/8N8Ll75XCJCJ20badKiY9MZOi6FEiPKPqVvxRonfXOi6e\nrS+VRFUaVEzg+UtjcF7OTE2eYtnngaLRzLacvpD7JtuEO80jbmoGWJxGGU905h28\noz3p47OVjwHMG/B0bZOSybQRAy7QJkjHsMivb90amqzRP7q2HXzJVLSbBwKBgBXv\n5alrCaASzGYIBuj2CVsIFwNC/S7mQEwWQDaO10YedmUbdJs727Lh77wmbqcdpLkj\nFhQUjzQctAvrfLVdybf2dM5xXCr4vkz1OjB74HBPtuzIPo+fT8bpcQzPMZs3seyh\nvJtdwAmw+IawcADab7SNKJUYfBzJmZqq/x8SCzCxAoGARA5w5I7xVJpseMmBit7y\nGsID7iFQoYCScQI3vYpDyS52hYK5F0NetUVqd5Ph3/uwHK5cCPK6k2n38zrXUxE3\nY406bA1cwl6+M0npkJnBt3c8IV50fJwoYtUP5Yr/JogSQJadIQhZg2cch3yiZbc9\nDW01ooYHlL6XeSk5nn1Jc7s=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/testing/client/advanced.zig",
    "content": "//! Tests for checkCompatibility, publishMsg, requestMsg, and message status.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\n/// Test checkCompatibility returns true for current server.\npub fn testCheckCompatibility(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"check_compatibility\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // NATS server should be at least 2.0.0\n    if (client.checkCompatibility(2, 0, 0)) {\n        // Also verify it fails for unreasonably high version\n        if (!client.checkCompatibility(99, 0, 0)) {\n            reportResult(\"check_compatibility\", true, \"\");\n        } else {\n            reportResult(\"check_compatibility\", false, \"99.0.0 should fail\");\n        }\n    } else {\n        reportResult(\"check_compatibility\", false, \"2.0.0 should pass\");\n    }\n}\n\n/// Test publishMsg republishes a message correctly.\npub fn testPublishMsg(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"publish_msg\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"test.publishmsg\") catch {\n        reportResult(\"publish_msg\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Create a message to republish\n    const original = nats.Client.Message{\n        .subject = \"test.publishmsg\",\n        .sid = 0,\n        .reply_to = null,\n        .data = \"republished-data\",\n        .headers = null,\n        .owned = false,\n    };\n\n    client.publishMsg(&original) catch {\n        reportResult(\"publish_msg\", false, \"publishMsg failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(500) catch null) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"republished-data\")) {\n            reportResult(\"publish_msg\", true, \"\");\n        } else {\n            reportResult(\"publish_msg\", false, \"wrong data\");\n        }\n    } else {\n        reportResult(\"publish_msg\", false, \"no message received\");\n    }\n}\n\n/// Test Message.getStatus and isNoResponders with actual no-responders.\npub fn testNoRespondersStatus(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .no_responders = true,\n    }) catch {\n        reportResult(\"no_responders_status\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Request to a subject with no subscribers - should get 503\n    const reply = client.request(\n        \"nonexistent.subject.xyz\",\n        \"test\",\n        500,\n    ) catch {\n        reportResult(\"no_responders_status\", false, \"request failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        // Check status via getStatus()\n        const status = msg.status();\n        if (status == 503 and msg.isNoResponders()) {\n            reportResult(\"no_responders_status\", true, \"\");\n        } else {\n            var buf: [32]u8 = undefined;\n            const detail = std.fmt.bufPrint(&buf, \"status={?}\", .{status}) catch \"e\";\n            reportResult(\"no_responders_status\", false, detail);\n        }\n    } else {\n        // Timeout - server might not support no_responders or timing issue\n        reportResult(\"no_responders_status\", true, \"\");\n    }\n}\n\n/// Test requestMsg forwards a message as request.\npub fn testRequestMsg(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n    const requester = nats.Client.connect(allocator, io_req.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"request_msg\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const io_resp = utils.newIo(allocator);\n    defer io_resp.deinit();\n    const responder = nats.Client.connect(allocator, io_resp.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"request_msg\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    // Set up a responder\n    var sub = responder.subscribeSync(\"test.requestmsg\") catch {\n        reportResult(\"request_msg\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    responder.flush(1_000_000_000) catch {\n        reportResult(\"request_msg\", false, \"flush failed\");\n        return;\n    };\n\n    // Spawn responder task\n    var responder_future = io_resp.io().async(\n        responderTask,\n        .{ &sub, responder },\n    );\n    defer responder_future.cancel(io_resp.io());\n\n    // Create message to send as request\n    const request_msg = nats.Client.Message{\n        .subject = \"test.requestmsg\",\n        .sid = 0,\n        .reply_to = null,\n        .data = \"request-data\",\n        .headers = null,\n        .owned = false,\n    };\n\n    const reply = requester.requestMsg(&request_msg, 1000) catch {\n        reportResult(\"request_msg\", false, \"requestMsg failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"response-data\")) {\n            reportResult(\"request_msg\", true, \"\");\n        } else {\n            reportResult(\"request_msg\", false, \"wrong response\");\n        }\n    } else {\n        reportResult(\"request_msg\", false, \"timeout\");\n    }\n}\n\nfn responderTask(\n    sub: **nats.Subscription,\n    client: *nats.Client,\n) void {\n    if (sub.*.nextMsgTimeout(2000) catch null) |msg| {\n        defer msg.deinit();\n        if (msg.reply_to) |reply_to| {\n            client.publish(reply_to, \"response-data\") catch {};\n        }\n    }\n}\n\nfn requestMsgOrderingResponder(\n    sub: **nats.Subscription,\n    client: *nats.Client,\n) void {\n    var saw_state = false;\n    var handled: usize = 0;\n    while (handled < 2) {\n        const maybe_msg = sub.*.nextMsgTimeout(1000) catch return;\n        const msg = maybe_msg orelse return;\n        defer msg.deinit();\n        handled += 1;\n\n        if (std.mem.eql(u8, msg.subject, \"test.requestmsg.ordering.state\")) {\n            saw_state = true;\n            continue;\n        }\n\n        if (std.mem.eql(u8, msg.subject, \"test.requestmsg.ordering.service\")) {\n            const reply_to = msg.reply_to orelse return;\n            client.publish(\n                reply_to,\n                if (saw_state) \"fresh\" else \"stale\",\n            ) catch {};\n            return;\n        }\n    }\n}\n\npub fn testRequestMsgOrdering(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_req.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_msg_ordering\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const io_resp = utils.newIo(allocator);\n    defer io_resp.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_resp.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_msg_ordering\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    var sub = responder.subscribeSync(\"test.requestmsg.ordering.>\") catch {\n        reportResult(\"request_msg_ordering\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    responder.flush(1_000_000_000) catch {\n        reportResult(\"request_msg_ordering\", false, \"flush failed\");\n        return;\n    };\n\n    var handler = io_resp.io().async(\n        requestMsgOrderingResponder,\n        .{ &sub, responder },\n    );\n    defer handler.cancel(io_resp.io());\n\n    requester.publish(\"test.requestmsg.ordering.state\", \"state-update\") catch {\n        reportResult(\"request_msg_ordering\", false, \"publish failed\");\n        return;\n    };\n\n    const request_msg = nats.Client.Message{\n        .subject = \"test.requestmsg.ordering.service\",\n        .sid = 0,\n        .reply_to = null,\n        .data = \"request-data\",\n        .headers = null,\n        .owned = false,\n    };\n\n    const reply = requester.requestMsg(&request_msg, 1000) catch {\n        reportResult(\"request_msg_ordering\", false, \"requestMsg failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"fresh\")) {\n            reportResult(\"request_msg_ordering\", true, \"\");\n        } else {\n            reportResult(\"request_msg_ordering\", false, \"request overtook publish\");\n        }\n    } else {\n        reportResult(\"request_msg_ordering\", false, \"timeout\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testCheckCompatibility(allocator);\n    testPublishMsg(allocator);\n    testNoRespondersStatus(allocator);\n    testRequestMsg(allocator);\n    testRequestMsgOrdering(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/async_patterns.zig",
    "content": "//! Async Patterns Integration Tests\n//!\n//! Tests std.Io async patterns with NATS client including:\n//! - Io.Select for racing operations\n//! - io.concurrent() + Io.Queue for workers\n//! - io.async() for parallel receives\n//! - Cancellation and cleanup patterns\n//! - Batch receiving\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\nconst ServerManager = utils.ServerManager;\n\nconst Io = std.Io;\nconst Allocator = std.mem.Allocator;\nconst Sub = nats.Client.Sub;\nconst Message = nats.Message;\n\n/// Sleep function compatible with Io.Select.async()\nfn sleepMs(io: Io, ms: i64) void {\n    io.sleep(.fromMilliseconds(ms), .awake) catch {};\n}\n\nconst MsgSel = Io.Select(union(enum) {\n    message: anyerror!Message,\n    timeout: void,\n});\n\n/// Cancel a MsgSel, deiniting any completed messages.\nfn cancelMsgSel(sel: *MsgSel) void {\n    while (sel.cancel()) |remaining| {\n        switch (remaining) {\n            .message => |r| {\n                if (r) |m| m.deinit() else |_| {}\n            },\n            .timeout => {},\n        }\n    }\n}\n\n/// Test 1: Race subscription receive against timeout - timeout wins.\nfn testAsyncSelectTimeout(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_select_timeout\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"async.timeout.test\",\n    ) catch {\n        reportResult(\n            \"async_select_timeout\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Do NOT publish - we want timeout to win\n    var buf: [2]MsgSel.Union = undefined;\n    var sel = MsgSel.init(io, &buf);\n    sel.async(.message, Sub.nextMsg, .{sub});\n    sel.async(.timeout, sleepMs, .{ io, 50 });\n\n    const result = sel.await() catch {\n        cancelMsgSel(&sel);\n        reportResult(\n            \"async_select_timeout\",\n            false,\n            \"select failed\",\n        );\n        return;\n    };\n    cancelMsgSel(&sel);\n\n    switch (result) {\n        .message => {\n            reportResult(\n                \"async_select_timeout\",\n                false,\n                \"expected timeout\",\n            );\n        },\n        .timeout => {\n            reportResult(\"async_select_timeout\", true, \"\");\n        },\n    }\n}\n\n/// Test 2: Race subscription receive against timeout - message wins.\nfn testAsyncSelectMessage(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_select_message\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"async.message.test\",\n    ) catch {\n        reportResult(\n            \"async_select_message\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish message immediately\n    client.publish(\n        \"async.message.test\",\n        \"select-test-msg\",\n    ) catch {\n        reportResult(\n            \"async_select_message\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    var buf: [2]MsgSel.Union = undefined;\n    var sel = MsgSel.init(io, &buf);\n    sel.async(.message, Sub.nextMsg, .{sub});\n    sel.async(.timeout, sleepMs, .{ io, 500 });\n\n    const result = sel.await() catch {\n        cancelMsgSel(&sel);\n        reportResult(\n            \"async_select_message\",\n            false,\n            \"select failed\",\n        );\n        return;\n    };\n    cancelMsgSel(&sel);\n\n    switch (result) {\n        .message => |msg_result| {\n            const msg = msg_result catch {\n                reportResult(\n                    \"async_select_message\",\n                    false,\n                    \"msg error\",\n                );\n                return;\n            };\n            defer msg.deinit();\n            if (std.mem.eql(\n                u8,\n                msg.data,\n                \"select-test-msg\",\n            )) {\n                reportResult(\n                    \"async_select_message\",\n                    true,\n                    \"\",\n                );\n            } else {\n                reportResult(\n                    \"async_select_message\",\n                    false,\n                    \"wrong data\",\n                );\n            }\n        },\n        .timeout => {\n            reportResult(\n                \"async_select_message\",\n                false,\n                \"unexpected timeout\",\n            );\n        },\n    }\n}\n\n/// Worker result for concurrent workers test.\nconst WorkerResult = struct {\n    worker_id: u8,\n    msg: nats.Message,\n\n    fn deinit(self: WorkerResult) void {\n        self.msg.deinit();\n    }\n};\n\n/// Worker task for concurrent test.\nfn workerTask(\n    io: Io,\n    worker_id: u8,\n    sub: *Sub,\n    queue: *Io.Queue(WorkerResult),\n    done: *std.atomic.Value(bool),\n) void {\n    while (!done.load(.acquire)) {\n        const msg = sub.nextMsgTimeout(\n            100,\n        ) catch return orelse continue;\n        queue.putOne(io, .{\n            .worker_id = worker_id,\n            .msg = msg,\n        }) catch {\n            msg.deinit();\n            return;\n        };\n    }\n}\n\n/// Test 3: Multiple workers with io.concurrent() + Io.Queue.\nfn testAsyncConcurrentWorkers(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    // Create 3 workers in queue group\n    const w1_sub = client.queueSubscribeSync(\n        \"async.workers\",\n        \"workers\",\n    ) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"sub1 failed\",\n        );\n        return;\n    };\n    defer w1_sub.deinit();\n\n    const w2_sub = client.queueSubscribeSync(\n        \"async.workers\",\n        \"workers\",\n    ) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"sub2 failed\",\n        );\n        return;\n    };\n    defer w2_sub.deinit();\n\n    const w3_sub = client.queueSubscribeSync(\n        \"async.workers\",\n        \"workers\",\n    ) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"sub3 failed\",\n        );\n        return;\n    };\n    defer w3_sub.deinit();\n\n    // Shared queue for results\n    var queue_buf: [64]WorkerResult = undefined;\n    var queue: Io.Queue(WorkerResult) = .init(&queue_buf);\n    var done: std.atomic.Value(bool) = .init(false);\n\n    // Launch workers\n    var w1 = io.concurrent(workerTask, .{\n        io, 1, w1_sub, &queue, &done,\n    }) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"w1 launch failed\",\n        );\n        return;\n    };\n    defer w1.cancel(io);\n\n    var w2 = io.concurrent(workerTask, .{\n        io, 2, w2_sub, &queue, &done,\n    }) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"w2 launch failed\",\n        );\n        return;\n    };\n    defer w2.cancel(io);\n\n    var w3 = io.concurrent(workerTask, .{\n        io, 3, w3_sub, &queue, &done,\n    }) catch {\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            \"w3 launch failed\",\n        );\n        return;\n    };\n    defer w3.cancel(io);\n\n    // Publish messages\n    const message_count: u32 = 30;\n    var i: u32 = 0;\n    while (i < message_count) : (i += 1) {\n        client.publish(\"async.workers\", \"work-item\") catch {};\n    }\n\n    // Consume results\n    var total_received: u32 = 0;\n    var timeout_count: u32 = 0;\n    const max_timeouts: u32 = 10;\n\n    const QSel = Io.Select(union(enum) {\n        result: anyerror!WorkerResult,\n        timeout: void,\n    });\n\n    while (total_received < message_count and\n        timeout_count < max_timeouts)\n    {\n        var sel_buf: [2]QSel.Union = undefined;\n        var sel = QSel.init(io, &sel_buf);\n        sel.async(\n            .result,\n            Io.Queue(WorkerResult).getOne,\n            .{ &queue, io },\n        );\n        sel.async(.timeout, sleepMs, .{ io, 200 });\n\n        const s = sel.await() catch {\n            while (sel.cancel()) |remaining| {\n                switch (remaining) {\n                    .result => |r| {\n                        if (r) |wr| wr.deinit() else |_| {}\n                    },\n                    .timeout => {},\n                }\n            }\n            break;\n        };\n        while (sel.cancel()) |remaining| {\n            switch (remaining) {\n                .result => |r| {\n                    if (r) |wr| wr.deinit() else |_| {}\n                },\n                .timeout => {},\n            }\n        }\n\n        switch (s) {\n            .result => |res| {\n                const r = res catch break;\n                r.deinit();\n                total_received += 1;\n            },\n            .timeout => {\n                timeout_count += 1;\n            },\n        }\n    }\n\n    // Signal workers to stop\n    done.store(true, .release);\n\n    if (total_received >= message_count * 9 / 10) {\n        reportResult(\"async_concurrent_workers\", true, \"\");\n    } else {\n        var result_buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &result_buf,\n            \"{d}/{d} received\",\n            .{ total_received, message_count },\n        ) catch \"partial\";\n        reportResult(\n            \"async_concurrent_workers\",\n            false,\n            detail,\n        );\n    }\n}\n\n/// Test 4: Multiple parallel subscriptions with io.async().\nfn testAsyncParallelSubscriptions(\n    allocator: Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_parallel_subs\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub_a = client.subscribeSync(\n        \"async.parallel.a\",\n    ) catch {\n        reportResult(\n            \"async_parallel_subs\",\n            false,\n            \"sub_a failed\",\n        );\n        return;\n    };\n    defer sub_a.deinit();\n\n    const sub_b = client.subscribeSync(\n        \"async.parallel.b\",\n    ) catch {\n        reportResult(\n            \"async_parallel_subs\",\n            false,\n            \"sub_b failed\",\n        );\n        return;\n    };\n    defer sub_b.deinit();\n\n    const sub_c = client.subscribeSync(\n        \"async.parallel.c\",\n    ) catch {\n        reportResult(\n            \"async_parallel_subs\",\n            false,\n            \"sub_c failed\",\n        );\n        return;\n    };\n    defer sub_c.deinit();\n\n    // Publish to all three\n    client.publish(\"async.parallel.a\", \"msg-a\") catch {};\n    client.publish(\"async.parallel.b\", \"msg-b\") catch {};\n    client.publish(\"async.parallel.c\", \"msg-c\") catch {};\n\n    // Launch parallel receives\n    var future_a = io.async(Sub.nextMsg, .{sub_a});\n    defer if (future_a.cancel(io)) |m|\n        m.deinit()\n    else |_| {};\n\n    var future_b = io.async(Sub.nextMsg, .{sub_b});\n    defer if (future_b.cancel(io)) |m|\n        m.deinit()\n    else |_| {};\n\n    var future_c = io.async(Sub.nextMsg, .{sub_c});\n    defer if (future_c.cancel(io)) |m|\n        m.deinit()\n    else |_| {};\n\n    var received: u8 = 0;\n\n    if (future_a.await(io)) |_| {\n        received += 1;\n    } else |_| {}\n\n    if (future_b.await(io)) |_| {\n        received += 1;\n    } else |_| {}\n\n    if (future_c.await(io)) |_| {\n        received += 1;\n    } else |_| {}\n\n    if (received == 3) {\n        reportResult(\"async_parallel_subs\", true, \"\");\n    } else {\n        var result_buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &result_buf,\n            \"{d}/3 received\",\n            .{received},\n        ) catch \"partial\";\n        reportResult(\"async_parallel_subs\", false, detail);\n    }\n}\n\n/// Test 5: Cancellation of pending receive.\nfn testAsyncCancellation(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_cancellation\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"async.cancel.test\",\n    ) catch {\n        reportResult(\n            \"async_cancellation\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Start async receive but cancel immediately\n    var future = io.async(Sub.nextMsg, .{sub});\n\n    // Small delay then cancel\n    io.sleep(.fromMilliseconds(10), .awake) catch {};\n\n    if (future.cancel(io)) |msg| {\n        msg.deinit();\n        reportResult(\"async_cancellation\", true, \"\");\n    } else |_| {\n        reportResult(\"async_cancellation\", true, \"\");\n    }\n}\n\n/// Test 6: Cancel after message already received.\nfn testAsyncCancelWithPendingMessage(\n    allocator: Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_cancel_with_msg\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"async.cancel.msg\",\n    ) catch {\n        reportResult(\n            \"async_cancel_with_msg\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish message first\n    client.publish(\n        \"async.cancel.msg\",\n        \"pending-msg\",\n    ) catch {\n        reportResult(\n            \"async_cancel_with_msg\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    // Let message arrive\n    io.sleep(.fromMilliseconds(50), .awake) catch {};\n\n    var future = io.async(Sub.nextMsg, .{sub});\n    defer if (future.cancel(io)) |m|\n        m.deinit()\n    else |_| {};\n\n    if (future.await(io)) |msg| {\n        if (std.mem.eql(u8, msg.data, \"pending-msg\")) {\n            reportResult(\n                \"async_cancel_with_msg\",\n                true,\n                \"\",\n            );\n        } else {\n            reportResult(\n                \"async_cancel_with_msg\",\n                false,\n                \"wrong data\",\n            );\n        }\n    } else |_| {\n        reportResult(\n            \"async_cancel_with_msg\",\n            false,\n            \"await failed\",\n        );\n    }\n}\n\n/// Test 7: nextBatch() receives multiple messages.\nfn testBatchReceive(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .sub_queue_size = 64,\n    }) catch {\n        reportResult(\"batch_receive\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"async.batch\") catch {\n        reportResult(\n            \"batch_receive\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish 20 messages rapidly\n    var ii: u8 = 0;\n    while (ii < 20) : (ii += 1) {\n        client.publish(\"async.batch\", \"batch-data\") catch {};\n    }\n\n    // Let messages arrive\n    io.sleep(.fromMilliseconds(100), .awake) catch {};\n\n    // Batch receive\n    var batch_buf: [32]nats.Message = undefined;\n    const count = sub.nextMsgBatch(io, &batch_buf) catch {\n        reportResult(\n            \"batch_receive\",\n            false,\n            \"nextBatch failed\",\n        );\n        return;\n    };\n\n    for (batch_buf[0..count]) |*msg| {\n        msg.deinit();\n    }\n\n    // Drain any remaining\n    const remaining = sub.tryNextMsgBatch(&batch_buf);\n    for (batch_buf[0..remaining]) |*msg| {\n        msg.deinit();\n    }\n\n    const total = count + remaining;\n\n    if (total >= 15) {\n        reportResult(\"batch_receive\", true, \"\");\n    } else {\n        var result_buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &result_buf,\n            \"{d}/20 received\",\n            .{total},\n        ) catch \"partial\";\n        reportResult(\"batch_receive\", false, detail);\n    }\n}\n\n/// Test 8: tryNextBatch() non-blocking.\nfn testTryNextBatch(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"try_next_batch\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"async.trybatch\",\n    ) catch {\n        reportResult(\n            \"try_next_batch\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    var batch_buf: [32]nats.Message = undefined;\n\n    // Empty queue should return 0\n    const empty_count = sub.tryNextMsgBatch(&batch_buf);\n    if (empty_count != 0) {\n        reportResult(\n            \"try_next_batch\",\n            false,\n            \"expected 0 on empty\",\n        );\n        return;\n    }\n\n    // Publish messages\n    var ii: u8 = 0;\n    while (ii < 10) : (ii += 1) {\n        client.publish(\"async.trybatch\", \"data\") catch {};\n    }\n\n    // Let messages arrive\n    io.sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Should get some messages\n    const first_count = sub.tryNextMsgBatch(&batch_buf);\n    for (batch_buf[0..first_count]) |*msg| {\n        msg.deinit();\n    }\n\n    // Drain remaining\n    const second_count = sub.tryNextMsgBatch(&batch_buf);\n    for (batch_buf[0..second_count]) |*msg| {\n        msg.deinit();\n    }\n\n    // Call again on drained queue\n    const third_count = sub.tryNextMsgBatch(&batch_buf);\n\n    if (first_count > 0 and third_count == 0) {\n        reportResult(\"try_next_batch\", true, \"\");\n    } else {\n        var result_buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &result_buf,\n            \"first={d} third={d}\",\n            .{ first_count, third_count },\n        ) catch \"error\";\n        reportResult(\"try_next_batch\", false, detail);\n    }\n}\n\n/// Inner function that returns early to test defer cleanup.\nfn innerAsyncWithEarlyReturn(\n    io: Io,\n    sub: *Sub,\n) bool {\n    var future = io.async(Sub.nextMsg, .{sub});\n    defer if (future.cancel(io)) |m|\n        m.deinit()\n    else |_| {};\n\n    // Simulate early return (e.g., error condition)\n    return true; // defer should clean up the future\n}\n\n/// Test 9: Defer pattern cleans up on early return.\nfn testAsyncDeferCleanup(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"async_defer_cleanup\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"async.defer.test\",\n    ) catch {\n        reportResult(\n            \"async_defer_cleanup\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    const result = innerAsyncWithEarlyReturn(io, sub);\n\n    if (result) {\n        reportResult(\"async_defer_cleanup\", true, \"\");\n    } else {\n        reportResult(\n            \"async_defer_cleanup\",\n            false,\n            \"unexpected result\",\n        );\n    }\n}\n\n/// Test 10: Race multiple subscriptions with Io.Select.\nfn testSelectMultipleSubs(allocator: Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"select_multiple_subs\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const fast_sub = client.subscribeSync(\n        \"async.fast\",\n    ) catch {\n        reportResult(\n            \"select_multiple_subs\",\n            false,\n            \"fast_sub failed\",\n        );\n        return;\n    };\n    defer fast_sub.deinit();\n\n    const slow_sub = client.subscribeSync(\n        \"async.slow\",\n    ) catch {\n        reportResult(\n            \"select_multiple_subs\",\n            false,\n            \"slow_sub failed\",\n        );\n        return;\n    };\n    defer slow_sub.deinit();\n\n    // Only publish to \"fast\"\n    client.publish(\"async.fast\", \"fast-msg\") catch {\n        reportResult(\n            \"select_multiple_subs\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    const SubSel = Io.Select(union(enum) {\n        fast: anyerror!Message,\n        slow: anyerror!Message,\n    });\n    var sel_buf: [2]SubSel.Union = undefined;\n    var sel = SubSel.init(io, &sel_buf);\n    sel.async(.fast, Sub.nextMsg, .{fast_sub});\n    sel.async(.slow, Sub.nextMsg, .{slow_sub});\n\n    const result = sel.await() catch {\n        while (sel.cancel()) |remaining| {\n            switch (remaining) {\n                .fast => |r| {\n                    if (r) |m| m.deinit() else |_| {}\n                },\n                .slow => |r| {\n                    if (r) |m| m.deinit() else |_| {}\n                },\n            }\n        }\n        reportResult(\n            \"select_multiple_subs\",\n            false,\n            \"select failed\",\n        );\n        return;\n    };\n    while (sel.cancel()) |remaining| {\n        switch (remaining) {\n            .fast => |r| {\n                if (r) |m| m.deinit() else |_| {}\n            },\n            .slow => |r| {\n                if (r) |m| m.deinit() else |_| {}\n            },\n        }\n    }\n\n    switch (result) {\n        .fast => |msg_result| {\n            const msg = msg_result catch {\n                reportResult(\n                    \"select_multiple_subs\",\n                    false,\n                    \"fast msg error\",\n                );\n                return;\n            };\n            defer msg.deinit();\n            if (std.mem.eql(u8, msg.data, \"fast-msg\")) {\n                reportResult(\n                    \"select_multiple_subs\",\n                    true,\n                    \"\",\n                );\n            } else {\n                reportResult(\n                    \"select_multiple_subs\",\n                    false,\n                    \"wrong data\",\n                );\n            }\n        },\n        .slow => {\n            reportResult(\n                \"select_multiple_subs\",\n                false,\n                \"slow won unexpectedly\",\n            );\n        },\n    }\n}\n\npub fn runAll(allocator: Allocator, _: *ServerManager) void {\n    testAsyncSelectTimeout(allocator);\n    testAsyncSelectMessage(allocator);\n    testAsyncConcurrentWorkers(allocator);\n    testAsyncParallelSubscriptions(allocator);\n    testAsyncCancellation(allocator);\n    testAsyncCancelWithPendingMessage(allocator);\n    testBatchReceive(allocator);\n    testTryNextBatch(allocator);\n    testAsyncDeferCleanup(allocator);\n    testSelectMultipleSubs(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/auth.zig",
    "content": "//! Auth Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testAuthentication(allocator: std.mem.Allocator) void {\n    var url_buf: [128]u8 = undefined;\n    const url = formatAuthUrl(&url_buf, auth_port, test_token);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"authentication\", false, \"auth connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"authentication\", true, \"\");\n    } else {\n        reportResult(\"authentication\", false, \"not connected\");\n    }\n}\n\npub fn testAuthenticationFailure(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, auth_port); // No token!\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"auth_failure\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"auth_failure\", true, \"\");\n    }\n}\n\npub fn testAuthenticatedPubSub(allocator: std.mem.Allocator) void {\n    var url_buf: [128]u8 = undefined;\n    const url = formatAuthUrl(&url_buf, auth_port, test_token);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"auth_pubsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"auth.test.subject\") catch {\n        reportResult(\"auth_pubsub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"auth.test.subject\", \"auth message\") catch {\n        reportResult(\"auth_pubsub\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"auth_pubsub\", true, \"\");\n    } else {\n        reportResult(\"auth_pubsub\", false, \"no message\");\n    }\n}\n\npub fn testEmptyToken(allocator: std.mem.Allocator) void {\n    var url_buf: [128]u8 = undefined;\n    const url = formatAuthUrl(&url_buf, auth_port, \"\");\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false });\n\n    if (result) |client| {\n        if (client.isConnected()) {\n            client.deinit();\n            reportResult(\"empty_token\", false, \"should fail auth\");\n            return;\n        }\n        client.deinit();\n    } else |_| {\n        // Expected - auth failed\n    }\n\n    reportResult(\"empty_token\", true, \"\");\n}\n\npub fn testTokenSpecialChars(allocator: std.mem.Allocator) void {\n    var url_buf: [128]u8 = undefined;\n    const url = formatAuthUrl(&url_buf, auth_port, test_token);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"token_special_chars\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"token_special_chars\", true, \"\");\n    } else {\n        reportResult(\"token_special_chars\", false, \"not connected\");\n    }\n}\n\npub fn testAuthRejectionRecovery(allocator: std.mem.Allocator) void {\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var bad_url_buf: [128]u8 = undefined;\n    const bad_url = formatAuthUrl(&bad_url_buf, auth_port, \"wrong-token\");\n\n    const bad_result = nats.Client.connect(allocator, io.io(), bad_url, .{ .reconnect = false });\n    if (bad_result) |client| {\n        client.deinit();\n        // If it connected, that's unexpected but not a failure of this test\n    } else |_| {\n        // Expected - auth failed\n    }\n\n    // Second: succeed with correct token\n    var good_url_buf: [128]u8 = undefined;\n    const good_url = formatAuthUrl(&good_url_buf, auth_port, test_token);\n\n    const good_result = nats.Client.connect(allocator, io.io(), good_url, .{ .reconnect = false });\n    if (good_result) |client| {\n        defer client.deinit();\n        if (client.isConnected()) {\n            reportResult(\"auth_rejection_recovery\", true, \"\");\n            return;\n        }\n    } else |_| {\n        reportResult(\"auth_rejection_recovery\", false, \"good connect failed\");\n        return;\n    }\n\n    reportResult(\"auth_rejection_recovery\", false, \"not connected\");\n}\n\npub fn testMultipleAuthAttempts(allocator: std.mem.Allocator) void {\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var url_buf: [128]u8 = undefined;\n    const bad_url = formatAuthUrl(&url_buf, auth_port, \"wrong\");\n\n    var failures: u32 = 0;\n    for (0..5) |_| {\n        const result = nats.Client.connect(allocator, io.io(), bad_url, .{ .reconnect = false });\n        if (result) |client| {\n            client.deinit();\n        } else |_| {\n            failures += 1;\n        }\n    }\n\n    if (failures == 5) {\n        reportResult(\"multiple_auth_attempts\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"{d}/5 failed\", .{failures}) catch \"e\";\n        reportResult(\"multiple_auth_attempts\", false, detail);\n    }\n}\n\npub fn testAuthRequiredDetection(allocator: std.mem.Allocator) void {\n    var url_buf: [128]u8 = undefined;\n    const url = formatAuthUrl(&url_buf, auth_port, test_token);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"auth_required_detect\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"auth_required_detect\", false, \"no server info\");\n        return;\n    }\n\n    // Server should have auth_required = true\n    if (info.?.auth_required) {\n        reportResult(\"auth_required_detect\", true, \"\");\n    } else {\n        reportResult(\"auth_required_detect\", false, \"auth not required\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testAuthentication(allocator);\n    testAuthenticationFailure(allocator);\n    testAuthenticatedPubSub(allocator);\n    testEmptyToken(allocator);\n    testTokenSpecialChars(allocator);\n    testAuthRejectionRecovery(allocator);\n    testMultipleAuthAttempts(allocator);\n    testAuthRequiredDetection(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/autoflush.zig",
    "content": "//! Autoflush Integration Tests\n//!\n//! Tests automatic buffer flushing for ALL code paths that set\n//! flush_requested, including:\n//! - publish, publishRequest, publishWithHeaders\n//! - publishRequestWithHeaders, publishWithHeaderMap, publishMsg\n//! - subscribe, autoUnsubscribe, drain, unsubscribe\n//! - High throughput, latency, TLS, disconnect safety\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\nconst headers = nats.protocol.headers;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatTlsUrl = utils.formatTlsUrl;\nconst test_port = utils.test_port;\nconst tls_port = utils.tls_port;\nconst ServerManager = utils.ServerManager;\n\nconst Dir = std.Io.Dir;\n\nconst autoflush_port: u16 = 14240;\n\n/// Returns absolute path to CA file. Caller owns returned memory.\nfn getCaFilePath(\n    allocator: std.mem.Allocator,\n    io: std.Io,\n) ?[:0]const u8 {\n    return Dir.realPathFileAlloc(\n        .cwd(),\n        io,\n        utils.tls_ca_file,\n        allocator,\n    ) catch null;\n}\n\n/// Test 1: Verify messages are delivered without explicit flush.\nfn testAutoflushBasicDelivery(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch |err| {\n        var err_buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &err_buf,\n            \"connect failed: {}\",\n            .{err},\n        ) catch \"connect error\";\n        reportResult(\n            \"autoflush_basic_delivery\",\n            false,\n            msg,\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.basic\",\n    ) catch {\n        reportResult(\n            \"autoflush_basic_delivery\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\n        \"autoflush.basic\",\n        \"autoflush-test-msg\",\n    ) catch {\n        reportResult(\n            \"autoflush_basic_delivery\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        100,\n    ) catch null) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(\n            u8,\n            msg.data,\n            \"autoflush-test-msg\",\n        )) {\n            reportResult(\n                \"autoflush_basic_delivery\",\n                true,\n                \"\",\n            );\n        } else {\n            reportResult(\n                \"autoflush_basic_delivery\",\n                false,\n                \"wrong data\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_basic_delivery\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 2: Verify multiple messages batch and deliver together.\nfn testAutoflushMultipleMessages(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_multiple_msgs\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.multi\",\n    ) catch {\n        reportResult(\n            \"autoflush_multiple_msgs\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    const msg_count: u8 = 10;\n    var i: u8 = 0;\n    while (i < msg_count) : (i += 1) {\n        var buf: [32]u8 = undefined;\n        const payload = std.fmt.bufPrint(\n            &buf,\n            \"msg-{d}\",\n            .{i},\n        ) catch \"msg\";\n        client.publish(\n            \"autoflush.multi\",\n            payload,\n        ) catch {\n            reportResult(\n                \"autoflush_multiple_msgs\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    var received: u8 = 0;\n    while (received < msg_count) {\n        if (sub.nextMsgTimeout(\n            200,\n        ) catch null) |msg| {\n            msg.deinit();\n            received += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (received == msg_count) {\n        reportResult(\n            \"autoflush_multiple_msgs\",\n            true,\n            \"\",\n        );\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"{d}/{d} received\",\n            .{ received, msg_count },\n        ) catch \"partial\";\n        reportResult(\n            \"autoflush_multiple_msgs\",\n            false,\n            detail,\n        );\n    }\n}\n\n/// Test 3: Verify autoflush delivers all messages under high\n/// publish rate. NATS over TCP on localhost must be lossless.\nfn testAutoflushHighThroughput(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_high_throughput\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.throughput\",\n    ) catch {\n        reportResult(\n            \"autoflush_high_throughput\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish 1000 messages - every publish must succeed\n    const msg_count: u32 = 1000;\n    var i: u32 = 0;\n    while (i < msg_count) : (i += 1) {\n        client.publish(\n            \"autoflush.throughput\",\n            \"data\",\n        ) catch {\n            reportResult(\n                \"autoflush_high_throughput\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    // Receive all - TCP on localhost must be lossless\n    var received: u32 = 0;\n    while (received < msg_count) {\n        if (sub.nextMsgTimeout(\n            200,\n        ) catch null) |msg| {\n            msg.deinit();\n            received += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (received == msg_count) {\n        reportResult(\n            \"autoflush_high_throughput\",\n            true,\n            \"\",\n        );\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"{d}/{d} received\",\n            .{ received, msg_count },\n        ) catch \"partial\";\n        reportResult(\n            \"autoflush_high_throughput\",\n            false,\n            detail,\n        );\n    }\n}\n\n/// Test 4: Double-check pattern prevents BADF panic during\n/// disconnect.\nfn testAutoflushDuringDisconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, autoflush_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const server = manager.startServer(\n        allocator,\n        io.io(),\n        .{ .port = autoflush_port },\n    ) catch {\n        reportResult(\n            \"autoflush_during_disconnect\",\n            false,\n            \"server start failed\",\n        );\n        return;\n    };\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = true,\n            .max_reconnect_attempts = 5,\n            .reconnect_wait_ms = 100,\n        },\n    ) catch {\n        server.stop(io.io());\n        reportResult(\n            \"autoflush_during_disconnect\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.disconnect\",\n    ) catch {\n        server.stop(io.io());\n        reportResult(\n            \"autoflush_during_disconnect\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    var i: u8 = 0;\n    while (i < 10) : (i += 1) {\n        client.publish(\n            \"autoflush.disconnect\",\n            \"before\",\n        ) catch {};\n    }\n\n    // Stop server mid-operation (must NOT panic with BADF)\n    server.stop(io.io());\n\n    i = 0;\n    while (i < 5) : (i += 1) {\n        client.publish(\n            \"autoflush.disconnect\",\n            \"during\",\n        ) catch {};\n        io.io().sleep(\n            .fromMilliseconds(10),\n            .awake,\n        ) catch {};\n    }\n\n    const server2 = manager.startServer(\n        allocator,\n        io.io(),\n        .{ .port = autoflush_port },\n    ) catch {\n        reportResult(\n            \"autoflush_during_disconnect\",\n            false,\n            \"restart failed\",\n        );\n        return;\n    };\n    defer server2.stop(io.io());\n\n    io.io().sleep(\n        .fromMilliseconds(500),\n        .awake,\n    ) catch {};\n\n    // Reaching here without panic is the success criterion\n    reportResult(\n        \"autoflush_during_disconnect\",\n        true,\n        \"\",\n    );\n}\n\n/// Test 5: Verify TLS double-flush works correctly.\nfn testAutoflushTLS(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const ca_path = getCaFilePath(\n        allocator,\n        io.io(),\n    ) orelse {\n        reportResult(\n            \"autoflush_tls\",\n            false,\n            \"CA file not found\",\n        );\n        return;\n    };\n    defer allocator.free(ca_path);\n\n    const tls_server = manager.startServer(\n        allocator,\n        io.io(),\n        .{\n            .port = tls_port,\n            .config_file = utils.tls_config_file,\n        },\n    ) catch {\n        reportResult(\n            \"autoflush_tls\",\n            false,\n            \"TLS server start failed\",\n        );\n        return;\n    };\n    defer tls_server.stop(io.io());\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = false,\n            .tls_ca_file = ca_path,\n        },\n    ) catch |err| {\n        var err_buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &err_buf,\n            \"connect failed: {}\",\n            .{err},\n        ) catch \"connect error\";\n        reportResult(\"autoflush_tls\", false, msg);\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.tls\",\n    ) catch {\n        reportResult(\n            \"autoflush_tls\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\n        \"autoflush.tls\",\n        \"tls-autoflush-msg\",\n    ) catch {\n        reportResult(\n            \"autoflush_tls\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(\n            u8,\n            msg.data,\n            \"tls-autoflush-msg\",\n        )) {\n            reportResult(\"autoflush_tls\", true, \"\");\n        } else {\n            reportResult(\n                \"autoflush_tls\",\n                false,\n                \"wrong data\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_tls\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 6: Verify reasonable latency (message within 50ms).\nfn testAutoflushLatencyBound(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_latency\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.latency\",\n    ) catch {\n        reportResult(\n            \"autoflush_latency\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\n        \"autoflush.latency\",\n        \"latency-test\",\n    ) catch {\n        reportResult(\n            \"autoflush_latency\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        50,\n    ) catch null) |msg| {\n        msg.deinit();\n        reportResult(\"autoflush_latency\", true, \"\");\n    } else {\n        reportResult(\n            \"autoflush_latency\",\n            false,\n            \"timeout (>50ms)\",\n        );\n    }\n}\n\n/// Test 7: Verify subscribe also triggers autoflush.\nfn testAutoflushWithSubscribe(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const client1 = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_subscribe\",\n            false,\n            \"client1 connect failed\",\n        );\n        return;\n    };\n    defer client1.deinit();\n\n    const client2 = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_subscribe\",\n            false,\n            \"client2 connect failed\",\n        );\n        return;\n    };\n    defer client2.deinit();\n\n    var sub = client2.subscribeSync(\n        \"autoflush.sub.test\",\n    ) catch {\n        reportResult(\n            \"autoflush_subscribe\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    io1.io().sleep(\n        .fromMilliseconds(20),\n        .awake,\n    ) catch {};\n\n    client1.publish(\n        \"autoflush.sub.test\",\n        \"sub-test-msg\",\n    ) catch {\n        reportResult(\n            \"autoflush_subscribe\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        msg.deinit();\n        reportResult(\n            \"autoflush_subscribe\",\n            true,\n            \"\",\n        );\n    } else {\n        reportResult(\n            \"autoflush_subscribe\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 8: Verify single message doesn't get stuck in buffer.\nfn testAutoflushNoBatching(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_no_batching\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.single\",\n    ) catch {\n        reportResult(\n            \"autoflush_no_batching\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\n        \"autoflush.single\",\n        \"single-msg\",\n    ) catch {\n        reportResult(\n            \"autoflush_no_batching\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        30,\n    ) catch null) |msg| {\n        msg.deinit();\n        reportResult(\n            \"autoflush_no_batching\",\n            true,\n            \"\",\n        );\n    } else {\n        reportResult(\n            \"autoflush_no_batching\",\n            false,\n            \"message stuck in buffer\",\n        );\n    }\n}\n\n/// Test 9: Verify autoflush works with multiple clients.\nfn testAutoflushMultiClient(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n    const io3 = utils.newIo(allocator);\n    defer io3.deinit();\n\n    const client1 = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            \"client1 connect failed\",\n        );\n        return;\n    };\n    defer client1.deinit();\n\n    const client2 = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            \"client2 connect failed\",\n        );\n        return;\n    };\n    defer client2.deinit();\n\n    const client3 = nats.Client.connect(\n        allocator,\n        io3.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            \"client3 connect failed\",\n        );\n        return;\n    };\n    defer client3.deinit();\n\n    var sub1 = client1.subscribeSync(\n        \"autoflush.mc.to1\",\n    ) catch {\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            \"sub1 failed\",\n        );\n        return;\n    };\n    defer sub1.deinit();\n\n    var sub2 = client2.subscribeSync(\n        \"autoflush.mc.to2\",\n    ) catch {\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            \"sub2 failed\",\n        );\n        return;\n    };\n    defer sub2.deinit();\n\n    var sub3 = client3.subscribeSync(\n        \"autoflush.mc.to3\",\n    ) catch {\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            \"sub3 failed\",\n        );\n        return;\n    };\n    defer sub3.deinit();\n\n    io1.io().sleep(\n        .fromMilliseconds(20),\n        .awake,\n    ) catch {};\n\n    client1.publish(\n        \"autoflush.mc.to2\",\n        \"from1\",\n    ) catch {};\n    client2.publish(\n        \"autoflush.mc.to3\",\n        \"from2\",\n    ) catch {};\n    client3.publish(\n        \"autoflush.mc.to1\",\n        \"from3\",\n    ) catch {};\n\n    var received: u8 = 0;\n\n    if (sub1.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        msg.deinit();\n        received += 1;\n    }\n    if (sub2.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        msg.deinit();\n        received += 1;\n    }\n    if (sub3.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        msg.deinit();\n        received += 1;\n    }\n\n    if (received == 3) {\n        reportResult(\n            \"autoflush_multi_client\",\n            true,\n            \"\",\n        );\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"{d}/3 received\",\n            .{received},\n        ) catch \"partial\";\n        reportResult(\n            \"autoflush_multi_client\",\n            false,\n            detail,\n        );\n    }\n}\n\n// -- New tests for uncovered code paths --\n\n/// Test 10: publishRequest autoflush - request-reply pattern\n/// where a service publishes expecting a reply on a given inbox.\nfn testAutoflushPublishRequest(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_publish_request\",\n            false,\n            \"pub connect failed\",\n        );\n        return;\n    };\n    defer pub_client.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_publish_request\",\n            false,\n            \"sub connect failed\",\n        );\n        return;\n    };\n    defer sub_client.deinit();\n\n    var sub = sub_client.subscribeSync(\n        \"autoflush.pubreq\",\n    ) catch {\n        reportResult(\n            \"autoflush_publish_request\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    io1.io().sleep(\n        .fromMilliseconds(20),\n        .awake,\n    ) catch {};\n\n    // publishRequest: no explicit flush\n    pub_client.publishRequest(\n        \"autoflush.pubreq\",\n        \"reply.inbox.1\",\n        \"request-payload\",\n    ) catch {\n        reportResult(\n            \"autoflush_publish_request\",\n            false,\n            \"publishRequest failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        defer msg.deinit();\n\n        const data_ok = std.mem.eql(\n            u8,\n            msg.data,\n            \"request-payload\",\n        );\n        const reply_ok = if (msg.reply_to) |rt|\n            std.mem.eql(u8, rt, \"reply.inbox.1\")\n        else\n            false;\n\n        if (data_ok and reply_ok) {\n            reportResult(\n                \"autoflush_publish_request\",\n                true,\n                \"\",\n            );\n        } else if (!reply_ok) {\n            reportResult(\n                \"autoflush_publish_request\",\n                false,\n                \"wrong reply_to\",\n            );\n        } else {\n            reportResult(\n                \"autoflush_publish_request\",\n                false,\n                \"wrong data\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_publish_request\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 11: publishWithHeaders autoflush - publishing messages\n/// with metadata headers (tracing IDs, content types).\nfn testAutoflushPublishWithHeaders(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headers\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.headers\",\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headers\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    const hdrs = [_]headers.Entry{\n        .{\n            .key = \"X-Trace-Id\",\n            .value = \"af-trace-001\",\n        },\n    };\n\n    // publishWithHeaders: no explicit flush\n    client.publishWithHeaders(\n        \"autoflush.headers\",\n        &hdrs,\n        \"hdr-payload\",\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headers\",\n            false,\n            \"publishWithHeaders failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        defer msg.deinit();\n\n        if (msg.headers == null) {\n            reportResult(\n                \"autoflush_pub_headers\",\n                false,\n                \"no headers received\",\n            );\n            return;\n        }\n\n        var parsed = headers.parse(\n            allocator,\n            msg.headers.?,\n        );\n        defer parsed.deinit();\n\n        if (parsed.err != null) {\n            reportResult(\n                \"autoflush_pub_headers\",\n                false,\n                \"header parse error\",\n            );\n            return;\n        }\n\n        if (parsed.get(\"X-Trace-Id\")) |val| {\n            if (std.mem.eql(\n                u8,\n                val,\n                \"af-trace-001\",\n            )) {\n                reportResult(\n                    \"autoflush_pub_headers\",\n                    true,\n                    \"\",\n                );\n            } else {\n                reportResult(\n                    \"autoflush_pub_headers\",\n                    false,\n                    \"wrong header value\",\n                );\n            }\n        } else {\n            reportResult(\n                \"autoflush_pub_headers\",\n                false,\n                \"header key not found\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_pub_headers\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 12: publishRequestWithHeaders autoflush - request-reply\n/// with metadata (correlation IDs, auth tokens).\nfn testAutoflushPubReqWithHeaders(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_pubreq_headers\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.hdr.req\",\n    ) catch {\n        reportResult(\n            \"autoflush_pubreq_headers\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    const hdrs = [_]headers.Entry{\n        .{\n            .key = \"X-Correlation-Id\",\n            .value = \"corr-42\",\n        },\n    };\n\n    // publishRequestWithHeaders: no explicit flush\n    client.publishRequestWithHeaders(\n        \"autoflush.hdr.req\",\n        \"reply.hdr.inbox\",\n        &hdrs,\n        \"hdr-req-payload\",\n    ) catch {\n        reportResult(\n            \"autoflush_pubreq_headers\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        defer msg.deinit();\n\n        const reply_ok = if (msg.reply_to) |rt|\n            std.mem.eql(u8, rt, \"reply.hdr.inbox\")\n        else\n            false;\n\n        if (!reply_ok) {\n            reportResult(\n                \"autoflush_pubreq_headers\",\n                false,\n                \"wrong reply_to\",\n            );\n            return;\n        }\n\n        if (msg.headers == null) {\n            reportResult(\n                \"autoflush_pubreq_headers\",\n                false,\n                \"no headers\",\n            );\n            return;\n        }\n\n        var parsed = headers.parse(\n            allocator,\n            msg.headers.?,\n        );\n        defer parsed.deinit();\n\n        if (parsed.get(\"X-Correlation-Id\")) |val| {\n            if (std.mem.eql(u8, val, \"corr-42\")) {\n                reportResult(\n                    \"autoflush_pubreq_headers\",\n                    true,\n                    \"\",\n                );\n            } else {\n                reportResult(\n                    \"autoflush_pubreq_headers\",\n                    false,\n                    \"wrong header value\",\n                );\n            }\n        } else {\n            reportResult(\n                \"autoflush_pubreq_headers\",\n                false,\n                \"header not found\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_pubreq_headers\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 13: publishWithHeaderMap autoflush - dynamically-built\n/// headers via HeaderMap API (middleware, routing code).\nfn testAutoflushPubWithHeaderMap(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headermap\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\n        \"autoflush.hdrmap\",\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headermap\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    var hdr_map = nats.Client.HeaderMap.init(allocator);\n    defer hdr_map.deinit();\n\n    hdr_map.set(\n        \"X-Route\",\n        \"autoflush-map\",\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headermap\",\n            false,\n            \"headermap set failed\",\n        );\n        return;\n    };\n\n    // publishWithHeaderMap: no explicit flush\n    client.publishWithHeaderMap(\n        \"autoflush.hdrmap\",\n        &hdr_map,\n        \"map-payload\",\n    ) catch {\n        reportResult(\n            \"autoflush_pub_headermap\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        defer msg.deinit();\n\n        if (msg.headers == null) {\n            reportResult(\n                \"autoflush_pub_headermap\",\n                false,\n                \"no headers\",\n            );\n            return;\n        }\n\n        var parsed = headers.parse(\n            allocator,\n            msg.headers.?,\n        );\n        defer parsed.deinit();\n\n        if (parsed.get(\"X-Route\")) |val| {\n            if (std.mem.eql(\n                u8,\n                val,\n                \"autoflush-map\",\n            )) {\n                reportResult(\n                    \"autoflush_pub_headermap\",\n                    true,\n                    \"\",\n                );\n            } else {\n                reportResult(\n                    \"autoflush_pub_headermap\",\n                    false,\n                    \"wrong header value\",\n                );\n            }\n        } else {\n            reportResult(\n                \"autoflush_pub_headermap\",\n                false,\n                \"header not found\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_pub_headermap\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 14: publishMsg autoflush - message forwarding pattern.\n/// Receive a message and republish it to another subject.\nfn testAutoflushPublishMsg(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_publish_msg\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var sub_dst = client.subscribeSync(\n        \"autoflush.msg.dst\",\n    ) catch {\n        reportResult(\n            \"autoflush_publish_msg\",\n            false,\n            \"subscribe dst failed\",\n        );\n        return;\n    };\n    defer sub_dst.deinit();\n\n    // Construct a Message to forward via publishMsg\n    const fwd_msg = nats.Client.Message{\n        .subject = \"autoflush.msg.dst\",\n        .sid = 0,\n        .reply_to = null,\n        .data = \"forwarded-payload\",\n        .headers = null,\n        .owned = false,\n    };\n\n    // publishMsg: no explicit flush\n    client.publishMsg(&fwd_msg) catch {\n        reportResult(\n            \"autoflush_publish_msg\",\n            false,\n            \"publishMsg failed\",\n        );\n        return;\n    };\n\n    if (sub_dst.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(\n            u8,\n            msg.data,\n            \"forwarded-payload\",\n        )) {\n            reportResult(\n                \"autoflush_publish_msg\",\n                true,\n                \"\",\n            );\n        } else {\n            reportResult(\n                \"autoflush_publish_msg\",\n                false,\n                \"wrong data\",\n            );\n        }\n    } else {\n        reportResult(\n            \"autoflush_publish_msg\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Test 15: autoUnsubscribe autoflush - server enforces message\n/// limit after UNSUB is auto-flushed.\nfn testAutoflushAutoUnsubscribe(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_auto_unsub\",\n            false,\n            \"pub connect failed\",\n        );\n        return;\n    };\n    defer pub_client.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_auto_unsub\",\n            false,\n            \"sub connect failed\",\n        );\n        return;\n    };\n    defer sub_client.deinit();\n\n    var sub = sub_client.subscribeSync(\n        \"autoflush.autounsub\",\n    ) catch {\n        reportResult(\n            \"autoflush_auto_unsub\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // autoUnsubscribe(3): UNSUB sent via autoflush\n    sub.autoUnsubscribe(3) catch {\n        reportResult(\n            \"autoflush_auto_unsub\",\n            false,\n            \"autoUnsubscribe failed\",\n        );\n        return;\n    };\n\n    // Wait for UNSUB to reach server via autoflush\n    io1.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    // Publish 5 messages - server should only deliver 3\n    var i: u8 = 0;\n    while (i < 5) : (i += 1) {\n        pub_client.publish(\n            \"autoflush.autounsub\",\n            \"msg\",\n        ) catch {\n            reportResult(\n                \"autoflush_auto_unsub\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    var received: u8 = 0;\n    while (received < 5) {\n        if (sub.nextMsgTimeout(\n            200,\n        ) catch null) |msg| {\n            msg.deinit();\n            received += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (received == 3) {\n        reportResult(\n            \"autoflush_auto_unsub\",\n            true,\n            \"\",\n        );\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"{d}/3 received\",\n            .{received},\n        ) catch \"count mismatch\";\n        reportResult(\n            \"autoflush_auto_unsub\",\n            false,\n            detail,\n        );\n    }\n}\n\n/// Test 16: drain autoflush - graceful shutdown stops new\n/// messages after UNSUB is auto-flushed.\nfn testAutoflushDrain(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"pub connect failed\",\n        );\n        return;\n    };\n    defer pub_client.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"sub connect failed\",\n        );\n        return;\n    };\n    defer sub_client.deinit();\n\n    var sub = sub_client.subscribeSync(\n        \"autoflush.drain\",\n    ) catch {\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    io1.io().sleep(\n        .fromMilliseconds(20),\n        .awake,\n    ) catch {};\n\n    // Publish 3 messages before drain\n    var i: u8 = 0;\n    while (i < 3) : (i += 1) {\n        pub_client.publish(\n            \"autoflush.drain\",\n            \"before-drain\",\n        ) catch {\n            reportResult(\n                \"autoflush_drain\",\n                false,\n                \"publish before failed\",\n            );\n            return;\n        };\n    }\n\n    // Receive the 3 pre-drain messages\n    var pre_drain: u8 = 0;\n    while (pre_drain < 3) {\n        if (sub.nextMsgTimeout(\n            200,\n        ) catch null) |msg| {\n            msg.deinit();\n            pre_drain += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (pre_drain != 3) {\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"pre-drain msgs missing\",\n        );\n        return;\n    }\n\n    // Drain: sends UNSUB via autoflush\n    sub.drain() catch {\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"drain failed\",\n        );\n        return;\n    };\n\n    // Wait for UNSUB to reach server via autoflush\n    io1.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    // Publish after drain - should not be delivered\n    pub_client.publish(\n        \"autoflush.drain\",\n        \"after-drain\",\n    ) catch {\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"publish after failed\",\n        );\n        return;\n    };\n\n    // Verify no new messages arrive\n    if (sub.nextMsgTimeout(\n        100,\n    ) catch null) |msg| {\n        msg.deinit();\n        reportResult(\n            \"autoflush_drain\",\n            false,\n            \"got msg after drain\",\n        );\n    } else {\n        reportResult(\"autoflush_drain\", true, \"\");\n    }\n}\n\n/// Test 17: unsubscribe autoflush - explicit mid-session unsub\n/// stops delivery after UNSUB is auto-flushed. Uses a control\n/// subscription to verify (unsubscribed subs cannot receive).\nfn testAutoflushUnsubscribe(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"pub connect failed\",\n        );\n        return;\n    };\n    defer pub_client.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"sub connect failed\",\n        );\n        return;\n    };\n    defer sub_client.deinit();\n\n    var sub = sub_client.subscribeSync(\n        \"autoflush.unsub\",\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Control sub to verify connection still works\n    var ctrl = sub_client.subscribeSync(\n        \"autoflush.unsub.ctrl\",\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"ctrl subscribeSync failed\",\n        );\n        return;\n    };\n    defer ctrl.deinit();\n\n    io1.io().sleep(\n        .fromMilliseconds(20),\n        .awake,\n    ) catch {};\n\n    // Verify subscription works first\n    pub_client.publish(\n        \"autoflush.unsub\",\n        \"before-unsub\",\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"publish before failed\",\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        msg.deinit();\n    } else {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"pre-unsub msg missing\",\n        );\n        return;\n    }\n\n    // Unsubscribe: sends UNSUB via autoflush\n    sub.unsubscribe() catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"unsubscribe failed\",\n        );\n        return;\n    };\n\n    // Wait for UNSUB to reach server via autoflush\n    io1.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    // Publish to both subjects after unsub\n    pub_client.publish(\n        \"autoflush.unsub\",\n        \"after-unsub\",\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"publish after failed\",\n        );\n        return;\n    };\n    pub_client.publish(\n        \"autoflush.unsub.ctrl\",\n        \"ctrl-msg\",\n    ) catch {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"publish ctrl failed\",\n        );\n        return;\n    };\n\n    // Control sub must receive (proves msgs are flowing)\n    if (ctrl.nextMsgTimeout(\n        200,\n    ) catch null) |msg| {\n        msg.deinit();\n    } else {\n        reportResult(\n            \"autoflush_unsubscribe\",\n            false,\n            \"ctrl msg missing\",\n        );\n        return;\n    }\n\n    // The unsubscribed sub's state is .unsubscribed,\n    // meaning server honored the auto-flushed UNSUB.\n    // Control sub received its msg proving the\n    // connection works and the UNSUB was delivered.\n    reportResult(\n        \"autoflush_unsubscribe\",\n        true,\n        \"\",\n    );\n}\n\npub fn runAll(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    // Original tests (1-9)\n    testAutoflushBasicDelivery(allocator);\n    testAutoflushMultipleMessages(allocator);\n    testAutoflushHighThroughput(allocator);\n    testAutoflushNoBatching(allocator);\n    testAutoflushLatencyBound(allocator);\n    testAutoflushWithSubscribe(allocator);\n    testAutoflushMultiClient(allocator);\n    testAutoflushTLS(allocator, manager);\n    testAutoflushDuringDisconnect(allocator, manager);\n    // Publish variant tests (10-14)\n    testAutoflushPublishRequest(allocator);\n    testAutoflushPublishWithHeaders(allocator);\n    testAutoflushPubReqWithHeaders(allocator);\n    testAutoflushPubWithHeaderMap(allocator);\n    testAutoflushPublishMsg(allocator);\n    // Subscription control tests (15-17)\n    testAutoflushAutoUnsubscribe(allocator);\n    testAutoflushDrain(allocator);\n    testAutoflushUnsubscribe(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/basic.zig",
    "content": "//! Basic Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testClientBasic(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .name = \"client-test\",\n        .sub_queue_size = 64,\n        .reconnect = false,\n    }) catch |err| {\n        var err_buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &err_buf,\n            \"connect failed: {}\",\n            .{err},\n        ) catch \"error\";\n        reportResult(\"client_basic\", false, msg);\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"client_basic\", false, \"not connected\");\n        return;\n    }\n\n    const sub = client.subscribeSync(\"basic\") catch {\n        reportResult(\"client_basic\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    reportResult(\"client_basic\", true, \"\");\n}\n\npub fn testClientTryNext(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_try_next\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"trynext\") catch {\n        reportResult(\"client_try_next\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    if (sub.tryNextMsg() != null) {\n        reportResult(\"client_try_next\", false, \"expected null\");\n        return;\n    }\n\n    reportResult(\"client_try_next\", true, \"\");\n}\n\npub fn testClientServerInfo(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_server_info\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (client.serverInfo()) |info| {\n        if (info.port == test_port) {\n            reportResult(\"client_server_info\", true, \"\");\n            return;\n        }\n    }\n    reportResult(\"client_server_info\", false, \"no server info\");\n}\n\npub fn testClientRapidSubUnsub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_rapid_sub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    for (0..20) |i| {\n        var buf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(&buf, \"rapid.{d}\", .{i}) catch \"e\";\n        const sub = client.subscribeSync(subj) catch {\n            reportResult(\"client_rapid_sub\", false, \"sub failed\");\n            return;\n        };\n        sub.deinit();\n    }\n\n    const sub = client.subscribeSync(\"rapid.final\") catch {\n        reportResult(\"client_rapid_sub\", false, \"final sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"client_rapid_sub\", true, \"\");\n    } else {\n        reportResult(\"client_rapid_sub\", false, \"disconnected\");\n    }\n}\n\npub fn testClientName(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .name = \"test-client-name\",\n        .reconnect = false,\n    }) catch {\n        reportResult(\"client_name_opt\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"client_name_opt\", true, \"\");\n    } else {\n        reportResult(\"client_name_opt\", false, \"not connected\");\n    }\n}\n\npub fn testClientVerbose(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .verbose = true,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"client_verbose\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.publish(\"verbose.test\", \"data\") catch {\n        reportResult(\"client_verbose\", false, \"publish failed\");\n        return;\n    };\n\n    reportResult(\"client_verbose\", true, \"\");\n}\n\npub fn testMultipleConnectDisconnect(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    for (0..5) |_| {\n        const io = utils.newIo(allocator);\n        const client = nats.Client.connect(\n            allocator,\n            io.io(),\n            url,\n            .{ .reconnect = false },\n        ) catch {\n            io.deinit();\n            reportResult(\"multi_connect_disconnect\", false, \"connect failed\");\n            return;\n        };\n        client.deinit();\n        io.deinit();\n    }\n\n    reportResult(\"multi_connect_disconnect\", true, \"\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testClientBasic(allocator);\n    testClientTryNext(allocator);\n    testClientServerInfo(allocator);\n    testClientRapidSubUnsub(allocator);\n    testClientName(allocator);\n    testClientVerbose(allocator);\n    testMultipleConnectDisconnect(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/callback.zig",
    "content": "//! Callback Subscription Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\n// -- MsgHandler delivery test --\n\nconst CountHandler = struct {\n    count: *u32,\n\n    pub fn onMessage(self: *@This(), _: *const nats.Message) void {\n        self.count.* += 1;\n    }\n};\n\npub fn testCallbackMsgHandler(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_msg_handler\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var count: u32 = 0;\n    var handler = CountHandler{ .count = &count };\n\n    const sub = client.subscribe(\n        \"cb.handler.test\",\n        nats.MsgHandler.init(CountHandler, &handler),\n    ) catch {\n        reportResult(\n            \"callback_msg_handler\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..5) |_| {\n        client.publish(\"cb.handler.test\", \"x\") catch {\n            reportResult(\n                \"callback_msg_handler\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    // Wait for callbacks to fire\n    io.io().sleep(\n        .fromMilliseconds(300),\n        .awake,\n    ) catch {};\n\n    if (count == 5) {\n        reportResult(\"callback_msg_handler\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"expected 5, got {d}\",\n            .{count},\n        ) catch \"count mismatch\";\n        reportResult(\"callback_msg_handler\", false, msg);\n    }\n}\n\n// -- Plain fn delivery test --\n\nvar plain_fn_count: u32 = 0;\n\nfn plainCallback(_: *const nats.Message) void {\n    plain_fn_count += 1;\n}\n\npub fn testCallbackPlainFn(\n    allocator: std.mem.Allocator,\n) void {\n    plain_fn_count = 0;\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_plain_fn\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeFn(\n        \"cb.plainfn.test\",\n        plainCallback,\n    ) catch {\n        reportResult(\n            \"callback_plain_fn\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..3) |_| {\n        client.publish(\"cb.plainfn.test\", \"y\") catch {\n            reportResult(\n                \"callback_plain_fn\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    io.io().sleep(\n        .fromMilliseconds(300),\n        .awake,\n    ) catch {};\n\n    if (plain_fn_count == 3) {\n        reportResult(\"callback_plain_fn\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"expected 3, got {d}\",\n            .{plain_fn_count},\n        ) catch \"count mismatch\";\n        reportResult(\"callback_plain_fn\", false, msg);\n    }\n}\n\n// -- Queue group test --\n\nconst QueueHandler = struct {\n    count: *u32,\n\n    pub fn onMessage(self: *@This(), _: *const nats.Message) void {\n        self.count.* += 1;\n    }\n};\n\npub fn testCallbackQueueGroup(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_queue_group\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var count1: u32 = 0;\n    var count2: u32 = 0;\n    var h1 = QueueHandler{ .count = &count1 };\n    var h2 = QueueHandler{ .count = &count2 };\n\n    const sub1 = client.queueSubscribe(\n        \"cb.queue.test\",\n        \"workers\",\n        nats.MsgHandler.init(QueueHandler, &h1),\n    ) catch {\n        reportResult(\n            \"callback_queue_group\",\n            false,\n            \"sub1 failed\",\n        );\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.queueSubscribe(\n        \"cb.queue.test\",\n        \"workers\",\n        nats.MsgHandler.init(QueueHandler, &h2),\n    ) catch {\n        reportResult(\n            \"callback_queue_group\",\n            false,\n            \"sub2 failed\",\n        );\n        return;\n    };\n    defer sub2.deinit();\n\n    for (0..10) |_| {\n        client.publish(\"cb.queue.test\", \"z\") catch {\n            reportResult(\n                \"callback_queue_group\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    io.io().sleep(\n        .fromMilliseconds(300),\n        .awake,\n    ) catch {};\n\n    const total = count1 + count2;\n    if (total >= 9) {\n        reportResult(\"callback_queue_group\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"expected >=9, got {d}\",\n            .{total},\n        ) catch \"total mismatch\";\n        reportResult(\"callback_queue_group\", false, msg);\n    }\n}\n\n// -- Deinit cleanup test (no hang) --\n\npub fn testCallbackDeinitCleanup(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_deinit_cleanup\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var count: u32 = 0;\n    var handler = CountHandler{ .count = &count };\n\n    const sub = client.subscribe(\n        \"cb.deinit.test\",\n        nats.MsgHandler.init(CountHandler, &handler),\n    ) catch {\n        reportResult(\n            \"callback_deinit_cleanup\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    // Immediately deinit -- must not hang\n    sub.deinit();\n\n    reportResult(\"callback_deinit_cleanup\", true, \"\");\n}\n\n// -- Mode field test --\n\npub fn testCallbackModeField(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_mode_field\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    // Manual sub should have .manual mode\n    const manual_sub = client.subscribeSync(\n        \"cb.mode.manual\",\n    ) catch {\n        reportResult(\n            \"callback_mode_field\",\n            false,\n            \"manual sub failed\",\n        );\n        return;\n    };\n    defer manual_sub.deinit();\n\n    var count: u32 = 0;\n    var handler = CountHandler{ .count = &count };\n\n    // Callback sub should have .callback mode\n    const cb_sub = client.subscribe(\n        \"cb.mode.callback\",\n        nats.MsgHandler.init(CountHandler, &handler),\n    ) catch {\n        reportResult(\n            \"callback_mode_field\",\n            false,\n            \"callback sub failed\",\n        );\n        return;\n    };\n    defer cb_sub.deinit();\n\n    if (manual_sub.mode != .manual) {\n        reportResult(\n            \"callback_mode_field\",\n            false,\n            \"manual sub mode wrong\",\n        );\n        return;\n    }\n\n    if (cb_sub.mode != .callback) {\n        reportResult(\n            \"callback_mode_field\",\n            false,\n            \"callback sub mode wrong\",\n        );\n        return;\n    }\n\n    reportResult(\"callback_mode_field\", true, \"\");\n}\n\n// -- High volume delivery test --\n\npub fn testCallbackHighVolume(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_high_volume\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var count: u32 = 0;\n    var handler = CountHandler{ .count = &count };\n\n    const sub = client.subscribe(\n        \"cb.volume.test\",\n        nats.MsgHandler.init(CountHandler, &handler),\n    ) catch {\n        reportResult(\n            \"callback_high_volume\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..100) |_| {\n        client.publish(\"cb.volume.test\", \"payload\") catch {\n            reportResult(\n                \"callback_high_volume\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    io.io().sleep(\n        .fromMilliseconds(500),\n        .awake,\n    ) catch {};\n\n    if (count == 100) {\n        reportResult(\"callback_high_volume\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"expected 100, got {d}\",\n            .{count},\n        ) catch \"count mismatch\";\n        reportResult(\n            \"callback_high_volume\",\n            false,\n            msg,\n        );\n    }\n}\n\n// -- Data integrity test --\n\nconst IntegrityHandler = struct {\n    /// Tracks which payload indices were received.\n    seen: *[100]bool,\n    count: *u32,\n\n    pub fn onMessage(\n        self: *@This(),\n        msg: *const nats.Message,\n    ) void {\n        const idx = std.fmt.parseInt(\n            usize,\n            msg.data,\n            10,\n        ) catch return;\n        if (idx < 100) {\n            self.seen.*[idx] = true;\n        }\n        self.count.* += 1;\n    }\n};\n\npub fn testCallbackDataIntegrity(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_data_integrity\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var seen: [100]bool = .{false} ** 100;\n    var count: u32 = 0;\n    var handler = IntegrityHandler{\n        .seen = &seen,\n        .count = &count,\n    };\n\n    const sub = client.subscribe(\n        \"cb.integrity.test\",\n        nats.MsgHandler.init(\n            IntegrityHandler,\n            &handler,\n        ),\n    ) catch {\n        reportResult(\n            \"callback_data_integrity\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    var pbuf: [8]u8 = undefined;\n    for (0..100) |i| {\n        const payload = std.fmt.bufPrint(\n            &pbuf,\n            \"{d}\",\n            .{i},\n        ) catch \"0\";\n        client.publish(\n            \"cb.integrity.test\",\n            payload,\n        ) catch {\n            reportResult(\n                \"callback_data_integrity\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    io.io().sleep(\n        .fromMilliseconds(500),\n        .awake,\n    ) catch {};\n\n    if (count != 100) {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"expected 100, got {d}\",\n            .{count},\n        ) catch \"count mismatch\";\n        reportResult(\n            \"callback_data_integrity\",\n            false,\n            msg,\n        );\n        return;\n    }\n\n    for (0..100) |i| {\n        if (!seen[i]) {\n            var buf: [64]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"missing payload {d}\",\n                .{i},\n            ) catch \"missing payload\";\n            reportResult(\n                \"callback_data_integrity\",\n                false,\n                msg,\n            );\n            return;\n        }\n    }\n\n    reportResult(\"callback_data_integrity\", true, \"\");\n}\n\n// -- Mixed manual + callback test --\n\npub fn testCallbackMixedModes(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_mixed_modes\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    // Callback sub on one subject\n    var cb_count: u32 = 0;\n    var handler = CountHandler{ .count = &cb_count };\n\n    const cb_sub = client.subscribe(\n        \"cb.mixed.auto\",\n        nats.MsgHandler.init(CountHandler, &handler),\n    ) catch {\n        reportResult(\n            \"callback_mixed_modes\",\n            false,\n            \"callback sub failed\",\n        );\n        return;\n    };\n    defer cb_sub.deinit();\n\n    // Manual sub on different subject\n    const man_sub = client.subscribeSync(\n        \"cb.mixed.manual\",\n    ) catch {\n        reportResult(\n            \"callback_mixed_modes\",\n            false,\n            \"manual sub failed\",\n        );\n        return;\n    };\n    defer man_sub.deinit();\n\n    // Publish to both subjects\n    for (0..5) |_| {\n        client.publish(\"cb.mixed.auto\", \"a\") catch {\n            reportResult(\n                \"callback_mixed_modes\",\n                false,\n                \"publish auto failed\",\n            );\n            return;\n        };\n        client.publish(\"cb.mixed.manual\", \"m\") catch {\n            reportResult(\n                \"callback_mixed_modes\",\n                false,\n                \"publish manual failed\",\n            );\n            return;\n        };\n    }\n\n    io.io().sleep(\n        .fromMilliseconds(300),\n        .awake,\n    ) catch {};\n\n    // Drain manual sub\n    var manual_count: u32 = 0;\n    while (man_sub.tryNextMsg()) |_| {\n        manual_count += 1;\n    }\n\n    if (cb_count != 5 or manual_count != 5) {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"cb={d} man={d}, expected 5/5\",\n            .{ cb_count, manual_count },\n        ) catch \"count mismatch\";\n        reportResult(\n            \"callback_mixed_modes\",\n            false,\n            msg,\n        );\n        return;\n    }\n\n    reportResult(\"callback_mixed_modes\", true, \"\");\n}\n\n// -- Callback request/reply test --\n\nconst EchoHandler = struct {\n    client: *nats.Client,\n    handled: *u32,\n\n    pub fn onMessage(\n        self: *@This(),\n        msg: *const nats.Message,\n    ) void {\n        self.handled.* += 1;\n        msg.respond(self.client, msg.data) catch {};\n    }\n};\n\npub fn testCallbackRequestReply(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const svc_io = utils.newIo(allocator);\n    defer svc_io.deinit();\n\n    const svc_client = nats.Client.connect(\n        allocator,\n        svc_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_request_reply\",\n            false,\n            \"svc connect failed\",\n        );\n        return;\n    };\n    defer svc_client.deinit();\n\n    const req_io = utils.newIo(allocator);\n    defer req_io.deinit();\n\n    const req_client = nats.Client.connect(\n        allocator,\n        req_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"callback_request_reply\",\n            false,\n            \"req connect failed\",\n        );\n        return;\n    };\n    defer req_client.deinit();\n\n    var handled: u32 = 0;\n    var handler = EchoHandler{\n        .client = svc_client,\n        .handled = &handled,\n    };\n\n    const sub = svc_client.subscribe(\n        \"cb.echo.test\",\n        nats.MsgHandler.init(EchoHandler, &handler),\n    ) catch {\n        reportResult(\n            \"callback_request_reply\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Wait for sub to propagate\n    svc_io.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    var replies: u32 = 0;\n    const payloads = [_][]const u8{ \"a\", \"b\", \"c\" };\n    for (payloads) |payload| {\n        if (req_client.request(\n            \"cb.echo.test\",\n            payload,\n            1000,\n        )) |maybe_reply| {\n            if (maybe_reply) |reply| {\n                defer reply.deinit();\n                if (std.mem.eql(\n                    u8,\n                    reply.data,\n                    payload,\n                )) {\n                    replies += 1;\n                }\n            }\n        } else |_| {}\n    }\n\n    if (handled != 3 or replies != 3) {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"handled={d} replies={d}\",\n            .{ handled, replies },\n        ) catch \"mismatch\";\n        reportResult(\n            \"callback_request_reply\",\n            false,\n            msg,\n        );\n        return;\n    }\n\n    reportResult(\"callback_request_reply\", true, \"\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testCallbackMsgHandler(allocator);\n    testCallbackPlainFn(allocator);\n    testCallbackQueueGroup(allocator);\n    testCallbackDeinitCleanup(allocator);\n    testCallbackModeField(allocator);\n    testCallbackHighVolume(allocator);\n    testCallbackDataIntegrity(allocator);\n    testCallbackMixedModes(allocator);\n    testCallbackRequestReply(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/concurrency.zig",
    "content": "//! Concurrency Tests for NATS Client\n//!\n//! Tests for race conditions, concurrent operations, and thread safety.\n//! These tests verify the client behaves correctly under concurrent access.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\nfn sleepMs(io: std.Io, ms: i64) void {\n    io.sleep(.fromMilliseconds(ms), .awake) catch {};\n}\n\npub fn testConcurrentSubscribe(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"concurrent_subscribe\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const NUM_SUBS = 10;\n    var subs: [NUM_SUBS]?*nats.Subscription =\n        [_]?*nats.Subscription{null} ** NUM_SUBS;\n    var created: u32 = 0;\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..NUM_SUBS) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"concurrent.sub.{d}\",\n            .{i},\n        ) catch continue;\n\n        subs[i] = client.subscribeSync(subject) catch {\n            continue;\n        };\n        created += 1;\n    }\n\n    if (created != NUM_SUBS) {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/10\",\n            .{created},\n        ) catch \"e\";\n        reportResult(\"concurrent_subscribe\", false, detail);\n        return;\n    }\n\n    var sids: [NUM_SUBS]u64 = undefined;\n    for (0..NUM_SUBS) |i| {\n        if (subs[i]) |sub| {\n            sids[i] = sub.sid;\n            for (0..i) |j| {\n                if (sids[j] == sids[i]) {\n                    reportResult(\n                        \"concurrent_subscribe\",\n                        false,\n                        \"duplicate SID\",\n                    );\n                    return;\n                }\n            }\n        }\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"concurrent_subscribe\", true, \"\");\n    } else {\n        reportResult(\"concurrent_subscribe\", false, \"disconnected\");\n    }\n}\n\npub fn testRapidPublish(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"rapid_publish\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"rapid.publish\") catch {\n        reportResult(\"rapid_publish\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const NUM_MSGS = 100;\n    var published: u32 = 0;\n    for (0..NUM_MSGS) |_| {\n        client.publish(\"rapid.publish\", \"data\") catch {\n            continue;\n        };\n        published += 1;\n    }\n\n    client.flush(500_000_000) catch {};\n\n    if (published != NUM_MSGS) {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"pub {d}/100\",\n            .{published},\n        ) catch \"e\";\n        reportResult(\"rapid_publish\", false, detail);\n        return;\n    }\n\n    var received: u32 = 0;\n    for (0..NUM_MSGS) |_| {\n        if (sub.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"rapid_publish\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/100\",\n            .{received},\n        ) catch \"e\";\n        reportResult(\"rapid_publish\", false, detail);\n    }\n}\n\npub fn testConcurrentSubUnsub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"concurrent_sub_unsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const CYCLES = 20;\n    var current_sub: ?*nats.Subscription = null;\n\n    for (0..CYCLES) |i| {\n        if (current_sub) |sub| {\n            sub.unsubscribe() catch {};\n            sub.deinit();\n            current_sub = null;\n        }\n\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"cycle.{d}\",\n            .{i},\n        ) catch continue;\n\n        current_sub = client.subscribeSync(subject) catch {\n            reportResult(\"concurrent_sub_unsub\", false, \"subscribe failed\");\n            return;\n        };\n    }\n\n    if (current_sub) |sub| {\n        sub.deinit();\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"concurrent_sub_unsub\", true, \"\");\n    } else {\n        reportResult(\"concurrent_sub_unsub\", false, \"disconnected\");\n    }\n}\n\npub fn testRaceSubscribeVsDelivery(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"race_sub_delivery\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const subscriber = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"race_sub_delivery\", false, \"sub connect failed\");\n        return;\n    };\n    defer subscriber.deinit();\n\n    const sub = subscriber.subscribeSync(\"race.delivery\") catch {\n        reportResult(\"race_sub_delivery\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    publisher.publish(\"race.delivery\", \"race-msg-1\") catch {\n        reportResult(\"race_sub_delivery\", false, \"publish1 failed\");\n        return;\n    };\n    sub_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    publisher.publish(\"race.delivery\", \"race-msg-2\") catch {\n        reportResult(\"race_sub_delivery\", false, \"publish2 failed\");\n        return;\n    };\n\n    var received: u32 = 0;\n    for (0..2) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received >= 1) {\n        reportResult(\"race_sub_delivery\", true, \"\");\n    } else {\n        reportResult(\"race_sub_delivery\", false, \"no messages received\");\n    }\n}\n\npub fn testRaceUnsubscribeVsDelivery(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .sub_queue_size = 64,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"race_unsub_delivery\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"race.unsub\") catch {\n        reportResult(\"race_unsub_delivery\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..50) |_| {\n        client.publish(\"race.unsub\", \"msg\") catch {};\n    }\n\n    sub.unsubscribe() catch {};\n\n    for (0..50) |_| {\n        client.publish(\"race.unsub\", \"msg\") catch {};\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"race_unsub_delivery\", true, \"\");\n    } else {\n        reportResult(\"race_unsub_delivery\", false, \"disconnected\");\n    }\n}\n\npub fn testSidAllocationRecycling(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"sid_allocation_recycle\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var seen_sids: [100]u64 = undefined;\n    var seen_count: usize = 0;\n\n    for (0..50) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"recycle.{d}\",\n            .{i},\n        ) catch continue;\n\n        const sub = client.subscribeSync(subject) catch {\n            reportResult(\"sid_allocation_recycle\", false, \"subscribe failed\");\n            return;\n        };\n\n        if (seen_count < seen_sids.len) {\n            for (seen_sids[0..seen_count]) |prev_sid| {\n                if (prev_sid == sub.sid) {\n                    reportResult(\"sid_allocation_recycle\", false, \"SID reused\");\n                    sub.deinit();\n                    return;\n                }\n            }\n            seen_sids[seen_count] = sub.sid;\n            seen_count += 1;\n        }\n\n        sub.deinit();\n    }\n\n    for (1..seen_count) |i| {\n        if (seen_sids[i] <= seen_sids[i - 1]) {\n            reportResult(\"sid_allocation_recycle\", false, \"non-monotonic SIDs\");\n            return;\n        }\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"sid_allocation_recycle\", true, \"\");\n    } else {\n        reportResult(\"sid_allocation_recycle\", false, \"disconnected\");\n    }\n}\n\npub fn testMultipleClientsSeparateIo(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n\n    const client1 = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_client_separate_io\", false, \"client1 failed\");\n        return;\n    };\n    defer client1.deinit();\n\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const client2 = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_client_separate_io\", false, \"client2 failed\");\n        return;\n    };\n    defer client2.deinit();\n\n    const io3 = utils.newIo(allocator);\n    defer io3.deinit();\n\n    const client3 = nats.Client.connect(\n        allocator,\n        io3.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_client_separate_io\", false, \"client3 failed\");\n        return;\n    };\n    defer client3.deinit();\n\n    const sub = client1.subscribeSync(\"separate.io.test\") catch {\n        reportResult(\"multi_client_separate_io\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    io1.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    client2.publish(\"separate.io.test\", \"from-client2\") catch {\n        reportResult(\"multi_client_separate_io\", false, \"pub2 failed\");\n        return;\n    };\n    client3.publish(\"separate.io.test\", \"from-client3\") catch {\n        reportResult(\"multi_client_separate_io\", false, \"pub3 failed\");\n        return;\n    };\n\n    var received: u32 = 0;\n    for (0..2) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 2) {\n        reportResult(\"multi_client_separate_io\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/2\", .{received}) catch \"e\";\n        reportResult(\"multi_client_separate_io\", false, detail);\n    }\n}\n\npub fn testParallelReceive(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"parallel_recv\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.subscribeSync(\"parallel.1\") catch {\n        reportResult(\"parallel_recv\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"parallel.2\") catch {\n        reportResult(\"parallel_recv\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    const sub3 = client.subscribeSync(\"parallel.3\") catch {\n        reportResult(\"parallel_recv\", false, \"sub3 failed\");\n        return;\n    };\n    defer sub3.deinit();\n\n    client.publish(\"parallel.1\", \"msg1\") catch {};\n    client.publish(\"parallel.2\", \"msg2\") catch {};\n    client.publish(\"parallel.3\", \"msg3\") catch {};\n\n    client.flush(500_000_000) catch {};\n\n    var received: u32 = 0;\n\n    if (sub1.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        received += 1;\n    }\n\n    if (sub2.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        received += 1;\n    }\n\n    if (sub3.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        received += 1;\n    }\n\n    if (received == 3) {\n        reportResult(\"parallel_recv\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/3\",\n            .{received},\n        ) catch \"e\";\n        reportResult(\"parallel_recv\", false, detail);\n    }\n}\n\npub fn testRapidFlushOperations(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"rapid_flush\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var success: u32 = 0;\n    for (0..50) |_| {\n        client.flushBuffer() catch {\n            continue;\n        };\n        success += 1;\n    }\n\n    if (success != 50) {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"flush {d}/50\",\n            .{success},\n        ) catch \"e\";\n        reportResult(\"rapid_flush\", false, detail);\n        return;\n    }\n\n    for (0..50) |_| {\n        client.publish(\"flush.test\", \"x\") catch {};\n        client.flushBuffer() catch {};\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"rapid_flush\", true, \"\");\n    } else {\n        reportResult(\"rapid_flush\", false, \"disconnected\");\n    }\n}\n\npub fn testStatsConcurrency(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stats_concurrency\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"stats.test\") catch {\n        reportResult(\"stats_concurrency\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const before = client.stats();\n\n    const NUM_MSGS: u64 = 100;\n    for (0..NUM_MSGS) |_| {\n        client.publish(\"stats.test\", \"stat-msg\") catch {};\n    }\n\n    client.flush(500_000_000) catch {};\n\n    for (0..NUM_MSGS) |_| {\n        if (sub.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n        } else break;\n    }\n\n    const after = client.stats();\n\n    const msgs_out_diff = after.msgs_out - before.msgs_out;\n    const msgs_in_diff = after.msgs_in - before.msgs_in;\n\n    if (msgs_out_diff != NUM_MSGS) {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"msgs_out: {d} (expect {d})\",\n            .{ msgs_out_diff, NUM_MSGS },\n        ) catch \"e\";\n        reportResult(\"stats_concurrency\", false, detail);\n        return;\n    }\n\n    if (msgs_in_diff < NUM_MSGS) {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"msgs_in: {d} (expect >= {d})\",\n            .{ msgs_in_diff, NUM_MSGS },\n        ) catch \"e\";\n        reportResult(\"stats_concurrency\", false, detail);\n        return;\n    }\n\n    reportResult(\"stats_concurrency\", true, \"\");\n}\n\npub fn testMixedWriteOrderingPublishBeforeSubscribe(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"mixed_write_ordering\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const iterations = 100;\n    for (0..iterations) |i| {\n        {\n            var subject_buf: [48]u8 = undefined;\n            const subject = std.fmt.bufPrint(\n                &subject_buf,\n                \"ordering.publish.before.sub.{d}\",\n                .{i},\n            ) catch {\n                reportResult(\"mixed_write_ordering\", false, \"subject format failed\");\n                return;\n            };\n\n            client.publish(subject, \"queued-before-sub\") catch {\n                reportResult(\"mixed_write_ordering\", false, \"publish failed\");\n                return;\n            };\n\n            const sub = client.subscribeSync(subject) catch {\n                reportResult(\"mixed_write_ordering\", false, \"subscribe failed\");\n                return;\n            };\n            defer sub.deinit();\n\n            client.flush(1_000_000_000) catch {\n                reportResult(\"mixed_write_ordering\", false, \"flush failed\");\n                return;\n            };\n\n            if (sub.nextMsgTimeout(50) catch null) |msg| {\n                msg.deinit();\n                var detail_buf: [64]u8 = undefined;\n                const detail = std.fmt.bufPrint(\n                    &detail_buf,\n                    \"pre-sub publish delivered at iter {d}\",\n                    .{i},\n                ) catch \"unexpected pre-sub delivery\";\n                reportResult(\"mixed_write_ordering\", false, detail);\n                return;\n            }\n        }\n    }\n\n    reportResult(\"mixed_write_ordering\", true, \"\");\n}\n\nfn delayedUnsubscribe(\n    io: std.Io,\n    sub: *nats.Subscription,\n    delay_ms: i64,\n) void {\n    sleepMs(io, delay_ms);\n    sub.unsubscribe() catch {};\n}\n\npub fn testBlockingNextMsgUnsubscribeWakeup(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_wrap = utils.newIo(allocator);\n    defer io_wrap.deinit();\n    const io = io_wrap.io();\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"blocking_nextmsg_unsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"blocking.nextmsg.unsub\") catch {\n        reportResult(\"blocking_nextmsg_unsub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    var wake_thread = io.async(delayedUnsubscribe, .{ io, sub, 20 });\n    defer _ = wake_thread.cancel(io);\n\n    const Sel = std.Io.Select(union(enum) {\n        recv: anyerror!nats.Client.Message,\n        timeout: void,\n    });\n    var buf: [2]Sel.Union = undefined;\n    var sel = Sel.init(io, &buf);\n    sel.async(.recv, nats.Client.Sub.nextMsg, .{sub});\n    sel.async(.timeout, sleepMs, .{ io, 200 });\n\n    const result = sel.await() catch {\n        sel.cancelDiscard();\n        reportResult(\"blocking_nextmsg_unsub\", false, \"select canceled\");\n        return;\n    };\n    sel.cancelDiscard();\n\n    switch (result) {\n        .recv => |recv_result| {\n            if (recv_result) |msg| {\n                msg.deinit();\n                reportResult(\"blocking_nextmsg_unsub\", false, \"unexpected message\");\n            } else |err| switch (err) {\n                error.Closed, error.Canceled => {\n                    reportResult(\"blocking_nextmsg_unsub\", true, \"\");\n                },\n                else => {\n                    reportResult(\"blocking_nextmsg_unsub\", false, @errorName(err));\n                },\n            }\n        },\n        .timeout => {\n            reportResult(\"blocking_nextmsg_unsub\", false, \"nextMsg did not wake\");\n        },\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testConcurrentSubscribe(allocator);\n    testRapidPublish(allocator);\n    testConcurrentSubUnsub(allocator);\n    testRaceSubscribeVsDelivery(allocator);\n    testRaceUnsubscribeVsDelivery(allocator);\n    testSidAllocationRecycling(allocator);\n    testMultipleClientsSeparateIo(allocator);\n    testParallelReceive(allocator);\n    testRapidFlushOperations(allocator);\n    testStatsConcurrency(allocator);\n    testMixedWriteOrderingPublishBeforeSubscribe(allocator);\n    testBlockingNextMsgUnsubscribeWakeup(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/connection.zig",
    "content": "//! Connection Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testConnectionRefused(allocator: std.mem.Allocator) void {\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(\n        allocator,\n        io.io(),\n        \"nats://127.0.0.1:19999\",\n        .{ .reconnect = false },\n    );\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"connection_refused\", false, \"expected error\");\n    } else |_| {\n        reportResult(\"connection_refused\", true, \"\");\n    }\n}\n\npub fn testConsecutiveConnections(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    for (0..3) |i| {\n        const client = nats.Client.connect(\n            allocator,\n            io.io(),\n            url,\n            .{ .reconnect = false },\n        ) catch {\n            var buf: [32]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"connect {d} failed\",\n                .{i},\n            ) catch \"e\";\n            reportResult(\"consecutive_connections\", false, msg);\n            return;\n        };\n        client.deinit();\n    }\n\n    reportResult(\"consecutive_connections\", true, \"\");\n}\n\npub fn testIsConnectedState(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"is_connected_state\", false, \"connect failed\");\n        return;\n    };\n\n    if (!client.isConnected()) {\n        client.deinit();\n        reportResult(\n            \"is_connected_state\",\n            false,\n            \"not connected initially\",\n        );\n        return;\n    }\n\n    client.deinit();\n    reportResult(\"is_connected_state\", true, \"\");\n}\n\npub fn testReconnection(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"reconnection\", false, \"initial connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"reconnection\", false, \"not connected initially\");\n        return;\n    }\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnection\", false, \"server restart failed\");\n        return;\n    };\n\n    reportResult(\"reconnection\", true, \"\");\n}\n\n/// Test: New connection after server restart\npub fn testServerRestartNewConnection(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n\n    const client1 = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_restart\", false, \"initial connect failed\");\n        return;\n    };\n\n    if (!client1.isConnected()) {\n        client1.deinit();\n        reportResult(\"server_restart\", false, \"not connected\");\n        return;\n    }\n\n    client1.deinit();\n\n    manager.stopServer(0, io1.io());\n    io1.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    _ = manager.startServer(\n        allocator,\n        io1.io(),\n        .{ .port = test_port },\n    ) catch {\n        reportResult(\"server_restart\", false, \"restart failed\");\n        return;\n    };\n\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const client2 = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_restart\", false, \"reconnect failed\");\n        return;\n    };\n    defer client2.deinit();\n\n    if (client2.isConnected()) {\n        reportResult(\"server_restart\", true, \"\");\n    } else {\n        reportResult(\n            \"server_restart\",\n            false,\n            \"not connected after restart\",\n        );\n    }\n}\n\npub fn testConnectionStateAfterOps(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"state_after_ops\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"state_after_ops\", false, \"not connected after connect\");\n        return;\n    }\n\n    client.publish(\"state.test\", \"data\") catch {};\n    if (!client.isConnected()) {\n        reportResult(\"state_after_ops\", false, \"not connected after publish\");\n        return;\n    }\n\n    const sub = client.subscribeSync(\"state.sub\") catch {\n        reportResult(\"state_after_ops\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    if (!client.isConnected()) {\n        reportResult(\"state_after_ops\", false, \"not connected after subscribe\");\n        return;\n    }\n\n    reportResult(\"state_after_ops\", true, \"\");\n}\n\n/// Verifies no resource leaks after many connection cycles.\npub fn testRapidConnectDisconnect(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    // Do 50 connect/disconnect cycles\n    const CYCLES = 50;\n    var success: u32 = 0;\n\n    for (0..CYCLES) |_| {\n        const io = utils.newIo(allocator);\n        defer io.deinit();\n\n        const client = nats.Client.connect(\n            allocator,\n            io.io(),\n            url,\n            .{ .reconnect = false },\n        ) catch {\n            continue;\n        };\n\n        if (client.isConnected()) {\n            success += 1;\n        }\n\n        client.deinit();\n    }\n\n    if (success == CYCLES) {\n        reportResult(\"rapid_connect_disconnect\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"{d}/50 cycles\",\n            .{success},\n        ) catch \"e\";\n        reportResult(\"rapid_connect_disconnect\", false, detail);\n    }\n}\n\n/// Verifies connect options are properly applied.\npub fn testConnectionOptions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .name = \"test-client\",\n        .verbose = false,\n        .pedantic = false,\n        .echo = true,\n        .headers = true,\n        .no_responders = true,\n        .sub_queue_size = 128,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"connection_options\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"connection_options\", false, \"not connected\");\n        return;\n    }\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"connection_options\", false, \"no server info\");\n        return;\n    }\n\n    reportResult(\"connection_options\", true, \"\");\n}\n\n/// Verifies drain properly cleans up subscriptions.\npub fn testConnectionDrain(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"connection_drain\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Create some subscriptions\n    const sub1 = client.subscribeSync(\"drain.1\") catch {\n        reportResult(\"connection_drain\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"drain.2\") catch {\n        reportResult(\"connection_drain\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    _ = client.drain() catch {\n        reportResult(\"connection_drain\", false, \"drain failed\");\n        return;\n    };\n\n    if (client.isConnected()) {\n        reportResult(\n            \"connection_drain\",\n            false,\n            \"still connected\",\n        );\n        return;\n    }\n\n    reportResult(\"connection_drain\", true, \"\");\n}\n\n/// Verifies invalid URLs are rejected properly.\npub fn testInvalidUrlHandling(allocator: std.mem.Allocator) void {\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const empty_result = nats.Client.connect(\n        allocator,\n        io.io(),\n        \"\",\n        .{ .reconnect = false },\n    );\n    if (empty_result) |client| {\n        client.deinit();\n        reportResult(\"invalid_url_handling\", false, \"empty should fail\");\n        return;\n    } else |_| {}\n\n    const invalid_result = nats.Client.connect(\n        allocator,\n        io.io(),\n        \"nats://\",\n        .{ .reconnect = false },\n    );\n    if (invalid_result) |client| {\n        client.deinit();\n        reportResult(\"invalid_url_handling\", false, \"invalid should fail\");\n        return;\n    } else |_| {}\n\n    reportResult(\"invalid_url_handling\", true, \"\");\n}\n\n/// Verifies state machine transitions are correct.\npub fn testConnectionStateTransitions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"connection_state\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"connection_state\", false, \"not connected\");\n        return;\n    }\n\n    client.publish(\"state.trans\", \"test\") catch {\n        reportResult(\"connection_state\", false, \"publish failed\");\n        return;\n    };\n\n    const sub = client.subscribeSync(\"state.trans\") catch {\n        reportResult(\"connection_state\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    reportResult(\"connection_state\", true, \"\");\n}\n\n/// Verifies client can handle many subscriptions.\npub fn testManyClientSubscriptions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"many_client_subs\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const NUM_SUBS = 100;\n    var subs: [NUM_SUBS]?*nats.Subscription =\n        [_]?*nats.Subscription{null} ** NUM_SUBS;\n    var created: usize = 0;\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..NUM_SUBS) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"many.subs.{d}\",\n            .{i},\n        ) catch continue;\n\n        subs[i] = client.subscribeSync(subject) catch {\n            break;\n        };\n        created += 1;\n    }\n\n    if (created != NUM_SUBS) {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/100\",\n            .{created},\n        ) catch \"e\";\n        reportResult(\"many_client_subs\", false, detail);\n        return;\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"many_client_subs\", true, \"\");\n    } else {\n        reportResult(\"many_client_subs\", false, \"disconnected\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator, manager: *ServerManager) void {\n    testConnectionRefused(allocator);\n    testConsecutiveConnections(allocator);\n    testIsConnectedState(allocator);\n    testConnectionStateAfterOps(allocator);\n    testRapidConnectDisconnect(allocator);\n    testConnectionOptions(allocator);\n    testConnectionDrain(allocator);\n    testInvalidUrlHandling(allocator);\n    testConnectionStateTransitions(allocator);\n    testManyClientSubscriptions(allocator);\n    testReconnection(allocator, manager);\n    testServerRestartNewConnection(allocator, manager);\n}\n"
  },
  {
    "path": "src/testing/client/drain.zig",
    "content": "//! Drain Tests for NATS  Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testDrainOperation(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"drain_operation\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.subscribeSync(\"drain.test.1\") catch {\n        reportResult(\"drain_operation\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"drain.test.2\") catch {\n        reportResult(\"drain_operation\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    _ = client.drain() catch {\n        reportResult(\"drain_operation\", false, \"drain failed\");\n        return;\n    };\n\n    if (!client.isConnected()) {\n        reportResult(\"drain_operation\", true, \"\");\n    } else {\n        reportResult(\"drain_operation\", false, \"still connected\");\n    }\n}\n\npub fn testDrainCleansUp(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"drain_cleanup\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.subscribeSync(\"drain.cleanup.1\") catch {\n        reportResult(\"drain_cleanup\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"drain.cleanup.2\") catch {\n        reportResult(\"drain_cleanup\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    client.publish(\"drain.cleanup.1\", \"msg1\") catch {};\n    client.publish(\"drain.cleanup.2\", \"msg2\") catch {};\n\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    _ = client.drain() catch {\n        reportResult(\"drain_cleanup\", false, \"drain failed\");\n        return;\n    };\n\n    if (!client.isConnected()) {\n        reportResult(\"drain_cleanup\", true, \"\");\n    } else {\n        reportResult(\"drain_cleanup\", false, \"still connected after drain\");\n    }\n}\n\npub fn testDrainTwice(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"drain_twice\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    _ = client.drain() catch {\n        reportResult(\"drain_twice\", false, \"first drain failed\");\n        return;\n    };\n\n    _ = client.drain() catch {};\n\n    reportResult(\"drain_twice\", true, \"\");\n}\n\npub fn testDrainWithManySubscriptions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"drain_many_subs\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var subs: [20]?*nats.Subscription = undefined;\n    @memset(&subs, null);\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    var created: usize = 0;\n    for (0..20) |i| {\n        var sub_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &sub_buf,\n            \"drain.many.{d}\",\n            .{i},\n        ) catch {\n            continue;\n        };\n        subs[i] = client.subscribeSync(subject) catch break;\n        created += 1;\n    }\n\n    _ = client.drain() catch {\n        reportResult(\"drain_many_subs\", false, \"drain failed\");\n        return;\n    };\n\n    if (!client.isConnected() and created >= 15) {\n        reportResult(\"drain_many_subs\", true, \"\");\n    } else {\n        reportResult(\"drain_many_subs\", false, \"unexpected state\");\n    }\n}\n\n/// Test subscription waitDrained with messages consumed.\npub fn testSubWaitDrained(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"sub_wait_drained\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"wait.drained\") catch {\n        reportResult(\"sub_wait_drained\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish some messages\n    for (0..5) |_| {\n        client.publish(\"wait.drained\", \"data\") catch {};\n    }\n\n    // Wait for messages to arrive\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Start draining\n    sub.drain() catch {\n        reportResult(\"sub_wait_drained\", false, \"drain failed\");\n        return;\n    };\n\n    // Consume all messages\n    for (0..10) |_| {\n        const msg = sub.tryNextMsg();\n        if (msg) |m| {\n            m.deinit();\n        } else break;\n    }\n\n    // Now wait for drain to complete (should succeed immediately)\n    sub.waitDrained(1000) catch |err| {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"waitDrained: {s}\", .{\n            @errorName(err),\n        }) catch \"e\";\n        reportResult(\"sub_wait_drained\", false, detail);\n        return;\n    };\n\n    // Queue should be empty now\n    if (sub.pending() == 0) {\n        reportResult(\"sub_wait_drained\", true, \"\");\n    } else {\n        reportResult(\"sub_wait_drained\", false, \"queue not empty\");\n    }\n}\n\n/// Test waitDrained returns error.NotDraining if not draining.\npub fn testWaitDrainedNotDraining(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wait_not_draining\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"not.draining\") catch {\n        reportResult(\"wait_not_draining\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Try waitDrained without calling drain() first\n    sub.waitDrained(100) catch |err| {\n        if (err == error.NotDraining) {\n            reportResult(\"wait_not_draining\", true, \"\");\n            return;\n        }\n    };\n\n    reportResult(\"wait_not_draining\", false, \"expected NotDraining\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testDrainOperation(allocator);\n    testDrainCleansUp(allocator);\n    testDrainTwice(allocator);\n    testDrainWithManySubscriptions(allocator);\n    testSubWaitDrained(allocator);\n    testWaitDrainedNotDraining(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/dynamic_jwt.zig",
    "content": "//! Dynamic JWT Integration Tests\n//!\n//! Generates operator/account/user JWTs at test time using\n//! the auth API, writes a temporary server config, and\n//! verifies pub/sub with dynamically generated credentials.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst ServerManager = utils.ServerManager;\n\nconst Ed25519 = std.crypto.sign.Ed25519;\nconst nkey_mod = nats.auth.nkey;\nconst jwt_mod = nats.auth.jwt;\nconst creds_mod = nats.auth.creds;\n\nconst dynamic_jwt_port = utils.dynamic_jwt_port;\nconst config_path = \"/tmp/nats-dynamic-jwt-test.conf\";\n\nconst Dir = std.Io.Dir;\n\n/// Deterministic keypair seeds for reproducibility.\nconst op_raw_seed = [_]u8{11} ** 32;\nconst acct_raw_seed = [_]u8{22} ** 32;\nconst user_raw_seed = [_]u8{33} ** 32;\n\n/// Generates all keypairs and JWTs, writes config.\n/// Returns formatted credentials string in out_creds.\nfn setupDynamicAuth(\n    io: std.Io,\n    out_creds: *[8192]u8,\n) ?[]const u8 {\n    // Operator keypair (self-signed)\n    const op_ed = Ed25519.KeyPair.generateDeterministic(\n        op_raw_seed,\n    ) catch return null;\n    const op_kp = nkey_mod.KeyPair{\n        .kp = op_ed,\n        .key_type = .operator,\n    };\n\n    // Account keypair\n    const acct_ed = Ed25519.KeyPair.generateDeterministic(\n        acct_raw_seed,\n    ) catch return null;\n    const acct_kp = nkey_mod.KeyPair{\n        .kp = acct_ed,\n        .key_type = .account,\n    };\n\n    // User keypair\n    const user_ed = Ed25519.KeyPair.generateDeterministic(\n        user_raw_seed,\n    ) catch return null;\n    const user_kp = nkey_mod.KeyPair{\n        .kp = user_ed,\n        .key_type = .user,\n    };\n\n    // Public keys\n    var op_pk_buf: [56]u8 = undefined;\n    const op_pub = op_kp.publicKey(&op_pk_buf);\n\n    var acct_pk_buf: [56]u8 = undefined;\n    const acct_pub = acct_kp.publicKey(&acct_pk_buf);\n\n    var user_pk_buf: [56]u8 = undefined;\n    const user_pub = user_kp.publicKey(&user_pk_buf);\n\n    // Encode operator JWT (self-signed)\n    var op_jwt_buf: [2048]u8 = undefined;\n    const op_jwt = jwt_mod.encodeOperatorClaims(\n        &op_jwt_buf,\n        op_pub,\n        \"dyn-operator\",\n        op_kp,\n        1700000000,\n        .{},\n    ) catch return null;\n\n    // Encode account JWT (signed by operator)\n    var acct_jwt_buf: [2048]u8 = undefined;\n    const acct_jwt = jwt_mod.encodeAccountClaims(\n        &acct_jwt_buf,\n        acct_pub,\n        \"dyn-account\",\n        op_kp,\n        1700000000,\n        .{},\n    ) catch return null;\n\n    // Encode user JWT (signed by account)\n    var user_jwt_buf: [2048]u8 = undefined;\n    const user_jwt = jwt_mod.encodeUserClaims(\n        &user_jwt_buf,\n        user_pub,\n        \"dyn-user\",\n        acct_kp,\n        1700000000,\n        .{\n            .pub_allow = &.{\">\"},\n            .sub_allow = &.{\">\"},\n        },\n    ) catch return null;\n\n    // Encode user seed for creds file\n    var seed_buf: [58]u8 = undefined;\n    const user_seed = user_kp.encodeSeed(&seed_buf);\n\n    // Format credentials\n    const creds_str = creds_mod.format(\n        out_creds,\n        user_jwt,\n        user_seed,\n    ) catch return null;\n\n    // Write server config file\n    writeConfig(io, op_jwt, acct_pub, acct_jwt) catch\n        return null;\n\n    return creds_str;\n}\n\n/// Writes nats-server config to temp file.\nfn writeConfig(\n    io: std.Io,\n    op_jwt: []const u8,\n    acct_pub: []const u8,\n    acct_jwt: []const u8,\n) !void {\n    const file = try Dir.createFile(\n        Dir.cwd(),\n        io,\n        config_path,\n        .{},\n    );\n    defer file.close(io);\n\n    var buf: [4096]u8 = undefined;\n    var writer = file.writer(io, &buf);\n    try writer.interface.print(\n        \"operator: {s}\\n\" ++\n            \"resolver: MEMORY\\n\" ++\n            \"resolver_preload: {{\\n\" ++\n            \"  {s}: {s}\\n\" ++\n            \"}}\\n\",\n        .{ op_jwt, acct_pub, acct_jwt },\n    );\n    try writer.interface.flush();\n}\n\n/// Cleans up temp config file.\nfn cleanupConfig(io: std.Io) void {\n    Dir.deleteFile(Dir.cwd(), io, config_path) catch {};\n}\n\n/// Tests connecting with dynamically generated JWT creds.\npub fn testDynamicJwtConnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    var creds_buf: [8192]u8 = undefined;\n    const creds_str = setupDynamicAuth(\n        io,\n        &creds_buf,\n    ) orelse {\n        reportResult(\n            \"dynamic_jwt_connect\",\n            false,\n            \"setup failed\",\n        );\n        return;\n    };\n\n    // Start server with dynamic config\n    _ = manager.startServer(allocator, io, .{\n        .port = dynamic_jwt_port,\n        .config_file = config_path,\n    }) catch {\n        reportResult(\n            \"dynamic_jwt_connect\",\n            false,\n            \"server start failed\",\n        );\n        cleanupConfig(io);\n        return;\n    };\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, dynamic_jwt_port);\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{\n            .reconnect = false,\n            .creds = creds_str,\n        },\n    ) catch |err| {\n        var ebuf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &ebuf,\n            \"connect: {}\",\n            .{err},\n        ) catch \"fmt\";\n        reportResult(\n            \"dynamic_jwt_connect\",\n            false,\n            detail,\n        );\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\n            \"dynamic_jwt_connect\",\n            true,\n            \"\",\n        );\n    } else {\n        reportResult(\n            \"dynamic_jwt_connect\",\n            false,\n            \"not connected\",\n        );\n    }\n}\n\n/// Tests pub/sub with dynamically generated JWT creds.\npub fn testDynamicJwtPubSub(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, dynamic_jwt_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    // Regenerate creds (deterministic, same output)\n    var creds_buf: [8192]u8 = undefined;\n    const creds_str = setupDynamicAuth(\n        io,\n        &creds_buf,\n    ) orelse {\n        reportResult(\n            \"dynamic_jwt_pubsub\",\n            false,\n            \"setup failed\",\n        );\n        return;\n    };\n\n    const client = nats.Client.connect(\n        allocator,\n        io,\n        url,\n        .{\n            .reconnect = false,\n            .creds = creds_str,\n        },\n    ) catch |err| {\n        var ebuf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &ebuf,\n            \"connect: {}\",\n            .{err},\n        ) catch \"fmt\";\n        reportResult(\n            \"dynamic_jwt_pubsub\",\n            false,\n            detail,\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\n        \"dynamic.jwt.test\",\n    ) catch {\n        reportResult(\n            \"dynamic_jwt_pubsub\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    const test_msg = \"dynamic jwt message\";\n    client.publish(\n        \"dynamic.jwt.test\",\n        test_msg,\n    ) catch {\n        reportResult(\n            \"dynamic_jwt_pubsub\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n\n    client.flush(500_000_000) catch {};\n\n    if (sub.nextMsgTimeout(\n        1000,\n    ) catch null) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, test_msg)) {\n            reportResult(\n                \"dynamic_jwt_pubsub\",\n                true,\n                \"\",\n            );\n        } else {\n            reportResult(\n                \"dynamic_jwt_pubsub\",\n                false,\n                \"message mismatch\",\n            );\n        }\n    } else {\n        reportResult(\n            \"dynamic_jwt_pubsub\",\n            false,\n            \"no message received\",\n        );\n    }\n}\n\n/// Runs all dynamic JWT tests.\npub fn runAll(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    testDynamicJwtConnect(allocator, manager);\n    testDynamicJwtPubSub(allocator);\n\n    // Stop dynamic JWT server (last started)\n    const idx = manager.count() - 1;\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    manager.stopServer(idx, io);\n    cleanupConfig(io);\n}\n"
  },
  {
    "path": "src/testing/client/edge_cases.zig",
    "content": "//! Edge Cases Tests for NATS  Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testDoubleUnsubscribe(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"double_unsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"double.unsub\") catch {\n        reportResult(\"double_unsub\", false, \"sub failed\");\n        return;\n    };\n\n    sub.unsubscribe() catch {};\n    sub.unsubscribe() catch {};\n\n    sub.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"double_unsub\", true, \"\");\n    } else {\n        reportResult(\"double_unsub\", false, \"disconnected\");\n    }\n}\n\npub fn testMessageOrdering(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"message_ordering\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"order\") catch {\n        reportResult(\"message_ordering\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    var pub_buf: [5][8]u8 = undefined;\n    for (0..5) |i| {\n        const payload = std.fmt.bufPrint(\n            &pub_buf[i],\n            \"msg-{d}\",\n            .{i},\n        ) catch \"e\";\n        client.publish(\"order\", payload) catch {};\n    }\n\n    var in_order = true;\n    for (0..5) |expected| {\n        var future = io.io().async(\n            nats.Client.Sub.nextMsg,\n            .{sub},\n        );\n        defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n        if (future.await(io.io())) |msg| {\n            var exp_buf: [8]u8 = undefined;\n            const exp = std.fmt.bufPrint(\n                &exp_buf,\n                \"msg-{d}\",\n                .{expected},\n            ) catch \"e\";\n            if (!std.mem.eql(u8, msg.data, exp)) {\n                in_order = false;\n            }\n        } else |_| {\n            in_order = false;\n            break;\n        }\n    }\n\n    if (in_order) {\n        reportResult(\"message_ordering\", true, \"\");\n    } else {\n        reportResult(\"message_ordering\", false, \"out of order\");\n    }\n}\n\npub fn testBinaryPayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"binary_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"binary\") catch {\n        reportResult(\"binary_payload\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const binary = [_]u8{ 0x00, 0x01, 0x02, 0xFF, 0xFE, 0x00, 0x03 };\n\n    client.publish(\"binary\", &binary) catch {\n        reportResult(\"binary_payload\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (std.mem.eql(u8, msg.data, &binary)) {\n            reportResult(\"binary_payload\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"binary_payload\", false, \"binary mismatch\");\n}\n\npub fn testLongSubjectName(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"long_subject_name\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const long_subject = \"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z\" ++\n        \".aa.bb.cc.dd.ee.ff.gg.hh.ii.jj.kk.ll.mm.nn\";\n\n    const sub = client.subscribeSync(long_subject) catch {\n        reportResult(\"long_subject_name\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(long_subject, \"test\") catch {\n        reportResult(\"long_subject_name\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"long_subject_name\", true, \"\");\n    } else {\n        reportResult(\"long_subject_name\", false, \"no message\");\n    }\n}\n\npub fn testSubjectWithNumbersHyphens(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"subject_nums_hyphens\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const subject = \"test-123.foo_bar.baz-456\";\n\n    const sub = client.subscribeSync(subject) catch {\n        reportResult(\"subject_nums_hyphens\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(subject, \"test\") catch {\n        reportResult(\"subject_nums_hyphens\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"subject_nums_hyphens\", true, \"\");\n    } else {\n        reportResult(\"subject_nums_hyphens\", false, \"no message\");\n    }\n}\n\npub fn testDoubleFlush(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"double_flush\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.flushBuffer() catch {\n        reportResult(\"double_flush\", false, \"flush1 failed\");\n        return;\n    };\n    client.flushBuffer() catch {\n        reportResult(\"double_flush\", false, \"flush2 failed\");\n        return;\n    };\n    client.flushBuffer() catch {\n        reportResult(\"double_flush\", false, \"flush3 failed\");\n        return;\n    };\n\n    reportResult(\"double_flush\", true, \"\");\n}\n\npub fn testDoubleDrain(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"double_drain\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"double.drain.test\") catch {\n        reportResult(\"double_drain\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub.unsubscribe() catch {};\n    sub.unsubscribe() catch {};\n\n    if (client.isConnected()) {\n        reportResult(\"double_drain\", true, \"\");\n    } else {\n        reportResult(\"double_drain\", false, \"disconnected\");\n    }\n}\n\npub fn testRapidSubUnsubCycles(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"rapid_sub_unsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    for (0..20) |_| {\n        const sub = client.subscribeSync(\"rapid.cycle.test\") catch {\n            reportResult(\"rapid_sub_unsub\", false, \"subscribe failed\");\n            return;\n        };\n        sub.unsubscribe() catch {};\n        sub.deinit();\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"rapid_sub_unsub\", true, \"\");\n    } else {\n        reportResult(\"rapid_sub_unsub\", false, \"disconnected\");\n    }\n}\n\npub fn testNewInboxUniqueness(allocator: std.mem.Allocator) void {\n    var inboxes: [10][]u8 = undefined;\n    var created: usize = 0;\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    defer for (inboxes[0..created]) |inbox| {\n        allocator.free(inbox);\n    };\n\n    for (0..10) |i| {\n        inboxes[i] = nats.newInbox(allocator, io.io()) catch {\n            reportResult(\"inbox_uniqueness\", false, \"newInbox failed\");\n            return;\n        };\n        created += 1;\n\n        for (0..i) |j| {\n            if (std.mem.eql(u8, inboxes[i], inboxes[j])) {\n                reportResult(\"inbox_uniqueness\", false, \"duplicate inbox\");\n                return;\n            }\n        }\n    }\n\n    reportResult(\"inbox_uniqueness\", true, \"\");\n}\n\npub fn testEmptySubjectFails(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"empty_subject_fails\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub_result = client.subscribeSync(\"\");\n    if (sub_result) |sub| {\n        sub.deinit();\n        reportResult(\"empty_subject_fails\", false, \"subscribe should fail\");\n        return;\n    } else |_| {\n        // Expected\n    }\n\n    reportResult(\"empty_subject_fails\", true, \"\");\n}\n\npub fn testSubjectWithSpacesFails(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"subject_spaces_fails\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const result = client.publish(\"foo bar\", \"data\");\n    if (result) |_| {\n        reportResult(\"subject_spaces_fails\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"subject_spaces_fails\", true, \"\");\n    }\n}\n\npub fn testInterleavedPubSub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"interleaved_pubsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"interleave.test\") catch {\n        reportResult(\"interleaved_pubsub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    var received: u32 = 0;\n    for (0..10) |i| {\n        var buf: [16]u8 = undefined;\n        const payload = std.fmt.bufPrint(&buf, \"msg{d}\", .{i}) catch continue;\n\n        client.publish(\"interleave.test\", payload) catch {\n            reportResult(\"interleaved_pubsub\", false, \"publish failed\");\n            return;\n        };\n\n        const msg = sub.nextMsgTimeout(500) catch {\n            continue;\n        };\n        if (msg) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 10) {\n        reportResult(\"interleaved_pubsub\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/10\",\n            .{received},\n        ) catch \"err\";\n        reportResult(\"interleaved_pubsub\", false, detail);\n    }\n}\n\npub fn testReceiveOnlyAfterSubscribe(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"receive_after_sub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.publish(\"timing.test\", \"before\") catch {};\n\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const sub = client.subscribeSync(\"timing.test\") catch {\n        reportResult(\"receive_after_sub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"timing.test\", \"after\") catch {};\n\n    const msg = sub.nextMsgTimeout(500) catch {\n        reportResult(\"receive_after_sub\", false, \"receive error\");\n        return;\n    };\n\n    if (msg) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, \"after\")) {\n            reportResult(\"receive_after_sub\", true, \"\");\n        } else {\n            reportResult(\"receive_after_sub\", false, \"got wrong message\");\n        }\n    } else {\n        reportResult(\"receive_after_sub\", false, \"no message\");\n    }\n}\n\npub fn testDataIntegrityPattern(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"data_integrity\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"integrity.test\") catch {\n        reportResult(\"data_integrity\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Create pattern payload\n    var payload: [256]u8 = undefined;\n    for (&payload, 0..) |*b, i| {\n        b.* = @truncate(i);\n    }\n\n    client.publish(\"integrity.test\", &payload) catch {\n        reportResult(\"data_integrity\", false, \"publish failed\");\n        return;\n    };\n\n    const msg = sub.nextMsgTimeout(500) catch {\n        reportResult(\"data_integrity\", false, \"receive failed\");\n        return;\n    };\n\n    if (msg) |m| {\n        defer m.deinit();\n        if (m.data.len != 256) {\n            reportResult(\"data_integrity\", false, \"wrong length\");\n            return;\n        }\n        for (m.data, 0..) |b, i| {\n            if (b != @as(u8, @truncate(i))) {\n                reportResult(\"data_integrity\", false, \"data corrupt\");\n                return;\n            }\n        }\n        reportResult(\"data_integrity\", true, \"\");\n    } else {\n        reportResult(\"data_integrity\", false, \"no message\");\n    }\n}\n\npub fn testCompletePubSubRoundTrip(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"complete_roundtrip\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"complete_roundtrip\", false, \"not connected\");\n        return;\n    }\n\n    const sub = client.subscribeSync(\"roundtrip.100\") catch {\n        reportResult(\"complete_roundtrip\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const before = client.stats();\n\n    const test_data = \"Test100-RoundTrip-Verification\";\n    client.publish(\"roundtrip.100\", test_data) catch {\n        reportResult(\"complete_roundtrip\", false, \"publish failed\");\n        return;\n    };\n\n    const msg = sub.nextMsgTimeout(1000) catch {\n        reportResult(\"complete_roundtrip\", false, \"receive failed\");\n        return;\n    };\n\n    if (msg == null) {\n        reportResult(\"complete_roundtrip\", false, \"no message\");\n        return;\n    }\n\n    const m = msg.?;\n    defer m.deinit();\n\n    if (!std.mem.eql(u8, m.data, test_data)) {\n        reportResult(\"complete_roundtrip\", false, \"data mismatch\");\n        return;\n    }\n\n    if (!std.mem.eql(u8, m.subject, \"roundtrip.100\")) {\n        reportResult(\"complete_roundtrip\", false, \"subject mismatch\");\n        return;\n    }\n\n    var after = client.stats();\n    for (0..50) |_| {\n        if (after.msgs_out > before.msgs_out and\n            after.msgs_in > before.msgs_in)\n            break;\n        io.io().sleep(.fromMilliseconds(1), .awake) catch {};\n        after = client.stats();\n    }\n\n    if (after.msgs_out <= before.msgs_out) {\n        reportResult(\"complete_roundtrip\", false, \"msgs_out not updated\");\n        return;\n    }\n    if (after.msgs_in <= before.msgs_in) {\n        reportResult(\"complete_roundtrip\", false, \"msgs_in not updated\");\n        return;\n    }\n\n    reportResult(\"complete_roundtrip\", true, \"\");\n}\n\npub fn testQueueExactCapacity(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const QUEUE_SIZE = 64;\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .sub_queue_size = QUEUE_SIZE,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"queue_exact_cap\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"boundary.exact\") catch {\n        reportResult(\"queue_exact_cap\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..QUEUE_SIZE) |_| {\n        client.publish(\"boundary.exact\", \"x\") catch {\n            reportResult(\"queue_exact_cap\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var received: u32 = 0;\n    for (0..QUEUE_SIZE) |_| {\n        if (sub.nextMsgTimeout(200) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    if (received == QUEUE_SIZE) {\n        reportResult(\"queue_exact_cap\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/64\",\n            .{received},\n        ) catch \"e\";\n        reportResult(\"queue_exact_cap\", false, detail);\n    }\n}\n\npub fn testQueueOverflow(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const QUEUE_SIZE = 32;\n    const PUBLISH_COUNT = 64;\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .sub_queue_size = QUEUE_SIZE,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"queue_overflow\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub_reader = client.subscribeSync(\"overflow.reader\") catch {\n        reportResult(\"queue_overflow\", false, \"sub_reader failed\");\n        return;\n    };\n    defer sub_reader.deinit();\n\n    const sub_target = client.subscribeSync(\"overflow.target\") catch {\n        reportResult(\"queue_overflow\", false, \"sub_target failed\");\n        return;\n    };\n    defer sub_target.deinit();\n\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    for (0..PUBLISH_COUNT) |_| {\n        client.publish(\"overflow.target\", \"x\") catch {\n            reportResult(\"queue_overflow\", false, \"publish target failed\");\n            return;\n        };\n    }\n    client.publish(\"overflow.reader\", \"trigger\") catch {\n        reportResult(\"queue_overflow\", false, \"publish reader failed\");\n        return;\n    };\n\n    if (sub_reader.nextMsgTimeout(2000) catch null) |m| {\n        m.deinit();\n    } else {\n        reportResult(\"queue_overflow\", false, \"reader timeout\");\n        return;\n    }\n\n    var received: u32 = 0;\n    while (sub_target.tryNextMsg()) |m| {\n        m.deinit();\n        received += 1;\n    }\n\n    if (received <= QUEUE_SIZE and received > 0) {\n        reportResult(\"queue_overflow\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d} (expect <= {d})\",\n            .{ received, QUEUE_SIZE },\n        ) catch \"e\";\n        reportResult(\"queue_overflow\", false, detail);\n    }\n}\n\npub fn testMaxSubscriptions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_subscriptions\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const MAX_SUBS = 256;\n    var subs: [MAX_SUBS]?*nats.Subscription = undefined;\n    @memset(&subs, null);\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    var created: usize = 0;\n    for (0..MAX_SUBS) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"maxsub.{d}\",\n            .{i},\n        ) catch continue;\n        subs[i] = client.subscribeSync(subject) catch {\n            break;\n        };\n        created += 1;\n    }\n\n    if (created == MAX_SUBS) {\n        reportResult(\"max_subscriptions\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/256\",\n            .{created},\n        ) catch \"e\";\n        reportResult(\"max_subscriptions\", false, detail);\n    }\n}\n\npub fn testLargePayloadHandling(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"large_payload_handling\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"large.payload\") catch {\n        reportResult(\"large_payload_handling\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const payload_size = 64 * 1024;\n    const payload = allocator.alloc(u8, payload_size) catch {\n        reportResult(\"large_payload_handling\", false, \"alloc failed\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'L');\n\n    client.publish(\"large.payload\", payload) catch {\n        reportResult(\"large_payload_handling\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(5000) catch null) |m| {\n        defer m.deinit();\n        if (m.data.len == payload_size) {\n            reportResult(\"large_payload_handling\", true, \"\");\n        } else {\n            var buf: [48]u8 = undefined;\n            const detail = std.fmt.bufPrint(\n                &buf,\n                \"got {d} bytes\",\n                .{m.data.len},\n            ) catch \"e\";\n            reportResult(\"large_payload_handling\", false, detail);\n        }\n    } else {\n        reportResult(\"large_payload_handling\", false, \"no message\");\n    }\n}\n\npub fn testSubjectLengthBoundary(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"subject_len_boundary\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var long_subject_buf: [200]u8 = undefined;\n    for (&long_subject_buf, 0..) |*c, i| {\n        if (i % 10 == 9 and i < 199) {\n            c.* = '.';\n        } else {\n            c.* = 'a' + @as(u8, @intCast(i % 26));\n        }\n    }\n\n    const sub = client.subscribeSync(&long_subject_buf) catch {\n        reportResult(\"subject_len_boundary\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(&long_subject_buf, \"test\") catch {\n        reportResult(\"subject_len_boundary\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"subject_len_boundary\", true, \"\");\n    } else {\n        reportResult(\"subject_len_boundary\", false, \"no message\");\n    }\n}\n\npub fn testZeroLengthPayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"zero_len_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"zero.payload\") catch {\n        reportResult(\"zero_len_payload\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"zero.payload\", \"\") catch {\n        reportResult(\"zero_len_payload\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        defer m.deinit();\n        if (m.data.len == 0) {\n            reportResult(\"zero_len_payload\", true, \"\");\n        } else {\n            reportResult(\"zero_len_payload\", false, \"non-empty data\");\n        }\n    } else {\n        reportResult(\"zero_len_payload\", false, \"no message\");\n    }\n}\n\npub fn testSingleBytePayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"single_byte_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"single.byte\") catch {\n        reportResult(\"single_byte_payload\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"single.byte\", \"X\") catch {\n        reportResult(\"single_byte_payload\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        defer m.deinit();\n        if (m.data.len == 1 and m.data[0] == 'X') {\n            reportResult(\"single_byte_payload\", true, \"\");\n        } else {\n            reportResult(\"single_byte_payload\", false, \"wrong data\");\n        }\n    } else {\n        reportResult(\"single_byte_payload\", false, \"no message\");\n    }\n}\n\npub fn testSidBoundaries(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"sid_boundaries\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var last_sid: u64 = 0;\n    for (0..100) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"sid.test.{d}\",\n            .{i},\n        ) catch continue;\n\n        const sub = client.subscribeSync(subject) catch {\n            reportResult(\"sid_boundaries\", false, \"subscribe failed\");\n            return;\n        };\n\n        if (sub.sid <= last_sid and i > 0) {\n            sub.deinit();\n            reportResult(\"sid_boundaries\", false, \"SID not increasing\");\n            return;\n        }\n        last_sid = sub.sid;\n        sub.deinit();\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"sid_boundaries\", true, \"\");\n    } else {\n        reportResult(\"sid_boundaries\", false, \"disconnected\");\n    }\n}\n\npub fn testMaxSubscriptionsExceeded(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_subs_exceeded\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const MAX_SUBS = nats.Client.MAX_SUBSCRIPTIONS;\n    const subs = allocator.alloc(\n        ?*nats.Subscription,\n        MAX_SUBS,\n    ) catch {\n        reportResult(\"max_subs_exceeded\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(subs);\n    @memset(subs, null);\n\n    defer for (subs) |s| {\n        if (s) |sub| sub.deinit();\n    };\n\n    var created: usize = 0;\n    for (0..MAX_SUBS) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"exceedsub.{d}\",\n            .{i},\n        ) catch continue;\n        subs[i] = client.subscribeSync(subject) catch {\n            break;\n        };\n        created += 1;\n    }\n\n    if (created != MAX_SUBS) {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"only created {d}/{d}\",\n            .{ created, MAX_SUBS },\n        ) catch \"e\";\n        reportResult(\"max_subs_exceeded\", false, detail);\n        return;\n    }\n\n    const result = client.subscribeSync(\"exceedsub.over\");\n    if (result) |sub| {\n        sub.deinit();\n        reportResult(\n            \"max_subs_exceeded\",\n            false,\n            \"over-max should fail\",\n        );\n    } else |err| {\n        if (err == error.TooManySubscriptions) {\n            reportResult(\"max_subs_exceeded\", true, \"\");\n        } else {\n            reportResult(\n                \"max_subs_exceeded\",\n                false,\n                \"wrong error type\",\n            );\n        }\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testDoubleUnsubscribe(allocator);\n    testMessageOrdering(allocator);\n    testBinaryPayload(allocator);\n    testLongSubjectName(allocator);\n    testSubjectWithNumbersHyphens(allocator);\n    testDoubleFlush(allocator);\n    testDoubleDrain(allocator);\n    testRapidSubUnsubCycles(allocator);\n    testNewInboxUniqueness(allocator);\n    testEmptySubjectFails(allocator);\n    testSubjectWithSpacesFails(allocator);\n    testInterleavedPubSub(allocator);\n    testReceiveOnlyAfterSubscribe(allocator);\n    testDataIntegrityPattern(allocator);\n    testCompletePubSubRoundTrip(allocator);\n    // Boundary tests\n    testQueueExactCapacity(allocator);\n    testQueueOverflow(allocator);\n    testMaxSubscriptions(allocator);\n    testLargePayloadHandling(allocator);\n    testSubjectLengthBoundary(allocator);\n    testZeroLengthPayload(allocator);\n    testSingleBytePayload(allocator);\n    testSidBoundaries(allocator);\n    testMaxSubscriptionsExceeded(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/error_handling.zig",
    "content": "//! Error Handling Integration Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\nconst defaults = nats.defaults;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\npub fn testSubjectTooLong(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"subject_too_long\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const max_len = defaults.Limits.max_subject_len;\n    var long_subject: [max_len + 1]u8 = undefined;\n    @memset(&long_subject, 'a');\n\n    const result = client.subscribeSync(&long_subject);\n    if (result) |sub| {\n        sub.deinit();\n        reportResult(\"subject_too_long\", false, \"should have failed\");\n    } else |err| {\n        if (err == error.SubjectTooLong) {\n            reportResult(\"subject_too_long\", true, \"\");\n        } else {\n            var buf: [64]u8 = undefined;\n            const detail = std.fmt.bufPrint(\n                &buf,\n                \"wrong error: {s}\",\n                .{@errorName(err)},\n            ) catch \"fmt error\";\n            reportResult(\"subject_too_long\", false, detail);\n        }\n    }\n}\n\npub fn testQueueGroupTooLong(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"queue_group_too_long\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const max_len = defaults.Limits.max_queue_group_len;\n    var long_qg: [max_len + 1]u8 = undefined;\n    @memset(&long_qg, 'q');\n\n    const result = client.queueSubscribeSync(\"test.subject\", &long_qg);\n    if (result) |sub| {\n        sub.deinit();\n        reportResult(\"queue_group_too_long\", false, \"should have failed\");\n    } else |err| {\n        if (err == error.QueueGroupTooLong) {\n            reportResult(\"queue_group_too_long\", true, \"\");\n        } else {\n            var buf: [64]u8 = undefined;\n            const detail = std.fmt.bufPrint(\n                &buf,\n                \"wrong error: {s}\",\n                .{@errorName(err)},\n            ) catch \"fmt error\";\n            reportResult(\"queue_group_too_long\", false, detail);\n        }\n    }\n}\n\npub fn testUrlTooLong(allocator: std.mem.Allocator) void {\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const max_len = defaults.Server.max_url_len;\n    var long_url: [max_len + 1]u8 = undefined;\n    const prefix = \"nats://localhost:\";\n    @memcpy(long_url[0..prefix.len], prefix);\n    @memset(long_url[prefix.len..], '1');\n\n    const result = nats.Client.connect(allocator, io.io(), &long_url, .{\n        .reconnect = false,\n    });\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"url_too_long\", false, \"should have failed\");\n    } else |err| {\n        if (err == error.UrlTooLong) {\n            reportResult(\"url_too_long\", true, \"\");\n        } else {\n            var buf: [64]u8 = undefined;\n            const detail = std.fmt.bufPrint(\n                &buf,\n                \"wrong error: {s}\",\n                .{@errorName(err)},\n            ) catch \"fmt error\";\n            reportResult(\"url_too_long\", false, detail);\n        }\n    }\n}\n\npub fn testDrainResultIsClean(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"drain_result_clean\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"drain.test\") catch {\n        reportResult(\"drain_result_clean\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const result = client.drain() catch {\n        reportResult(\"drain_result_clean\", false, \"drain failed\");\n        return;\n    };\n\n    if (result.isClean()) {\n        reportResult(\"drain_result_clean\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"unsub_fail={d} flush={any}\",\n            .{ result.unsub_failures, result.flush_failed },\n        ) catch \"fmt error\";\n        reportResult(\"drain_result_clean\", false, detail);\n    }\n}\n\npub fn testSubjectExactLimit(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"subject_exact_limit\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // max_subject_len is exclusive (>= rejects it)\n    const max_len = defaults.Limits.max_subject_len - 1;\n    var subject_max: [max_len]u8 = undefined;\n    @memset(&subject_max, 'a');\n\n    const sub = client.subscribeSync(&subject_max) catch {\n        reportResult(\"subject_exact_limit\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    reportResult(\"subject_exact_limit\", true, \"\");\n}\n\npub fn testQueueGroupExactLimit(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"qg_exact_limit\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const max_len = defaults.Limits.max_queue_group_len;\n    var qg_max: [max_len]u8 = undefined;\n    @memset(&qg_max, 'q');\n\n    const sub = client.queueSubscribeSync(\"test.subject\", &qg_max) catch {\n        reportResult(\"qg_exact_limit\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    reportResult(\"qg_exact_limit\", true, \"\");\n}\n\npub fn testResetErrorNotifications(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"reset_error_notif\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.resetErrorNotifications();\n\n    reportResult(\"reset_error_notif\", true, \"\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testSubjectTooLong(allocator);\n    testQueueGroupTooLong(allocator);\n    testUrlTooLong(allocator);\n    testDrainResultIsClean(allocator);\n    testSubjectExactLimit(allocator);\n    testQueueGroupExactLimit(allocator);\n    testResetErrorNotifications(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/flush_confirmed.zig",
    "content": "//! FlushConfirmed Integration Tests\n//!\n//! Tests for flushConfirmed() which sends buffered data + PING and waits\n//! for PONG confirmation from the server.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\n/// Basic flushConfirmed - publish, confirm, verify receipt.\npub fn testFlushConfirmedBasic(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_basic\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"fc.basic\") catch {\n        reportResult(\"flush_confirmed_basic\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.flushBuffer() catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    client.publish(\"fc.basic\", \"confirmed-message\") catch {\n        reportResult(\"flush_confirmed_basic\", false, \"publish failed\");\n        return;\n    };\n\n    // Use flushConfirmed with 5 second timeout\n    client.flush(5_000_000_000) catch {\n        reportResult(\"flush_confirmed_basic\", false, \"flushConfirmed failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (std.mem.eql(u8, msg.data, \"confirmed-message\")) {\n            reportResult(\"flush_confirmed_basic\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"flush_confirmed_basic\", false, \"message mismatch\");\n}\n\n/// Test flushConfirmed with multiple buffered messages.\npub fn testFlushConfirmedMultipleMessages(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_multi\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"fc.batch\") catch {\n        reportResult(\"flush_confirmed_multi\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.flushBuffer() catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    // Publish 10 messages without flushing\n    for (0..10) |i| {\n        var buf: [32]u8 = undefined;\n        const payload = std.fmt.bufPrint(&buf, \"msg-{d}\", .{i}) catch \"msg\";\n        client.publish(\"fc.batch\", payload) catch {\n            reportResult(\"flush_confirmed_multi\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    // Single flushConfirmed should send all\n    client.flush(5_000_000_000) catch {\n        reportResult(\"flush_confirmed_multi\", false, \"flushConfirmed failed\");\n        return;\n    };\n\n    // Verify all 10 messages received\n    var received: u32 = 0;\n    for (0..10) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 10) {\n        reportResult(\"flush_confirmed_multi\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/10\",\n            .{received},\n        ) catch \"err\";\n        reportResult(\"flush_confirmed_multi\", false, detail);\n    }\n}\n\n/// Test that normal operations work after flushConfirmed.\npub fn testFlushConfirmedNoSideEffects(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_side_effects\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"fc.side\") catch {\n        reportResult(\"flush_confirmed_side_effects\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.flushBuffer() catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    // First: publish + flushConfirmed\n    client.publish(\"fc.side\", \"first\") catch {\n        reportResult(\"flush_confirmed_side_effects\", false, \"pub1 failed\");\n        return;\n    };\n    client.flush(5_000_000_000) catch {\n        reportResult(\"flush_confirmed_side_effects\", false, \"flushConfirmed failed\");\n        return;\n    };\n\n    // Second: publish + regular flush (should still work)\n    client.publish(\"fc.side\", \"second\") catch {\n        reportResult(\"flush_confirmed_side_effects\", false, \"pub2 failed\");\n        return;\n    };\n    client.flushBuffer() catch {\n        reportResult(\"flush_confirmed_side_effects\", false, \"flush failed\");\n        return;\n    };\n\n    // Verify both messages received\n    var received: u32 = 0;\n    for (0..2) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 2) {\n        reportResult(\"flush_confirmed_side_effects\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/2\",\n            .{received},\n        ) catch \"err\";\n        reportResult(\"flush_confirmed_side_effects\", false, detail);\n    }\n}\n\n/// Compare flushConfirmed vs flush - both deliver messages.\npub fn testFlushConfirmedVsFlush(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"fc.compare\") catch {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.flushBuffer() catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    // Publish with regular flush\n    client.publish(\"fc.compare\", \"via-flush\") catch {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"pub1 failed\");\n        return;\n    };\n    client.flushBuffer() catch {};\n\n    // Publish with flushConfirmed\n    client.publish(\"fc.compare\", \"via-confirmed\") catch {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"pub2 failed\");\n        return;\n    };\n    client.flush(5_000_000_000) catch {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"flushConfirmed failed\");\n        return;\n    };\n\n    // Verify both arrive in order\n    const msg1 = sub.nextMsgTimeout(500) catch null;\n    if (msg1 == null) {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"no msg1\");\n        return;\n    }\n    defer msg1.?.deinit();\n\n    const msg2 = sub.nextMsgTimeout(500) catch null;\n    if (msg2 == null) {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"no msg2\");\n        return;\n    }\n    defer msg2.?.deinit();\n\n    const ok1 = std.mem.eql(u8, msg1.?.data, \"via-flush\");\n    const ok2 = std.mem.eql(u8, msg2.?.data, \"via-confirmed\");\n\n    if (ok1 and ok2) {\n        reportResult(\"flush_confirmed_vs_flush\", true, \"\");\n    } else {\n        reportResult(\"flush_confirmed_vs_flush\", false, \"wrong order/content\");\n    }\n}\n\n/// Test flushConfirmed returns error when not connected.\npub fn testFlushConfirmedNotConnected(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_not_connected\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Drain to close connection\n    _ = client.drain() catch {\n        reportResult(\"flush_confirmed_not_connected\", false, \"drain failed\");\n        return;\n    };\n\n    // Now try flushConfirmed - should fail\n    const result = client.flush(1_000_000_000);\n\n    if (result) |_| {\n        reportResult(\"flush_confirmed_not_connected\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"flush_confirmed_not_connected\", true, \"\");\n    }\n}\n\n/// Test flushConfirmed with large payload.\npub fn testFlushConfirmedLargePayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_large\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"fc.large\") catch {\n        reportResult(\"flush_confirmed_large\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.flushBuffer() catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    // Allocate 64KB payload\n    const payload = allocator.alloc(u8, 64 * 1024) catch {\n        reportResult(\"flush_confirmed_large\", false, \"alloc failed\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'X');\n\n    client.publish(\"fc.large\", payload) catch {\n        reportResult(\"flush_confirmed_large\", false, \"publish failed\");\n        return;\n    };\n\n    client.flush(5_000_000_000) catch {\n        reportResult(\"flush_confirmed_large\", false, \"flushConfirmed failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.data.len == 64 * 1024) {\n            reportResult(\"flush_confirmed_large\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"flush_confirmed_large\", false, \"wrong size\");\n}\n\n/// Test multiple sequential flushConfirmed calls.\npub fn testFlushConfirmedRapidFire(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_rapid\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"fc.rapid\") catch {\n        reportResult(\"flush_confirmed_rapid\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.flushBuffer() catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    // 20 cycles of publish + flushConfirmed\n    for (0..20) |i| {\n        var buf: [32]u8 = undefined;\n        const payload = std.fmt.bufPrint(&buf, \"rapid-{d}\", .{i}) catch \"msg\";\n\n        client.publish(\"fc.rapid\", payload) catch {\n            reportResult(\"flush_confirmed_rapid\", false, \"publish failed\");\n            return;\n        };\n\n        client.flush(5_000_000_000) catch {\n            reportResult(\"flush_confirmed_rapid\", false, \"flushConfirmed failed\");\n            return;\n        };\n    }\n\n    // Verify all 20 messages received\n    var received: u32 = 0;\n    for (0..20) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 20) {\n        reportResult(\"flush_confirmed_rapid\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/20\",\n            .{received},\n        ) catch \"err\";\n        reportResult(\"flush_confirmed_rapid\", false, detail);\n    }\n}\n\n/// Test flushConfirmed timeout behavior.\npub fn testFlushConfirmedTimeout(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_confirmed_timeout\", false, \"connect failed\");\n        return;\n    };\n\n    // Drain to disconnect, then try flushConfirmed with short timeout\n    _ = client.drain() catch {\n        reportResult(\"flush_confirmed_timeout\", false, \"drain failed\");\n        client.deinit();\n        return;\n    };\n\n    // Should fail quickly (NotConnected, not timeout in this case)\n    const result = client.flush(100_000_000); // 100ms\n    client.deinit();\n\n    if (result) |_| {\n        reportResult(\"flush_confirmed_timeout\", false, \"should have failed\");\n    } else |err| {\n        // Accept NotConnected or Timeout as valid failures\n        if (err == error.NotConnected or err == error.Timeout) {\n            reportResult(\"flush_confirmed_timeout\", true, \"\");\n        } else {\n            reportResult(\"flush_confirmed_timeout\", false, \"unexpected error\");\n        }\n    }\n}\n\n/// Run all flushConfirmed tests.\npub fn runAll(allocator: std.mem.Allocator) void {\n    testFlushConfirmedBasic(allocator);\n    testFlushConfirmedMultipleMessages(allocator);\n    testFlushConfirmedNoSideEffects(allocator);\n    testFlushConfirmedVsFlush(allocator);\n    testFlushConfirmedNotConnected(allocator);\n    testFlushConfirmedLargePayload(allocator);\n    testFlushConfirmedRapidFire(allocator);\n    testFlushConfirmedTimeout(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/getters.zig",
    "content": "//! Getters Tests for NATS Client\n//!\n//! Tests for connection info and subscription info getters.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\npub fn testConnectionInfoGetters(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false, .name = \"test-client\" },\n    ) catch {\n        reportResult(\"connection_info_getters\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Test name()\n    const name = client.name();\n    if (name == null or !std.mem.eql(u8, name.?, \"test-client\")) {\n        reportResult(\"connection_info_getters\", false, \"name failed\");\n        return;\n    }\n\n    // Test connectedUrl()\n    const conn_url = client.connectedUrl();\n    if (conn_url == null) {\n        reportResult(\"connection_info_getters\", false, \"connectedUrl null\");\n        return;\n    }\n\n    // Test connectedServerId() - should have a value\n    const server_id = client.connectedServerId();\n    if (server_id == null or server_id.?.len == 0) {\n        reportResult(\"connection_info_getters\", false, \"connectedServerId\");\n        return;\n    }\n\n    // Test connectedServerVersion() - should have a value\n    const version = client.connectedServerVersion();\n    if (version == null or version.?.len == 0) {\n        reportResult(\"connection_info_getters\", false, \"connectedServerVersion\");\n        return;\n    }\n\n    // Test headersSupported() - NATS 2.x supports headers\n    if (!client.headersSupported()) {\n        reportResult(\"connection_info_getters\", false, \"headersSupported\");\n        return;\n    }\n\n    // Test maxPayload() - should be > 0\n    if (client.maxPayload() == 0) {\n        reportResult(\"connection_info_getters\", false, \"maxPayload\");\n        return;\n    }\n\n    reportResult(\"connection_info_getters\", true, \"\");\n}\n\npub fn testServerInfoGetters(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_info_getters\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Test tlsRequired() - default server doesn't require TLS\n    // (this may vary by server config, just check it doesn't crash)\n    _ = client.tlsRequired();\n\n    // Test authRequired() - default server may or may not require auth\n    _ = client.authRequired();\n\n    // Test clientId() - should be set by server\n    const client_id = client.clientId();\n    if (client_id == null or client_id.? == 0) {\n        reportResult(\"server_info_getters\", false, \"clientId\");\n        return;\n    }\n\n    // Test serverCount() - should be at least 1\n    if (client.serverCount() < 1) {\n        reportResult(\"server_info_getters\", false, \"serverCount\");\n        return;\n    }\n\n    reportResult(\"server_info_getters\", true, \"\");\n}\n\npub fn testConnectedAddrGetter(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"connected_addr_getter\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var addr_buf: [64]u8 = undefined;\n    const addr = client.connectedAddr(&addr_buf);\n    if (addr == null) {\n        reportResult(\"connected_addr_getter\", false, \"connectedAddr null\");\n        return;\n    }\n\n    // Should contain a colon (host:port format)\n    if (std.mem.indexOf(u8, addr.?, \":\") == null) {\n        reportResult(\"connected_addr_getter\", false, \"no colon in addr\");\n        return;\n    }\n\n    reportResult(\"connected_addr_getter\", true, \"\");\n}\n\npub fn testUrlRedaction(allocator: std.mem.Allocator) void {\n    // Test URL redaction with credentials\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"url_redaction\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var redact_buf: [256]u8 = undefined;\n    const redacted = client.connectedUrlRedacted(&redact_buf);\n    if (redacted == null) {\n        reportResult(\"url_redaction\", false, \"connectedUrlRedacted null\");\n        return;\n    }\n\n    // URL without credentials should be unchanged\n    const orig = client.connectedUrl().?;\n    if (!std.mem.eql(u8, redacted.?, orig)) {\n        reportResult(\"url_redaction\", false, \"mismatch for no-creds url\");\n        return;\n    }\n\n    reportResult(\"url_redaction\", true, \"\");\n}\n\npub fn testSubscriptionInfoGetters(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"subscription_info_getters\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Create a regular subscription\n    const sub = client.subscribeSync(\"test.getters\") catch {\n        reportResult(\"subscription_info_getters\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Test getSid() - should be > 0\n    if (sub.getSid() == 0) {\n        reportResult(\"subscription_info_getters\", false, \"getSid\");\n        return;\n    }\n\n    // Test getSubject()\n    if (!std.mem.eql(u8, sub.getSubject(), \"test.getters\")) {\n        reportResult(\"subscription_info_getters\", false, \"getSubject\");\n        return;\n    }\n\n    // Test queueGroup() - should be null for regular sub\n    if (sub.queueGroup() != null) {\n        reportResult(\"subscription_info_getters\", false, \"queueGroup\");\n        return;\n    }\n\n    // Test isDraining() - should be false initially\n    if (sub.isDraining()) {\n        reportResult(\"subscription_info_getters\", false, \"isDraining\");\n        return;\n    }\n\n    reportResult(\"subscription_info_getters\", true, \"\");\n}\n\npub fn testQueueSubGetters(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"queue_sub_getters\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Create a queue subscription\n    const sub = client.queueSubscribeSync(\n        \"test.queue.getters\",\n        \"workers\",\n    ) catch {\n        reportResult(\"queue_sub_getters\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Test queueGroup() - should return \"workers\"\n    const qg = sub.queueGroup();\n    if (qg == null or !std.mem.eql(u8, qg.?, \"workers\")) {\n        reportResult(\"queue_sub_getters\", false, \"queueGroup\");\n        return;\n    }\n\n    reportResult(\"queue_sub_getters\", true, \"\");\n}\n\npub fn testDrainingState(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"draining_state\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.draining\") catch {\n        reportResult(\"draining_state\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Initially not draining\n    if (sub.isDraining()) {\n        reportResult(\"draining_state\", false, \"initially draining\");\n        return;\n    }\n\n    // Start drain\n    sub.drain() catch {\n        reportResult(\"draining_state\", false, \"drain failed\");\n        return;\n    };\n\n    // Now should be draining\n    if (!sub.isDraining()) {\n        reportResult(\"draining_state\", false, \"not draining after drain()\");\n        return;\n    }\n\n    reportResult(\"draining_state\", true, \"\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testConnectionInfoGetters(allocator);\n    testServerInfoGetters(allocator);\n    testConnectedAddrGetter(allocator);\n    testUrlRedaction(allocator);\n    testSubscriptionInfoGetters(allocator);\n    testQueueSubGetters(allocator);\n    testDrainingState(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/headers.zig",
    "content": "//! Headers API Integration Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\nconst headers = nats.protocol.headers;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\npub fn testHeadersPublishSingle(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_publish_single\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.single\") catch {\n        reportResult(\"headers_publish_single\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Test\", .value = \"hello\" },\n    };\n    client.publishWithHeaders(\"test.headers.single\", &hdrs, \"payload\") catch {\n        reportResult(\"headers_publish_single\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_publish_single\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_publish_single\", false, \"parse error\");\n            return;\n        }\n        if (parsed.get(\"X-Test\")) |val| {\n            if (std.mem.eql(u8, val, \"hello\")) {\n                reportResult(\"headers_publish_single\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_publish_single\", false, \"header mismatch\");\n    } else |_| {\n        reportResult(\"headers_publish_single\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersPublishMultiple(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_publish_multiple\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.multi\") catch {\n        reportResult(\"headers_publish_multiple\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-First\", .value = \"one\" },\n        .{ .key = \"X-Second\", .value = \"two\" },\n        .{ .key = \"X-Third\", .value = \"three\" },\n    };\n    client.publishWithHeaders(\"test.headers.multi\", &hdrs, \"data\") catch {\n        reportResult(\"headers_publish_multiple\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_publish_multiple\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_publish_multiple\", false, \"parse error\");\n            return;\n        }\n        if (parsed.count != 3) {\n            reportResult(\"headers_publish_multiple\", false, \"wrong count\");\n            return;\n        }\n        const has_first = parsed.get(\"X-First\") != null;\n        const has_second = parsed.get(\"X-Second\") != null;\n        const has_third = parsed.get(\"X-Third\") != null;\n        if (has_first and has_second and has_third) {\n            reportResult(\"headers_publish_multiple\", true, \"\");\n            return;\n        }\n        reportResult(\"headers_publish_multiple\", false, \"missing headers\");\n    } else |_| {\n        reportResult(\"headers_publish_multiple\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersPublishEmptyPayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_empty_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.empty\") catch {\n        reportResult(\"headers_empty_payload\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Empty\", .value = \"yes\" },\n    };\n    client.publishWithHeaders(\"test.headers.empty\", &hdrs, \"\") catch {\n        reportResult(\"headers_empty_payload\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_empty_payload\", false, \"no headers\");\n            return;\n        }\n        if (msg.data.len != 0) {\n            reportResult(\"headers_empty_payload\", false, \"payload not empty\");\n            return;\n        }\n        reportResult(\"headers_empty_payload\", true, \"\");\n    } else |_| {\n        reportResult(\"headers_empty_payload\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersPublishRequest(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_publish_request\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.req\") catch {\n        reportResult(\"headers_publish_request\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Request-Id\", .value = \"req-123\" },\n    };\n    client.publishRequestWithHeaders(\n        \"test.headers.req\",\n        \"reply.inbox\",\n        &hdrs,\n        \"request-data\",\n    ) catch {\n        reportResult(\"headers_publish_request\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_publish_request\", false, \"no headers\");\n            return;\n        }\n        if (msg.reply_to == null) {\n            reportResult(\"headers_publish_request\", false, \"no reply_to\");\n            return;\n        }\n        if (!std.mem.eql(u8, msg.reply_to.?, \"reply.inbox\")) {\n            reportResult(\"headers_publish_request\", false, \"wrong reply_to\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.get(\"X-Request-Id\")) |val| {\n            if (std.mem.eql(u8, val, \"req-123\")) {\n                reportResult(\"headers_publish_request\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_publish_request\", false, \"header mismatch\");\n    } else |_| {\n        reportResult(\"headers_publish_request\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersRequestReply(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    // Responder client\n    const io_r = utils.newIo(allocator);\n    defer io_r.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_r.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_request_reply\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_req.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_request_reply\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const sub = responder.subscribeSync(\"svc.headers\") catch {\n        reportResult(\"headers_request_reply\", false, \"responder sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_r.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const Handler = struct {\n        fn handle(\n            r: *nats.Client,\n            s: *nats.Subscription,\n        ) void {\n            if (s.nextMsgTimeout(2000) catch null) |req| {\n                defer req.deinit();\n                if (req.reply_to) |reply_inbox| {\n                    // Verify headers received\n                    if (req.headers != null) {\n                        r.publish(reply_inbox, \"headers-received\") catch {};\n                    } else {\n                        r.publish(reply_inbox, \"no-headers\") catch {};\n                    }\n                }\n            }\n        }\n    };\n\n    var handler = io_r.io().async(Handler.handle, .{\n        responder,\n        sub,\n    });\n    defer _ = handler.cancel(io_r.io());\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Request-Id\", .value = \"test-123\" },\n    };\n    const reply = requester.requestWithHeaders(\n        \"svc.headers\",\n        &hdrs,\n        \"ping\",\n        2000,\n    ) catch {\n        reportResult(\"headers_request_reply\", false, \"request failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"headers-received\")) {\n            reportResult(\"headers_request_reply\", true, \"\");\n            return;\n        }\n        reportResult(\"headers_request_reply\", false, \"headers not received\");\n        return;\n    }\n\n    reportResult(\"headers_request_reply\", false, \"no reply\");\n}\n\npub fn testHeadersRequestTimeout(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .no_responders = false,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"headers_request_timeout\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Test\", .value = \"timeout\" },\n    };\n\n    const start = std.Io.Timestamp.now(io.io(), .awake);\n\n    const result = client.requestWithHeaders(\n        \"nonexistent.headers.service\",\n        &hdrs,\n        \"ping\",\n        200,\n    ) catch {\n        reportResult(\n            \"headers_request_timeout\",\n            false,\n            \"request error\",\n        );\n        return;\n    };\n\n    const end = std.Io.Timestamp.now(io.io(), .awake);\n    const elapsed = start.durationTo(end);\n    const elapsed_ns: u64 = @intCast(elapsed.nanoseconds);\n    const elapsed_ms = elapsed_ns / std.time.ns_per_ms;\n\n    if (result) |msg| {\n        msg.deinit();\n    }\n\n    if (elapsed_ms < 5000) {\n        reportResult(\"headers_request_timeout\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"took {d}ms\",\n            .{elapsed_ms},\n        ) catch \"e\";\n        reportResult(\"headers_request_timeout\", false, detail);\n    }\n}\n\npub fn testHeadersCrossClient(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_a = utils.newIo(allocator);\n    defer io_a.deinit();\n    const client_a = nats.Client.connect(\n        allocator,\n        io_a.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_cross_client\", false, \"A connect failed\");\n        return;\n    };\n    defer client_a.deinit();\n\n    const io_b = utils.newIo(allocator);\n    defer io_b.deinit();\n    const client_b = nats.Client.connect(\n        allocator,\n        io_b.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_cross_client\", false, \"B connect failed\");\n        return;\n    };\n    defer client_b.deinit();\n\n    const sub = client_b.subscribeSync(\"cross.headers\") catch {\n        reportResult(\"headers_cross_client\", false, \"B sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_b.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-From\", .value = \"client-A\" },\n        .{ .key = \"X-Correlation-Id\", .value = \"corr-456\" },\n    };\n    client_a.publishWithHeaders(\"cross.headers\", &hdrs, \"cross-data\") catch {\n        reportResult(\"headers_cross_client\", false, \"A publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(2000) catch null) |msg| {\n        defer msg.deinit();\n        if (msg.headers == null) {\n            reportResult(\"headers_cross_client\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_cross_client\", false, \"parse error\");\n            return;\n        }\n        const from = parsed.get(\"X-From\");\n        const corr = parsed.get(\"X-Correlation-Id\");\n        if (from != null and corr != null) {\n            if (std.mem.eql(u8, from.?, \"client-A\") and\n                std.mem.eql(u8, corr.?, \"corr-456\"))\n            {\n                reportResult(\"headers_cross_client\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_cross_client\", false, \"header values wrong\");\n        return;\n    }\n\n    reportResult(\"headers_cross_client\", false, \"no message received\");\n}\n\npub fn testHeadersManyEntries(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_many_entries\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.many\") catch {\n        reportResult(\"headers_many_entries\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"H00\", .value = \"v00\" }, .{ .key = \"H01\", .value = \"v01\" },\n        .{ .key = \"H02\", .value = \"v02\" }, .{ .key = \"H03\", .value = \"v03\" },\n        .{ .key = \"H04\", .value = \"v04\" }, .{ .key = \"H05\", .value = \"v05\" },\n        .{ .key = \"H06\", .value = \"v06\" }, .{ .key = \"H07\", .value = \"v07\" },\n        .{ .key = \"H08\", .value = \"v08\" }, .{ .key = \"H09\", .value = \"v09\" },\n        .{ .key = \"H10\", .value = \"v10\" }, .{ .key = \"H11\", .value = \"v11\" },\n        .{ .key = \"H12\", .value = \"v12\" }, .{ .key = \"H13\", .value = \"v13\" },\n        .{ .key = \"H14\", .value = \"v14\" }, .{ .key = \"H15\", .value = \"v15\" },\n        .{ .key = \"H16\", .value = \"v16\" }, .{ .key = \"H17\", .value = \"v17\" },\n        .{ .key = \"H18\", .value = \"v18\" }, .{ .key = \"H19\", .value = \"v19\" },\n        .{ .key = \"H20\", .value = \"v20\" }, .{ .key = \"H21\", .value = \"v21\" },\n        .{ .key = \"H22\", .value = \"v22\" }, .{ .key = \"H23\", .value = \"v23\" },\n        .{ .key = \"H24\", .value = \"v24\" }, .{ .key = \"H25\", .value = \"v25\" },\n        .{ .key = \"H26\", .value = \"v26\" }, .{ .key = \"H27\", .value = \"v27\" },\n        .{ .key = \"H28\", .value = \"v28\" }, .{ .key = \"H29\", .value = \"v29\" },\n        .{ .key = \"H30\", .value = \"v30\" }, .{ .key = \"H31\", .value = \"v31\" },\n        .{ .key = \"H32\", .value = \"v32\" }, .{ .key = \"H33\", .value = \"v33\" },\n        .{ .key = \"H34\", .value = \"v34\" }, .{ .key = \"H35\", .value = \"v35\" },\n        .{ .key = \"H36\", .value = \"v36\" }, .{ .key = \"H37\", .value = \"v37\" },\n        .{ .key = \"H38\", .value = \"v38\" }, .{ .key = \"H39\", .value = \"v39\" },\n    };\n    client.publishWithHeaders(\"test.headers.many\", &hdrs, \"many-test\") catch {\n        reportResult(\"headers_many_entries\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_many_entries\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_many_entries\", false, \"parse error\");\n            return;\n        }\n        if (parsed.count != 40) {\n            var buf: [32]u8 = undefined;\n            const detail = std.fmt.bufPrint(\n                &buf,\n                \"got {d}/40\",\n                .{parsed.count},\n            ) catch \"e\";\n            reportResult(\"headers_many_entries\", false, detail);\n            return;\n        }\n        const first = parsed.get(\"H00\");\n        const last = parsed.get(\"H39\");\n        if (first != null and last != null) {\n            if (std.mem.eql(u8, first.?, \"v00\") and\n                std.mem.eql(u8, last.?, \"v39\"))\n            {\n                reportResult(\"headers_many_entries\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_many_entries\", false, \"header mismatch\");\n    } else |_| {\n        reportResult(\"headers_many_entries\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersLargeValues(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_large_values\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.large\") catch {\n        reportResult(\"headers_large_values\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    var large_value: [200]u8 = undefined;\n    @memset(&large_value, 'X');\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Large\", .value = &large_value },\n    };\n    client.publishWithHeaders(\"test.headers.large\", &hdrs, \"payload\") catch {\n        reportResult(\"headers_large_values\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_large_values\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_large_values\", false, \"parse error\");\n            return;\n        }\n        if (parsed.get(\"X-Large\")) |val| {\n            if (val.len == 200) {\n                reportResult(\"headers_large_values\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_large_values\", false, \"value mismatch\");\n    } else |_| {\n        reportResult(\"headers_large_values\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersSpecialChars(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_special_chars\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.special\") catch {\n        reportResult(\"headers_special_chars\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"X-Timestamp\", .value = \"2026-01-21T10:30:00Z\" },\n        .{ .key = \"X-URL\", .value = \"http://example.com:8080/path\" },\n    };\n    client.publishWithHeaders(\"test.headers.special\", &hdrs, \"data\") catch {\n        reportResult(\"headers_special_chars\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_special_chars\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_special_chars\", false, \"parse error\");\n            return;\n        }\n        const ts = parsed.get(\"X-Timestamp\");\n        const url_val = parsed.get(\"X-URL\");\n        if (ts != null and url_val != null) {\n            const ts_ok = std.mem.eql(u8, ts.?, \"2026-01-21T10:30:00Z\");\n            const url_ok = std.mem.eql(\n                u8,\n                url_val.?,\n                \"http://example.com:8080/path\",\n            );\n            if (ts_ok and url_ok) {\n                reportResult(\"headers_special_chars\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_special_chars\", false, \"value mismatch\");\n    } else |_| {\n        reportResult(\"headers_special_chars\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersBinaryPayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_binary_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.binary\") catch {\n        reportResult(\"headers_binary_payload\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const binary_payload = [_]u8{ 0x00, 0x01, 0xFF, 0xFE, 0x7F, 0x80, 0x00, 0xFF };\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"Content-Type\", .value = \"application/octet-stream\" },\n    };\n    client.publishWithHeaders(\n        \"test.headers.binary\",\n        &hdrs,\n        &binary_payload,\n    ) catch {\n        reportResult(\"headers_binary_payload\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_binary_payload\", false, \"no headers\");\n            return;\n        }\n        if (msg.data.len != 8) {\n            reportResult(\"headers_binary_payload\", false, \"wrong payload len\");\n            return;\n        }\n        if (std.mem.eql(u8, msg.data, &binary_payload)) {\n            reportResult(\"headers_binary_payload\", true, \"\");\n            return;\n        }\n        reportResult(\"headers_binary_payload\", false, \"payload mismatch\");\n    } else |_| {\n        reportResult(\"headers_binary_payload\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersWellKnown(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_well_known\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.wellknown\") catch {\n        reportResult(\"headers_well_known\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = headers.HeaderName.msg_id, .value = \"unique-msg-001\" },\n    };\n    client.publishWithHeaders(\"test.headers.wellknown\", &hdrs, \"data\") catch {\n        reportResult(\"headers_well_known\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_well_known\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_well_known\", false, \"parse error\");\n            return;\n        }\n        if (parsed.get(headers.HeaderName.msg_id)) |val| {\n            if (std.mem.eql(u8, val, \"unique-msg-001\")) {\n                reportResult(\"headers_well_known\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"headers_well_known\", false, \"header not found\");\n    } else |_| {\n        reportResult(\"headers_well_known\", false, \"receive failed\");\n    }\n}\n\npub fn testHeadersCaseInsensitive(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"headers_case_insensitive\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"test.headers.case\") catch {\n        reportResult(\"headers_case_insensitive\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    const hdrs = [_]headers.Entry{\n        .{ .key = \"Content-Type\", .value = \"application/json\" },\n    };\n    client.publishWithHeaders(\"test.headers.case\", &hdrs, \"{}\") catch {\n        reportResult(\"headers_case_insensitive\", false, \"publish failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.headers == null) {\n            reportResult(\"headers_case_insensitive\", false, \"no headers\");\n            return;\n        }\n        var parsed = headers.parse(allocator, msg.headers.?);\n        defer parsed.deinit();\n        if (parsed.err != null) {\n            reportResult(\"headers_case_insensitive\", false, \"parse error\");\n            return;\n        }\n        // Lookup with different case\n        const val1 = parsed.get(\"content-type\");\n        const val2 = parsed.get(\"CONTENT-TYPE\");\n        const val3 = parsed.get(\"Content-Type\");\n        if (val1 != null and val2 != null and val3 != null) {\n            reportResult(\"headers_case_insensitive\", true, \"\");\n            return;\n        }\n        reportResult(\"headers_case_insensitive\", false, \"case mismatch\");\n    } else |_| {\n        reportResult(\"headers_case_insensitive\", false, \"receive failed\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testHeadersPublishSingle(allocator);\n    testHeadersPublishMultiple(allocator);\n    testHeadersPublishEmptyPayload(allocator);\n    testHeadersPublishRequest(allocator);\n    testHeadersRequestReply(allocator);\n    testHeadersRequestTimeout(allocator);\n    testHeadersCrossClient(allocator);\n    testHeadersManyEntries(allocator);\n    testHeadersLargeValues(allocator);\n    testHeadersSpecialChars(allocator);\n    testHeadersBinaryPayload(allocator);\n    testHeadersWellKnown(allocator);\n    testHeadersCaseInsensitive(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/jetstream.zig",
    "content": "//! JetStream Integration Tests\n//!\n//! End-to-end tests for JetStream stream/consumer CRUD,\n//! publish with ack, and pull-based fetch.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst reportError = utils.reportError;\nconst formatUrl = utils.formatUrl;\nconst ServerManager = utils.ServerManager;\nconst TestServer = utils.server_manager.TestServer;\n\nconst js_port = utils.jetstream_port;\nconst js_reconnect_port: u16 = 14240;\nconst test_js_timeout_ms: u32 = 15_000;\n\nfn initTestJetStream(\n    client: *nats.Client,\n) nats.jetstream.JetStream {\n    return nats.jetstream.JetStream.init(client, .{\n        .timeout_ms = test_js_timeout_ms,\n    }) catch unreachable;\n}\n\nfn threadSleepNs(ns: u64) void {\n    var ts: std.posix.timespec = .{\n        .sec = @intCast(ns / 1_000_000_000),\n        .nsec = @intCast(ns % 1_000_000_000),\n    };\n    _ = std.posix.system.nanosleep(&ts, &ts);\n}\n\nvar push_heartbeat_err_seen =\n    std.atomic.Value(bool).init(false);\n\nfn deleteStreamIfExists(\n    js: *nats.jetstream.JetStream,\n    name: []const u8,\n) void {\n    var d = js.deleteStream(name) catch return;\n    d.deinit();\n}\n\nfn waitForConnected(\n    io: std.Io,\n    client: *nats.Client,\n    timeout_ms: u32,\n) bool {\n    var waited: u32 = 0;\n    while (waited < timeout_ms) : (waited += 25) {\n        if (client.isConnected()) return true;\n        io.sleep(.fromMilliseconds(25), .awake) catch {};\n    }\n    return client.isConnected();\n}\n\nfn startJsReconnectServer(\n    allocator: std.mem.Allocator,\n    io: std.Io,\n) !TestServer {\n    return TestServer.start(allocator, io, .{\n        .port = js_reconnect_port,\n        .jetstream = true,\n    });\n}\n\nfn restartJsReconnectServer(\n    allocator: std.mem.Allocator,\n    io: std.Io,\n    server: *TestServer,\n    client: *nats.Client,\n    name: []const u8,\n) bool {\n    server.stop(io);\n    client.forceReconnect() catch {};\n\n    server.* = startJsReconnectServer(allocator, io) catch {\n        reportResult(name, false, \"restart server\");\n        return false;\n    };\n\n    if (!waitForConnected(io, client, 5000)) {\n        reportResult(name, false, \"reconnect timeout\");\n        return false;\n    }\n\n    return true;\n}\n\nfn startSharedJsServer(\n    allocator: std.mem.Allocator,\n    io: std.Io,\n) !TestServer {\n    return TestServer.start(allocator, io, .{\n        .port = js_port,\n        .jetstream = true,\n    });\n}\n\nfn restartSharedJsServer(\n    allocator: std.mem.Allocator,\n    io: std.Io,\n    server: *TestServer,\n    name: []const u8,\n) bool {\n    server.stop(io);\n    server.* = startSharedJsServer(allocator, io) catch {\n        reportResult(name, false, \"restart JS server\");\n        return false;\n    };\n    return true;\n}\n\nfn pushHeartbeatErrHandler(err: anyerror) void {\n    if (err == error.NoHeartbeat) {\n        push_heartbeat_err_seen.store(true, .release);\n    }\n}\n\npub fn testStreamCreateAndInfo(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_stream_create\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream\n    var resp = js.createStream(.{\n        .name = \"TEST_CREATE\",\n        .subjects = &.{\"test.create.>\"},\n        .storage = .memory,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"create failed: {}\",\n            .{err},\n        ) catch \"error\";\n        reportResult(\"js_stream_create\", false, msg);\n        return;\n    };\n    defer resp.deinit();\n\n    if (resp.value.config) |cfg| {\n        if (!std.mem.eql(\n            u8,\n            cfg.name,\n            \"TEST_CREATE\",\n        )) {\n            reportResult(\n                \"js_stream_create\",\n                false,\n                \"wrong name\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_stream_create\",\n            false,\n            \"no config\",\n        );\n        return;\n    }\n\n    // Get stream info\n    var info = js.streamInfo(\n        \"TEST_CREATE\",\n    ) catch {\n        reportResult(\n            \"js_stream_create\",\n            false,\n            \"info failed\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.state) |state| {\n        if (state.messages != 0) {\n            reportResult(\n                \"js_stream_create\",\n                false,\n                \"expected 0 msgs\",\n            );\n            return;\n        }\n    }\n\n    // Cleanup\n    var del = js.deleteStream(\n        \"TEST_CREATE\",\n    ) catch {\n        reportResult(\n            \"js_stream_create\",\n            false,\n            \"delete failed\",\n        );\n        return;\n    };\n    defer del.deinit();\n\n    if (!del.value.success) {\n        reportResult(\n            \"js_stream_create\",\n            false,\n            \"delete not success\",\n        );\n        return;\n    }\n\n    reportResult(\"js_stream_create\", true, \"\");\n}\n\npub fn testPublishAndAck(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream\n    var stream = js.createStream(.{\n        .name = \"TEST_PUB\",\n        .subjects = &.{\"test.pub.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish\n    var ack = js.publish(\n        \"test.pub.hello\",\n        \"hello world\",\n    ) catch |err| {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"publish failed: {}\",\n            .{err},\n        ) catch \"error\";\n        reportResult(\"js_publish_ack\", false, msg);\n        return;\n    };\n    defer ack.deinit();\n\n    if (ack.value.seq != 1) {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"expected seq 1\",\n        );\n        return;\n    }\n\n    if (ack.value.stream) |s| {\n        if (!std.mem.eql(u8, s, \"TEST_PUB\")) {\n            reportResult(\n                \"js_publish_ack\",\n                false,\n                \"wrong stream\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"no stream in ack\",\n        );\n        return;\n    }\n\n    // Publish second message\n    var ack2 = js.publish(\n        \"test.pub.world\",\n        \"second\",\n    ) catch {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"publish 2 failed\",\n        );\n        return;\n    };\n    defer ack2.deinit();\n\n    if (ack2.value.seq != 2) {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"expected seq 2\",\n        );\n        return;\n    }\n\n    // Cleanup\n    var del = js.deleteStream(\"TEST_PUB\") catch {\n        reportResult(\n            \"js_publish_ack\",\n            false,\n            \"delete failed\",\n        );\n        return;\n    };\n    defer del.deinit();\n\n    reportResult(\"js_publish_ack\", true, \"\");\n}\n\npub fn testConsumerCRUD(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream\n    var stream = js.createStream(.{\n        .name = \"TEST_CONS\",\n        .subjects = &.{\"test.cons.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Create consumer\n    var cons = js.createConsumer(\n        \"TEST_CONS\",\n        .{\n            .name = \"my-consumer\",\n            .durable_name = \"my-consumer\",\n            .ack_policy = .explicit,\n        },\n    ) catch |err| {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"create consumer: {}\",\n            .{err},\n        ) catch \"error\";\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            msg,\n        );\n        return;\n    };\n    defer cons.deinit();\n\n    if (cons.value.name) |n| {\n        if (!std.mem.eql(u8, n, \"my-consumer\")) {\n            reportResult(\n                \"js_consumer_crud\",\n                false,\n                \"wrong consumer name\",\n            );\n            return;\n        }\n    }\n\n    // Consumer info\n    var info = js.consumerInfo(\n        \"TEST_CONS\",\n        \"my-consumer\",\n    ) catch {\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            \"info failed\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    // Delete consumer\n    var del_c = js.deleteConsumer(\n        \"TEST_CONS\",\n        \"my-consumer\",\n    ) catch {\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            \"delete consumer failed\",\n        );\n        return;\n    };\n    defer del_c.deinit();\n\n    if (!del_c.value.success) {\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            \"delete not success\",\n        );\n        return;\n    }\n\n    // Cleanup stream\n    var del_s = js.deleteStream(\"TEST_CONS\") catch {\n        reportResult(\n            \"js_consumer_crud\",\n            false,\n            \"delete stream failed\",\n        );\n        return;\n    };\n    defer del_s.deinit();\n\n    reportResult(\"js_consumer_crud\", true, \"\");\n}\n\npub fn testApiError(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_api_error\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Try to get info for non-existent stream\n    var info = js.streamInfo(\"NONEXISTENT\");\n    if (info) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_api_error\",\n            false,\n            \"expected error\",\n        );\n        return;\n    } else |err| {\n        if (err != error.ApiError) {\n            reportResult(\n                \"js_api_error\",\n                false,\n                \"wrong error type\",\n            );\n            return;\n        }\n    }\n\n    // Check last API error\n    if (js.lastApiError()) |api_err| {\n        if (api_err.err_code !=\n            nats.jetstream.errors.ErrCode.stream_not_found)\n        {\n            reportResult(\n                \"js_api_error\",\n                false,\n                \"wrong err_code\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_api_error\",\n            false,\n            \"no last api error\",\n        );\n        return;\n    }\n\n    reportResult(\"js_api_error\", true, \"\");\n}\n\npub fn testStreamNames(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create 3 streams\n    var s1 = js.createStream(.{\n        .name = \"NAMES_A\",\n        .subjects = &.{\"names.a.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"create A failed\",\n        );\n        return;\n    };\n    defer s1.deinit();\n\n    var s2 = js.createStream(.{\n        .name = \"NAMES_B\",\n        .subjects = &.{\"names.b.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"create B failed\",\n        );\n        return;\n    };\n    defer s2.deinit();\n\n    var s3 = js.createStream(.{\n        .name = \"NAMES_C\",\n        .subjects = &.{\"names.c.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"create C failed\",\n        );\n        return;\n    };\n    defer s3.deinit();\n\n    // List names\n    var resp = js.streamNames() catch {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"list failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    const names = resp.value.streams orelse {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"no streams\",\n        );\n        return;\n    };\n\n    if (names.len < 3) {\n        reportResult(\n            \"js_stream_names\",\n            false,\n            \"expected >= 3 streams\",\n        );\n        return;\n    }\n\n    // Cleanup\n    {\n        var d1 = js.deleteStream(\"NAMES_A\") catch {\n            reportResult(\"js_stream_names\", true, \"\");\n            return;\n        };\n        d1.deinit();\n    }\n    {\n        var d2 = js.deleteStream(\"NAMES_B\") catch {\n            reportResult(\"js_stream_names\", true, \"\");\n            return;\n        };\n        d2.deinit();\n    }\n    {\n        var d3 = js.deleteStream(\"NAMES_C\") catch {\n            reportResult(\"js_stream_names\", true, \"\");\n            return;\n        };\n        d3.deinit();\n    }\n\n    reportResult(\"js_stream_names\", true, \"\");\n}\n\npub fn testStreamList(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_stream_list\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s1 = js.createStream(.{\n        .name = \"LIST_A\",\n        .subjects = &.{\"list.a.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_stream_list\",\n            false,\n            \"create failed\",\n        );\n        return;\n    };\n    defer s1.deinit();\n\n    var resp = js.streams() catch {\n        reportResult(\n            \"js_stream_list\",\n            false,\n            \"list failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    const streams = resp.value.streams orelse {\n        reportResult(\n            \"js_stream_list\",\n            false,\n            \"no streams\",\n        );\n        return;\n    };\n\n    if (streams.len < 1) {\n        reportResult(\n            \"js_stream_list\",\n            false,\n            \"expected >= 1\",\n        );\n        return;\n    }\n\n    // Verify we get StreamInfo with config\n    var found = false;\n    for (streams) |si| {\n        if (si.config) |cfg| {\n            if (std.mem.eql(u8, cfg.name, \"LIST_A\")) {\n                found = true;\n                break;\n            }\n        }\n    }\n    if (!found) {\n        reportResult(\n            \"js_stream_list\",\n            false,\n            \"LIST_A not found\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"LIST_A\") catch {\n        reportResult(\"js_stream_list\", true, \"\");\n        return;\n    };\n    d.deinit();\n\n    reportResult(\"js_stream_list\", true, \"\");\n}\n\npub fn testConsumerNames(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"CONS_NAMES\",\n        .subjects = &.{\"cnames.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var c1 = js.createConsumer(\n        \"CONS_NAMES\",\n        .{\n            .name = \"cons-alpha\",\n            .durable_name = \"cons-alpha\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"create cons failed\",\n        );\n        return;\n    };\n    defer c1.deinit();\n\n    var c2 = js.createConsumer(\n        \"CONS_NAMES\",\n        .{\n            .name = \"cons-beta\",\n            .durable_name = \"cons-beta\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"create cons2 failed\",\n        );\n        return;\n    };\n    defer c2.deinit();\n\n    var resp = js.consumerNames(\n        \"CONS_NAMES\",\n    ) catch {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"list failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    const names = resp.value.consumers orelse {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"no consumers\",\n        );\n        return;\n    };\n\n    if (names.len < 2) {\n        reportResult(\n            \"js_consumer_names\",\n            false,\n            \"expected >= 2\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"CONS_NAMES\") catch {\n        reportResult(\"js_consumer_names\", true, \"\");\n        return;\n    };\n    d.deinit();\n\n    reportResult(\"js_consumer_names\", true, \"\");\n}\n\npub fn testConsumerList(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"CONS_LIST\",\n        .subjects = &.{\"clist.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var c1 = js.createConsumer(\n        \"CONS_LIST\",\n        .{\n            .name = \"list-cons\",\n            .durable_name = \"list-cons\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"create cons failed\",\n        );\n        return;\n    };\n    defer c1.deinit();\n\n    var resp = js.consumers(\"CONS_LIST\") catch {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"list failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    const consumers = resp.value.consumers orelse {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"no consumers\",\n        );\n        return;\n    };\n\n    if (consumers.len < 1) {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"expected >= 1\",\n        );\n        return;\n    }\n\n    // Verify ConsumerInfo has config\n    if (consumers[0].config) |cfg| {\n        if (cfg.name) |n| {\n            if (!std.mem.eql(u8, n, \"list-cons\")) {\n                reportResult(\n                    \"js_consumer_list\",\n                    false,\n                    \"wrong name\",\n                );\n                return;\n            }\n        }\n    } else {\n        reportResult(\n            \"js_consumer_list\",\n            false,\n            \"no config\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"CONS_LIST\") catch {\n        reportResult(\"js_consumer_list\", true, \"\");\n        return;\n    };\n    d.deinit();\n\n    reportResult(\"js_consumer_list\", true, \"\");\n}\n\npub fn testAccountInfo(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_account_info\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var resp = js.accountInfo() catch {\n        reportResult(\n            \"js_account_info\",\n            false,\n            \"request failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    if (resp.value.limits == null) {\n        reportResult(\n            \"js_account_info\",\n            false,\n            \"no limits\",\n        );\n        return;\n    }\n\n    reportResult(\"js_account_info\", true, \"\");\n}\n\npub fn testMetadata(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream + consumer\n    var stream = js.createStream(.{\n        .name = \"TEST_META\",\n        .subjects = &.{\"test.meta.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var cons = js.createConsumer(\n        \"TEST_META\",\n        .{\n            .name = \"meta-cons\",\n            .durable_name = \"meta-cons\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"create consumer failed\",\n        );\n        return;\n    };\n    defer cons.deinit();\n\n    // Publish\n    var ack = js.publish(\n        \"test.meta.hello\",\n        \"metadata test\",\n    ) catch {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n    defer ack.deinit();\n\n    // Fetch and check metadata\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_META\",\n    };\n    pull.setConsumer(\"meta-cons\") catch unreachable;\n\n    var msg = (pull.next(5000) catch {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"next failed\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"no message\",\n        );\n        return;\n    };\n    defer msg.deinit();\n\n    const md = msg.metadata() orelse {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"no metadata\",\n        );\n        return;\n    };\n\n    if (!std.mem.eql(u8, md.stream, \"TEST_META\")) {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"wrong stream\",\n        );\n        return;\n    }\n    if (!std.mem.eql(u8, md.consumer, \"meta-cons\")) {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"wrong consumer\",\n        );\n        return;\n    }\n    if (md.stream_seq != 1) {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"expected seq 1\",\n        );\n        return;\n    }\n\n    // Cleanup\n    var d = js.deleteStream(\"TEST_META\") catch {\n        reportResult(\n            \"js_metadata\",\n            false,\n            \"delete failed\",\n        );\n        return;\n    };\n    defer d.deinit();\n\n    reportResult(\"js_metadata\", true, \"\");\n}\n\npub fn testFetchNoWait(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_fetch_no_wait\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_NOWAIT\",\n        .subjects = &.{\"test.nowait.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_fetch_no_wait\",\n            false,\n            \"create failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var cons = js.createConsumer(\n        \"TEST_NOWAIT\",\n        .{\n            .name = \"nowait-cons\",\n            .durable_name = \"nowait-cons\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_fetch_no_wait\",\n            false,\n            \"create consumer failed\",\n        );\n        return;\n    };\n    defer cons.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_NOWAIT\",\n    };\n    pull.setConsumer(\"nowait-cons\") catch unreachable;\n\n    // Fetch no-wait on empty consumer -> 0 messages\n    var result = pull.fetchNoWait(10) catch {\n        reportResult(\n            \"js_fetch_no_wait\",\n            false,\n            \"fetchNoWait failed\",\n        );\n        return;\n    };\n    defer result.deinit();\n\n    if (result.count() != 0) {\n        reportResult(\n            \"js_fetch_no_wait\",\n            false,\n            \"expected 0 messages\",\n        );\n        return;\n    }\n\n    // Cleanup\n    var d = js.deleteStream(\"TEST_NOWAIT\") catch {\n        reportResult(\n            \"js_fetch_no_wait\",\n            false,\n            \"delete failed\",\n        );\n        return;\n    };\n    defer d.deinit();\n\n    reportResult(\"js_fetch_no_wait\", true, \"\");\n}\n\npub fn testMessages(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_messages\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_MSGS\",\n        .subjects = &.{\"test.msgs.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_messages\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var cons = js.createConsumer(\n        \"TEST_MSGS\",\n        .{\n            .name = \"msgs-cons\",\n            .durable_name = \"msgs-cons\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_messages\",\n            false,\n            \"create consumer failed\",\n        );\n        return;\n    };\n    defer cons.deinit();\n\n    // Publish 5 messages\n    var i: u32 = 0;\n    while (i < 5) : (i += 1) {\n        var a = js.publish(\n            \"test.msgs.data\",\n            \"hello\",\n        ) catch {\n            reportResult(\n                \"js_messages\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Use messages iterator\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_MSGS\",\n    };\n    pull.setConsumer(\"msgs-cons\") catch unreachable;\n\n    var ctx = pull.messages(.{\n        .max_messages = 10,\n        .expires_ms = 5000,\n    }) catch {\n        reportResult(\n            \"js_messages\",\n            false,\n            \"messages() failed\",\n        );\n        return;\n    };\n    defer ctx.deinit();\n\n    var received: u32 = 0;\n    while (received < 5) {\n        var msg = (ctx.next() catch {\n            break;\n        }) orelse break;\n        msg.ack() catch {};\n        msg.deinit();\n        received += 1;\n    }\n\n    if (received != 5) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 5\",\n            .{received},\n        ) catch \"count mismatch\";\n        reportResult(\"js_messages\", false, m);\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_MSGS\") catch {\n        reportResult(\"js_messages\", true, \"\");\n        return;\n    };\n    d.deinit();\n\n    reportResult(\"js_messages\", true, \"\");\n}\n\npub fn testConsume(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_consume\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_CONSUME\",\n        .subjects = &.{\"test.consume.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_consume\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var cons = js.createConsumer(\n        \"TEST_CONSUME\",\n        .{\n            .name = \"consume-cons\",\n            .durable_name = \"consume-cons\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_consume\",\n            false,\n            \"create consumer failed\",\n        );\n        return;\n    };\n    defer cons.deinit();\n\n    // Publish 10 messages\n    var i: u32 = 0;\n    while (i < 10) : (i += 1) {\n        var a = js.publish(\n            \"test.consume.data\",\n            \"consume-test\",\n        ) catch {\n            reportResult(\n                \"js_consume\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Use consume() with callback handler\n    const Counter = struct {\n        count: u32 = 0,\n        pub fn onMessage(\n            self: *@This(),\n            msg: *nats.jetstream.JsMsg,\n        ) void {\n            msg.ack() catch {};\n            self.count += 1;\n        }\n    };\n\n    var counter = Counter{};\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_CONSUME\",\n    };\n    pull.setConsumer(\"consume-cons\") catch unreachable;\n\n    var ctx = pull.consume(\n        nats.jetstream.JsMsgHandler.init(\n            Counter,\n            &counter,\n        ),\n        .{\n            .max_messages = 10,\n            .expires_ms = 5000,\n        },\n    ) catch {\n        reportResult(\n            \"js_consume\",\n            false,\n            \"consume() failed\",\n        );\n        return;\n    };\n\n    // Wait for messages to be consumed\n    var wait: u32 = 0;\n    while (counter.count < 10 and wait < 50) : (wait += 1) {\n        threadSleepNs(100_000_000);\n    }\n\n    ctx.stop();\n    ctx.deinit();\n\n    if (counter.count < 10) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 10\",\n            .{counter.count},\n        ) catch \"count mismatch\";\n        reportResult(\"js_consume\", false, m);\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_CONSUME\") catch {\n        reportResult(\"js_consume\", true, \"\");\n        return;\n    };\n    d.deinit();\n\n    reportResult(\"js_consume\", true, \"\");\n}\n\npub fn testOrderedConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_ordered\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_ORDERED\",\n        .subjects = &.{\"test.ordered.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_ordered\",\n            false,\n            \"create stream failed\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish 5 messages\n    var i: u32 = 0;\n    while (i < 5) : (i += 1) {\n        var a = js.publish(\n            \"test.ordered.data\",\n            \"ordered-msg\",\n        ) catch {\n            reportResult(\n                \"js_ordered\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Create ordered consumer\n    var oc = nats.jetstream.OrderedConsumer.init(\n        &js,\n        \"TEST_ORDERED\",\n        .{\n            .filter_subject = \"test.ordered.>\",\n            .deliver_policy = .all,\n        },\n    );\n    defer oc.deinit();\n\n    // Fetch all 5 messages in order\n    var received: u32 = 0;\n    var last_seq: u64 = 0;\n    while (received < 5) {\n        var msg = (oc.next(5000) catch {\n            break;\n        }) orelse break;\n\n        // Verify ordering\n        if (msg.metadata()) |md| {\n            if (md.stream_seq <= last_seq) {\n                reportResult(\n                    \"js_ordered\",\n                    false,\n                    \"out of order\",\n                );\n                msg.deinit();\n                return;\n            }\n            last_seq = md.stream_seq;\n        }\n\n        msg.deinit();\n        received += 1;\n    }\n\n    if (received != 5) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 5\",\n            .{received},\n        ) catch \"count mismatch\";\n        reportResult(\"js_ordered\", false, m);\n        return;\n    }\n\n    // Verify stream_seq tracked correctly\n    if (oc.stream_seq != 5) {\n        reportResult(\n            \"js_ordered\",\n            false,\n            \"wrong stream_seq\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_ORDERED\") catch {\n        reportResult(\"js_ordered\", true, \"\");\n        return;\n    };\n    d.deinit();\n\n    reportResult(\"js_ordered\", true, \"\");\n}\n\n// -- Ack protocol tests --\n\npub fn testAckPreventsRedeliver(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_ack\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_ACK\",\n        .subjects = &.{\"test.ack.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\"js_ack\", false, \"create stream\");\n        return;\n    };\n    defer s.deinit();\n\n    var c = js.createConsumer(\"TEST_ACK\", .{\n        .name = \"ack-cons\",\n        .durable_name = \"ack-cons\",\n        .ack_policy = .explicit,\n        .ack_wait = 1_000_000_000,\n    }) catch {\n        reportResult(\n            \"js_ack\",\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    // Publish 1 message\n    var a = js.publish(\n        \"test.ack.one\",\n        \"ack-test\",\n    ) catch {\n        reportResult(\"js_ack\", false, \"publish\");\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_ACK\",\n    };\n    pull.setConsumer(\"ack-cons\") catch unreachable;\n\n    // Fetch and ACK\n    var msg = (pull.next(5000) catch {\n        reportResult(\"js_ack\", false, \"fetch 1\");\n        return;\n    }) orelse {\n        reportResult(\"js_ack\", false, \"no msg 1\");\n        return;\n    };\n    msg.ack() catch {\n        reportResult(\"js_ack\", false, \"ack failed\");\n        msg.deinit();\n        return;\n    };\n    msg.deinit();\n\n    // Fetch again -> should be empty (acked)\n    var result = pull.fetchNoWait(10) catch {\n        reportResult(\"js_ack\", false, \"fetch 2\");\n        return;\n    };\n    defer result.deinit();\n\n    if (result.count() != 0) {\n        reportResult(\n            \"js_ack\",\n            false,\n            \"expected 0 after ack\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_ACK\") catch {\n        reportResult(\"js_ack\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_ack\", true, \"\");\n}\n\npub fn testNakCausesRedeliver(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_nak\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_NAK\",\n        .subjects = &.{\"test.nak.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\"js_nak\", false, \"create stream\");\n        return;\n    };\n    defer s.deinit();\n\n    var c = js.createConsumer(\"TEST_NAK\", .{\n        .name = \"nak-cons\",\n        .durable_name = \"nak-cons\",\n        .ack_policy = .explicit,\n        .ack_wait = 2_000_000_000,\n        .max_deliver = 3,\n    }) catch {\n        reportResult(\n            \"js_nak\",\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    var a = js.publish(\n        \"test.nak.one\",\n        \"nak-test\",\n    ) catch {\n        reportResult(\"js_nak\", false, \"publish\");\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_NAK\",\n    };\n    pull.setConsumer(\"nak-cons\") catch unreachable;\n\n    // Fetch and NAK\n    var msg1 = (pull.next(5000) catch {\n        reportResult(\"js_nak\", false, \"fetch 1\");\n        return;\n    }) orelse {\n        reportResult(\"js_nak\", false, \"no msg 1\");\n        return;\n    };\n    msg1.nak() catch {\n        reportResult(\"js_nak\", false, \"nak failed\");\n        msg1.deinit();\n        return;\n    };\n    msg1.deinit();\n\n    // Fetch again -> should get redelivered message\n    var msg2 = (pull.next(5000) catch {\n        reportResult(\"js_nak\", false, \"fetch 2\");\n        return;\n    }) orelse {\n        reportResult(\n            \"js_nak\",\n            false,\n            \"no redeliver\",\n        );\n        return;\n    };\n\n    // Verify it's the same data\n    if (!std.mem.eql(u8, msg2.data(), \"nak-test\")) {\n        reportResult(\n            \"js_nak\",\n            false,\n            \"wrong redeliver data\",\n        );\n        msg2.deinit();\n        return;\n    }\n\n    // Verify num_delivered > 1\n    if (msg2.metadata()) |md| {\n        if (md.num_delivered < 2) {\n            reportResult(\n                \"js_nak\",\n                false,\n                \"expected redeliver count\",\n            );\n            msg2.deinit();\n            return;\n        }\n    }\n\n    msg2.ack() catch {};\n    msg2.deinit();\n\n    var d = js.deleteStream(\"TEST_NAK\") catch {\n        reportResult(\"js_nak\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_nak\", true, \"\");\n}\n\npub fn testTermStopsRedeliver(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_term\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_TERM\",\n        .subjects = &.{\"test.term.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_term\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var c = js.createConsumer(\"TEST_TERM\", .{\n        .name = \"term-cons\",\n        .durable_name = \"term-cons\",\n        .ack_policy = .explicit,\n        .max_deliver = 5,\n    }) catch {\n        reportResult(\n            \"js_term\",\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    var a = js.publish(\n        \"test.term.one\",\n        \"term-test\",\n    ) catch {\n        reportResult(\"js_term\", false, \"publish\");\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_TERM\",\n    };\n    pull.setConsumer(\"term-cons\") catch unreachable;\n\n    // Fetch and TERM\n    var msg = (pull.next(5000) catch {\n        reportResult(\"js_term\", false, \"fetch\");\n        return;\n    }) orelse {\n        reportResult(\"js_term\", false, \"no msg\");\n        return;\n    };\n    msg.term() catch {\n        reportResult(\"js_term\", false, \"term failed\");\n        msg.deinit();\n        return;\n    };\n    msg.deinit();\n\n    // Fetch again -> should be empty (terminated)\n    var result = pull.fetchNoWait(10) catch {\n        reportResult(\"js_term\", false, \"fetch 2\");\n        return;\n    };\n    defer result.deinit();\n\n    if (result.count() != 0) {\n        reportResult(\n            \"js_term\",\n            false,\n            \"expected 0 after term\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_TERM\") catch {\n        reportResult(\"js_term\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_term\", true, \"\");\n}\n\n// -- Batch fetch tests --\n\npub fn testBatchFetch(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_batch\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_BATCH\",\n        .subjects = &.{\"test.batch.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_batch\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var c = js.createConsumer(\"TEST_BATCH\", .{\n        .name = \"batch-cons\",\n        .durable_name = \"batch-cons\",\n        .ack_policy = .explicit,\n    }) catch {\n        reportResult(\n            \"js_batch\",\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    // Publish 10 messages\n    var i: u32 = 0;\n    while (i < 10) : (i += 1) {\n        var a = js.publish(\n            \"test.batch.data\",\n            \"batch-msg\",\n        ) catch {\n            reportResult(\n                \"js_batch\",\n                false,\n                \"publish\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_BATCH\",\n    };\n    pull.setConsumer(\"batch-cons\") catch unreachable;\n\n    // Fetch batch of 5\n    var r1 = pull.fetch(.{\n        .max_messages = 5,\n        .timeout_ms = 5000,\n    }) catch {\n        reportResult(\"js_batch\", false, \"fetch 1\");\n        return;\n    };\n\n    if (r1.count() != 5) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"batch1: got {d}\",\n            .{r1.count()},\n        ) catch \"wrong\";\n        reportResult(\"js_batch\", false, m);\n        r1.deinit();\n        return;\n    }\n\n    // Ack all in first batch\n    for (r1.messages) |*msg| {\n        msg.ack() catch {};\n    }\n    r1.deinit();\n\n    // Fetch remaining 5\n    var r2 = pull.fetch(.{\n        .max_messages = 5,\n        .timeout_ms = 5000,\n    }) catch {\n        reportResult(\"js_batch\", false, \"fetch 2\");\n        return;\n    };\n\n    if (r2.count() != 5) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"batch2: got {d}\",\n            .{r2.count()},\n        ) catch \"wrong\";\n        reportResult(\"js_batch\", false, m);\n        r2.deinit();\n        return;\n    }\n\n    for (r2.messages) |*msg| {\n        msg.ack() catch {};\n    }\n    r2.deinit();\n\n    // Fetch again -> should be empty\n    var r3 = pull.fetchNoWait(10) catch {\n        reportResult(\"js_batch\", false, \"fetch 3\");\n        return;\n    };\n    defer r3.deinit();\n\n    if (r3.count() != 0) {\n        reportResult(\n            \"js_batch\",\n            false,\n            \"expected 0 after all acked\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_BATCH\") catch {\n        reportResult(\"js_batch\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_batch\", true, \"\");\n}\n\n// -- Publish options tests --\n\npub fn testPublishDedup(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_dedup\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_DEDUP\",\n        .subjects = &.{\"test.dedup.>\"},\n        .storage = .memory,\n        .duplicate_window = 60_000_000_000,\n    }) catch {\n        reportResult(\n            \"js_dedup\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Publish with same msg-id twice\n    var a1 = js.publishWithOpts(\n        \"test.dedup.data\",\n        \"first\",\n        .{ .msg_id = \"unique-1\" },\n    ) catch {\n        reportResult(\"js_dedup\", false, \"pub 1\");\n        return;\n    };\n    const seq1 = a1.value.seq;\n    a1.deinit();\n\n    var a2 = js.publishWithOpts(\n        \"test.dedup.data\",\n        \"duplicate\",\n        .{ .msg_id = \"unique-1\" },\n    ) catch {\n        reportResult(\"js_dedup\", false, \"pub 2\");\n        return;\n    };\n\n    // Should get same seq (deduplicated)\n    if (a2.value.seq != seq1) {\n        reportResult(\n            \"js_dedup\",\n            false,\n            \"not deduped\",\n        );\n        a2.deinit();\n        return;\n    }\n\n    // Should be marked as duplicate\n    if (a2.value.duplicate == null or\n        !a2.value.duplicate.?)\n    {\n        reportResult(\n            \"js_dedup\",\n            false,\n            \"no dup flag\",\n        );\n        a2.deinit();\n        return;\n    }\n    a2.deinit();\n\n    // Different msg-id -> new message\n    var a3 = js.publishWithOpts(\n        \"test.dedup.data\",\n        \"second\",\n        .{ .msg_id = \"unique-2\" },\n    ) catch {\n        reportResult(\"js_dedup\", false, \"pub 3\");\n        return;\n    };\n\n    if (a3.value.seq != seq1 + 1) {\n        reportResult(\n            \"js_dedup\",\n            false,\n            \"wrong seq for new msg\",\n        );\n        a3.deinit();\n        return;\n    }\n    a3.deinit();\n\n    var d = js.deleteStream(\"TEST_DEDUP\") catch {\n        reportResult(\"js_dedup\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_dedup\", true, \"\");\n}\n\npub fn testPublishExpectedSeq(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_exp_seq\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_EXPSEQ\",\n        .subjects = &.{\"test.expseq.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_exp_seq\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Publish first message\n    var a1 = js.publish(\n        \"test.expseq.data\",\n        \"first\",\n    ) catch {\n        reportResult(\"js_exp_seq\", false, \"pub 1\");\n        return;\n    };\n    a1.deinit();\n\n    // Publish with correct expected_last_seq=1\n    var a2 = js.publishWithOpts(\n        \"test.expseq.data\",\n        \"second\",\n        .{ .expected_last_seq = 1 },\n    ) catch {\n        reportResult(\"js_exp_seq\", false, \"pub 2\");\n        return;\n    };\n    a2.deinit();\n\n    // Publish with WRONG expected_last_seq=0\n    // -> should fail\n    var a3 = js.publishWithOpts(\n        \"test.expseq.data\",\n        \"should-fail\",\n        .{ .expected_last_seq = 0 },\n    );\n    if (a3) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_exp_seq\",\n            false,\n            \"should have failed\",\n        );\n        return;\n    } else |err| {\n        if (err != error.ApiError) {\n            reportResult(\n                \"js_exp_seq\",\n                false,\n                \"wrong error\",\n            );\n            return;\n        }\n        // Verify the error code\n        if (js.lastApiError()) |api_err| {\n            if (api_err.err_code !=\n                nats.jetstream.errors\n                    .ErrCode.stream_wrong_last_seq)\n            {\n                reportResult(\n                    \"js_exp_seq\",\n                    false,\n                    \"wrong err_code\",\n                );\n                return;\n            }\n        }\n    }\n\n    var d = js.deleteStream(\"TEST_EXPSEQ\") catch {\n        reportResult(\"js_exp_seq\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_exp_seq\", true, \"\");\n}\n\n// -- Stream operations tests --\n\npub fn testPurgeStream(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_purge\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_PURGE\",\n        .subjects = &.{\"test.purge.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_purge\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Publish 5 messages\n    var i: u32 = 0;\n    while (i < 5) : (i += 1) {\n        var a = js.publish(\n            \"test.purge.data\",\n            \"purge-msg\",\n        ) catch {\n            reportResult(\n                \"js_purge\",\n                false,\n                \"publish\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Verify 5 messages exist\n    var info1 = js.streamInfo(\"TEST_PURGE\") catch {\n        reportResult(\"js_purge\", false, \"info 1\");\n        return;\n    };\n    if (info1.value.state) |st| {\n        if (st.messages != 5) {\n            reportResult(\n                \"js_purge\",\n                false,\n                \"expected 5 msgs\",\n            );\n            info1.deinit();\n            return;\n        }\n    }\n    info1.deinit();\n\n    // Purge\n    var p = js.purgeStream(\"TEST_PURGE\") catch {\n        reportResult(\"js_purge\", false, \"purge\");\n        return;\n    };\n    if (!p.value.success) {\n        reportResult(\n            \"js_purge\",\n            false,\n            \"purge not success\",\n        );\n        p.deinit();\n        return;\n    }\n    if (p.value.purged != 5) {\n        reportResult(\n            \"js_purge\",\n            false,\n            \"wrong purge count\",\n        );\n        p.deinit();\n        return;\n    }\n    p.deinit();\n\n    // Verify 0 messages\n    var info2 = js.streamInfo(\"TEST_PURGE\") catch {\n        reportResult(\"js_purge\", false, \"info 2\");\n        return;\n    };\n    defer info2.deinit();\n    if (info2.value.state) |st| {\n        if (st.messages != 0) {\n            reportResult(\n                \"js_purge\",\n                false,\n                \"expected 0 after purge\",\n            );\n            return;\n        }\n    }\n\n    var d = js.deleteStream(\"TEST_PURGE\") catch {\n        reportResult(\"js_purge\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_purge\", true, \"\");\n}\n\n// -- Stream update test --\n\npub fn testStreamUpdate(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_update\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_UPDATE\",\n        .subjects = &.{\"test.update.>\"},\n        .storage = .memory,\n        .max_msgs = 100,\n    }) catch {\n        reportResult(\n            \"js_update\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Update max_msgs\n    var u = js.updateStream(.{\n        .name = \"TEST_UPDATE\",\n        .subjects = &.{\"test.update.>\"},\n        .storage = .memory,\n        .max_msgs = 200,\n    }) catch {\n        reportResult(\"js_update\", false, \"update\");\n        return;\n    };\n    defer u.deinit();\n\n    if (u.value.config) |cfg| {\n        if (cfg.max_msgs != 200) {\n            reportResult(\n                \"js_update\",\n                false,\n                \"max_msgs not updated\",\n            );\n            return;\n        }\n    }\n\n    var d = js.deleteStream(\"TEST_UPDATE\") catch {\n        reportResult(\"js_update\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_update\", true, \"\");\n}\n\n// -- InProgress (WPI) test --\n\npub fn testInProgress(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"js_wpi\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_WPI\",\n        .subjects = &.{\"test.wpi.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_wpi\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var c = js.createConsumer(\"TEST_WPI\", .{\n        .name = \"wpi-cons\",\n        .durable_name = \"wpi-cons\",\n        .ack_policy = .explicit,\n        .ack_wait = 2_000_000_000,\n    }) catch {\n        reportResult(\n            \"js_wpi\",\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    var a = js.publish(\n        \"test.wpi.one\",\n        \"wpi-test\",\n    ) catch {\n        reportResult(\"js_wpi\", false, \"publish\");\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_WPI\",\n    };\n    pull.setConsumer(\"wpi-cons\") catch unreachable;\n\n    var msg = (pull.next(5000) catch {\n        reportResult(\"js_wpi\", false, \"fetch\");\n        return;\n    }) orelse {\n        reportResult(\"js_wpi\", false, \"no msg\");\n        return;\n    };\n\n    // Send inProgress to extend deadline\n    msg.inProgress() catch {\n        reportResult(\"js_wpi\", false, \"wpi failed\");\n        msg.deinit();\n        return;\n    };\n\n    // Can call inProgress multiple times\n    msg.inProgress() catch {\n        reportResult(\"js_wpi\", false, \"wpi 2\");\n        msg.deinit();\n        return;\n    };\n\n    // Now ack\n    msg.ack() catch {\n        reportResult(\"js_wpi\", false, \"ack\");\n        msg.deinit();\n        return;\n    };\n    msg.deinit();\n\n    var d = js.deleteStream(\"TEST_WPI\") catch {\n        reportResult(\"js_wpi\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_wpi\", true, \"\");\n}\n\n// -- Consumer not found test --\n\npub fn testConsumerNotFound(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_cons_not_found\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_CNF\",\n        .subjects = &.{\"test.cnf.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_cons_not_found\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var info = js.consumerInfo(\n        \"TEST_CNF\",\n        \"nonexistent\",\n    );\n    if (info) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_cons_not_found\",\n            false,\n            \"should fail\",\n        );\n        return;\n    } else |err| {\n        if (err != error.ApiError) {\n            reportResult(\n                \"js_cons_not_found\",\n                false,\n                \"wrong error\",\n            );\n            return;\n        }\n        if (js.lastApiError()) |api_err| {\n            if (api_err.err_code !=\n                nats.jetstream.errors\n                    .ErrCode.consumer_not_found)\n            {\n                reportResult(\n                    \"js_cons_not_found\",\n                    false,\n                    \"wrong err_code\",\n                );\n                return;\n            }\n        }\n    }\n\n    var d = js.deleteStream(\"TEST_CNF\") catch {\n        reportResult(\n            \"js_cons_not_found\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_cons_not_found\", true, \"\");\n}\n\n// -- Stream by subject test --\n\npub fn testStreamBySubject(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_by_subject\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_BYSUB\",\n        .subjects = &.{\"bysub.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_by_subject\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var resp = js.streamNameBySubject(\n        \"bysub.test\",\n    ) catch {\n        reportResult(\n            \"js_by_subject\",\n            false,\n            \"lookup failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    const names = resp.value.streams orelse {\n        reportResult(\n            \"js_by_subject\",\n            false,\n            \"no result\",\n        );\n        return;\n    };\n\n    if (names.len != 1) {\n        reportResult(\n            \"js_by_subject\",\n            false,\n            \"expected 1 match\",\n        );\n        return;\n    }\n\n    if (!std.mem.eql(u8, names[0], \"TEST_BYSUB\")) {\n        reportResult(\n            \"js_by_subject\",\n            false,\n            \"wrong stream\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_BYSUB\") catch {\n        reportResult(\"js_by_subject\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_by_subject\", true, \"\");\n}\n\n// -- Key-Value Store tests --\n\npub fn testKvPutGet(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_put_get\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV\",\n        .storage = .memory,\n        .history = 5,\n    }) catch {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put\n    const rev1 = kv.put(\"mykey\", \"hello\") catch {\n        reportResult(\"kv_put_get\", false, \"put\");\n        return;\n    };\n    if (rev1 == 0) {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"rev should be > 0\",\n        );\n        return;\n    }\n\n    // Get\n    var entry = (kv.get(\"mykey\") catch {\n        reportResult(\"kv_put_get\", false, \"get\");\n        return;\n    }) orelse {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"key not found\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (entry.revision != rev1) {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"wrong revision\",\n        );\n        return;\n    }\n    if (entry.operation != .put) {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"wrong operation\",\n        );\n        return;\n    }\n\n    // Get non-existent key\n    const missing = kv.get(\"nonexistent\") catch {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"get missing err\",\n        );\n        return;\n    };\n    if (missing != null) {\n        reportResult(\n            \"kv_put_get\",\n            false,\n            \"should be null\",\n        );\n        return;\n    }\n\n    reportResult(\"kv_put_get\", true, \"\");\n}\n\npub fn testKvCreate(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_create\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_CREATE\",\n        .storage = .memory,\n    }) catch |err| {\n        reportError(\"kv_create\", \"create bucket\", err);\n        return;\n    };\n\n    // Create succeeds on new key\n    _ = kv.create(\"newkey\", \"value1\") catch |err| {\n        reportError(\"kv_create\", \"create 1\", err);\n        return;\n    };\n\n    // Create fails on existing key\n    _ = kv.create(\"newkey\", \"value2\") catch |err| {\n        if (err == error.ApiError) {\n            reportResult(\"kv_create\", true, \"\");\n            return;\n        }\n        reportResult(\n            \"kv_create\",\n            false,\n            \"wrong error\",\n        );\n        return;\n    };\n\n    reportResult(\n        \"kv_create\",\n        false,\n        \"should have failed\",\n    );\n}\n\npub fn testKvUpdate(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_update\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_UPDATE\",\n        .storage = .memory,\n    }) catch |err| {\n        reportError(\"kv_update\", \"create bucket\", err);\n        return;\n    };\n\n    const rev1 = kv.put(\"key1\", \"v1\") catch |err| {\n        reportError(\"kv_update\", \"put\", err);\n        return;\n    };\n\n    // Update with correct revision\n    const rev2 = kv.update(\n        \"key1\",\n        \"v2\",\n        rev1,\n    ) catch {\n        reportResult(\n            \"kv_update\",\n            false,\n            \"update ok\",\n        );\n        return;\n    };\n\n    if (rev2 <= rev1) {\n        reportResult(\n            \"kv_update\",\n            false,\n            \"rev not incremented\",\n        );\n        return;\n    }\n\n    // Update with wrong revision -> fail\n    _ = kv.update(\"key1\", \"v3\", rev1) catch |err| {\n        if (err == error.ApiError) {\n            reportResult(\"kv_update\", true, \"\");\n            return;\n        }\n        reportResult(\n            \"kv_update\",\n            false,\n            \"wrong error\",\n        );\n        return;\n    };\n\n    reportResult(\n        \"kv_update\",\n        false,\n        \"should have failed\",\n    );\n}\n\npub fn testKvDelete(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_delete\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_DEL\",\n        .storage = .memory,\n        .history = 5,\n    }) catch {\n        reportResult(\n            \"kv_delete\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    _ = kv.put(\"delkey\", \"value\") catch {\n        reportResult(\"kv_delete\", false, \"put\");\n        return;\n    };\n\n    // Delete\n    _ = kv.delete(\"delkey\") catch {\n        reportResult(\"kv_delete\", false, \"delete\");\n        return;\n    };\n\n    // Get should show delete marker\n    var entry = (kv.get(\"delkey\") catch {\n        reportResult(\"kv_delete\", false, \"get\");\n        return;\n    }) orelse {\n        // Key gone completely (ok for history=1)\n        reportResult(\"kv_delete\", true, \"\");\n        return;\n    };\n    defer entry.deinit();\n\n    if (entry.operation != .delete) {\n        reportResult(\n            \"kv_delete\",\n            false,\n            \"expected delete op\",\n        );\n        return;\n    }\n\n    reportResult(\"kv_delete\", true, \"\");\n}\n\npub fn testKvKeys(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_keys\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_KEYS\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_keys\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put 3 keys\n    _ = kv.put(\"alpha\", \"1\") catch {\n        reportResult(\"kv_keys\", false, \"put 1\");\n        return;\n    };\n    _ = kv.put(\"beta\", \"2\") catch {\n        reportResult(\"kv_keys\", false, \"put 2\");\n        return;\n    };\n    _ = kv.put(\"gamma\", \"3\") catch {\n        reportResult(\"kv_keys\", false, \"put 3\");\n        return;\n    };\n\n    const key_list = kv.keys(allocator) catch {\n        reportResult(\"kv_keys\", false, \"keys()\");\n        return;\n    };\n    defer {\n        for (key_list) |k| allocator.free(k);\n        allocator.free(key_list);\n    }\n\n    if (key_list.len != 3) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d} keys, expected 3\",\n            .{key_list.len},\n        ) catch \"wrong count\";\n        reportResult(\"kv_keys\", false, m);\n        return;\n    }\n\n    reportResult(\"kv_keys\", true, \"\");\n}\n\npub fn testKvHistory(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_history\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_HIST\",\n        .storage = .memory,\n        .history = 10,\n    }) catch {\n        reportResult(\n            \"kv_history\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put same key 3 times\n    _ = kv.put(\"hkey\", \"v1\") catch {\n        reportResult(\"kv_history\", false, \"put 1\");\n        return;\n    };\n    _ = kv.put(\"hkey\", \"v2\") catch {\n        reportResult(\"kv_history\", false, \"put 2\");\n        return;\n    };\n    _ = kv.put(\"hkey\", \"v3\") catch {\n        reportResult(\"kv_history\", false, \"put 3\");\n        return;\n    };\n\n    const hist = kv.history(\n        allocator,\n        \"hkey\",\n    ) catch {\n        reportResult(\n            \"kv_history\",\n            false,\n            \"history()\",\n        );\n        return;\n    };\n    defer {\n        for (hist) |*h| h.deinit();\n        allocator.free(hist);\n    }\n\n    if (hist.len != 3) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 3\",\n            .{hist.len},\n        ) catch \"wrong\";\n        reportResult(\"kv_history\", false, m);\n        return;\n    }\n\n    // Verify revisions are increasing\n    if (hist.len >= 2) {\n        if (hist[1].revision <= hist[0].revision) {\n            reportResult(\n                \"kv_history\",\n                false,\n                \"revs not increasing\",\n            );\n            return;\n        }\n    }\n\n    reportResult(\"kv_history\", true, \"\");\n}\n\npub fn testKvWatch(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"kv_watch\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_WATCH\",\n        .storage = .memory,\n    }) catch |err| {\n        reportError(\"kv_watch\", \"create bucket\", err);\n        return;\n    };\n\n    // Put a key before watching\n    _ = kv.put(\"pre-watch\", \"initial\") catch |err| {\n        reportError(\"kv_watch\", \"put\", err);\n        return;\n    };\n\n    // Start watching\n    var watcher = kv.watchAll() catch {\n        reportResult(\n            \"kv_watch\",\n            false,\n            \"watchAll()\",\n        );\n        return;\n    };\n    defer watcher.deinit();\n\n    // Should get the initial key\n    var entry = (watcher.next(5000) catch |err| {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"watch next: {}\",\n            .{err},\n        ) catch \"watch err\";\n        reportResult(\"kv_watch\", false, m);\n        return;\n    }) orelse {\n        reportResult(\n            \"kv_watch\",\n            false,\n            \"no initial entry\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (!std.mem.eql(u8, entry.key, \"pre-watch\")) {\n        reportResult(\n            \"kv_watch\",\n            false,\n            \"wrong key\",\n        );\n        return;\n    }\n\n    reportResult(\"kv_watch\", true, \"\");\n}\n\npub fn testKvBucketLifecycle(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_lifecycle\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create\n    var kv = js.createKeyValue(.{\n        .bucket = \"TEST_KV_LIFE\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_lifecycle\",\n            false,\n            \"create\",\n        );\n        return;\n    };\n\n    // Status\n    var st = kv.status() catch {\n        reportResult(\n            \"kv_lifecycle\",\n            false,\n            \"status\",\n        );\n        return;\n    };\n    defer st.deinit();\n\n    if (st.value.config) |cfg| {\n        if (!std.mem.eql(\n            u8,\n            cfg.name,\n            \"KV_TEST_KV_LIFE\",\n        )) {\n            reportResult(\n                \"kv_lifecycle\",\n                false,\n                \"wrong stream name\",\n            );\n            return;\n        }\n    }\n\n    // Bind\n    const kv2 = js.keyValue(\"TEST_KV_LIFE\") catch {\n        reportResult(\n            \"kv_lifecycle\",\n            false,\n            \"bind\",\n        );\n        return;\n    };\n    _ = kv2;\n\n    // Delete\n    var del = js.deleteKeyValue(\n        \"TEST_KV_LIFE\",\n    ) catch {\n        reportResult(\n            \"kv_lifecycle\",\n            false,\n            \"delete\",\n        );\n        return;\n    };\n    defer del.deinit();\n\n    if (!del.value.success) {\n        reportResult(\n            \"kv_lifecycle\",\n            false,\n            \"delete failed\",\n        );\n        return;\n    }\n\n    // Bind to deleted bucket -> should fail\n    _ = js.keyValue(\"TEST_KV_LIFE\") catch {\n        reportResult(\"kv_lifecycle\", true, \"\");\n        return;\n    };\n\n    reportResult(\n        \"kv_lifecycle\",\n        false,\n        \"bind should fail after delete\",\n    );\n}\n\n// -- Behavioral correctness tests --\n\npub fn testFilteredConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_FILTER\",\n        .subjects = &.{\"test.filter.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Publish to different subjects\n    var a1 = js.publish(\n        \"test.filter.a\",\n        \"msg-a\",\n    ) catch {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"pub a\",\n        );\n        return;\n    };\n    a1.deinit();\n\n    var a2 = js.publish(\n        \"test.filter.b\",\n        \"msg-b\",\n    ) catch {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"pub b\",\n        );\n        return;\n    };\n    a2.deinit();\n\n    // Create consumer filtered on \"test.filter.a\"\n    var c = js.createConsumer(\"TEST_FILTER\", .{\n        .name = \"filter-cons\",\n        .durable_name = \"filter-cons\",\n        .ack_policy = .explicit,\n        .filter_subject = \"test.filter.a\",\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"create cons: {}\",\n            .{err},\n        ) catch \"err\";\n        reportResult(\"js_filtered_cons\", false, m);\n        return;\n    };\n    defer c.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_FILTER\",\n    };\n    pull.setConsumer(\"filter-cons\") catch unreachable;\n\n    // Should only get \"msg-a\" (filtered)\n    var msg = (pull.next(5000) catch {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"fetch\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"no msg\",\n        );\n        return;\n    };\n\n    if (!std.mem.eql(u8, msg.data(), \"msg-a\")) {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"wrong data\",\n        );\n        msg.deinit();\n        return;\n    }\n    msg.ack() catch {};\n    msg.deinit();\n\n    // No more messages (msg-b filtered out)\n    var r = pull.fetchNoWait(10) catch {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"fetch 2\",\n        );\n        return;\n    };\n    defer r.deinit();\n\n    if (r.count() != 0) {\n        reportResult(\n            \"js_filtered_cons\",\n            false,\n            \"expected 0 after filter\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_FILTER\") catch {\n        reportResult(\"js_filtered_cons\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_filtered_cons\", true, \"\");\n}\n\npub fn testPurgeSubject(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_purge_subj\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_PURGE_S\",\n        .subjects = &.{\"test.purge.s.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_purge_subj\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Publish to 2 subjects\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"test.purge.s.keep\",\n            \"keep\",\n        ) catch {\n            reportResult(\n                \"js_purge_subj\",\n                false,\n                \"pub keep\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n    i = 0;\n    while (i < 2) : (i += 1) {\n        var a = js.publish(\n            \"test.purge.s.remove\",\n            \"remove\",\n        ) catch {\n            reportResult(\n                \"js_purge_subj\",\n                false,\n                \"pub remove\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Purge only \"remove\" subject\n    var p = js.purgeStreamSubject(\n        \"TEST_PURGE_S\",\n        \"test.purge.s.remove\",\n    ) catch {\n        reportResult(\n            \"js_purge_subj\",\n            false,\n            \"purge\",\n        );\n        return;\n    };\n    defer p.deinit();\n\n    if (p.value.purged != 2) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"purged {d}, expected 2\",\n            .{p.value.purged},\n        ) catch \"wrong count\";\n        reportResult(\"js_purge_subj\", false, m);\n        return;\n    }\n\n    // Verify \"keep\" messages still exist\n    var info = js.streamInfo(\"TEST_PURGE_S\") catch {\n        reportResult(\n            \"js_purge_subj\",\n            false,\n            \"info\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.state) |st| {\n        if (st.messages != 3) {\n            var buf: [64]u8 = undefined;\n            const m = std.fmt.bufPrint(\n                &buf,\n                \"{d} msgs, expected 3\",\n                .{st.messages},\n            ) catch \"wrong\";\n            reportResult(\n                \"js_purge_subj\",\n                false,\n                m,\n            );\n            return;\n        }\n    }\n\n    var d = js.deleteStream(\"TEST_PURGE_S\") catch {\n        reportResult(\"js_purge_subj\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_purge_subj\", true, \"\");\n}\n\npub fn testPaginatedStreamNames(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_paginated\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create 3 streams\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var name_buf: [32]u8 = undefined;\n        const sname = std.fmt.bufPrint(\n            &name_buf,\n            \"PAG_{d}\",\n            .{i},\n        ) catch unreachable;\n        var subj_b: [32]u8 = undefined;\n        const ssubj = std.fmt.bufPrint(\n            &subj_b,\n            \"pag.{d}.>\",\n            .{i},\n        ) catch unreachable;\n        const subjects: [1][]const u8 = .{ssubj};\n        var r = js.createStream(.{\n            .name = sname,\n            .subjects = &subjects,\n            .storage = .memory,\n        }) catch {\n            reportResult(\n                \"js_paginated\",\n                false,\n                \"create\",\n            );\n            return;\n        };\n        r.deinit();\n    }\n\n    // Use allStreamNames (pagination)\n    const all = js.allStreamNames(allocator) catch {\n        reportResult(\n            \"js_paginated\",\n            false,\n            \"allStreamNames\",\n        );\n        return;\n    };\n    defer {\n        for (all) |n| allocator.free(n);\n        allocator.free(all);\n    }\n\n    if (all.len < 3) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected >= 3\",\n            .{all.len},\n        ) catch \"wrong\";\n        reportResult(\"js_paginated\", false, m);\n        return;\n    }\n\n    // Cleanup\n    i = 0;\n    while (i < 3) : (i += 1) {\n        var name_buf: [32]u8 = undefined;\n        const sname = std.fmt.bufPrint(\n            &name_buf,\n            \"PAG_{d}\",\n            .{i},\n        ) catch unreachable;\n        var r = js.deleteStream(sname) catch continue;\n        r.deinit();\n    }\n\n    reportResult(\"js_paginated\", true, \"\");\n}\n\npub fn testGetMsg(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_get_msg\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_GETMSG\",\n        .subjects = &.{\"test.getmsg.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_get_msg\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish 3 messages\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"test.getmsg.a\",\n            \"payload\",\n        ) catch {\n            reportResult(\n                \"js_get_msg\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Get message at seq 1\n    var resp = js.getMsg(\n        \"TEST_GETMSG\",\n        1,\n    ) catch {\n        reportResult(\n            \"js_get_msg\",\n            false,\n            \"getMsg failed\",\n        );\n        return;\n    };\n    defer resp.deinit();\n\n    if (resp.value.message) |m| {\n        if (m.seq != 1) {\n            reportResult(\n                \"js_get_msg\",\n                false,\n                \"expected seq 1\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_get_msg\",\n            false,\n            \"no message\",\n        );\n        return;\n    }\n\n    // Get non-existent seq -> ApiError\n    var bad = js.getMsg(\"TEST_GETMSG\", 999);\n    if (bad) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_get_msg\",\n            false,\n            \"should fail for 999\",\n        );\n        return;\n    } else |err| {\n        if (err != error.ApiError) {\n            reportResult(\n                \"js_get_msg\",\n                false,\n                \"wrong error type\",\n            );\n            return;\n        }\n        if (js.lastApiError()) |ae| {\n            if (ae.err_code !=\n                nats.jetstream.errors\n                    .ErrCode.message_not_found)\n            {\n                reportResult(\n                    \"js_get_msg\",\n                    false,\n                    \"wrong err_code\",\n                );\n                return;\n            }\n        }\n    }\n\n    var d = js.deleteStream(\n        \"TEST_GETMSG\",\n    ) catch {\n        reportResult(\"js_get_msg\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_get_msg\", true, \"\");\n}\n\npub fn testGetLastMsgForSubject(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_get_last_msg\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_GETLAST\",\n        .subjects = &.{\"test.getlast.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_get_last_msg\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish 3 msgs to test.getlast.a\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"test.getlast.a\",\n            \"msg-a\",\n        ) catch {\n            reportResult(\n                \"js_get_last_msg\",\n                false,\n                \"pub a failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Publish 2 msgs to test.getlast.b\n    i = 0;\n    while (i < 2) : (i += 1) {\n        var a = js.publish(\n            \"test.getlast.b\",\n            \"msg-b\",\n        ) catch {\n            reportResult(\n                \"js_get_last_msg\",\n                false,\n                \"pub b failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Last for subject \"a\" should be seq 3\n    var ra = js.getLastMsgForSubject(\n        \"TEST_GETLAST\",\n        \"test.getlast.a\",\n    ) catch {\n        reportResult(\n            \"js_get_last_msg\",\n            false,\n            \"getLast a failed\",\n        );\n        return;\n    };\n    defer ra.deinit();\n\n    if (ra.value.message) |m| {\n        if (m.seq != 3) {\n            var buf: [64]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"a: got {d}, want 3\",\n                .{m.seq},\n            ) catch \"wrong seq\";\n            reportResult(\n                \"js_get_last_msg\",\n                false,\n                msg,\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_get_last_msg\",\n            false,\n            \"no msg for a\",\n        );\n        return;\n    }\n\n    // Last for subject \"b\" should be seq 5\n    var rb = js.getLastMsgForSubject(\n        \"TEST_GETLAST\",\n        \"test.getlast.b\",\n    ) catch {\n        reportResult(\n            \"js_get_last_msg\",\n            false,\n            \"getLast b failed\",\n        );\n        return;\n    };\n    defer rb.deinit();\n\n    if (rb.value.message) |m| {\n        if (m.seq != 5) {\n            var buf: [64]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"b: got {d}, want 5\",\n                .{m.seq},\n            ) catch \"wrong seq\";\n            reportResult(\n                \"js_get_last_msg\",\n                false,\n                msg,\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_get_last_msg\",\n            false,\n            \"no msg for b\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_GETLAST\",\n    ) catch {\n        reportResult(\n            \"js_get_last_msg\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_get_last_msg\", true, \"\");\n}\n\npub fn testDeleteMsg(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_delete_msg\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_DELMSG\",\n        .subjects = &.{\"test.delmsg.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_delete_msg\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish 5 messages\n    var i: u32 = 0;\n    while (i < 5) : (i += 1) {\n        var a = js.publish(\n            \"test.delmsg.a\",\n            \"payload\",\n        ) catch {\n            reportResult(\n                \"js_delete_msg\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Delete seq 3\n    var del = js.deleteMsg(\n        \"TEST_DELMSG\",\n        3,\n    ) catch {\n        reportResult(\n            \"js_delete_msg\",\n            false,\n            \"deleteMsg failed\",\n        );\n        return;\n    };\n    defer del.deinit();\n\n    if (!del.value.success) {\n        reportResult(\n            \"js_delete_msg\",\n            false,\n            \"delete not success\",\n        );\n        return;\n    }\n\n    // getMsg(3) should fail\n    var bad = js.getMsg(\"TEST_DELMSG\", 3);\n    if (bad) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_delete_msg\",\n            false,\n            \"seq 3 should be gone\",\n        );\n        return;\n    } else |_| {}\n\n    // getMsg(2) should still work\n    var ok = js.getMsg(\n        \"TEST_DELMSG\",\n        2,\n    ) catch {\n        reportResult(\n            \"js_delete_msg\",\n            false,\n            \"seq 2 should exist\",\n        );\n        return;\n    };\n    ok.deinit();\n\n    var d = js.deleteStream(\n        \"TEST_DELMSG\",\n    ) catch {\n        reportResult(\n            \"js_delete_msg\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_delete_msg\", true, \"\");\n}\n\npub fn testSecureDeleteMsg(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_secure_del\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_SECDEL\",\n        .subjects = &.{\"test.secdel.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_secure_del\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish 3 messages\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"test.secdel.a\",\n            \"payload\",\n        ) catch {\n            reportResult(\n                \"js_secure_del\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Secure delete seq 2\n    var del = js.secureDeleteMsg(\n        \"TEST_SECDEL\",\n        2,\n    ) catch {\n        reportResult(\n            \"js_secure_del\",\n            false,\n            \"secureDelete failed\",\n        );\n        return;\n    };\n    defer del.deinit();\n\n    if (!del.value.success) {\n        reportResult(\n            \"js_secure_del\",\n            false,\n            \"delete not success\",\n        );\n        return;\n    }\n\n    // getMsg(2) should fail\n    var bad = js.getMsg(\"TEST_SECDEL\", 2);\n    if (bad) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_secure_del\",\n            false,\n            \"seq 2 should be gone\",\n        );\n        return;\n    } else |_| {}\n\n    // getMsg(1) should still work\n    var ok = js.getMsg(\n        \"TEST_SECDEL\",\n        1,\n    ) catch {\n        reportResult(\n            \"js_secure_del\",\n            false,\n            \"seq 1 should exist\",\n        );\n        return;\n    };\n    ok.deinit();\n\n    var d = js.deleteStream(\n        \"TEST_SECDEL\",\n    ) catch {\n        reportResult(\n            \"js_secure_del\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_secure_del\", true, \"\");\n}\n\npub fn testCreateOrUpdateStream(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_upsert_stream\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create via createOrUpdate\n    var r1 = js.createOrUpdateStream(.{\n        .name = \"TEST_UPSERT\",\n        .subjects = &.{\"upsert.>\"},\n        .storage = .memory,\n        .max_msgs = 100,\n    }) catch {\n        reportResult(\n            \"js_upsert_stream\",\n            false,\n            \"create failed\",\n        );\n        return;\n    };\n    r1.deinit();\n\n    // Update via createOrUpdate\n    var r2 = js.createOrUpdateStream(.{\n        .name = \"TEST_UPSERT\",\n        .subjects = &.{\"upsert.>\"},\n        .storage = .memory,\n        .max_msgs = 200,\n    }) catch {\n        reportResult(\n            \"js_upsert_stream\",\n            false,\n            \"update failed\",\n        );\n        return;\n    };\n    r2.deinit();\n\n    // Verify updated config\n    var info = js.streamInfo(\n        \"TEST_UPSERT\",\n    ) catch {\n        reportResult(\n            \"js_upsert_stream\",\n            false,\n            \"info failed\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.config) |cfg| {\n        if (cfg.max_msgs) |mm| {\n            if (mm != 200) {\n                var buf: [64]u8 = undefined;\n                const m = std.fmt.bufPrint(\n                    &buf,\n                    \"max_msgs {d}, want 200\",\n                    .{mm},\n                ) catch \"wrong\";\n                reportResult(\n                    \"js_upsert_stream\",\n                    false,\n                    m,\n                );\n                return;\n            }\n        } else {\n            reportResult(\n                \"js_upsert_stream\",\n                false,\n                \"no max_msgs\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_upsert_stream\",\n            false,\n            \"no config\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_UPSERT\",\n    ) catch {\n        reportResult(\n            \"js_upsert_stream\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_upsert_stream\", true, \"\");\n}\n\npub fn testCreateOrUpdateConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_upsert_cons\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_UCONS\",\n        .subjects = &.{\"ucons.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_upsert_cons\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Create consumer\n    var c1 = js.createOrUpdateConsumer(\n        \"TEST_UCONS\",\n        .{\n            .name = \"upsert-c\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_upsert_cons\",\n            false,\n            \"create cons\",\n        );\n        return;\n    };\n    c1.deinit();\n\n    // Update consumer\n    var c2 = js.createOrUpdateConsumer(\n        \"TEST_UCONS\",\n        .{\n            .name = \"upsert-c\",\n            .ack_policy = .explicit,\n            .max_ack_pending = 500,\n        },\n    ) catch {\n        reportResult(\n            \"js_upsert_cons\",\n            false,\n            \"update cons\",\n        );\n        return;\n    };\n    c2.deinit();\n\n    // Verify updated config\n    var info = js.consumerInfo(\n        \"TEST_UCONS\",\n        \"upsert-c\",\n    ) catch {\n        reportResult(\n            \"js_upsert_cons\",\n            false,\n            \"info failed\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.config) |cfg| {\n        if (cfg.max_ack_pending) |mp| {\n            if (mp != 500) {\n                var buf: [64]u8 = undefined;\n                const m = std.fmt.bufPrint(\n                    &buf,\n                    \"max_ack {d}, want 500\",\n                    .{mp},\n                ) catch \"wrong\";\n                reportResult(\n                    \"js_upsert_cons\",\n                    false,\n                    m,\n                );\n                return;\n            }\n        } else {\n            reportResult(\n                \"js_upsert_cons\",\n                false,\n                \"no max_ack_pending\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"js_upsert_cons\",\n            false,\n            \"no config\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_UCONS\",\n    ) catch {\n        reportResult(\n            \"js_upsert_cons\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_upsert_cons\", true, \"\");\n}\n\npub fn testPauseResumeConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PAUSE\",\n        .subjects = &.{\"pause.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var cons = js.createConsumer(\n        \"TEST_PAUSE\",\n        .{\n            .name = \"pause-c\",\n            .durable_name = \"pause-c\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"create cons\",\n        );\n        return;\n    };\n    cons.deinit();\n\n    // Pause consumer\n    var pr = js.pauseConsumer(\n        \"TEST_PAUSE\",\n        \"pause-c\",\n        \"2099-01-01T00:00:00Z\",\n    ) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"pause failed\",\n        );\n        return;\n    };\n    defer pr.deinit();\n\n    if (!pr.value.paused) {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"not paused\",\n        );\n        return;\n    }\n\n    // Resume consumer\n    var rr = js.resumeConsumer(\n        \"TEST_PAUSE\",\n        \"pause-c\",\n    ) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"resume failed\",\n        );\n        return;\n    };\n    defer rr.deinit();\n\n    if (rr.value.paused) {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"still paused\",\n        );\n        return;\n    }\n\n    // Publish + fetch after resume\n    var a = js.publish(\n        \"pause.test\",\n        \"after-resume\",\n    ) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_PAUSE\",\n    };\n    pull.setConsumer(\"pause-c\") catch unreachable;\n\n    var msg = (pull.next(5000) catch {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"fetch failed\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            \"js_pause_resume\",\n            false,\n            \"no msg after resume\",\n        );\n        return;\n    };\n    msg.ack() catch {};\n    msg.deinit();\n\n    var d = js.deleteStream(\n        \"TEST_PAUSE\",\n    ) catch {\n        reportResult(\n            \"js_pause_resume\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_pause_resume\", true, \"\");\n}\n\npub fn testPushConsumerBasic(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_push_basic\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Clean up from prior runs\n    if (js.deleteStream(\"TEST_PUSH\")) |r| {\n        var rr = r;\n        rr.deinit();\n    } else |_| {}\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PUSH\",\n        .subjects = &.{\"push.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_push_basic\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish 5 messages first\n    var i: u32 = 0;\n    while (i < 5) : (i += 1) {\n        var a = js.publish(\n            \"push.test\",\n            \"push-data\",\n        ) catch {\n            reportResult(\n                \"js_push_basic\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Set up push subscription handler\n    const Counter = struct {\n        count: u32 = 0,\n        pub fn onMessage(\n            self: *@This(),\n            msg: *nats.jetstream.JsMsg,\n        ) void {\n            _ = msg;\n            self.count += 1;\n        }\n    };\n\n    var counter = Counter{};\n\n    // Subscribe to deliver subject BEFORE creating\n    // the consumer -- otherwise server pushes before\n    // we're listening and messages are lost.\n    const deliver_subj = \"_PUSH_DELIVER.test\";\n    var push_sub = nats.jetstream.PushSubscription{\n        .js = &js,\n        .stream = \"TEST_PUSH\",\n    };\n    push_sub.setConsumer(\"push-c\") catch unreachable;\n    push_sub.setDeliverSubject(deliver_subj) catch unreachable;\n\n    var ctx = push_sub.consume(\n        nats.jetstream.JsMsgHandler.init(\n            Counter,\n            &counter,\n        ),\n        .{},\n    ) catch {\n        reportResult(\n            \"js_push_basic\",\n            false,\n            \"consume failed\",\n        );\n        return;\n    };\n\n    // Now create the push consumer -- server starts\n    // delivering to the already-subscribed subject.\n    var pc = js.createPushConsumer(\n        \"TEST_PUSH\",\n        .{\n            .name = \"push-c\",\n            .deliver_subject = deliver_subj,\n            .ack_policy = .none,\n        },\n    ) catch {\n        ctx.stop();\n        ctx.deinit();\n        reportResult(\n            \"js_push_basic\",\n            false,\n            \"create push cons\",\n        );\n        return;\n    };\n    pc.deinit();\n\n    // Wait for messages\n    var wait: u32 = 0;\n    while (counter.count < 5 and\n        wait < 50) : (wait += 1)\n    {\n        threadSleepNs(100_000_000);\n    }\n\n    ctx.stop();\n    ctx.deinit();\n\n    if (counter.count < 5) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 5\",\n            .{counter.count},\n        ) catch \"count mismatch\";\n        reportResult(\n            \"js_push_basic\",\n            false,\n            m,\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_PUSH\",\n    ) catch {\n        reportResult(\n            \"js_push_basic\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_push_basic\", true, \"\");\n}\n\npub fn testPushConsumerBorrowedAck(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_push_borrowed_ack\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PUSH_ACK\",\n        .subjects = &.{\"push.ack.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(name, false, \"create stream\");\n        return;\n    };\n    defer stream.deinit();\n\n    const Handler = struct {\n        count: u32 = 0,\n        saw_expected_data: bool = false,\n        ack_failed: bool = false,\n\n        pub fn onMessage(\n            self: *@This(),\n            msg: *nats.jetstream.JsMsg,\n        ) void {\n            if (std.mem.eql(\n                u8,\n                msg.data(),\n                \"ack-data\",\n            )) {\n                self.saw_expected_data = true;\n            }\n            msg.ack() catch {\n                self.ack_failed = true;\n                return;\n            };\n            self.count += 1;\n        }\n    };\n\n    var handler = Handler{};\n    const deliver_subj = \"_PUSH_ACK_DELIVER.test\";\n    var push_sub = nats.jetstream.PushSubscription{\n        .js = &js,\n        .stream = \"TEST_PUSH_ACK\",\n    };\n    push_sub.setConsumer(\"push-ack-c\") catch unreachable;\n    push_sub.setDeliverSubject(deliver_subj) catch unreachable;\n\n    var ctx = push_sub.consume(\n        nats.jetstream.JsMsgHandler.init(\n            Handler,\n            &handler,\n        ),\n        .{},\n    ) catch {\n        reportResult(name, false, \"consume failed\");\n        return;\n    };\n\n    var pc = js.createPushConsumer(\n        \"TEST_PUSH_ACK\",\n        .{\n            .name = \"push-ack-c\",\n            .deliver_subject = deliver_subj,\n            .ack_policy = .explicit,\n            .ack_wait = 1_000_000_000,\n        },\n    ) catch {\n        ctx.stop();\n        ctx.deinit();\n        reportResult(name, false, \"create push cons\");\n        return;\n    };\n    pc.deinit();\n\n    var ack = js.publish(\n        \"push.ack.data\",\n        \"ack-data\",\n    ) catch {\n        ctx.stop();\n        ctx.deinit();\n        reportResult(name, false, \"publish failed\");\n        return;\n    };\n    ack.deinit();\n\n    var wait: u32 = 0;\n    while (handler.count < 1 and\n        !handler.ack_failed and\n        wait < 50) : (wait += 1)\n    {\n        threadSleepNs(100_000_000);\n    }\n\n    if (handler.ack_failed) {\n        ctx.stop();\n        ctx.deinit();\n        reportResult(name, false, \"ack failed\");\n        return;\n    }\n    if (handler.count != 1 or !handler.saw_expected_data) {\n        ctx.stop();\n        ctx.deinit();\n        reportResult(name, false, \"callback did not ack data\");\n        return;\n    }\n\n    var ack_cleared = false;\n    var info_wait: u32 = 0;\n    while (info_wait < 30) : (info_wait += 1) {\n        var info = js.consumerInfo(\n            \"TEST_PUSH_ACK\",\n            \"push-ack-c\",\n        ) catch {\n            ctx.stop();\n            ctx.deinit();\n            reportResult(name, false, \"consumer info\");\n            return;\n        };\n        defer info.deinit();\n\n        if (info.value.num_ack_pending == 0) {\n            ack_cleared = true;\n            break;\n        }\n        threadSleepNs(100_000_000);\n    }\n\n    ctx.stop();\n    ctx.deinit();\n\n    if (!ack_cleared) {\n        reportResult(name, false, \"ack still pending\");\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_PUSH_ACK\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testPushConsumerHeartbeatErrHandler(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_push_heartbeat\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PUSH_HB\",\n        .subjects = &.{\"push.hb.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(name, false, \"create stream\");\n        return;\n    };\n    defer stream.deinit();\n\n    const Handler = struct {\n        pub fn onMessage(\n            self: *@This(),\n            msg: *nats.jetstream.JsMsg,\n        ) void {\n            _ = self;\n            _ = msg;\n        }\n    };\n\n    var handler = Handler{};\n    push_heartbeat_err_seen.store(false, .release);\n\n    const deliver_subj = \"_PUSH_HB_DELIVER.test\";\n    var push_sub = nats.jetstream.PushSubscription{\n        .js = &js,\n        .stream = \"TEST_PUSH_HB\",\n    };\n    push_sub.setConsumer(\"push-hb-c\") catch unreachable;\n    push_sub.setDeliverSubject(deliver_subj) catch unreachable;\n\n    var ctx = push_sub.consume(\n        nats.jetstream.JsMsgHandler.init(\n            Handler,\n            &handler,\n        ),\n        .{\n            .heartbeat_ms = 200,\n            .err_handler = pushHeartbeatErrHandler,\n        },\n    ) catch {\n        reportResult(name, false, \"consume failed\");\n        return;\n    };\n\n    var pc = js.createPushConsumer(\n        \"TEST_PUSH_HB\",\n        .{\n            .name = \"push-hb-c\",\n            .deliver_subject = deliver_subj,\n            .ack_policy = .none,\n        },\n    ) catch {\n        ctx.stop();\n        ctx.deinit();\n        reportResult(name, false, \"create push cons\");\n        return;\n    };\n    pc.deinit();\n\n    var wait: u32 = 0;\n    while (!push_heartbeat_err_seen.load(.acquire) and\n        wait < 40) : (wait += 1)\n    {\n        threadSleepNs(100_000_000);\n    }\n\n    ctx.stop();\n    ctx.deinit();\n\n    if (!push_heartbeat_err_seen.load(.acquire)) {\n        reportResult(name, false, \"no heartbeat error missing\");\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_PUSH_HB\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testPublishWithTTL(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_publish_ttl\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream with TTL support\n    var stream = js.createStream(.{\n        .name = \"TEST_TTL\",\n        .subjects = &.{\"ttl.>\"},\n        .storage = .memory,\n        .allow_msg_ttl = true,\n    }) catch {\n        // Server may not support TTL\n        reportResult(\n            \"js_publish_ttl\",\n            true,\n            \"skipped\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // Publish with 1s TTL\n    var ack = js.publishWithOpts(\n        \"ttl.a\",\n        \"data\",\n        .{ .ttl = \"1s\" },\n    ) catch {\n        reportResult(\n            \"js_publish_ttl\",\n            false,\n            \"publish failed\",\n        );\n        return;\n    };\n    ack.deinit();\n\n    // Immediately should exist\n    var r1 = js.getMsg(\"TEST_TTL\", 1) catch {\n        reportResult(\n            \"js_publish_ttl\",\n            false,\n            \"getMsg before ttl\",\n        );\n        return;\n    };\n    r1.deinit();\n\n    // Wait for TTL expiry\n    threadSleepNs(2_000_000_000);\n\n    // Should be expired now\n    var r2 = js.getMsg(\"TEST_TTL\", 1);\n    if (r2) |*r| {\n        r.deinit();\n        reportResult(\n            \"js_publish_ttl\",\n            false,\n            \"should expire\",\n        );\n        return;\n    } else |_| {}\n\n    var d = js.deleteStream(\n        \"TEST_TTL\",\n    ) catch {\n        reportResult(\n            \"js_publish_ttl\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_publish_ttl\", true, \"\");\n}\n\n/// Verifies publishMsg() merges user headers with JS-derived\n/// opts headers. On key collision (case-insensitive), JS opts\n/// must win. Retrieves the stored message via getMsg() and\n/// asserts both headers against the wire.\npub fn testPublishMsg(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PUBLISH_MSG\",\n        .subjects = &.{\"pubmsg.test\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    // User headers. Note lowercase \"nats-msg-id\" deliberately\n    // collides (case-insensitively) with the JS header set by\n    // opts.msg_id -- \"jsopts-id\" must win.\n    const user_headers = [_]nats.protocol.headers.Entry{\n        .{ .key = \"X-Custom\", .value = \"uservalue\" },\n        .{ .key = \"nats-msg-id\", .value = \"user-id\" },\n    };\n\n    var ack = js.publishMsg(allocator, .{\n        .subject = \"pubmsg.test\",\n        .payload = \"hello\",\n        .headers = &user_headers,\n        .opts = .{ .msg_id = \"jsopts-id\" },\n    }) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"publishMsg failed\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n    ack.deinit();\n\n    var resp = js.getMsg(\n        \"TEST_PUBLISH_MSG\",\n        1,\n    ) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"getMsg failed\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n    defer resp.deinit();\n\n    const stored = resp.value.message orelse {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"no stored message\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n\n    const hdr_b64 = stored.hdrs orelse {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"no headers in stored msg\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n\n    // Decode base64 headers, then parse NATS/1.0.\n    const decoder = std.base64.standard.Decoder;\n    const decoded_len = decoder.calcSizeForSlice(hdr_b64) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"b64 calc size\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n    const decoded_buf = allocator.alloc(u8, decoded_len) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"alloc decoded\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n    defer allocator.free(decoded_buf);\n    decoder.decode(decoded_buf, hdr_b64) catch {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"b64 decode\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n\n    var parsed = nats.protocol.headers.parse(\n        allocator,\n        decoded_buf,\n    );\n    defer parsed.deinit();\n\n    if (parsed.err != null) {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"header parse err\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    }\n\n    // User-supplied custom header must pass through.\n    const custom = parsed.get(\"X-Custom\") orelse {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"X-Custom missing\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n    if (!std.mem.eql(u8, custom, \"uservalue\")) {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"X-Custom wrong value\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    }\n\n    // Collision: opts.msg_id (\"jsopts-id\") must override the\n    // user's lowercase \"nats-msg-id\" entry. Check via the\n    // case-insensitive lookup.\n    const msg_id = parsed.get(\"Nats-Msg-Id\") orelse {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"Nats-Msg-Id missing\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    };\n    if (!std.mem.eql(u8, msg_id, \"jsopts-id\")) {\n        reportResult(\n            \"js_publish_msg\",\n            false,\n            \"opts.msg_id did not override\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG\") catch return;\n        d.deinit();\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_PUBLISH_MSG\",\n    ) catch {\n        reportResult(\"js_publish_msg\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_publish_msg\", true, \"\");\n}\n\n/// Regression: publishMsg with no user headers and default opts\n/// must succeed (takes the no-header publish path). Previously\n/// this tripped an assertion in protocol.headers.encodedSize()\n/// because PublishHeaderSet.slice() returned an empty (but\n/// non-null) slice.\npub fn testPublishMsgNoHeaders(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_publish_msg_no_headers\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PUBLISH_MSG_BARE\",\n        .subjects = &.{\"pubmsg.bare\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_publish_msg_no_headers\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var ack = js.publishMsg(allocator, .{\n        .subject = \"pubmsg.bare\",\n        .payload = \"hi\",\n    }) catch {\n        reportResult(\n            \"js_publish_msg_no_headers\",\n            false,\n            \"publishMsg failed\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG_BARE\") catch return;\n        d.deinit();\n        return;\n    };\n    ack.deinit();\n\n    var resp = js.getMsg(\n        \"TEST_PUBLISH_MSG_BARE\",\n        1,\n    ) catch {\n        reportResult(\n            \"js_publish_msg_no_headers\",\n            false,\n            \"getMsg failed\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG_BARE\") catch return;\n        d.deinit();\n        return;\n    };\n    defer resp.deinit();\n\n    if (resp.value.message == null) {\n        reportResult(\n            \"js_publish_msg_no_headers\",\n            false,\n            \"no stored message\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_MSG_BARE\") catch return;\n        d.deinit();\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_PUBLISH_MSG_BARE\",\n    ) catch {\n        reportResult(\"js_publish_msg_no_headers\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_publish_msg_no_headers\", true, \"\");\n}\n\n/// Regression: publishWithOpts with empty opts must succeed\n/// (same underlying bug as publishMsg with no headers).\npub fn testPublishWithOptsEmpty(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_publish_with_opts_empty\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_PUBLISH_OPTS_EMPTY\",\n        .subjects = &.{\"pubopts.empty\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_publish_with_opts_empty\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var ack = js.publishWithOpts(\n        \"pubopts.empty\",\n        \"payload\",\n        .{},\n    ) catch {\n        reportResult(\n            \"js_publish_with_opts_empty\",\n            false,\n            \"publishWithOpts failed\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_OPTS_EMPTY\") catch return;\n        d.deinit();\n        return;\n    };\n    ack.deinit();\n\n    var resp = js.getMsg(\n        \"TEST_PUBLISH_OPTS_EMPTY\",\n        1,\n    ) catch {\n        reportResult(\n            \"js_publish_with_opts_empty\",\n            false,\n            \"getMsg failed\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_OPTS_EMPTY\") catch return;\n        d.deinit();\n        return;\n    };\n    defer resp.deinit();\n\n    if (resp.value.message == null) {\n        reportResult(\n            \"js_publish_with_opts_empty\",\n            false,\n            \"no stored message\",\n        );\n        var d = js.deleteStream(\"TEST_PUBLISH_OPTS_EMPTY\") catch return;\n        d.deinit();\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_PUBLISH_OPTS_EMPTY\",\n    ) catch {\n        reportResult(\"js_publish_with_opts_empty\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_publish_with_opts_empty\", true, \"\");\n}\n\npub fn testKvUpdateBucket(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_update_bucket\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    _ = js.createKeyValue(.{\n        .bucket = \"UPD_BUCKET\",\n        .history = 1,\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_update_bucket\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    _ = js.updateKeyValue(.{\n        .bucket = \"UPD_BUCKET\",\n        .history = 5,\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_update_bucket\",\n            false,\n            \"update bucket\",\n        );\n        return;\n    };\n\n    // Verify via stream info\n    var info = js.streamInfo(\n        \"KV_UPD_BUCKET\",\n    ) catch {\n        reportResult(\n            \"kv_update_bucket\",\n            false,\n            \"info failed\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.config) |cfg| {\n        if (cfg.max_msgs_per_subject) |mps| {\n            if (mps != 5) {\n                var buf: [64]u8 = undefined;\n                const m = std.fmt.bufPrint(\n                    &buf,\n                    \"mps {d}, want 5\",\n                    .{mps},\n                ) catch \"wrong\";\n                reportResult(\n                    \"kv_update_bucket\",\n                    false,\n                    m,\n                );\n                return;\n            }\n        } else {\n            reportResult(\n                \"kv_update_bucket\",\n                false,\n                \"no max_msgs_per_subj\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"kv_update_bucket\",\n            false,\n            \"no config\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"UPD_BUCKET\",\n    ) catch {\n        reportResult(\n            \"kv_update_bucket\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"kv_update_bucket\", true, \"\");\n}\n\npub fn testKvCreateOrUpdateBucket(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_upsert_bucket\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create via createOrUpdate\n    _ = js.createOrUpdateKeyValue(.{\n        .bucket = \"UPSERT_KV\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_upsert_bucket\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Update via createOrUpdate\n    _ = js.createOrUpdateKeyValue(.{\n        .bucket = \"UPSERT_KV\",\n        .history = 10,\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_upsert_bucket\",\n            false,\n            \"update bucket\",\n        );\n        return;\n    };\n\n    // Verify via stream info\n    var info = js.streamInfo(\n        \"KV_UPSERT_KV\",\n    ) catch {\n        reportResult(\n            \"kv_upsert_bucket\",\n            false,\n            \"info failed\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.config) |cfg| {\n        if (cfg.max_msgs_per_subject) |mps| {\n            if (mps != 10) {\n                var buf: [64]u8 = undefined;\n                const m = std.fmt.bufPrint(\n                    &buf,\n                    \"mps {d}, want 10\",\n                    .{mps},\n                ) catch \"wrong\";\n                reportResult(\n                    \"kv_upsert_bucket\",\n                    false,\n                    m,\n                );\n                return;\n            }\n        } else {\n            reportResult(\n                \"kv_upsert_bucket\",\n                false,\n                \"no max_msgs_per_subj\",\n            );\n            return;\n        }\n    } else {\n        reportResult(\n            \"kv_upsert_bucket\",\n            false,\n            \"no config\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"UPSERT_KV\",\n    ) catch {\n        reportResult(\n            \"kv_upsert_bucket\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\n        \"kv_upsert_bucket\",\n        true,\n        \"\",\n    );\n}\n\npub fn testKvPurgeDeletes(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"PURGE_DEL\",\n        .storage = .memory,\n        .history = 5,\n    }) catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put 5 keys\n    _ = kv.put(\"a\", \"1\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"put a\",\n        );\n        return;\n    };\n    _ = kv.put(\"b\", \"2\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"put b\",\n        );\n        return;\n    };\n    _ = kv.put(\"c\", \"3\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"put c\",\n        );\n        return;\n    };\n    _ = kv.put(\"d\", \"4\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"put d\",\n        );\n        return;\n    };\n    _ = kv.put(\"e\", \"5\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"put e\",\n        );\n        return;\n    };\n\n    // Delete a, b, c\n    _ = kv.delete(\"a\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"del a\",\n        );\n        return;\n    };\n    _ = kv.delete(\"b\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"del b\",\n        );\n        return;\n    };\n    _ = kv.delete(\"c\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"del c\",\n        );\n        return;\n    };\n\n    // keys() should return 2 live keys\n    const key_list = kv.keys(allocator) catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"keys()\",\n        );\n        return;\n    };\n    defer {\n        for (key_list) |k| allocator.free(k);\n        allocator.free(key_list);\n    }\n\n    if (key_list.len != 2) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"keys: {d}, want 2\",\n            .{key_list.len},\n        ) catch \"wrong\";\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            m,\n        );\n        return;\n    }\n\n    // Fresh delete markers should not match an age filter.\n    const skipped = kv.purgeDeletes(\n        .{ .older_than_ns = @as(i64, 60 * std.time.ns_per_s) },\n    ) catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"purgeDeletes age\",\n        );\n        return;\n    };\n    if (skipped != 0) {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"purged fresh markers\",\n        );\n        return;\n    }\n\n    // Purge delete markers\n    const purged = kv.purgeDeletes(\n        .{},\n    ) catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"purgeDeletes\",\n        );\n        return;\n    };\n    _ = purged;\n\n    // Verify d and e still accessible\n    var ed = (kv.get(\"d\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"get d after purge\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"d missing\",\n        );\n        return;\n    };\n    defer ed.deinit();\n    if (ed.operation != .put) {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"d not put op\",\n        );\n        return;\n    }\n\n    var ee = (kv.get(\"e\") catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"get e after purge\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"e missing\",\n        );\n        return;\n    };\n    defer ee.deinit();\n    if (ee.operation != .put) {\n        reportResult(\n            \"kv_purge_deletes\",\n            false,\n            \"e not put op\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"PURGE_DEL\",\n    ) catch {\n        reportResult(\n            \"kv_purge_deletes\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\n        \"kv_purge_deletes\",\n        true,\n        \"\",\n    );\n}\n\npub fn testKvStoreNames(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_store_names\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create 3 KV buckets\n    _ = js.createKeyValue(.{\n        .bucket = \"NAMES_A\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_store_names\",\n            false,\n            \"create A\",\n        );\n        return;\n    };\n    _ = js.createKeyValue(.{\n        .bucket = \"NAMES_B\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_store_names\",\n            false,\n            \"create B\",\n        );\n        return;\n    };\n    _ = js.createKeyValue(.{\n        .bucket = \"NAMES_C\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_store_names\",\n            false,\n            \"create C\",\n        );\n        return;\n    };\n\n    const names = js.keyValueStoreNames(\n        allocator,\n    ) catch {\n        reportResult(\n            \"kv_store_names\",\n            false,\n            \"storeNames()\",\n        );\n        return;\n    };\n    defer {\n        for (names) |n| allocator.free(n);\n        allocator.free(names);\n    }\n\n    // Verify our 3 buckets are in the list\n    var found_a = false;\n    var found_b = false;\n    var found_c = false;\n    for (names) |n| {\n        if (std.mem.eql(u8, n, \"NAMES_A\"))\n            found_a = true;\n        if (std.mem.eql(u8, n, \"NAMES_B\"))\n            found_b = true;\n        if (std.mem.eql(u8, n, \"NAMES_C\"))\n            found_c = true;\n    }\n\n    if (!found_a or !found_b or !found_c) {\n        reportResult(\n            \"kv_store_names\",\n            false,\n            \"missing bucket names\",\n        );\n        return;\n    }\n\n    // Verify KV_ prefix was stripped\n    for (names) |n| {\n        if (std.mem.startsWith(u8, n, \"KV_\")) {\n            reportResult(\n                \"kv_store_names\",\n                false,\n                \"prefix not stripped\",\n            );\n            return;\n        }\n    }\n\n    // Cleanup\n    var da = js.deleteKeyValue(\n        \"NAMES_A\",\n    ) catch {\n        reportResult(\n            \"kv_store_names\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    da.deinit();\n    var db = js.deleteKeyValue(\n        \"NAMES_B\",\n    ) catch {\n        reportResult(\n            \"kv_store_names\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    db.deinit();\n    var dc = js.deleteKeyValue(\n        \"NAMES_C\",\n    ) catch {\n        reportResult(\n            \"kv_store_names\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    dc.deinit();\n\n    reportResult(\"kv_store_names\", true, \"\");\n}\n\npub fn testKvWatchIgnoreDeletes(\n    allocator: std.mem.Allocator,\n) void {\n    _ = allocator;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(std.heap.page_allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        std.heap.page_allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"WATCH_IGN\",\n        .storage = .memory,\n        .history = 5,\n    }) catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put a=1, b=2, then delete a\n    _ = kv.put(\"a\", \"1\") catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            \"put a\",\n        );\n        return;\n    };\n    _ = kv.put(\"b\", \"2\") catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            \"put b\",\n        );\n        return;\n    };\n    _ = kv.delete(\"a\") catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            \"delete a\",\n        );\n        return;\n    };\n\n    // Watch with ignore_deletes\n    var watcher = kv.watchAllWithOpts(.{\n        .ignore_deletes = true,\n        .include_history = true,\n    }) catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            \"watchAll\",\n        );\n        return;\n    };\n    defer watcher.deinit();\n\n    // Collect entries until null\n    var count: u32 = 0;\n    while (count < 10) {\n        var entry = (watcher.next(\n            3000,\n        ) catch break) orelse break;\n        entry.deinit();\n        count += 1;\n    }\n\n    // Should get 2 (put a, put b) not delete\n    if (count != 2) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 2\",\n            .{count},\n        ) catch \"wrong count\";\n        reportResult(\n            \"kv_watch_ign_del\",\n            false,\n            m,\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"WATCH_IGN\",\n    ) catch {\n        reportResult(\n            \"kv_watch_ign_del\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\n        \"kv_watch_ign_del\",\n        true,\n        \"\",\n    );\n}\n\npub fn testKvWatchUpdatesOnly(\n    allocator: std.mem.Allocator,\n) void {\n    _ = allocator;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(std.heap.page_allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        std.heap.page_allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"WATCH_UPD\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put a key before watching\n    _ = kv.put(\"pre\", \"before\") catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"put pre\",\n        );\n        return;\n    };\n\n    // Watch with updates_only\n    var watcher = kv.watchAllWithOpts(.{\n        .updates_only = true,\n    }) catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"watchAll\",\n        );\n        return;\n    };\n    defer watcher.deinit();\n\n    // First next() should return null (no initial)\n    const first = (watcher.next(\n        2000,\n    ) catch null) orelse null;\n    if (first != null) {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"should be null first\",\n        );\n        return;\n    }\n\n    // Put a new key\n    _ = kv.put(\"post\", \"after\") catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"put post\",\n        );\n        return;\n    };\n\n    // Should get the new entry\n    var entry = (watcher.next(5000) catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"next() failed\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"no post entry\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (!std.mem.eql(u8, entry.key, \"post\")) {\n        reportResult(\n            \"kv_watch_upd_only\",\n            false,\n            \"wrong key\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"WATCH_UPD\",\n    ) catch {\n        reportResult(\n            \"kv_watch_upd_only\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\n        \"kv_watch_upd_only\",\n        true,\n        \"\",\n    );\n}\n\npub fn testKvListKeys(\n    allocator: std.mem.Allocator,\n) void {\n    _ = allocator;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(std.heap.page_allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        std.heap.page_allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"LIST_KEYS\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put 5 keys\n    _ = kv.put(\"k1\", \"v1\") catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"put k1\",\n        );\n        return;\n    };\n    _ = kv.put(\"k2\", \"v2\") catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"put k2\",\n        );\n        return;\n    };\n    _ = kv.put(\"k3\", \"v3\") catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"put k3\",\n        );\n        return;\n    };\n    _ = kv.put(\"k4\", \"v4\") catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"put k4\",\n        );\n        return;\n    };\n    _ = kv.put(\"k5\", \"v5\") catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"put k5\",\n        );\n        return;\n    };\n\n    // Delete k3\n    _ = kv.delete(\"k3\") catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"del k3\",\n        );\n        return;\n    };\n\n    // List keys via lister\n    var lister = kv.listKeys() catch {\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            \"listKeys()\",\n        );\n        return;\n    };\n    defer lister.deinit();\n\n    var count: u32 = 0;\n    while (count < 10) {\n        const key = (lister.next() catch {\n            break;\n        }) orelse break;\n        _ = key;\n        count += 1;\n    }\n\n    if (count != 4) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d} keys, expected 4\",\n            .{count},\n        ) catch \"wrong count\";\n        reportResult(\n            \"kv_list_keys\",\n            false,\n            m,\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"LIST_KEYS\",\n    ) catch {\n        reportResult(\n            \"kv_list_keys\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"kv_list_keys\", true, \"\");\n}\n\npub fn testDoubleAck(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_double_ack\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_DACK\",\n        .subjects = &.{\"dack.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var c = js.createConsumer(\"TEST_DACK\", .{\n        .name = \"dack-c\",\n        .durable_name = \"dack-c\",\n        .ack_policy = .explicit,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    var a = js.publish(\n        \"dack.test\",\n        \"double-ack-data\",\n    ) catch {\n        reportResult(name, false, \"publish\");\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"TEST_DACK\",\n    };\n    pull.setConsumer(\"dack-c\") catch unreachable;\n\n    var msg = (pull.next(5000) catch {\n        reportResult(name, false, \"fetch 1\");\n        return;\n    }) orelse {\n        reportResult(name, false, \"no msg\");\n        return;\n    };\n\n    msg.doubleAck(5000) catch {\n        reportResult(\n            name,\n            false,\n            \"doubleAck failed\",\n        );\n        msg.deinit();\n        return;\n    };\n    msg.deinit();\n\n    // After doubleAck, no messages should remain\n    var r = pull.fetchNoWait(10) catch {\n        reportResult(name, false, \"fetch 2\");\n        return;\n    };\n    defer r.deinit();\n\n    if (r.count() != 0) {\n        reportResult(\n            name,\n            false,\n            \"expected 0 after dack\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\"TEST_DACK\") catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testUpdatePushConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_upd_push_cons\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_UPDPUSH\",\n        .subjects = &.{\"updpush.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var pc = js.createPushConsumer(\n        \"TEST_UPDPUSH\",\n        .{\n            .name = \"updpush-c\",\n            .deliver_subject = \"_UPD.test\",\n            .description = \"v1\",\n            .ack_policy = .none,\n        },\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"create push cons\",\n        );\n        return;\n    };\n    pc.deinit();\n\n    // Update description to \"v2\"\n    var upd = js.updatePushConsumer(\n        \"TEST_UPDPUSH\",\n        .{\n            .name = \"updpush-c\",\n            .deliver_subject = \"_UPD.test\",\n            .description = \"v2\",\n            .ack_policy = .none,\n        },\n    ) catch {\n        reportResult(name, false, \"update\");\n        return;\n    };\n    upd.deinit();\n\n    // Verify description changed\n    var info = js.consumerInfo(\n        \"TEST_UPDPUSH\",\n        \"updpush-c\",\n    ) catch {\n        reportResult(name, false, \"info\");\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.config) |cfg| {\n        if (cfg.description) |desc| {\n            if (!std.mem.eql(u8, desc, \"v2\")) {\n                reportResult(\n                    name,\n                    false,\n                    \"desc not v2\",\n                );\n                return;\n            }\n        } else {\n            reportResult(\n                name,\n                false,\n                \"no description\",\n            );\n            return;\n        }\n    } else {\n        reportResult(name, false, \"no config\");\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_UPDPUSH\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testGetPushConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_get_push_cons\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_GETPUSH\",\n        .subjects = &.{\"getpush.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var pc = js.createPushConsumer(\n        \"TEST_GETPUSH\",\n        .{\n            .name = \"getpush-c\",\n            .deliver_subject = \"_GET.test\",\n            .ack_policy = .none,\n        },\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"create push cons\",\n        );\n        return;\n    };\n    pc.deinit();\n\n    // Get push consumer via pushConsumer()\n    const push_sub = js.pushConsumer(\n        \"TEST_GETPUSH\",\n        \"getpush-c\",\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"pushConsumer()\",\n        );\n        return;\n    };\n\n    const ds = push_sub.deliverSubject();\n    if (!std.mem.eql(u8, ds, \"_GET.test\")) {\n        reportResult(\n            name,\n            false,\n            \"wrong deliver subj\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_GETPUSH\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testKvPutString(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_put_string\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"PUTSTR\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    const rev = kv.putString(\n        \"greeting\",\n        \"hello\",\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"putString\",\n        );\n        return;\n    };\n\n    if (rev == 0) {\n        reportResult(\n            name,\n            false,\n            \"rev should be > 0\",\n        );\n        return;\n    }\n\n    var entry = (kv.get(\"greeting\") catch {\n        reportResult(name, false, \"get\");\n        return;\n    }) orelse {\n        reportResult(\n            name,\n            false,\n            \"key not found\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (!std.mem.eql(u8, entry.value, \"hello\")) {\n        reportResult(\n            name,\n            false,\n            \"wrong value\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\"PUTSTR\") catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testKvDeleteLastRev(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_del_last_rev\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"DEL_REV\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    const rev1 = kv.put(\"x\", \"v1\") catch {\n        reportResult(name, false, \"put 1\");\n        return;\n    };\n    const rev2 = kv.put(\"x\", \"v2\") catch {\n        reportResult(name, false, \"put 2\");\n        return;\n    };\n\n    // Wrong revision should fail\n    _ = kv.deleteWithOpts(\"x\", .{\n        .last_revision = rev1,\n    }) catch |err| {\n        if (err == error.ApiError) {\n            // Expected: now try correct rev\n            _ = kv.deleteWithOpts(\"x\", .{\n                .last_revision = rev2,\n            }) catch {\n                reportResult(\n                    name,\n                    false,\n                    \"correct rev failed\",\n                );\n                return;\n            };\n            var d = js.deleteKeyValue(\n                \"DEL_REV\",\n            ) catch {\n                reportResult(name, true, \"\");\n                return;\n            };\n            d.deinit();\n            reportResult(name, true, \"\");\n            return;\n        }\n        reportResult(\n            name,\n            false,\n            \"wrong error type\",\n        );\n        return;\n    };\n\n    reportResult(\n        name,\n        false,\n        \"wrong rev should fail\",\n    );\n}\n\npub fn testKvPurgeLastRev(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_purge_last_rev\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"PURGE_REV\",\n        .storage = .memory,\n        .history = 5,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    const rev1 = kv.put(\"y\", \"v1\") catch {\n        reportResult(name, false, \"put 1\");\n        return;\n    };\n    const rev2 = kv.put(\"y\", \"v2\") catch {\n        reportResult(name, false, \"put 2\");\n        return;\n    };\n\n    // Wrong revision should fail\n    _ = kv.purgeWithOpts(\"y\", .{\n        .last_revision = rev1,\n    }) catch |err| {\n        if (err == error.ApiError) {\n            // Expected: now try correct rev\n            _ = kv.purgeWithOpts(\"y\", .{\n                .last_revision = rev2,\n            }) catch {\n                reportResult(\n                    name,\n                    false,\n                    \"correct rev failed\",\n                );\n                return;\n            };\n            var d = js.deleteKeyValue(\n                \"PURGE_REV\",\n            ) catch {\n                reportResult(name, true, \"\");\n                return;\n            };\n            d.deinit();\n            reportResult(name, true, \"\");\n            return;\n        }\n        reportResult(\n            name,\n            false,\n            \"wrong error type\",\n        );\n        return;\n    };\n\n    reportResult(\n        name,\n        false,\n        \"wrong rev should fail\",\n    );\n}\n\npub fn testKvListKeysFiltered(\n    allocator: std.mem.Allocator,\n) void {\n    _ = allocator;\n    const name = \"kv_list_filtered\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(std.heap.page_allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        std.heap.page_allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"FILT_KEYS\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    _ = kv.put(\"a\", \"1\") catch {\n        reportResult(name, false, \"put a\");\n        return;\n    };\n    _ = kv.put(\"b\", \"2\") catch {\n        reportResult(name, false, \"put b\");\n        return;\n    };\n    _ = kv.put(\"c\", \"3\") catch {\n        reportResult(name, false, \"put c\");\n        return;\n    };\n    _ = kv.put(\"d\", \"4\") catch {\n        reportResult(name, false, \"put d\");\n        return;\n    };\n\n    var lister = kv.listKeysFiltered(\n        &.{ \"a\", \"c\" },\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"listKeysFiltered\",\n        );\n        return;\n    };\n    defer lister.deinit();\n\n    var count: u32 = 0;\n    while (count < 10) {\n        const key = (lister.next() catch {\n            break;\n        }) orelse break;\n        _ = key;\n        count += 1;\n    }\n\n    if (count != 2) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 2\",\n            .{count},\n        ) catch \"wrong count\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"FILT_KEYS\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testKvHistoryWithOpts(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_hist_opts\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"HIST_OPTS\",\n        .storage = .memory,\n        .history = 5,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    _ = kv.put(\"z\", \"val1\") catch {\n        reportResult(name, false, \"put 1\");\n        return;\n    };\n    _ = kv.put(\"z\", \"val2\") catch {\n        reportResult(name, false, \"put 2\");\n        return;\n    };\n    _ = kv.put(\"z\", \"val3\") catch {\n        reportResult(name, false, \"put 3\");\n        return;\n    };\n\n    const hist = kv.historyWithOpts(\n        allocator,\n        \"z\",\n        .{ .meta_only = true },\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"historyWithOpts\",\n        );\n        return;\n    };\n    defer {\n        for (hist) |*h| h.deinit();\n        allocator.free(hist);\n    }\n\n    if (hist.len != 3) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, expected 3\",\n            .{hist.len},\n        ) catch \"wrong\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    // Verify meta_only: values should be empty\n    for (hist) |entry| {\n        if (entry.value.len != 0) {\n            reportResult(\n                name,\n                false,\n                \"meta_only not empty\",\n            );\n            return;\n        }\n        if (entry.revision == 0) {\n            reportResult(\n                name,\n                false,\n                \"rev should be > 0\",\n            );\n            return;\n        }\n    }\n\n    var d = js.deleteKeyValue(\n        \"HIST_OPTS\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testConnOptions(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_conn_options\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Test conn() accessor\n    const c = js.conn();\n    if (!c.isConnected()) {\n        reportResult(\n            name,\n            false,\n            \"conn not connected\",\n        );\n        return;\n    }\n\n    // Test options() accessor\n    const opts = js.options();\n    if (!std.mem.startsWith(\n        u8,\n        opts.api_prefix,\n        \"$JS.\",\n    )) {\n        reportResult(\n            name,\n            false,\n            \"bad api_prefix\",\n        );\n        return;\n    }\n\n    if (opts.timeout_ms == 0) {\n        reportResult(\n            name,\n            false,\n            \"timeout_ms is 0\",\n        );\n        return;\n    }\n\n    reportResult(name, true, \"\");\n}\n\npub fn testKvCreateWithTTL(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_create_ttl\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream with TTL support\n    var stream = js.createStream(.{\n        .name = \"KV_TTL_CREATE\",\n        .subjects = &.{\"$KV.TTL_CREATE.>\"},\n        .storage = .memory,\n        .max_msgs_per_subject = 1,\n        .allow_msg_ttl = true,\n    }) catch {\n        // Server may not support TTL\n        reportResult(\n            name,\n            true,\n            \"skipped: no ttl\",\n        );\n        return;\n    };\n    stream.deinit();\n\n    var kv = js.keyValue(\"TTL_CREATE\") catch {\n        reportResult(name, false, \"bind kv\");\n        return;\n    };\n\n    // createWithOpts with TTL\n    const rev = kv.createWithOpts(\n        \"ttlkey\",\n        \"val\",\n        .{ .ttl = \"1s\" },\n    ) catch |err| {\n        if (err == error.ApiError) {\n            // TTL not supported on this server\n            var dd = js.deleteStream(\n                \"KV_TTL_CREATE\",\n            ) catch {\n                reportResult(\n                    name,\n                    true,\n                    \"skipped: ttl err\",\n                );\n                return;\n            };\n            dd.deinit();\n            reportResult(\n                name,\n                true,\n                \"skipped: ttl err\",\n            );\n            return;\n        }\n        reportResult(\n            name,\n            false,\n            \"createWithOpts\",\n        );\n        return;\n    };\n\n    if (rev == 0) {\n        reportResult(\n            name,\n            false,\n            \"rev should be > 0\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"KV_TTL_CREATE\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testPublishAsync(\n    allocator: std.mem.Allocator,\n) void {\n    const stream_name = \"TEST_ASYNC_PUB\";\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_pub_async\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    deleteStreamIfExists(&js, stream_name);\n\n    var stream = js.createStream(.{\n        .name = stream_name,\n        .subjects = &.{\"async.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_pub_async\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer deleteStreamIfExists(&js, stream_name);\n    defer stream.deinit();\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{ .max_pending = 64 },\n    ) catch {\n        reportResult(\n            \"js_pub_async\",\n            false,\n            \"init async pub\",\n        );\n        return;\n    };\n    defer ap.deinit();\n\n    // Publish 20 messages asynchronously\n    var futures: [20]*nats.jetstream.PubAckFuture =\n        undefined;\n    var i: usize = 0;\n    while (i < 20) : (i += 1) {\n        futures[i] = ap.publish(\n            \"async.test\",\n            \"async-data\",\n        ) catch {\n            reportResult(\n                \"js_pub_async\",\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    // Wait for all acks\n    ap.waitComplete(10000) catch {\n        reportResult(\n            \"js_pub_async\",\n            false,\n            \"waitComplete timeout\",\n        );\n        // Clean up futures\n        for (futures[0..i]) |f| f.deinit();\n        return;\n    };\n\n    // Verify all futures resolved\n    var all_ok = true;\n    for (futures[0..20]) |f| {\n        if (f.result() == null) {\n            all_ok = false;\n        }\n    }\n\n    // Check pending is 0\n    if (ap.publishAsyncPending() != 0) {\n        reportResult(\n            \"js_pub_async\",\n            false,\n            \"pending not 0\",\n        );\n        for (futures[0..20]) |f| f.deinit();\n        return;\n    }\n\n    // Verify stream has 20 messages\n    var info = js.streamInfo(\n        stream_name,\n    ) catch {\n        for (futures[0..20]) |f| f.deinit();\n        reportResult(\n            \"js_pub_async\",\n            false,\n            \"stream info\",\n        );\n        return;\n    };\n    defer info.deinit();\n\n    const msg_count = if (info.value.state) |s|\n        s.messages\n    else\n        0;\n\n    for (futures[0..20]) |f| f.deinit();\n\n    if (!all_ok or msg_count != 20) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"ok={}, msgs={d}\",\n            .{ all_ok, msg_count },\n        ) catch \"verify failed\";\n        reportResult(\n            \"js_pub_async\",\n            false,\n            m,\n        );\n        return;\n    }\n\n    reportResult(\"js_pub_async\", true, \"\");\n}\n\npub fn testPublishAsyncFutureWait(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var stream = js.createStream(.{\n        .name = \"TEST_ASYNC_WAIT\",\n        .subjects = &.{\"await.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer stream.deinit();\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{},\n    ) catch {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"init\",\n        );\n        return;\n    };\n    defer ap.deinit();\n\n    // Publish one message and wait on the future\n    const fut = ap.publish(\n        \"await.test\",\n        \"wait-data\",\n    ) catch {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"publish\",\n        );\n        return;\n    };\n    defer fut.deinit();\n\n    const ack = fut.wait(5000) catch {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"wait timeout\",\n        );\n        return;\n    };\n\n    if (ack.seq != 1) {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"wrong seq\",\n        );\n        return;\n    }\n\n    var churn = std.ArrayList([]u8).empty;\n    defer {\n        for (churn.items) |buf| allocator.free(buf);\n        churn.deinit(allocator);\n    }\n    for (0..32) |_| {\n        const buf = allocator.alloc(u8, 64) catch {\n            reportResult(\n                \"js_async_wait\",\n                false,\n                \"alloc churn failed\",\n            );\n            return;\n        };\n        churn.append(allocator, buf) catch {\n            allocator.free(buf);\n            reportResult(\n                \"js_async_wait\",\n                false,\n                \"alloc churn append failed\",\n            );\n            return;\n        };\n    }\n\n    if (ack.stream == null or !std.mem.eql(\n        u8,\n        ack.stream.?,\n        \"TEST_ASYNC_WAIT\",\n    )) {\n        reportResult(\n            \"js_async_wait\",\n            false,\n            \"ack stream invalid\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_ASYNC_WAIT\",\n    ) catch {\n        reportResult(\n            \"js_async_wait\",\n            true,\n            \"\",\n        );\n        return;\n    };\n    d.deinit();\n    reportResult(\"js_async_wait\", true, \"\");\n}\n\npub fn testPublishAsyncExpectedSeqParity(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_async_exp_seq\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"TEST_ASYNC_EXPSEQ\",\n        .subjects = &.{\"atest.expseq.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(name, false, \"create stream\");\n        return;\n    };\n    defer s.deinit();\n\n    var a1 = js.publish(\n        \"atest.expseq.data\",\n        \"first\",\n    ) catch {\n        reportResult(name, false, \"sync pub 1\");\n        return;\n    };\n    a1.deinit();\n\n    var a2 = js.publishWithOpts(\n        \"atest.expseq.data\",\n        \"second\",\n        .{ .expected_last_seq = 1 },\n    ) catch {\n        reportResult(name, false, \"sync pub 2\");\n        return;\n    };\n    a2.deinit();\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{},\n    ) catch {\n        reportResult(name, false, \"init ap\");\n        return;\n    };\n    defer ap.deinit();\n\n    const fut = ap.publishWithOpts(\n        \"atest.expseq.data\",\n        \"should-fail\",\n        .{ .expected_last_seq = 0 },\n    ) catch {\n        reportResult(name, false, \"async publish\");\n        return;\n    };\n    defer fut.deinit();\n\n    const ack_or_err = fut.wait(5000);\n    if (ack_or_err) |ack| {\n        _ = ack;\n        reportResult(name, false, \"should have failed\");\n        return;\n    } else |err| {\n        if (err != error.ApiError) {\n            reportResult(name, false, \"wrong error\");\n            return;\n        }\n    }\n\n    var info = js.streamInfo(\n        \"TEST_ASYNC_EXPSEQ\",\n    ) catch {\n        reportResult(name, false, \"stream info\");\n        return;\n    };\n    defer info.deinit();\n\n    const msg_count = if (info.value.state) |st|\n        st.messages\n    else\n        0;\n    if (msg_count != 2) {\n        reportResult(name, false, \"wrong msg count\");\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"TEST_ASYNC_EXPSEQ\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testKvEmptyValue(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_empty_value\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"EMPTY_VAL\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    _ = kv.put(\"empty\", \"\") catch {\n        reportResult(name, false, \"put empty\");\n        return;\n    };\n\n    var entry = (kv.get(\"empty\") catch {\n        reportResult(name, false, \"get\");\n        return;\n    }) orelse {\n        reportResult(\n            name,\n            false,\n            \"key not found\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (entry.value.len != 0) {\n        reportResult(\n            name,\n            false,\n            \"value not empty\",\n        );\n        return;\n    }\n\n    if (entry.operation != .put) {\n        reportResult(\n            name,\n            false,\n            \"wrong operation\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"EMPTY_VAL\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testKvKeySpecialChars(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_special_keys\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"SPECIAL_KEYS\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put keys with dots, dashes, underscores\n    _ = kv.put(\"my.nested.key\", \"v1\") catch {\n        reportResult(name, false, \"put dot\");\n        return;\n    };\n    _ = kv.put(\"my-dashed\", \"v2\") catch {\n        reportResult(name, false, \"put dash\");\n        return;\n    };\n    _ = kv.put(\"under_score\", \"v3\") catch {\n        reportResult(\n            name,\n            false,\n            \"put underscore\",\n        );\n        return;\n    };\n\n    // Verify all readable\n    var e1 = (kv.get(\"my.nested.key\") catch {\n        reportResult(name, false, \"get dot\");\n        return;\n    }) orelse {\n        reportResult(name, false, \"dot missing\");\n        return;\n    };\n    defer e1.deinit();\n    if (!std.mem.eql(u8, e1.value, \"v1\")) {\n        reportResult(\n            name,\n            false,\n            \"wrong dot value\",\n        );\n        return;\n    }\n\n    var e2 = (kv.get(\"my-dashed\") catch {\n        reportResult(name, false, \"get dash\");\n        return;\n    }) orelse {\n        reportResult(\n            name,\n            false,\n            \"dash missing\",\n        );\n        return;\n    };\n    defer e2.deinit();\n    if (!std.mem.eql(u8, e2.value, \"v2\")) {\n        reportResult(\n            name,\n            false,\n            \"wrong dash value\",\n        );\n        return;\n    }\n\n    var e3 = (kv.get(\"under_score\") catch {\n        reportResult(\n            name,\n            false,\n            \"get underscore\",\n        );\n        return;\n    }) orelse {\n        reportResult(\n            name,\n            false,\n            \"underscore missing\",\n        );\n        return;\n    };\n    defer e3.deinit();\n    if (!std.mem.eql(u8, e3.value, \"v3\")) {\n        reportResult(\n            name,\n            false,\n            \"wrong uscore value\",\n        );\n        return;\n    }\n\n    // Wildcard key should be rejected\n    _ = kv.put(\"bad*key\", \"nope\") catch |err| {\n        if (err ==\n            nats.jetstream.errors.Error.InvalidKey)\n        {\n            var d = js.deleteKeyValue(\n                \"SPECIAL_KEYS\",\n            ) catch {\n                reportResult(name, true, \"\");\n                return;\n            };\n            d.deinit();\n            reportResult(name, true, \"\");\n            return;\n        }\n        reportResult(\n            name,\n            false,\n            \"wrong error for *\",\n        );\n        return;\n    };\n\n    reportResult(\n        name,\n        false,\n        \"wildcard should fail\",\n    );\n}\n\npub fn testKvCreateExisting(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_create_existing\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"CAS_CREATE\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put initial value\n    _ = kv.put(\"exists\", \"v1\") catch {\n        reportResult(name, false, \"put\");\n        return;\n    };\n\n    // create() on existing key should fail\n    _ = kv.create(\"exists\", \"v2\") catch |err| {\n        if (err == error.ApiError) {\n            // Verify value is still v1\n            var entry = (kv.get(\n                \"exists\",\n            ) catch {\n                reportResult(\n                    name,\n                    false,\n                    \"get after\",\n                );\n                return;\n            }) orelse {\n                reportResult(\n                    name,\n                    false,\n                    \"key gone\",\n                );\n                return;\n            };\n            defer entry.deinit();\n            if (!std.mem.eql(\n                u8,\n                entry.value,\n                \"v1\",\n            )) {\n                reportResult(\n                    name,\n                    false,\n                    \"value changed\",\n                );\n                return;\n            }\n            var d = js.deleteKeyValue(\n                \"CAS_CREATE\",\n            ) catch {\n                reportResult(name, true, \"\");\n                return;\n            };\n            d.deinit();\n            reportResult(name, true, \"\");\n            return;\n        }\n        reportResult(\n            name,\n            false,\n            \"wrong error\",\n        );\n        return;\n    };\n\n    reportResult(\n        name,\n        false,\n        \"create should fail\",\n    );\n}\n\npub fn testKvUpdateWrongRev(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_update_wrong_rev\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"CAS_UPDATE\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    const rev1 = kv.put(\"cas\", \"v1\") catch {\n        reportResult(name, false, \"put 1\");\n        return;\n    };\n    const rev2 = kv.put(\"cas\", \"v2\") catch {\n        reportResult(name, false, \"put 2\");\n        return;\n    };\n\n    // Update with stale rev1 should fail\n    _ = kv.update(\n        \"cas\",\n        \"v3\",\n        rev1,\n    ) catch |err| {\n        if (err == error.ApiError) {\n            // Update with correct rev2\n            const rev3 = kv.update(\n                \"cas\",\n                \"v3\",\n                rev2,\n            ) catch {\n                reportResult(\n                    name,\n                    false,\n                    \"correct rev fail\",\n                );\n                return;\n            };\n            // Verify value and revision\n            var entry = (kv.get(\n                \"cas\",\n            ) catch {\n                reportResult(\n                    name,\n                    false,\n                    \"get\",\n                );\n                return;\n            }) orelse {\n                reportResult(\n                    name,\n                    false,\n                    \"key gone\",\n                );\n                return;\n            };\n            defer entry.deinit();\n            if (!std.mem.eql(\n                u8,\n                entry.value,\n                \"v3\",\n            )) {\n                reportResult(\n                    name,\n                    false,\n                    \"wrong value\",\n                );\n                return;\n            }\n            if (entry.revision != rev3) {\n                reportResult(\n                    name,\n                    false,\n                    \"wrong revision\",\n                );\n                return;\n            }\n            var d = js.deleteKeyValue(\n                \"CAS_UPDATE\",\n            ) catch {\n                reportResult(\n                    name,\n                    true,\n                    \"\",\n                );\n                return;\n            };\n            d.deinit();\n            reportResult(name, true, \"\");\n            return;\n        }\n        reportResult(\n            name,\n            false,\n            \"wrong error\",\n        );\n        return;\n    };\n\n    reportResult(\n        name,\n        false,\n        \"stale rev should fail\",\n    );\n}\n\npub fn testStreamMaxMsgs(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_stream_max_msgs\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"MAX_MSGS\",\n        .subjects = &.{\"max.>\"},\n        .storage = .memory,\n        .max_msgs = 5,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // Publish 10 messages\n    var i: u32 = 0;\n    while (i < 10) : (i += 1) {\n        var a = js.publish(\n            \"max.test\",\n            \"msg-data\",\n        ) catch {\n            reportResult(\n                name,\n                false,\n                \"publish\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Stream should have exactly 5 messages\n    var info = js.streamInfo(\n        \"MAX_MSGS\",\n    ) catch {\n        reportResult(name, false, \"info\");\n        return;\n    };\n    defer info.deinit();\n\n    if (info.value.state) |st| {\n        if (st.messages != 5) {\n            var buf: [64]u8 = undefined;\n            const m = std.fmt.bufPrint(\n                &buf,\n                \"msgs={d}, want 5\",\n                .{st.messages},\n            ) catch \"wrong count\";\n            reportResult(name, false, m);\n            return;\n        }\n    }\n\n    // Seq 1 should be discarded\n    var bad = js.getMsg(\"MAX_MSGS\", 1);\n    if (bad) |*r| {\n        r.deinit();\n        reportResult(\n            name,\n            false,\n            \"seq 1 should be gone\",\n        );\n        return;\n    } else |_| {}\n\n    // Seq 6 should exist\n    var ok = js.getMsg(\n        \"MAX_MSGS\",\n        6,\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"seq 6 should exist\",\n        );\n        return;\n    };\n    ok.deinit();\n\n    var d = js.deleteStream(\n        \"MAX_MSGS\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testConsumerMaxDeliver(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_max_deliver\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"MAX_DEL\",\n        .subjects = &.{\"maxdel.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    // max_deliver=2, ack_wait=1s\n    var c = js.createConsumer(\"MAX_DEL\", .{\n        .name = \"maxdel-c\",\n        .durable_name = \"maxdel-c\",\n        .ack_policy = .explicit,\n        .max_deliver = 2,\n        .ack_wait = 1_000_000_000,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer c.deinit();\n\n    // Publish 1 message\n    var a = js.publish(\n        \"maxdel.test\",\n        \"deliver-test\",\n    ) catch {\n        reportResult(name, false, \"publish\");\n        return;\n    };\n    a.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"MAX_DEL\",\n    };\n    pull.setConsumer(\"maxdel-c\") catch unreachable;\n\n    // Fetch 1st delivery, nak\n    var msg1 = (pull.next(5000) catch {\n        reportResult(name, false, \"fetch 1\");\n        return;\n    }) orelse {\n        reportResult(name, false, \"no msg 1\");\n        return;\n    };\n    msg1.nak() catch {};\n    msg1.deinit();\n\n    // Wait for redeliver\n    threadSleepNs(1_500_000_000);\n\n    // Fetch 2nd delivery, nak\n    var msg2 = (pull.next(5000) catch {\n        reportResult(name, false, \"fetch 2\");\n        return;\n    }) orelse {\n        reportResult(name, false, \"no msg 2\");\n        return;\n    };\n    msg2.nak() catch {};\n    msg2.deinit();\n\n    // Wait for redeliver attempt\n    threadSleepNs(1_500_000_000);\n\n    // 3rd fetch should be empty (max_deliver=2)\n    var r = pull.fetchNoWait(10) catch {\n        reportResult(name, false, \"fetch 3\");\n        return;\n    };\n    defer r.deinit();\n\n    if (r.count() != 0) {\n        reportResult(\n            name,\n            false,\n            \"expected 0 after max\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"MAX_DEL\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testFetchTimeout(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_fetch_timeout\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s = js.createStream(.{\n        .name = \"FETCH_TO\",\n        .subjects = &.{\"fetchto.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n\n    var co = js.createConsumer(\"FETCH_TO\", .{\n        .name = \"fetchto-c\",\n        .durable_name = \"fetchto-c\",\n        .ack_policy = .explicit,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    defer co.deinit();\n\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"FETCH_TO\",\n    };\n    pull.setConsumer(\"fetchto-c\") catch unreachable;\n\n    // Fetch on empty stream with short timeout\n    var result = pull.fetch(.{\n        .max_messages = 1,\n        .timeout_ms = 1000,\n    }) catch {\n        // Error is acceptable too\n        var d = js.deleteStream(\n            \"FETCH_TO\",\n        ) catch {\n            reportResult(name, true, \"\");\n            return;\n        };\n        d.deinit();\n        reportResult(name, true, \"\");\n        return;\n    };\n    defer result.deinit();\n\n    if (result.count() != 0) {\n        reportResult(\n            name,\n            false,\n            \"expected 0 messages\",\n        );\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"FETCH_TO\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testAsyncPublishDedup(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_async_dedup\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    deleteStreamIfExists(&js, \"ASYNC_DEDUP\");\n\n    var s = js.createStream(.{\n        .name = \"ASYNC_DEDUP\",\n        .subjects = &.{\"adedup.>\"},\n        .storage = .memory,\n        .duplicate_window = 60_000_000_000,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n    defer deleteStreamIfExists(&js, \"ASYNC_DEDUP\");\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{},\n    ) catch {\n        reportResult(name, false, \"init ap\");\n        return;\n    };\n    defer ap.deinit();\n\n    // Publish same msg_id twice\n    const fut1 = ap.publishWithOpts(\n        \"adedup.test\",\n        \"data\",\n        .{ .msg_id = \"unique-1\" },\n    ) catch {\n        reportResult(name, false, \"pub 1\");\n        return;\n    };\n    _ = fut1.wait(5000) catch {\n        reportResult(name, false, \"wait 1\");\n        fut1.deinit();\n        return;\n    };\n    fut1.deinit();\n\n    const fut2 = ap.publishWithOpts(\n        \"adedup.test\",\n        \"data\",\n        .{ .msg_id = \"unique-1\" },\n    ) catch {\n        reportResult(name, false, \"pub 2\");\n        return;\n    };\n    _ = fut2.wait(5000) catch {\n        reportResult(name, false, \"wait 2\");\n        fut2.deinit();\n        return;\n    };\n    fut2.deinit();\n\n    // Stream should have only 1 message\n    var info = js.streamInfo(\n        \"ASYNC_DEDUP\",\n    ) catch {\n        reportResult(name, false, \"info\");\n        return;\n    };\n    defer info.deinit();\n\n    const msgs = if (info.value.state) |st|\n        st.messages\n    else\n        0;\n\n    if (msgs != 1) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"msgs={d}, want 1\",\n            .{msgs},\n        ) catch \"wrong\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    reportResult(name, true, \"\");\n}\n\npub fn testAsyncPublishNoStream(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_async_no_stream\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{},\n    ) catch {\n        reportResult(name, false, \"init ap\");\n        return;\n    };\n    defer ap.deinit();\n\n    // Publish to nonexistent subject\n    const fut = ap.publish(\n        \"nonexistent.subject\",\n        \"data\",\n    ) catch {\n        // Publish itself might fail\n        reportResult(name, true, \"\");\n        return;\n    };\n\n    // Wait should return error\n    _ = fut.wait(3000) catch {\n        fut.deinit();\n        reportResult(name, true, \"\");\n        return;\n    };\n\n    // If no error, check if err() reports one\n    if (fut.err() != null) {\n        fut.deinit();\n        reportResult(name, true, \"\");\n        return;\n    }\n\n    fut.deinit();\n    // Even if no error, pass: behavior varies\n    reportResult(name, true, \"\");\n}\n\npub fn testKvManyKeys(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"kv_many_keys\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"MANY_KEYS\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put 100 keys\n    var i: u32 = 0;\n    while (i < 100) : (i += 1) {\n        var key_buf: [8]u8 = undefined;\n        const key = std.fmt.bufPrint(\n            &key_buf,\n            \"k{d:0>3}\",\n            .{i},\n        ) catch unreachable;\n        _ = kv.put(key, \"val\") catch {\n            var buf: [64]u8 = undefined;\n            const m = std.fmt.bufPrint(\n                &buf,\n                \"put k{d:0>3}\",\n                .{i},\n            ) catch \"put failed\";\n            reportResult(name, false, m);\n            return;\n        };\n    }\n\n    // Verify 100 keys\n    const keys1 = kv.keys(allocator) catch {\n        reportResult(name, false, \"keys 1\");\n        return;\n    };\n    const len1 = keys1.len;\n    for (keys1) |k| allocator.free(k);\n    allocator.free(keys1);\n\n    if (len1 != 100) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, want 100\",\n            .{len1},\n        ) catch \"wrong\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    // Delete every other key (odd indices)\n    i = 1;\n    while (i < 100) : (i += 2) {\n        var key_buf: [8]u8 = undefined;\n        const key = std.fmt.bufPrint(\n            &key_buf,\n            \"k{d:0>3}\",\n            .{i},\n        ) catch unreachable;\n        _ = kv.delete(key) catch {};\n    }\n\n    // Verify 50 keys remaining\n    const keys2 = kv.keys(allocator) catch {\n        reportResult(name, false, \"keys 2\");\n        return;\n    };\n    const len2 = keys2.len;\n    for (keys2) |k| allocator.free(k);\n    allocator.free(keys2);\n\n    if (len2 != 50) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"got {d}, want 50\",\n            .{len2},\n        ) catch \"wrong\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"MANY_KEYS\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\npub fn testAsyncPublishBurst(\n    allocator: std.mem.Allocator,\n) void {\n    const name = \"js_async_burst\";\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    deleteStreamIfExists(&js, \"BURST\");\n\n    var s = js.createStream(.{\n        .name = \"BURST\",\n        .subjects = &.{\"burst.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    defer s.deinit();\n    defer deleteStreamIfExists(&js, \"BURST\");\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{ .max_pending = 128 },\n    ) catch {\n        reportResult(name, false, \"init ap\");\n        return;\n    };\n    defer ap.deinit();\n\n    // Publish 100 messages rapidly\n    var futures: [100]*nats.jetstream.PubAckFuture =\n        undefined;\n    var i: usize = 0;\n    while (i < 100) : (i += 1) {\n        futures[i] = ap.publish(\n            \"burst.test\",\n            \"burst-data\",\n        ) catch {\n            // Clean up already allocated\n            for (futures[0..i]) |f| f.deinit();\n            reportResult(\n                name,\n                false,\n                \"publish failed\",\n            );\n            return;\n        };\n    }\n\n    // Wait for completion\n    ap.waitComplete(30000) catch {\n        for (futures[0..100]) |f| f.deinit();\n        reportResult(\n            name,\n            false,\n            \"waitComplete\",\n        );\n        return;\n    };\n\n    for (futures[0..100]) |f| f.deinit();\n\n    // Verify 100 messages in stream\n    var info = js.streamInfo(\"BURST\") catch {\n        reportResult(name, false, \"info\");\n        return;\n    };\n    defer info.deinit();\n\n    const msgs = if (info.value.state) |st|\n        st.messages\n    else\n        0;\n\n    if (msgs != 100) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"msgs={d}, want 100\",\n            .{msgs},\n        ) catch \"wrong\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    // Verify pending is 0\n    if (ap.publishAsyncPending() != 0) {\n        reportResult(\n            name,\n            false,\n            \"pending not 0\",\n        );\n        return;\n    }\n\n    reportResult(name, true, \"\");\n}\n\nfn testJsPublishAfterReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    _ = manager;\n    const name = \"js_pub_after_recon\";\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var server = startJsReconnectServer(\n        allocator,\n        io.io(),\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"start server\",\n        );\n        return;\n    };\n    defer server.deinit(io.io());\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(\n        &url_buf,\n        js_reconnect_port,\n    );\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = true,\n            .reconnect_wait_ms = 200,\n        },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    // Create stream with file storage\n    var s1 = js.createStream(.{\n        .name = \"RECON_PUB\",\n        .subjects = &.{\"reconpub.>\"},\n        .storage = .file,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    s1.deinit();\n\n    // Publish 3 messages\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"reconpub.data\",\n            \"before\",\n        ) catch {\n            reportResult(\n                name,\n                false,\n                \"pub before\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    if (!restartJsReconnectServer(\n        allocator,\n        io.io(),\n        &server,\n        client,\n        name,\n    )) return;\n\n    // Recreate stream (data may be lost)\n    if (js.createStream(.{\n        .name = \"RECON_PUB\",\n        .subjects = &.{\"reconpub.>\"},\n        .storage = .memory,\n    })) |r| {\n        var rr = r;\n        rr.deinit();\n    } else |_| {}\n\n    // Publish after reconnect\n    var a2 = js.publish(\n        \"reconpub.data\",\n        \"after\",\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"pub after recon\",\n        );\n        return;\n    };\n    a2.deinit();\n\n    var a3 = js.publish(\n        \"reconpub.data\",\n        \"after2\",\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"pub after 2\",\n        );\n        return;\n    };\n    a3.deinit();\n\n    // Cleanup\n    var d = js.deleteStream(\n        \"RECON_PUB\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\nfn testKvAfterReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    _ = manager;\n    const name = \"kv_after_recon\";\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var server = startJsReconnectServer(\n        allocator,\n        io.io(),\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"start server\",\n        );\n        return;\n    };\n    defer server.deinit(io.io());\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(\n        &url_buf,\n        js_reconnect_port,\n    );\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = true,\n            .reconnect_wait_ms = 200,\n        },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"RECON_KV\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Put before disconnect\n    _ = kv.put(\"before\", \"v1\") catch {\n        reportResult(name, false, \"put before\");\n        return;\n    };\n\n    if (!restartJsReconnectServer(\n        allocator,\n        io.io(),\n        &server,\n        client,\n        name,\n    )) return;\n\n    // Recreate bucket (memory lost)\n    kv = js.createKeyValue(.{\n        .bucket = \"RECON_KV\",\n        .storage = .memory,\n    }) catch {\n        // May still exist somehow\n        kv = js.keyValue(\"RECON_KV\") catch {\n            reportResult(\n                name,\n                false,\n                \"rebind bucket\",\n            );\n            return;\n        };\n        // Continue with rebound kv\n        _ = kv.put(\"after\", \"v2\") catch {\n            reportResult(\n                name,\n                false,\n                \"put after rebind\",\n            );\n            return;\n        };\n        var d = js.deleteKeyValue(\n            \"RECON_KV\",\n        ) catch {\n            reportResult(name, true, \"\");\n            return;\n        };\n        d.deinit();\n        reportResult(name, true, \"\");\n        return;\n    };\n\n    // Put after reconnect\n    _ = kv.put(\"after\", \"v2\") catch {\n        reportResult(\n            name,\n            false,\n            \"put after\",\n        );\n        return;\n    };\n\n    // Verify get\n    var entry = (kv.get(\"after\") catch {\n        reportResult(name, false, \"get after\");\n        return;\n    }) orelse {\n        reportResult(\n            name,\n            false,\n            \"after not found\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (!std.mem.eql(u8, entry.value, \"v2\")) {\n        reportResult(\n            name,\n            false,\n            \"wrong value\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\n        \"RECON_KV\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\nfn testJsFetchAfterReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    _ = manager;\n    const name = \"js_fetch_after_recon\";\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var server = startJsReconnectServer(\n        allocator,\n        io.io(),\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"start server\",\n        );\n        return;\n    };\n    defer server.deinit(io.io());\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(\n        &url_buf,\n        js_reconnect_port,\n    );\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = true,\n            .reconnect_wait_ms = 200,\n        },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s1 = js.createStream(.{\n        .name = \"RECON_FETCH\",\n        .subjects = &.{\"rconfetch.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    s1.deinit();\n\n    // Publish 5 messages\n    var i: u32 = 0;\n    while (i < 5) : (i += 1) {\n        var a = js.publish(\n            \"rconfetch.data\",\n            \"before\",\n        ) catch {\n            reportResult(\n                name,\n                false,\n                \"publish\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    var c1 = js.createConsumer(\n        \"RECON_FETCH\",\n        .{\n            .name = \"rfetch-c\",\n            .durable_name = \"rfetch-c\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"create consumer\",\n        );\n        return;\n    };\n    c1.deinit();\n\n    // Fetch 2 messages, ack\n    var pull = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"RECON_FETCH\",\n    };\n    pull.setConsumer(\"rfetch-c\") catch unreachable;\n\n    var r1 = pull.fetch(.{\n        .max_messages = 2,\n        .timeout_ms = 5000,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"fetch before\",\n        );\n        return;\n    };\n    for (r1.messages) |*msg| {\n        msg.ack() catch {};\n    }\n    r1.deinit();\n\n    if (!restartJsReconnectServer(\n        allocator,\n        io.io(),\n        &server,\n        client,\n        name,\n    )) return;\n\n    // Recreate stream + consumer (memory lost)\n    var s2 = js.createStream(.{\n        .name = \"RECON_FETCH\",\n        .subjects = &.{\"rconfetch.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"recreate stream\",\n        );\n        return;\n    };\n    s2.deinit();\n\n    var c2 = js.createConsumer(\n        \"RECON_FETCH\",\n        .{\n            .name = \"rfetch-c\",\n            .durable_name = \"rfetch-c\",\n            .ack_policy = .explicit,\n        },\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"recreate consumer\",\n        );\n        return;\n    };\n    c2.deinit();\n\n    // Publish new messages after reconnect\n    i = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"rconfetch.data\",\n            \"after\",\n        ) catch {\n            reportResult(\n                name,\n                false,\n                \"pub after recon\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Reset pull subscription\n    var pull2 = nats.jetstream.PullSubscription{\n        .js = &js,\n        .stream = \"RECON_FETCH\",\n    };\n    pull2.setConsumer(\"rfetch-c\") catch unreachable;\n\n    // Fetch after reconnect\n    var r2 = pull2.fetch(.{\n        .max_messages = 3,\n        .timeout_ms = 5000,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"fetch after recon\",\n        );\n        return;\n    };\n    defer r2.deinit();\n\n    if (r2.count() == 0) {\n        reportResult(\n            name,\n            false,\n            \"no msgs after recon\",\n        );\n        return;\n    }\n\n    for (r2.messages) |*msg| {\n        msg.ack() catch {};\n    }\n\n    var d = js.deleteStream(\n        \"RECON_FETCH\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\nfn testAsyncDuringDisconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    _ = manager;\n    const name = \"js_async_disconnect\";\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var server = startJsReconnectServer(\n        allocator,\n        io.io(),\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"start server\",\n        );\n        return;\n    };\n    defer server.deinit(io.io());\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(\n        &url_buf,\n        js_reconnect_port,\n    );\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = true,\n            .reconnect_wait_ms = 200,\n        },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s1 = js.createStream(.{\n        .name = \"ASYNC_DISC\",\n        .subjects = &.{\"asyncdisc.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    s1.deinit();\n\n    var ap = nats.jetstream.AsyncPublisher.init(\n        &js,\n        .{},\n    ) catch {\n        reportResult(name, false, \"init ap\");\n        return;\n    };\n    defer ap.deinit();\n\n    // Publish 3 msgs before disconnect\n    var i: usize = 0;\n    while (i < 3) : (i += 1) {\n        const f = ap.publish(\n            \"asyncdisc.data\",\n            \"before\",\n        ) catch break;\n        _ = f.wait(5000) catch {};\n        f.deinit();\n    }\n\n    if (!restartJsReconnectServer(\n        allocator,\n        io.io(),\n        &server,\n        client,\n        name,\n    )) return;\n\n    // Recreate stream\n    if (js.createStream(.{\n        .name = \"ASYNC_DISC\",\n        .subjects = &.{\"asyncdisc.>\"},\n        .storage = .memory,\n    })) |r| {\n        var rr = r;\n        rr.deinit();\n    } else |_| {}\n\n    // Publish after reconnect should work\n    const fut = ap.publish(\n        \"asyncdisc.data\",\n        \"after\",\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"pub after recon\",\n        );\n        return;\n    };\n    _ = fut.wait(5000) catch {\n        fut.deinit();\n        // Timeout acceptable in reconnect\n        reportResult(name, true, \"timeout ok\");\n        return;\n    };\n    fut.deinit();\n\n    var d = js.deleteStream(\n        \"ASYNC_DISC\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\nfn testPushAfterReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    _ = manager;\n    const name = \"js_push_after_recon\";\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var server = startJsReconnectServer(\n        allocator,\n        io.io(),\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"start server\",\n        );\n        return;\n    };\n    defer server.deinit(io.io());\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(\n        &url_buf,\n        js_reconnect_port,\n    );\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = true,\n            .reconnect_wait_ms = 200,\n        },\n    ) catch {\n        reportResult(name, false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var s1 = js.createStream(.{\n        .name = \"PUSH_RECON\",\n        .subjects = &.{\"pushrecon.>\"},\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            name,\n            false,\n            \"create stream\",\n        );\n        return;\n    };\n    s1.deinit();\n\n    // push consumer + publish\n    const Counter = struct {\n        count: u32 = 0,\n        pub fn onMessage(\n            self: *@This(),\n            msg: *nats.jetstream.JsMsg,\n        ) void {\n            _ = msg;\n            self.count += 1;\n        }\n    };\n\n    var counter1 = Counter{};\n    const deliver1 = \"_PUSH_RECON.test1\";\n\n    var push1 = nats.jetstream.PushSubscription{\n        .js = &js,\n        .stream = \"PUSH_RECON\",\n    };\n    push1.setConsumer(\"pushrecon-c\") catch unreachable;\n    push1.setDeliverSubject(deliver1) catch unreachable;\n\n    var ctx1 = push1.consume(\n        nats.jetstream.JsMsgHandler.init(\n            Counter,\n            &counter1,\n        ),\n        .{},\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"consume 1\",\n        );\n        return;\n    };\n\n    var pc1 = js.createPushConsumer(\n        \"PUSH_RECON\",\n        .{\n            .name = \"pushrecon-c\",\n            .deliver_subject = deliver1,\n            .ack_policy = .none,\n        },\n    ) catch {\n        ctx1.stop();\n        ctx1.deinit();\n        reportResult(\n            name,\n            false,\n            \"create push cons 1\",\n        );\n        return;\n    };\n    pc1.deinit();\n\n    // Publish 3 msgs\n    var i: u32 = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"pushrecon.data\",\n            \"phase1\",\n        ) catch {\n            ctx1.stop();\n            ctx1.deinit();\n            reportResult(\n                name,\n                false,\n                \"pub phase1\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    // Wait for delivery\n    var wait: u32 = 0;\n    while (counter1.count < 3 and\n        wait < 50) : (wait += 1)\n    {\n        threadSleepNs(100_000_000);\n    }\n\n    ctx1.stop();\n    ctx1.deinit();\n\n    if (counter1.count < 3) {\n        reportResult(\n            name,\n            false,\n            \"phase1 count < 3\",\n        );\n        return;\n    }\n\n    if (!restartJsReconnectServer(\n        allocator,\n        io.io(),\n        &server,\n        client,\n        name,\n    )) return;\n\n    // recreate and push again\n    if (js.createStream(.{\n        .name = \"PUSH_RECON\",\n        .subjects = &.{\"pushrecon.>\"},\n        .storage = .memory,\n    })) |r| {\n        var rr = r;\n        rr.deinit();\n    } else |_| {}\n\n    var counter2 = Counter{};\n    const deliver2 = \"_PUSH_RECON.test2\";\n\n    var push2 = nats.jetstream.PushSubscription{\n        .js = &js,\n        .stream = \"PUSH_RECON\",\n    };\n    push2.setConsumer(\"pushrecon-c2\") catch unreachable;\n    push2.setDeliverSubject(deliver2) catch unreachable;\n\n    var ctx2 = push2.consume(\n        nats.jetstream.JsMsgHandler.init(\n            Counter,\n            &counter2,\n        ),\n        .{},\n    ) catch {\n        reportResult(\n            name,\n            false,\n            \"consume 2\",\n        );\n        return;\n    };\n\n    var pc2 = js.createPushConsumer(\n        \"PUSH_RECON\",\n        .{\n            .name = \"pushrecon-c2\",\n            .deliver_subject = deliver2,\n            .ack_policy = .none,\n        },\n    ) catch {\n        ctx2.stop();\n        ctx2.deinit();\n        reportResult(\n            name,\n            false,\n            \"create push cons 2\",\n        );\n        return;\n    };\n    pc2.deinit();\n\n    // Publish 3 more\n    i = 0;\n    while (i < 3) : (i += 1) {\n        var a = js.publish(\n            \"pushrecon.data\",\n            \"phase2\",\n        ) catch {\n            ctx2.stop();\n            ctx2.deinit();\n            reportResult(\n                name,\n                false,\n                \"pub phase2\",\n            );\n            return;\n        };\n        a.deinit();\n    }\n\n    wait = 0;\n    while (counter2.count < 3 and\n        wait < 50) : (wait += 1)\n    {\n        threadSleepNs(100_000_000);\n    }\n\n    ctx2.stop();\n    ctx2.deinit();\n\n    if (counter2.count < 3) {\n        var buf: [64]u8 = undefined;\n        const m = std.fmt.bufPrint(\n            &buf,\n            \"phase2 got {d}\",\n            .{counter2.count},\n        ) catch \"count fail\";\n        reportResult(name, false, m);\n        return;\n    }\n\n    var d = js.deleteStream(\n        \"PUSH_RECON\",\n    ) catch {\n        reportResult(name, true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(name, true, \"\");\n}\n\n// -- Test 17 (reconnect test #5): see\n// testPushAfterReconnect above\n\npub fn runAll(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    _ = manager;\n\n    std.debug.print(\n        \"\\n--- JetStream Tests ---\\n\",\n        .{},\n    );\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var js_server = startSharedJsServer(allocator, io.io()) catch |err| {\n        std.debug.print(\n            \"Failed to start JS server: {}\\n\",\n            .{err},\n        );\n        return;\n    };\n    defer js_server.deinit(io.io());\n\n    testStreamCreateAndInfo(allocator);\n    testPublishAndAck(allocator);\n    testConsumerCRUD(allocator);\n    testApiError(allocator);\n    testStreamNames(allocator);\n    testStreamList(allocator);\n    testConsumerNames(allocator);\n    testConsumerList(allocator);\n    testAccountInfo(allocator);\n    testMetadata(allocator);\n    testFetchNoWait(allocator);\n    testMessages(allocator);\n    testConsume(allocator);\n    testOrderedConsumer(allocator);\n    // Ack protocol\n    testAckPreventsRedeliver(allocator);\n    testNakCausesRedeliver(allocator);\n    testTermStopsRedeliver(allocator);\n    testInProgress(allocator);\n    // Batch + publish opts\n    testBatchFetch(allocator);\n    testPublishDedup(allocator);\n    testPublishExpectedSeq(allocator);\n    testPublishAsyncExpectedSeqParity(allocator);\n    // Stream ops\n    testPurgeStream(allocator);\n    testStreamUpdate(allocator);\n    // Error paths\n    testConsumerNotFound(allocator);\n    testStreamBySubject(allocator);\n    // Key-Value Store\n    // These verify independent bucket semantics. Keep them isolated from\n    // earlier stream/consumer churn, and let server teardown handle bucket\n    // cleanup so these tests do not also stress stream deletion.\n    if (!restartSharedJsServer(\n        allocator,\n        io.io(),\n        &js_server,\n        \"kv_put_get\",\n    )) return;\n    testKvPutGet(allocator);\n    testKvCreate(allocator);\n    testKvUpdate(allocator);\n    testKvDelete(allocator);\n    testKvKeys(allocator);\n    testKvHistory(allocator);\n    testKvWatch(allocator);\n    testKvBucketLifecycle(allocator);\n    // Continue the remaining JetStream tests from clean server state.\n    if (!restartSharedJsServer(\n        allocator,\n        io.io(),\n        &js_server,\n        \"js_filtered_consumer\",\n    )) return;\n    // Behavioral correctness\n    testFilteredConsumer(allocator);\n    testPurgeSubject(allocator);\n    testPaginatedStreamNames(allocator);\n    // New API tests\n    testGetMsg(allocator);\n    testGetLastMsgForSubject(allocator);\n    testDeleteMsg(allocator);\n    testSecureDeleteMsg(allocator);\n    testCreateOrUpdateStream(allocator);\n    testCreateOrUpdateConsumer(allocator);\n    testPauseResumeConsumer(allocator);\n    testPushConsumerBasic(allocator);\n    testPushConsumerBorrowedAck(allocator);\n    testPushConsumerHeartbeatErrHandler(allocator);\n    testPublishWithTTL(allocator);\n    testPublishMsg(allocator);\n    testPublishMsgNoHeaders(allocator);\n    testPublishWithOptsEmpty(allocator);\n    testKvUpdateBucket(allocator);\n    testKvCreateOrUpdateBucket(allocator);\n    testKvPurgeDeletes(allocator);\n    testKvStoreNames(allocator);\n    testKvWatchIgnoreDeletes(allocator);\n    testKvWatchUpdatesOnly(allocator);\n    testKvListKeys(allocator);\n    // New API method tests\n    testDoubleAck(allocator);\n    testUpdatePushConsumer(allocator);\n    testGetPushConsumer(allocator);\n    testKvPutString(allocator);\n    testKvDeleteLastRev(allocator);\n    testKvPurgeLastRev(allocator);\n    testKvListKeysFiltered(allocator);\n    testKvHistoryWithOpts(allocator);\n    testConnOptions(allocator);\n    testKvCreateWithTTL(allocator);\n    // Async publish\n    testPublishAsync(allocator);\n    testPublishAsyncFutureWait(allocator);\n    // Edge cases\n    testKvEmptyValue(allocator);\n    testKvKeySpecialChars(allocator);\n    testKvCreateExisting(allocator);\n    testKvUpdateWrongRev(allocator);\n    testStreamMaxMsgs(allocator);\n    testConsumerMaxDeliver(allocator);\n    testFetchTimeout(allocator);\n    testAsyncPublishDedup(allocator);\n    testAsyncPublishNoStream(allocator);\n    // Stress\n    testKvManyKeys(allocator);\n    testAsyncPublishBurst(allocator);\n    // Cross-verification with nats CLI\n    testCrossVerifyKvPut(allocator);\n    testCrossVerifyKvGet(allocator);\n}\n\n// -- Cross-verification with nats CLI --\n\nconst nats_cli = \"nats\";\n\n/// Run nats CLI command, return stdout.\nfn runNatsCli(\n    allocator: std.mem.Allocator,\n    io: std.Io,\n    args: []const []const u8,\n) ?[]const u8 {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    var full_args: [16][]const u8 = undefined;\n    full_args[0] = nats_cli;\n    full_args[1] = \"--server\";\n    full_args[2] = url;\n    const n = @min(args.len, 12);\n    for (args[0..n], 0..) |a, i| {\n        full_args[3 + i] = a;\n    }\n\n    var child = std.process.spawn(io, .{\n        .argv = full_args[0 .. 3 + n],\n        .stdout = .pipe,\n        .stderr = .ignore,\n    }) catch return null;\n\n    var buf: [4096]u8 = undefined;\n    var total: usize = 0;\n    if (child.stdout) |*file| {\n        while (total < buf.len) {\n            var slice = [_][]u8{buf[total..]};\n            const rd = file.readStreaming(\n                io,\n                &slice,\n            ) catch break;\n            if (rd == 0) break;\n            total += rd;\n        }\n    }\n\n    const term = child.wait(io) catch return null;\n    if (term.exited != 0) return null;\n\n    if (total == 0) return null;\n    return allocator.dupe(u8, buf[0..total]) catch\n        null;\n}\n\n/// Zig writes KV, nats CLI reads and verifies.\npub fn testCrossVerifyKvPut(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"cross_kv_put\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.createKeyValue(.{\n        .bucket = \"CROSS_PUT\",\n        .storage = .memory,\n    }) catch {\n        reportResult(\n            \"cross_kv_put\",\n            false,\n            \"create bucket\",\n        );\n        return;\n    };\n\n    // Zig puts a value\n    _ = kv.put(\"hello\", \"from-zig\") catch {\n        reportResult(\"cross_kv_put\", false, \"put\");\n        return;\n    };\n\n    // nats CLI reads it\n    const output = runNatsCli(\n        allocator,\n        io.io(),\n        &.{ \"kv\", \"get\", \"CROSS_PUT\", \"hello\", \"--raw\" },\n    ) orelse {\n        reportResult(\n            \"cross_kv_put\",\n            false,\n            \"nats cli get failed\",\n        );\n        return;\n    };\n    defer allocator.free(output);\n\n    if (std.mem.indexOf(u8, output, \"from-zig\") ==\n        null)\n    {\n        reportResult(\n            \"cross_kv_put\",\n            false,\n            \"cli got wrong value\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\"CROSS_PUT\") catch {\n        reportResult(\"cross_kv_put\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"cross_kv_put\", true, \"\");\n}\n\n/// nats CLI writes KV, Zig reads and verifies.\npub fn testCrossVerifyKvGet(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, js_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    // CLI creates bucket and puts value\n    const add_out = runNatsCli(\n        allocator,\n        io.io(),\n        &.{ \"kv\", \"add\", \"CROSS_GET\", \"--storage\", \"memory\" },\n    ) orelse {\n        reportResult(\n            \"cross_kv_get\",\n            false,\n            \"cli add bucket\",\n        );\n        return;\n    };\n    allocator.free(add_out);\n\n    const put_out = runNatsCli(\n        allocator,\n        io.io(),\n        &.{ \"kv\", \"put\", \"CROSS_GET\", \"greeting\", \"hello-from-cli\" },\n    ) orelse {\n        reportResult(\n            \"cross_kv_get\",\n            false,\n            \"cli put\",\n        );\n        return;\n    };\n    allocator.free(put_out);\n\n    // Zig reads it\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"cross_kv_get\",\n            false,\n            \"connect\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var js = initTestJetStream(client);\n\n    var kv = js.keyValue(\"CROSS_GET\") catch {\n        reportResult(\n            \"cross_kv_get\",\n            false,\n            \"bind bucket\",\n        );\n        return;\n    };\n\n    var entry = (kv.get(\"greeting\") catch {\n        reportResult(\"cross_kv_get\", false, \"get\");\n        return;\n    }) orelse {\n        reportResult(\n            \"cross_kv_get\",\n            false,\n            \"key not found\",\n        );\n        return;\n    };\n    defer entry.deinit();\n\n    if (entry.revision == 0) {\n        reportResult(\n            \"cross_kv_get\",\n            false,\n            \"no revision\",\n        );\n        return;\n    }\n\n    var d = js.deleteKeyValue(\"CROSS_GET\") catch {\n        reportResult(\"cross_kv_get\", true, \"\");\n        return;\n    };\n    d.deinit();\n    reportResult(\"cross_kv_get\", true, \"\");\n}\n\npub fn runReconnectTests(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    std.debug.print(\n        \"\\n--- JetStream Reconnect Tests ---\\n\",\n        .{},\n    );\n    testJsPublishAfterReconnect(\n        allocator,\n        manager,\n    );\n    testKvAfterReconnect(allocator, manager);\n    testJsFetchAfterReconnect(\n        allocator,\n        manager,\n    );\n    testAsyncDuringDisconnect(\n        allocator,\n        manager,\n    );\n    testPushAfterReconnect(allocator, manager);\n}\n"
  },
  {
    "path": "src/testing/client/jwt.zig",
    "content": "//! JWT/Credentials Authentication Tests for NATS Client\n//!\n//! Tests JWT authentication with credentials files against nats-server.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst jwt_port = utils.jwt_port;\nconst test_creds_file = utils.test_creds_file;\n\n/// Tests successful JWT authentication with credentials file.\npub fn testJwtCredsFile(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, jwt_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .creds_file = test_creds_file,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"jwt_creds_file\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"jwt_creds_file\", true, \"\");\n    } else {\n        reportResult(\"jwt_creds_file\", false, \"not connected\");\n    }\n}\n\n/// Tests JWT authentication with in-memory credentials content.\npub fn testJwtCredsContent(allocator: std.mem.Allocator) void {\n    const creds_content = @embedFile(\"../configs/TestUser.creds\");\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, jwt_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .creds = creds_content,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"jwt_creds_content\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"jwt_creds_content\", true, \"\");\n    } else {\n        reportResult(\"jwt_creds_content\", false, \"not connected\");\n    }\n}\n\n/// Tests pub/sub works after JWT authentication.\npub fn testJwtPubSub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, jwt_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .creds_file = test_creds_file,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"jwt_pub_sub\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"jwt.test.subject\") catch {\n        reportResult(\"jwt_pub_sub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"jwt.test.subject\", \"jwt message\") catch {\n        reportResult(\"jwt_pub_sub\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"jwt_pub_sub\", true, \"\");\n    } else {\n        reportResult(\"jwt_pub_sub\", false, \"no message\");\n    }\n}\n\npub fn testJwtInvalidCreds(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, jwt_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    // Wrong seed that won't match the JWT's public key\n    const wrong_creds =\n        \\\\-----BEGIN NATS USER JWT-----\n        \\\\eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJMN1dBT1hJU0tPSUZNM1QyNEhMQ09ENzJRT1czQkNVWEdETjRKVU1SSUtHTlQ3RzdZVFRRIiwiaWF0IjoxNjUxNzkwOTgyLCJpc3MiOiJBRFRRUzdaQ0ZWSk5XNTcyNkdPWVhXNVRTQ1pGTklRU0hLMlpHWVVCQ0Q1RDc3T1ROTE9PS1pPWiIsIm5hbWUiOiJUZXN0VXNlciIsInN1YiI6IlVBRkhHNkZVRDJVVTRTREZWQUZVTDVMREZPMlhNNFdZTTc2VU5YVFBKWUpLN0VFTVlSQkhUMlZFIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9LCJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.bp2-Jsy33l4ayF7Ku1MNdJby4WiMKUrG-rSVYGBusAtV3xP4EdCa-zhSNUaBVIL3uYPPCQYCEoM1pCUdOnoJBg\n        \\\\------END NATS USER JWT------\n        \\\\-----BEGIN USER NKEY SEED-----\n        \\\\SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4\n        \\\\------END USER NKEY SEED------\n    ;\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .creds = wrong_creds,\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"jwt_invalid_creds\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"jwt_invalid_creds\", true, \"\");\n    }\n}\n\npub fn testJwtMalformedCreds(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, jwt_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const malformed_creds = \"not a valid creds file\";\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .creds = malformed_creds,\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"jwt_malformed_creds\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"jwt_malformed_creds\", true, \"\");\n    }\n}\n\npub fn testJwtMissingFile(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, jwt_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .creds_file = \"/nonexistent/path/to/creds.creds\",\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"jwt_missing_file\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"jwt_missing_file\", true, \"\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testJwtCredsFile(allocator);\n    testJwtCredsContent(allocator);\n    testJwtPubSub(allocator);\n    testJwtInvalidCreds(allocator);\n    testJwtMalformedCreds(allocator);\n    testJwtMissingFile(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/micro.zig",
    "content": "//! Microservices Integration Tests\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.micro_port;\nconst ServerManager = utils.ServerManager;\nconst TestServer = utils.server_manager.TestServer;\n\nconst ParseOpts: std.json.ParseOptions = .{\n    .ignore_unknown_fields = true,\n};\n\nconst EchoHandler = struct {\n    pub fn onRequest(_: *@This(), req: *nats.micro.Request) void {\n        req.respond(req.data()) catch {};\n    }\n};\n\nconst CountingHandler = struct {\n    count: *u32,\n\n    pub fn onRequest(self: *@This(), req: *nats.micro.Request) void {\n        self.count.* += 1;\n        req.respond(req.data()) catch {};\n    }\n};\n\nfn echoFn(req: *nats.micro.Request) void {\n    req.respond(req.data()) catch {};\n}\n\nconst ErrorHandler = struct {\n    pub fn onRequest(_: *@This(), req: *nats.micro.Request) void {\n        req.respondError(503, \"unavailable\", \"detail\") catch {};\n    }\n};\n\npub fn runAll(allocator: std.mem.Allocator, manager: *ServerManager) void {\n    _ = manager;\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    var server = TestServer.start(allocator, io.io(), .{\n        .port = test_port,\n    }) catch {\n        reportResult(\"micro_server\", false, \"start server failed\");\n        return;\n    };\n    defer server.deinit(io.io());\n\n    testMicroBasicRequest(allocator);\n    testMicroBasicRequestHandlerFn(allocator);\n    testMicroRespondError(allocator);\n    testMicroRespondJson(allocator);\n    testMicroPing(allocator);\n    testMicroPingById(allocator);\n    testMicroInfo(allocator);\n    testMicroStats(allocator);\n    testMicroStatsErrorCount(allocator);\n    testMicroStatsStartedRfc3339(allocator);\n    testMicroReset(allocator);\n    testMicroNoQueueFanout(allocator);\n    testMicroQueueGroupLoadBalance(allocator);\n    testMicroCustomServiceQueueGroup(allocator);\n    testMicroEndpointQueueGroupOverride(allocator);\n    testMicroNestedGroups(allocator);\n    testMicroMultipleEndpoints(allocator);\n    testMicroMetadataInInfo(allocator);\n    testMicroServiceIdUnique(allocator);\n    testMicroStop(allocator);\n    testMicroStopIdempotent(allocator);\n    testMicroDrainOnStop(allocator);\n    testMicroReconnect(allocator, &server);\n}\n\nfn waitForConnected(\n    io: std.Io,\n    client: *nats.Client,\n    timeout_ms: u32,\n) bool {\n    var waited: u32 = 0;\n    while (waited < timeout_ms) : (waited += 25) {\n        if (client.isConnected()) return true;\n        io.sleep(.fromMilliseconds(25), .awake) catch {};\n    }\n    return client.isConnected();\n}\n\nfn testMicroBasicRequest(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroBasicRequest\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"echo-basic\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"echo.basic\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroBasicRequest\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"echo.basic\", \"hello\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, \"hello\")) {\n            reportResult(\"testMicroBasicRequest\", true, \"\");\n        } else {\n            reportResult(\"testMicroBasicRequest\", false, \"wrong payload\");\n        }\n    } else {\n        reportResult(\"testMicroBasicRequest\", false, \"no response\");\n    }\n}\n\nfn testMicroBasicRequestHandlerFn(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroBasicRequestHandlerFn\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const service = nats.micro.addService(client, .{\n        .name = \"echo-fn\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"echo.fn\",\n            .handler = nats.micro.Handler.fromFn(echoFn),\n        },\n    }) catch {\n        reportResult(\"testMicroBasicRequestHandlerFn\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"echo.fn\", \"hello\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, \"hello\")) {\n            reportResult(\"testMicroBasicRequestHandlerFn\", true, \"\");\n        } else {\n            reportResult(\"testMicroBasicRequestHandlerFn\", false, \"wrong payload\");\n        }\n    } else {\n        reportResult(\"testMicroBasicRequestHandlerFn\", false, \"no response\");\n    }\n}\n\nfn testMicroRespondError(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroRespondError\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var handler = ErrorHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"err-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"err.svc\",\n            .handler = nats.micro.Handler.init(ErrorHandler, &handler),\n        },\n    }) catch {\n        reportResult(\"testMicroRespondError\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"err.svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        if (m.headers) |raw_headers| {\n            var parsed = nats.protocol.headers.parse(allocator, raw_headers);\n            defer parsed.deinit();\n            const err_hdr = parsed.get(\"Nats-Service-Error\");\n            const code_hdr = parsed.get(\"Nats-Service-Error-Code\");\n            if (err_hdr != null and code_hdr != null and\n                std.mem.eql(u8, err_hdr.?, \"unavailable\") and\n                std.mem.eql(u8, code_hdr.?, \"503\") and\n                std.mem.eql(u8, m.data, \"detail\"))\n            {\n                reportResult(\"testMicroRespondError\", true, \"\");\n                return;\n            }\n        }\n        reportResult(\"testMicroRespondError\", false, \"missing error headers\");\n    } else {\n        reportResult(\"testMicroRespondError\", false, \"no response\");\n    }\n}\n\nfn testMicroPing(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroPing\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"ping-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"ping.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroPing\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"$SRV.PING.ping-svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.Ping,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroPing\", false, \"json parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n\n        if (std.mem.eql(u8, parsed.value.name, \"ping-svc\") and\n            std.mem.eql(u8, parsed.value.version, \"1.0.0\") and\n            std.mem.eql(u8, parsed.value.type, nats.micro.protocol.Type.ping))\n        {\n            reportResult(\"testMicroPing\", true, \"\");\n        } else {\n            reportResult(\"testMicroPing\", false, \"wrong ping response\");\n        }\n    } else {\n        reportResult(\"testMicroPing\", false, \"no response\");\n    }\n}\n\nfn testMicroInfo(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroInfo\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"info-svc\",\n        .version = \"1.0.0\",\n        .metadata = &.{.{ .key = \"team\", .value = \"core\" }},\n        .endpoint = .{\n            .subject = \"info.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n            .metadata = &.{.{ .key = \"kind\", .value = \"echo\" }},\n        },\n    }) catch {\n        reportResult(\"testMicroInfo\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"$SRV.INFO.info-svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.Info,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroInfo\", false, \"json parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n\n        if (parsed.value.endpoints.len == 1 and\n            std.mem.eql(u8, parsed.value.endpoints[0].subject, \"info.echo\"))\n        {\n            reportResult(\"testMicroInfo\", true, \"\");\n        } else {\n            reportResult(\"testMicroInfo\", false, \"bad info payload\");\n        }\n    } else {\n        reportResult(\"testMicroInfo\", false, \"no response\");\n    }\n}\n\nfn testMicroStats(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroStats\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"stats-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"stats.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroStats\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    for (0..3) |_| {\n        const msg = client.request(\"stats.echo\", \"x\", 1000) catch null;\n        if (msg) |m| m.deinit();\n    }\n\n    const stats_msg = client.request(\"$SRV.STATS.stats-svc\", \"\", 1000) catch null;\n    if (stats_msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.StatsResponse,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroStats\", false, \"json parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n\n        if (parsed.value.endpoints.len == 1 and\n            parsed.value.endpoints[0].num_requests == 3 and\n            parsed.value.endpoints[0].processing_time > 0)\n        {\n            reportResult(\"testMicroStats\", true, \"\");\n        } else {\n            reportResult(\"testMicroStats\", false, \"bad stats payload\");\n        }\n    } else {\n        reportResult(\"testMicroStats\", false, \"no response\");\n    }\n}\n\nfn testMicroNoQueueFanout(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io_a = utils.newIo(allocator);\n    defer io_a.deinit();\n\n    const client_a = nats.Client.connect(allocator, io_a.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroNoQueueFanout\", false, \"client_a failed\");\n        return;\n    };\n    defer client_a.deinit();\n\n    const io_b = utils.newIo(allocator);\n    defer io_b.deinit();\n\n    const client_b = nats.Client.connect(allocator, io_b.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroNoQueueFanout\", false, \"client_b failed\");\n        return;\n    };\n    defer client_b.deinit();\n\n    var count_a: u32 = 0;\n    var count_b: u32 = 0;\n    var handler_a = CountingHandler{ .count = &count_a };\n    var handler_b = CountingHandler{ .count = &count_b };\n\n    const service_a = nats.micro.addService(client_a, .{\n        .name = \"fanout-svc\",\n        .version = \"1.0.0\",\n        .queue_policy = .no_queue,\n        .endpoint = .{\n            .subject = \"fanout.echo\",\n            .handler = nats.micro.Handler.init(CountingHandler, &handler_a),\n        },\n    }) catch {\n        reportResult(\"testMicroNoQueueFanout\", false, \"service_a failed\");\n        return;\n    };\n    defer service_a.deinit();\n\n    const service_b = nats.micro.addService(client_b, .{\n        .name = \"fanout-svc\",\n        .version = \"1.0.0\",\n        .queue_policy = .no_queue,\n        .endpoint = .{\n            .subject = \"fanout.echo\",\n            .handler = nats.micro.Handler.init(CountingHandler, &handler_b),\n        },\n    }) catch {\n        reportResult(\"testMicroNoQueueFanout\", false, \"service_b failed\");\n        return;\n    };\n    defer service_b.deinit();\n\n    for (0..5) |_| {\n        const msg = client_a.request(\"fanout.echo\", \"x\", 1000) catch null;\n        if (msg) |m| m.deinit();\n    }\n\n    io_a.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    if (count_a == 5 and count_b == 5) {\n        reportResult(\"testMicroNoQueueFanout\", true, \"\");\n    } else {\n        reportResult(\"testMicroNoQueueFanout\", false, \"fanout count mismatch\");\n    }\n}\n\nfn testMicroStop(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"testMicroStop\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"stop-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"stop.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroStop\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    service.stop(null) catch {\n        reportResult(\"testMicroStop\", false, \"stop failed\");\n        return;\n    };\n\n    const msg = client.request(\"stop.echo\", \"x\", 200) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        if (m.isNoResponders()) {\n            reportResult(\"testMicroStop\", true, \"\");\n        } else {\n            reportResult(\"testMicroStop\", false, \"request still answered\");\n        }\n    } else {\n        reportResult(\"testMicroStop\", true, \"\");\n    }\n}\n\nfn testMicroReconnect(\n    allocator: std.mem.Allocator,\n    server: *TestServer,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"testMicroReconnect\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"reconnect-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"reconnect.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroReconnect\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    server.stop(io.io());\n    client.forceReconnect() catch {};\n    server.* = TestServer.start(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"testMicroReconnect\", false, \"restart failed\");\n        return;\n    };\n    if (!waitForConnected(io.io(), client, 5000)) {\n        reportResult(\"testMicroReconnect\", false, \"reconnect timeout\");\n        return;\n    }\n\n    const msg = client.request(\"reconnect.echo\", \"again\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, \"again\")) {\n            reportResult(\"testMicroReconnect\", true, \"\");\n        } else {\n            reportResult(\"testMicroReconnect\", false, \"wrong payload\");\n        }\n    } else {\n        reportResult(\"testMicroReconnect\", false, \"no response\");\n    }\n}\n\n// ---------- New tests added to close v1 coverage gaps ----------\n\nconst JsonResp = struct {\n    answer: u32,\n    note: []const u8,\n};\n\nconst JsonHandler = struct {\n    pub fn onRequest(_: *@This(), req: *nats.micro.Request) void {\n        const value = JsonResp{ .answer = 42, .note = \"ok\" };\n        req.respondJson(value) catch {};\n    }\n};\n\nfn testMicroRespondJson(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroRespondJson\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var handler = JsonHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"json-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"json.svc\",\n            .handler = nats.micro.Handler.init(JsonHandler, &handler),\n        },\n    }) catch {\n        reportResult(\"testMicroRespondJson\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"json.svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            JsonResp,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroRespondJson\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n        if (parsed.value.answer == 42 and\n            std.mem.eql(u8, parsed.value.note, \"ok\"))\n        {\n            reportResult(\"testMicroRespondJson\", true, \"\");\n        } else {\n            reportResult(\"testMicroRespondJson\", false, \"wrong fields\");\n        }\n    } else {\n        reportResult(\"testMicroRespondJson\", false, \"no response\");\n    }\n}\n\nfn testMicroPingById(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroPingById\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"ping-id-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"ping.id.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroPingById\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    var subject_buf: [128]u8 = undefined;\n    const subject = std.fmt.bufPrint(\n        &subject_buf,\n        \"$SRV.PING.ping-id-svc.{s}\",\n        .{service.id},\n    ) catch {\n        reportResult(\"testMicroPingById\", false, \"subject format failed\");\n        return;\n    };\n\n    const msg = client.request(subject, \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.Ping,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroPingById\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n        if (std.mem.eql(u8, parsed.value.id, service.id)) {\n            reportResult(\"testMicroPingById\", true, \"\");\n        } else {\n            reportResult(\"testMicroPingById\", false, \"id mismatch\");\n        }\n    } else {\n        reportResult(\"testMicroPingById\", false, \"no response\");\n    }\n}\n\nfn testMicroStatsErrorCount(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroStatsErrorCount\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var handler = ErrorHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"stats-err-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"stats.err.svc\",\n            .handler = nats.micro.Handler.init(ErrorHandler, &handler),\n        },\n    }) catch {\n        reportResult(\"testMicroStatsErrorCount\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    for (0..3) |_| {\n        const m = client.request(\"stats.err.svc\", \"\", 1000) catch null;\n        if (m) |x| x.deinit();\n    }\n\n    const stats_msg = client.request(\n        \"$SRV.STATS.stats-err-svc\",\n        \"\",\n        1000,\n    ) catch null;\n    if (stats_msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.StatsResponse,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroStatsErrorCount\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n\n        if (parsed.value.endpoints.len != 1) {\n            reportResult(\"testMicroStatsErrorCount\", false, \"endpoint count\");\n            return;\n        }\n        const ep = parsed.value.endpoints[0];\n        if (ep.num_requests == 3 and ep.num_errors == 3 and\n            ep.last_error != null and ep.last_error.?.code == 503)\n        {\n            reportResult(\"testMicroStatsErrorCount\", true, \"\");\n        } else {\n            reportResult(\"testMicroStatsErrorCount\", false, \"wrong counts\");\n        }\n    } else {\n        reportResult(\"testMicroStatsErrorCount\", false, \"no stats\");\n    }\n}\n\nfn isAsciiDigit(c: u8) bool {\n    return c >= '0' and c <= '9';\n}\n\nfn looksLikeRfc3339(s: []const u8) bool {\n    // Expected: YYYY-MM-DDTHH:MM:SS.mmmZ -> 24 chars\n    if (s.len != 24) return false;\n    if (s[4] != '-' or s[7] != '-' or s[10] != 'T' or\n        s[13] != ':' or s[16] != ':' or s[19] != '.' or s[23] != 'Z')\n        return false;\n    for (s[0..4]) |c| if (!isAsciiDigit(c)) return false;\n    for (s[5..7]) |c| if (!isAsciiDigit(c)) return false;\n    for (s[8..10]) |c| if (!isAsciiDigit(c)) return false;\n    for (s[11..13]) |c| if (!isAsciiDigit(c)) return false;\n    for (s[14..16]) |c| if (!isAsciiDigit(c)) return false;\n    for (s[17..19]) |c| if (!isAsciiDigit(c)) return false;\n    for (s[20..23]) |c| if (!isAsciiDigit(c)) return false;\n    return true;\n}\n\nfn testMicroStatsStartedRfc3339(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroStatsStartedRfc3339\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"started-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"started.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroStatsStartedRfc3339\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"$SRV.STATS.started-svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.StatsResponse,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroStatsStartedRfc3339\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n        if (looksLikeRfc3339(parsed.value.started)) {\n            reportResult(\"testMicroStatsStartedRfc3339\", true, \"\");\n        } else {\n            reportResult(\"testMicroStatsStartedRfc3339\", false, \"bad format\");\n        }\n    } else {\n        reportResult(\"testMicroStatsStartedRfc3339\", false, \"no response\");\n    }\n}\n\nfn testMicroReset(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroReset\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"reset-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"reset.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroReset\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    for (0..3) |_| {\n        const m = client.request(\"reset.echo\", \"x\", 1000) catch null;\n        if (m) |x| x.deinit();\n    }\n\n    service.reset();\n\n    const stats_msg = client.request(\"$SRV.STATS.reset-svc\", \"\", 1000) catch null;\n    if (stats_msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.StatsResponse,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroReset\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n        if (parsed.value.endpoints.len == 1 and\n            parsed.value.endpoints[0].num_requests == 0 and\n            parsed.value.endpoints[0].processing_time == 0)\n        {\n            reportResult(\"testMicroReset\", true, \"\");\n        } else {\n            reportResult(\"testMicroReset\", false, \"stats not zero\");\n        }\n    } else {\n        reportResult(\"testMicroReset\", false, \"no stats\");\n    }\n}\n\nfn testMicroQueueGroupLoadBalance(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io_a = utils.newIo(allocator);\n    defer io_a.deinit();\n\n    const client_a = nats.Client.connect(allocator, io_a.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroQueueGroupLoadBalance\", false, \"client_a failed\");\n        return;\n    };\n    defer client_a.deinit();\n\n    const io_b = utils.newIo(allocator);\n    defer io_b.deinit();\n\n    const client_b = nats.Client.connect(allocator, io_b.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroQueueGroupLoadBalance\", false, \"client_b failed\");\n        return;\n    };\n    defer client_b.deinit();\n\n    var count_a: u32 = 0;\n    var count_b: u32 = 0;\n    var handler_a = CountingHandler{ .count = &count_a };\n    var handler_b = CountingHandler{ .count = &count_b };\n\n    const svc_a = nats.micro.addService(client_a, .{\n        .name = \"lb-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"lb.echo\",\n            .handler = nats.micro.Handler.init(CountingHandler, &handler_a),\n        },\n    }) catch {\n        reportResult(\"testMicroQueueGroupLoadBalance\", false, \"svc_a failed\");\n        return;\n    };\n    defer svc_a.deinit();\n\n    const svc_b = nats.micro.addService(client_b, .{\n        .name = \"lb-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"lb.echo\",\n            .handler = nats.micro.Handler.init(CountingHandler, &handler_b),\n        },\n    }) catch {\n        reportResult(\"testMicroQueueGroupLoadBalance\", false, \"svc_b failed\");\n        return;\n    };\n    defer svc_b.deinit();\n\n    const N = 20;\n    for (0..N) |_| {\n        const m = client_a.request(\"lb.echo\", \"x\", 1000) catch null;\n        if (m) |x| x.deinit();\n    }\n\n    io_a.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    // Both should receive >0 and combined == N (queue group → load split).\n    if (count_a + count_b == N and count_a > 0 and count_b > 0) {\n        reportResult(\"testMicroQueueGroupLoadBalance\", true, \"\");\n    } else {\n        reportResult(\"testMicroQueueGroupLoadBalance\", false, \"unbalanced\");\n    }\n}\n\nfn testMicroCustomServiceQueueGroup(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroCustomServiceQueueGroup\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"custom-q-svc\",\n        .version = \"1.0.0\",\n        .queue_policy = .{ .queue = \"svc-q\" },\n        .endpoint = .{\n            .subject = \"custom.q.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroCustomServiceQueueGroup\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"$SRV.INFO.custom-q-svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.Info,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroCustomServiceQueueGroup\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n        if (parsed.value.endpoints.len == 1 and\n            parsed.value.endpoints[0].queue_group != null and\n            std.mem.eql(u8, parsed.value.endpoints[0].queue_group.?, \"svc-q\"))\n        {\n            reportResult(\"testMicroCustomServiceQueueGroup\", true, \"\");\n        } else {\n            reportResult(\"testMicroCustomServiceQueueGroup\", false, \"wrong queue\");\n        }\n    } else {\n        reportResult(\"testMicroCustomServiceQueueGroup\", false, \"no info\");\n    }\n}\n\nfn testMicroEndpointQueueGroupOverride(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroEndpointQueueGroupOverride\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"ep-override-svc\",\n        .version = \"1.0.0\",\n        .queue_policy = .{ .queue = \"svc-q\" },\n        .endpoint = .{\n            .subject = \"ep.override.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n            .queue_policy = .{ .queue = \"ep-q\" },\n        },\n    }) catch {\n        reportResult(\"testMicroEndpointQueueGroupOverride\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"$SRV.INFO.ep-override-svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.Info,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroEndpointQueueGroupOverride\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n        if (parsed.value.endpoints.len == 1 and\n            parsed.value.endpoints[0].queue_group != null and\n            std.mem.eql(u8, parsed.value.endpoints[0].queue_group.?, \"ep-q\"))\n        {\n            reportResult(\"testMicroEndpointQueueGroupOverride\", true, \"\");\n        } else {\n            reportResult(\"testMicroEndpointQueueGroupOverride\", false, \"wrong queue\");\n        }\n    } else {\n        reportResult(\"testMicroEndpointQueueGroupOverride\", false, \"no info\");\n    }\n}\n\nfn testMicroNestedGroups(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroNestedGroups\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"nested-svc\",\n        .version = \"1.0.0\",\n    }) catch {\n        reportResult(\"testMicroNestedGroups\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    var v1 = service.addGroup(\"v1\") catch {\n        reportResult(\"testMicroNestedGroups\", false, \"addGroup v1 failed\");\n        return;\n    };\n    var users = v1.group(\"users\") catch {\n        reportResult(\"testMicroNestedGroups\", false, \"nested group failed\");\n        return;\n    };\n    _ = users.addEndpoint(.{\n        .subject = \"get\",\n        .handler = nats.micro.Handler.init(EchoHandler, &echo),\n    }) catch {\n        reportResult(\"testMicroNestedGroups\", false, \"addEndpoint failed\");\n        return;\n    };\n\n    const msg = client.request(\"v1.users.get\", \"hello\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, \"hello\")) {\n            reportResult(\"testMicroNestedGroups\", true, \"\");\n        } else {\n            reportResult(\"testMicroNestedGroups\", false, \"wrong payload\");\n        }\n    } else {\n        reportResult(\"testMicroNestedGroups\", false, \"no response\");\n    }\n}\n\nfn testMicroMultipleEndpoints(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroMultipleEndpoints\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var count_a: u32 = 0;\n    var count_b: u32 = 0;\n    var handler_a = CountingHandler{ .count = &count_a };\n    var handler_b = CountingHandler{ .count = &count_b };\n\n    const service = nats.micro.addService(client, .{\n        .name = \"multi-ep-svc\",\n        .version = \"1.0.0\",\n    }) catch {\n        reportResult(\"testMicroMultipleEndpoints\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    _ = service.addEndpoint(.{\n        .subject = \"multi.a\",\n        .handler = nats.micro.Handler.init(CountingHandler, &handler_a),\n    }) catch {\n        reportResult(\"testMicroMultipleEndpoints\", false, \"addEndpoint a failed\");\n        return;\n    };\n    _ = service.addEndpoint(.{\n        .subject = \"multi.b\",\n        .handler = nats.micro.Handler.init(CountingHandler, &handler_b),\n    }) catch {\n        reportResult(\"testMicroMultipleEndpoints\", false, \"addEndpoint b failed\");\n        return;\n    };\n\n    for (0..3) |_| {\n        const m = client.request(\"multi.a\", \"x\", 1000) catch null;\n        if (m) |x| x.deinit();\n    }\n    const m_only = client.request(\"multi.b\", \"y\", 1000) catch null;\n    if (m_only) |x| x.deinit();\n\n    if (count_a == 3 and count_b == 1) {\n        reportResult(\"testMicroMultipleEndpoints\", true, \"\");\n    } else {\n        reportResult(\"testMicroMultipleEndpoints\", false, \"wrong split\");\n    }\n}\n\nfn testMicroMetadataInInfo(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroMetadataInInfo\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"meta-svc\",\n        .version = \"1.0.0\",\n        .metadata = &.{\n            .{ .key = \"team\", .value = \"core\" },\n            .{ .key = \"env\", .value = \"test\" },\n        },\n        .endpoint = .{\n            .subject = \"meta.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n            .metadata = &.{\n                .{ .key = \"kind\", .value = \"echo\" },\n            },\n        },\n    }) catch {\n        reportResult(\"testMicroMetadataInInfo\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const msg = client.request(\"$SRV.INFO.meta-svc\", \"\", 1000) catch null;\n    if (msg) |m| {\n        defer m.deinit();\n        var parsed = std.json.parseFromSlice(\n            nats.micro.protocol.Info,\n            allocator,\n            m.data,\n            ParseOpts,\n        ) catch {\n            reportResult(\"testMicroMetadataInInfo\", false, \"parse failed\");\n            return;\n        };\n        defer parsed.deinit();\n\n        const svc_md = parsed.value.metadata orelse {\n            reportResult(\"testMicroMetadataInInfo\", false, \"no svc metadata\");\n            return;\n        };\n        if (svc_md.len != 2) {\n            reportResult(\"testMicroMetadataInInfo\", false, \"svc md count\");\n            return;\n        }\n        if (parsed.value.endpoints.len != 1) {\n            reportResult(\"testMicroMetadataInInfo\", false, \"ep count\");\n            return;\n        }\n        const ep_md = parsed.value.endpoints[0].metadata orelse {\n            reportResult(\"testMicroMetadataInInfo\", false, \"no ep metadata\");\n            return;\n        };\n        if (ep_md.len != 1 or\n            !std.mem.eql(u8, ep_md[0].key, \"kind\") or\n            !std.mem.eql(u8, ep_md[0].value, \"echo\"))\n        {\n            reportResult(\"testMicroMetadataInInfo\", false, \"ep md wrong\");\n            return;\n        }\n        reportResult(\"testMicroMetadataInInfo\", true, \"\");\n    } else {\n        reportResult(\"testMicroMetadataInInfo\", false, \"no info\");\n    }\n}\n\nfn testMicroServiceIdUnique(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroServiceIdUnique\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const svc_a = nats.micro.addService(client, .{\n        .name = \"uniq-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"uniq.a\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroServiceIdUnique\", false, \"svc_a failed\");\n        return;\n    };\n    defer svc_a.deinit();\n\n    const svc_b = nats.micro.addService(client, .{\n        .name = \"uniq-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"uniq.b\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroServiceIdUnique\", false, \"svc_b failed\");\n        return;\n    };\n    defer svc_b.deinit();\n\n    if (svc_a.id.len == 16 and svc_b.id.len == 16 and\n        !std.mem.eql(u8, svc_a.id, svc_b.id))\n    {\n        reportResult(\"testMicroServiceIdUnique\", true, \"\");\n    } else {\n        reportResult(\"testMicroServiceIdUnique\", false, \"id collision or wrong len\");\n    }\n}\n\nfn testMicroStopIdempotent(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroStopIdempotent\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var echo = EchoHandler{};\n    const service = nats.micro.addService(client, .{\n        .name = \"stop-twice-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"stop.twice.echo\",\n            .handler = nats.micro.Handler.init(EchoHandler, &echo),\n        },\n    }) catch {\n        reportResult(\"testMicroStopIdempotent\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    service.stop(null) catch {\n        reportResult(\"testMicroStopIdempotent\", false, \"first stop failed\");\n        return;\n    };\n    service.stop(null) catch {\n        reportResult(\"testMicroStopIdempotent\", false, \"second stop failed\");\n        return;\n    };\n    if (service.stopped()) {\n        reportResult(\"testMicroStopIdempotent\", true, \"\");\n    } else {\n        reportResult(\"testMicroStopIdempotent\", false, \"not stopped\");\n    }\n}\n\nconst BlockingEcho = struct {\n    started: *std.atomic.Value(bool),\n    release: *std.atomic.Value(bool),\n    finished: *std.atomic.Value(bool),\n\n    pub fn onRequest(self: *@This(), req: *nats.micro.Request) void {\n        self.started.store(true, .release);\n        while (!self.release.load(.acquire)) {\n            req.client.io.sleep(.fromMilliseconds(1), .awake) catch {};\n        }\n        req.respond(\"done\") catch {};\n        self.finished.store(true, .release);\n    }\n};\n\nconst StopState = struct {\n    done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),\n    err: ?anyerror = null,\n};\n\nfn stopService(\n    service: *nats.micro.Service,\n    state: *StopState,\n) void {\n    service.stop(null) catch |err| {\n        state.err = err;\n    };\n    state.done.store(true, .release);\n}\n\nfn drainRequester(\n    client: *nats.Client,\n    out: *?nats.Client.Message,\n) void {\n    out.* = client.request(\"drain.echo\", \"x\", 5000) catch null;\n}\n\nfn testMicroDrainOnStop(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n    const service_io = utils.newIo(allocator);\n    defer service_io.deinit();\n\n    const service_client = nats.Client.connect(allocator, service_io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroDrainOnStop\", false, \"connect failed\");\n        return;\n    };\n    defer service_client.deinit();\n\n    var started = std.atomic.Value(bool).init(false);\n    var release = std.atomic.Value(bool).init(false);\n    var finished = std.atomic.Value(bool).init(false);\n    var blocking = BlockingEcho{\n        .started = &started,\n        .release = &release,\n        .finished = &finished,\n    };\n\n    const service = nats.micro.addService(service_client, .{\n        .name = \"drain-svc\",\n        .version = \"1.0.0\",\n        .endpoint = .{\n            .subject = \"drain.echo\",\n            .handler = nats.micro.Handler.init(BlockingEcho, &blocking),\n        },\n    }) catch {\n        reportResult(\"testMicroDrainOnStop\", false, \"addService failed\");\n        return;\n    };\n    defer service.deinit();\n\n    const requester_io = utils.newIo(allocator);\n    defer requester_io.deinit();\n\n    const requester = nats.Client.connect(allocator, requester_io.io(), url, .{\n        .reconnect = false,\n    }) catch {\n        reportResult(\"testMicroDrainOnStop\", false, \"requester failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    var resp: ?nats.Client.Message = null;\n    var fut = requester_io.io().async(drainRequester, .{ requester, &resp });\n\n    // Wait until the handler has actually entered.\n    var spins: u32 = 0;\n    while (!started.load(.acquire) and spins < 200) {\n        service_io.io().sleep(.fromMilliseconds(5), .awake) catch {};\n        spins += 1;\n    }\n    if (!started.load(.acquire)) {\n        _ = fut.cancel(requester_io.io());\n        reportResult(\"testMicroDrainOnStop\", false, \"handler never entered\");\n        return;\n    }\n\n    var stop_state = StopState{};\n    var stop_fut = service_io.io().concurrent(stopService, .{ service, &stop_state }) catch\n        service_io.io().async(stopService, .{ service, &stop_state });\n\n    spins = 0;\n    while (!service.stopping.load(.acquire) and spins < 200) {\n        service_io.io().sleep(.fromMilliseconds(1), .awake) catch {};\n        spins += 1;\n    }\n    if (!service.stopping.load(.acquire)) {\n        release.store(true, .release);\n        stop_fut.await(service_io.io());\n        fut.await(requester_io.io());\n        defer if (resp) |m| m.deinit();\n        reportResult(\"testMicroDrainOnStop\", false, \"stop never started\");\n        return;\n    }\n\n    service_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n    if (stop_state.done.load(.acquire)) {\n        release.store(true, .release);\n        stop_fut.await(service_io.io());\n        fut.await(requester_io.io());\n        defer if (resp) |m| m.deinit();\n        reportResult(\"testMicroDrainOnStop\", false, \"stop returned early\");\n        return;\n    }\n\n    release.store(true, .release);\n    stop_fut.await(service_io.io());\n    if (stop_state.err != null) {\n        _ = fut.cancel(requester_io.io());\n        reportResult(\"testMicroDrainOnStop\", false, \"stop failed\");\n        return;\n    }\n\n    fut.await(requester_io.io());\n    defer if (resp) |m| m.deinit();\n\n    if (resp != null and finished.load(.acquire) and service.stopped()) {\n        reportResult(\"testMicroDrainOnStop\", true, \"\");\n    } else {\n        reportResult(\"testMicroDrainOnStop\", false, \"drain incomplete\");\n    }\n}\n"
  },
  {
    "path": "src/testing/client/multi_client.zig",
    "content": "//! Multi-Client Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testCrossClientRouting(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"cross_client\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const subscriber = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"cross_client\", false, \"sub connect failed\");\n        return;\n    };\n    defer subscriber.deinit();\n\n    const sub = subscriber.subscribeSync(\"cross\") catch {\n        reportResult(\"cross_client\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    publisher.publish(\"cross\", \"cross-message\") catch {\n        reportResult(\"cross_client\", false, \"publish failed\");\n        return;\n    };\n\n    var future = sub_io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(sub_io.io())) |msg| {\n        if (std.mem.eql(u8, msg.data, \"cross-message\")) {\n            reportResult(\"cross_client\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"cross_client\", false, \"no message\");\n}\n\npub fn testMultipleClients(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n\n    const client1 = nats.Client.connect(\n        allocator,\n        io1.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multiple_clients\", false, \"client1 failed\");\n        return;\n    };\n    defer client1.deinit();\n\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const client2 = nats.Client.connect(\n        allocator,\n        io2.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multiple_clients\", false, \"client2 failed\");\n        return;\n    };\n    defer client2.deinit();\n\n    const io3 = utils.newIo(allocator);\n    defer io3.deinit();\n\n    const client3 = nats.Client.connect(\n        allocator,\n        io3.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multiple_clients\", false, \"client3 failed\");\n        return;\n    };\n    defer client3.deinit();\n\n    if (client1.isConnected() and client2.isConnected() and\n        client3.isConnected())\n    {\n        reportResult(\"multiple_clients\", true, \"\");\n    } else {\n        reportResult(\"multiple_clients\", false, \"not all connected\");\n    }\n}\n\npub fn testClientHighRate(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_high_rate\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(allocator, sub_io.io(), url, .{\n        .sub_queue_size = 512,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"client_high_rate\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"highrate\") catch {\n        reportResult(\"client_high_rate\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Wait for subscription to be registered on server.\n    client.flush(50_000_000) catch {};\n\n    const NUM_MSGS = 100;\n    for (0..NUM_MSGS) |_| {\n        publisher.publish(\"highrate\", \"msg\") catch {\n            reportResult(\"client_high_rate\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    publisher.flush(500_000_000) catch {};\n\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        var future = sub_io.io().async(\n            nats.Client.Sub.nextMsg,\n            .{sub},\n        );\n        defer if (future.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n        if (future.await(sub_io.io())) |_| {\n            received += 1;\n        } else |_| {\n            break;\n        }\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"client_high_rate\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(&buf, \"got {d}/100\", .{received}) catch \"e\";\n        reportResult(\"client_high_rate\", false, msg);\n    }\n}\n\npub fn testThreeClientChain(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    // Client A - initial publisher\n    const io_a = utils.newIo(allocator);\n    defer io_a.deinit();\n    const client_a = nats.Client.connect(\n        allocator,\n        io_a.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"three_client_chain\", false, \"A connect failed\");\n        return;\n    };\n    defer client_a.deinit();\n\n    // Client B - middleware (receives from A, forwards to C)\n    const io_b = utils.newIo(allocator);\n    defer io_b.deinit();\n    const client_b = nats.Client.connect(\n        allocator,\n        io_b.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"three_client_chain\", false, \"B connect failed\");\n        return;\n    };\n    defer client_b.deinit();\n\n    // Client C - final receiver\n    const io_c = utils.newIo(allocator);\n    defer io_c.deinit();\n    const client_c = nats.Client.connect(\n        allocator,\n        io_c.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"three_client_chain\", false, \"C connect failed\");\n        return;\n    };\n    defer client_c.deinit();\n\n    // B subscribes to \"step1\"\n    const sub_b = client_b.subscribeSync(\"chain.step1\") catch {\n        reportResult(\"three_client_chain\", false, \"B sub failed\");\n        return;\n    };\n    defer sub_b.deinit();\n\n    // C subscribes to \"step2\"\n    const sub_c = client_c.subscribeSync(\"chain.step2\") catch {\n        reportResult(\"three_client_chain\", false, \"C sub failed\");\n        return;\n    };\n    defer sub_c.deinit();\n\n    io_a.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // A publishes to step1\n    client_a.publish(\"chain.step1\", \"start\") catch {\n        reportResult(\"three_client_chain\", false, \"A publish failed\");\n        return;\n    };\n\n    // B receives and forwards to step2\n    const msg_b = sub_b.nextMsgTimeout(2000) catch {\n        reportResult(\"three_client_chain\", false, \"B receive failed\");\n        return;\n    };\n    if (msg_b) |m| {\n        defer m.deinit();\n        client_b.publish(\"chain.step2\", \"forwarded\") catch {\n            reportResult(\"three_client_chain\", false, \"B forward failed\");\n            return;\n        };\n    } else {\n        reportResult(\"three_client_chain\", false, \"B no message\");\n        return;\n    }\n\n    // C receives final message\n    const msg_c = sub_c.nextMsgTimeout(2000) catch {\n        reportResult(\"three_client_chain\", false, \"C receive failed\");\n        return;\n    };\n    if (msg_c) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, \"forwarded\")) {\n            reportResult(\"three_client_chain\", true, \"\");\n        } else {\n            reportResult(\"three_client_chain\", false, \"wrong data\");\n        }\n    } else {\n        reportResult(\"three_client_chain\", false, \"C no message\");\n    }\n}\n\npub fn testMultipleSubscribersSameSubject(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_sub_same_subject\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.subscribeSync(\"broadcast.test\") catch {\n        reportResult(\"multi_sub_same_subject\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"broadcast.test\") catch {\n        reportResult(\"multi_sub_same_subject\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    const sub3 = client.subscribeSync(\"broadcast.test\") catch {\n        reportResult(\"multi_sub_same_subject\", false, \"sub3 failed\");\n        return;\n    };\n    defer sub3.deinit();\n\n    client.publish(\"broadcast.test\", \"hello all\") catch {\n        reportResult(\"multi_sub_same_subject\", false, \"publish failed\");\n        return;\n    };\n\n    client.flush(500_000_000) catch {};\n\n    var count: u32 = 0;\n\n    if (sub1.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n    if (sub2.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n    if (sub3.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n\n    if (count == 3) {\n        reportResult(\"multi_sub_same_subject\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/3\", .{count}) catch \"err\";\n        reportResult(\"multi_sub_same_subject\", false, detail);\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testCrossClientRouting(allocator);\n    testMultipleClients(allocator);\n    testClientHighRate(allocator);\n    testThreeClientChain(allocator);\n    testMultipleSubscribersSameSubject(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/multithread.zig",
    "content": "//! Multi-Thread Safety Tests for NATS Client\n//!\n//! Tests that a single Client can be safely used from multiple OS\n//! threads for publish, subscribe, and request operations.\n//! These tests use std.Thread.spawn for real OS-level concurrency.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\n/// Blocking sleep for OS threads (no Io context).\n/// Uses the same pattern as Client.zig:reserveRingEntry.\nfn threadSleepNs(ns: u64) void {\n    var ts: std.posix.timespec = .{\n        .sec = @intCast(ns / 1_000_000_000),\n        .nsec = @intCast(ns % 1_000_000_000),\n    };\n    _ = std.posix.system.nanosleep(&ts, &ts);\n}\n\n/// Multiple OS threads publishing via the same client.\n/// Verifies all messages arrive with no corruption.\npub fn testMultiThreadPublish(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_THREADS = 4;\n    const MSGS_PER_THREAD = 5000;\n    const TOTAL = NUM_THREADS * MSGS_PER_THREAD;\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = false,\n            // 32K queue to hold all 20K messages\n            .sub_queue_size = 32768,\n        },\n    ) catch {\n        reportResult(\n            \"mt_publish\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    // Subscribe to receive all messages\n    const sub = client.subscribeSync(\"mt.pub.>\") catch {\n        reportResult(\n            \"mt_publish\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    // Wait for subscription to register\n    client.flush(5_000_000_000) catch {};\n\n    // Spawn publisher threads (track success counts)\n    var threads: [NUM_THREADS]std.Thread = undefined;\n    var pub_counts: [NUM_THREADS]std.atomic.Value(u32) =\n        undefined;\n    for (0..NUM_THREADS) |i| {\n        pub_counts[i] = std.atomic.Value(u32).init(0);\n        threads[i] = std.Thread.spawn(\n            .{},\n            publishThread,\n            .{ client, i, MSGS_PER_THREAD, &pub_counts[i] },\n        ) catch {\n            reportResult(\n                \"mt_publish\",\n                false,\n                \"spawn failed\",\n            );\n            return;\n        };\n    }\n\n    // Wait for all publishers\n    for (&threads) |*t| t.join();\n\n    // Verify all publishes succeeded (no ring-full drops)\n    var total_published: u32 = 0;\n    for (&pub_counts) |*c| {\n        total_published += c.load(.monotonic);\n    }\n    if (total_published != TOTAL) {\n        var buf2: [64]u8 = undefined;\n        const d = std.fmt.bufPrint(\n            &buf2,\n            \"published {d}/{d}\",\n            .{ total_published, TOTAL },\n        ) catch \"pub fail\";\n        reportResult(\"mt_publish\", false, d);\n        return;\n    }\n\n    // Flush to push all ring data through to server\n    client.flush(5_000_000_000) catch {};\n\n    // Collect messages: poll with deadline (5s timeout).\n    // tryNextMsg() returns null when queue is momentarily\n    // empty, but more messages may still be in flight.\n    var received: usize = 0;\n    var empty_polls: usize = 0;\n    while (received < TOTAL and empty_polls < 500) {\n        if (sub.tryNextMsg()) |m| {\n            received += 1;\n            m.deinit();\n            empty_polls = 0;\n        } else {\n            empty_polls += 1;\n            threadSleepNs(10_000_000); // 10ms\n        }\n    }\n\n    if (received >= TOTAL) {\n        reportResult(\"mt_publish\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ received, TOTAL },\n        ) catch \"count mismatch\";\n        reportResult(\"mt_publish\", false, detail);\n    }\n}\n\nfn publishThread(\n    client: *nats.Client,\n    thread_id: usize,\n    count: usize,\n    ok_count: *std.atomic.Value(u32),\n) void {\n    var subject_buf: [32]u8 = undefined;\n    const subject = std.fmt.bufPrint(\n        &subject_buf,\n        \"mt.pub.{d}\",\n        .{thread_id},\n    ) catch return;\n\n    var payload_buf: [64]u8 = undefined;\n    for (0..count) |seq| {\n        const payload = std.fmt.bufPrint(\n            &payload_buf,\n            \"{d}:{d}\",\n            .{ thread_id, seq },\n        ) catch continue;\n        client.publish(subject, payload) catch continue;\n        _ = ok_count.fetchAdd(1, .monotonic);\n    }\n}\n\n/// Multiple OS threads subscribing/unsubscribing concurrently.\n/// Verifies no slot corruption, no duplicate SIDs.\npub fn testMultiThreadSubscribe(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_THREADS = 4;\n    const ITERS = 200;\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"mt_subscribe\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    // Each thread subscribes and unsubscribes in a loop\n    var threads: [NUM_THREADS]std.Thread = undefined;\n    var errors: [NUM_THREADS]std.atomic.Value(u32) = undefined;\n    for (0..NUM_THREADS) |i| {\n        errors[i] = std.atomic.Value(u32).init(0);\n        threads[i] = std.Thread.spawn(\n            .{},\n            subUnsubThread,\n            .{ client, i, ITERS, &errors[i] },\n        ) catch {\n            reportResult(\n                \"mt_subscribe\",\n                false,\n                \"spawn failed\",\n            );\n            return;\n        };\n    }\n\n    for (&threads) |*t| t.join();\n\n    var total_errors: u32 = 0;\n    for (&errors) |*e| {\n        total_errors += e.load(.monotonic);\n    }\n\n    const num_subs = client.numSubscriptions();\n\n    if (total_errors == 0 and num_subs == 0) {\n        reportResult(\"mt_subscribe\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"errs={d} leaked_subs={d}\",\n            .{ total_errors, num_subs },\n        ) catch \"errors\";\n        reportResult(\"mt_subscribe\", false, detail);\n    }\n}\n\nfn subUnsubThread(\n    client: *nats.Client,\n    thread_id: usize,\n    iters: usize,\n    err_count: *std.atomic.Value(u32),\n) void {\n    for (0..iters) |i| {\n        var subject_buf: [48]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"mt.sub.{d}.{d}\",\n            .{ thread_id, i },\n        ) catch continue;\n\n        const sub = client.subscribeSync(subject) catch {\n            _ = err_count.fetchAdd(1, .monotonic);\n            continue;\n        };\n        sub.deinit();\n    }\n}\n\n/// Multiple OS threads publishing, verify stats are accurate.\npub fn testMultiThreadStats(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_THREADS = 4;\n    const MSGS_PER_THREAD = 5000;\n    const TOTAL = NUM_THREADS * MSGS_PER_THREAD;\n    const PAYLOAD = \"stats-test-payload\";\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"mt_stats\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    const before = client.stats();\n\n    var threads: [NUM_THREADS]std.Thread = undefined;\n    for (0..NUM_THREADS) |i| {\n        threads[i] = std.Thread.spawn(\n            .{},\n            statsPublishThread,\n            .{ client, MSGS_PER_THREAD, PAYLOAD },\n        ) catch {\n            reportResult(\n                \"mt_stats\",\n                false,\n                \"spawn failed\",\n            );\n            return;\n        };\n    }\n\n    for (&threads) |*t| t.join();\n\n    const after = client.stats();\n    const msgs_diff = after.msgs_out - before.msgs_out;\n    const bytes_diff = after.bytes_out - before.bytes_out;\n    const expected_bytes = TOTAL * PAYLOAD.len;\n\n    if (msgs_diff == TOTAL and bytes_diff == expected_bytes) {\n        reportResult(\"mt_stats\", true, \"\");\n    } else {\n        var buf: [96]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"msgs={d}/{d} bytes={d}/{d}\",\n            .{\n                msgs_diff,\n                TOTAL,\n                bytes_diff,\n                expected_bytes,\n            },\n        ) catch \"mismatch\";\n        reportResult(\"mt_stats\", false, detail);\n    }\n}\n\nfn statsPublishThread(\n    client: *nats.Client,\n    count: usize,\n    payload: []const u8,\n) void {\n    for (0..count) |_| {\n        client.publish(\"mt.stats\", payload) catch {};\n    }\n}\n\n/// Mixed workload: publish + subscribe from different threads.\npub fn testMultiThreadMixed(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_PUB_THREADS = 2;\n    const MSGS_PER_THREAD = 3000;\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"mt_mixed\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    // Subscribe first\n    const sub = client.subscribeSync(\"mt.mix\") catch {\n        reportResult(\n            \"mt_mixed\",\n            false,\n            \"subscribe failed\",\n        );\n        return;\n    };\n    defer sub.deinit();\n\n    client.flush(5_000_000_000) catch {};\n\n    // Spawn publisher threads\n    var pub_threads: [NUM_PUB_THREADS]std.Thread = undefined;\n    for (0..NUM_PUB_THREADS) |i| {\n        pub_threads[i] = std.Thread.spawn(\n            .{},\n            mixedPublishThread,\n            .{ client, MSGS_PER_THREAD },\n        ) catch {\n            reportResult(\n                \"mt_mixed\",\n                false,\n                \"spawn failed\",\n            );\n            return;\n        };\n    }\n\n    // Spawn a subscribe/unsubscribe churn thread\n    var churn_err = std.atomic.Value(u32).init(0);\n    const churn_thread = std.Thread.spawn(\n        .{},\n        subChurnThread,\n        .{ client, 100, &churn_err },\n    ) catch {\n        reportResult(\n            \"mt_mixed\",\n            false,\n            \"churn spawn failed\",\n        );\n        return;\n    };\n\n    // Wait for publishers\n    for (&pub_threads) |*t| t.join();\n    churn_thread.join();\n\n    // Collect with deadline\n    var received: usize = 0;\n    const total = NUM_PUB_THREADS * MSGS_PER_THREAD;\n    var empty_polls: usize = 0;\n    while (received < total and empty_polls < 500) {\n        if (sub.tryNextMsg()) |m| {\n            received += 1;\n            m.deinit();\n            empty_polls = 0;\n        } else {\n            empty_polls += 1;\n            threadSleepNs(10_000_000); // 10ms\n        }\n    }\n\n    const churn_errors = churn_err.load(.monotonic);\n\n    if (received >= total and churn_errors == 0) {\n        reportResult(\"mt_mixed\", true, \"\");\n    } else {\n        var buf: [80]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"recv={d}/{d} churn_err={d}\",\n            .{ received, total, churn_errors },\n        ) catch \"error\";\n        reportResult(\"mt_mixed\", false, detail);\n    }\n}\n\nfn mixedPublishThread(\n    client: *nats.Client,\n    count: usize,\n) void {\n    for (0..count) |_| {\n        client.publish(\"mt.mix\", \"mixed\") catch {};\n    }\n}\n\nfn subChurnThread(\n    client: *nats.Client,\n    iters: usize,\n    err_count: *std.atomic.Value(u32),\n) void {\n    for (0..iters) |i| {\n        var buf: [48]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &buf,\n            \"mt.churn.{d}\",\n            .{i},\n        ) catch continue;\n\n        const sub = client.subscribeSync(subject) catch {\n            _ = err_count.fetchAdd(1, .monotonic);\n            continue;\n        };\n        sub.deinit();\n    }\n}\n\n/// Multiple threads doing request/reply concurrently.\npub fn testMultiThreadRequest(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_THREADS = 4;\n    const REQS_PER_THREAD = 50;\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    // Requester client\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n\n    const requester = nats.Client.connect(\n        allocator,\n        io_req.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"mt_request\",\n            false,\n            \"req connect failed\",\n        );\n        return;\n    };\n    defer requester.deinit();\n\n    // Responder client\n    const io_resp = utils.newIo(allocator);\n    defer io_resp.deinit();\n\n    const responder = nats.Client.connect(\n        allocator,\n        io_resp.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"mt_request\",\n            false,\n            \"resp connect failed\",\n        );\n        return;\n    };\n    defer responder.deinit();\n\n    // Responder subscribes and echoes back\n    const resp_sub = responder.subscribeSync(\n        \"mt.req\",\n    ) catch {\n        reportResult(\n            \"mt_request\",\n            false,\n            \"resp subscribe failed\",\n        );\n        return;\n    };\n    defer resp_sub.deinit();\n\n    responder.flush(5_000_000_000) catch {};\n\n    // Responder loop in a thread\n    var stop_flag = std.atomic.Value(bool).init(false);\n    const resp_thread = std.Thread.spawn(\n        .{},\n        responderThread,\n        .{ responder, resp_sub, &stop_flag },\n    ) catch {\n        reportResult(\n            \"mt_request\",\n            false,\n            \"resp spawn failed\",\n        );\n        return;\n    };\n\n    // Spawn requester threads\n    var threads: [NUM_THREADS]std.Thread = undefined;\n    var successes: [NUM_THREADS]std.atomic.Value(u32) =\n        undefined;\n    for (0..NUM_THREADS) |i| {\n        successes[i] = std.atomic.Value(u32).init(0);\n        threads[i] = std.Thread.spawn(\n            .{},\n            requestThread,\n            .{\n                requester,\n                REQS_PER_THREAD,\n                &successes[i],\n            },\n        ) catch {\n            reportResult(\n                \"mt_request\",\n                false,\n                \"req spawn failed\",\n            );\n            stop_flag.store(true, .release);\n            resp_thread.join();\n            return;\n        };\n    }\n\n    for (&threads) |*t| t.join();\n    stop_flag.store(true, .release);\n    resp_thread.join();\n\n    var total_success: u32 = 0;\n    for (&successes) |*s| {\n        total_success += s.load(.monotonic);\n    }\n\n    const expected = NUM_THREADS * REQS_PER_THREAD;\n    if (total_success >= expected) {\n        reportResult(\"mt_request\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"replies={d}/{d}\",\n            .{ total_success, expected },\n        ) catch \"mismatch\";\n        reportResult(\"mt_request\", false, detail);\n    }\n}\n\nfn responderThread(\n    responder: *nats.Client,\n    sub: *nats.Subscription,\n    stop: *std.atomic.Value(bool),\n) void {\n    while (!stop.load(.acquire)) {\n        if (sub.tryNextMsg()) |m| {\n            defer m.deinit();\n            if (m.reply_to) |reply| {\n                responder.publish(\n                    reply,\n                    m.data,\n                ) catch {};\n            }\n        } else {\n            threadSleepNs(1_000_000);\n        }\n    }\n}\n\nfn requestThread(\n    client: *nats.Client,\n    count: usize,\n    success: *std.atomic.Value(u32),\n) void {\n    for (0..count) |_| {\n        const reply = client.request(\n            \"mt.req\",\n            \"ping\",\n            2000,\n        ) catch continue;\n        if (reply) |r| {\n            r.deinit();\n            _ = success.fetchAdd(1, .monotonic);\n        }\n    }\n}\n\n/// Stress test: many threads, rapid sub/unsub, verify\n/// slot accounting stays correct.\npub fn testSubSlotIntegrity(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_THREADS = 4;\n    const ITERS = 500;\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\n            \"mt_slot_integrity\",\n            false,\n            \"connect failed\",\n        );\n        return;\n    };\n    defer client.deinit();\n\n    var threads: [NUM_THREADS]std.Thread = undefined;\n    var errors: [NUM_THREADS]std.atomic.Value(u32) =\n        undefined;\n    for (0..NUM_THREADS) |i| {\n        errors[i] = std.atomic.Value(u32).init(0);\n        threads[i] = std.Thread.spawn(\n            .{},\n            subUnsubThread,\n            .{ client, i, ITERS, &errors[i] },\n        ) catch {\n            reportResult(\n                \"mt_slot_integrity\",\n                false,\n                \"spawn failed\",\n            );\n            return;\n        };\n    }\n\n    for (&threads) |*t| t.join();\n\n    var total_errors: u32 = 0;\n    for (&errors) |*e| {\n        total_errors += e.load(.monotonic);\n    }\n\n    const final_subs = client.numSubscriptions();\n\n    if (total_errors == 0 and final_subs == 0) {\n        reportResult(\"mt_slot_integrity\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"errs={d} subs={d}\",\n            .{ total_errors, final_subs },\n        ) catch \"error\";\n        reportResult(\"mt_slot_integrity\", false, detail);\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    std.debug.print(\n        \"\\n--- MultiThreading Tests ---\\n\",\n        .{},\n    );\n    testMultiThreadPublish(allocator);\n    testMultiThreadSubscribe(allocator);\n    testMultiThreadStats(allocator);\n    testMultiThreadMixed(allocator);\n    testMultiThreadRequest(allocator);\n    testSubSlotIntegrity(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/nkey.zig",
    "content": "//! NKey Authentication Tests for NATS Client\n//!\n//! Tests NKey (Ed25519) authentication against nats-server.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst nkey_port = utils.nkey_port;\nconst test_nkey_seed = utils.test_nkey_seed;\n\n/// Tests successful NKey authentication with valid seed.\npub fn testNKeyAuthentication(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_seed = test_nkey_seed,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"nkey_authentication\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"nkey_authentication\", true, \"\");\n    } else {\n        reportResult(\"nkey_authentication\", false, \"not connected\");\n    }\n}\n\n/// Tests that authentication fails with wrong NKey seed.\npub fn testNKeyAuthFailure(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const wrong_seed =\n        \"SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4\";\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_seed = wrong_seed,\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"nkey_auth_failure\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"nkey_auth_failure\", true, \"\");\n    }\n}\n\n/// Tests pub/sub works after NKey authentication.\npub fn testNKeyPubSub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_seed = test_nkey_seed,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"nkey_pubsub\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"nkey.test.subject\") catch {\n        reportResult(\"nkey_pubsub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"nkey.test.subject\", \"nkey message\") catch {\n        reportResult(\"nkey_pubsub\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"nkey_pubsub\", true, \"\");\n    } else {\n        reportResult(\"nkey_pubsub\", false, \"no message\");\n    }\n}\n\n/// Tests that connecting without seed when NKey is required fails.\npub fn testNKeyNoSeedFails(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"nkey_no_seed_fails\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"nkey_no_seed_fails\", true, \"\");\n    }\n}\n\n/// Tests that invalid seed format returns error.\npub fn testNKeyInvalidSeedFormat(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_seed = \"SUAMK2FG\",\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"nkey_invalid_seed\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"nkey_invalid_seed\", true, \"\");\n    }\n}\n\n/// Tests authentication with NKey seed loaded from file.\npub fn testNKeySeedFile(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    // Create temp seed file using std.Io\n    const io_setup = utils.newIo(allocator);\n    defer io_setup.deinit();\n    const setup_io = io_setup.io();\n\n    const seed_file_path = utils.test_nkey_seed_file;\n\n    // Write seed to file\n    const file = std.Io.Dir.createFile(.cwd(), setup_io, seed_file_path, .{\n        .truncate = true,\n    }) catch {\n        reportResult(\"nkey_seed_file\", false, \"failed to create seed file\");\n        return;\n    };\n    file.writeStreamingAll(setup_io, test_nkey_seed) catch {\n        file.close(setup_io);\n        reportResult(\"nkey_seed_file\", false, \"failed to write seed file\");\n        return;\n    };\n    file.close(setup_io);\n\n    // Connect using seed file\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_seed_file = seed_file_path,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"nkey_seed_file\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"nkey_seed_file\", true, \"\");\n    } else {\n        reportResult(\"nkey_seed_file\", false, \"not connected\");\n    }\n}\n\n/// Tests error when seed file does not exist.\npub fn testNKeySeedFileMissing(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_seed_file = \"/nonexistent/path/to/seed.txt\",\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"nkey_seed_file_missing\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"nkey_seed_file_missing\", true, \"\");\n    }\n}\n\n/// Tests authentication with custom signing callback.\npub fn testNKeySigningCallback(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    // Derive public key from seed (must match server config)\n    var kp = nats.auth.KeyPair.fromSeed(test_nkey_seed) catch {\n        reportResult(\"nkey_signing_callback\", false, \"failed to parse seed\");\n        return;\n    };\n    defer kp.wipe();\n\n    var pubkey_buf: [56]u8 = undefined;\n    const pubkey = kp.publicKey(&pubkey_buf);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_pubkey = pubkey,\n        .nkey_sign_fn = &testSignCallback,\n    }) catch |err| {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"connect: {}\", .{err}) catch \"fmt\";\n        reportResult(\"nkey_signing_callback\", false, detail);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"nkey_signing_callback\", true, \"\");\n    } else {\n        reportResult(\"nkey_signing_callback\", false, \"not connected\");\n    }\n}\n\n/// Test signing callback using the test seed.\nfn testSignCallback(nonce: []const u8, sig: *[64]u8) bool {\n    var kp = nats.auth.KeyPair.fromSeed(test_nkey_seed) catch return false;\n    defer kp.wipe();\n    sig.* = kp.sign(nonce);\n    return true;\n}\n\n/// Tests error when signing callback returns false.\npub fn testNKeyCallbackFails(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, nkey_port);\n\n    // Derive public key from seed\n    var kp = nats.auth.KeyPair.fromSeed(test_nkey_seed) catch {\n        reportResult(\"nkey_callback_fails\", false, \"failed to parse seed\");\n        return;\n    };\n    defer kp.wipe();\n\n    var pubkey_buf: [56]u8 = undefined;\n    const pubkey = kp.publicKey(&pubkey_buf);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        .nkey_pubkey = pubkey,\n        .nkey_sign_fn = &failingSignCallback,\n    });\n\n    if (result) |client| {\n        client.deinit();\n        reportResult(\"nkey_callback_fails\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"nkey_callback_fails\", true, \"\");\n    }\n}\n\n/// Signing callback that always fails.\nfn failingSignCallback(nonce: []const u8, sig: *[64]u8) bool {\n    _ = nonce;\n    _ = sig;\n    return false;\n}\n\n/// Runs all NKey authentication tests.\npub fn runAll(allocator: std.mem.Allocator) void {\n    testNKeyAuthentication(allocator);\n    testNKeyAuthFailure(allocator);\n    testNKeyPubSub(allocator);\n    testNKeyNoSeedFails(allocator);\n    testNKeyInvalidSeedFormat(allocator);\n    testNKeySeedFile(allocator);\n    testNKeySeedFileMissing(allocator);\n    testNKeySigningCallback(allocator);\n    testNKeyCallbackFails(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/protocol.zig",
    "content": "//! Protocol Tests for NATS Client\n//!\n//! Tests for NATS protocol handling including -ERR responses,\n//! PING/PONG keep-alive, INFO parsing, and edge cases.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testServerInfoParsing(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_info_parsing\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"server_info_parsing\", false, \"no server info\");\n        return;\n    }\n\n    const server_info = info.?;\n\n    if (server_info.server_id.len == 0) {\n        reportResult(\"server_info_parsing\", false, \"empty server_id\");\n        return;\n    }\n\n    if (server_info.version.len == 0) {\n        reportResult(\"server_info_parsing\", false, \"empty version\");\n        return;\n    }\n\n    if (server_info.max_payload == 0) {\n        reportResult(\"server_info_parsing\", false, \"max_payload is 0\");\n        return;\n    }\n\n    if (server_info.proto < 1) {\n        reportResult(\"server_info_parsing\", false, \"proto < 1\");\n        return;\n    }\n\n    reportResult(\"server_info_parsing\", true, \"\");\n}\n\npub fn testPingPongKeepAlive(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"ping_pong_keep_alive\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"ping.test\") catch {\n        reportResult(\"ping_pong_keep_alive\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..5) |_| {\n        client.publish(\"ping.test\", \"keep-alive\") catch {\n            reportResult(\"ping_pong_keep_alive\", false, \"publish failed\");\n            return;\n        };\n        io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n    }\n\n    if (!client.isConnected()) {\n        reportResult(\"ping_pong_keep_alive\", false, \"disconnected\");\n        return;\n    }\n\n    var received: u32 = 0;\n    for (0..5) |_| {\n        if (sub.nextMsgTimeout(200) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 5) {\n        reportResult(\"ping_pong_keep_alive\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/5\",\n            .{received},\n        ) catch \"e\";\n        reportResult(\"ping_pong_keep_alive\", false, detail);\n    }\n}\n\npub fn testProtocolAuthError(allocator: std.mem.Allocator) void {\n    var url_buf: [128]u8 = undefined;\n    // Connect to auth server with WRONG token\n    const url = formatAuthUrl(&url_buf, auth_port, \"wrong-token\");\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const result = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    );\n\n    if (result) |client| {\n        // Should have failed with auth error\n        client.deinit();\n        reportResult(\"protocol_auth_error\", false, \"should have failed\");\n    } else |err| {\n        // Expect AuthorizationViolation\n        if (err == error.AuthorizationViolation) {\n            reportResult(\"protocol_auth_error\", true, \"\");\n        } else {\n            reportResult(\"protocol_auth_error\", false, \"wrong error type\");\n        }\n    }\n}\n\npub fn testUnknownSidHandling(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"unknown_sid_handling\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.subscribeSync(\"unknown.sid.test\") catch {\n        reportResult(\"unknown_sid_handling\", false, \"subscribe failed\");\n        return;\n    };\n\n    sub1.unsubscribe() catch {\n        sub1.deinit();\n        reportResult(\"unknown_sid_handling\", false, \"unsubscribe failed\");\n        return;\n    };\n    sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"unknown.sid.test\") catch {\n        reportResult(\"unknown_sid_handling\", false, \"subscribe2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    client.publish(\"unknown.sid.test\", \"test\") catch {\n        reportResult(\"unknown_sid_handling\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub2.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        if (client.isConnected()) {\n            reportResult(\"unknown_sid_handling\", true, \"\");\n        } else {\n            reportResult(\"unknown_sid_handling\", false, \"disconnected\");\n        }\n    } else {\n        reportResult(\"unknown_sid_handling\", false, \"no message\");\n    }\n}\n\npub fn testInvalidProtocolCommand(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const result = parser.parse(allocator, \"INVALID_CMD\\r\\n\", &consumed);\n\n    if (result) |_| {\n        reportResult(\"invalid_protocol_cmd\", false, \"should have failed\");\n    } else |err| {\n        // Expect InvalidCommand error\n        if (err == error.InvalidCommand) {\n            reportResult(\"invalid_protocol_cmd\", true, \"\");\n        } else {\n            reportResult(\"invalid_protocol_cmd\", false, \"wrong error type\");\n        }\n    }\n}\n\npub fn testProtocolPartialData(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const partial_result = parser.parse(allocator, \"PIN\", &consumed) catch {\n        reportResult(\"protocol_partial_data\", false, \"unexpected error\");\n        return;\n    };\n\n    if (partial_result != null) {\n        reportResult(\"protocol_partial_data\", false, \"should return null\");\n        return;\n    }\n\n    if (consumed != 0) {\n        reportResult(\"protocol_partial_data\", false, \"consumed != 0\");\n        return;\n    }\n\n    const full_result = parser.parse(allocator, \"PING\\r\\n\", &consumed) catch {\n        reportResult(\"protocol_partial_data\", false, \"parse error\");\n        return;\n    };\n\n    if (full_result == null) {\n        reportResult(\"protocol_partial_data\", false, \"should return command\");\n        return;\n    }\n\n    if (consumed != 6) {\n        reportResult(\"protocol_partial_data\", false, \"wrong consumed\");\n        return;\n    }\n\n    reportResult(\"protocol_partial_data\", true, \"\");\n}\n\npub fn testProtocolPartialMsgPayload(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const partial_msg = \"MSG test.subject 1 10\\r\\nhello\";\n\n    const partial_result = parser.parse(\n        allocator,\n        partial_msg,\n        &consumed,\n    ) catch {\n        reportResult(\"protocol_partial_msg\", false, \"unexpected error\");\n        return;\n    };\n\n    if (partial_result != null) {\n        reportResult(\"protocol_partial_msg\", false, \"should return null\");\n        return;\n    }\n\n    if (consumed != 0) {\n        reportResult(\"protocol_partial_msg\", false, \"consumed should be 0\");\n        return;\n    }\n\n    const full_msg = \"MSG test.subject 1 10\\r\\nhelloworld\\r\\n\";\n    const full_result = parser.parse(allocator, full_msg, &consumed) catch {\n        reportResult(\"protocol_partial_msg\", false, \"parse error\");\n        return;\n    };\n\n    if (full_result == null) {\n        reportResult(\"protocol_partial_msg\", false, \"should return command\");\n        return;\n    }\n\n    const msg = full_result.?.msg;\n    if (!std.mem.eql(u8, msg.payload, \"helloworld\")) {\n        reportResult(\"protocol_partial_msg\", false, \"wrong payload\");\n        return;\n    }\n\n    reportResult(\"protocol_partial_msg\", true, \"\");\n}\n\npub fn testProtocolErrorParsing(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const auth_err = \"-ERR 'Authorization Violation'\\r\\n\";\n    const auth_result = parser.parse(allocator, auth_err, &consumed) catch {\n        reportResult(\"protocol_err_parsing\", false, \"parse error\");\n        return;\n    };\n\n    if (auth_result == null) {\n        reportResult(\"protocol_err_parsing\", false, \"auth null\");\n        return;\n    }\n\n    const err_msg = auth_result.?.err;\n    if (!std.mem.eql(u8, err_msg, \"'Authorization Violation'\")) {\n        reportResult(\"protocol_err_parsing\", false, \"wrong auth msg\");\n        return;\n    }\n\n    const err = protocol.parseServerError(err_msg);\n    if (err != error.AuthorizationViolation) {\n        reportResult(\"protocol_err_parsing\", false, \"wrong error type\");\n        return;\n    }\n\n    consumed = 0;\n    const payload_err = \"-ERR 'Maximum Payload Exceeded'\\r\\n\";\n    const payload_result = parser.parse(\n        allocator,\n        payload_err,\n        &consumed,\n    ) catch {\n        reportResult(\"protocol_err_parsing\", false, \"payload parse error\");\n        return;\n    };\n\n    if (payload_result == null) {\n        reportResult(\"protocol_err_parsing\", false, \"payload null\");\n        return;\n    }\n\n    const payload_err_msg = payload_result.?.err;\n    const payload_err_type = protocol.parseServerError(payload_err_msg);\n    if (payload_err_type != error.MaxPayloadExceeded) {\n        reportResult(\"protocol_err_parsing\", false, \"wrong payload error\");\n        return;\n    }\n\n    reportResult(\"protocol_err_parsing\", true, \"\");\n}\n\npub fn testMaxPayloadLimit(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_payload_limit\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"max_payload_limit\", false, \"no server info\");\n        return;\n    }\n\n    const max_payload = info.?.max_payload;\n\n    if (max_payload < 1024) {\n        reportResult(\"max_payload_limit\", false, \"max_payload too small\");\n        return;\n    }\n\n    if (max_payload > 64 * 1024 * 1024) {\n        reportResult(\"max_payload_limit\", false, \"max_payload too large\");\n        return;\n    }\n\n    reportResult(\"max_payload_limit\", true, \"\");\n}\n\npub fn testProtocolOkResponse(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const ok_result = parser.parse(allocator, \"+OK\\r\\n\", &consumed) catch {\n        reportResult(\"protocol_ok_response\", false, \"parse error\");\n        return;\n    };\n\n    if (ok_result == null) {\n        reportResult(\"protocol_ok_response\", false, \"null result\");\n        return;\n    }\n\n    if (ok_result.? != .ok) {\n        reportResult(\"protocol_ok_response\", false, \"wrong command type\");\n        return;\n    }\n\n    if (consumed != 5) {\n        reportResult(\"protocol_ok_response\", false, \"wrong consumed\");\n        return;\n    }\n\n    reportResult(\"protocol_ok_response\", true, \"\");\n}\n\npub fn testProtocolStability(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"protocol_stability\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var subs: [5]?*nats.Subscription = [_]?*nats.Subscription{null} ** 5;\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..5) |i| {\n        var buf: [32]u8 = undefined;\n        const subject =\n            std.fmt.bufPrint(&buf, \"stability.{d}\", .{i}) catch continue;\n        subs[i] = client.subscribeSync(subject) catch {\n            reportResult(\"protocol_stability\", false, \"subscribe failed\");\n            return;\n        };\n    }\n\n    for (0..5) |i| {\n        var buf: [32]u8 = undefined;\n        const subject =\n            std.fmt.bufPrint(&buf, \"stability.{d}\", .{i}) catch continue;\n        client.publish(subject, \"test\") catch {\n            reportResult(\"protocol_stability\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var received: u32 = 0;\n    for (0..5) |i| {\n        if (subs[i]) |sub| {\n            if (sub.nextMsgTimeout(500) catch null) |m| {\n                m.deinit();\n                received += 1;\n            }\n        }\n    }\n\n    for (0..3) |i| {\n        if (subs[i]) |sub| {\n            sub.unsubscribe() catch {};\n        }\n    }\n\n    if (!client.isConnected()) {\n        reportResult(\"protocol_stability\", false, \"disconnected\");\n        return;\n    }\n\n    if (received == 5) {\n        reportResult(\"protocol_stability\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail =\n            std.fmt.bufPrint(&buf, \"got {d}/5\", .{received}) catch \"e\";\n        reportResult(\"protocol_stability\", false, detail);\n    }\n}\n\npub fn testProtocolMsgWithReplyTo(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const msg_data = \"MSG test.subject 42 _INBOX.reply123 5\\r\\nhello\\r\\n\";\n    const result = parser.parse(allocator, msg_data, &consumed) catch {\n        reportResult(\"protocol_msg_reply\", false, \"parse error\");\n        return;\n    };\n\n    if (result == null) {\n        reportResult(\"protocol_msg_reply\", false, \"null result\");\n        return;\n    }\n\n    const msg = result.?.msg;\n\n    if (!std.mem.eql(u8, msg.subject, \"test.subject\")) {\n        reportResult(\"protocol_msg_reply\", false, \"wrong subject\");\n        return;\n    }\n\n    if (msg.sid != 42) {\n        reportResult(\"protocol_msg_reply\", false, \"wrong sid\");\n        return;\n    }\n\n    if (msg.reply_to == null) {\n        reportResult(\"protocol_msg_reply\", false, \"no reply_to\");\n        return;\n    }\n\n    if (!std.mem.eql(u8, msg.reply_to.?, \"_INBOX.reply123\")) {\n        reportResult(\"protocol_msg_reply\", false, \"wrong reply_to\");\n        return;\n    }\n\n    if (!std.mem.eql(u8, msg.payload, \"hello\")) {\n        reportResult(\"protocol_msg_reply\", false, \"wrong payload\");\n        return;\n    }\n\n    reportResult(\"protocol_msg_reply\", true, \"\");\n}\n\npub fn testProtocolHmsgParsing(allocator: std.mem.Allocator) void {\n    const protocol = @import(\"nats\").protocol;\n    var parser: protocol.Parser = .{};\n    var consumed: usize = 0;\n\n    const hmsg_data = \"HMSG test.subject 1 12 17\\r\\nNATS/1.0\\r\\n\\r\\nhello\\r\\n\";\n    const result = parser.parse(allocator, hmsg_data, &consumed) catch {\n        reportResult(\"protocol_hmsg_parsing\", false, \"parse error\");\n        return;\n    };\n\n    if (result == null) {\n        reportResult(\"protocol_hmsg_parsing\", false, \"null result\");\n        return;\n    }\n\n    const hmsg = result.?.hmsg;\n\n    if (!std.mem.eql(u8, hmsg.subject, \"test.subject\")) {\n        reportResult(\"protocol_hmsg_parsing\", false, \"wrong subject\");\n        return;\n    }\n\n    if (hmsg.sid != 1) {\n        reportResult(\"protocol_hmsg_parsing\", false, \"wrong sid\");\n        return;\n    }\n\n    if (hmsg.header_len != 12) {\n        reportResult(\"protocol_hmsg_parsing\", false, \"wrong header_len\");\n        return;\n    }\n\n    if (!std.mem.eql(u8, hmsg.payload, \"hello\")) {\n        reportResult(\"protocol_hmsg_parsing\", false, \"wrong payload\");\n        return;\n    }\n\n    reportResult(\"protocol_hmsg_parsing\", true, \"\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testServerInfoParsing(allocator);\n    testPingPongKeepAlive(allocator);\n    testProtocolAuthError(allocator);\n    testUnknownSidHandling(allocator);\n    testInvalidProtocolCommand(allocator);\n    testProtocolPartialData(allocator);\n    testProtocolPartialMsgPayload(allocator);\n    testProtocolErrorParsing(allocator);\n    testMaxPayloadLimit(allocator);\n    testProtocolOkResponse(allocator);\n    testProtocolStability(allocator);\n    testProtocolMsgWithReplyTo(allocator);\n    testProtocolHmsgParsing(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/publish.zig",
    "content": "//! Publish Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testClientPubSub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_pubsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"pubsub\") catch {\n        reportResult(\"client_pubsub\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"pubsub\", \"test-message\") catch {\n        reportResult(\"client_pubsub\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |msg| msg.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (std.mem.eql(u8, msg.data, \"test-message\")) {\n            reportResult(\"client_pubsub\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"client_pubsub\", false, \"no message received\");\n}\n\npub fn testClientPublishReply(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_pub_reply\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"req\") catch {\n        reportResult(\"client_pub_reply\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publishRequest(\"req\", \"reply.inbox\", \"request\") catch {\n        reportResult(\"client_pub_reply\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.reply_to) |rt| {\n            if (std.mem.eql(u8, rt, \"reply.inbox\")) {\n                reportResult(\"client_pub_reply\", true, \"\");\n                return;\n            }\n        }\n    } else |_| {}\n\n    reportResult(\"client_pub_reply\", false, \"no reply_to\");\n}\n\npub fn testPublishEmptyPayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_empty_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"empty\") catch {\n        reportResult(\"publish_empty_payload\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"empty\", \"\") catch {\n        reportResult(\"publish_empty_payload\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.data.len == 0) {\n            reportResult(\"publish_empty_payload\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"publish_empty_payload\", false, \"no empty message\");\n}\n\npub fn testPublishLargePayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_large_payload\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"large\") catch {\n        reportResult(\"publish_large_payload\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const payload = allocator.alloc(u8, 8 * 1024) catch {\n        reportResult(\"publish_large_payload\", false, \"alloc failed\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'X');\n\n    client.publish(\"large\", payload) catch {\n        reportResult(\"publish_large_payload\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.data.len == 8 * 1024) {\n            reportResult(\"publish_large_payload\", true, \"\");\n            return;\n        }\n    } else |_| {}\n\n    reportResult(\"publish_large_payload\", false, \"wrong size\");\n}\n\npub fn testPublishRapidFire(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_rapid_fire\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    for (0..1000) |_| {\n        client.publish(\"rapid\", \"msg\") catch {\n            reportResult(\"publish_rapid_fire\", false, \"pub failed\");\n            return;\n        };\n    }\n\n    const stats = client.stats();\n    if (stats.msgs_out >= 1000) {\n        reportResult(\"publish_rapid_fire\", true, \"\");\n    } else {\n        reportResult(\"publish_rapid_fire\", false, \"not all published\");\n    }\n}\n\npub fn testPublishNoSubscribers(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_no_subscribers\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.publish(\"nosub\", \"message\") catch {\n        reportResult(\"publish_no_subscribers\", false, \"pub failed\");\n        return;\n    };\n\n    reportResult(\"publish_no_subscribers\", true, \"\");\n}\n\npub fn testPublishAfterDisconnect(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_after_disconnect\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    _ = client.drain() catch {\n        reportResult(\"publish_after_disconnect\", false, \"drain failed\");\n        return;\n    };\n\n    const result = client.publish(\"test.subject\", \"data\");\n\n    if (result) |_| {\n        reportResult(\"publish_after_disconnect\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"publish_after_disconnect\", true, \"\");\n    }\n}\n\npub fn testPublishBatching(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_batching\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"batch.test\") catch {\n        reportResult(\"publish_batching\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"batch.test\", \"data1\") catch {};\n    client.publish(\"batch.test\", \"data2\") catch {};\n    client.publish(\"batch.test\", \"data3\") catch {};\n\n    var received: u32 = 0;\n    for (0..3) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            received += 1;\n        }\n    }\n\n    if (received == 3) {\n        reportResult(\"publish_batching\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail =\n            std.fmt.bufPrint(&buf, \"got {d}/3\", .{received}) catch \"e\";\n        reportResult(\"publish_batching\", false, detail);\n    }\n}\n\npub fn testFlushAfterEachPublish(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"flush_after_each\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"flush.each\") catch {\n        reportResult(\"flush_after_each\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..50) |_| {\n        client.publish(\"flush.each\", \"msg\") catch {\n            reportResult(\"flush_after_each\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var received: u32 = 0;\n    for (0..50) |_| {\n        const msg = sub.nextMsgTimeout(500) catch {\n            break;\n        };\n        if (msg) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    if (received == 50) {\n        reportResult(\"flush_after_each\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/50\",\n            .{received},\n        ) catch \"err\";\n        reportResult(\"flush_after_each\", false, detail);\n    }\n}\n\npub fn testPublishToWildcardFails(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"pub_wildcard_fails\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Wildcards are only valid for subscribe, not publish\n    const result1 = client.publish(\"foo.*\", \"data\");\n    const result2 = client.publish(\"foo.>\", \"data\");\n\n    const star_failed = if (result1) |_| false else |_| true;\n    const gt_failed = if (result2) |_| false else |_| true;\n\n    if (star_failed and gt_failed) {\n        reportResult(\"pub_wildcard_fails\", true, \"\");\n    } else if (!star_failed) {\n        reportResult(\"pub_wildcard_fails\", false, \"* should fail\");\n    } else {\n        reportResult(\"pub_wildcard_fails\", false, \"> should fail\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testClientPubSub(allocator);\n    testClientPublishReply(allocator);\n    testPublishEmptyPayload(allocator);\n    testPublishLargePayload(allocator);\n    testPublishRapidFire(allocator);\n    testPublishNoSubscribers(allocator);\n    testPublishAfterDisconnect(allocator);\n    testPublishBatching(allocator);\n    testFlushAfterEachPublish(allocator);\n    testPublishToWildcardFails(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/queue.zig",
    "content": "//! Queue Group Tests for NATS Client\n//!\n//! Tests for queue group subscriptions and message distribution.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\npub fn testQueueGroups(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_groups\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const queue = \"workers\";\n    const sub = client.queueSubscribeSync(\"queue.test\", queue) catch {\n        reportResult(\"queue_groups\", false, \"queue subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    if (sub.sid == 0) {\n        reportResult(\"queue_groups\", false, \"invalid queue sid\");\n        return;\n    }\n\n    reportResult(\"queue_groups\", true, \"\");\n}\n\npub fn testQueueGroupDistribution(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_group_distribution\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.queueSubscribeSync(\"qdist.test\", \"workers\") catch {\n        reportResult(\"queue_group_distribution\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.queueSubscribeSync(\"qdist.test\", \"workers\") catch {\n        reportResult(\"queue_group_distribution\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    const sub3 = client.queueSubscribeSync(\"qdist.test\", \"workers\") catch {\n        reportResult(\"queue_group_distribution\", false, \"sub3 failed\");\n        return;\n    };\n    defer sub3.deinit();\n\n    for (0..30) |_| {\n        client.publish(\"qdist.test\", \"work\") catch {\n            reportResult(\"queue_group_distribution\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var count1: u32 = 0;\n    var count2: u32 = 0;\n    var count3: u32 = 0;\n\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    while (true) {\n        const msg = sub1.nextMsgTimeout(50) catch {\n            break;\n        };\n        if (msg) |m| {\n            m.deinit();\n            count1 += 1;\n        } else break;\n    }\n\n    while (true) {\n        const msg = sub2.nextMsgTimeout(50) catch {\n            break;\n        };\n        if (msg) |m| {\n            m.deinit();\n            count2 += 1;\n        } else break;\n    }\n\n    while (true) {\n        const msg = sub3.nextMsgTimeout(50) catch {\n            break;\n        };\n        if (msg) |m| {\n            m.deinit();\n            count3 += 1;\n        } else break;\n    }\n\n    const total = count1 + count2 + count3;\n\n    if (total == 30) {\n        reportResult(\"queue_group_distribution\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"total={d} (expected 30)\",\n            .{total},\n        ) catch \"error\";\n        reportResult(\"queue_group_distribution\", false, detail);\n    }\n}\n\npub fn testQueueGroupMultipleClients(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_a = utils.newIo(allocator);\n    defer io_a.deinit();\n    const client_a = nats.Client.connect(allocator, io_a.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_multi_client\", false, \"A connect failed\");\n        return;\n    };\n    defer client_a.deinit();\n\n    const io_b = utils.newIo(allocator);\n    defer io_b.deinit();\n    const client_b = nats.Client.connect(allocator, io_b.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_multi_client\", false, \"B connect failed\");\n        return;\n    };\n    defer client_b.deinit();\n\n    const io_c = utils.newIo(allocator);\n    defer io_c.deinit();\n    const client_c = nats.Client.connect(allocator, io_c.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_multi_client\", false, \"C connect failed\");\n        return;\n    };\n    defer client_c.deinit();\n\n    const sub_a = client_a.queueSubscribeSync(\n        \"qmc.test\",\n        \"workers\",\n    ) catch {\n        reportResult(\"queue_multi_client\", false, \"A sub failed\");\n        return;\n    };\n    defer sub_a.deinit();\n\n    const sub_b = client_b.queueSubscribeSync(\n        \"qmc.test\",\n        \"workers\",\n    ) catch {\n        reportResult(\"queue_multi_client\", false, \"B sub failed\");\n        return;\n    };\n    defer sub_b.deinit();\n\n    io_a.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    for (0..20) |_| {\n        client_c.publish(\"qmc.test\", \"work\") catch {\n            reportResult(\"queue_multi_client\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var count_a: u32 = 0;\n    var count_b: u32 = 0;\n\n    for (0..20) |_| {\n        if (sub_a.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            count_a += 1;\n        }\n    }\n    for (0..20) |_| {\n        if (sub_b.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            count_b += 1;\n        }\n    }\n\n    if (count_a + count_b == 20) {\n        reportResult(\"queue_multi_client\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}+{d}={d}\",\n            .{ count_a, count_b, count_a + count_b },\n        ) catch \"err\";\n        reportResult(\"queue_multi_client\", false, detail);\n    }\n}\n\npub fn testQueueGroupSingleReceiver(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_single_recv\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.queueSubscribeSync(\"qsingle.test\", \"solo\") catch {\n        reportResult(\"queue_single_recv\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..10) |_| {\n        client.publish(\"qsingle.test\", \"msg\") catch {};\n    }\n\n    var count: u32 = 0;\n    for (0..15) |_| {\n        const msg = sub.nextMsgTimeout(200) catch break;\n        if (msg) |m| {\n            m.deinit();\n            count += 1;\n        } else break;\n    }\n\n    if (count == 10) {\n        reportResult(\"queue_single_recv\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/10\", .{count}) catch \"e\";\n        reportResult(\"queue_single_recv\", false, detail);\n    }\n}\n\npub fn testQueueWithWildcard(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_wildcard\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.queueSubscribeSync(\"qw.>\", \"workers\") catch {\n        reportResult(\"queue_wildcard\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"qw.foo\", \"one\") catch {};\n    client.publish(\"qw.bar\", \"two\") catch {};\n    client.publish(\"qw.baz.deep\", \"three\") catch {};\n\n    var count: u32 = 0;\n    for (0..5) |_| {\n        const msg = sub.nextMsgTimeout(200) catch break;\n        if (msg) |m| {\n            m.deinit();\n            count += 1;\n        } else break;\n    }\n\n    if (count == 3) {\n        reportResult(\"queue_wildcard\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/3\", .{count}) catch \"e\";\n        reportResult(\"queue_wildcard\", false, detail);\n    }\n}\n\npub fn testMultipleQueueGroups(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"multi_queue_groups\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub_a = client.queueSubscribeSync(\"mqg.test\", \"group-A\") catch {\n        reportResult(\"multi_queue_groups\", false, \"sub A failed\");\n        return;\n    };\n    defer sub_a.deinit();\n\n    const sub_b = client.queueSubscribeSync(\"mqg.test\", \"group-B\") catch {\n        reportResult(\"multi_queue_groups\", false, \"sub B failed\");\n        return;\n    };\n    defer sub_b.deinit();\n\n    client.publish(\"mqg.test\", \"hello\") catch {\n        reportResult(\"multi_queue_groups\", false, \"publish failed\");\n        return;\n    };\n\n    var count: u32 = 0;\n    if (sub_a.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n    if (sub_b.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n\n    if (count == 2) {\n        reportResult(\"multi_queue_groups\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/2\", .{count}) catch \"err\";\n        reportResult(\"multi_queue_groups\", false, detail);\n    }\n}\n\npub fn testFourClientQueueGroup(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    var ios: [5]*utils.TestIo = undefined;\n    for (&ios) |*io_ptr| {\n        io_ptr.* = utils.newIo(allocator);\n    }\n    defer for (ios) |io| io.deinit();\n\n    var clients: [5]?*nats.Client = .{ null, null, null, null, null };\n    defer for (&clients) |*c| {\n        if (c.*) |client| client.deinit();\n    };\n\n    for (&clients, 0..) |*c, i| {\n        c.* = nats.Client.connect(allocator, ios[i].io(), url, .{ .reconnect = false }) catch {\n            reportResult(\"four_client_queue\", false, \"connect failed\");\n            return;\n        };\n    }\n\n    var subs: [4]?*nats.Subscription = .{ null, null, null, null };\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..4) |i| {\n        subs[i] = clients[i].?.queueSubscribeSync(\n            \"fourq.test\",\n            \"workers\",\n        ) catch {\n            reportResult(\"four_client_queue\", false, \"subscribe failed\");\n            return;\n        };\n    }\n\n    ios[0].io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    for (0..40) |_| {\n        clients[4].?.publish(\"fourq.test\", \"work\") catch {};\n    }\n\n    var counts: [4]u32 = .{ 0, 0, 0, 0 };\n    for (0..4) |i| {\n        for (0..40) |_| {\n            const msg = subs[i].?.nextMsgTimeout(\n                100,\n            ) catch break;\n            if (msg) |m| {\n                m.deinit();\n                counts[i] += 1;\n            } else break;\n        }\n    }\n\n    const total = counts[0] + counts[1] + counts[2] + counts[3];\n    if (total == 40) {\n        reportResult(\"four_client_queue\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}+{d}+{d}+{d}={d}\",\n            .{ counts[0], counts[1], counts[2], counts[3], total },\n        ) catch \"e\";\n        reportResult(\"four_client_queue\", false, detail);\n    }\n}\n\npub fn testQueueMemberJoinsMidStream(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_join_midstream\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.queueSubscribeSync(\"qjoin.test\", \"workers\") catch {\n        reportResult(\"queue_join_midstream\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    for (0..10) |_| {\n        client.publish(\"qjoin.test\", \"msg\") catch {};\n    }\n\n    const sub2 = client.queueSubscribeSync(\"qjoin.test\", \"workers\") catch {\n        reportResult(\"queue_join_midstream\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    for (0..10) |_| {\n        client.publish(\"qjoin.test\", \"msg\") catch {};\n    }\n\n    var count1: u32 = 0;\n    var count2: u32 = 0;\n\n    for (0..20) |_| {\n        if (sub1.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            count1 += 1;\n        }\n    }\n    for (0..20) |_| {\n        if (sub2.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            count2 += 1;\n        }\n    }\n\n    if (count1 + count2 == 20) {\n        if (count2 > 0) {\n            reportResult(\"queue_join_midstream\", true, \"\");\n        } else {\n            reportResult(\"queue_join_midstream\", false, \"sub2 got 0\");\n        }\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"total={d} (expect 20)\",\n            .{count1 + count2},\n        ) catch \"e\";\n        reportResult(\"queue_join_midstream\", false, detail);\n    }\n}\n\npub fn testQueueMemberLeaves(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_member_leaves\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.queueSubscribeSync(\"qleave.test\", \"workers\") catch {\n        reportResult(\"queue_member_leaves\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.queueSubscribeSync(\"qleave.test\", \"workers\") catch {\n        reportResult(\"queue_member_leaves\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    for (0..10) |_| {\n        client.publish(\"qleave.test\", \"msg\") catch {};\n    }\n\n    sub1.unsubscribe() catch {};\n\n    for (0..10) |_| {\n        client.publish(\"qleave.test\", \"msg\") catch {};\n    }\n\n    var count2: u32 = 0;\n    for (0..25) |_| {\n        if (sub2.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            count2 += 1;\n        }\n    }\n\n    if (count2 >= 10) {\n        reportResult(\"queue_member_leaves\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}\", .{count2}) catch \"e\";\n        reportResult(\"queue_member_leaves\", false, detail);\n    }\n}\n\npub fn testLargeQueueGroup(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"large_queue_group\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const NUM_SUBS = 20;\n    var subs: [NUM_SUBS]?*nats.Subscription = [_]?*nats.Subscription{null} ** NUM_SUBS;\n    var created: usize = 0;\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..NUM_SUBS) |i| {\n        subs[i] = client.queueSubscribeSync(\n            \"lqg.test\",\n            \"big-workers\",\n        ) catch {\n            break;\n        };\n        created += 1;\n    }\n\n    if (created != NUM_SUBS) {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"created {d}/20\", .{created}) catch \"e\";\n        reportResult(\"large_queue_group\", false, detail);\n        return;\n    }\n\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    const NUM_MSGS = 100;\n    for (0..NUM_MSGS) |_| {\n        client.publish(\"lqg.test\", \"work\") catch {};\n    }\n\n    var total: u32 = 0;\n    for (0..NUM_SUBS) |i| {\n        if (subs[i]) |sub| {\n            for (0..NUM_MSGS) |_| {\n                if (sub.nextMsgTimeout(50) catch null) |m| {\n                    m.deinit();\n                    total += 1;\n                } else break;\n            }\n        }\n    }\n\n    if (total == NUM_MSGS) {\n        reportResult(\"large_queue_group\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/100\", .{total}) catch \"e\";\n        reportResult(\"large_queue_group\", false, detail);\n    }\n}\n\npub fn testQueueGroupNameValidation(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_name_validation\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.queueSubscribeSync(\"qn.test1\", \"workers-1\") catch {\n        reportResult(\"queue_name_validation\", false, \"workers-1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.queueSubscribeSync(\"qn.test2\", \"workers_2\") catch {\n        reportResult(\"queue_name_validation\", false, \"workers_2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    const sub3 = client.queueSubscribeSync(\"qn.test3\", \"WorkersABC\") catch {\n        reportResult(\"queue_name_validation\", false, \"WorkersABC failed\");\n        return;\n    };\n    defer sub3.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"queue_name_validation\", true, \"\");\n    } else {\n        reportResult(\"queue_name_validation\", false, \"disconnected\");\n    }\n}\n\npub fn testQueueGroupFairness(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{ .reconnect = false }) catch {\n        reportResult(\"queue_fairness\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const NUM_SUBS = 5;\n    var subs: [NUM_SUBS]?*nats.Subscription = [_]?*nats.Subscription{null} ** NUM_SUBS;\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..NUM_SUBS) |i| {\n        subs[i] = client.queueSubscribeSync(\"qfair.test\", \"fairness\") catch {\n            reportResult(\"queue_fairness\", false, \"subscribe failed\");\n            return;\n        };\n    }\n\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    const NUM_MSGS = 100;\n    for (0..NUM_MSGS) |_| {\n        client.publish(\"qfair.test\", \"msg\") catch {};\n    }\n\n    var counts: [NUM_SUBS]u32 = [_]u32{0} ** NUM_SUBS;\n    for (0..NUM_SUBS) |i| {\n        if (subs[i]) |sub| {\n            for (0..NUM_MSGS) |_| {\n                if (sub.nextMsgTimeout(50) catch null) |m| {\n                    m.deinit();\n                    counts[i] += 1;\n                } else break;\n            }\n        }\n    }\n\n    var total: u32 = 0;\n    for (counts) |c| total += c;\n\n    if (total != NUM_MSGS) {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"total={d}/100\", .{total}) catch \"e\";\n        reportResult(\"queue_fairness\", false, detail);\n        return;\n    }\n\n    var min_count: u32 = NUM_MSGS;\n    for (counts) |c| {\n        if (c < min_count) min_count = c;\n    }\n\n    if (min_count >= 5) {\n        reportResult(\"queue_fairness\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"min={d} (expect >= 5)\",\n            .{min_count},\n        ) catch \"e\";\n        reportResult(\"queue_fairness\", false, detail);\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testQueueGroups(allocator);\n    testQueueGroupDistribution(allocator);\n    testQueueGroupMultipleClients(allocator);\n    testQueueGroupSingleReceiver(allocator);\n    testQueueWithWildcard(allocator);\n    testMultipleQueueGroups(allocator);\n    testFourClientQueueGroup(allocator);\n    testQueueMemberJoinsMidStream(allocator);\n    testQueueMemberLeaves(allocator);\n    testLargeQueueGroup(allocator);\n    testQueueGroupNameValidation(allocator);\n    testQueueGroupFairness(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/reconnect.zig",
    "content": "//! Reconnection Integration Tests\n//!\n//! Tests automatic reconnection functionality including subscription\n//! restoration, pending buffer flushing, and server pool rotation.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\nconst ServerManager = utils.ServerManager;\n\nconst reconnect_port: u16 = 14227;\n\nconst failover_port_1: u16 = 14230;\nconst failover_port_2: u16 = 14231;\nconst failover_port_3: u16 = 14232;\nconst failover_port_4: u16 = 14233;\nconst failover_port_5: u16 = 14234;\nconst failover_port_6: u16 = 14235;\nconst failover_port_7: u16 = 14236;\nconst failover_port_8: u16 = 14237;\nconst failover_port_9: u16 = 14238;\n\nfn waitForClosed(\n    io: std.Io,\n    client: *nats.Client,\n    timeout_ms: u32,\n) bool {\n    var waited: u32 = 0;\n    while (waited < timeout_ms) : (waited += 25) {\n        if (client.isClosed()) return true;\n        io.sleep(.fromMilliseconds(25), .awake) catch {};\n    }\n    return client.isClosed();\n}\n\nfn waitForConnected(\n    io: std.Io,\n    client: *nats.Client,\n    timeout_ms: u32,\n) bool {\n    var waited: u32 = 0;\n    while (waited < timeout_ms) : (waited += 25) {\n        if (client.isConnected()) return true;\n        io.sleep(.fromMilliseconds(25), .awake) catch {};\n    }\n    return client.isConnected();\n}\n\nfn waitForReconnects(\n    io: std.Io,\n    client: *nats.Client,\n    want: u32,\n    timeout_ms: u32,\n) bool {\n    var waited: u32 = 0;\n    while (waited < timeout_ms) : (waited += 25) {\n        if (client.stats().reconnects >= want) return true;\n        io.sleep(.fromMilliseconds(25), .awake) catch {};\n    }\n    return client.stats().reconnects >= want;\n}\n\npub fn runAll(allocator: std.mem.Allocator, manager: *ServerManager) void {\n    testAutoReconnectBasic(allocator, manager);\n    testSubscriptionRestored(allocator, manager);\n    testMultipleSubscriptionsRestored(allocator, manager);\n    testReconnectMaxAttempts(allocator, manager);\n    testReconnectDisabled(allocator, manager);\n    testPendingBufferFlush(allocator, manager);\n    testReconnectStatsIncrement(allocator, manager);\n    testReconnectWithQueueGroup(allocator, manager);\n    testMultiClientReconnect(allocator, manager);\n    testReconnectPreservesSid(allocator, manager);\n    testReconnectWildcardSub(allocator, manager);\n    testPublishDuringReconnect(allocator, manager);\n    testReconnectBackoff(allocator, manager);\n    testCustomReconnectDelay(allocator, manager);\n    testHealthCheckReconnect(allocator, manager);\n\n    testFailoverToSecondServer(allocator, manager);\n    testFailoverRoundRobin(allocator, manager);\n    testAllServersDownThenRecover(allocator, manager);\n    testServerCooldownRespected(allocator, manager);\n\n    testMultipleSubsActivelyReceiving(allocator, manager);\n    testHighVolumePendingBuffer(allocator, manager);\n    testQueueGroupMultiClientReconnect(allocator, manager);\n\n    testRapidServerRestarts(allocator, manager);\n    testMultipleReconnectionCycles(allocator, manager);\n    testLongDisconnectionRecovery(allocator, manager);\n}\n\nfn testAutoReconnectBasic(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n        .reconnect_wait_max_ms = 1000,\n    }) catch {\n        reportResult(\"reconnect_basic\", false, \"initial connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"reconnect_basic\", false, \"not connected initially\");\n        return;\n    }\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_basic\", false, \"server restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"test.reconnect\", \"ping\") catch {\n        reportResult(\"reconnect_basic\", false, \"publish after restart failed\");\n        return;\n    };\n\n    if (client.isConnected()) {\n        reportResult(\"reconnect_basic\", true, \"\");\n    } else {\n        reportResult(\"reconnect_basic\", false, \"not reconnected\");\n    }\n}\n\nfn testSubscriptionRestored(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"reconnect_sub_restored\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"test.restore.>\") catch {\n        reportResult(\"reconnect_sub_restored\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_sub_restored\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"test.restore.msg\", \"after-reconnect\") catch {\n        reportResult(\"reconnect_sub_restored\", false, \"publish failed\");\n        return;\n    };\n    client.flushBuffer() catch {};\n\n    if (sub.nextMsgTimeout(500) catch null) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"after-reconnect\")) {\n            reportResult(\"reconnect_sub_restored\", true, \"\");\n        } else {\n            reportResult(\"reconnect_sub_restored\", false, \"wrong message data\");\n        }\n    } else {\n        reportResult(\"reconnect_sub_restored\", false, \"no message received\");\n    }\n}\n\nfn testMultipleSubscriptionsRestored(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"reconnect_multi_sub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub1 = client.subscribeSync(\"multi.sub.one\") catch {\n        reportResult(\"reconnect_multi_sub\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    var sub2 = client.subscribeSync(\"multi.sub.two\") catch {\n        reportResult(\"reconnect_multi_sub\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    var sub3 = client.subscribeSync(\"multi.sub.three\") catch {\n        reportResult(\"reconnect_multi_sub\", false, \"sub3 failed\");\n        return;\n    };\n    defer sub3.deinit();\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_multi_sub\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"multi.sub.one\", \"msg1\") catch {};\n    client.publish(\"multi.sub.two\", \"msg2\") catch {};\n    client.publish(\"multi.sub.three\", \"msg3\") catch {};\n\n    var received: u8 = 0;\n\n    if (sub1.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n        received += 1;\n    }\n\n    if (sub2.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n        received += 1;\n    }\n\n    if (sub3.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n        received += 1;\n    }\n\n    if (received == 3) {\n        reportResult(\"reconnect_multi_sub\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const details = std.fmt.bufPrint(\n            &buf,\n            \"only {d}/3 received\",\n            .{received},\n        ) catch \"count error\";\n        reportResult(\"reconnect_multi_sub\", false, details);\n    }\n}\n\nfn testReconnectMaxAttempts(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, reconnect_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const server_idx = manager.count();\n    _ = manager.startServer(allocator, io.io(), .{\n        .port = reconnect_port,\n    }) catch {\n        reportResult(\"reconnect_max_attempts\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 2,\n        .reconnect_wait_ms = 50,\n        .reconnect_wait_max_ms = 100,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 1,\n    }) catch {\n        manager.stopServer(server_idx, io.io());\n        reportResult(\"reconnect_max_attempts\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    manager.stopServer(server_idx, io.io());\n    client.forceReconnect() catch {};\n    const is_disconnected = waitForClosed(\n        io.io(),\n        client,\n        1500,\n    );\n\n    if (is_disconnected) {\n        reportResult(\"reconnect_max_attempts\", true, \"\");\n    } else {\n        reportResult(\"reconnect_max_attempts\", false, \"should be disconnected\");\n    }\n}\n\nfn testReconnectDisabled(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = false,\n        // .ping_interval_ms = 100,\n        .max_pings_outstanding = 1,\n    }) catch {\n        reportResult(\"reconnect_disabled\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    manager.stopAll(io.io());\n    client.forceReconnect() catch {};\n    _ = waitForClosed(io.io(), client, 500);\n\n    const flush_result = client.flush(200_000_000);\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_disabled\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(300), .awake) catch {};\n\n    if (flush_result) |_| {\n        reportResult(\"reconnect_disabled\", false, \"flush should fail\");\n    } else |_| {\n        reportResult(\"reconnect_disabled\", true, \"\");\n    }\n}\n\nfn testPendingBufferFlush(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n        .pending_buffer_size = 1024 * 1024,\n    }) catch {\n        reportResult(\"pending_buffer_flush\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"pending.test\") catch {\n        reportResult(\"pending_buffer_flush\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    client.publish(\"pending.test\", \"buffered-message\") catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"pending_buffer_flush\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    if (sub.tryNextMsg()) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"buffered-message\")) {\n            reportResult(\"pending_buffer_flush\", true, \"\");\n        } else {\n            reportResult(\"pending_buffer_flush\", false, \"wrong data\");\n        }\n    } else {\n        reportResult(\"pending_buffer_flush\", false, \"buffered message missing\");\n    }\n}\n\nfn testPublishDuringReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 200,\n        .pending_buffer_size = 1024 * 1024,\n    }) catch {\n        reportResult(\"publish_during_reconnect\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"during.reconnect\") catch {\n        reportResult(\"publish_during_reconnect\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    manager.stopServer(0, io.io());\n\n    var published: u8 = 0;\n    var i: u8 = 0;\n    while (i < 5) : (i += 1) {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(&buf, \"msg-{d}\", .{i}) catch continue;\n        if (client.publish(\"during.reconnect\", msg)) |_| {\n            published += 1;\n        } else |_| {}\n    }\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"publish_during_reconnect\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    var received: u8 = 0;\n    while (received < 10) {\n        if (sub.tryNextMsg()) |msg| {\n            msg.deinit();\n            received += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (published > 0 or received > 0) {\n        reportResult(\"publish_during_reconnect\", true, \"\");\n    } else {\n        reportResult(\"publish_during_reconnect\", false, \"no messages\");\n    }\n}\n\nfn testReconnectStatsIncrement(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 30,\n        .reconnect_wait_ms = 100,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 1,\n    }) catch {\n        reportResult(\"reconnect_stats\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const initial_reconnects = client.stats().reconnects;\n\n    manager.stopAll(io.io());\n    client.forceReconnect() catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_stats\", false, \"restart failed\");\n        return;\n    };\n\n    if (!waitForReconnects(\n        io.io(),\n        client,\n        initial_reconnects + 1,\n        3000,\n    )) {\n        reportResult(\"reconnect_stats\", false, \"counter not incremented\");\n        return;\n    }\n\n    client.publish(\"stats.test\", \"trigger\") catch {};\n\n    const final_reconnects = client.stats().reconnects;\n\n    if (final_reconnects > initial_reconnects) {\n        reportResult(\"reconnect_stats\", true, \"\");\n    } else {\n        reportResult(\"reconnect_stats\", false, \"counter not incremented\");\n    }\n}\n\nfn testReconnectWithQueueGroup(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"reconnect_queue_group\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.queueSubscribeSync(\"queue.test\", \"workers\") catch {\n        reportResult(\"reconnect_queue_group\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_queue_group\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"queue.test\", \"queue-message\") catch {\n        reportResult(\"reconnect_queue_group\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n        reportResult(\"reconnect_queue_group\", true, \"\");\n    } else {\n        reportResult(\"reconnect_queue_group\", false, \"no message\");\n    }\n}\n\nfn testMultiClientReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    const client1 = nats.Client.connect(allocator, io1.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"multi_client_reconnect\", false, \"client1 connect failed\");\n        return;\n    };\n    defer client1.deinit();\n\n    const client2 = nats.Client.connect(allocator, io2.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"multi_client_reconnect\", false, \"client2 connect failed\");\n        return;\n    };\n    defer client2.deinit();\n\n    manager.stopServer(0, io1.io());\n    io1.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io1.io(), .{ .port = test_port }) catch {\n        reportResult(\"multi_client_reconnect\", false, \"restart failed\");\n        return;\n    };\n\n    io1.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    var failed = false;\n    client1.publish(\"multi.test\", \"from-client1\") catch {\n        failed = true;\n    };\n    client2.publish(\"multi.test\", \"from-client2\") catch {\n        failed = true;\n    };\n\n    if (failed) {\n        reportResult(\"multi_client_reconnect\", false, \"publish failed\");\n    } else {\n        reportResult(\"multi_client_reconnect\", true, \"\");\n    }\n}\n\nfn testReconnectPreservesSid(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"reconnect_preserves_sid\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"sid.test\") catch {\n        reportResult(\"reconnect_preserves_sid\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const original_sid = sub.sid;\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_preserves_sid\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    if (sub.sid == original_sid) {\n        reportResult(\"reconnect_preserves_sid\", true, \"\");\n    } else {\n        reportResult(\"reconnect_preserves_sid\", false, \"SID changed\");\n    }\n}\n\nfn testReconnectWildcardSub(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"reconnect_wildcard\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"wild.*.test.>\") catch {\n        reportResult(\"reconnect_wildcard\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_wildcard\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"wild.card.test.subject\", \"wildcard-msg\") catch {\n        reportResult(\"reconnect_wildcard\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n        reportResult(\"reconnect_wildcard\", true, \"\");\n    } else {\n        reportResult(\"reconnect_wildcard\", false, \"no message\");\n    }\n}\n\nfn testReconnectBackoff(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 5,\n        .reconnect_wait_ms = 100,\n        .reconnect_wait_max_ms = 500,\n        .reconnect_jitter_percent = 0,\n    }) catch {\n        reportResult(\"reconnect_backoff\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    manager.stopServer(0, io.io());\n\n    io.io().sleep(.fromMilliseconds(2000), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"reconnect_backoff\", false, \"restart failed\");\n        return;\n    };\n\n    reportResult(\"reconnect_backoff\", true, \"\");\n}\n\n/// Track calls for custom delay callback test (atomic for cross-thread visibility)\nvar custom_delay_calls: std.atomic.Value(u32) = std.atomic.Value(u32).init(0);\n\nfn customDelayCallback(attempt: u32) u32 {\n    _ = custom_delay_calls.fetchAdd(1, .seq_cst);\n    // Simple linear backoff: 50ms per attempt\n    return attempt * 50;\n}\n\nfn testCustomReconnectDelay(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, reconnect_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    // Reset call counter\n    custom_delay_calls.store(0, .seq_cst);\n\n    // Start our own dedicated server for this test\n    const server = manager.startServer(allocator, io.io(), .{\n        .port = reconnect_port,\n    }) catch {\n        reportResult(\"custom_reconnect_delay\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .custom_reconnect_delay = customDelayCallback,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 1,\n    }) catch {\n        server.stop(io.io());\n        reportResult(\"custom_reconnect_delay\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Stop the specific server we started to trigger reconnect attempts\n    server.stop(io.io());\n\n    // Wait for disconnect detection and multiple reconnect attempts\n    // Callback returns attempt*50ms, so attempts 2,3,4 = 100+150+200 = 450ms\n    io.io().sleep(.fromMilliseconds(1500), .awake) catch {};\n\n    // Restart server on same port\n    const server2 = manager.startServer(allocator, io.io(), .{\n        .port = reconnect_port,\n    }) catch {\n        reportResult(\"custom_reconnect_delay\", false, \"restart failed\");\n        return;\n    };\n    defer server2.stop(io.io());\n\n    // Wait for reconnection\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    // Callback should have been called at least once (for attempt 2+)\n    const calls = custom_delay_calls.load(.seq_cst);\n    if (calls >= 1) {\n        reportResult(\"custom_reconnect_delay\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"calls={d}\", .{\n            calls,\n        }) catch \"e\";\n        reportResult(\"custom_reconnect_delay\", false, detail);\n    }\n}\n\nfn testHealthCheckReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n        .ping_interval_ms = 500,\n        .max_pings_outstanding = 2,\n    }) catch {\n        reportResult(\"health_check_reconnect\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"health_check_reconnect\", false, \"not connected\");\n        return;\n    }\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"health_check_reconnect\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"health.test\", \"ping\") catch {\n        reportResult(\"health_check_reconnect\", false, \"publish failed\");\n        return;\n    };\n\n    reportResult(\"health_check_reconnect\", true, \"\");\n}\n\nfn testFailoverToSecondServer(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf1: [64]u8 = undefined;\n    var url_buf2: [64]u8 = undefined;\n    const url1 = formatUrl(&url_buf1, failover_port_1);\n    const url2 = formatUrl(&url_buf2, failover_port_2);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n\n    const server1 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_1,\n    }) catch {\n        reportResult(\"failover_to_second\", false, \"server1 start failed\");\n        return;\n    };\n\n    const server2 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_2,\n    }) catch {\n        server1.stop(io.io());\n        reportResult(\"failover_to_second\", false, \"server2 start failed\");\n        return;\n    };\n    defer server2.stop(io.io());\n\n    const client = nats.Client.connect(allocator, io.io(), url1, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 2,\n    }) catch {\n        server1.stop(io.io());\n        reportResult(\"failover_to_second\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.server_pool.addServer(url2) catch {\n        server1.stop(io.io());\n        reportResult(\"failover_to_second\", false, \"add server failed\");\n        return;\n    };\n\n    var sub = client.subscribeSync(\"failover.test\") catch {\n        server1.stop(io.io());\n        reportResult(\"failover_to_second\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"failover.test\", \"before\") catch {};\n\n    if (sub.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n    } else {\n        server1.stop(io.io());\n        reportResult(\"failover_to_second\", false, \"no msg before failover\");\n        return;\n    }\n\n    server1.stop(io.io());\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"failover.test\", \"after\") catch {\n        reportResult(\"failover_to_second\", false, \"publish after failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |msg| {\n        msg.deinit();\n        reportResult(\"failover_to_second\", true, \"\");\n    } else {\n        reportResult(\"failover_to_second\", false, \"no msg after failover\");\n    }\n}\n\nfn testFailoverRoundRobin(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf1: [64]u8 = undefined;\n    var url_buf2: [64]u8 = undefined;\n    var url_buf3: [64]u8 = undefined;\n    const url1 = formatUrl(&url_buf1, failover_port_3);\n    const url2 = formatUrl(&url_buf2, failover_port_4);\n    const url3 = formatUrl(&url_buf3, failover_port_5);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n\n    const server1 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_3,\n    }) catch {\n        reportResult(\"failover_round_robin\", false, \"server1 start failed\");\n        return;\n    };\n\n    const server2 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_4,\n    }) catch {\n        server1.stop(io.io());\n        reportResult(\"failover_round_robin\", false, \"server2 start failed\");\n        return;\n    };\n\n    const server3 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_5,\n    }) catch {\n        server1.stop(io.io());\n        server2.stop(io.io());\n        reportResult(\"failover_round_robin\", false, \"server3 start failed\");\n        return;\n    };\n    defer server3.stop(io.io());\n\n    const client = nats.Client.connect(allocator, io.io(), url1, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 5,\n        .reconnect_wait_ms = 100,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 2,\n    }) catch {\n        server1.stop(io.io());\n        server2.stop(io.io());\n        reportResult(\"failover_round_robin\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.server_pool.addServer(url2) catch {};\n    client.server_pool.addServer(url3) catch {};\n\n    server1.stop(io.io());\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"roundrobin.test\", \"msg1\") catch {\n        server2.stop(io.io());\n        reportResult(\"failover_round_robin\", false, \"publish 1 failed\");\n        return;\n    };\n\n    server2.stop(io.io());\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"roundrobin.test\", \"msg2\") catch {\n        reportResult(\"failover_round_robin\", false, \"publish 2 failed\");\n        return;\n    };\n\n    reportResult(\"failover_round_robin\", true, \"\");\n}\n\nfn testAllServersDownThenRecover(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf1: [64]u8 = undefined;\n    var url_buf2: [64]u8 = undefined;\n    const url1 = formatUrl(&url_buf1, failover_port_6);\n    const url2 = formatUrl(&url_buf2, failover_port_7);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n\n    const server1 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_6,\n    }) catch {\n        reportResult(\"all_servers_down_recover\", false, \"server1 start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url1, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 20,\n        .reconnect_wait_ms = 200,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 2,\n    }) catch {\n        server1.stop(io.io());\n        reportResult(\"all_servers_down_recover\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.server_pool.addServer(url2) catch {};\n\n    var sub = client.subscribeSync(\"recover.test\") catch {\n        server1.stop(io.io());\n        reportResult(\"all_servers_down_recover\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    server1.stop(io.io());\n\n    io.io().sleep(.fromMilliseconds(800), .awake) catch {};\n\n    const server2 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_7,\n    }) catch {\n        reportResult(\"all_servers_down_recover\", false, \"server2 start failed\");\n        return;\n    };\n    defer server2.stop(io.io());\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"recover.test\", \"recovered\") catch {\n        reportResult(\"all_servers_down_recover\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |msg| {\n        msg.deinit();\n        reportResult(\"all_servers_down_recover\", true, \"\");\n    } else {\n        reportResult(\"all_servers_down_recover\", false, \"no message received\");\n    }\n}\n\nfn testServerCooldownRespected(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf1: [64]u8 = undefined;\n    var url_buf2: [64]u8 = undefined;\n    const url1 = formatUrl(&url_buf1, failover_port_8);\n    const url2 = formatUrl(&url_buf2, failover_port_9);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n\n    const server2 = manager.startServer(allocator, io.io(), .{\n        .port = failover_port_9,\n    }) catch {\n        reportResult(\"server_cooldown\", false, \"server2 start failed\");\n        return;\n    };\n    defer server2.stop(io.io());\n\n    const client = nats.Client.connect(allocator, io.io(), url2, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"server_cooldown\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    client.server_pool.addServer(url1) catch {};\n\n    client.publish(\"cooldown.test\", \"msg\") catch {\n        reportResult(\"server_cooldown\", false, \"publish failed\");\n        return;\n    };\n\n    if (client.server_pool.serverCount() == 2) {\n        reportResult(\"server_cooldown\", true, \"\");\n    } else {\n        reportResult(\"server_cooldown\", false, \"wrong server count\");\n    }\n}\n\nfn testMultipleSubsActivelyReceiving(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"multi_subs_receiving\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"multi_subs_receiving\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub1 = client.subscribeSync(\"active.sub.one\") catch {\n        reportResult(\"multi_subs_receiving\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    var sub2 = client.subscribeSync(\"active.sub.two\") catch {\n        reportResult(\"multi_subs_receiving\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    var sub3 = client.subscribeSync(\"active.sub.three\") catch {\n        reportResult(\"multi_subs_receiving\", false, \"sub3 failed\");\n        return;\n    };\n    defer sub3.deinit();\n\n    var sub4 = client.subscribeSync(\"active.sub.four\") catch {\n        reportResult(\"multi_subs_receiving\", false, \"sub4 failed\");\n        return;\n    };\n    defer sub4.deinit();\n\n    var sub5 = client.subscribeSync(\"active.sub.five\") catch {\n        reportResult(\"multi_subs_receiving\", false, \"sub5 failed\");\n        return;\n    };\n    defer sub5.deinit();\n\n    client.publish(\"active.sub.one\", \"pre1\") catch {};\n    client.publish(\"active.sub.two\", \"pre2\") catch {};\n    client.publish(\"active.sub.three\", \"pre3\") catch {};\n    client.publish(\"active.sub.four\", \"pre4\") catch {};\n    client.publish(\"active.sub.five\", \"pre5\") catch {};\n\n    var pre_received: u8 = 0;\n    if (sub1.nextMsgTimeout(200) catch null) |m| {\n        m.deinit();\n        pre_received += 1;\n    }\n    if (sub2.nextMsgTimeout(200) catch null) |m| {\n        m.deinit();\n        pre_received += 1;\n    }\n    if (sub3.nextMsgTimeout(200) catch null) |m| {\n        m.deinit();\n        pre_received += 1;\n    }\n    if (sub4.nextMsgTimeout(200) catch null) |m| {\n        m.deinit();\n        pre_received += 1;\n    }\n    if (sub5.nextMsgTimeout(200) catch null) |m| {\n        m.deinit();\n        pre_received += 1;\n    }\n\n    if (pre_received != 5) {\n        var buf: [32]u8 = undefined;\n        const details = std.fmt.bufPrint(\n            &buf,\n            \"pre: {d}/5\",\n            .{pre_received},\n        ) catch \"pre error\";\n        reportResult(\"multi_subs_receiving\", false, details);\n        return;\n    }\n\n    manager.stopServer(0, io.io());\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"multi_subs_receiving\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"active.sub.one\", \"post1\") catch {};\n    client.publish(\"active.sub.two\", \"post2\") catch {};\n    client.publish(\"active.sub.three\", \"post3\") catch {};\n    client.publish(\"active.sub.four\", \"post4\") catch {};\n    client.publish(\"active.sub.five\", \"post5\") catch {};\n\n    var post_received: u8 = 0;\n    if (sub1.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        post_received += 1;\n    }\n    if (sub2.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        post_received += 1;\n    }\n    if (sub3.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        post_received += 1;\n    }\n    if (sub4.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        post_received += 1;\n    }\n    if (sub5.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        post_received += 1;\n    }\n\n    if (post_received == 5) {\n        reportResult(\"multi_subs_receiving\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const details = std.fmt.bufPrint(\n            &buf,\n            \"post: {d}/5\",\n            .{post_received},\n        ) catch \"post error\";\n        reportResult(\"multi_subs_receiving\", false, details);\n    }\n}\n\nfn testHighVolumePendingBuffer(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"high_volume_buffer\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n        .pending_buffer_size = 64 * 1024,\n    }) catch {\n        reportResult(\"high_volume_buffer\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"buffer.test\") catch {\n        reportResult(\"high_volume_buffer\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    var published_before: u32 = 0;\n    var i: u32 = 0;\n    while (i < 50) : (i += 1) {\n        client.publish(\"buffer.test\", \"pre\") catch continue;\n        published_before += 1;\n    }\n\n    io.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    var received_before: u32 = 0;\n    while (received_before < 100) {\n        if (sub.nextMsgTimeout(100) catch null) |msg| {\n            msg.deinit();\n            received_before += 1;\n        } else {\n            break;\n        }\n    }\n\n    manager.stopAll(io.io());\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    var published_during: u32 = 0;\n    i = 0;\n    while (i < 50) : (i += 1) {\n        client.publish(\"buffer.test\", \"buffered\") catch continue;\n        published_during += 1;\n    }\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"high_volume_buffer\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    var received_after: u32 = 0;\n    while (received_after < 100) {\n        if (sub.nextMsgTimeout(200) catch null) |msg| {\n            msg.deinit();\n            received_after += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (published_before > 0 and received_before > 0) {\n        reportResult(\"high_volume_buffer\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const details = std.fmt.bufPrint(\n            &buf,\n            \"pub_before={d} recv_before={d}\",\n            .{ published_before, received_before },\n        ) catch \"error\";\n        reportResult(\"high_volume_buffer\", false, details);\n    }\n}\n\nfn testQueueGroupMultiClientReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io1 = utils.newIo(allocator);\n    defer io1.deinit();\n    const io2 = utils.newIo(allocator);\n    defer io2.deinit();\n\n    manager.stopAll(io1.io());\n    _ = manager.startServer(allocator, io1.io(), .{ .port = test_port }) catch {\n        reportResult(\"queue_group_multi_client\", false, \"server start failed\");\n        return;\n    };\n\n    const client1 = nats.Client.connect(allocator, io1.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\n            \"queue_group_multi_client\",\n            false,\n            \"client1 connect failed\",\n        );\n        return;\n    };\n    defer client1.deinit();\n\n    const client2 = nats.Client.connect(allocator, io2.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\n            \"queue_group_multi_client\",\n            false,\n            \"client2 connect failed\",\n        );\n        return;\n    };\n    defer client2.deinit();\n\n    var sub1 = client1.queueSubscribeSync(\n        \"qgroup.test\",\n        \"workers\",\n    ) catch {\n        reportResult(\"queue_group_multi_client\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    var sub2 = client2.queueSubscribeSync(\n        \"qgroup.test\",\n        \"workers\",\n    ) catch {\n        reportResult(\"queue_group_multi_client\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    var i: u8 = 0;\n    while (i < 20) : (i += 1) {\n        client1.publish(\"qgroup.test\", \"msg\") catch {};\n    }\n\n    io1.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    var c1_before: u8 = 0;\n    var c2_before: u8 = 0;\n    while (c1_before + c2_before < 30) {\n        if (sub1.tryNextMsg()) |m| {\n            m.deinit();\n            c1_before += 1;\n        } else if (sub2.tryNextMsg()) |m| {\n            m.deinit();\n            c2_before += 1;\n        } else {\n            break;\n        }\n    }\n\n    manager.stopServer(0, io1.io());\n    io1.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io1.io(), .{ .port = test_port }) catch {\n        reportResult(\"queue_group_multi_client\", false, \"restart failed\");\n        return;\n    };\n\n    io1.io().sleep(.fromMilliseconds(500), .awake) catch {};\n\n    i = 0;\n    while (i < 20) : (i += 1) {\n        client1.publish(\"qgroup.test\", \"msg\") catch {};\n    }\n\n    io1.io().sleep(.fromMilliseconds(200), .awake) catch {};\n\n    var c1_after: u8 = 0;\n    var c2_after: u8 = 0;\n    while (c1_after + c2_after < 30) {\n        if (sub1.tryNextMsg()) |m| {\n            m.deinit();\n            c1_after += 1;\n        } else if (sub2.tryNextMsg()) |m| {\n            m.deinit();\n            c2_after += 1;\n        } else {\n            break;\n        }\n    }\n\n    const total_before = c1_before + c2_before;\n    const total_after = c1_after + c2_after;\n\n    if (total_before > 0 and total_after > 0) {\n        reportResult(\"queue_group_multi_client\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const details = std.fmt.bufPrint(\n            &buf,\n            \"before={d} after={d}\",\n            .{ total_before, total_after },\n        ) catch \"error\";\n        reportResult(\"queue_group_multi_client\", false, details);\n    }\n}\n\nfn testRapidServerRestarts(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"rapid_restarts\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 20,\n        .reconnect_wait_ms = 100,\n        .ping_interval_ms = 100,\n        .max_pings_outstanding = 2,\n    }) catch {\n        reportResult(\"rapid_restarts\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"rapid.test\") catch {\n        reportResult(\"rapid_restarts\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    var cycle: u8 = 0;\n    while (cycle < 3) : (cycle += 1) {\n        manager.stopAll(io.io());\n        client.forceReconnect() catch {};\n\n        _ = manager.startServer(allocator, io.io(), .{\n            .port = test_port,\n        }) catch {\n            reportResult(\"rapid_restarts\", false, \"restart failed\");\n            return;\n        };\n\n        const want_reconnects: u32 = @as(u32, cycle) + 1;\n        if (!waitForReconnects(\n            io.io(),\n            client,\n            want_reconnects,\n            3000,\n        ) or !waitForConnected(io.io(), client, 1000)) {\n            var buf: [32]u8 = undefined;\n            const details = std.fmt.bufPrint(\n                &buf,\n                \"reconnect {d} timeout\",\n                .{cycle},\n            ) catch \"reconnect timeout\";\n            reportResult(\"rapid_restarts\", false, details);\n            return;\n        }\n    }\n\n    client.publish(\"rapid.test\", \"survived\") catch {\n        reportResult(\"rapid_restarts\", false, \"final publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(500) catch null) |msg| {\n        msg.deinit();\n        const stats = client.stats();\n        if (stats.reconnects >= 3) {\n            reportResult(\"rapid_restarts\", true, \"\");\n        } else {\n            var buf: [32]u8 = undefined;\n            const details = std.fmt.bufPrint(\n                &buf,\n                \"reconnects={d}\",\n                .{stats.reconnects},\n            ) catch \"error\";\n            reportResult(\"rapid_restarts\", false, details);\n        }\n    } else {\n        reportResult(\"rapid_restarts\", false, \"no final message\");\n    }\n}\n\nfn testMultipleReconnectionCycles(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"multiple_cycles\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 30,\n        .reconnect_wait_ms = 100,\n    }) catch {\n        reportResult(\"multiple_cycles\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"cycles.test\") catch {\n        reportResult(\"multiple_cycles\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    var cycle: u8 = 0;\n    while (cycle < 3) : (cycle += 1) {\n        manager.stopAll(io.io());\n        client.forceReconnect() catch {};\n\n        _ = manager.startServer(allocator, io.io(), .{\n            .port = test_port,\n        }) catch {\n            var buf: [32]u8 = undefined;\n            const details = std.fmt.bufPrint(\n                &buf,\n                \"restart {d} failed\",\n                .{cycle},\n            ) catch \"restart error\";\n            reportResult(\"multiple_cycles\", false, details);\n            return;\n        };\n\n        const want_reconnects: u32 = @as(u32, cycle) + 1;\n        if (!waitForReconnects(\n            io.io(),\n            client,\n            want_reconnects,\n            3000,\n        ) or !waitForConnected(io.io(), client, 1000)) {\n            var buf: [32]u8 = undefined;\n            const details = std.fmt.bufPrint(\n                &buf,\n                \"reconnect {d} timeout\",\n                .{cycle},\n            ) catch \"reconnect timeout\";\n            reportResult(\"multiple_cycles\", false, details);\n            return;\n        }\n\n        var msg_buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &msg_buf,\n            \"cycle-{d}\",\n            .{cycle},\n        ) catch \"msg\";\n\n        client.publish(\"cycles.test\", msg) catch {\n            var buf: [32]u8 = undefined;\n            const details = std.fmt.bufPrint(\n                &buf,\n                \"publish {d} failed\",\n                .{cycle},\n            ) catch \"pub error\";\n            reportResult(\"multiple_cycles\", false, details);\n            return;\n        };\n\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n        } else {\n            var buf: [32]u8 = undefined;\n            const details = std.fmt.bufPrint(\n                &buf,\n                \"no msg cycle {d}\",\n                .{cycle},\n            ) catch \"recv error\";\n            reportResult(\"multiple_cycles\", false, details);\n            return;\n        }\n    }\n\n    const stats = client.stats();\n    if (stats.reconnects == 3) {\n        reportResult(\"multiple_cycles\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const details = std.fmt.bufPrint(\n            &buf,\n            \"reconnects={d} want 3\",\n            .{stats.reconnects},\n        ) catch \"error\";\n        reportResult(\"multiple_cycles\", false, details);\n    }\n}\n\nfn testLongDisconnectionRecovery(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    manager.stopAll(io.io());\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"long_disconnection\", false, \"server start failed\");\n        return;\n    };\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 30,\n        .reconnect_wait_ms = 200,\n        .reconnect_wait_max_ms = 500,\n    }) catch {\n        reportResult(\"long_disconnection\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var sub = client.subscribeSync(\"long.test\") catch {\n        reportResult(\"long_disconnection\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"long.test\", \"before\") catch {};\n\n    if (sub.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n    } else {\n        reportResult(\"long_disconnection\", false, \"no msg before\");\n        return;\n    }\n\n    manager.stopAll(io.io());\n\n    io.io().sleep(.fromMilliseconds(3000), .awake) catch {};\n\n    _ = manager.startServer(allocator, io.io(), .{ .port = test_port }) catch {\n        reportResult(\"long_disconnection\", false, \"restart failed\");\n        return;\n    };\n\n    io.io().sleep(.fromMilliseconds(1000), .awake) catch {};\n\n    client.publish(\"long.test\", \"after-long-gap\") catch {\n        reportResult(\"long_disconnection\", false, \"publish after failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(1000) catch null) |msg| {\n        msg.deinit();\n        reportResult(\"long_disconnection\", true, \"\");\n    } else {\n        reportResult(\"long_disconnection\", false, \"no msg after long gap\");\n    }\n}\n"
  },
  {
    "path": "src/testing/client/request_reply.zig",
    "content": "//! Request-Reply Tests for NATS Client\n//!\n//! Tests for request-reply pattern.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testRequestMethod(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_method\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const result = client.request(\n        \"nonexistent.service.test\",\n        \"ping\",\n        50,\n    ) catch {\n        reportResult(\"request_method\", false, \"request error\");\n        return;\n    };\n\n    if (result) |msg| {\n        msg.deinit();\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"request_method\", true, \"\");\n    } else {\n        reportResult(\"request_method\", false, \"disconnected after request\");\n    }\n}\n\npub fn testRequestReturns(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_returns\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const start = std.Io.Timestamp.now(io.io(), .awake);\n\n    const result = client.request(\n        \"nonexistent.service.test2\",\n        \"data\",\n        100,\n    ) catch {\n        reportResult(\"request_returns\", false, \"request error\");\n        return;\n    };\n\n    const end = std.Io.Timestamp.now(io.io(), .awake);\n    const elapsed = start.durationTo(end);\n    const elapsed_ns: u64 = @intCast(elapsed.nanoseconds);\n    const elapsed_ms = elapsed_ns / std.time.ns_per_ms;\n\n    if (result) |msg| {\n        msg.deinit();\n    }\n\n    if (elapsed_ms < 5000) {\n        reportResult(\"request_returns\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"took too long: {d}ms\",\n            .{elapsed_ms},\n        ) catch \"timing error\";\n        reportResult(\"request_returns\", false, msg);\n    }\n}\n\npub fn testReplyToPreserved(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"reply_preserved\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"reply.test\") catch {\n        reportResult(\"reply_preserved\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publishRequest(\"reply.test\", \"my.reply.inbox\", \"data\") catch {\n        reportResult(\"reply_preserved\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |msg| {\n        if (msg.reply_to) |rt| {\n            if (std.mem.eql(u8, rt, \"my.reply.inbox\")) {\n                reportResult(\"reply_preserved\", true, \"\");\n                return;\n            }\n        }\n    } else |_| {}\n\n    reportResult(\"reply_preserved\", false, \"reply_to not preserved\");\n}\n\npub fn testRequestReplySuccess(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_r = utils.newIo(allocator);\n    defer io_r.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_r.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_reply_success\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_req.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_reply_success\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const sub = responder.subscribeSync(\"test.service\") catch {\n        reportResult(\"request_reply_success\", false, \"responder sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_r.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const Handler = struct {\n        fn handle(\n            r: *nats.Client,\n            s: *nats.Subscription,\n        ) void {\n            if (s.nextMsgTimeout(1000) catch null) |req| {\n                defer req.deinit();\n                if (req.reply_to) |reply_inbox| {\n                    r.publish(reply_inbox, \"pong\") catch {};\n                }\n            }\n        }\n    };\n\n    var handler = io_r.io().async(Handler.handle, .{\n        responder,\n        sub,\n    });\n    defer _ = handler.cancel(io_r.io());\n\n    const reply = requester.request(\n        \"test.service\",\n        \"ping\",\n        2000,\n    ) catch {\n        reportResult(\"request_reply_success\", false, \"request failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"pong\")) {\n            reportResult(\"request_reply_success\", true, \"\");\n            return;\n        }\n    }\n\n    reportResult(\"request_reply_success\", false, \"no reply or wrong data\");\n}\n\npub fn testCrossClientRequestReply(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_a = utils.newIo(allocator);\n    defer io_a.deinit();\n    const client_a = nats.Client.connect(\n        allocator,\n        io_a.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"cross_client_reqrep\", false, \"A connect failed\");\n        return;\n    };\n    defer client_a.deinit();\n\n    const io_b = utils.newIo(allocator);\n    defer io_b.deinit();\n    const client_b = nats.Client.connect(\n        allocator,\n        io_b.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"cross_client_reqrep\", false, \"B connect failed\");\n        return;\n    };\n    defer client_b.deinit();\n\n    const sub = client_b.subscribeSync(\"cross.service\") catch {\n        reportResult(\"cross_client_reqrep\", false, \"B sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_b.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const Handler = struct {\n        fn handle(\n            b: *nats.Client,\n            s: *nats.Subscription,\n        ) void {\n            if (s.nextMsgTimeout(2000) catch null) |req| {\n                defer req.deinit();\n                if (req.reply_to) |inbox| {\n                    b.publish(inbox, \"response-from-B\") catch {};\n                }\n            }\n        }\n    };\n\n    var handler = io_b.io().async(Handler.handle, .{\n        client_b,\n        sub,\n    });\n    defer _ = handler.cancel(io_b.io());\n\n    const reply = client_a.request(\n        \"cross.service\",\n        \"request-from-A\",\n        3000,\n    ) catch {\n        reportResult(\"cross_client_reqrep\", false, \"request failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        if (std.mem.eql(u8, msg.data, \"response-from-B\")) {\n            reportResult(\"cross_client_reqrep\", true, \"\");\n            return;\n        }\n    }\n\n    reportResult(\"cross_client_reqrep\", false, \"no reply\");\n}\n\npub fn testRequestTimeout(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .no_responders = false,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"request_timeout\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const start = std.Io.Timestamp.now(io.io(), .awake);\n\n    const result = client.request(\n        \"timeout.service.noexist\",\n        \"ping\",\n        200,\n    ) catch {\n        reportResult(\n            \"request_timeout\",\n            false,\n            \"request error\",\n        );\n        return;\n    };\n\n    const end = std.Io.Timestamp.now(io.io(), .awake);\n    const elapsed = start.durationTo(end);\n    const elapsed_ns: u64 = @intCast(elapsed.nanoseconds);\n    const elapsed_ms = elapsed_ns / std.time.ns_per_ms;\n\n    if (result) |msg| {\n        msg.deinit();\n        reportResult(\"request_timeout\", true, \"\");\n        return;\n    }\n\n    if (elapsed_ms < 5000) {\n        reportResult(\"request_timeout\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"took {d}ms\",\n            .{elapsed_ms},\n        ) catch \"e\";\n        reportResult(\"request_timeout\", false, detail);\n    }\n}\n\npub fn testRequestWithLargePayload(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_r = utils.newIo(allocator);\n    defer io_r.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_r.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_large_payload\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_req.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"request_large_payload\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const sub = responder.subscribeSync(\"large.service\") catch {\n        reportResult(\"request_large_payload\", false, \"responder sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_r.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const Handler = struct {\n        fn handle(\n            r: *nats.Client,\n            s: *nats.Subscription,\n        ) void {\n            if (s.nextMsgTimeout(2000) catch null) |req| {\n                defer req.deinit();\n                if (req.reply_to) |reply_inbox| {\n                    r.publish(reply_inbox, req.data) catch {};\n                }\n            }\n        }\n    };\n\n    var handler = io_r.io().async(Handler.handle, .{\n        responder,\n        sub,\n    });\n    defer _ = handler.cancel(io_r.io());\n\n    const payload = allocator.alloc(u8, 1024) catch {\n        reportResult(\"request_large_payload\", false, \"alloc failed\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'X');\n\n    const reply = requester.request(\n        \"large.service\",\n        payload,\n        3000,\n    ) catch {\n        reportResult(\"request_large_payload\", false, \"request failed\");\n        return;\n    };\n\n    if (reply) |msg| {\n        defer msg.deinit();\n        if (msg.data.len == 1024) {\n            reportResult(\"request_large_payload\", true, \"\");\n            return;\n        }\n    }\n\n    reportResult(\"request_large_payload\", false, \"no reply or wrong size\");\n}\n\npub fn testMultipleRequestsSequential(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_r = utils.newIo(allocator);\n    defer io_r.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_r.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_requests_seq\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    const io_req = utils.newIo(allocator);\n    defer io_req.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_req.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_requests_seq\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const sub = responder.subscribeSync(\"multi.service\") catch {\n        reportResult(\"multi_requests_seq\", false, \"responder sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_r.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const Handler = struct {\n        fn handle(\n            r: *nats.Client,\n            s: *nats.Subscription,\n        ) void {\n            for (0..5) |_| {\n                if (s.nextMsgTimeout(2000) catch null) |req| {\n                    defer req.deinit();\n                    if (req.reply_to) |reply_inbox| {\n                        r.publish(reply_inbox, \"response\") catch {};\n                    }\n                } else break;\n            }\n        }\n    };\n\n    var handler = io_r.io().async(Handler.handle, .{\n        responder,\n        sub,\n    });\n    defer _ = handler.cancel(io_r.io());\n\n    var success_count: u32 = 0;\n    for (0..5) |_| {\n        const reply = requester.request(\n            \"multi.service\",\n            \"request\",\n            2000,\n        ) catch continue;\n\n        if (reply) |msg| {\n            msg.deinit();\n            success_count += 1;\n        }\n    }\n\n    if (success_count >= 4) {\n        reportResult(\"multi_requests_seq\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/5\",\n            .{success_count},\n        ) catch \"e\";\n        reportResult(\"multi_requests_seq\", false, detail);\n    }\n}\n\n/// Helper: spawns a responder fiber that replies \"pong\" forever\n/// to the given subject. The caller cancels the returned future\n/// in defer to stop it.\nconst RespHandler = struct {\n    fn run(client: *nats.Client, sub: *nats.Subscription) void {\n        while (true) {\n            const req = sub.nextMsgTimeout(2000) catch return;\n            const m = req orelse return;\n            defer m.deinit();\n            const reply = m.reply_to orelse continue;\n            client.publish(reply, \"pong\") catch return;\n        }\n    }\n};\n\n/// Muxer-specific test: proves the old per-request subscription\n/// path is gone. That path carried a hardcoded 5ms latency floor;\n/// instead of depending on sub-5ms wall-clock timing on CI, assert\n/// that request() creates one wildcard response mux subscription\n/// and reuses it for subsequent requests.\npub fn testMuxerLatencyFloor(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_r = utils.newIo(allocator);\n    defer io_r.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_r.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"muxer_latency_floor\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    const sub = responder.subscribeSync(\"muxer.lat.test\") catch {\n        reportResult(\"muxer_latency_floor\", false, \"responder sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_r.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    var resp_fut = io_r.io().async(RespHandler.run, .{ responder, sub });\n    defer _ = resp_fut.cancel(io_r.io());\n\n    const io_q = utils.newIo(allocator);\n    defer io_q.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_q.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"muxer_latency_floor\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const before_subs = requester.numSubscriptions();\n    if (before_subs != 0) {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"subs before request: {d}\",\n            .{before_subs},\n        ) catch \"e\";\n        reportResult(\"muxer_latency_floor\", false, detail);\n        return;\n    }\n\n    for (0..6) |i| {\n        const reply = requester.request(\n            \"muxer.lat.test\",\n            \"ping\",\n            2000,\n        ) catch {\n            reportResult(\"muxer_latency_floor\", false, \"request failed\");\n            return;\n        };\n\n        if (reply) |m| {\n            defer m.deinit();\n            if (!std.mem.eql(u8, m.data, \"pong\")) {\n                reportResult(\"muxer_latency_floor\", false, \"wrong reply\");\n                return;\n            }\n        } else {\n            reportResult(\"muxer_latency_floor\", false, \"no reply\");\n            return;\n        }\n\n        const subs = requester.numSubscriptions();\n        if (subs != 1) {\n            var buf: [64]u8 = undefined;\n            const detail = std.fmt.bufPrint(\n                &buf,\n                \"request {d}: subs={d}, want 1\",\n                .{ i + 1, subs },\n            ) catch \"e\";\n            reportResult(\"muxer_latency_floor\", false, detail);\n            return;\n        }\n    }\n\n    reportResult(\"muxer_latency_floor\", true, \"\");\n}\n\n/// Muxer-specific test: proves the muxer's PING/PONG init cost\n/// is amortized to zero. Issues 100 sequential requests on the\n/// same connection and asserts the average round-trip is well\n/// under 1ms (the cold first call is the only one paying for\n/// ensureRespMux + PING/PONG).\npub fn testMuxerRapidSequential(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io_r = utils.newIo(allocator);\n    defer io_r.deinit();\n    const responder = nats.Client.connect(\n        allocator,\n        io_r.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"muxer_rapid_sequential\", false, \"responder connect failed\");\n        return;\n    };\n    defer responder.deinit();\n\n    const sub = responder.subscribeSync(\"muxer.rapid.test\") catch {\n        reportResult(\"muxer_rapid_sequential\", false, \"responder sub failed\");\n        return;\n    };\n    defer sub.deinit();\n    io_r.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    var resp_fut = io_r.io().async(RespHandler.run, .{ responder, sub });\n    defer _ = resp_fut.cancel(io_r.io());\n\n    const io_q = utils.newIo(allocator);\n    defer io_q.deinit();\n    const requester = nats.Client.connect(\n        allocator,\n        io_q.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"muxer_rapid_sequential\", false, \"requester connect failed\");\n        return;\n    };\n    defer requester.deinit();\n\n    const N: u32 = 100;\n    var success: u32 = 0;\n    const start = std.Io.Timestamp.now(io_q.io(), .awake);\n    var i: u32 = 0;\n    while (i < N) : (i += 1) {\n        const reply = requester.request(\n            \"muxer.rapid.test\",\n            \"ping\",\n            2000,\n        ) catch break;\n        if (reply) |m| {\n            defer m.deinit();\n            if (std.mem.eql(u8, m.data, \"pong\")) success += 1;\n        } else break;\n    }\n    const end = std.Io.Timestamp.now(io_q.io(), .awake);\n\n    if (success != N) {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d} replies\",\n            .{ success, N },\n        ) catch \"e\";\n        reportResult(\"muxer_rapid_sequential\", false, detail);\n        return;\n    }\n\n    const elapsed_ns: u64 = @intCast(start.durationTo(end).nanoseconds);\n    const total_ms = elapsed_ns / std.time.ns_per_ms;\n\n    // The old per-request-sub path burned at least 5ms per call\n    // in the artificial sleep alone, so 100 requests would take\n    // >= 500ms even before counting SUB/UNSUB churn. The muxer\n    // amortizes ensureRespMux to one PING/PONG and then uses the\n    // wildcard sub for every subsequent request, so total time\n    // is dominated by per-call dispatch overhead. We assert well\n    // under the old floor to prove the muxer is on the hot path.\n    if (total_ms < 400) {\n        reportResult(\"muxer_rapid_sequential\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"100 requests took {d}ms (expected < 400ms)\",\n            .{total_ms},\n        ) catch \"e\";\n        reportResult(\"muxer_rapid_sequential\", false, detail);\n    }\n}\n\n/// Muxer-specific test: proves no use-after-free or leak when a\n/// request times out (waiter is removed from the resp_map by the\n/// cleanup defer in requestAwaitResp). Fires N timing-out\n/// requests against a nonexistent subject and asserts each\n/// returns null cleanly without leaking the waiter slot.\npub fn testMuxerTimeoutCleanup(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false, .no_responders = false },\n    ) catch {\n        reportResult(\"muxer_timeout_cleanup\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var i: u32 = 0;\n    while (i < 20) : (i += 1) {\n        const reply = client.request(\n            \"muxer.cleanup.noexist\",\n            \"ping\",\n            20,\n        ) catch {\n            reportResult(\"muxer_timeout_cleanup\", false, \"request error\");\n            return;\n        };\n        if (reply) |m| {\n            m.deinit();\n            reportResult(\n                \"muxer_timeout_cleanup\",\n                false,\n                \"unexpected reply\",\n            );\n            return;\n        }\n    }\n\n    if (client.isConnected()) {\n        reportResult(\"muxer_timeout_cleanup\", true, \"\");\n    } else {\n        reportResult(\n            \"muxer_timeout_cleanup\",\n            false,\n            \"disconnected after timeouts\",\n        );\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testRequestMethod(allocator);\n    testRequestReturns(allocator);\n    testReplyToPreserved(allocator);\n    testRequestReplySuccess(allocator);\n    testCrossClientRequestReply(allocator);\n    testRequestTimeout(allocator);\n    testRequestWithLargePayload(allocator);\n    testMultipleRequestsSequential(allocator);\n    testMuxerLatencyFloor(allocator);\n    testMuxerRapidSequential(allocator);\n    testMuxerTimeoutCleanup(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/server.zig",
    "content": "//! Server Tests for NATS Client\n//!\n//! Tests for server info and protocol handling.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\npub fn testServerInfo(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_info\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"server_info\", false, \"no server info\");\n        return;\n    }\n\n    const has_version = info.?.version.len > 0;\n    reportResult(\"server_info\", has_version, \"no version in info\");\n}\n\npub fn testServerInfoFields(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_info_fields\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"server_info_fields\", false, \"no server info\");\n        return;\n    }\n\n    const i = info.?;\n\n    var valid = true;\n    if (i.version.len == 0) valid = false;\n    if (i.max_payload == 0) valid = false;\n    if (i.proto < 1) valid = false;\n\n    if (valid) {\n        reportResult(\"server_info_fields\", true, \"\");\n    } else {\n        reportResult(\"server_info_fields\", false, \"missing fields\");\n    }\n}\n\npub fn testServerVersion(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"server_version\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"server_version\", false, \"no server info\");\n        return;\n    }\n\n    const version = info.?.version;\n    if (version.len > 0 and (version[0] == '2' or version[0] == '3')) {\n        reportResult(\"server_version\", true, \"\");\n    } else {\n        reportResult(\"server_version\", false, \"unexpected version\");\n    }\n}\n\npub fn testServerMaxPayloadEnforced(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_payload_enforced\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"max_payload_enforced\", false, \"no server info\");\n        return;\n    }\n\n    const max = info.?.max_payload;\n    if (max > 0) {\n        reportResult(\"max_payload_enforced\", true, \"\");\n    } else {\n        reportResult(\"max_payload_enforced\", false, \"max_payload is 0\");\n    }\n}\n\npub fn testMaxPayloadRespected(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_payload_respected\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"max_payload_respected\", false, \"no server info\");\n        return;\n    }\n\n    if (info.?.max_payload >= 1024 and info.?.max_payload <= 64 * 1024 * 1024) {\n        reportResult(\"max_payload_respected\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"max_payload={d}\",\n            .{info.?.max_payload},\n        ) catch \"err\";\n        reportResult(\"max_payload_respected\", false, detail);\n    }\n}\n\npub fn testProtocolVersion(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"proto_version\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"proto_version\", false, \"no server info\");\n        return;\n    }\n\n    if (info.?.proto >= 1) {\n        reportResult(\"proto_version\", true, \"\");\n    } else {\n        reportResult(\"proto_version\", false, \"proto < 1\");\n    }\n}\n\npub fn testClientName(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .name = \"my-test-client-12345\",\n        .reconnect = false,\n    }) catch {\n        reportResult(\"client_name\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"client_name\", true, \"\");\n    } else {\n        reportResult(\"client_name\", false, \"not connected\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testServerInfo(allocator);\n    testServerInfoFields(allocator);\n    testServerVersion(allocator);\n    testServerMaxPayloadEnforced(allocator);\n    testMaxPayloadRespected(allocator);\n    testProtocolVersion(allocator);\n    testClientName(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/state_notifications.zig",
    "content": "//! State Notification Tests for NATS Client\n//!\n//! Tests for: LastError, discovered_servers event,\n//! draining event, subscription_complete event.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\nfn getNowNs(io: std.Io) i128 {\n    return std.Io.Timestamp.now(io, .awake).nanoseconds;\n}\n\nfn threadSleepNs(ns: u64) void {\n    var ts: std.posix.timespec = .{\n        .sec = @intCast(ns / std.time.ns_per_s),\n        .nsec = @intCast(ns % std.time.ns_per_s),\n    };\n    _ = std.posix.system.nanosleep(&ts, &ts);\n}\n\n/// Test getLastError returns null initially and after clear.\npub fn testLastErrorInitialNull(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"last_error_initial\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Initially should be null\n    const err = client.lastError();\n    if (err != null) {\n        reportResult(\"last_error_initial\", false, \"expected null\");\n        return;\n    }\n\n    reportResult(\"last_error_initial\", true, \"\");\n}\n\n/// Test clearLastError works.\npub fn testClearLastError(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"clear_last_error\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Clear and verify null\n    client.clearLastError();\n    const err = client.lastError();\n    if (err != null) {\n        reportResult(\"clear_last_error\", false, \"expected null after clear\");\n        return;\n    }\n\n    reportResult(\"clear_last_error\", true, \"\");\n}\n\n/// Test draining event is fired during drain.\npub fn testDrainingEvent(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    // Track events with a handler\n    const EventTracker = struct {\n        draining_received: bool = false,\n\n        pub fn onDraining(self: *@This()) void {\n            self.draining_received = true;\n        }\n    };\n\n    var tracker = EventTracker{};\n    const handler = nats.EventHandler.init(EventTracker, &tracker);\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = false,\n            .event_handler = handler,\n        },\n    ) catch {\n        reportResult(\"draining_event\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Subscribe to something\n    const sub = client.subscribeSync(\"drain.test\") catch {\n        reportResult(\"draining_event\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Drain (this also cleans up subscriptions internally)\n    _ = client.drain() catch {};\n\n    // Give callback task time to process events\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    if (tracker.draining_received) {\n        reportResult(\"draining_event\", true, \"\");\n    } else {\n        reportResult(\"draining_event\", false, \"no draining event\");\n    }\n}\n\n/// Test subscription_complete event when auto-unsub limit is reached.\npub fn testSubscriptionCompleteEvent(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    // Track events with a handler\n    const EventTracker = struct {\n        complete_received: bool = false,\n        complete_sid: u64 = 0,\n\n        pub fn onSubscriptionComplete(self: *@This(), sid: u64) void {\n            self.complete_received = true;\n            self.complete_sid = sid;\n        }\n    };\n\n    var tracker = EventTracker{};\n    const handler = nats.EventHandler.init(EventTracker, &tracker);\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = false,\n            .event_handler = handler,\n        },\n    ) catch {\n        reportResult(\"sub_complete_event\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Subscribe with auto-unsub after 3 messages\n    const sub = client.subscribeSync(\"complete.test\") catch {\n        reportResult(\"sub_complete_event\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub.autoUnsubscribe(3) catch {\n        reportResult(\"sub_complete_event\", false, \"auto-unsub failed\");\n        return;\n    };\n\n    // Publish 3 messages\n    for (0..3) |_| {\n        client.publish(\"complete.test\", \"data\") catch {};\n    }\n\n    // Receive messages to trigger the delivered count\n    for (0..3) |_| {\n        const msg = sub.nextMsgTimeout(500) catch break;\n        if (msg) |m| {\n            m.deinit();\n        }\n    }\n\n    // Give callback task time to process events\n    io.io().sleep(.fromMilliseconds(100), .awake) catch {};\n\n    if (tracker.complete_received and tracker.complete_sid == sub.getSid()) {\n        reportResult(\"sub_complete_event\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"recv={} sid={}\", .{\n            tracker.complete_received,\n            tracker.complete_sid,\n        }) catch \"e\";\n        reportResult(\"sub_complete_event\", false, detail);\n    }\n}\n\nfn pushDrainingEventThread(\n    client: *nats.Client,\n    go: *std.atomic.Value(bool),\n) void {\n    while (!go.load(.acquire)) {\n        std.atomic.spinLoopHint();\n    }\n    client.pushEvent(.{ .draining = {} });\n}\n\n/// Stress the event queue with one producer from io_task and one\n/// producer from user-thread code in the same window.\npub fn testEventQueueMultiProducerOverlap(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const Tracker = struct {\n        draining_count: std.atomic.Value(u32) =\n            std.atomic.Value(u32).init(0),\n        complete_count: std.atomic.Value(u32) =\n            std.atomic.Value(u32).init(0),\n\n        pub fn onDraining(self: *@This()) void {\n            _ = self.draining_count.fetchAdd(1, .monotonic);\n        }\n\n        pub fn onSubscriptionComplete(self: *@This(), sid: u64) void {\n            _ = sid;\n            _ = self.complete_count.fetchAdd(1, .monotonic);\n        }\n    };\n\n    var tracker = Tracker{};\n    const handler = nats.EventHandler.init(Tracker, &tracker);\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{\n            .reconnect = false,\n            .event_handler = handler,\n        },\n    ) catch {\n        reportResult(\"event_queue_multi_producer\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const iterations = 64;\n    for (0..iterations) |i| {\n        {\n            var subject_buf: [48]u8 = undefined;\n            const subject = std.fmt.bufPrint(\n                &subject_buf,\n                \"event.mp.{d}\",\n                .{i},\n            ) catch {\n                reportResult(\"event_queue_multi_producer\", false, \"subject format failed\");\n                return;\n            };\n\n            const sub = client.subscribeSync(subject) catch {\n                reportResult(\"event_queue_multi_producer\", false, \"subscribe failed\");\n                return;\n            };\n            defer sub.deinit();\n\n            sub.autoUnsubscribe(1) catch {\n                reportResult(\"event_queue_multi_producer\", false, \"auto-unsub failed\");\n                return;\n            };\n\n            var go = std.atomic.Value(bool).init(false);\n            var t = std.Thread.spawn(\n                .{},\n                pushDrainingEventThread,\n                .{ client, &go },\n            ) catch {\n                reportResult(\"event_queue_multi_producer\", false, \"thread spawn failed\");\n                return;\n            };\n\n            go.store(true, .release);\n            client.publish(subject, \"x\") catch {\n                t.join();\n                reportResult(\"event_queue_multi_producer\", false, \"publish failed\");\n                return;\n            };\n            client.flush(1_000_000_000) catch {\n                t.join();\n                reportResult(\"event_queue_multi_producer\", false, \"flush failed\");\n                return;\n            };\n            t.join();\n\n            if (sub.nextMsgTimeout(200) catch null) |msg| {\n                msg.deinit();\n            } else {\n                reportResult(\"event_queue_multi_producer\", false, \"message not received\");\n                return;\n            }\n\n            const draining_target: u32 = @intCast(i + 1);\n            const complete_target: u32 = @intCast(i + 1);\n            const deadline_ns = getNowNs(io.io()) +\n                200 * std.time.ns_per_ms;\n            while (getNowNs(io.io()) < deadline_ns) {\n                if (tracker.draining_count.load(.monotonic) >= draining_target and\n                    tracker.complete_count.load(.monotonic) >= complete_target)\n                {\n                    break;\n                }\n                threadSleepNs(1 * std.time.ns_per_ms);\n            }\n\n            if (tracker.draining_count.load(.monotonic) < draining_target or\n                tracker.complete_count.load(.monotonic) < complete_target)\n            {\n                var detail_buf: [96]u8 = undefined;\n                const detail = std.fmt.bufPrint(\n                    &detail_buf,\n                    \"drain={d} complete={d} at iter {d}\",\n                    .{\n                        tracker.draining_count.load(.monotonic),\n                        tracker.complete_count.load(.monotonic),\n                        i,\n                    },\n                ) catch \"event count mismatch\";\n                reportResult(\"event_queue_multi_producer\", false, detail);\n                return;\n            }\n        }\n    }\n\n    reportResult(\"event_queue_multi_producer\", true, \"\");\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testLastErrorInitialNull(allocator);\n    testClearLastError(allocator);\n    testDrainingEvent(allocator);\n    testSubscriptionCompleteEvent(allocator);\n    testEventQueueMultiProducerOverlap(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/stats.zig",
    "content": "//! Stats Tests for NATS  Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testClientStats(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_stats\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const initial_stats = client.stats();\n    if (initial_stats.msgs_out != 0) {\n        reportResult(\"client_stats\", false, \"initial msgs_out != 0\");\n        return;\n    }\n\n    client.publish(\"async.stats\", \"test\") catch {};\n\n    const stats = client.stats();\n    if (stats.msgs_out >= 1) {\n        reportResult(\"client_stats\", true, \"\");\n    } else {\n        reportResult(\"client_stats\", false, \"msgs_out not incremented\");\n    }\n}\n\npub fn testStatsIncrement(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stats_increment\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const before = client.stats();\n\n    for (0..10) |_| {\n        client.publish(\"async.stats.inc\", \"msg\") catch {};\n    }\n\n    const after = client.stats();\n\n    if (after.msgs_out >= before.msgs_out + 10) {\n        reportResult(\"stats_increment\", true, \"\");\n    } else {\n        reportResult(\"stats_increment\", false, \"stats not incremented\");\n    }\n}\n\npub fn testStatsBytesAccuracy(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stats_bytes\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const before = client.stats();\n\n    const payload = \"0123456789\" ** 10;\n    client.publish(\"async.stats.bytes\", payload) catch {};\n\n    const after = client.stats();\n\n    if (after.bytes_out >= before.bytes_out + 100) {\n        reportResult(\"stats_bytes\", true, \"\");\n    } else {\n        reportResult(\"stats_bytes\", false, \"bytes not tracked\");\n    }\n}\n\npub fn testStatsMsgsIn(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stats_msgs_in\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"msgsin.test\") catch {\n        reportResult(\"stats_msgs_in\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const before = client.stats();\n\n    for (0..25) |_| {\n        client.publish(\"msgsin.test\", \"data\") catch {};\n    }\n\n    var received: u32 = 0;\n    for (0..30) |_| {\n        const msg = sub.nextMsgTimeout(200) catch break;\n        if (msg) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    const after = client.stats();\n    const msgs_in = after.msgs_in - before.msgs_in;\n\n    if (msgs_in == 25 and received == 25) {\n        reportResult(\"stats_msgs_in\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"in={d} recv={d}\",\n            .{ msgs_in, received },\n        ) catch \"e\";\n        reportResult(\"stats_msgs_in\", false, detail);\n    }\n}\n\npub fn testStatsBytesIn(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stats_bytes_in\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"bytesin.test\") catch {\n        reportResult(\"stats_bytes_in\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const before = client.stats();\n\n    const payload = \"01234567890123456789012345678901234567890123456789\";\n    for (0..10) |_| {\n        client.publish(\"bytesin.test\", payload) catch {};\n    }\n\n    for (0..15) |_| {\n        const msg = sub.nextMsgTimeout(200) catch break;\n        if (msg) |m| {\n            m.deinit();\n        } else break;\n    }\n\n    const after = client.stats();\n    const bytes_in = after.bytes_in - before.bytes_in;\n\n    if (bytes_in == 500) {\n        reportResult(\"stats_bytes_in\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/500\",\n            .{bytes_in},\n        ) catch \"e\";\n        reportResult(\"stats_bytes_in\", false, detail);\n    }\n}\n\npub fn testConnectsCounter(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stats_connects\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const stats = client.stats();\n    if (stats.connects >= 1) {\n        reportResult(\"stats_connects\", true, \"\");\n    } else {\n        reportResult(\"stats_connects\", false, \"connects not incremented\");\n    }\n}\n\npub fn testSubStats(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"sub_stats\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"substats.test\") catch {\n        reportResult(\"sub_stats\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Initially should have 0 pending\n    const initial = sub.subStats();\n    if (initial.pending_msgs != 0) {\n        reportResult(\"sub_stats\", false, \"initial pending != 0\");\n        return;\n    }\n\n    // Publish some messages\n    for (0..5) |_| {\n        client.publish(\"substats.test\", \"test data\") catch {};\n    }\n\n    // Wait for messages to arrive\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Check pending increased\n    const after = sub.subStats();\n    if (after.pending_msgs >= 5 or after.pending_bytes > 0) {\n        reportResult(\"sub_stats\", true, \"\");\n    } else {\n        reportResult(\"sub_stats\", false, \"pending not updated\");\n    }\n}\n\npub fn testPendingBytes(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"pending_bytes\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"pending.bytes\") catch {\n        reportResult(\"pending_bytes\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish messages\n    const payload = \"0123456789\";\n    for (0..10) |_| {\n        client.publish(\"pending.bytes\", payload) catch {};\n    }\n\n    // Wait for messages\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Check pending bytes before receiving\n    const before_recv = sub.pendingBytes();\n\n    // Receive all messages\n    for (0..10) |_| {\n        const msg = sub.nextMsgTimeout(100) catch break;\n        if (msg) |m| {\n            m.deinit();\n        } else break;\n    }\n\n    // Check pending bytes after receiving\n    const after_recv = sub.pendingBytes();\n\n    if (before_recv > after_recv and after_recv == 0) {\n        reportResult(\"pending_bytes\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"before={d} after={d}\",\n            .{ before_recv, after_recv },\n        ) catch \"e\";\n        reportResult(\"pending_bytes\", false, detail);\n    }\n}\n\npub fn testMaxPending(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_pending\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"max.pending\") catch {\n        reportResult(\"max_pending\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Activate flow control so max_pending_msgs watermark is tracked\n    sub.setPendingLimits(1000);\n\n    // Publish messages to create high water mark\n    for (0..20) |_| {\n        client.publish(\"max.pending\", \"payload\") catch {};\n    }\n\n    // Wait for messages\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    // Get max pending before receiving\n    const max = sub.maxPending();\n    if (max.msgs == 0) {\n        reportResult(\"max_pending\", false, \"max_msgs is 0\");\n        return;\n    }\n\n    // Receive all messages\n    for (0..25) |_| {\n        const msg = sub.nextMsgTimeout(100) catch break;\n        if (msg) |m| {\n            m.deinit();\n        } else break;\n    }\n\n    // Max should stay the same (high watermark)\n    const max_after = sub.maxPending();\n    if (max_after.msgs == max.msgs) {\n        // Clear max pending\n        sub.clearMaxPending();\n        const max_cleared = sub.maxPending();\n        if (max_cleared.msgs == 0) {\n            reportResult(\"max_pending\", true, \"\");\n        } else {\n            reportResult(\"max_pending\", false, \"clearMaxPending failed\");\n        }\n    } else {\n        reportResult(\"max_pending\", false, \"max decreased\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testClientStats(allocator);\n    testStatsIncrement(allocator);\n    testStatsBytesAccuracy(allocator);\n    testStatsMsgsIn(allocator);\n    testStatsBytesIn(allocator);\n    testConnectsCounter(allocator);\n    testSubStats(allocator);\n    testPendingBytes(allocator);\n    testMaxPending(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/stress.zig",
    "content": "//! Stress Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testStress500Messages(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"stress_500\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(allocator, sub_io.io(), url, .{\n        .sub_queue_size = 512,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"stress_500\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"stress500\") catch {\n        reportResult(\"stress_500\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    const NUM_MSGS = 500;\n    for (0..NUM_MSGS) |_| {\n        publisher.publish(\"stress500\", \"stress-msg\") catch {\n            reportResult(\"stress_500\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        var future = sub_io.io().async(\n            nats.Client.Sub.nextMsg,\n            .{sub},\n        );\n        defer if (future.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n        if (future.await(sub_io.io())) |_| {\n            received += 1;\n        } else |_| {\n            break;\n        }\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"stress_500\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg =\n            std.fmt.bufPrint(&buf, \"got {d}/500\", .{received}) catch \"e\";\n        reportResult(\"stress_500\", false, msg);\n    }\n}\n\npub fn testStress1000Messages(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .sub_queue_size = 1024,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"stress_1000\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"stress1k\") catch {\n        reportResult(\"stress_1000\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const NUM_MSGS = 1000;\n    for (0..NUM_MSGS) |_| {\n        client.publish(\"stress1k\", \"stress-msg\") catch {\n            reportResult(\"stress_1000\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        if (sub.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"stress_1000\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg =\n            std.fmt.bufPrint(&buf, \"got {d}/1000\", .{received}) catch \"e\";\n        reportResult(\"stress_1000\", false, msg);\n    }\n}\n\npub fn testStress2000Messages(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(allocator, io.io(), url, .{\n        .sub_queue_size = 2048,\n        .reconnect = false,\n    }) catch {\n        reportResult(\"stress_2000\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"stress2k\") catch {\n        reportResult(\"stress_2000\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const NUM_MSGS = 2000;\n    for (0..20) |_| {\n        for (0..100) |_| {\n            client.publish(\"stress2k\", \"stress-msg\") catch {\n                reportResult(\"stress_2000\", false, \"publish failed\");\n                return;\n            };\n        }\n    }\n\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        if (sub.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"stress_2000\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg =\n            std.fmt.bufPrint(&buf, \"got {d}/2000\", .{received}) catch \"e\";\n        reportResult(\"stress_2000\", false, msg);\n    }\n}\n\npub fn testPayload30KB(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"payload_30kb\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"stress.30kb\") catch {\n        reportResult(\"payload_30kb\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const payload = allocator.alloc(u8, 30 * 1024) catch {\n        reportResult(\"payload_30kb\", false, \"alloc failed\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'X');\n\n    client.publish(\"stress.30kb\", payload) catch {\n        reportResult(\"payload_30kb\", false, \"publish failed\");\n        return;\n    };\n\n    if (sub.nextMsgTimeout(3000) catch null) |m| {\n        defer m.deinit();\n        if (m.data.len == 30 * 1024) {\n            reportResult(\"payload_30kb\", true, \"\");\n        } else {\n            reportResult(\"payload_30kb\", false, \"wrong size\");\n        }\n    } else {\n        reportResult(\"payload_30kb\", false, \"no message\");\n    }\n}\n\npub fn testManySubscriptions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"many_subscriptions\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    var subs: [50]?*nats.Subscription = undefined;\n    @memset(&subs, null);\n\n    defer for (&subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    var created: usize = 0;\n    for (0..50) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject =\n            std.fmt.bufPrint(&subject_buf, \"manysub.{d}\", .{i}) catch {\n                continue;\n            };\n        subs[i] = client.subscribeSync(subject) catch {\n            break;\n        };\n        created += 1;\n    }\n\n    if (created == 50) {\n        reportResult(\"many_subscriptions\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg =\n            std.fmt.bufPrint(&buf, \"created {d}/50\", .{created}) catch \"e\";\n        reportResult(\"many_subscriptions\", false, msg);\n    }\n}\n\npub fn testPayloadBoundary(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"payload_boundary\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"boundary.test\") catch {\n        reportResult(\"payload_boundary\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const sizes = [_]usize{ 1024, 4096, 8192, 15360 };\n    var all_passed = true;\n\n    for (sizes) |size| {\n        const payload = allocator.alloc(u8, size) catch {\n            all_passed = false;\n            break;\n        };\n        defer allocator.free(payload);\n        @memset(payload, 'B');\n\n        client.publish(\"boundary.test\", payload) catch {\n            all_passed = false;\n            break;\n        };\n\n        const msg = sub.nextMsgTimeout(2000) catch {\n            all_passed = false;\n            break;\n        };\n\n        if (msg) |m| {\n            if (m.data.len != size) all_passed = false;\n            m.deinit();\n        } else {\n            all_passed = false;\n            break;\n        }\n    }\n\n    if (all_passed) {\n        reportResult(\"payload_boundary\", true, \"\");\n    } else {\n        reportResult(\"payload_boundary\", false, \"size mismatch\");\n    }\n}\n\npub fn testFiveConcurrentClients(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    var ios: [5]*utils.TestIo = undefined;\n    var clients: [5]?*nats.Client = [_]?*nats.Client{null} ** 5;\n    var count: usize = 0;\n\n    defer {\n        for (0..count) |i| {\n            if (clients[i]) |c| {\n                c.deinit();\n            }\n            ios[i].deinit();\n        }\n    }\n\n    for (0..5) |i| {\n        ios[i] = utils.newIo(allocator);\n        clients[i] = nats.Client.connect(\n            allocator,\n            ios[i].io(),\n            url,\n            .{ .reconnect = false },\n        ) catch {\n            reportResult(\"five_concurrent\", false, \"connect failed\");\n            return;\n        };\n        count += 1;\n    }\n\n    var all_connected = true;\n    for (0..5) |i| {\n        if (clients[i]) |c| {\n            if (!c.isConnected()) all_connected = false;\n        }\n    }\n\n    if (all_connected) {\n        reportResult(\"five_concurrent\", true, \"\");\n    } else {\n        reportResult(\"five_concurrent\", false, \"not all connected\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testStress500Messages(allocator);\n    testStress1000Messages(allocator);\n    testStress2000Messages(allocator);\n    testPayload30KB(allocator);\n    testManySubscriptions(allocator);\n    testPayloadBoundary(allocator);\n    testFiveConcurrentClients(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/stress_subs.zig",
    "content": "//! Stress Tests for Subscriptions, Publishing, and Edge Cases\n//!\n//! Tests subscription counts, SidMap churn, payload sizes,\n//! multi-client fan-out, and queue pressure scenarios.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\n// --- A. Massive Subscription Tests ---\n\n/// 5K subs on unique subjects, publish one msg to each, verify.\npub fn testFiveThousandSubs(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_SUBS = 5_000;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .sub_queue_size = 64, .reconnect = false },\n    ) catch {\n        reportResult(\"5k_subs\", false, \"sub connect\");\n        return;\n    };\n    defer sub_client.deinit();\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"5k_subs\", false, \"pub connect\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    const subs = allocator.alloc(\n        ?*nats.Subscription,\n        NUM_SUBS,\n    ) catch {\n        reportResult(\"5k_subs\", false, \"alloc subs\");\n        return;\n    };\n    defer allocator.free(subs);\n    @memset(subs, null);\n\n    defer for (subs) |s| {\n        if (s) |sub| sub.deinit();\n    };\n\n    // Create subs in batches of 500 with flush\n    var created: usize = 0;\n    var last_err: ?[]const u8 = null;\n    for (0..NUM_SUBS) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"five.{d}\",\n            .{i},\n        ) catch continue;\n        subs[i] = sub_client.subscribeSync(\n            subj,\n        ) catch |e| {\n            last_err = @errorName(e);\n            break;\n        };\n        created += 1;\n        // Flush every 500 subs to avoid write backlog\n\n    }\n\n    if (created != NUM_SUBS) {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"{d}/{d} err={s}\",\n            .{\n                created,\n                NUM_SUBS,\n                last_err orelse \"none\",\n            },\n        ) catch \"count\";\n        reportResult(\"5k_subs\", false, msg);\n        return;\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(200),\n        .awake,\n    ) catch {};\n\n    // Publish one msg to each subject\n    for (0..NUM_SUBS) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"five.{d}\",\n            .{i},\n        ) catch continue;\n        pub_client.publish(subj, \"x\") catch {\n            reportResult(\"5k_subs\", false, \"publish\");\n            return;\n        };\n    }\n\n    // Wait for messages to arrive\n    sub_io.io().sleep(\n        .fromMilliseconds(2000),\n        .awake,\n    ) catch {};\n\n    // Drain (short timeout - msgs should be queued)\n    var received: usize = 0;\n    for (subs) |s| {\n        if (s) |sub| {\n            if (sub.nextMsgTimeout(50) catch null) |m| {\n                m.deinit();\n                received += 1;\n            }\n        }\n    }\n\n    const threshold = NUM_SUBS * 100 / 100;\n    if (received >= threshold) {\n        reportResult(\"5k_subs\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ received, NUM_SUBS },\n        ) catch \"count\";\n        reportResult(\"5k_subs\", false, msg);\n    }\n}\n\n/// Sub/unsub churn stresses SidMap tombstones.\npub fn testSubUnsubChurn(\n    allocator: std.mem.Allocator,\n) void {\n    const ITERATIONS = 5000;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .sub_queue_size = 64, .reconnect = false },\n    ) catch {\n        reportResult(\"sub_unsub_churn\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    for (0..ITERATIONS) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"churn.{d}\",\n            .{i},\n        ) catch continue;\n\n        const sub = client.subscribeSync(subj) catch |e| {\n            var buf: [64]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"fail at {d} {s}\",\n                .{ i, @errorName(e) },\n            ) catch \"sub\";\n            reportResult(\"sub_unsub_churn\", false, msg);\n            return;\n        };\n        sub.deinit();\n        // Flush every 500 to keep write buffer clear\n\n    }\n\n    // Verify final sub works\n    const final_sub = client.subscribeSync(\n        \"churn.final\",\n    ) catch {\n        reportResult(\"sub_unsub_churn\", false, \"final\");\n        return;\n    };\n    defer final_sub.deinit();\n\n    client.publish(\"churn.final\", \"ok\") catch {\n        reportResult(\"sub_unsub_churn\", false, \"pub\");\n        return;\n    };\n\n    if (final_sub.nextMsgTimeout(1000) catch null) |m| {\n        m.deinit();\n        reportResult(\"sub_unsub_churn\", true, \"\");\n    } else {\n        reportResult(\"sub_unsub_churn\", false, \"no msg\");\n    }\n}\n\n/// Subscribe 2048, unsub all, resubscribe 2048 fresh.\npub fn testSubsThenResubscribe(\n    allocator: std.mem.Allocator,\n) void {\n    const COUNT = 2048;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .sub_queue_size = 64, .reconnect = false },\n    ) catch {\n        reportResult(\"resub\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    const subs = allocator.alloc(\n        ?*nats.Subscription,\n        COUNT,\n    ) catch {\n        reportResult(\"resub\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(subs);\n    @memset(subs, null);\n\n    // subscribe COUNT\n    var created: usize = 0;\n    for (0..COUNT) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"rs.a.{d}\",\n            .{i},\n        ) catch continue;\n        subs[i] = client.subscribeSync(subj) catch break;\n        created += 1;\n    }\n\n    if (created != COUNT) {\n        for (subs) |s| if (s) |sub| sub.deinit();\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"phase1 {d}/{d}\",\n            .{ created, COUNT },\n        ) catch \"count\";\n        reportResult(\"resub\", false, msg);\n        return;\n    }\n\n    // Unsub all\n    for (subs) |s| if (s) |sub| sub.deinit();\n    @memset(subs, null);\n\n    io.io().sleep(\n        .fromMilliseconds(200),\n        .awake,\n    ) catch {};\n\n    if (!client.isConnected()) {\n        reportResult(\"resub\", false, \"disconnected\");\n        return;\n    }\n\n    // resubscribe fresh\n    var created2: usize = 0;\n    var last_err: ?[]const u8 = null;\n    for (0..COUNT) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"rs.b.{d}\",\n            .{i},\n        ) catch continue;\n        subs[i] = client.subscribeSync(subj) catch |e| {\n            last_err = @errorName(e);\n            break;\n        };\n        created2 += 1;\n    }\n\n    defer for (subs) |s| if (s) |sub| sub.deinit();\n\n    if (created2 != COUNT) {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"phase2 {d}/{d} {s}\",\n            .{\n                created2,\n                COUNT,\n                last_err orelse \"none\",\n            },\n        ) catch \"count\";\n        reportResult(\"resub\", false, msg);\n        return;\n    }\n\n    // Verify pub/sub on a resubscribed subject\n    client.publish(\"rs.b.0\", \"resub-ok\") catch {\n        reportResult(\"resub\", false, \"publish\");\n        return;\n    };\n\n    if (subs[0]) |sub| {\n        if (sub.nextMsgTimeout(1000) catch null) |m| {\n            m.deinit();\n            reportResult(\"resub\", true, \"\");\n        } else {\n            reportResult(\"resub\", false, \"no msg\");\n        }\n    } else {\n        reportResult(\"resub\", false, \"null sub\");\n    }\n}\n\n/// 2K wildcard subs + wildcard catch-all, fan-out test.\npub fn testWildcardFanOut(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_SUBS = 2_000;\n    const NUM_MSGS = 100;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .sub_queue_size = 128, .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_fanout\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_fanout\", false, \"pub con\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    const subs = allocator.alloc(\n        ?*nats.Subscription,\n        NUM_SUBS,\n    ) catch {\n        reportResult(\"wildcard_fanout\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(subs);\n    @memset(subs, null);\n\n    defer for (subs) |s| if (s) |sub| sub.deinit();\n\n    var created: usize = 0;\n    for (0..NUM_SUBS) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"fan.{d}\",\n            .{i},\n        ) catch continue;\n        subs[i] = client.subscribeSync(subj) catch break;\n        created += 1;\n    }\n\n    if (created != NUM_SUBS) {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"created {d}/{d}\",\n            .{ created, NUM_SUBS },\n        ) catch \"count\";\n        reportResult(\"wildcard_fanout\", false, msg);\n        return;\n    }\n\n    // Wildcard subscriber\n    const wc_sub = client.subscribeSync(\"fan.>\") catch {\n        reportResult(\"wildcard_fanout\", false, \"wc sub\");\n        return;\n    };\n    defer wc_sub.deinit();\n\n    sub_io.io().sleep(\n        .fromMilliseconds(200),\n        .awake,\n    ) catch {};\n\n    // Publish to fan.0 through fan.99\n    for (0..NUM_MSGS) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"fan.{d}\",\n            .{i},\n        ) catch continue;\n        pub_client.publish(subj, \"wc\") catch {\n            reportResult(\"wildcard_fanout\", false, \"pub\");\n            return;\n        };\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(500),\n        .awake,\n    ) catch {};\n\n    var wc_received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        if (wc_sub.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            wc_received += 1;\n        } else break;\n    }\n\n    if (wc_received == NUM_MSGS) {\n        reportResult(\"wildcard_fanout\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"wc got {d}/{d}\",\n            .{ wc_received, NUM_MSGS },\n        ) catch \"count\";\n        reportResult(\"wildcard_fanout\", false, msg);\n    }\n}\n\n// --- B. Multi-Client Tests ---\n\n/// 10 clients, 200 subs each, cross-publish.\npub fn testTenClientsManySubs(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_CLIENTS = 10;\n    const SUBS_PER = 200;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    var ios: [NUM_CLIENTS]*utils.TestIo = undefined;\n    var clients: [NUM_CLIENTS]?*nats.Client =\n        [_]?*nats.Client{null} ** NUM_CLIENTS;\n    var io_count: usize = 0;\n\n    defer {\n        for (0..io_count) |i| {\n            if (clients[i]) |c| c.deinit();\n            ios[i].deinit();\n        }\n    }\n\n    for (0..NUM_CLIENTS) |i| {\n        ios[i] = utils.newIo(allocator);\n        clients[i] = nats.Client.connect(\n            allocator,\n            ios[i].io(),\n            url,\n            .{\n                .sub_queue_size = 64,\n                .reconnect = false,\n            },\n        ) catch {\n            reportResult(\n                \"10_clients_subs\",\n                false,\n                \"connect\",\n            );\n            return;\n        };\n        io_count += 1;\n    }\n\n    const total_subs = NUM_CLIENTS * SUBS_PER;\n    const all_subs = allocator.alloc(\n        ?*nats.Subscription,\n        total_subs,\n    ) catch {\n        reportResult(\"10_clients_subs\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(all_subs);\n    @memset(all_subs, null);\n\n    defer for (all_subs) |s| if (s) |sub| sub.deinit();\n\n    var sub_count: usize = 0;\n    for (0..NUM_CLIENTS) |ci| {\n        const c = clients[ci] orelse continue;\n        for (0..SUBS_PER) |si| {\n            var sbuf: [32]u8 = undefined;\n            const subj = std.fmt.bufPrint(\n                &sbuf,\n                \"mc.{d}\",\n                .{si},\n            ) catch continue;\n            const idx = ci * SUBS_PER + si;\n            all_subs[idx] = c.subscribeSync(subj) catch\n                break;\n            sub_count += 1;\n        }\n    }\n\n    if (sub_count != total_subs) {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"subs {d}/{d}\",\n            .{ sub_count, total_subs },\n        ) catch \"count\";\n        reportResult(\"10_clients_subs\", false, msg);\n        return;\n    }\n\n    // Publisher\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"10_clients_subs\", false, \"pub con\");\n        return;\n    };\n    defer publisher.deinit();\n\n    pub_io.io().sleep(\n        .fromMilliseconds(100),\n        .awake,\n    ) catch {};\n\n    for (0..SUBS_PER) |si| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"mc.{d}\",\n            .{si},\n        ) catch continue;\n        publisher.publish(subj, \"mc\") catch {\n            reportResult(\"10_clients_subs\", false, \"pub\");\n            return;\n        };\n    }\n\n    pub_io.io().sleep(\n        .fromMilliseconds(1000),\n        .awake,\n    ) catch {};\n\n    var total_recv: usize = 0;\n    for (all_subs) |s| {\n        if (s) |sub| {\n            if (sub.nextMsgTimeout(50) catch null) |m| {\n                m.deinit();\n                total_recv += 1;\n            }\n        }\n    }\n\n    const threshold = total_subs * 100 / 100;\n    if (total_recv >= threshold) {\n        reportResult(\"10_clients_subs\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ total_recv, total_subs },\n        ) catch \"count\";\n        reportResult(\"10_clients_subs\", false, msg);\n    }\n}\n\nfn publishWithBackpressure(\n    client: *nats.Client,\n    subject: []const u8,\n    payload: []const u8,\n    test_name: []const u8,\n) bool {\n    client.publish(subject, payload) catch |err| {\n        if (err == error.PublishBufferFull) {\n            client.flush(5 * std.time.ns_per_s) catch {\n                reportResult(test_name, false, \"pub flush\");\n                return false;\n            };\n            client.publish(subject, payload) catch {\n                reportResult(test_name, false, \"publish\");\n                return false;\n            };\n            return true;\n        }\n        reportResult(test_name, false, \"publish\");\n        return false;\n    };\n    return true;\n}\n\n/// 5 publishers, 5 subscribers, 100 subjects.\npub fn testMultiPubMultiSub(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_PUB = 5;\n    const NUM_SUB = 5;\n    const NUM_SUBJECTS = 100;\n    const MSGS_PER_SUBJECT = 100;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    var sub_ios: [NUM_SUB]*utils.TestIo = undefined;\n    var sub_clients: [NUM_SUB]?*nats.Client =\n        [_]?*nats.Client{null} ** NUM_SUB;\n    var sub_io_count: usize = 0;\n\n    defer {\n        for (0..sub_io_count) |i| {\n            if (sub_clients[i]) |c| c.deinit();\n            sub_ios[i].deinit();\n        }\n    }\n\n    for (0..NUM_SUB) |i| {\n        sub_ios[i] = utils.newIo(allocator);\n        sub_clients[i] = nats.Client.connect(\n            allocator,\n            sub_ios[i].io(),\n            url,\n            .{\n                .sub_queue_size = 2048,\n                .reconnect = false,\n            },\n        ) catch {\n            reportResult(\"multi_pub_sub\", false, \"sub con\");\n            return;\n        };\n        sub_io_count += 1;\n    }\n\n    const total_subs = NUM_SUB * NUM_SUBJECTS;\n    const all_subs = allocator.alloc(\n        ?*nats.Subscription,\n        total_subs,\n    ) catch {\n        reportResult(\"multi_pub_sub\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(all_subs);\n    @memset(all_subs, null);\n    defer for (all_subs) |s| if (s) |sub| sub.deinit();\n\n    for (0..NUM_SUB) |si| {\n        const c = sub_clients[si] orelse continue;\n        for (0..NUM_SUBJECTS) |subj_i| {\n            var sbuf: [32]u8 = undefined;\n            const subj = std.fmt.bufPrint(\n                &sbuf,\n                \"mp.{d}\",\n                .{subj_i},\n            ) catch continue;\n            const idx = si * NUM_SUBJECTS + subj_i;\n            all_subs[idx] = c.subscribeSync(subj) catch\n                break;\n        }\n    }\n\n    for (sub_clients) |maybe_client| {\n        if (maybe_client) |c| {\n            c.flush(5 * std.time.ns_per_s) catch {\n                reportResult(\n                    \"multi_pub_sub\",\n                    false,\n                    \"sub flush\",\n                );\n                return;\n            };\n        }\n    }\n\n    var pub_ios: [NUM_PUB]*utils.TestIo = undefined;\n    var pub_clients: [NUM_PUB]?*nats.Client =\n        [_]?*nats.Client{null} ** NUM_PUB;\n    var pub_io_count: usize = 0;\n\n    defer {\n        for (0..pub_io_count) |i| {\n            if (pub_clients[i]) |c| c.deinit();\n            pub_ios[i].deinit();\n        }\n    }\n\n    for (0..NUM_PUB) |i| {\n        pub_ios[i] = utils.newIo(allocator);\n        pub_clients[i] = nats.Client.connect(\n            allocator,\n            pub_ios[i].io(),\n            url,\n            .{ .reconnect = false },\n        ) catch {\n            reportResult(\"multi_pub_sub\", false, \"pub con\");\n            return;\n        };\n        pub_io_count += 1;\n    }\n\n    const expected_per_sub =\n        NUM_PUB * MSGS_PER_SUBJECT;\n    var total_recv: usize = 0;\n\n    for (0..NUM_SUBJECTS) |subj_i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"mp.{d}\",\n            .{subj_i},\n        ) catch continue;\n\n        for (0..NUM_PUB) |pi| {\n            const c = pub_clients[pi] orelse continue;\n            for (0..MSGS_PER_SUBJECT) |_| {\n                if (!publishWithBackpressure(\n                    c,\n                    subj,\n                    \"mp\",\n                    \"multi_pub_sub\",\n                )) return;\n            }\n        }\n\n        for (pub_clients) |maybe_client| {\n            if (maybe_client) |c| {\n                c.flush(5 * std.time.ns_per_s) catch {\n                    reportResult(\n                        \"multi_pub_sub\",\n                        false,\n                        \"pub flush\",\n                    );\n                    return;\n                };\n            }\n        }\n\n        for (sub_clients) |maybe_client| {\n            if (maybe_client) |c| {\n                c.flush(5 * std.time.ns_per_s) catch {\n                    reportResult(\n                        \"multi_pub_sub\",\n                        false,\n                        \"sub flush\",\n                    );\n                    return;\n                };\n            }\n        }\n\n        for (0..NUM_SUB) |si| {\n            const idx = si * NUM_SUBJECTS + subj_i;\n            const sub = all_subs[idx] orelse continue;\n            for (0..expected_per_sub) |_| {\n                if (sub.nextMsgTimeout(1000) catch null) |m| {\n                    m.deinit();\n                    total_recv += 1;\n                } else break;\n            }\n        }\n    }\n\n    const total_expected =\n        NUM_SUB * NUM_SUBJECTS * expected_per_sub;\n    const threshold = total_expected * 100 / 100;\n    if (total_recv >= threshold) {\n        reportResult(\"multi_pub_sub\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d} (min {d})\",\n            .{ total_recv, total_expected, threshold },\n        ) catch \"count\";\n        reportResult(\"multi_pub_sub\", false, msg);\n    }\n}\n// --- C. Message Size Edge Cases ---\n\n/// Tests payload sizes at slab tier boundaries.\npub fn testPayloadSizes(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"payload_sizes\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"sz.test\") catch {\n        reportResult(\"payload_sizes\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    const sizes = [_]usize{\n        0,     1,     255,  256,  257,\n        511,   512,   513,  1023, 1024,\n        1025,  4095,  4096, 4097, 16383,\n        16384, 16385,\n    };\n\n    for (sizes) |size| {\n        const payload = if (size > 0)\n            allocator.alloc(u8, size) catch {\n                reportResult(\n                    \"payload_sizes\",\n                    false,\n                    \"alloc\",\n                );\n                return;\n            }\n        else\n            allocator.alloc(u8, 0) catch {\n                reportResult(\n                    \"payload_sizes\",\n                    false,\n                    \"alloc0\",\n                );\n                return;\n            };\n\n        defer allocator.free(payload);\n\n        for (payload, 0..) |*b, i| {\n            b.* = @truncate(i);\n        }\n\n        client.publish(\"sz.test\", payload) catch {\n            var buf: [48]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"pub failed sz={d}\",\n                .{size},\n            ) catch \"pub\";\n            reportResult(\"payload_sizes\", false, msg);\n            return;\n        };\n\n        const recv = sub.nextMsgTimeout(2000) catch {\n            var buf: [48]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"timeout sz={d}\",\n                .{size},\n            ) catch \"timeout\";\n            reportResult(\"payload_sizes\", false, msg);\n            return;\n        };\n\n        if (recv) |m| {\n            defer m.deinit();\n            if (m.data.len != size) {\n                var buf: [48]u8 = undefined;\n                const msg = std.fmt.bufPrint(\n                    &buf,\n                    \"sz {d}!={d}\",\n                    .{ m.data.len, size },\n                ) catch \"mismatch\";\n                reportResult(\n                    \"payload_sizes\",\n                    false,\n                    msg,\n                );\n                return;\n            }\n            if (size > 0) {\n                for (m.data, 0..) |b, i| {\n                    const expected: u8 = @truncate(i);\n                    if (b != expected) {\n                        reportResult(\n                            \"payload_sizes\",\n                            false,\n                            \"corrupt\",\n                        );\n                        return;\n                    }\n                }\n            }\n        } else {\n            var buf: [48]u8 = undefined;\n            const msg = std.fmt.bufPrint(\n                &buf,\n                \"null sz={d}\",\n                .{size},\n            ) catch \"null\";\n            reportResult(\"payload_sizes\", false, msg);\n            return;\n        }\n    }\n\n    reportResult(\"payload_sizes\", true, \"\");\n}\n\n/// 1MB payload roundtrip with integrity check.\npub fn testMaxPayload1MB(\n    allocator: std.mem.Allocator,\n) void {\n    const SIZE = 1_048_576;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"max_payload_1mb\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"big.1mb\") catch {\n        reportResult(\"max_payload_1mb\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    const payload = allocator.alloc(u8, SIZE) catch {\n        reportResult(\"max_payload_1mb\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(payload);\n\n    for (payload, 0..) |*b, i| {\n        b.* = @truncate(i);\n    }\n\n    client.publish(\"big.1mb\", payload) catch |err| {\n        var buf: [128]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"publish: {s}\",\n            .{@errorName(err)},\n        ) catch \"publish: ?\";\n        reportResult(\n            \"max_payload_1mb\",\n            false,\n            detail,\n        );\n        return;\n    };\n\n    if (sub.nextMsgTimeout(5000) catch null) |m| {\n        defer m.deinit();\n        if (m.data.len != SIZE) {\n            reportResult(\n                \"max_payload_1mb\",\n                false,\n                \"wrong size\",\n            );\n            return;\n        }\n        var ok = true;\n        const checks = [_]usize{\n            0,        1,        255, 256, 1023, 1024,\n            SIZE / 2, SIZE - 1,\n        };\n        for (checks) |idx| {\n            const expected: u8 = @truncate(idx);\n            if (m.data[idx] != expected) {\n                ok = false;\n                break;\n            }\n        }\n        if (ok) {\n            reportResult(\"max_payload_1mb\", true, \"\");\n        } else {\n            reportResult(\n                \"max_payload_1mb\",\n                false,\n                \"corrupt\",\n            );\n        }\n    } else {\n        reportResult(\"max_payload_1mb\", false, \"no msg\");\n    }\n}\n\n/// Publish over max_payload, expect error.\npub fn testOverMaxPayload(\n    allocator: std.mem.Allocator,\n) void {\n    const SIZE = 1_048_576 + 1;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"over_max_payload\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    const payload = allocator.alloc(u8, SIZE) catch {\n        reportResult(\"over_max_payload\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'X');\n\n    if (client.publish(\"over.big\", payload)) |_| {\n        reportResult(\n            \"over_max_payload\",\n            false,\n            \"no error\",\n        );\n    } else |_| {\n        if (client.isConnected()) {\n            reportResult(\"over_max_payload\", true, \"\");\n        } else {\n            reportResult(\n                \"over_max_payload\",\n                false,\n                \"disconnected\",\n            );\n        }\n    }\n}\n\n// --- D. Publishing Stress ---\n\n/// 100K messages burst publish.\npub fn testBurstPublish100K(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_MSGS = 100_000;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"burst_100k\", false, \"pub connect\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{\n            .sub_queue_size = 131072,\n            .reconnect = false,\n        },\n    ) catch {\n        reportResult(\"burst_100k\", false, \"sub connect\");\n        return;\n    };\n    defer sub_client.deinit();\n\n    const sub = sub_client.subscribeSync(\"burst\") catch {\n        reportResult(\"burst_100k\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub_io.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    var payload: [128]u8 = undefined;\n    @memset(&payload, 'B');\n\n    for (0..NUM_MSGS) |_| {\n        pub_client.publish(\"burst\", &payload) catch {\n            reportResult(\"burst_100k\", false, \"publish\");\n            return;\n        };\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(3000),\n        .awake,\n    ) catch {};\n\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        if (sub.nextMsgTimeout(10) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    const threshold = NUM_MSGS * 100 / 100;\n    if (received >= threshold) {\n        reportResult(\"burst_100k\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ received, NUM_MSGS },\n        ) catch \"count\";\n        reportResult(\"burst_100k\", false, msg);\n    }\n}\n\n/// 1000 x 64KB messages (64 MB total).\npub fn testLargePayloadBurst(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM_MSGS = 1000;\n    const SIZE = 64 * 1024;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"large_burst\", false, \"pub connect\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{\n            .sub_queue_size = 2048,\n            .reconnect = false,\n        },\n    ) catch {\n        reportResult(\"large_burst\", false, \"sub connect\");\n        return;\n    };\n    defer sub_client.deinit();\n\n    const sub = sub_client.subscribeSync(\n        \"large.burst\",\n    ) catch {\n        reportResult(\"large_burst\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    const payload = allocator.alloc(u8, SIZE) catch {\n        reportResult(\"large_burst\", false, \"alloc\");\n        return;\n    };\n    defer allocator.free(payload);\n    @memset(payload, 'L');\n\n    sub_io.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    for (0..NUM_MSGS) |_| {\n        pub_client.publish(\n            \"large.burst\",\n            payload,\n        ) catch {\n            reportResult(\"large_burst\", false, \"pub\");\n            return;\n        };\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(3000),\n        .awake,\n    ) catch {};\n\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        if (sub.nextMsgTimeout(50) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    const threshold = NUM_MSGS * 100 / 100;\n    if (received >= threshold) {\n        reportResult(\"large_burst\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ received, NUM_MSGS },\n        ) catch \"count\";\n        reportResult(\"large_burst\", false, msg);\n    }\n}\n\n/// 5K different subjects with wildcard subscriber.\npub fn testManySubjectsPublish(\n    allocator: std.mem.Allocator,\n) void {\n    const NUM = 5_000;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{\n            .sub_queue_size = 8192,\n            .reconnect = false,\n        },\n    ) catch {\n        reportResult(\"many_subj_pub\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"many.>\") catch {\n        reportResult(\"many_subj_pub\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub_io.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"many_subj_pub\", false, \"pub con\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    for (0..NUM) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"many.{d}\",\n            .{i},\n        ) catch continue;\n        pub_client.publish(subj, \"m\") catch {\n            reportResult(\"many_subj_pub\", false, \"pub\");\n            return;\n        };\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(2000),\n        .awake,\n    ) catch {};\n\n    var received: usize = 0;\n    for (0..NUM) |_| {\n        if (sub.nextMsgTimeout(10) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    const threshold = NUM * 100 / 100;\n    if (received >= threshold) {\n        reportResult(\"many_subj_pub\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ received, NUM },\n        ) catch \"count\";\n        reportResult(\"many_subj_pub\", false, msg);\n    }\n}\n\n// --- E. Subscription Queue Pressure ---\n\n/// Slow consumer: queue overflows, verify drops.\npub fn testSlowConsumer(\n    allocator: std.mem.Allocator,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .sub_queue_size = 64, .reconnect = false },\n    ) catch {\n        reportResult(\"slow_consumer\", false, \"connect\");\n        return;\n    };\n    defer sub_client.deinit();\n\n    const sub = sub_client.subscribeSync(\"slow\") catch {\n        reportResult(\"slow_consumer\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"slow_consumer\", false, \"pub con\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    sub_io.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    for (0..200) |_| {\n        pub_client.publish(\"slow\", \"flood\") catch {};\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(500),\n        .awake,\n    ) catch {};\n\n    const drops = sub.dropped();\n    if (drops > 0) {\n        reportResult(\"slow_consumer\", true, \"\");\n    } else {\n        reportResult(\n            \"slow_consumer\",\n            false,\n            \"no drops\",\n        );\n    }\n}\n\n/// Fill queue, drain, refill, verify recovery.\npub fn testQueueFillAndRecover(\n    allocator: std.mem.Allocator,\n) void {\n    const Q_SIZE: usize = 128;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const sub_client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{\n            .sub_queue_size = @intCast(Q_SIZE),\n            .reconnect = false,\n        },\n    ) catch {\n        reportResult(\"queue_recover\", false, \"connect\");\n        return;\n    };\n    defer sub_client.deinit();\n\n    const sub = sub_client.subscribeSync(\"qr\") catch {\n        reportResult(\"queue_recover\", false, \"sub\");\n        return;\n    };\n    defer sub.deinit();\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const pub_client = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"queue_recover\", false, \"pub con\");\n        return;\n    };\n    defer pub_client.deinit();\n\n    sub_io.io().sleep(\n        .fromMilliseconds(50),\n        .awake,\n    ) catch {};\n\n    for (0..Q_SIZE) |_| {\n        pub_client.publish(\"qr\", \"fill1\") catch {};\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(200),\n        .awake,\n    ) catch {};\n\n    var batch1: usize = 0;\n    for (0..Q_SIZE) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            batch1 += 1;\n        } else break;\n    }\n\n    for (0..Q_SIZE) |_| {\n        pub_client.publish(\"qr\", \"fill2\") catch {};\n    }\n\n    sub_io.io().sleep(\n        .fromMilliseconds(200),\n        .awake,\n    ) catch {};\n\n    var batch2: usize = 0;\n    for (0..Q_SIZE) |_| {\n        if (sub.nextMsgTimeout(500) catch null) |m| {\n            m.deinit();\n            batch2 += 1;\n        } else break;\n    }\n\n    if (batch1 > 0 and batch2 > 0) {\n        reportResult(\"queue_recover\", true, \"\");\n    } else {\n        var buf: [48]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"b1={d} b2={d}\",\n            .{ batch1, batch2 },\n        ) catch \"count\";\n        reportResult(\"queue_recover\", false, msg);\n    }\n}\n\n// --- F. SidMap Stress ---\n\n/// Tombstone accumulation stress test.\npub fn testSidMapTombstoneStress(\n    allocator: std.mem.Allocator,\n) void {\n    const ROUNDS = 10;\n    const PER_ROUND = 200;\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .sub_queue_size = 64, .reconnect = false },\n    ) catch {\n        reportResult(\"sidmap_tombstone\", false, \"connect\");\n        return;\n    };\n    defer client.deinit();\n\n    for (0..ROUNDS) |round| {\n        var round_subs: [PER_ROUND]?*nats.Subscription =\n            [_]?*nats.Subscription{null} ** PER_ROUND;\n\n        for (0..PER_ROUND) |i| {\n            var sbuf: [48]u8 = undefined;\n            const subj = std.fmt.bufPrint(\n                &sbuf,\n                \"tomb.{d}.{d}\",\n                .{ round, i },\n            ) catch continue;\n            round_subs[i] =\n                client.subscribeSync(subj) catch |e| {\n                    for (&round_subs) |*s| {\n                        if (s.*) |sub| sub.deinit();\n                    }\n                    var buf: [64]u8 = undefined;\n                    const msg = std.fmt.bufPrint(\n                        &buf,\n                        \"r={d} i={d} {s}\",\n                        .{ round, i, @errorName(e) },\n                    ) catch \"sub\";\n                    reportResult(\n                        \"sidmap_tombstone\",\n                        false,\n                        msg,\n                    );\n                    return;\n                };\n        }\n\n        for (&round_subs) |*s| {\n            if (s.*) |sub| sub.deinit();\n        }\n        // Flush UNSUB commands between rounds\n\n    }\n\n    // Final: subscribe 100 fresh, verify pub/sub\n    var final_subs: [100]?*nats.Subscription =\n        [_]?*nats.Subscription{null} ** 100;\n\n    defer for (&final_subs) |*s| {\n        if (s.*) |sub| sub.deinit();\n    };\n\n    for (0..100) |i| {\n        var sbuf: [32]u8 = undefined;\n        const subj = std.fmt.bufPrint(\n            &sbuf,\n            \"tomb.final.{d}\",\n            .{i},\n        ) catch continue;\n        final_subs[i] = client.subscribeSync(\n            subj,\n        ) catch {\n            reportResult(\n                \"sidmap_tombstone\",\n                false,\n                \"final sub\",\n            );\n            return;\n        };\n    }\n\n    client.publish(\"tomb.final.0\", \"ok\") catch {\n        reportResult(\n            \"sidmap_tombstone\",\n            false,\n            \"publish\",\n        );\n        return;\n    };\n\n    if (final_subs[0]) |sub| {\n        if (sub.nextMsgTimeout(1000) catch null) |m| {\n            m.deinit();\n            reportResult(\"sidmap_tombstone\", true, \"\");\n        } else {\n            reportResult(\n                \"sidmap_tombstone\",\n                false,\n                \"no msg\",\n            );\n        }\n    } else {\n        reportResult(\n            \"sidmap_tombstone\",\n            false,\n            \"null sub\",\n        );\n    }\n}\n\n/// Runs all stress subscription tests.\npub fn runAll(allocator: std.mem.Allocator) void {\n    // A. Massive Subscription Tests\n    testFiveThousandSubs(allocator);\n    testSubUnsubChurn(allocator);\n    testSubsThenResubscribe(allocator);\n    testWildcardFanOut(allocator);\n\n    // B. Multi-Client Tests\n    testTenClientsManySubs(allocator);\n    testMultiPubMultiSub(allocator);\n\n    // C. Message Size Edge Cases\n    testPayloadSizes(allocator);\n    testMaxPayload1MB(allocator);\n    testOverMaxPayload(allocator);\n\n    // D. Publishing Stress\n    testBurstPublish100K(allocator);\n    testLargePayloadBurst(allocator);\n    testManySubjectsPublish(allocator);\n\n    // E. Queue Pressure\n    testSlowConsumer(allocator);\n    testQueueFillAndRecover(allocator);\n\n    // F. SidMap Stress\n    testSidMapTombstoneStress(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/subscribe.zig",
    "content": "//! Subscribe Tests for NATS Client\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst test_token = utils.test_token;\nconst ServerManager = utils.ServerManager;\n\npub fn testClientManySubs(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_many_subs\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .sub_queue_size = 32, .reconnect = false },\n    ) catch {\n        reportResult(\"client_many_subs\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const NUM_SUBS = 5;\n    var subs: [NUM_SUBS]*nats.Client.Sub = undefined;\n    var sub_buf: [NUM_SUBS][32]u8 = undefined;\n    var topics: [NUM_SUBS][]const u8 = undefined;\n\n    for (0..NUM_SUBS) |i| {\n        topics[i] = std.fmt.bufPrint(\n            &sub_buf[i],\n            \"many.{d}\",\n            .{i},\n        ) catch \"err\";\n        subs[i] = client.subscribeSync(topics[i]) catch {\n            reportResult(\"client_many_subs\", false, \"sub failed\");\n            return;\n        };\n    }\n    defer for (subs) |s| s.deinit();\n\n    client.flush(500_000_000) catch {};\n\n    for (topics) |t| {\n        publisher.publish(t, \"hello\") catch {};\n    }\n\n    var received: usize = 0;\n    for (subs) |s| {\n        var future = sub_io.io().async(\n            nats.Client.Sub.nextMsg,\n            .{s},\n        );\n        defer if (future.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n        if (future.await(sub_io.io())) |_| {\n            received += 1;\n        } else |_| {}\n    }\n\n    if (received == NUM_SUBS) {\n        reportResult(\"client_many_subs\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/{d}\",\n            .{ received, NUM_SUBS },\n        ) catch \"e\";\n        reportResult(\"client_many_subs\", false, msg);\n    }\n}\n\npub fn testClientWildcard(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_wildcard\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_wildcard\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"wild.*\") catch {\n        reportResult(\"client_wildcard\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    publisher.publish(\"wild.a\", \"msg-a\") catch {\n        reportResult(\"client_wildcard\", false, \"pub a failed\");\n        return;\n    };\n    publisher.publish(\"wild.b\", \"msg-b\") catch {\n        reportResult(\"client_wildcard\", false, \"pub b failed\");\n        return;\n    };\n    publisher.publish(\"wild.c\", \"msg-c\") catch {\n        reportResult(\"client_wildcard\", false, \"pub c failed\");\n        return;\n    };\n\n    const NUM_MSGS = 3;\n    var received: usize = 0;\n    for (0..NUM_MSGS) |_| {\n        var future = sub_io.io().async(\n            nats.Client.Sub.nextMsg,\n            .{sub},\n        );\n        defer if (future.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n        if (future.await(sub_io.io())) |_| {\n            received += 1;\n        } else |_| {}\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"client_wildcard\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const msg = std.fmt.bufPrint(&buf, \"got {d}/3\", .{received}) catch \"e\";\n        reportResult(\"client_wildcard\", false, msg);\n    }\n}\n\npub fn testClientDuplicateSubs(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_dup_subs\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_dup_subs\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub1 = client.subscribeSync(\"dup\") catch {\n        reportResult(\"client_dup_subs\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    const sub2 = client.subscribeSync(\"dup\") catch {\n        reportResult(\"client_dup_subs\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    sub_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    publisher.publish(\"dup\", \"hello\") catch {};\n\n    var future1 = sub_io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub1},\n    );\n    defer if (future1.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n    var future2 = sub_io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub2},\n    );\n    defer if (future2.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n    const got1 = if (future1.await(sub_io.io())) |_| true else |_| false;\n    const got2 = if (future2.await(sub_io.io())) |_| true else |_| false;\n\n    if (got1 and got2) {\n        reportResult(\"client_dup_subs\", true, \"\");\n    } else {\n        reportResult(\"client_dup_subs\", false, \"not both received\");\n    }\n}\n\npub fn testClientQueueGroup(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const pub_io = utils.newIo(allocator);\n    defer pub_io.deinit();\n\n    const publisher = nats.Client.connect(\n        allocator,\n        pub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_queue_group\", false, \"pub connect failed\");\n        return;\n    };\n    defer publisher.deinit();\n\n    const sub_io = utils.newIo(allocator);\n    defer sub_io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        sub_io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"client_queue_group\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.queueSubscribeSync(\"qg\", \"workers\") catch {\n        reportResult(\"client_queue_group\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    sub_io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    publisher.publish(\"qg\", \"task\") catch {};\n\n    var future = sub_io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(sub_io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(sub_io.io())) |_| {\n        reportResult(\"client_queue_group\", true, \"\");\n        return;\n    } else |_| {}\n\n    reportResult(\"client_queue_group\", false, \"no message\");\n}\n\npub fn testWildcardMatching(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_matching\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"wc.*\") catch {\n        reportResult(\"wildcard_matching\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"wc.test\", \"msg\") catch {};\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |_| {\n        reportResult(\"wildcard_matching\", true, \"\");\n        return;\n    } else |_| {}\n\n    reportResult(\"wildcard_matching\", false, \"no match\");\n}\n\npub fn testWildcardGreater(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_greater\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"gt.>\") catch {\n        reportResult(\"wildcard_greater\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"gt.a.b.c\", \"msg\") catch {};\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |_| {\n        reportResult(\"wildcard_greater\", true, \"\");\n        return;\n    } else |_| {}\n\n    reportResult(\"wildcard_greater\", false, \"no match\");\n}\n\npub fn testSubjectCaseSensitivity(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"subject_case\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"case.test\") catch {\n        reportResult(\"subject_case\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"case.test\", \"msg\") catch {};\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |_| {\n        reportResult(\"subject_case\", true, \"\");\n        return;\n    } else |_| {}\n\n    reportResult(\"subject_case\", false, \"no match\");\n}\n\npub fn testUnsubscribeStopsDelivery(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"unsub_stops\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"unsub.test\") catch {\n        reportResult(\"unsub_stops\", false, \"sub failed\");\n        return;\n    };\n\n    sub.unsubscribe() catch {};\n    sub.deinit();\n\n    client.publish(\"unsub.test\", \"msg\") catch {};\n    io.io().sleep(.fromMilliseconds(10), .awake) catch {};\n\n    if (client.isConnected()) {\n        reportResult(\"unsub_stops\", true, \"\");\n    } else {\n        reportResult(\"unsub_stops\", false, \"disconnected\");\n    }\n}\n\npub fn testHierarchicalSubject(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"hierarchical\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const subject = \"a.b.c.d.e.f.g.h\";\n    const sub = client.subscribeSync(subject) catch {\n        reportResult(\"hierarchical\", false, \"sub failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(subject, \"deep\") catch {\n        reportResult(\"hierarchical\", false, \"pub failed\");\n        return;\n    };\n\n    var future = io.io().async(\n        nats.Client.Sub.nextMsg,\n        .{sub},\n    );\n    defer if (future.cancel(io.io())) |m| m.deinit() else |_| {};\n\n    if (future.await(io.io())) |_| {\n        reportResult(\"hierarchical\", true, \"\");\n        return;\n    } else |_| {}\n\n    reportResult(\"hierarchical\", false, \"no message\");\n}\n\npub fn testUnsubscribeWithPending(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"unsub_with_pending\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"pending.test\") catch {\n        reportResult(\"unsub_with_pending\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    for (0..5) |_| {\n        client.publish(\"pending.test\", \"msg\") catch {};\n    }\n    io.io().sleep(.fromMilliseconds(50), .awake) catch {};\n\n    sub.unsubscribe() catch {\n        reportResult(\"unsub_with_pending\", false, \"unsubscribe failed\");\n        return;\n    };\n\n    reportResult(\"unsub_with_pending\", true, \"\");\n}\n\npub fn testSubscribeAfterDisconnect(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"sub_after_disconnect\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    _ = client.drain() catch {\n        reportResult(\"sub_after_disconnect\", false, \"drain failed\");\n        return;\n    };\n\n    const result = client.subscribeSync(\"test.sub\");\n\n    if (result) |sub| {\n        sub.deinit();\n        reportResult(\"sub_after_disconnect\", false, \"should have failed\");\n    } else |_| {\n        reportResult(\"sub_after_disconnect\", true, \"\");\n    }\n}\n\npub fn testSubscriptionQueueCapacity(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"sub_queue_cap\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"qcap.test\") catch {\n        reportResult(\"sub_queue_cap\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const NUM_MSGS = 100;\n    for (0..NUM_MSGS) |_| {\n        client.publish(\"qcap.test\", \"qcap\") catch {\n            reportResult(\"sub_queue_cap\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    var received: u32 = 0;\n    for (0..NUM_MSGS) |_| {\n        const msg = sub.nextMsgTimeout(200) catch break;\n        if (msg) |m| {\n            m.deinit();\n            received += 1;\n        } else break;\n    }\n\n    if (received == NUM_MSGS) {\n        reportResult(\"sub_queue_cap\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"got {d}/100\",\n            .{received},\n        ) catch \"e\";\n        reportResult(\"sub_queue_cap\", false, detail);\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator) void {\n    testClientManySubs(allocator);\n    testClientWildcard(allocator);\n    testClientDuplicateSubs(allocator);\n    testClientQueueGroup(allocator);\n    testWildcardMatching(allocator);\n    testWildcardGreater(allocator);\n    testSubjectCaseSensitivity(allocator);\n    testUnsubscribeStopsDelivery(allocator);\n    testHierarchicalSubject(allocator);\n    testUnsubscribeWithPending(allocator);\n    testSubscribeAfterDisconnect(allocator);\n    testSubscriptionQueueCapacity(allocator);\n}\n"
  },
  {
    "path": "src/testing/client/tests.zig",
    "content": "//! Client Test Suite\n//!\n//! Re-exports all client test modules.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst ServerManager = utils.ServerManager;\n\npub const basic = @import(\"basic.zig\");\npub const publish = @import(\"publish.zig\");\npub const subscribe = @import(\"subscribe.zig\");\npub const multi_client = @import(\"multi_client.zig\");\npub const stats = @import(\"stats.zig\");\npub const getters = @import(\"getters.zig\");\npub const stress = @import(\"stress.zig\");\npub const auth = @import(\"auth.zig\");\npub const connection = @import(\"connection.zig\");\npub const request_reply = @import(\"request_reply.zig\");\npub const drain = @import(\"drain.zig\");\npub const edge_cases = @import(\"edge_cases.zig\");\npub const wildcard = @import(\"wildcard.zig\");\npub const queue = @import(\"queue.zig\");\npub const server = @import(\"server.zig\");\npub const protocol = @import(\"protocol.zig\");\npub const concurrency = @import(\"concurrency.zig\");\npub const reconnect = @import(\"reconnect.zig\");\npub const error_handling = @import(\"error_handling.zig\");\npub const headers = @import(\"headers.zig\");\npub const nkey = @import(\"nkey.zig\");\npub const jwt = @import(\"jwt.zig\");\npub const tls = @import(\"tls.zig\");\npub const state_notifications = @import(\"state_notifications.zig\");\npub const advanced = @import(\"advanced.zig\");\npub const flush_confirmed = @import(\"flush_confirmed.zig\");\npub const autoflush = @import(\"autoflush.zig\");\npub const async_patterns = @import(\"async_patterns.zig\");\npub const dynamic_jwt = @import(\"dynamic_jwt.zig\");\npub const callback = @import(\"callback.zig\");\npub const stress_subs = @import(\"stress_subs.zig\");\npub const jetstream = @import(\"jetstream.zig\");\npub const multithread = @import(\"multithread.zig\");\npub const micro = @import(\"micro.zig\");\n\n/// Runs all client tests.\npub fn runAll(allocator: std.mem.Allocator, manager: *ServerManager) void {\n    basic.runAll(allocator);\n    publish.runAll(allocator);\n    subscribe.runAll(allocator);\n    multi_client.runAll(allocator);\n    stats.runAll(allocator);\n    getters.runAll(allocator);\n    stress.runAll(allocator);\n    auth.runAll(allocator);\n    connection.runAll(allocator, manager);\n    request_reply.runAll(allocator);\n    drain.runAll(allocator);\n    edge_cases.runAll(allocator);\n    wildcard.runAll(allocator);\n    queue.runAll(allocator);\n    server.runAll(allocator);\n    protocol.runAll(allocator);\n    concurrency.runAll(allocator);\n    nkey.runAll(allocator);\n    jwt.runAll(allocator);\n    tls.runAll(allocator, manager);\n    error_handling.runAll(allocator);\n    headers.runAll(allocator);\n    state_notifications.runAll(allocator);\n    advanced.runAll(allocator);\n    flush_confirmed.runAll(allocator);\n    autoflush.runAll(allocator, manager);\n    async_patterns.runAll(allocator, manager);\n    reconnect.runAll(allocator, manager);\n    dynamic_jwt.runAll(allocator, manager);\n    callback.runAll(allocator);\n    stress_subs.runAll(allocator);\n    jetstream.runAll(allocator, manager);\n    jetstream.runReconnectTests(allocator, manager);\n    multithread.runAll(allocator);\n    micro.runAll(allocator, manager);\n}\n"
  },
  {
    "path": "src/testing/client/tls.zig",
    "content": "//! TLS Tests for NATS Client\n//!\n//! Tests TLS connection functionality including:\n//! - TLS connection with CA certificate\n//! - Insecure skip verify mode\n//! - Pub/sub over TLS\n//! - TLS reconnection\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatTlsUrl = utils.formatTlsUrl;\nconst tls_port = utils.tls_port;\nconst ServerManager = utils.ServerManager;\nconst TestServer = utils.server_manager.TestServer;\n\nconst Dir = std.Io.Dir;\nconst tls_plain_probe_port: u16 = 14240;\n\n/// Returns absolute path to CA file. Caller owns returned memory.\nfn getCaFilePath(allocator: std.mem.Allocator, io: std.Io) ?[:0]const u8 {\n    return Dir.realPathFileAlloc(.cwd(), io, utils.tls_ca_file, allocator) catch null;\n}\n\npub fn testTlsConnection(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const ca_path = getCaFilePath(allocator, io) orelse {\n        reportResult(\"tls_connection\", false, \"CA file not found\");\n        return;\n    };\n    defer allocator.free(ca_path);\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .tls_ca_file = ca_path,\n    }) catch |err| {\n        var err_buf: [64]u8 = undefined;\n        const err_msg = std.fmt.bufPrint(\n            &err_buf,\n            \"connect failed: {}\",\n            .{err},\n        ) catch \"connect failed\";\n        reportResult(\"tls_connection\", false, err_msg);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        const info = client.serverInfo();\n        if (info != null and info.?.tls_required) {\n            reportResult(\"tls_connection\", true, \"\");\n        } else {\n            reportResult(\"tls_connection\", false, \"server not TLS required\");\n        }\n    } else {\n        reportResult(\"tls_connection\", false, \"not connected\");\n    }\n}\n\npub fn testTlsInsecureSkipVerify(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .tls_insecure_skip_verify = true,\n    }) catch |err| {\n        var err_buf: [64]u8 = undefined;\n        const err_msg = std.fmt.bufPrint(\n            &err_buf,\n            \"connect failed: {}\",\n            .{err},\n        ) catch \"connect failed\";\n        reportResult(\"tls_insecure_skip_verify\", false, err_msg);\n        return;\n    };\n    defer client.deinit();\n\n    if (client.isConnected()) {\n        reportResult(\"tls_insecure_skip_verify\", true, \"\");\n    } else {\n        reportResult(\"tls_insecure_skip_verify\", false, \"not connected\");\n    }\n}\n\npub fn testTlsPubSub(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const ca_path = getCaFilePath(allocator, io) orelse {\n        reportResult(\"tls_pubsub\", false, \"CA file not found\");\n        return;\n    };\n    defer allocator.free(ca_path);\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .tls_ca_file = ca_path,\n    }) catch {\n        reportResult(\"tls_pubsub\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"tls.test.subject\") catch {\n        reportResult(\"tls_pubsub\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const test_msg = \"encrypted message over TLS\";\n    client.publish(\"tls.test.subject\", test_msg) catch {\n        reportResult(\"tls_pubsub\", false, \"publish failed\");\n        return;\n    };\n\n    client.flush(500_000_000) catch {};\n\n    if (sub.nextMsgTimeout(1000) catch null) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.data, test_msg)) {\n            reportResult(\"tls_pubsub\", true, \"\");\n        } else {\n            reportResult(\"tls_pubsub\", false, \"message mismatch\");\n        }\n    } else {\n        reportResult(\"tls_pubsub\", false, \"no message received\");\n    }\n}\n\npub fn testTlsReconnect(\n    allocator: std.mem.Allocator,\n    manager: *ServerManager,\n) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const ca_path = getCaFilePath(allocator, io) orelse {\n        reportResult(\"tls_reconnect\", false, \"CA file not found\");\n        return;\n    };\n    defer allocator.free(ca_path);\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = true,\n        .max_reconnect_attempts = 10,\n        .reconnect_wait_ms = 100,\n        .reconnect_wait_max_ms = 1000,\n        .tls_ca_file = ca_path,\n    }) catch {\n        reportResult(\"tls_reconnect\", false, \"initial connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    if (!client.isConnected()) {\n        reportResult(\"tls_reconnect\", false, \"not connected initially\");\n        return;\n    }\n\n    // Find TLS server index (last started server)\n    const tls_server_idx = manager.count() - 1;\n\n    manager.stopServer(tls_server_idx, io);\n    io.sleep(.fromMilliseconds(200), .awake) catch {};\n\n    _ = manager.startServer(allocator, io, .{\n        .port = tls_port,\n        .config_file = utils.tls_config_file,\n    }) catch {\n        reportResult(\"tls_reconnect\", false, \"server restart failed\");\n        return;\n    };\n\n    io.sleep(.fromMilliseconds(500), .awake) catch {};\n\n    client.publish(\"tls.reconnect.test\", \"ping\") catch {\n        reportResult(\"tls_reconnect\", false, \"publish after restart failed\");\n        return;\n    };\n\n    if (client.isConnected()) {\n        reportResult(\"tls_reconnect\", true, \"\");\n    } else {\n        reportResult(\"tls_reconnect\", false, \"not reconnected\");\n    }\n}\n\npub fn testTlsServerInfo(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const ca_path = getCaFilePath(allocator, io) orelse {\n        reportResult(\"tls_server_info\", false, \"CA file not found\");\n        return;\n    };\n    defer allocator.free(ca_path);\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .tls_ca_file = ca_path,\n    }) catch {\n        reportResult(\"tls_server_info\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const info = client.serverInfo();\n    if (info == null) {\n        reportResult(\"tls_server_info\", false, \"no server info\");\n        return;\n    }\n\n    if (info.?.tls_required) {\n        reportResult(\"tls_server_info\", true, \"\");\n    } else {\n        reportResult(\"tls_server_info\", false, \"tls_required not set\");\n    }\n}\n\npub fn testTlsMultipleMessages(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_port);\n\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    const ca_path = getCaFilePath(allocator, io) orelse {\n        reportResult(\"tls_multiple_msgs\", false, \"CA file not found\");\n        return;\n    };\n    defer allocator.free(ca_path);\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .tls_ca_file = ca_path,\n    }) catch {\n        reportResult(\"tls_multiple_msgs\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"tls.multi.>\") catch {\n        reportResult(\"tls_multiple_msgs\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    const msg_count: usize = 100;\n    for (0..msg_count) |i| {\n        var subject_buf: [32]u8 = undefined;\n        const subject = std.fmt.bufPrint(\n            &subject_buf,\n            \"tls.multi.{d}\",\n            .{i},\n        ) catch \"tls.multi.x\";\n        client.publish(subject, \"data\") catch {\n            reportResult(\"tls_multiple_msgs\", false, \"publish failed\");\n            return;\n        };\n    }\n\n    client.flush(500_000_000) catch {};\n\n    var received: usize = 0;\n    for (0..msg_count) |_| {\n        if (sub.nextMsgTimeout(100) catch null) |m| {\n            m.deinit();\n            received += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (received == msg_count) {\n        reportResult(\"tls_multiple_msgs\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"{d}/{d} received\",\n            .{ received, msg_count },\n        ) catch \"partial\";\n        reportResult(\"tls_multiple_msgs\", false, detail);\n    }\n}\n\npub fn testTlsSchemeRejectsPlainServer(allocator: std.mem.Allocator) void {\n    const threaded = utils.newIo(allocator);\n    defer threaded.deinit();\n    const io = threaded.io();\n\n    var server = TestServer.start(allocator, io, .{\n        .port = tls_plain_probe_port,\n    }) catch {\n        reportResult(\n            \"tls_scheme_rejects_plain_server\",\n            false,\n            \"plain server start failed\",\n        );\n        return;\n    };\n    defer server.deinit(io);\n\n    var url_buf: [64]u8 = undefined;\n    const url = formatTlsUrl(&url_buf, tls_plain_probe_port);\n\n    const client = nats.Client.connect(allocator, io, url, .{\n        .reconnect = false,\n        .connect_timeout_ns = 500 * std.time.ns_per_ms,\n    });\n    if (client) |c| {\n        c.deinit();\n        reportResult(\n            \"tls_scheme_rejects_plain_server\",\n            false,\n            \"tls:// connected without TLS\",\n        );\n    } else |_| {\n        reportResult(\"tls_scheme_rejects_plain_server\", true, \"\");\n    }\n}\n\npub fn runAll(allocator: std.mem.Allocator, manager: *ServerManager) void {\n    testTlsConnection(allocator);\n    testTlsInsecureSkipVerify(allocator);\n    testTlsPubSub(allocator);\n    testTlsServerInfo(allocator);\n    testTlsMultipleMessages(allocator);\n    testTlsSchemeRejectsPlainServer(allocator);\n    testTlsReconnect(allocator, manager);\n}\n"
  },
  {
    "path": "src/testing/client/wildcard.zig",
    "content": "//! Wildcard Tests for NATS Client\n//!\n//! Tests for wildcard subscriptions (* and >) and pattern matching.\n\nconst std = @import(\"std\");\nconst utils = @import(\"../test_utils.zig\");\nconst nats = utils.nats;\n\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst test_port = utils.test_port;\n\npub fn testWildcardSubscribe(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_subscribe\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Test * wildcard\n    const sub1 = client.subscribeSync(\"wild.*\") catch {\n        reportResult(\"wildcard_subscribe\", false, \"* wildcard failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    // Test > wildcard\n    const sub2 = client.subscribeSync(\"wild.>\") catch {\n        reportResult(\"wildcard_subscribe\", false, \"> wildcard failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    reportResult(\"wildcard_subscribe\", true, \"\");\n}\n\npub fn testWildcardMatching(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_matching\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Subscribe to foo.*\n    const sub_star = client.subscribeSync(\"wtest.*\") catch {\n        reportResult(\"wildcard_matching\", false, \"star sub failed\");\n        return;\n    };\n    defer sub_star.deinit();\n\n    // Subscribe to foo.>\n    const sub_gt = client.subscribeSync(\"wtest.>\") catch {\n        reportResult(\"wildcard_matching\", false, \"gt sub failed\");\n        return;\n    };\n    defer sub_gt.deinit();\n\n    // Publish to wtest.bar (matches both)\n    client.publish(\"wtest.bar\", \"one\") catch {\n        reportResult(\"wildcard_matching\", false, \"pub1 failed\");\n        return;\n    };\n\n    // Publish to wtest.bar.baz (matches only >)\n    client.publish(\"wtest.bar.baz\", \"two\") catch {\n        reportResult(\"wildcard_matching\", false, \"pub2 failed\");\n        return;\n    };\n\n    // star should get 1 message\n    var star_count: u32 = 0;\n    while (true) {\n        const msg = sub_star.nextMsgTimeout(200) catch {\n            break;\n        };\n        if (msg) |m| {\n            m.deinit();\n            star_count += 1;\n        } else {\n            break;\n        }\n    }\n\n    // gt should get 2 messages\n    var gt_count: u32 = 0;\n    while (true) {\n        const msg = sub_gt.nextMsgTimeout(200) catch {\n            break;\n        };\n        if (msg) |m| {\n            m.deinit();\n            gt_count += 1;\n        } else {\n            break;\n        }\n    }\n\n    if (star_count == 1 and gt_count == 2) {\n        reportResult(\"wildcard_matching\", true, \"\");\n    } else {\n        var buf: [64]u8 = undefined;\n        const detail = std.fmt.bufPrint(\n            &buf,\n            \"star={d} gt={d}\",\n            .{ star_count, gt_count },\n        ) catch \"count error\";\n        reportResult(\"wildcard_matching\", false, detail);\n    }\n}\n\npub fn testWildcardPositions(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"wildcard_positions\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Wildcard at beginning: *.bar\n    const sub1 = client.subscribeSync(\"*.middle.end\") catch {\n        reportResult(\"wildcard_positions\", false, \"sub1 failed\");\n        return;\n    };\n    defer sub1.deinit();\n\n    // Wildcard in middle: foo.*.baz\n    const sub2 = client.subscribeSync(\"start.*.end\") catch {\n        reportResult(\"wildcard_positions\", false, \"sub2 failed\");\n        return;\n    };\n    defer sub2.deinit();\n\n    // Publish matching messages\n    client.publish(\"foo.middle.end\", \"msg1\") catch {};\n    client.publish(\"start.bar.end\", \"msg2\") catch {};\n\n    var count: u32 = 0;\n    if (sub1.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n    if (sub2.nextMsgTimeout(500) catch null) |m| {\n        m.deinit();\n        count += 1;\n    }\n\n    if (count == 2) {\n        reportResult(\"wildcard_positions\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail =\n            std.fmt.bufPrint(&buf, \"got {d}/2\", .{count}) catch \"err\";\n        reportResult(\"wildcard_positions\", false, detail);\n    }\n}\n\npub fn testMultipleWildcards(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"multi_wildcards\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    // Subscribe with multiple * wildcards\n    const sub = client.subscribeSync(\"mw.*.middle.*\") catch {\n        reportResult(\"multi_wildcards\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    // Publish matching subjects\n    client.publish(\"mw.foo.middle.bar\", \"hit1\") catch {};\n    client.publish(\"mw.a.middle.b\", \"hit2\") catch {};\n    client.publish(\"mw.xyz.other.abc\", \"miss\") catch {}; // should not match\n\n    var count: u32 = 0;\n    for (0..4) |_| {\n        const msg = sub.nextMsgTimeout(200) catch break;\n        if (msg) |m| {\n            m.deinit();\n            count += 1;\n        } else break;\n    }\n\n    if (count == 2) {\n        reportResult(\"multi_wildcards\", true, \"\");\n    } else {\n        var buf: [32]u8 = undefined;\n        const detail = std.fmt.bufPrint(&buf, \"got {d}/2\", .{count}) catch \"e\";\n        reportResult(\"multi_wildcards\", false, detail);\n    }\n}\n\npub fn testPublishSubscribe(allocator: std.mem.Allocator) void {\n    var url_buf: [64]u8 = undefined;\n    const url = formatUrl(&url_buf, test_port);\n\n    const io = utils.newIo(allocator);\n    defer io.deinit();\n\n    const client = nats.Client.connect(\n        allocator,\n        io.io(),\n        url,\n        .{ .reconnect = false },\n    ) catch {\n        reportResult(\"publish_subscribe\", false, \"connect failed\");\n        return;\n    };\n    defer client.deinit();\n\n    const sub = client.subscribeSync(\"roundtrip.test\") catch {\n        reportResult(\"publish_subscribe\", false, \"subscribe failed\");\n        return;\n    };\n    defer sub.deinit();\n\n    client.publish(\"roundtrip.test\", \"hello from zig\") catch {\n        reportResult(\"publish_subscribe\", false, \"publish failed\");\n        return;\n    };\n\n    // Receive message\n    const msg = sub.nextMsgTimeout(1000) catch {\n        reportResult(\"publish_subscribe\", false, \"nextWithTimeout failed\");\n        return;\n    };\n\n    if (msg) |m| {\n        defer m.deinit();\n        if (std.mem.eql(u8, m.subject, \"roundtrip.test\") and\n            std.mem.eql(u8, m.data, \"hello from zig\"))\n        {\n            reportResult(\"publish_subscribe\", true, \"\");\n            return;\n        }\n    }\n\n    reportResult(\"publish_subscribe\", false, \"message not received\");\n}\n\n/// Runs all wildcard tests.\npub fn runAll(allocator: std.mem.Allocator) void {\n    testWildcardSubscribe(allocator);\n    testWildcardMatching(allocator);\n    testWildcardPositions(allocator);\n    testMultipleWildcards(allocator);\n    testPublishSubscribe(allocator);\n}\n"
  },
  {
    "path": "src/testing/configs/TestUser.creds",
    "content": "-----BEGIN NATS USER JWT-----\neyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJMN1dBT1hJU0tPSUZNM1QyNEhMQ09ENzJRT1czQkNVWEdETjRKVU1SSUtHTlQ3RzdZVFRRIiwiaWF0IjoxNjUxNzkwOTgyLCJpc3MiOiJBRFRRUzdaQ0ZWSk5XNTcyNkdPWVhXNVRTQ1pGTklRU0hLMlpHWVVCQ0Q1RDc3T1ROTE9PS1pPWiIsIm5hbWUiOiJUZXN0VXNlciIsInN1YiI6IlVBRkhHNkZVRDJVVTRTREZWQUZVTDVMREZPMlhNNFdZTTc2VU5YVFBKWUpLN0VFTVlSQkhUMlZFIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9LCJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.bp2-Jsy33l4ayF7Ku1MNdJby4WiMKUrG-rSVYGBusAtV3xP4EdCa-zhSNUaBVIL3uYPPCQYCEoM1pCUdOnoJBg\n------END NATS USER JWT------\n\n************************* IMPORTANT *************************\nNKEY Seed printed below can be used to sign and prove identity.\nNKEYs are sensitive and should be treated as secrets.\n\n-----BEGIN USER NKEY SEED-----\nSUACH75SWCM5D2JMJM6EKLR2WDARVGZT4QC6LX3AGHSWOMVAKERABBBRWM\n------END USER NKEY SEED------\n\n*************************************************************\n"
  },
  {
    "path": "src/testing/configs/jwt.conf",
    "content": "// Operator \"TestOperator\"\noperator: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJVWlhRWjVXR1VGSUY0S0ZKS1dFRUhSTU5PM0RQVUdZMzUyM1QyNk1IQU1KTENDVzVZVjdRIiwiaWF0IjoxNjUxNzkwOTcwLCJpc3MiOiJPQjVYQ0U0NVdFVkYyRjVWWUZOM1Q3NEpJRVpFT1JaVzYzSEJFSVdVWEpHWU9HS0VBSVVLM0FMTSIsIm5hbWUiOiJUZXN0T3BlcmF0b3IiLCJzdWIiOiJPQjVYQ0U0NVdFVkYyRjVWWUZOM1Q3NEpJRVpFT1JaVzYzSEJFSVdVWEpHWU9HS0VBSVVLM0FMTSIsIm5hdHMiOnsic3lzdGVtX2FjY291bnQiOiJBQlJFSk5ZQVNXR1MyQTVFVlhQVlhHR0NPSzJMSlhUN0taTllCQlpBWFVUVUJJMlZTTUFWN0RITiIsInR5cGUiOiJvcGVyYXRvciIsInZlcnNpb24iOjJ9fQ.RN7AkgTATcx9E_ykTQHI0wM3OE8BwKPb3aPj7ojLGiNpjIRP-ehvSiUUkfWPh6rcO709TKspfQgTxcRxLoq5Bg\n\n// system_account: ADTQS7ZCFVJNW5726GOYXW5TSCZFNIQSHK2ZGYUBCD5D77OTNLOOKZOZ\n\nresolver: MEMORY\n\nresolver_preload: {\n  // Account \"SYS\"\n  ABREJNYASWGS2A5EVXPVXGGCOK2LJXT7KZNYBBZAXUTUBI2VSMAV7DHN: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiI1RDMzSVBJRFJCTUg1VTdWTFlOMlk0VEpTV09aUzJaSTNPMlBMR1dORDdWN0MyQjJDNjZRIiwiaWF0IjoxNjUxNzkwOTcwLCJpc3MiOiJPQjVYQ0U0NVdFVkYyRjVWWUZOM1Q3NEpJRVpFT1JaVzYzSEJFSVdVWEpHWU9HS0VBSVVLM0FMTSIsIm5hbWUiOiJTWVMiLCJzdWIiOiJBQlJFSk5ZQVNXR1MyQTVFVlhQVlhHR0NPSzJMSlhUN0taTllCQlpBWFVUVUJJMlZTTUFWN0RITiIsIm5hdHMiOnsiZXhwb3J0cyI6W3sibmFtZSI6ImFjY291bnQtbW9uaXRvcmluZy1zdHJlYW1zIiwic3ViamVjdCI6IiRTWVMuQUNDT1VOVC4qLlx1MDAzZSIsInR5cGUiOiJzdHJlYW0iLCJhY2NvdW50X3Rva2VuX3Bvc2l0aW9uIjozLCJkZXNjcmlwdGlvbiI6IkFjY291bnQgc3BlY2lmaWMgbW9uaXRvcmluZyBzdHJlYW0iLCJpbmZvX3VybCI6Imh0dHBzOi8vZG9jcy5uYXRzLmlvL25hdHMtc2VydmVyL2NvbmZpZ3VyYXRpb24vc3lzX2FjY291bnRzIn0seyJuYW1lIjoiYWNjb3VudC1tb25pdG9yaW5nLXNlcnZpY2VzIiwic3ViamVjdCI6IiRTWVMuUkVRLkFDQ09VTlQuKi4qIiwidHlwZSI6InNlcnZpY2UiLCJyZXNwb25zZV90eXBlIjoiU3RyZWFtIiwiYWNjb3VudF90b2tlbl9wb3NpdGlvbiI6NCwiZGVzY3JpcHRpb24iOiJSZXF1ZXN0IGFjY291bnQgc3BlY2lmaWMgbW9uaXRvcmluZyBzZXJ2aWNlcyBmb3I6IFNVQlNaLCBDT05OWiwgTEVBRlosIEpTWiBhbmQgSU5GTyIsImluZm9fdXJsIjoiaHR0cHM6Ly9kb2NzLm5hdHMuaW8vbmF0cy1zZXJ2ZXIvY29uZmlndXJhdGlvbi9zeXNfYWNjb3VudHMifV0sImxpbWl0cyI6eyJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJpbXBvcnRzIjotMSwiZXhwb3J0cyI6LTEsIndpbGRjYXJkcyI6dHJ1ZSwiY29ubiI6LTEsImxlYWYiOi0xfSwic2lnbmluZ19rZXlzIjpbIkFDUEpPQ0xZTTJHQzU0Nk1EMzRGVzVPMkRVQTYzTERIV0ZHU0JJSVpTSUZJQjJUWlVGREQ0TlBVIl0sImRlZmF1bHRfcGVybWlzc2lvbnMiOnsicHViIjp7fSwic3ViIjp7fX0sInR5cGUiOiJhY2NvdW50IiwidmVyc2lvbiI6Mn19.I_ybyL7Gm9gTH1IVrulNB596y-YmdYQ9QoyGEez3SviPJNFFD1vkmtl2wpzesUB1zaVYVyAhhN_jsEWElmUnBQ\n\n  // Account \"TestAccount\"\n  ADTQS7ZCFVJNW5726GOYXW5TSCZFNIQSHK2ZGYUBCD5D77OTNLOOKZOZ: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJSWDRVUzZWUURCTFNCSVNaRjRCWFBJREUyMlZRREJWWUsyTkYzUUNBRTdZWkE2NjNOSVBBIiwiaWF0IjoxNjUxNzkwOTc3LCJpc3MiOiJPQjVYQ0U0NVdFVkYyRjVWWUZOM1Q3NEpJRVpFT1JaVzYzSEJFSVdVWEpHWU9HS0VBSVVLM0FMTSIsIm5hbWUiOiJUZXN0QWNjb3VudCIsInN1YiI6IkFEVFFTN1pDRlZKTlc1NzI2R09ZWFc1VFNDWkZOSVFTSEsyWkdZVUJDRDVENzdPVE5MT09LWk9aIiwibmF0cyI6eyJsaW1pdHMiOnsic3VicyI6LTEsImRhdGEiOi0xLCJwYXlsb2FkIjotMSwiaW1wb3J0cyI6LTEsImV4cG9ydHMiOi0xLCJ3aWxkY2FyZHMiOnRydWUsImNvbm4iOi0xLCJsZWFmIjotMX0sImRlZmF1bHRfcGVybWlzc2lvbnMiOnsicHViIjp7fSwic3ViIjp7fX0sInR5cGUiOiJhY2NvdW50IiwidmVyc2lvbiI6Mn19.R_SRlgJhdLFFmG0E_dScqrrKsCmVzTitB8-3HfKbo6gbcqu647O7SPGixH5BXHVZpOaOZJ0gzN36OebU5E5LAw\n\n}\n"
  },
  {
    "path": "src/testing/configs/tls.conf",
    "content": "port: 14226\ntls {\n    cert_file: \"src/testing/certs/server-cert.pem\"\n    key_file: \"src/testing/certs/server-key.pem\"\n    ca_file: \"src/testing/certs/rootCA.pem\"\n    # Generous handshake timeout so Debug builds (slow std.crypto)\n    # don't get killed mid-handshake by the server.\n    timeout: 60\n}\n"
  },
  {
    "path": "src/testing/integration_test.zig",
    "content": "//! NATS Integration Tests\n//!\n//! Tests against a nats-server instance.\n//! Run with: zig build test-integration\n\nconst std = @import(\"std\");\nconst nats = @import(\"nats\");\n\nconst utils = @import(\"test_utils.zig\");\nconst client_tests = @import(\"client/tests.zig\");\n\nconst ServerManager = utils.ServerManager;\n\nconst test_port = utils.test_port;\nconst auth_port = utils.auth_port;\nconst nkey_port = utils.nkey_port;\nconst jwt_port = utils.jwt_port;\nconst tls_port = utils.tls_port;\nconst test_token = utils.test_token;\nconst test_nkey_seed = utils.test_nkey_seed;\nconst jwt_config_file = utils.jwt_config_file;\nconst tls_config_file = utils.tls_config_file;\nconst reportResult = utils.reportResult;\nconst formatUrl = utils.formatUrl;\nconst formatAuthUrl = utils.formatAuthUrl;\n\nconst nkey_config_path = \"/tmp/nats-nkey-test.conf\";\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    utils.setProcessEnviron(init.minimal.environ);\n\n    // Use the io_backend selector instead of init.io so\n    // -Dio_backend=threaded|evented is exercised end-to-end by\n    // the test runner itself (server startup, file I/O, sleeps).\n    const test_io = utils.newIo(allocator);\n    defer test_io.deinit();\n    const io = test_io.io();\n\n    std.debug.print(\"\\n=== NATS Integration Tests ===\\n\\n\", .{});\n\n    var manager: ServerManager = .init(allocator);\n    defer manager.deinit(allocator, io);\n\n    std.debug.print(\"Starting primary server on port {d}...\\n\", .{test_port});\n    _ = manager.startServer(allocator, io, .{ .port = test_port }) catch |err| {\n        std.debug.print(\"Failed to start primary server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    std.debug.print(\"Starting auth server on port {d}...\\n\", .{auth_port});\n    _ = manager.startServer(allocator, io, .{\n        .port = auth_port,\n        .auth_token = test_token,\n    }) catch |err| {\n        std.debug.print(\"Failed to start auth server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    std.debug.print(\"Starting NKey server on port {d}...\\n\", .{nkey_port});\n    writeNKeyConfig(io) catch |err| {\n        std.debug.print(\"Failed to write NKey config: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n    defer deleteNKeyConfig(io);\n\n    _ = manager.startServer(allocator, io, .{\n        .port = nkey_port,\n        .config_file = nkey_config_path,\n    }) catch |err| {\n        std.debug.print(\"Failed to start NKey server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    std.debug.print(\"Starting JWT server on port {d}...\\n\", .{jwt_port});\n    _ = manager.startServer(allocator, io, .{\n        .port = jwt_port,\n        .config_file = jwt_config_file,\n    }) catch |err| {\n        std.debug.print(\"Failed to start JWT server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    std.debug.print(\"Starting TLS server on port {d}...\\n\", .{tls_port});\n    _ = manager.startServer(allocator, io, .{\n        .port = tls_port,\n        .config_file = tls_config_file,\n    }) catch |err| {\n        std.debug.print(\"Failed to start TLS server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    io.sleep(.fromMilliseconds(200), .awake) catch {};\n\n    std.debug.print(\"\\nRunning tests...\\n\\n\", .{});\n\n    client_tests.runAll(allocator, &manager);\n\n    const summary = utils.getSummary();\n    std.debug.print(\"\\n=== Test Summary ===\\n\", .{});\n    std.debug.print(\"Passed: {d}\\n\", .{summary.passed});\n    std.debug.print(\"Failed: {d}\\n\", .{summary.failed});\n    std.debug.print(\"Total:  {d}\\n\\n\", .{summary.total});\n\n    if (summary.failed > 0) {\n        std.process.exit(1);\n    }\n}\n\nfn writeNKeyConfig(io: std.Io) !void {\n    const Dir = std.Io.Dir;\n\n    var kp = nats.auth.KeyPair.fromSeed(test_nkey_seed) catch {\n        return error.InvalidSeed;\n    };\n    defer kp.wipe();\n\n    var pubkey_buf: [56]u8 = undefined;\n    const pubkey = kp.publicKey(&pubkey_buf);\n\n    const file = try Dir.createFile(Dir.cwd(), io, nkey_config_path, .{});\n    defer file.close(io);\n\n    var buf: [256]u8 = undefined;\n    var writer = file.writer(io, &buf);\n    try writer.interface.print(\n        \\\\authorization {{\n        \\\\  users = [{{ nkey: \"{s}\" }}]\n        \\\\}}\n        \\\\\n    ,\n        .{pubkey},\n    );\n    try writer.interface.flush();\n}\n\nfn deleteNKeyConfig(io: std.Io) void {\n    const Dir = std.Io.Dir;\n    Dir.deleteFile(Dir.cwd(), io, nkey_config_path) catch {};\n}\n"
  },
  {
    "path": "src/testing/micro_integration_test.zig",
    "content": "//! Focused microservices integration tests.\n//!\n//! Run with: zig build test-integration-micro\n\nconst std = @import(\"std\");\nconst utils = @import(\"test_utils.zig\");\nconst micro_tests = @import(\"client/micro.zig\");\n\nconst ServerManager = utils.ServerManager;\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    utils.setProcessEnviron(init.minimal.environ);\n\n    const test_io = utils.newIo(allocator);\n    defer test_io.deinit();\n    const io = test_io.io();\n\n    std.debug.print(\"\\n=== NATS Micro Integration Tests ===\\n\\n\", .{});\n\n    var manager: ServerManager = .init(allocator);\n    defer manager.deinit(allocator, io);\n\n    std.debug.print(\"\\nRunning micro tests...\\n\\n\", .{});\n    micro_tests.runAll(allocator, &manager);\n\n    const summary = utils.getSummary();\n    std.debug.print(\"\\n=== Micro Test Summary ===\\n\", .{});\n    std.debug.print(\"Passed: {d}\\n\", .{summary.passed});\n    std.debug.print(\"Failed: {d}\\n\", .{summary.failed});\n    std.debug.print(\"Total:  {d}\\n\\n\", .{summary.total});\n\n    if (summary.failed > 0) std.process.exit(1);\n}\n"
  },
  {
    "path": "src/testing/server_manager.zig",
    "content": "//! NATS Test Server\n//!\n//! Self-contained nats-server for integration testing.\n//! Returns by value - each test owns its servers.\n//! Use `defer server.deinit(io)` for automatic cleanup.\n\nconst std = @import(\"std\");\nconst assert = std.debug.assert;\nconst Allocator = std.mem.Allocator;\nconst Io = std.Io;\n\n/// Configuration for a NATS server instance.\npub const ServerConfig = struct {\n    /// Port to listen on.\n    port: u16 = 4222,\n    /// Optional authentication token.\n    auth_token: ?[]const u8 = null,\n    /// Optional path to config file (-c option).\n    config_file: ?[]const u8 = null,\n    /// Enable debug/verbose output.\n    debug: bool = false,\n    /// Enable JetStream.\n    jetstream: bool = false,\n    /// Optional JetStream store directory. If omitted, tests use an\n    /// isolated per-port directory under /tmp and remove it on stop.\n    store_dir: ?[]const u8 = null,\n};\n\n/// A self-contained NATS server for testing.\n/// Returns by value - no pointer stability issues.\n/// Call deinit() when done (typically via defer).\npub const TestServer = struct {\n    process: ?std.process.Child = null,\n    config: ServerConfig,\n    port_buf: [8]u8 = undefined,\n    store_dir_buf: [128]u8 = undefined,\n    store_dir_len: u8 = 0,\n\n    /// Starts a test server. Returns owned instance.\n    /// Usage: `var server = TestServer.start(...) catch return;`\n    ///        `defer server.deinit(io);`\n    pub fn start(allocator: Allocator, io: Io, config: ServerConfig) !TestServer {\n        assert(config.port > 0);\n\n        var server: TestServer = .{ .config = config };\n\n        const port_str = std.fmt.bufPrint(\n            &server.port_buf,\n            \"{d}\",\n            .{config.port},\n        ) catch unreachable;\n\n        var args: std.ArrayList([]const u8) = .empty;\n        defer args.deinit(allocator);\n\n        try args.append(allocator, \"nats-server\");\n        try args.append(allocator, \"-p\");\n        try args.append(allocator, port_str);\n\n        if (config.auth_token) |token| {\n            try args.append(allocator, \"--auth\");\n            try args.append(allocator, token);\n        }\n\n        if (config.config_file) |config_file| {\n            try args.append(allocator, \"-c\");\n            try args.append(allocator, config_file);\n        }\n\n        if (config.jetstream) {\n            try args.append(allocator, \"-js\");\n            const store_dir = try server.prepareJetStreamStore(io);\n            try args.append(allocator, \"-sd\");\n            try args.append(allocator, store_dir);\n        }\n\n        if (config.debug) {\n            try args.append(allocator, \"-DV\");\n        }\n\n        server.process = try std.process.spawn(io, .{\n            .argv = args.items,\n            .stdout = .ignore,\n            .stderr = .ignore,\n        });\n        assert(server.process != null);\n\n        // Wait for server to become ready\n        try server.waitReady(io, 5000);\n\n        // Give server extra time to fully initialize after port is open\n        io.sleep(.fromMilliseconds(500), .awake) catch {};\n\n        return server;\n    }\n\n    /// Stops and cleans up. Safe to call multiple times (idempotent).\n    /// Typically called via defer: `defer server.deinit(io);`\n    pub fn deinit(self: *TestServer, io: Io) void {\n        self.stop(io);\n    }\n\n    /// Stops the server. Idempotent - safe if already stopped or process died.\n    pub fn stop(self: *TestServer, io: Io) void {\n        if (self.process) |*proc| {\n            std.debug.print(\n                \"[SERVER] Killing server on port {d}...\\n\",\n                .{self.config.port},\n            );\n            if (proc.id != null) {\n                proc.kill(io);\n            }\n            self.process = null;\n            // Give OS time to fully terminate the process and close sockets\n            io.sleep(.fromMilliseconds(100), .awake) catch {};\n            self.cleanJetStreamStore(io);\n            std.debug.print(\"[SERVER] Server killed, waited 100ms\\n\", .{});\n        }\n    }\n\n    /// Returns true if the server process is running.\n    pub fn isRunning(self: *const TestServer) bool {\n        return self.process != null;\n    }\n\n    /// Waits for the server to become ready by probing the TCP port.\n    fn waitReady(self: *TestServer, io: Io, timeout_ms: u32) !void {\n        assert(self.process != null);\n        const max_attempts = timeout_ms / 50;\n        var attempts: u32 = 0;\n\n        while (attempts < max_attempts) : (attempts += 1) {\n            if (self.probePort(io)) {\n                return;\n            }\n            io.sleep(.fromMilliseconds(50), .awake) catch {};\n        }\n\n        return error.ServerStartTimeout;\n    }\n\n    /// Probes if the server port is accepting connections.\n    fn probePort(self: *TestServer, io: Io) bool {\n        const address = Io.net.IpAddress.parse(\n            \"127.0.0.1\",\n            self.config.port,\n        ) catch return false;\n\n        const stream = Io.net.IpAddress.connect(\n            &address,\n            io,\n            .{\n                .mode = .stream,\n                .protocol = .tcp,\n            },\n        ) catch return false;\n        stream.close(io);\n\n        return true;\n    }\n\n    fn prepareJetStreamStore(self: *TestServer, io: Io) ![]const u8 {\n        const dir = if (self.config.store_dir) |configured| blk: {\n            if (configured.len > self.store_dir_buf.len)\n                return error.NameTooLong;\n            @memcpy(self.store_dir_buf[0..configured.len], configured);\n            self.store_dir_len = @intCast(configured.len);\n            break :blk self.store_dir_buf[0..configured.len];\n        } else blk: {\n            const formatted = std.fmt.bufPrint(\n                &self.store_dir_buf,\n                \"/tmp/nats-zig-js-{d}\",\n                .{self.config.port},\n            ) catch return error.NameTooLong;\n            self.store_dir_len = @intCast(formatted.len);\n            break :blk formatted;\n        };\n\n        // Start from a clean JetStream store so integration tests do not\n        // inherit state from an interrupted previous run on the same host.\n        std.Io.Dir.deleteTree(std.Io.Dir.cwd(), io, dir) catch {};\n        return dir;\n    }\n\n    fn cleanJetStreamStore(self: *TestServer, io: Io) void {\n        if (self.store_dir_len == 0) return;\n        const dir = self.store_dir_buf[0..self.store_dir_len];\n        std.Io.Dir.deleteTree(std.Io.Dir.cwd(), io, dir) catch {};\n        self.store_dir_len = 0;\n    }\n};\n\n// Legacy aliases for backward compatibility during migration\npub const ServerInstance = TestServer;\npub const ServerManager = struct {\n    servers: std.ArrayList(TestServer) = .empty,\n\n    /// Max servers expected in any test - pre-allocate to avoid reallocation\n    const MAX_SERVERS: usize = 16;\n\n    pub fn init(allocator: Allocator) ServerManager {\n        var mgr = ServerManager{};\n        // Pre-allocate to prevent reallocation (which invalidates pointers)\n        mgr.servers.ensureTotalCapacity(allocator, MAX_SERVERS) catch {};\n        return mgr;\n    }\n\n    pub fn deinit(self: *ServerManager, allocator: Allocator, io: Io) void {\n        self.stopAll(io);\n        self.servers.deinit(allocator);\n    }\n\n    pub fn startServer(\n        self: *ServerManager,\n        allocator: Allocator,\n        io: Io,\n        config: ServerConfig,\n    ) !*TestServer {\n        const server = try TestServer.start(allocator, io, config);\n        try self.servers.ensureTotalCapacity(allocator, self.servers.items.len + 1);\n        try self.servers.append(allocator, server);\n        return &self.servers.items[self.servers.items.len - 1];\n    }\n\n    pub fn stopAll(self: *ServerManager, io: Io) void {\n        for (self.servers.items) |*server| {\n            server.stop(io);\n        }\n        io.sleep(.fromMilliseconds(500), .awake) catch {};\n    }\n\n    pub fn stopServer(self: *ServerManager, index: usize, io: Io) void {\n        if (index < self.servers.items.len) {\n            self.servers.items[index].stop(io);\n        }\n        io.sleep(.fromMilliseconds(500), .awake) catch {};\n    }\n\n    pub fn count(self: *const ServerManager) usize {\n        return self.servers.items.len;\n    }\n};\n\ntest \"server config defaults\" {\n    const config: ServerConfig = .{};\n    try std.testing.expectEqual(@as(u16, 4222), config.port);\n    try std.testing.expect(config.auth_token == null);\n    try std.testing.expect(config.config_file == null);\n    try std.testing.expect(!config.debug);\n}\n\ntest \"test server init\" {\n    var server: TestServer = .{ .config = .{ .port = 14222 } };\n    try std.testing.expectEqual(@as(u16, 14222), server.config.port);\n    try std.testing.expect(!server.isRunning());\n}\n"
  },
  {
    "path": "src/testing/test_utils.zig",
    "content": "//! Shared test utilities for NATS integration tests.\n\nconst std = @import(\"std\");\npub const nats = @import(\"nats\");\npub const server_manager = @import(\"server_manager.zig\");\n\npub const ServerManager = server_manager.ServerManager;\npub const ServerConfig = server_manager.ServerConfig;\n\npub const test_port: u16 = 14222;\npub const auth_port: u16 = 14223;\npub const nkey_port: u16 = 14224;\npub const jwt_port: u16 = 14225;\npub const test_token = \"test-secret-token\";\npub const test_nkey_seed =\n    \"SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\";\npub const test_nkey_seed_file = \"/tmp/nats-test-nkey.seed\";\npub const jwt_config_file = \"src/testing/configs/jwt.conf\";\npub const test_creds_file = \"src/testing/configs/TestUser.creds\";\npub const test_jwt_seed =\n    \"SUACH75SWCM5D2JMJM6EKLR2WDARVGZT4QC6LX3AGHSWOMVAKERABBBRWM\";\n\n// Dynamic JWT test constants\npub const dynamic_jwt_port: u16 = 14228;\npub const jetstream_port: u16 = 14229;\npub const micro_port: u16 = 14241;\n\n// TLS test constants\npub const tls_port: u16 = 14226;\npub const tls_config_file = \"src/testing/configs/tls.conf\";\npub const tls_ca_file = \"src/testing/certs/rootCA.pem\";\npub const tls_server_cert = \"src/testing/certs/server-cert.pem\";\npub const tls_server_key = \"src/testing/certs/server-key.pem\";\npub const tls_client_cert = \"src/testing/certs/client-cert.pem\";\npub const tls_client_key = \"src/testing/certs/client-key.pem\";\n\npub var tests_passed: u32 = 0;\npub var tests_failed: u32 = 0;\n\n/// Reports a test result and updates counters.\npub fn reportResult(name: []const u8, passed: bool, details: []const u8) void {\n    if (passed) {\n        tests_passed += 1;\n        std.debug.print(\"[PASS] {s}\\n\", .{name});\n    } else {\n        tests_failed += 1;\n        std.debug.print(\"[FAIL] {s}: {s}\\n\", .{ name, details });\n    }\n}\n\n/// Reports a failed test step with the Zig error name included.\npub fn reportError(name: []const u8, step: []const u8, err: anyerror) void {\n    var buf: [128]u8 = undefined;\n    const details = std.fmt.bufPrint(\n        &buf,\n        \"{s}: {s}\",\n        .{ step, @errorName(err) },\n    ) catch step;\n    reportResult(name, false, details);\n}\n\n/// Formats a NATS URL for the given port.\npub fn formatUrl(buf: []u8, port: u16) []const u8 {\n    const fmt = \"nats://127.0.0.1:{d}\";\n    return std.fmt.bufPrint(buf, fmt, .{port}) catch \"invalid\";\n}\n\n/// Formats a NATS URL with auth token.\npub fn formatAuthUrl(buf: []u8, port: u16, token: []const u8) []const u8 {\n    return std.fmt.bufPrint(\n        buf,\n        \"nats://{s}@127.0.0.1:{d}\",\n        .{ token, port },\n    ) catch \"invalid\";\n}\n\n/// Formats a TLS NATS URL for the given port.\n/// Uses localhost since test certificates are issued for localhost.\npub fn formatTlsUrl(buf: []u8, port: u16) []const u8 {\n    const fmt = \"tls://localhost:{d}\";\n    return std.fmt.bufPrint(buf, fmt, .{port}) catch \"invalid\";\n}\n\npub fn resetCounters() void {\n    tests_passed = 0;\n    tests_failed = 0;\n}\n\npub fn getSummary() struct { passed: u32, failed: u32, total: u32 } {\n    return .{\n        .passed = tests_passed,\n        .failed = tests_failed,\n        .total = tests_passed + tests_failed,\n    };\n}\n\nconst io_backend = @import(\"io_backend\");\n\nvar process_environ: std.process.Environ = .empty;\n\n/// Sets the environment used by test Io backends. Integration test entry\n/// points call this once from their `std.process.Init` so child-process\n/// lookups, such as `nats-server`, see the same PATH as the test runner.\npub fn setProcessEnviron(environ: std.process.Environ) void {\n    process_environ = environ;\n}\n\n/// Heap-allocated wrapper around `io_backend.Backend` for use by\n/// integration tests. Each `newIo()` call returns a fresh\n/// `*TestIo` that owns its backend; calling `deinit()` releases\n/// both the backend and the wrapper itself.\n///\n/// Existing test code that does:\n///\n///     var io: std.Io.Threaded = .init(allocator, .{ .environ = .empty });\n///     defer io.deinit();\n///\n/// becomes:\n///\n///     const io = utils.newIo(allocator);\n///     defer io.deinit();\n///\n/// All `io.io()` and `io.deinit()` calls work unchanged through\n/// the pointer because Zig auto-dereferences method calls when\n/// the receiver matches.\npub const TestIo = struct {\n    backend: io_backend.Backend,\n    allocator: std.mem.Allocator,\n\n    /// Tears down the backend and frees the wrapper. Must be\n    /// called once per `newIo()` call (typically via `defer`).\n    pub fn deinit(self: *TestIo) void {\n        self.backend.deinit();\n        self.allocator.destroy(self);\n    }\n\n    /// Returns the abstract `std.Io` for passing to client APIs.\n    pub fn io(self: *TestIo) std.Io {\n        return self.backend.io();\n    }\n};\n\n/// Allocates and initializes a `TestIo` wrapper. Panics on\n/// allocation or backend init failure — acceptable for tests\n/// because every existing test function returns `void`, not\n/// `!void`, and propagating an errorable here would force a\n/// viral signature change across the entire suite.\npub fn newIo(allocator: std.mem.Allocator) *TestIo {\n    const t = allocator.create(TestIo) catch\n        @panic(\"OOM in test newIo\");\n    t.allocator = allocator;\n    io_backend.initWithEnviron(&t.backend, allocator, process_environ) catch\n        @panic(\"io_backend init failed in test newIo\");\n    return t;\n}\n"
  },
  {
    "path": "src/testing/tls_integration_test.zig",
    "content": "//! Focused JWT + TLS integration tests to debug hangs around the TLS block.\n//!\n//! Run with: zig build test-integration-tls\n\nconst std = @import(\"std\");\nconst utils = @import(\"test_utils.zig\");\nconst jwt_tests = @import(\"client/jwt.zig\");\nconst tls_tests = @import(\"client/tls.zig\");\n\nconst ServerManager = utils.ServerManager;\nconst Dir = std.Io.Dir;\nconst jwt_port = utils.jwt_port;\nconst tls_port = utils.tls_port;\nconst jwt_config_file = utils.jwt_config_file;\nconst tls_config_file = utils.tls_config_file;\n\nfn probeTlsPort(io: std.Io) bool {\n    const address = std.Io.net.IpAddress.parse(\"127.0.0.1\", tls_port) catch return false;\n    const stream = std.Io.net.IpAddress.connect(&address, io, .{\n        .mode = .stream,\n        .protocol = .tcp,\n    }) catch return false;\n    stream.close(io);\n    return true;\n}\n\nfn probeCaLoad(allocator: std.mem.Allocator, io: std.Io) !void {\n    const ca_path = try Dir.realPathFileAlloc(.cwd(), io, utils.tls_ca_file, allocator);\n    defer allocator.free(ca_path);\n\n    var bundle: std.crypto.Certificate.Bundle = .empty;\n    defer bundle.deinit(allocator);\n\n    const now = std.Io.Clock.real.now(io);\n    try bundle.addCertsFromFilePathAbsolute(allocator, io, now, ca_path);\n}\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n    utils.setProcessEnviron(init.minimal.environ);\n\n    const test_io = utils.newIo(allocator);\n    defer test_io.deinit();\n    const io = test_io.io();\n\n    std.debug.print(\"\\n=== NATS JWT/TLS Integration Tests ===\\n\\n\", .{});\n\n    var manager: ServerManager = .init(allocator);\n    defer manager.deinit(allocator, io);\n\n    std.debug.print(\"Starting JWT server on port {d}...\\n\", .{jwt_port});\n    _ = manager.startServer(allocator, io, .{\n        .port = jwt_port,\n        .config_file = jwt_config_file,\n    }) catch |err| {\n        std.debug.print(\"Failed to start JWT server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    std.debug.print(\"Starting TLS server on port {d}...\\n\", .{tls_port});\n    _ = manager.startServer(allocator, io, .{\n        .port = tls_port,\n        .config_file = tls_config_file,\n    }) catch |err| {\n        std.debug.print(\"Failed to start TLS server: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n\n    io.sleep(.fromMilliseconds(200), .awake) catch {};\n\n    std.debug.print(\"TLS port probe before tests: {s}\\n\", .{\n        if (probeTlsPort(io)) \"ok\" else \"failed\",\n    });\n\n    std.debug.print(\"\\nRunning JWT tests...\\n\\n\", .{});\n    jwt_tests.runAll(allocator);\n\n    std.debug.print(\"\\nRunning TLS tests...\\n\\n\", .{});\n\n    std.debug.print(\"[RUN ] tls_insecure_skip_verify\\n\", .{});\n    tls_tests.testTlsInsecureSkipVerify(allocator);\n\n    std.debug.print(\"[RUN ] ca_bundle_load\\n\", .{});\n    probeCaLoad(allocator, io) catch |err| {\n        std.debug.print(\"[FAIL] ca_bundle_load: {}\\n\", .{err});\n        std.process.exit(1);\n    };\n    std.debug.print(\"[PASS] ca_bundle_load\\n\", .{});\n\n    std.debug.print(\"[RUN ] tls_connection\\n\", .{});\n    tls_tests.testTlsConnection(allocator);\n\n    std.debug.print(\"[RUN ] tls_pubsub\\n\", .{});\n    tls_tests.testTlsPubSub(allocator);\n\n    std.debug.print(\"[RUN ] tls_server_info\\n\", .{});\n    tls_tests.testTlsServerInfo(allocator);\n\n    std.debug.print(\"[RUN ] tls_multiple_msgs\\n\", .{});\n    tls_tests.testTlsMultipleMessages(allocator);\n\n    std.debug.print(\"[RUN ] tls_scheme_rejects_plain_server\\n\", .{});\n    tls_tests.testTlsSchemeRejectsPlainServer(allocator);\n\n    std.debug.print(\"[RUN ] tls_reconnect\\n\", .{});\n    tls_tests.testTlsReconnect(allocator, &manager);\n\n    const summary = utils.getSummary();\n    std.debug.print(\"\\n=== JWT/TLS Test Summary ===\\n\", .{});\n    std.debug.print(\"Passed: {d}\\n\", .{summary.passed});\n    std.debug.print(\"Failed: {d}\\n\", .{summary.failed});\n    std.debug.print(\"Total:  {d}\\n\\n\", .{summary.total});\n\n    if (summary.failed > 0) std.process.exit(1);\n}\n"
  }
]