Skip to content

fix(plugins): keep plugin event maps under exactOptionalPropertyTypes - #239

Merged
vinikjkkj merged 1 commit into
masterfrom
fix/plugin-events-exact-optional
Aug 11, 2026
Merged

fix(plugins): keep plugin event maps under exactOptionalPropertyTypes#239
vinikjkkj merged 1 commit into
masterfrom
fix/plugin-events-exact-optional

Conversation

@vinikjkkj

@vinikjkkj vinikjkkj commented Aug 11, 2026

Copy link
Copy Markdown
Owner

The __pluginEvents marker is a phantom, so it is declared optional and the emitted declaration writes it as ?: E | undefined. A consumer compiling with exactOptionalPropertyTypes keeps that undefined when EventsOf infers E, and UnionToIntersection then reduces Events | undefined to never. The client resolves as WaClient<never>, which types every listener parameter as never, including core events that have nothing to do with a plugin:

client.on('auth_qr', ({ qr }) => ...)
// Argument of type '...' is not assignable to parameter of type 'never'
// Binding element 'qr' implicitly has an 'any' type

EventsOf now drops undefined before the union reaches the intersection. Every plugin was affected, not only the one reported: voip fails identically despite declaring a real event map, since the collapse happens after the marker is read, not because of what it holds.

The break only reproduces through the built .d.ts. Compiling against the sources infers E from an optional property with no explicit | undefined, which the flag has nothing to preserve, so it cannot be covered from src/: an assertion there passes with and without this change. A real guard needs a fixture compiled against dist/ with the flag on.

Verified against the reporter's dependency set (zapo-js 1.7.0, @zapo-js/wam 0.1.1, @zapo-js/voip) with the flag on: core events, plugin getters and plugin events all type, and an unknown event name is still rejected.

Closes #236.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved plugin event type handling when optional event markers are used.
    • Preserved support for unknown event types while providing more accurate event mappings.
  • Documentation

    • Added guidance on optional event markers and their interaction with TypeScript’s exactOptionalPropertyTypes setting.

The `__pluginEvents` marker is a phantom, so it is declared optional and
the emitted declaration writes it as `?: E | undefined`. A consumer
compiling with `exactOptionalPropertyTypes` keeps that `undefined` when
`EventsOf` infers `E`, and `UnionToIntersection` then reduces
`Events | undefined` to `never`. The client resolves as
`WaClient<never>`, which types every listener parameter as `never`,
including core events that have nothing to do with a plugin:

    client.on('auth_qr', ({ qr }) => ...)
    // Argument of type '...' is not assignable to parameter of type 'never'
    // Binding element 'qr' implicitly has an 'any' type

`EventsOf` now drops `undefined` before the union reaches the
intersection. Every plugin was affected, not only the one reported:
voip fails identically despite declaring a real event map, since the
collapse happens after the marker is read, not because of what it holds.

The break only reproduces through the built .d.ts. Compiling against
the sources infers `E` from an optional property with no explicit
`| undefined`, which the flag has nothing to preserve, so it cannot be
covered from `src/`: an assertion there passes with and without this
change. A real guard needs a fixture compiled against `dist/` with the
flag on.

Verified against the reporter's dependency set (zapo-js 1.7.0,
@zapo-js/wam 0.1.1, @zapo-js/voip) with the flag on: core events,
plugin getters and plugin events all type, and an unknown event name
is still rejected.

Reported in #236.
@github-actions github-actions Bot added the fix Bug fix label Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

EventsOf now removes undefined from optional plugin event markers before event maps are intersected. Its documentation explains the marker and exactOptionalPropertyTypes behavior.

Changes

Plugin event type handling

Layer / File(s) Summary
EventsOf optional marker handling
src/client/plugins/types.ts
EventsOf uses Exclude<E, undefined> for inferred event maps. Documentation describes optional phantom markers and their interaction with exactOptionalPropertyTypes.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix for plugin event maps under TypeScript's exactOptionalPropertyTypes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/plugin-events-exact-optional

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 1 file

Re-trigger cubic

@vinikjkkj
vinikjkkj merged commit 4ca77a8 into master Aug 11, 2026
32 checks passed
@vinikjkkj
vinikjkkj deleted the fix/plugin-events-exact-optional branch August 11, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Adding wamPlugin() breaks event listener type inference in WaClient.on()

1 participant