Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/serialize-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"capnweb": minor
---

Support serializing `URL` objects over RPC.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ The following types can be passed over RPC (in arguments or return values), and
* `Error` and its well-known subclasses
* `Blob`
* `ReadableStream` and `WritableStream`, with automatic flow control.
* `URL`
* `Headers`, `Request`, and `Response` from the Fetch API.

The following types are not supported as of this writing, but may be added in the future:
Expand Down
4 changes: 3 additions & 1 deletion __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ let SERIALIZE_TEST_CASES: Record<string, unknown> = {
'["-inf"]': -Infinity,
'["nan"]': NaN,

'["url","https://example.com/path?q=1"]': new URL("https://example.com/path?q=1"),

'["headers",[]]': new Headers(),
'["headers",[["content-type","text/plain"],["x-custom","hello"]]]':
new Headers({"Content-Type": "text/plain", "X-Custom": "hello"}),
Expand Down Expand Up @@ -80,7 +82,7 @@ describe("simple serialization", () => {
it("can deserialize", () => {
for (let key in SERIALIZE_TEST_CASES) {
let value = deserialize(key);
if (value instanceof Uint8Array ||
if (value instanceof Uint8Array || value instanceof URL ||
value instanceof Headers || value instanceof Request || value instanceof Response) {
// toStrictEqual() won't work for these (e.g. in Node.js, Uint8Array may deserialize as
// Buffer), so test by serializing again and making sure they round-trip.
Expand Down
9 changes: 8 additions & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type PropertyPath = (string | number)[];

type TypeForRpc = "unsupported" | "primitive" | "object" | "function" | "array" | "date" |
"bigint" | "bytes" | "blob" | "stub" | "rpc-promise" | "rpc-target" | "rpc-thenable" |
"error" | "undefined" | "writable" | "readable" | "headers" | "request" | "response";
"error" | "undefined" | "writable" | "readable" | "url" | "headers" | "request" | "response";

const AsyncFunction = (async function () {}).constructor;

Expand Down Expand Up @@ -115,6 +115,9 @@ export function typeForRpc(value: unknown): TypeForRpc {
case ReadableStream.prototype:
return "readable";

case URL.prototype:
return "url";

case Headers.prototype:
return "headers";

Expand Down Expand Up @@ -963,6 +966,7 @@ export class RpcPayload {
case "date":
case "bytes":
case "blob":
case "url":
case "error":
case "undefined":
// immutable, no need to copy
Expand Down Expand Up @@ -1344,6 +1348,7 @@ export class RpcPayload {
case "bytes":
case "blob":
case "date":
case "url":
case "error":
case "undefined":
return;
Expand Down Expand Up @@ -1489,6 +1494,7 @@ export class RpcPayload {
case "rpc-target":
case "writable":
case "readable":
case "url":
case "headers":
case "request":
case "response":
Expand Down Expand Up @@ -1642,6 +1648,7 @@ function followPath(value: unknown, parent: object | undefined,
case "blob":
case "date":
case "error":
case "url":
case "headers":
case "request":
case "response":
Expand Down
13 changes: 13 additions & 0 deletions src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ export class Devaluator {
return alternateTypeName === undefined ? ["bytes", b64] : ["bytes", b64, alternateTypeName];
}

case "url":
// At structuredClonable level, keep URL as native value.
if (this.encodingLevel === "structuredClonable") {
return value;
}
return ["url", (<URL>value).href];
Comment thread
Copilot marked this conversation as resolved.
Outdated

case "headers":
// The `Headers` TS type apparently doesn't declare itself as being
// Iterable<[string, string]>, but it is.
Expand Down Expand Up @@ -950,6 +957,12 @@ export class Evaluator {
case "nan":
return NaN;

case "url":
if (value.length === 2 && typeof value[1] === "string") {
return new URL(value[1]);
}
break;

case "headers":
// We only need to validate that the parameter is an array, so as not to invoke an
// unexpected variant of the Headers constructor. So long as it is an array then we can
Expand Down
Loading