Skip to content

fix: ensure module commands respect proxy typeMapping #3261

Open
watersRand wants to merge 4 commits intoredis:masterfrom
watersRand:fix/withTypeMappings
Open

fix: ensure module commands respect proxy typeMapping #3261
watersRand wants to merge 4 commits intoredis:masterfrom
watersRand:fix/withTypeMappings

Conversation

@watersRand
Copy link
Copy Markdown
Contributor

@watersRand watersRand commented May 2, 2026

Description

Closes #3055 ,a bug where commands generated via static factories (Modules and Functions) were incorrectly referencing the root client's options (this._self._commandOptions) instead of the immediate instance's options (this._commandOptions).

The Problem:
When using .withCommandOptions() or .withTypeMapping(), the library creates a proxy object. However, because Modules and Functions were hard-coded to look at the internal _self reference for their configuration, they completely ignored any user-defined type mappings or timeouts set on the proxy. This effectively broke RESP3 type mapping for all Redis Module commands.

The Solution:

  • Updated #createModuleCommand and #createFunctionCommand to reference this._commandOptions.
  • Updated sendCommand to prioritize instance-level options.
  • Updated the NamespaceProxyClient type definition to include _commandOptions to ensure type safety.

Allows developers to seamlessly switch between different configurations—such as string vs. binary (Buffer) outputs—across all available commands, including built-in modules, third-party modules, and user-defined libraries (Redis Functions). It enables this flexibility on the fly without the overhead of re-initializing the entire connection or maintaining multiple client instances for different return types.


Checklist

  • Does npm test pass with this change (including linting)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?

Note

Medium Risk
Changes how command options are propagated/merged for module/function namespaces and sendCommand, which can affect timeouts/type mappings across many commands and could introduce subtle option-inheritance regressions.

Overview
Fixes a bug where module/function commands created via static factories ignored options set on proxy clients (e.g., from withCommandOptions/withTypeMapping) by switching them to read this._commandOptions and ensuring namespace proxies inherit the parent client’s _commandOptions.

Updates option merging to use prototype-based inheritance (Object.create + Object.assign) so proxy chains inherit defaults without copying, and adds a regression test asserting module replies respect proxy typeMapping while inheriting (not duplicating) other options like timeout.

Reviewed by Cursor Bugbot for commit 1fde3a2. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread packages/client/lib/client/index.ts
Comment thread packages/client/lib/client/index.ts Outdated
Comment thread packages/client/lib/client/index.spec.ts
Comment thread packages/client/lib/commander.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 1fde3a2. Configure here.

const value = Object.create(fns);
value._self = this;
Object.defineProperty(this, name, { value });
value._commandOptions = (this as any)._commandOptions ?? null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespace getter allocates new object on every access

Medium Severity

The attachNamespace function removes the previous Object.defineProperty(this, name, { value }) caching mechanism. Previously, the namespace object was created once and cached as an own property on the instance, so subsequent accesses (e.g., client.module.echo(...)) returned the same cached object. Now, every single property access triggers the getter and allocates a new Object.create(fns) object with freshly assigned _self and _commandOptions. For high-throughput Redis usage where module commands are called frequently, this introduces unnecessary object allocation and GC pressure on every command invocation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1fde3a2. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

withTypeMappings not working

1 participant