Allow custom serialization strategy class instances over RPC
Describe the solution
Hi
In ex. a DurableObject, communicating via a stub from a worker, we can call functions to the DurableObject (or other WorkerEntryPoint for that matter) via RPC.
When a function inside the DurableObject returns a non-POJO it attempts to make a structured clone, but in doing so throwing errors, when it sees prototype on the object.
[wrangler:error] DataCloneError: Could not serialize object of type "Result". This type does not support serialization.
My proposal
Allow class instances to be serializable by running toJSON() before structurally cloning them. If developers wish to rehydrate an instance on the receiving end, they can wrap the stub in a Proxy to rehydrate the class instances over the wire.
If toJSON is not run for a reason, perhaps allowing developers to explicitly make them serializable in the Workers RPC environment via a symbol; [Symbol.for('cloudflare.serialize')]() { ... }
Background
I'm working on (and actively using) a Result-based library much like neverthrow called xult with function wrappers. And I have to overcome this issue by having to explicitly return plain JSON objects from functions when working in an environment such as Cloudflare. This is not fun, because when developing, we have to juggle between the concepts of a class instance and a JSON-object as a part of the mental load.
Because I ❤️ Cloudflare so much, I am making a personal request here.
import Result, { funcJSON, from } from 'xult'
export class MyDO extends DurableObject {
// This returns a Result object as JSON rather than an instance
sayHello = funcJSON((name: string) => {
if (!name) return Result.err('NAME_REQUIRED', 'Name argument is required to pass into the function.')
return `Hello, ${name}!`
})
processHello = funcJSON(function * (this: MyDO, name: string) {
// extract `Result.ok` like normal
const helloString = yield* this.sayHello('Shiba')
// or rehydrate into a Result and ex. handle the error
const result = from(this.sayHello('Shiba'))
.mapErr(() => Result.err('UNHELLOABLE', 'Could not say hello'))
...
})
}
Hi @Refzlund 🙂
When returning a class instance could you simply make the class extend RpcTarget? if not, could you explain why?
Hi @dario-piotrowicz, thank you for responding.
This feature would benefit libraries that are not strictly tied to Cloudflare Workers, such as the one I am working on.
RpcTarget requires the library[^1] to depend on Cloudflare Workers. Hence, allowing/enabling serialization of class instances would allow developers to provide a serialization/deserialization workflow via Proxies[^2].
If developer intentionality is a concern, providing a Symbol property to serialize by would at least provide the means to fullfill this proposal.
[^1]: Any library, such as xult or neverthrow, which premise is to work across all environments. xult provides specific APIs to serialize and rehydrate Results. This is ex. used for SvelteKit Transport Hooks when sending class instances from the backend to the frontend and vice versa.
[^2]: JavaScript Proxy class https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy