Can the DurableObject `ctx` able be safely used as WorkerEntrypoint `ctx` object?

Lets say I have a Worker with service entrypoints like so:
// WorkerEntrypoint class definition
export class MyEntrypointService extends WorkerEntrypoint<CloudflareEnv> {
constructor(ctx: ExecutionContext, env: CloudflareEnv) {
super(ctx, env);
}
}

// If I need to instantiate the service
const myService = new MyEntrypointService(this.ctx, this.env);
// WorkerEntrypoint class definition
export class MyEntrypointService extends WorkerEntrypoint<CloudflareEnv> {
constructor(ctx: ExecutionContext, env: CloudflareEnv) {
super(ctx, env);
}
}

// If I need to instantiate the service
const myService = new MyEntrypointService(this.ctx, this.env);
However, within a Durable Object, I get a Typescript error if I try to instantiate a entrypoint class in the same manner due to different types of the ctx variable:
export class MyDurableObject extends DurableObject<CloudflareEnv> {
...
const myService = new MyEntrypointService(this.ctx, this.env); // typescript error
}
export class MyDurableObject extends DurableObject<CloudflareEnv> {
...
const myService = new MyEntrypointService(this.ctx, this.env); // typescript error
}
The error is:
Argument of type 'DurableObjectState' is not assignable to parameter of type 'ExecutionContext'. Type 'DurableObjectState' is missing the following properties from type 'ExecutionContext': passThroughOnException, props ts(2345)
My question is: Is there anything inherently wrong/dangerous with using the durable object's ctx (type DurableObjectState) as the ctx for the WorkerEntrypoing (type ExecutionContext)? It is supposedly missing passThroughOnException and props. Can anyone enlighten me on the implications?
2 Replies
Patrick M
Patrick M4mo ago
I don't think props and passThroughOnException are commonly used so you could probably just ignore the typescript warning and it would work... That said it's quite unusual to instantiate a WorkerEntrypoint yourself, usually Cloudflare will do that when you add the service binding in wrangler.json/wrangler.json. if your goal is to share some logic between both WorkerEntrypoint and DurableObject, maybe you can extract the logic outside of the WorkerEntrypoint and reference from both of these. Here's one way to resolve the typescript error:
class MySharedLogic {
/** From WorkerEntrypoint or DurableObject */
private ctx: ExecutionContext | DurableObjectState;
private env: CloudflareEnv;

constructor(ctx: ExecutionContext | DurableObjectState, env: CloudflareEnv) {
this.ctx = ctx;
this.env = env;
}

getRandomNumber() {
return Math.random();
}
}

export class MyEntrypointService extends WorkerEntrypoint<CloudflareEnv> {
sharedLogic = new MySharedLogic(this.ctx, this.env);

getRandomNumber() {
return this.sharedLogic.getRandomNumber();
}
}

export class MyDurableObject extends DurableObject<CloudflareEnv> {
sharedLogic = new MySharedLogic(this.ctx, this.env);

getRandomNumber() {
return this.sharedLogic.getRandomNumber();
}
}
class MySharedLogic {
/** From WorkerEntrypoint or DurableObject */
private ctx: ExecutionContext | DurableObjectState;
private env: CloudflareEnv;

constructor(ctx: ExecutionContext | DurableObjectState, env: CloudflareEnv) {
this.ctx = ctx;
this.env = env;
}

getRandomNumber() {
return Math.random();
}
}

export class MyEntrypointService extends WorkerEntrypoint<CloudflareEnv> {
sharedLogic = new MySharedLogic(this.ctx, this.env);

getRandomNumber() {
return this.sharedLogic.getRandomNumber();
}
}

export class MyDurableObject extends DurableObject<CloudflareEnv> {
sharedLogic = new MySharedLogic(this.ctx, this.env);

getRandomNumber() {
return this.sharedLogic.getRandomNumber();
}
}
scook
scookOP3mo ago
@Patrick M Thanks! I suspected so, but I wanted to be sure props and passThroughOnException weren't needed behind-the-scenes during the class instantiation. The reason I'm instantiating the WorkerEntrypoint myself is that I have many of them defined in the same Worker. Some of the WorkerEntrpoints (e.g. a logger service) are used very frequently across the Worker code. I figured I have two choices of calling a method: 1. Instantiate one of the workerentrypoint classes myself and call a method, e.g. new LoggerEntrypoint(this.ctx, this.env).log(); 2. Bind the service to the worker in wrangler.toml and call the method through the binding, e.g. this.env.LOGGER_SERVICE.log(); My understanding is that #2 would count toward the worker subrequests limit since it is a service binding, whereas #1 would not (https://developers.cloudflare.com/workers/platform/limits/#worker-to-worker-subrequests). Avoiding the subrequest limit is my primary motivation. And yes, the alternative is to refactor so that the code is extracted into a shared function separate from the service so that I can call it anywhere. From support:
By using the Durable Object's ctx in the Worker Entrypoint, you might encounter the following issues: Exceptions thrown by the Worker Entrypoint might not be handled as expected, potentially leading to unexpected behavior or errors. You won't have access to the Worker's configuration and environment variables, which might be necessary for your application.

Did you find this page helpful?