Getting DataCloneError while using instance of a class as return value of RPC method

Error message: DataCloneError: Could not serialize object of type "st". This type does not support serialization. Hono route
app.get("/pay/:id", async (c) => {
const id = c.req.param("id");
const services = c.get("services");

const paymentService = await c.env.PAYMENTS.newPaymentService(services.logger);
try {
const response = await paymentService.queryPaymentStatus({
pspReference: id,
reference: id,
sessionId: id
});

return c.json({
status: "successful",
message: "api request completed",
rpcResponse: response
})
} finally {
paymentService[Symbol.dispose]()
}
})
app.get("/pay/:id", async (c) => {
const id = c.req.param("id");
const services = c.get("services");

const paymentService = await c.env.PAYMENTS.newPaymentService(services.logger);
try {
const response = await paymentService.queryPaymentStatus({
pspReference: id,
reference: id,
sessionId: id
});

return c.json({
status: "successful",
message: "api request completed",
rpcResponse: response
})
} finally {
paymentService[Symbol.dispose]()
}
})
RPC class
export class PaymentService extends WorkerEntrypoint<Env> {
fetch(request: Request): Response | Promise<Response> {
return new Response("Hello from payments RPC")
}

async newPaymentService(logger: Logger) {
initCache(this.ctx, this.env);
return new Payments(logger);
}
}
export class PaymentService extends WorkerEntrypoint<Env> {
fetch(request: Request): Response | Promise<Response> {
return new Response("Hello from payments RPC")
}

async newPaymentService(logger: Logger) {
initCache(this.ctx, this.env);
return new Payments(logger);
}
}
5 Replies
Hard@Work
Hard@Work4w ago
What is a Logger?
omar
omarOP4w ago
This is what services look like:
import { Env } from "../env";
import type { Logger } from "@paycashless/worker-logger";
import type { Metrics } from "../metrics";

export type ServiceContext = {
logger: Logger;
metrics: Metrics;
};

export type HonoEnv = {
Bindings: Env;
Variables: {
requestId: string;
services: ServiceContext;
/**
* IP address or region information
*/
location: string;
userAgent?: string;
};
};
import { Env } from "../env";
import type { Logger } from "@paycashless/worker-logger";
import type { Metrics } from "../metrics";

export type ServiceContext = {
logger: Logger;
metrics: Metrics;
};

export type HonoEnv = {
Bindings: Env;
Variables: {
requestId: string;
services: ServiceContext;
/**
* IP address or region information
*/
location: string;
userAgent?: string;
};
};
Logger is a custom logger, wrapped around console.log
import { Log, type LogSchema } from "@paycashless/logs";
import type { Fields, Logger } from "./interface";

export class ConsoleLogger implements Logger {
private requestId: string;
private readonly application: LogSchema["application"];
private readonly defaultFields: Fields;

constructor(opts: {
requestId: string;
application: LogSchema["application"];
defaultFields?: Fields;
}) {
this.requestId = opts.requestId;
this.application = opts.application;
this.defaultFields = opts.defaultFields ?? {};
}

private marshal(
level: "debug" | "info" | "warn" | "error" | "fatal",
message: string,
fields?: Fields,
): string {
return new Log({
type: "log",
application: this.application,
requestId: this.requestId,
time: Date.now(),
level,
message,
context: { ...this.defaultFields, ...fields },
}).toString();
}

public debug(message: string, fields?: Fields): void {
console.debug(this.marshal("debug", message, fields));
}
public info(message: string, fields?: Fields): void {
console.info(this.marshal("info", message, fields));
}
public warn(message: string, fields?: Fields): void {
console.warn(this.marshal("warn", message, fields));
}
public error(message: string, fields?: Fields): void {
console.error(this.marshal("error", message, fields));
}
public fatal(message: string, fields?: Fields): void {
console.error(this.marshal("fatal", message, fields));
}

public setRequestId(requestId: string): void {
this.requestId = requestId;
}
}
import { Log, type LogSchema } from "@paycashless/logs";
import type { Fields, Logger } from "./interface";

export class ConsoleLogger implements Logger {
private requestId: string;
private readonly application: LogSchema["application"];
private readonly defaultFields: Fields;

constructor(opts: {
requestId: string;
application: LogSchema["application"];
defaultFields?: Fields;
}) {
this.requestId = opts.requestId;
this.application = opts.application;
this.defaultFields = opts.defaultFields ?? {};
}

private marshal(
level: "debug" | "info" | "warn" | "error" | "fatal",
message: string,
fields?: Fields,
): string {
return new Log({
type: "log",
application: this.application,
requestId: this.requestId,
time: Date.now(),
level,
message,
context: { ...this.defaultFields, ...fields },
}).toString();
}

public debug(message: string, fields?: Fields): void {
console.debug(this.marshal("debug", message, fields));
}
public info(message: string, fields?: Fields): void {
console.info(this.marshal("info", message, fields));
}
public warn(message: string, fields?: Fields): void {
console.warn(this.marshal("warn", message, fields));
}
public error(message: string, fields?: Fields): void {
console.error(this.marshal("error", message, fields));
}
public fatal(message: string, fields?: Fields): void {
console.error(this.marshal("fatal", message, fields));
}

public setRequestId(requestId: string): void {
this.requestId = requestId;
}
}
this is how Payments class looks like too
class Payments extends RpcTarget {
logger: Logger;

constructor(logger: Logger) {
super();
this.logger = logger;
}
}
class Payments extends RpcTarget {
logger: Logger;

constructor(logger: Logger) {
super();
this.logger = logger;
}
}
cc @Hard@Work works well when i don't pass services.logger, does this mean i can't pass any variable to the class instance ? @Hard@Work also works when i pass logger as a function, a bit weird that passing a class instance to the RPC stub does not work because the structured clone algorithm can copy a class instance solution:
const paymentService = await c.env.PAYMENTS.newPaymentService(() => services.logger);
const paymentService = await c.env.PAYMENTS.newPaymentService(() => services.logger);
Hard@Work
Hard@Work3w ago
Is Logger extending RpcTarget too? If not, that might be your issue
omar
omarOP3w ago
It does not extend RpcTarget
Hard@Work
Hard@Work3w ago
Yeah, try making it extend RpcTarget, and see if it works

Did you find this page helpful?