From 0ee489cf474e3f774dce12078744d0bb5c173cad Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Mon, 12 Oct 2026 11:24:59 +0100 Subject: [PATCH 1/2] DOC-7172 Update tce-examples and command-api-mapping skill references Corrects reference facts that turned out stale or missing during the HIMPORT work (PR #4242). tce-examples: - The Ruby 3.4+ hash inspect format. - A new section listing the harness client pins that predate HIMPORT, with the isolated scratch-project workaround to use until they're raised. command-api-mapping: - The redis_rb client ID and where its source lives. - node-redis method names come from the commands/index.ts export, and some commands have JSDoc there. - Plain Task for async versions of void SE.Redis methods. - Predis container @method docblocks are in src/Command/Container. - A new ioredis section covering the typed-subcommand convention. Ticket: DOC-7172 Co-Authored-By: Claude Opus 5.5 --- .../reference/client-quirks.md | 31 ++++++++++++++----- .../reference/client-source-map.md | 4 +++ .../reference/output-schema.md | 2 +- .../assets/ruby/RUBY_TEST_PATTERNS.md | 6 +++- .../skills/tce-examples/reference/testing.md | 15 +++++++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/.claude/skills/command-api-mapping/reference/client-quirks.md b/.claude/skills/command-api-mapping/reference/client-quirks.md index 9c46c8246b..d25e66c693 100644 --- a/.claude/skills/command-api-mapping/reference/client-quirks.md +++ b/.claude/skills/command-api-mapping/reference/client-quirks.md @@ -18,9 +18,10 @@ export default { ``` Translation rules: -- **Method name = camelCase of the filename**, not `parseCommand`. `ARGET.ts` → `arGet`, - `ARGETRANGE.ts` → `arGetRange`, `ARSET.ts` → `arSet`. (node-redis generates the public - method from the filename.) +- **Method name = the name exported in `packages/client/lib/commands/index.ts`**, not + `parseCommand`. It is usually the camelCase of the filename (`ARGET.ts` → `arGet`, + `ARGETRANGE.ts` → `arGetRange`), but check `index.ts` when the word split isn't obvious: + `HIMPORT_PREPARE.ts` is exported as `hImportPrepare`, not `himportPrepare` (DOC-7172). - **Drop the first `parser: CommandParser` param** — it's internal. - **Keep the remaining params** with their types (`key: RedisArgument`, `index: number | string`). These RESP types (`RedisArgument`, `RedisVariadicArgument`) are what users effectively pass; @@ -28,7 +29,9 @@ Translation rules: - **Return type comes from `transformReply`**, not a return annotation. The type after `() =>` is the reply (`NumberReply`, `BlobStringReply | NullReply`, …). Render the signature return as `Promise<...>` of that reply where it reads naturally. -- **No doc comments exist** — all `description` fields are `""`. +- **The command files have no doc comments.** Some newer commands carry JSDoc (`@param`) on + their export in `commands/index.ts` (HIMPORT does, as of `redis@6.2.0`); use it for param + descriptions when present, otherwise `""`. - **House-style format (matches existing entries — important):** the `signature` string carries **no return-type suffix** (write `arSet(key: RedisArgument, index: number | string, value: RedisVariadicArgument)`, not `...): Promise`), and `returns.type` is the **bare reply type without the @@ -108,7 +111,8 @@ Translation rules: `returns.type` differs (sync `V` / async `RedisFuture` / reactive `Mono`). ## nredisstack_sync / nredisstack_async (C#) -- PascalCase methods; async variants end in `Async` and return `Task`. +- PascalCase methods; async variants end in `Async` and return `Task`, or plain `Task` + when the sync method returns `void` (`HashImport` → `HashImportAsync`). - **Core commands aren't in NRedisStack** — they're in StackExchange.Redis (`IDatabase.cs` / `IDatabaseAsync.cs`). Module commands (JSON/Search/TimeSeries) are in NRedisStack. - XML doc comments (``, ``, ``) → descriptions; strip ``. @@ -126,8 +130,21 @@ Translation rules: return type (`int`, `array`, `string|null`, `mixed`). - Predis folds option flags into params rather than separate variants (e.g. `argrep` has a `bool $withValues` param — no separate WITHVALUES method). -- Container commands (XGROUP/XINFO) expose subcommands via `@method` entries in - `src/Command/Container/`. PHPDoc descriptions when present. +- Container commands (XGROUP/XINFO/HIMPORT) expose subcommands via `@method` entries in + `src/Command/Container/.php`, not in `ClientInterface.php`, which lists them only as a + property (`@property HIMPORT $himport`). Map the bare subcommand method (`prepare(...)`, + `set(...)`), as `XGROUP CREATE.json` does, with the return type from the container's + `@method` docblock. PHPDoc descriptions when present. + +## ioredis (TypeScript) +- Signatures come from the generated `lib/utils/RedisCommander.ts`. +- Container commands are one generic method with a literal-typed `subcommand` first param. + Follow `XGROUP CREATE.json`: `xgroup(subcommand: "CREATE", ..., callback?: ...)`, with + `subcommand` and `callback?` listed as params. +- For `...args: [subcommand: 'X', ...]` tuple overloads (HIMPORT PREPARE/SET), list each + tuple element as its own param. Existing variadic rows such as `HSET.json` have garbled + params like `"[key"`; don't copy that. Use the real `Result<...>` return type, as + `HEXPIRE.json` does, rather than `"Any"`. ## redis_rs_sync / redis_rs_async (Rust) - snake_case methods inside the `implement_commands!` macro. Drop `&self`/`&mut self`. diff --git a/.claude/skills/command-api-mapping/reference/client-source-map.md b/.claude/skills/command-api-mapping/reference/client-source-map.md index d3847cda0a..27a93f7c84 100644 --- a/.claude/skills/command-api-mapping/reference/client-source-map.md +++ b/.claude/skills/command-api-mapping/reference/client-source-map.md @@ -18,6 +18,7 @@ here yet; use the "finding new command files" recipe below. | `nredisstack_sync` / `nredisstack_async` | github.com/redis/NRedisStack | `master` | C# | | `php` | github.com/predis/predis | `main` | PHP | | `redis_rs_sync` / `redis_rs_async` | github.com/redis-rs/redis-rs | `main` | Rust | +| `redis_rb` | github.com/redis/redis-rb | `master` | Ruby | (Branch names drift; if a fetch 404s, try `main`/`master`/`develop`.) @@ -47,6 +48,9 @@ here yet; use the "finding new command files" recipe below. - **php (predis)** — `src/ClientInterface.php` for most signatures; container commands (XGROUP, XINFO) define subcommands in `@method` docblocks under `src/Command/Container/`. - **redis-rs** — `redis/src/commands/mod.rs` (the `implement_commands!` macro block). +- **redis_rb** — per-type modules under `lib/redis/commands/` (`hashes.rb`, `strings.rb`, …). + Some methods are overridden in `lib/redis.rb` (for example the HIMPORT fieldset registry); + map the public method name from `commands/`. ## Finding new command files (the AR* lesson) diff --git a/.claude/skills/command-api-mapping/reference/output-schema.md b/.claude/skills/command-api-mapping/reference/output-schema.md index 87a1428bc2..4565df79ed 100644 --- a/.claude/skills/command-api-mapping/reference/output-schema.md +++ b/.claude/skills/command-api-mapping/reference/output-schema.md @@ -46,7 +46,7 @@ redis_py go-redis jedis node_redis ioredis lettuce_sync lettuce_async lettuce_reactive nredisstack_sync nredisstack_async php redis_rs_sync -redis_rs_async redisvl +redis_rs_async redisvl redis_rb ``` Note the inconsistency: **`go-redis` uses a hyphen**, everything else uses underscores. diff --git a/.claude/skills/tce-examples/assets/ruby/RUBY_TEST_PATTERNS.md b/.claude/skills/tce-examples/assets/ruby/RUBY_TEST_PATTERNS.md index 6f5c0ee652..70016d0514 100644 --- a/.claude/skills/tce-examples/assets/ruby/RUBY_TEST_PATTERNS.md +++ b/.claude/skills/tce-examples/assets/ruby/RUBY_TEST_PATTERNS.md @@ -88,13 +88,17 @@ r = Redis.new ```ruby res = r.hgetall('myhash') puts res.inspect -# >>> {"field1"=>"value1", "field2"=>"value2"} +# >>> {"field1" => "value1", "field2" => "value2"} ``` Note the `=>` hash-rocket form — that is what `inspect` actually emits. Output comments are published verbatim, so writing the modern `{field1: "value1"}` there would show readers something Ruby never printed. +Ruby 3.4 and later put spaces around the `=>` (`{"field1" => "value1"}`); earlier versions +print `{"field1"=>"value1"}`. Older files in `local_examples/` use the unspaced form. Write +the form your run actually printed, and don't rewrite existing files to match. + ### 4. Replies are strings, except counters Values read back from Redis are strings even when written as integers. The reply of an diff --git a/.claude/skills/tce-examples/reference/testing.md b/.claude/skills/tce-examples/reference/testing.md index ca50f95899..799206e9a9 100644 --- a/.claude/skills/tce-examples/reference/testing.md +++ b/.claude/skills/tce-examples/reference/testing.md @@ -120,6 +120,21 @@ version per client requires running both Java toolchains against the search sets Java-heavy command sets. Until that's done, **a Java example that passes in one mode may fail in the other**, and that's information rather than a bug. +## Known gap: client pins older than new commands + +The portable pins lag the newest commands. As of DOC-7172 (HIMPORT, Redis 8.10), these +portable pins predated the first release with the command: jedis 7.5.3 (needs 8.0.0), +lettuce-core 6.5.5 in `pom-lettuce-sync.xml` (needs 7.7.0; the async and reactive poms are +already on 7.7.0), StackExchange.Redis 3.0.0 in `dotnet/harness.csproj` (needs 3.0.25), +the cached `npm i redis` 6.1.0 (needs 6.2.0) and ioredis 5.11.1 (needs 6.0.0) in `work/`, +and go-redis v9.18.0 in `fidelity/go-redis.mod` (needs 9.22.0, which also needs Go 1.24). + +A run against those pins fails with "method not found" or "unknown command", which looks +like an example bug but is a pin gap. Until the pins are raised (a separate change, because +raising them can break other sets), test such a set outside `run.sh`. Give each client a +scratch project pinned to its first supporting release, with its own throwaway +`redis:` container on a spare port, and change the port only in the scratch copy. + ## Codex evaluation (Phase 4) An independent reviewer with fresh context. It catches what the harness structurally cannot: From ba84e15344b61e477a567d957701271f2e3205b9 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Mon, 12 Oct 2026 11:41:04 +0100 Subject: [PATCH 2/2] DOC-7172 Add null-reply gotchas to Lettuce reactive and Predis TCE patterns Two client gotchas from the DOC-7170 cas_cad example (PR #4240), which until now were recorded only in that commit. In Lettuce reactive, a nil reply is an empty Mono, so later flatMap/doOnNext steps silently never run and the test still passes; show the result with hasElement() or defaultIfEmpty(). In Predis, echo of null prints an empty line, so "// >>> (null)" comments are wrong; use var_export and NULL. Ticket: DOC-7172 Co-Authored-By: Claude Opus 5.5 --- .../LETTUCE_REACTIVE_TEST_PATTERNS.md | 12 ++++++++++++ .../assets/predis/PREDIS_TEST_PATTERNS.md | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/.claude/skills/tce-examples/assets/lettuce-reactive/LETTUCE_REACTIVE_TEST_PATTERNS.md b/.claude/skills/tce-examples/assets/lettuce-reactive/LETTUCE_REACTIVE_TEST_PATTERNS.md index 46f973ba4b..f89bf616bb 100644 --- a/.claude/skills/tce-examples/assets/lettuce-reactive/LETTUCE_REACTIVE_TEST_PATTERNS.md +++ b/.claude/skills/tce-examples/assets/lettuce-reactive/LETTUCE_REACTIVE_TEST_PATTERNS.md @@ -117,6 +117,18 @@ Mono chain = reactiveCommands.hincrby("stats", "count", 1) chain.block(); ``` +**Null replies are empty Monos.** A command that replies with nil completes without +emitting anything, so every later `flatMap`/`doOnNext` in the chain silently never runs — +and the test still passes. This covers a conditional `SET` that doesn't apply (`NX`, `XX`, +`IFEQ`) and a `GET` of a missing key. Where the step needs to show the nil result, end that +command with `.hasElement()` (emits `false`) or `.defaultIfEmpty(...)`: + +```java +.flatMap(v -> reactiveCommands.set("bike:1", "Zeus", + SetArgs.Builder.compareCondition(CompareCondition.valueEq("Deimos"))).hasElement()) +.doOnNext(res -> System.out.println(res)) // >>> false +``` + ### 4. Parallel Execution ```java Mono.when(mono1, mono2, mono3).block(); diff --git a/.claude/skills/tce-examples/assets/predis/PREDIS_TEST_PATTERNS.md b/.claude/skills/tce-examples/assets/predis/PREDIS_TEST_PATTERNS.md index d8600be959..6a25ebe3c2 100644 --- a/.claude/skills/tce-examples/assets/predis/PREDIS_TEST_PATTERNS.md +++ b/.claude/skills/tce-examples/assets/predis/PREDIS_TEST_PATTERNS.md @@ -129,6 +129,13 @@ echo json_encode($all) . PHP_EOL; // >>> {"field1":"value1","field2":"value2"} ``` +`echo` prints an empty line for a `null` reply, so a `// >>> (null)` comment after it is +wrong. Use `var_export`: + +```php +echo var_export($res, true) . PHP_EOL; // >>> NULL +``` + ### 5. Hash Operations ```php // Single field — returns the number of new fields