Repository navigation
Conversation
commit: |
|
/bonk review this |
1 similar comment
|
/bonk review this |
There was a problem hiding this comment.
Reviewed the Map serialization support. The implementation is well thought out and the test coverage is genuinely impressive — the promises inside a Map suite exercises the tricky bits (key/value slots resolving in either order, key collapse under Map semantics, insertion-order preservation, Blob round-trips, and the local deepCopy path). I ran the full node suite (165 passed) and test:types (clean) locally against this branch.
The core design decision — using entries as the source of truth and rebuilding the whole Map on each promise resolution via one-time-use setters — is a clever way to reuse the existing parent[property] = resolved delivery machinery for a structure that has no addressable positions. Nicely mirrors the Set-less-ness reasoning in the comments.
A few non-blocking observations below. None of them are correctness bugs — all tests pass — but the O(n^2) rebuild is worth at least acknowledging.
Notes
-
O(n^2) rebuild on resolution. Each resolved promise slot calls
map.clear()and re-inserts every entry, so a Map with n promise slots does O(n^2) work total. For typical payloads this is fine, but a large Map full of pipelined promises could be pathological. Given the constraint (Map has no stable index to write into), this is a reasonable tradeoff — just flagging it. A future optimization could defer the rebuild until all slots for a given batch have resolved, but that adds bookkeeping complexity that probably isn't worth it now. -
Type coverage for serialization. The
Map<K,V>types already exist insrc/types.d.ts(Stubify/Unstubify/RpcCompatible), so this PR correctly fills the runtime gap. Worth confirming there's a round-trip/type assertion somewhere in__type-tests__that aMapvalue survives —roundTripHeaders(new Map(...))exists but that's Headers-typed, not a Map-typed round trip. Optional.
Overall this looks good to merge. Nice work on the tests.
|
Review submitted successfully. Here's a summary of what I reviewed and posted on PR #232: Review SummaryThe PR adds runtime serialization for Verification I did:
Assessment: looks good to merge. The design is sound:
Non-blocking notes I posted (3 inline + summary):
No correctness bugs found. |
Co-authored-by: teamchong <teamchong@users.noreply.github.com>
🦋 Changeset detectedLatest commit: 25c3a7b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
recheck |
|
All contributors have signed the CLA ✍️ ✅ |
| | ------------------------------------------- | --------- | ----------- | | ||
| | `Map` and some other built-ins | Not yet | Yes | | ||
| | `Set` | Yes | Yes | | ||
| | Some other built-ins | Not yet | Yes | |
There was a problem hiding this comment.
AbortSignal comes to mind but we can remove the line too
| // RpcPayload | ||
|
|
||
| export type LocatedPromise = {parent: object, property: string | number, promise: RpcPromise}; | ||
| export type LocatedPromise = {parent: object, property: unknown, promise: RpcPromise}; |
There was a problem hiding this comment.
why was this necessary?
There was a problem hiding this comment.
Map keys can be anything
There was a problem hiding this comment.
I think this is probably the right general approach, but it also makes me nervous that we might mishandle properties somewhere. I worry about what happens e.g. if someone uses undefined as a map key, or if they somehow manage to encode a value that happens to stringify to __proto__ and then we accidentally do object[propertyName] somewhere and oops.
What if we required arbitrary-typed map keys to be boxed? Like:
type PropertyName = string | number | {mapKey: unknown}There was a problem hiding this comment.
What if we required arbitrary-typed map keys to be boxed? Like:
We will allocate for every key but apart from that it will work. I can see that this flips the accidental case to do .set instead of object[propertyName] but the check that prevents this is the same. Instead of an instance check on Map we will check for the key shape to match. I am not sure it is worth the allocations.
| } | ||
|
|
||
| let result = new Map(); | ||
| for (let [key, val] of entries) { |
There was a problem hiding this comment.
why do we have to loop over all the entires a second time? can't we just check the key here, as we encounter them, and then just throw immediately if we hit one that's not valid?
There was a problem hiding this comment.
To avoid a potential deepCopy of a value
|
|
||
| case "map": { | ||
| let map = <Map<unknown, unknown>>value; | ||
| let mapEntries = [...map]; |
There was a problem hiding this comment.
as far as I can tell (I didn't measure it) we should prefer map.entries() here because the destructure creates eagerly allocates memory for a whole new array immediately, while .entries() lazily makes an interable zero-allocation structural pointer.
There was a problem hiding this comment.
It will create the iterator twice, in this case it will just create the array once and loop over the same one twice
| // rebuilding itself. Values may be promises or Blobs. As with `Set`, validate all keys | ||
| // before encoding anything, since encoded streams and Blobs (including in values) create | ||
| // pipes that cannot be rolled back. | ||
| for (let [key] of mapEntries) { |
There was a problem hiding this comment.
same comment as above, why do we need to loop twice?
| } | ||
| break; | ||
| case "map": | ||
| if (value.length === 2 && value[1] instanceof Array) { |
There was a problem hiding this comment.
I see all the others work this way, but it's strange to me that we silently (right?) drop any error resulting from not hitting this condition. seems like we should protect against malformed stuff here, no? we can handle separately in a new PR for test coverage for this for all types if you think - I'd be happy to do it.
There was a problem hiding this comment.
We don't need to surface a specific error because users are unlikely to hit this case. The only ones that will likely see this could be authors of capnweb ports to other languages.
Adds serialization support for
Map. Doesn't special case.map()like we do for arrays, uses the default.Just like #229, we support reject lazy values in keys.
Closes #230