fix: expose .snapshot() type on nested proxy objects#64
Merged
Conversation
`Snapshotable<T>` only added `.snapshot()` to the top-level proxy, even though the runtime proxy `get` trap returns it for every nested object. Callers passing a nested slice to a server action (e.g. `state.filter`) hit a TS error and had to cast. Make `Snapshotable<T>` recursive — every plain object/array layer gets `.snapshot()`. Same fix applied to `BoundResourceState<T>` so the action `state()` accessor types match. Array element types are intentionally not recursed into so `Array.push` etc. keep accepting plain elements. Also exports `snapshot` and `Snapshotable` from the public API. Bumps version to 0.3.3.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The recursive `Snapshotable<T>` and `BoundResourceState<T>` were adding
`.snapshot()` to every object-typed field, including built-ins like
`Date` and `Map`. The runtime proxy never wraps those (canProxy() is
false for non-Object.prototype prototypes), so the decoration broke
assignments — `this.model.user = userFromApi` failed because plain
`Date` doesn't satisfy `Date & { snapshot(): Date }`.
Exclude common built-in object types (`Date`, `RegExp`, `Error`,
`Promise`, `Map`, `Set`, `WeakMap`, `WeakSet`, `ArrayBuffer`,
`DataView`) from the recursion so plain values remain assignable.
Bumps to 0.3.4.
Recursive `T & { snapshot(): T }` made plain values un-assignable to
state fields (`state.user = userFromApi` failed). Excluding only built-in
objects didn't help — any user-defined plain type (User, ProjectDetail)
hit the same wall.
Revert to top-level-only `Snapshotable<T>` on `createProxy`. For nested
slices, the proxy `get` trap still intercepts `.snapshot()` at runtime;
at the type level use the standalone `snapshot(state.slice)` helper.
Also bumps to 0.3.5 and updates llms.txt with the new pattern.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Snapshotable<T>recursive so every nested plain object/array proxy exposes.snapshot()at the type level (matches the existing runtime behavior in the proxygettrap).BoundResourceState<T>, so the actionstate()accessor types match.snapshotandSnapshotablefrom the public API.0.3.3.Why
Passing a nested proxy slice to a React Server Action threw
Only plain objects can be passed to Server Functions ... Objects with symbol properties like proxy are not supported. The recommended fix is to call.snapshot()first — but.snapshot()was only typed on the top-level proxy, even though the runtime proxygettrap returns it for every nested object. Callers had to cast (as Snapshotable<T>) which defeats the type system.Array element types are intentionally not recursed into, so
Array.prototype.push,[i] = …, etc. keep accepting the plain element type. For array elements, use the standalonesnapshot(state.items[0])helper.Test plan
tests/snapshot-types.test.ts— 7 type-level tests covering top-level / nested object / array.snapshot(), array push compatibility, standalone helper, function pass-through, and theaction(({ state }) => …)accessor.tsc -p tsconfig.jsonclean.🤖 Generated with Claude Code