Skip to content

fix(core): keep constructor unbound on withEventMeta proxies - #109

Merged
dinwwwh merged 1 commit into
mainfrom
claude/event-meta-proxy-constructor-87a62f
Sep 26, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/event-meta-proxy-constructor-87a62f

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Sep 26, 2026

Copy link
Copy Markdown
Member

Events that carry an id, retry or comments now keep their real constructor, so they pass plain-object checks again. Before, a received { a: 2 } with an id reported constructor as bound Object, which made zod's z.record(...), is-plain-object and toStrictEqual reject it, while the same event without metadata passed. Class instances wrapped with withEventMeta also lost constructor === Foo and their static members.

Fixes

  • Received events from the fetch and peer adapters now match the shape of the same event without metadata, as the README promises.
  • Methods that rely on the original object (Map#get, Date methods, class #private fields) still work through the proxy.

Testing

  • Added a core test covering a plain object, a class instance (constructor, statics, #private method) and a Map.
  • The fetch and peer receive tests now assert received values with toStrictEqual instead of toEqual. toEqual doesn't compare constructors, which is how this slipped through. These tests fail without the fix.
  • Full suite passes; lint and tsc are clean.

Note for reviewers

Removing the binding entirely would mean dropping the proxy for a WeakMap side table. That fixes more cases (structuredClone of an event currently throws), but withEventMeta would attach metadata to the original object instead of returning a new value, which is a breaking change. It's left out of this PR.

The withEventMeta proxy binds every function-valued property to the
target so internal-slot methods (Map#get, #private) keep working, but it
also bound the inherited `constructor`. Events carrying an id, retry or
comments then had `constructor !== Object`, which broke plain-object
checks (zod records, is-plain-object) and `toStrictEqual`.
@pkg-pr-new

pkg-pr-new Bot commented Sep 26, 2026

Copy link
Copy Markdown
@standard-server/aws-lambda

npm i https://pkg.pr.new/@standard-server/aws-lambda@109

@standard-server/core

npm i https://pkg.pr.new/@standard-server/core@109

@standard-server/fastify

npm i https://pkg.pr.new/@standard-server/fastify@109

@standard-server/fetch

npm i https://pkg.pr.new/@standard-server/fetch@109

@standard-server/node

npm i https://pkg.pr.new/@standard-server/node@109

@standard-server/peer

npm i https://pkg.pr.new/@standard-server/peer@109

@standard-server/shared

npm i https://pkg.pr.new/@standard-server/shared@109

commit: 0e0f8ce

@codspeed

codspeed Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 108 skipped benchmarks1


Comparing claude/event-meta-proxy-constructor-87a62f (0e0f8ce) with main (901bb47)

Open in CodSpeed

Footnotes

  1. 108 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@codecov

codecov Bot commented Sep 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • constructor stays unbound on withEventMeta proxies — packages/core/src/event-stream/meta.ts:48 now calls getOrBind(target, prop, { bind: prop !== 'constructor' }), so a metadata-wrapped value reports its real constructor (Object, Foo, Map) instead of bound Object, while methods remain bound to the real target.
  • Core shape test — packages/core/src/event-stream/meta.test.ts pins the visible shape for a plain object (constructor === Object + toStrictEqual), a class instance (constructor, static member, instanceof, #private method) and a Map (get/size).
  • Receive-path assertions tightened — packages/fetch/src/event-stream.test.ts and packages/peer/src/event-stream.test.ts switch the relevant toEqual checks to toStrictEqual, which compares constructors and therefore actually catches this regression.

I ran the core, fetch and peer suites (413 passing), then temporarily reverted the one-line change: the new core assertion and both tightened receive assertions fail as expected (bound Object vs Object), confirming the tests are discriminating rather than passing by construction. The bind option already existed on getOrBind and has no other call sites, and the node/aws-lambda receive paths delegate to the fetch iterator, so the fetch test change covers them. The structuredClone limitation noted in the PR body is a fair scoping call and is left out deliberately.

Pullfrog  | View workflow run | Using DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

@dinwwwh
dinwwwh merged commit 3e28554 into main Sep 26, 2026
11 checks passed
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.

1 participant