Skip to content

fix(@Persist): add unwrapProxy for RPC serialization#5

Merged
apankov1 merged 2 commits into
devfrom
fix/unwrap-persist-proxy
Dec 10, 2025
Merged

fix(@Persist): add unwrapProxy for RPC serialization#5
apankov1 merged 2 commits into
devfrom
fix/unwrap-persist-proxy

Conversation

@apankov1
Copy link
Copy Markdown
Owner

Summary

Cherry-pick of cloudflare#100 by @dmmulroy

Adds unwrapProxy() and containsProxy() functions to properly strip @persist proxy wrappers before RPC serialization.

Problem

@persist decorator creates Proxy objects for change tracking. These proxies:

  • Fail with DataCloneError when passed to structuredClone()
  • Return RpcStub objects when returned via RPC instead of plain data

Solution

  • Export unwrapProxy(value) function that recursively strips proxy markers
  • Export containsProxy(value) helper to detect proxied objects
  • Export IS_PROXIED symbol for manual detection
  • Automatically wraps RPC returns to serialize proxy state

Test Results

All 26 persist tests pass including:

References

dmmulroy and others added 2 commits December 10, 2025 10:35
Fixes cloudflare#99 - @persist state returned RpcStub objects instead of data

Problem: Cloudflare RPC uses structured clone which can't serialize
Proxy objects. @persist wraps values in Proxy for mutation tracking.

Solution: Unwrap proxies before RPC serialization via _wrapMethodsForRpc().
Optimized to skip non-proxied values (most RPC returns) for performance.

Key changes in persist.ts:
- Add containsProxy() to detect IS_PROXIED symbol recursively
- unwrapProxy() returns original when no proxy detected (fast path)
- Only recreate objects with null prototype when proxy present

```typescript
// Before: always recreated all objects
export function unwrapProxy<T>(value: T): T {
  // ... always traverse and recreate with Object.create(null)
}

// After: fast path for non-proxied values
export function unwrapProxy<T>(value: T): T {
  if (!containsProxy(value)) return value; // identity return
  // ... only unwrap when proxy detected
}
```
Changed Object.create(null) to {} so returned objects retain
standard prototype methods (hasOwnProperty, toString, etc).

Updated tests to verify malicious keys aren't copied as own
properties while allowing inherited prototype methods.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
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.

2 participants