diff --git a/src/content/docs/durable-objects/api/id.mdx b/src/content/docs/durable-objects/api/id.mdx index 97c339ff62fa022..255f77699c1b210 100644 --- a/src/content/docs/durable-objects/api/id.mdx +++ b/src/content/docs/durable-objects/api/id.mdx @@ -5,15 +5,15 @@ sidebar: order: 2 --- -import { Tabs, TabItem } from "~/components"; +import { Render, Tabs, TabItem } from "~/components"; ## Description -The `DurableObjectId` interface refers to a new or existing Durable Object instance. This interface is most frequently used by [`DurableObjectNamespace::get`](/durable-objects/api/namespace/#get) to obtain a stub for submitting requests to a Durable Object instance. +A Durable Object ID is a 64-digit hexadecimal number used to identify a Durable Object instance. Not all 64-digit hex numbers are valid IDs. Durable Object IDs are constructed indirectly via the [`DurableObjectNamespace`](/durable-objects/api/namespace) interface. -Note that creating an ID for a Durable Object instance does not create the Durable Object. The Durable Object is created lazily after calling [`DurableObjectNamespace::get`](/durable-objects/api/namespace/#get) to create a [`DurableObjectStub`](/durable-objects/api/stub) from a `DurableObjectId`. This ensures that objects are not constructed until they are actually accessed. +The `DurableObjectId` interface refers to a new or existing Durable Object instance. This interface is most frequently used by [`DurableObjectNamespace::get`](/durable-objects/api/namespace/#get) to obtain a [`DurableObjectStub`](/durable-objects/api/stub) for submitting requests to a Durable Object instance. Note that creating an ID for a Durable Object instance does not create the Durable Object. The Durable Object is created lazily after creating a stub from a `DurableObjectId`. This ensures that objects are not constructed until they are actually accessed. -:::note[`DurableObjectId`] +:::note[Logging] If you are experiencing an issue with a particular Durable Object instance, you may wish to log the `DurableObjectId` from your Worker and include it in your Cloudflare support request. @@ -25,16 +25,7 @@ If you are experiencing an issue with a particular Durable Object instance, you `toString` converts a `DurableObjectId` to a 64 digit hex string. This string is useful for logging purposes or storing the `DurableObjectId` elsewhere, for example, in a session cookie. This string can be used to reconstruct a `DurableObjectId` via `DurableObjectNamespace::idFromString`. -```js -// Create a new unique ID -const id = env.MY_DURABLE_OBJECT.newUniqueId(); -// Convert the ID to a string to be saved elsewhere, e.g. a session cookie -const session_id = id.toString(); - -... -// Recreate the ID from the string -const id = env.MY_DURABLE_OBJECT.idFromString(session_id); -``` + #### Parameters diff --git a/src/content/docs/durable-objects/api/namespace.mdx b/src/content/docs/durable-objects/api/namespace.mdx index 4e53d3398dc9131..946c10aad081c2c 100644 --- a/src/content/docs/durable-objects/api/namespace.mdx +++ b/src/content/docs/durable-objects/api/namespace.mdx @@ -5,11 +5,13 @@ sidebar: order: 1 --- -import { Tabs, TabItem } from "~/components"; +import { Render, Tabs, TabItem } from "~/components"; ## Description -The `DurableObjectNamespace` interface is used to obtain a reference to a new or existing Durable Object instance. The interface is accessible from the fetch handler on a Cloudflare Worker via the `env` parameter, which is the standard interface when referencing bindings declared in `wrangler.toml`. +A Durable Object namespace is a set of Durable Object instances that are backed by the same Durable Object class. There is only one Durable Object namespace per class. A Durable Object namespace can contain any number of Durable Object instances. + +The `DurableObjectNamespace` interface is used to obtain a reference to new or existing Durable Object instances. The interface is accessible from the fetch handler on a Cloudflare Worker via the `env` parameter, which is the standard interface when referencing bindings declared in `wrangler.toml`. This interface defines several [methods](/durable-objects/api/namespace/#methods) that can be used to create an ID for a Durable Object instance. Note that creating an ID for a Durable Object instance does not create the Durable Object. The Durable Object is created lazily after calling [`DurableObjectNamespace::get`](/durable-objects/api/namespace/#get) to create a [`DurableObjectStub`](/durable-objects/api/stub) from a `DurableObjectId`. This ensures that objects are not constructed until they are actually accessed. @@ -69,7 +71,7 @@ export default { ### `idFromName` -`idFromName` creates a [`DurableObjectId`](/durable-objects/api/id) which refers to an individual instance of the Durable Object class from a particular name. +`idFromName` creates a unique [`DurableObjectId`](/durable-objects/api/id) which refers to an individual instance of the Durable Object class. Named Durable Object instances are the most common method of referring to Durable Object instances. ```js const fooId = env.MY_DURABLE_OBJECT.idFromName("foo"); @@ -86,16 +88,18 @@ const barId = env.MY_DURABLE_OBJECT.idFromName("bar"); ### `newUniqueId` -`newUniqueId` creates a `DurableObjectId` which refers to an individual instance of the Durable Object class. +`newUniqueId` creates a randomly generated and unique [`DurableObjectId`](/durable-objects/api/id) which refers to an individual instance of the Durable Object class. IDs created using `newUniqueId`, will need to be stored as a string in order to refer to the same Durable Object again in the future. For example, the ID can be stored in Workers KV, another Durable Object instance, or in a cookie in the user's browser. ```js const id = env.MY_DURABLE_OBJECT.newUniqueId(); const euId = env.MY_DURABLE_OBJECT.newUniqueId({ jurisdiction: "eu" }); ``` -:::note[`newUniqueId`] +:::note[`newUniqueId` results in lower request latency at first use] + +The first time you get a Durable Object stub based on an ID derived from a name, the system has to take into account the possibility that a Worker on the opposite side of the world could have coincidentally accessed the same named Durable Object at the same time. To guarantee that only one instance of the Durable Object is created, the system must check that the Durable Object has not been created anywhere else. Due to the inherent limit of the speed of light, this round-the-world check can take up to a few hundred milliseconds. `newUniqueId` can skip this check. -IDs created by `newUniqueId` will result in lower latencies when getting a [`DurableObjectStub`](/durable-objects/api/stub) relative to [`idFromName`](/durable-objects/api/namespace/#idfromname). +After this first use, the location of the Durable Object instance will be cached around the world so that subsequent lookups are faster. ::: @@ -109,16 +113,9 @@ IDs created by `newUniqueId` will result in lower latencies when getting a [`Dur ### `idFromString` -`idFromString` creates a [`DurableObjectId`](/durable-objects/api/id) from a previously generated ID that has been converted to a string. This method ensures the ID is valid, for example, it checks that the ID consists of 64 hex digits. +`idFromString` creates a [`DurableObjectId`](/durable-objects/api/id) from a previously generated ID that has been converted to a string. This method throws an exception if the ID is invalid, for example, if the ID was not created from the same `DurableObjectNamespace`. -```js -// Create a new unique ID -const id = env.MY_DURABLE_OBJECT.newUniqueId(); -// Save the unique ID elsewhere, e.g. a session cookie via id.toString() -... -// Recreate the ID from the string -const id = env.MY_DURABLE_OBJECT.idFromString(session_id); -``` + #### Parameters @@ -132,6 +129,8 @@ const id = env.MY_DURABLE_OBJECT.idFromString(session_id); `get` obtains a [`DurableObjectStub`](/durable-objects/api/stub) from a [`DurableObjectId`](/durable-objects/api/id) which can be used to invoke methods on a Durable Object instance. +This method returns the stub immediately, often before a connection has been established to the Durable Object instance. This allows requests to be sent to the instance right away, without waiting for a network round trip. + ```js const id = env.MY_DURABLE_OBJECT.newUniqueId(); const stub = env.MY_DURABLE_OBJECT.get(id); @@ -139,7 +138,8 @@ const stub = env.MY_DURABLE_OBJECT.get(id); #### Parameters -- A required [`DurableObjectId`](/durable-objects/api/id) and an optional object with the key `locationHint` and value of a [locationHint](/durable-objects/reference/data-location/#provide-a-location-hint) string. +- A required [`DurableObjectId`](/durable-objects/api/id) +- An optional object with the key `locationHint` and value of a [locationHint](/durable-objects/reference/data-location/#provide-a-location-hint) string. #### Return values @@ -165,4 +165,3 @@ const euId = subnamespace.idFromName("foo"); ## Related resources - [Durable Objects: Easy, Fast, Correct – Choose Three](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/). -- [Durable Objects best practices](/durable-objects/best-practices/access-durable-objects-from-a-worker/). diff --git a/src/content/docs/durable-objects/api/stub.mdx b/src/content/docs/durable-objects/api/stub.mdx index 19dfbd9cc724082..bebf9de97b50200 100644 --- a/src/content/docs/durable-objects/api/stub.mdx +++ b/src/content/docs/durable-objects/api/stub.mdx @@ -5,83 +5,17 @@ sidebar: order: 3 --- -import { Tabs, TabItem } from "~/components"; +import { Render } from "~/components"; ## Description -The `DurableObjectStub` interface is used to obtain a reference a Durable Object instance and invoke methods on that instance. The type of `DurableObjectStub` is generic to allow for RPC methods to be invoked on the stub. +The `DurableObjectStub` interface is a client used to invoke methods on a remote Durable Object instance. The type of `DurableObjectStub` is generic to allow for RPC methods to be invoked on the stub. - +Durable Objects implement E-order semantics, a concept deriving from the [E distributed programming language](). When you make multiple calls to the same Durable Object, it is guaranteed that the calls will be delivered to the remote Durable Object in the order in which you made them. E-order semantics makes many distributed programming problems easier. E-order is implemented by the [Cap'n Proto](https://capnproto.org) distributed object-capability RPC protocol, which Cloudflare Workers uses for internal communications. -```js -import { DurableObject } from "cloudflare:workers"; - -// Durable Object -export class MyDurableObject extends DurableObject { - constructor(ctx, env) { - super(ctx, env); - } - - sayHello() { - return "Hello, World!"; - } -} - -// Worker -export default { - async fetch(request, env) { - // Every unique ID refers to an individual instance of the Durable Object class - const id = env.MY_DURABLE_OBJECT.idFromName("foo"); - - // A stub is a client used to invoke methods on the Durable Object instance - const stub = env.MY_DURABLE_OBJECT.get(id); - - // Methods on the Durable Object are invoked via the stub - const rpcResponse = stub.sayHello(); - - return new Response(rpcResponse); - }, -}; -``` - - - -```ts -import { DurableObject } from "cloudflare:workers"; - -export interface Env { - MY_DURABLE_OBJECT: DurableObjectNamespace; -} - -// Durable Object -export class MyDurableObject extends DurableObject { - constructor(ctx: DurableObjectState, env: Env) { - super(ctx, env); - } - - async sayHello(): String { - return "Hello, World!"; - } -} - -// Worker -export default { - async fetch(request, env) { - // Every unique ID refers to an individual instance of the Durable Object class - const id = env.MY_DURABLE_OBJECT.idFromName("foo"); - - // A stub is a client used to invoke methods on the Durable Object instance - const stub = env.MY_DURABLE_OBJECT.get(id); - - // Methods on the Durable Object are invoked via the stub - const rpcResponse = await stub.sayHello(); - - return new Response(rpcResponse); - }, -} satisfies ExportedHandler; -``` +If an exception is thrown by a Durable Object stub all in-flight calls and future calls will fail with [exceptions](/durable-objects/observability/troubleshooting/). To continue invoking methods on a remote Durable Object instance a Worker must recreate the stub. There are no ordering guarantees between different stubs. - + ## Properties diff --git a/src/content/docs/durable-objects/best-practices/access-durable-objects-from-a-worker.mdx b/src/content/docs/durable-objects/best-practices/access-durable-objects-from-a-worker.mdx deleted file mode 100644 index d485e9a5f0a136e..000000000000000 --- a/src/content/docs/durable-objects/best-practices/access-durable-objects-from-a-worker.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: Access a Durable Object from a Worker -pcx_content_type: concept -sidebar: - order: 1 - ---- - -import { Type } from "~/components"; - -To access a Durable Object from a Worker, you must first create a [Durable Object binding](/workers/runtime-apis/bindings/) in your Worker project's [`wrangler.toml`](/workers/wrangler/configuration/#durable-objects) file. The binding is configured to use a particular class and controls access to instances of that class. - -Durable Object bindings allow for communication between a Worker and a Durable Object. - -If you are using [Wrangler environments](/workers/wrangler/environments/), you must specify any Durable Object bindings you wish to use on a per-environment basis. - -## 1. Create Durable Object IDs - -A Durable Object ID is a 64-digit hexadecimal number used to identify the Durable Object you are sending the request to. Not all 64-digit hex numbers are valid IDs. The Durable Object ID is tied to a class. - -To create a Durable Object ID, you can choose to: - -* Generate IDs randomly. -* Derive IDs from names (these names are string data types). -* Parse previously-created IDs from strings. - -All three methods will allow you to create Durable Object IDs. - -### Generate IDs randomly - -The following code gives you a new Durable Object ID. Add the following to your Worker code. - -```js -let id = OBJECT_NAMESPACE.newUniqueId(); -``` - -The `newUniqueId()` method on a Durable Object namespace creates a new Durable Object ID randomly. `newUniqueId()` will never return the same ID twice. Thus, it is guaranteed that the Durable Object does not yet exist and has never existed at the time the method returns. - -:::note[Durable Object namespace] - -A Durable Object namespace is a set of Durable Objects that can be addressed by name, backed by the same class. There is only one Durable Object namespace per class. A Durable Object namespace can contain any number of Durable Objects. -::: - -When generating an ID randomly, you need to store the ID somewhere to reach the same Durable Object again in the future. For example, you can store the ID in Workers KV, in an external database, or in a cookie in the user's browser. - -Unique IDs are unguessable. You can use unique IDs in URL-based access control. - -To store the ID in external storage, use its `toString()` method to convert it into a hexadecimal string, and `OBJECT_NAMESPACE.idFromString()` to convert the string back into an ID later. - -:::note[Unique IDs perform best] - -When you construct a new unique ID, the system knows that the same ID will not be generated by another Worker running on the other side of the world at the same time. Therefore, you can instantiate the Durable Object nearby without waiting for any round-the-world synchronization. Whenever you have a convenient place to store the ID, it is recommended to use randomly-generated IDs for best performance. -::: - -### Derive IDs from names - -The following code allows you to use a name (which is a `String`) to extract the ID of your Durable Object. - -```js -let id = OBJECT_NAMESPACE.idFromName(name); -``` - -#### Parameters - -* `name` - * The Object name, an arbitrary string from which the ID is derived. - -This method derives a unique Durable Object ID from the given name string. It will always return the same ID when given the same name as input. - -:::note[Name-derived IDs require global lookups at creation] - -The first time you access a Durable Object based on an ID derived from a name, the system does not know anything about the Durable Object. It is possible that a Worker on the opposite side of the world could have coincidentally decided to access the same Durable Object at the same time. To guarantee that only one instance of the Durable Object is created worldwide, the system must check whether the Durable Object has been created anywhere else. Due to the inherent limit of the speed of light, this round-the-world check can take up to a few hundred milliseconds. After this check, the Durable Object will be instantiated near where it was first requested. - -After the Durable Object has been accessed the first time, location information will be cached around the world so that subsequent lookups can be faster. -::: - -### Parse previously-created IDs from strings - -```js -let id = OBJECT_NAMESPACE.idFromString(hexId); -``` - -#### Parameters - -* `hexId` - * An ID string constructed by calling the `toString()` method of an existing ID. - -This method parses an ID that was previously stringified. This is useful with IDs created using `newUniqueId()`, as these IDs need to be stored somewhere as a string. - -This method will throw an exception if it is passed an ID that was not originally created by `newUniqueId()` or `idFromName()`. It will also throw an exception if the ID was originally created for a different Durable Object namespace. - -## 2. Construct the stub using the ID - -Construct the stub for the Durable Object using the ID. A stub is a client Durable Object used to send messages to the Durable Object. - -```js -let stub = env.EXAMPLE_CLASS.get(id); -``` - -## 3. Use `fetch()` handler method - -The system calls the `fetch()` method of a Durable Object namespace when an HTTP request is sent to the Durable Object. These requests are not sent from the public Internet, but from other Workers using a Durable Object binding. - -The `fetch()` method takes a [`Request`](/workers/runtime-apis/request/) as the parameter and returns a [`Response`](/workers/runtime-apis/response/) (or a `Promise` for a `Response`). - -If the method fails with an uncaught exception, the exception will be thrown into the calling Worker that made the `fetch()` request. - -```js -let response = await stub.fetch(request); -``` diff --git a/src/content/docs/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.mdx b/src/content/docs/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.mdx index 5629a64a6a6b6da..a933da8f3a0689f 100644 --- a/src/content/docs/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.mdx +++ b/src/content/docs/durable-objects/best-practices/create-durable-object-stubs-and-send-requests.mdx @@ -1,128 +1,222 @@ --- -title: Create Durable Object stubs and send requests +title: Invoking methods pcx_content_type: concept sidebar: order: 2 - --- -import { Glossary, Type } from "~/components"; - -A Durable Object stub is a client Object used to send requests to a remote Durable Object. +import { Render, Tabs, TabItem } from "~/components"; -`OBJECT_NAMESPACE.get(id)` creates a Durable Object. +## Invoking methods on a Durable Object -Durable Objects implement E-order semantics. When you make multiple calls to the same Durable Object, it is guaranteed that the calls will be delivered to the remote Durable Object in the order in which you made them. E-order semantics makes many distributed programming problems easier. +All new projects and existing projects with a compatibility date greater than or equal to [`2024-04-03`](/workers/configuration/compatibility-dates/#durable-object-stubs-and-service-bindings-support-rpc) should prefer to invoke [Remote Procedure Call (RPC)](/workers/runtime-apis/rpc/) methods defined on a Durable Object class. Legacy projects can continue to invoke the `fetch` handler on the Durable Object class indefinitely. -However, due to random network disruptions or other transient issues, a Durable Object stub may become disconnected from its remote Durable Object. A disconnected stub is permanently broken. In this scenario, all in-flight calls and future calls will fail with [exceptions](/durable-objects/observability/troubleshooting/). +### Invoke RPC methods -To make new requests to the Durable Object, you must call `OBJECT_NAMESPACE.get(id)` again to get a new Durable Object stub. There are no ordering guarantees between requests to the new stub compared to the old one. If ordering is not a concern, you can create a new Durable Object for every request. +By writing a Durable Object class which inherits from the built-in type `DurableObject`, public methods on the Durable Objects class are exposed as [RPC methods](/workers/runtime-apis/rpc/), which you can call using a [DurableObjectStub](/durable-objects/api/stub) from a Worker. -:::note[E-order] +All RPC calls are [asynchronous](/workers/runtime-apis/rpc/lifecycle/), accept and return [serializable types](/workers/runtime-apis/rpc/), and [propagate exceptions](/workers/runtime-apis/rpc/error-handling/) to the caller without a stack trace. Refer to [Workers RPC](/workers/runtime-apis/rpc/) for complete details. + -E-order is a concept deriving from the [E distributed programming language](https://en.wikipedia.org/wiki/E_\(programming_language\)). E-order is implemented by the [Cap'n Proto](https://capnproto.org) distributed object-capability RPC protocol, which Cloudflare Workers uses for internal communications. +:::note +With RPC, the `DurableObject` superclass defines `ctx` and `env` as class properties. What was previously called `state` is now called `ctx` when you extend the `DurableObject` class. The name `ctx` is adopted rather than `state` for the `DurableObjectState` interface to be consistent between `DurableObject` and `WorkerEntrypoint` objects. ::: -## Terminology +Refer to [Build a Counter](/durable-objects/examples/build-a-counter/) for a complete example. -Durable Objects documentation uses several concepts and terms, including: +### Invoking the `fetch` handler - +If your project is stuck on a compatibility date before [`2024-04-03`](/workers/configuration/compatibility-dates/#durable-object-stubs-and-service-bindings-support-rpc), or has the need to send a [`Request`](/workers/runtime-apis/request/) object and return a `Response` object, then you should send requests to a Durable Object via the fetch handler. -## Get a Durable Object stub + ```js -let durableObjectStub = OBJECT_NAMESPACE.get(id); -``` +import { DurableObject } from "cloudflare:workers"; -### Parameters +// Durable Object +export class MyDurableObject extends DurableObject { + constructor(ctx, env) { + super(ctx, env); + } -* `id` - * An ID constructed using `newUniqueId()`, `idFromName()`, or `idFromString()` on this Durable Object namespace. For details, refer to [Access a Durable Object](/durable-objects/best-practices/access-durable-objects-from-a-worker/#generate-ids-randomly) . + async fetch(request) { + return new Response("Hello, World!"); + } +} - * This method constructs an Object, which is a local client that provides access to a remote Object. +// Worker +export default { + async fetch(request, env) { + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); - * If the remote Object does not already exist, it will be created. Thus, there will always be an Object accessible from the stub. + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); - * This method always returns the Object immediately, before it has connected to the remote Object. This allows you to begin making requests to the Object right away, without waiting for a network round trip. + // Methods on the Durable Object are invoked via the stub + const response = await stub.fetch(request); + return response; + }, +}; +``` -## Call a Durable Object + -You can call a Durable Object by: +```ts +import { DurableObject } from "cloudflare:workers"; -* **Recommended** Invoking [Remote Procedure Call (RPC)](/workers/runtime-apis/rpc/) methods defined on a Durable Object class (available for Workers with a [compatibility date greater than or equal to `2024-04-03`](/workers/configuration/compatibility-dates/#durable-object-stubs-and-service-bindings-support-rpc)). -* [Sending HTTP requests](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#send-http-requests) to a Durable Object's `fetch()` handler. -* Using the [WebSocket API](/durable-objects/reference/websockets/). +export interface Env { + MY_DURABLE_OBJECT: DurableObjectNamespace; +} -### Call RPC methods +// Durable Object +export class MyDurableObject extends DurableObject { + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + } -To use RPC, a Durable Objects class must extend the built-in type `DurableObject`. Then, public methods on a Durable Objects class are exposed as [RPC methods](/workers/runtime-apis/rpc/), which you can call from a Durable Object stub in a Worker. All RPC calls are [asynchronous](/workers/runtime-apis/rpc/lifecycle/), accept and return [serializable types](/workers/runtime-apis/rpc/), and [propagate exceptions](/workers/runtime-apis/rpc/error-handling/) to the caller without a stack trace. Refer to [Workers RPC](/workers/runtime-apis/rpc/) for complete details. + async fetch(request: Request): Promise { + return new Response("Hello, World!"); + } +} -```ts -import { DurableObject } from "cloudflare:workers"; +// Worker +export default { + async fetch(request, env) { + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); -export class Counter extends DurableObject { + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); - async increment(amount = 1) { - let value: number = (await this.ctx.storage.get("value")) || 0; - value += amount; - this.ctx.storage.put("value", value); - return value; - } -} -``` + // Methods on the Durable Object are invoked via the stub + const response = await stub.fetch(request); -```ts -// A Worker calling a Durable Object -let durableObjectStub = env.COUNTERS.get(id); -let response = await durableObjectStub.increment(); + return response; + }, +} satisfies ExportedHandler; ``` -:::note + -When you write a Worker that does not extend the [`DurableObject` class](/workers/configuration/compatibility-dates/#durable-object-stubs-and-service-bindings-support-rpc), the `ctx` object in your Durable Object is instead called `state`, and is provided as the first argument to the constructor, but not set as a property of your class. +The `URL` associated with the [`Request`](/workers/runtime-apis/request/) object passed to the `fetch()` handler of your Durable Object must be a well-formed URL, but does not have to be a publicly-resolvable hostname. -With RPC, the `DurableObject` superclass defines `ctx` and `env` as class properties. What was previously called `state` is now called `ctx` when you extend the `DurableObject` class. +Without RPC, customers frequently construct requests which corresponded to private methods on the Durable Object and dispatch requests from the `fetch` handler. RPC is obviously more ergonomic in this example. -This naming change from `state` to `ctx` was made both to ensure consistency between `DurableObject` and `WorkerEntrypoint`, and to avoid setting the false expectation that directly mutating the value of `state` would persist data. -::: + -Refer to [Build a Counter](/durable-objects/examples/build-a-counter/) for a full example. +```js +import { DurableObject } from "cloudflare:workers"; -### Send HTTP requests +// Durable Object +export class MyDurableObject extends DurableObject { + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + } + + private hello(name) { + return new Response(`Hello, ${name}!`); + } + + private goodbye(name) { + return new Response(`Goodbye, ${name}!`); + } + + async fetch(request) { + const url = new URL(request.url); + let name = url.searchParams.get("name"); + if (!name) { + name = "World"; + } + + switch (url.pathname) { + case "/hello": + return this.hello(name); + case "/goodbye": + return this.goodbye(name); + default: + return new Response("Bad Request", { status: 400 }); + } + } +} -```js -let response = await durableObjectStub.fetch(request); -// Alternatively, passing a URL directly: -let response = await durableObjectStub.fetch(url, options); -``` +// Worker +export default { + async fetch(_request, env, _ctx) { + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); + + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); -The `url` passed to the `fetch()` handler of your Durable Object must be a well-formed URL, but does not have to be a publicly-resolvable hostname. You can: + // Invoke the fetch handler on the Durable Object stub + let response = await stub.fetch("http://do/hello?name=World"); -* Pass the client `Request` directly into the `fetch()` handler as is. -* Use an internal URL scheme, such as `http://do/some-path`, as the `url` parameter in the `fetch()` handler. This allows you to construct your own path or query parameter based approach to sharing state between your client-facing Worker and your Durable Object. -* Alternatively, you can construct your own [`Request` object](/workers/runtime-apis/request/), which allows you to use a [`Headers`](/workers/runtime-apis/headers/) object to pass in key-value pairs, representing data you wish to pass to your Durable Objects. + return response; + }, +}; +``` -The example below shows you how to construct your own `Request` object: + ```ts -// Constructing a new Request and passing metadata to the Durable Object via headers -let doReq = new Request("http://do/write", { headers: { "user-id": userId }}) -let resp = await durableObjectStub.fetch(doReq) +import { DurableObject } from "cloudflare:workers"; -// Alternatively, using URL query params or paths -let resp = await durableObjectStub.fetch(`http://do/write?userId=${userId}`) -``` +export interface Env { + MY_DURABLE_OBJECT: DurableObjectNamespace; +} -:::note +// Durable Object +export class MyDurableObject extends DurableObject { + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + } + + private hello(name: string) { + return new Response(`Hello, ${name}!`); + } + + private goodbye(name: string) { + return new Response(`Goodbye, ${name}!`); + } + + async fetch(request: Request): Promise { + const url = new URL(request.url); + let name = url.searchParams.get("name"); + if (!name) { + name = "World"; + } + + switch (url.pathname) { + case "/hello": + return this.hello(name); + case "/goodbye": + return this.goodbye(name); + default: + return new Response("Bad Request", { status: 400 }); + } + } +} -To understand how exceptions are thrown from within a Durable Object, refer to the [Error handling](/durable-objects/best-practices/error-handling/) documentation. -::: +// Worker +export default { + async fetch(_request, env, _ctx) { + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); + + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); -## List Durable Objects + // Invoke the fetch handler on the Durable Object stub + let response = await stub.fetch("http://do/hello?name=World"); + + return response; + }, +} satisfies ExportedHandler; +``` -The Cloudflare REST API supports retrieving a [list of Durable Objects](/api/operations/durable-objects-namespace-list-objects) within a Durable Object namespace and a [list of namespaces](/api/operations/durable-objects-namespace-list-namespaces) associated with an account. + diff --git a/src/content/docs/durable-objects/best-practices/error-handling.mdx b/src/content/docs/durable-objects/best-practices/error-handling.mdx index c4a9e21467175d0..18c2a222b61a07a 100644 --- a/src/content/docs/durable-objects/best-practices/error-handling.mdx +++ b/src/content/docs/durable-objects/best-practices/error-handling.mdx @@ -3,102 +3,94 @@ title: Error handling pcx_content_type: concept sidebar: order: 4 - --- -Review how to handle errors (exceptions) generated by both your own Durable Object code as well as exceptions thrown by Durable Objects' infrastructure (such as overloads or network errors). +Any uncaught exceptions thrown by a Durable Object or thrown by Durable Objects' infrastructure (such as overloads or network errors) will be propagated to the callsite of the client. Catching these exceptions allows you to retry creating the [`DurableObjectStub`](/durable-objects/api/stub) and sending requests. + +JavaScript Errors with the property `.retryable` set to True are suggested to be retried if requests to the Durable Object are idempotent, or can be applied multiple times without changing the response. If requests are not idempotent, then you will need to decide what is best for your application. + +JavaScript Errors with the property `.overloaded` set to True should not be retried. If a Durable Object is overloaded, then retrying will worsen the overload and increase the overall error rate. + +It is strongly recommended to retry requests following the exponential backoff algorithm in production code when the error properties indicate that it is safe to do so. ## How exceptions are thrown Durable Objects can throw exceptions in one of two ways: -* An exception can be thrown within the user code which implements a Durable Object class. The resulting exception will have a `.remote` property set to `True` in this case. -* An exception can be generated by Durable Object's infrastructure. Some sources of infrastructure exceptions include: transient internal errors, sending too many requests to a single Durable Object, and too many requests being queued due to slow or excessive I/O (external API calls or storage operations) within an individual Durable Object. Some infrastructure exceptions may also have the `.remote` property set to `True` -- for example, when the Durable Object exceeds its memory or CPU limits. +- An exception can be thrown within the user code which implements a Durable Object class. The resulting exception will have a `.remote` property set to `True` in this case. +- An exception can be generated by Durable Object's infrastructure. Some sources of infrastructure exceptions include: transient internal errors, sending too many requests to a single Durable Object, and too many requests being queued due to slow or excessive I/O (external API calls or storage operations) within an individual Durable Object. Some infrastructure exceptions may also have the `.remote` property set to `True` -- for example, when the Durable Object exceeds its memory or CPU limits. Refer to [Troubleshooting](/durable-objects/observability/troubleshooting/) to review the types of errors returned by a Durable Object and/or Durable Objects infrastructure and how to prevent them. -## Understanding stubs - -A Durable Object stub is a client Object used to send requests to a remote Durable Object. To learn more about how to make requests to a Durable Object, refer to [Create Durable Objects stubs](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) and [Access a Durable Objects from a Worker](/durable-objects/best-practices/access-durable-objects-from-a-worker/). - ## Example -Any uncaught exceptions thrown by a Durable Object or its infrastructure will be propagated to the callsite of the client. Catching these exceptions allows you to retry creating the Durable Object stub and sending requests. - -:::note[Durable Object stubs] - - -If any exception is thrown from a Durable Object stub, then the stub will need to be recreated prior to retrying requests. This is to provide E-order semantics as discussed in [create Durable Object stubs and send requests](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#create-durable-object-stubs-and-send-requests). - -JavaScript Errors with the property `.retryable` set to True are suggested to be retried if requests to the Durable Object are idempotent, or can be applied multiple times without changing the response. If requests are not idempotent, then you will need to decide what is best for your application. - -JavaScript Errors with the property `.overloaded` set to True should not be retried. If a Durable Object is overloaded, then retrying will worsen the overload and increase the overall error rate. - - -::: +This example demonstrates retrying requests using the recommended exponential backoff algorithm. ```ts import { DurableObject } from "cloudflare:workers"; export interface Env { - ErrorThrowingObject: DurableObjectNamespace; + ErrorThrowingObject: DurableObjectNamespace; } export default { - async fetch(request, env, ctx) { - let userId = new URL(request.url).searchParams.get("userId") || ""; - const id = env.ErrorThrowingObject.idFromName(userId); - - // Retry behavior can be adjusted to fit your application. - let maxAttempts = 3; - let baseBackoffMs = 100; - let maxBackoffMs = 20000; - - let attempt = 0; - while (true) { - // Try sending the request - try { - // Create a Durable Object stub for each attempt, because certain types of - // errors will break the Durable Object stub. - const doStub = env.ErrorThrowingObject.get(id); - const resp = await doStub.fetch("http://your-do/"); - - return Response.json(resp); - } catch (e: any) { - if (!e.retryable) { - // Failure was not a transient internal error, so don't retry. - break; - } - } - let backoffMs = Math.min(maxBackoffMs, baseBackoffMs * Math.random() * Math.pow(2, attempt)); - attempt += 1; - if (attempt >= maxAttempts) { - // Reached max attempts, so don't retry. - break; - } - await scheduler.wait(backoffMs); - } - return new Response("server error", { status: 500 }); - }, + async fetch(request, env, ctx) { + let userId = new URL(request.url).searchParams.get("userId") || ""; + const id = env.ErrorThrowingObject.idFromName(userId); + + // Retry behavior can be adjusted to fit your application. + let maxAttempts = 3; + let baseBackoffMs = 100; + let maxBackoffMs = 20000; + + let attempt = 0; + while (true) { + // Try sending the request + try { + // Create a Durable Object stub for each attempt, because certain types of + // errors will break the Durable Object stub. + const doStub = env.ErrorThrowingObject.get(id); + const resp = await doStub.fetch("http://your-do/"); + + return Response.json(resp); + } catch (e: any) { + if (!e.retryable) { + // Failure was not a transient internal error, so don't retry. + break; + } + } + let backoffMs = Math.min( + maxBackoffMs, + baseBackoffMs * Math.random() * Math.pow(2, attempt), + ); + attempt += 1; + if (attempt >= maxAttempts) { + // Reached max attempts, so don't retry. + break; + } + await scheduler.wait(backoffMs); + } + return new Response("server error", { status: 500 }); + }, } satisfies ExportedHandler; export class ErrorThrowingObject extends DurableObject { - constructor(state: DurableObjectState, env: Env) { - super(state, env); - - // Any exceptions that are raised in your constructor will also set the - // .remote property to True - throw new Error("no good"); - } - - async fetch(req: Request) { - // Generate an uncaught exception - // A .remote property will be added to the exception propagated to the caller - // and will be set to True - throw new Error("example error"); - - // We never reach this - return Response.json({}); - } -}; + constructor(state: DurableObjectState, env: Env) { + super(state, env); + + // Any exceptions that are raised in your constructor will also set the + // .remote property to True + throw new Error("no good"); + } + + async fetch(req: Request) { + // Generate an uncaught exception + // A .remote property will be added to the exception propagated to the caller + // and will be set to True + throw new Error("example error"); + + // We never reach this + return Response.json({}); + } +} ``` diff --git a/src/content/docs/durable-objects/get-started/tutorial-with-sql-api.mdx b/src/content/docs/durable-objects/get-started/tutorial-with-sql-api.mdx index cfc02cef3aec8cc..a87688cd7b381a2 100644 --- a/src/content/docs/durable-objects/get-started/tutorial-with-sql-api.mdx +++ b/src/content/docs/durable-objects/get-started/tutorial-with-sql-api.mdx @@ -93,7 +93,7 @@ export class MyDurableObject extends DurableObject { -Workers communicate with a Durable Object using [remote-procedure call](/workers/runtime-apis/rpc/#_top). Public methods on a Durable Object class are exposed as [RPC methods](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#call-rpc-methods) to be called by another Worker. +Workers communicate with a Durable Object using [remote-procedure call](/workers/runtime-apis/rpc/#_top). Public methods on a Durable Object class are exposed as [RPC methods](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) to be called by another Worker. Your file should now look like: @@ -104,7 +104,9 @@ export class MyDurableObject extends DurableObject { constructor(ctx, env) {} async sayHello() { - let result = this.ctx.storage.sql.exec("SELECT 'Hello, World!' as greeting").one(); + let result = this.ctx.storage.sql + .exec("SELECT 'Hello, World!' as greeting") + .one(); return result.greeting; } } @@ -117,7 +119,9 @@ export class MyDurableObject extends DurableObject { constructor(ctx: DurableObjectState, env: Env) {} async sayHello() { - let result = this.ctx.storage.sql.exec("SELECT 'Hello, World!' as greeting").one(); + let result = this.ctx.storage.sql + .exec("SELECT 'Hello, World!' as greeting") + .one(); return result.greeting; } } @@ -126,9 +130,10 @@ export class MyDurableObject extends DurableObject { In the code above, you have: + 1. Defined a RPC method, `sayHello()`, that can be called by a Worker to communicate with a Durable Object. 2. Accessed a Durable Object's attached storage, which is a private SQLite database only accesible to the object, using [SQL API](/durable-objects/api/storage-api/#sqlexec) methods (`sql.exec()`) available on `ctx.storage` . -3. Returned an object representing the single row query result using `one()`, which checks that the query result has exactly one row. +3. Returned an object representing the single row query result using `one()`, which checks that the query result has exactly one row. 4. Return the `greeting` column from the row object result. ## 4. Instantiate and communicate with a Durable Object @@ -180,7 +185,7 @@ export default { In the code above, you have: 1. Exported your Worker's main event handlers, such as the `fetch()` handler for receiving HTTP requests. -2. Passed `env` into the `fetch()` handler. Bindings are delivered as a property of the environment object passed as the second parameter when an event handler or class constructor is invoked. By calling the `idFromName()` function on the binding, you use a string-derived object ID. You can also ask the system to [generate random unique IDs](/durable-objects/best-practices/access-durable-objects-from-a-worker/#generate-ids-randomly). System-generated unique IDs have better performance characteristics, but require you to store the ID somewhere to access the Object again later. +2. Passed `env` into the `fetch()` handler. Bindings are delivered as a property of the environment object passed as the second parameter when an event handler or class constructor is invoked. By calling the `idFromName()` function on the binding, you use a string-derived object ID. You can also ask the system to [generate random unique IDs](/durable-objects/api/namespace/#newuniqueid). System-generated unique IDs have better performance characteristics, but require you to store the ID somewhere to access the Object again later. 3. Derived an object ID from the URL path. `MY_DURABLE_OBJECT.idFromName()` always returns the same ID when given the same string as input (and called on the same class), but never the same ID for two different strings (or for different classes). In this case, you are creating a new object for each unique path. 4. Constructed the stub for the Durable Object using the ID. A stub is a client object used to send messages to the Durable Object. 5. Called a Durable Object by invoking a RPC method, `sayHello()`, on the Durable Object, which returns a `Hello, World!` string greeting. @@ -213,7 +218,7 @@ The `[[durable_objects.bindings]]` section contains the following fields: ## 6. Configure Durable Object class with SQLite storage backend -A migration is a mapping process from a class name to a runtime state. You perform a migration when creating a new Durable Object class, or when renaming, deleting or transferring an existing Durable Object class. +A migration is a mapping process from a class name to a runtime state. You perform a migration when creating a new Durable Object class, or when renaming, deleting or transferring an existing Durable Object class. Migrations are performed through the `[[migrations]]` configurations key in your `wrangler.toml` file. @@ -253,7 +258,6 @@ By finishing this tutorial, you have successfully created, tested and deployed a ### Related resources -- [Access a Durable Object from a Worker](/durable-objects/best-practices/access-durable-objects-from-a-worker/) - [Create Durable Object stubs](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) - [Access Durable Objects Storage](/durable-objects/best-practices/access-durable-objects-storage/) - [Miniflare](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare) - Helpful tools for mocking and testing your Durable Objects. diff --git a/src/content/docs/durable-objects/get-started/walkthrough.mdx b/src/content/docs/durable-objects/get-started/walkthrough.mdx index 6efea571227b8c0..feada4dea356241 100644 --- a/src/content/docs/durable-objects/get-started/walkthrough.mdx +++ b/src/content/docs/durable-objects/get-started/walkthrough.mdx @@ -9,8 +9,8 @@ import { Render, TabItem, Tabs, PackageManagers } from "~/components"; This guide will instruct you through: -- Writing a class that defines a Durable Object. -- Instantiating and communicating with a Durable Object from another Worker via the `Fetch` API. +- Writing a Durable Object class. +- Writing a Worker which invokes methods on a Durable Object instance. - Deploying a Durable Object. ## Prerequisites @@ -27,7 +27,7 @@ To enable Durable Objects, you will need to purchase the Workers Paid plan: ## 2. Create a Worker project -You will access your Durable Object from a [Worker](/workers/). Your Worker application is an interface to interact with your Durable Object. +Durable Objects are accessed from a [Worker](/workers/). To create a Worker project, run: @@ -57,9 +57,9 @@ Move into your new directory: cd durable-object-starter ``` -## 3. Write a class to define a Durable Object +## 3. Write a Durable Object class -Before you create and access a Durable Object, its behavior must be defined by an ordinary exported JavaScript class. +Durable Objects are defined by a exporting a standard JavaScript class which extends from the `DurableObject` base class. :::note @@ -90,9 +90,9 @@ export class MyDurableObject extends DurableObject { -Workers communicate with a Durable Object via the fetch API. Like a Worker, a Durable Object listens for incoming fetch events by registering an event handler. For a Durable Object, the fetch handler is defined as a method on the class. +Workers can invoke public methods defined on a Durable Object via Remote Procedure Call (RPC). -Your file should now look like: +The `sayHello` method demonstrates this capability: @@ -102,8 +102,8 @@ import { DurableObject } from "cloudflare:workers"; export class MyDurableObject extends DurableObject { constructor(state, env) {} - async fetch(request) { - return new Response("Hello World"); + async sayHello() { + return "Hello, World!"; } } ``` @@ -116,46 +116,36 @@ import { DurableObject } from "cloudflare:workers"; export class MyDurableObject extends DurableObject { constructor(state: DurableObjectState, env: Env) {} - async fetch(request: Request) { - return new Response("Hello World"); + async sayHello(): Promise { + return "Hello, World!"; } } ``` -A Worker can pass information to a Durable Object via headers, the HTTP method, the Request body, or the Request URI. +## 4. Invoke methods on a Durable Object class -:::note - -HTTP requests received by a Durable Object do not come directly from the Internet. HTTP requests come from other Workers and other Durable Objects. Durable Objects use HTTP for familiarity, but plan to introduce other protocols in the future. - -::: - -## 4. Instantiate and communicate with a Durable Object - -The `fetch()` handler allows you to instantiate and communicate to a Durable Object from a Worker. - -:::note - -Durable Objects do not receive requests directly from the Internet. Durable Objects receive requests from Workers or other Durable Objects. -This is achieved by configuring a binding in the calling Worker for each Durable Object class that you would like it to be able to talk to. These bindings must be configured at upload time. Methods exposed by the binding can be used to communicate with particular Durable Object instances. -::: +As mentioned previously, methods on a Durable Object class are invoked by a Worker. This is done by creating an ID refering to an instance of the Durable Object class, getting a stub that refers to a particular instance of a Durable Object class, and invoking methods on that stub. -To communicate with a Durable Object, the fetch handler should look like the following: +The fetch handler should look like the following: ```js +// Worker export default { async fetch(request, env) { - let id = env.MY_DURABLE_OBJECT.idFromName(new URL(request.url).pathname); + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); - let stub = env.MY_DURABLE_OBJECT.get(id); + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); - let response = await stub.fetch(request); + // Methods on the Durable Object are invoked via the stub + const rpcResponse = await stub.sayHello(); - return response; + return new Response(rpcResponse); }, }; ``` @@ -163,35 +153,28 @@ export default { ```ts +// Worker export default { async fetch(request, env, ctx): Promise { - let id = env.MY_DURABLE_OBJECT.idFromName(new URL(request.url).pathname); + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); - let stub = env.MY_DURABLE_OBJECT.get(id); + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); - let response = await stub.fetch(request); + // Methods on the Durable Object are invoked via the stub + const rpcResponse = await stub.sayHello(); - return response; + return new Response(rpcResponse); }, } satisfies ExportedHandler; ``` -In the code above, you have: - -1. Exported your Worker's main event handlers, such as the `fetch()` handler for receiving HTTP requests. -2. Passed `env` into the `fetch()` handler. Bindings are delivered as a property of the environment object passed as the second parameter when an event handler or class constructor is invoked. By calling the `idFromName()` function on the binding, you use a string-derived object ID. You can also ask the system to [generate random unique IDs](/durable-objects/best-practices/access-durable-objects-from-a-worker/#generate-ids-randomly). System-generated unique IDs have better performance characteristics, but require you to store the ID somewhere to access the Object again later. -3. Derived an object ID from the URL path. `MY_DURABLE_OBJECT.idFromName()` always returns the same ID when given the same string as input (and called on the same class), but never the same ID for two different strings (or for different classes). In this case, you are creating a new object for each unique path. -4. Constructed the stub for the Durable Object using the ID. A stub is a client object used to send messages to the Durable Object. -5. Forwarded the request to the Durable Object. `stub.fetch()` has the same signature as the global `fetch()` function, except that the request is always sent to the object, regardless of the request's URL. The first time you send a request to a new object, the object will be created for us. If you do not store durable state in the object, it will automatically be deleted later (and recreated if you request it again). If you store durable state, then the object may be evicted from memory but its durable state will be kept permanently. -6. Received an HTTP response back to the client with `return response`. - -Refer to [Access a Durable Object from a Worker](/durable-objects/best-practices/access-durable-objects-from-a-worker/) to learn more about communicating to a Durable Object. - ## 5. Configure Durable Object bindings -[Bindings](/workers/runtime-apis/bindings/) allow your Workers to interact with resources on the Cloudflare developer platform. The Durable Object bindings in your Worker project's `wrangler.toml` will include a binding name (for this guide, use `MY_DURABLE_OBJECT`) and the class name (`MyDurableObject`). Refer to [Wrangler Configuration](/workers/wrangler/configuration/#durable-objects) for more detail. +To allow a Worker to invoke methods on a Durable Object instance, the Worker must have a [Durable Object binding](/workers/runtime-apis/bindings/) in the project's [`wrangler.toml`](/workers/wrangler/configuration/#durable-objects) file. The binding is configured to use a particular Durable Object class. ```toml [[durable_objects.bindings]] @@ -213,6 +196,8 @@ The `[[durable_objects.bindings]]` section contains the following fields: - `script_name` - Optional. The name of the Worker if the Durable Object is external to this Worker. - `environment` - Optional. The environment of the `script_name` to bind to. +Refer to [Wrangler Configuration](/workers/wrangler/configuration/#durable-objects) for more detail. + ## 6. Configure Durable Object classes with migrations A migration is a mapping process from a class name to a runtime state. You perform a migration when creating a new Durable Object class, or when renaming, deleting or transferring an existing Durable Object class. @@ -273,6 +258,5 @@ By finishing this tutorial, you have successfully created, tested and deployed a ### Related resources -- [Access a Durable Object from a Worker](/durable-objects/best-practices/access-durable-objects-from-a-worker/) -- [Create Durable Object stubs](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) +- [Send requests to Durable Objects](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) - [Miniflare](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare) - Helpful tools for mocking and testing your Durable Objects. diff --git a/src/content/docs/durable-objects/observability/troubleshooting.mdx b/src/content/docs/durable-objects/observability/troubleshooting.mdx index a1ed17d803ed531..78a126ce25b8d73 100644 --- a/src/content/docs/durable-objects/observability/troubleshooting.mdx +++ b/src/content/docs/durable-objects/observability/troubleshooting.mdx @@ -3,7 +3,6 @@ title: Troubleshooting pcx_content_type: concept sidebar: order: 5 - --- ## Debugging @@ -39,7 +38,7 @@ These errors and others that are due to overload will have an [`.overloaded` pro ### Your account is generating too much load on Durable Objects. Please back off and try again later. -There is a limit on how quickly you can [create new Durable Objects or lookup different existing Durable Objects](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/). Those lookups are usually cached, meaning attempts for the same set of recently accessed Durable Objects should be successful, so catching this error and retrying after a short wait is safe. If possible, also consider spreading those lookups across multiple requests. +There is a limit on how quickly you can create new [stubs](/durable-objects/api/stub) for new or existing Durable Objects. Those lookups are usually cached, meaning attempts for the same set of recently accessed Durable Objects should be successful, so catching this error and retrying after a short wait is safe. If possible, also consider spreading those lookups across multiple requests. ### Durable Object reset because its code was updated. diff --git a/src/content/docs/durable-objects/reference/websockets.mdx b/src/content/docs/durable-objects/reference/websockets.mdx index fc32364f937c402..0345fa49d578645 100644 --- a/src/content/docs/durable-objects/reference/websockets.mdx +++ b/src/content/docs/durable-objects/reference/websockets.mdx @@ -3,7 +3,6 @@ pcx_content_type: concept title: Durable Objects with WebSockets sidebar: order: 3 - --- [WebSockets](/durable-objects/api/websockets/) allow real time communication between a client and server. Both Cloudflare Durable Objects and Workers can act as WebSocket endpoints – either as a client or as a server. @@ -14,16 +13,14 @@ Durable Objects provide a single-point-of-coordination for [Cloudflare Workers]( While there are other use cases for using Workers exclusively with WebSockets, WebSockets are most useful when combined with Durable Objects. -When a client connects to your application using a WebSocket, you need a way for server-generated messages to be sent using the existing socket connection. Multiple clients can establish a WebSocket connection with a specific Durable Object addressed by its [unique ID](/durable-objects/best-practices/access-durable-objects-from-a-worker/#1-create-durable-object-ids). The Durable Object can then send messages to each client over the WebSocket connection. +When a client connects to your application using a WebSocket, you need a way for server-generated messages to be sent using the existing socket connection. Multiple clients can establish a WebSocket connection with a specific Durable Object addressed by its unique ID. The Durable Object can then send messages to each client over the WebSocket connection. Durable Objects can use the web standard APIs described in [WebSockets API](/durable-objects/api/websockets/). Refer to [Cloudflare Edge Chat Demo](https://github.com/cloudflare/workers-chat-demo) for an example of using Durable Objects with WebSockets. :::caution[WebSockets disconnection] - Code updates will disconnect all WebSockets. If you deploy a new version of a Worker, every Durable Object is restarted. Any connections to old Durable Objects will be disconnected. - ::: ## WebSocket Hibernation @@ -32,19 +29,17 @@ The WebSocket Hibernation API allows a Durable Object that is not currently runn :::note - Hibernation is only supported when a Durable Object acts as a WebSocket server. Outgoing WebSockets cannot be hibernated as of now. - ::: A Durable Object with WebSockets created via the Hibernation API will not incur billable [Duration (GB-s) charges](/durable-objects/platform/pricing/) during periods of inactivity, unlike Durable Objects using the [regular WebSockets API](/workers/runtime-apis/websockets/). The WebSocket Hibernation API includes: -* Cloudflare-specific extensions to the web standard WebSocket API. -* Related methods on the [`state`](/durable-objects/api/websockets/#state-methods) of the Durable Object. -* [Handler methods](/durable-objects/api/websockets/#handler-methods) that a Durable Object can implement for processing WebSocket events. +- Cloudflare-specific extensions to the web standard WebSocket API. +- Related methods on the [`state`](/durable-objects/api/websockets/#state-methods) of the Durable Object. +- [Handler methods](/durable-objects/api/websockets/#handler-methods) that a Durable Object can implement for processing WebSocket events. The WebSocket Hibernation API enables you to terminate (not proxy) WebSocket connections within a Durable Object, and push messages to all connected clients based on state stored within the [Storage API](/durable-objects/api/storage-api/), HTTP fetches to external services, and/or data stored in [R2](/r2/) and [Workers KV](/kv/api/). @@ -54,16 +49,14 @@ If an event occurs for a hibernated Durable Object's corresponding handler metho :::caution[Support for local development] - Prior to `wrangler@3.13.2` and Miniflare `v3.20231016.0`, WebSockets did not hibernate when using local development environments such as `wrangler dev` or Miniflare. If you are using older versions, note that while hibernatable WebSocket events such as [`webSocketMessage()`](/durable-objects/api/websockets/#websocketmessage) will still be delivered, the Durable Object will never be evicted from memory. - ::: -*** +--- ## Related resources -* Refer to [Build a WebSocket server with WebSocket Hibernation](/durable-objects/examples/websocket-hibernation-server/) to learn more about building a WebSocket server using WebSocket Hibernation on Durable Objects and Workers. +- Refer to [Build a WebSocket server with WebSocket Hibernation](/durable-objects/examples/websocket-hibernation-server/) to learn more about building a WebSocket server using WebSocket Hibernation on Durable Objects and Workers. diff --git a/src/content/docs/queues/examples/use-queues-with-durable-objects.mdx b/src/content/docs/queues/examples/use-queues-with-durable-objects.mdx index 851a909d53430d2..3c420b8009d766f 100644 --- a/src/content/docs/queues/examples/use-queues-with-durable-objects.mdx +++ b/src/content/docs/queues/examples/use-queues-with-durable-objects.mdx @@ -8,16 +8,15 @@ head: - tag: title content: Queues - Use Queues and Durable Objects description: Publish to a queue from within a Durable Object. - --- The following example shows you how to write a Worker script to publish to [Cloudflare Queues](/queues/) from within a [Durable Object](/durable-objects/). Prerequisites: -* A [queue created](/queues/get-started/#3-create-a-queue) via the Cloudflare dashboard or the [wrangler CLI](/workers/wrangler/install-and-update/). -* A [configured **producer** binding](/queues/configuration/configure-queues/#producer) in the Cloudflare dashboard or `wrangler.toml` file. -* A [Durable Object namespace binding](/workers/wrangler/configuration/#durable-objects). +- A [queue created](/queues/get-started/#3-create-a-queue) via the Cloudflare dashboard or the [wrangler CLI](/workers/wrangler/install-and-update/). +- A [configured **producer** binding](/queues/configuration/configure-queues/#producer) in the Cloudflare dashboard or `wrangler.toml` file. +- A [Durable Object namespace binding](/workers/wrangler/configuration/#durable-objects). Configure your `wrangler.toml` file as follows: @@ -44,7 +43,7 @@ The following Worker script: 2. Passes request data to the Durable Object. 3. Publishes to a queue from within the Durable Object. -The `constructor()` in the Durable Object makes your `Environment` available (in scope) on `this.env` to the [`fetch()` handler](/durable-objects/best-practices/access-durable-objects-from-a-worker/#3-use-fetch-handler-method) in the Durable Object. +The `constructor()` in the Durable Object makes your `Environment` available (in scope) on `this.env` to the [`fetch()` handler](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) in the Durable Object. ```ts interface Env { diff --git a/src/content/docs/workers/configuration/versions-and-deployments/gradual-deployments.mdx b/src/content/docs/workers/configuration/versions-and-deployments/gradual-deployments.mdx index 333cc558fbc3e90..b7652480d2c2e7a 100644 --- a/src/content/docs/workers/configuration/versions-and-deployments/gradual-deployments.mdx +++ b/src/content/docs/workers/configuration/versions-and-deployments/gradual-deployments.mdx @@ -205,7 +205,7 @@ When you create a new gradual deployment for a Durable Object Worker, each Durab ### Example -This example assumes that you have previously created 3 Durable Objects and [derived their IDs from the names](/durable-objects/best-practices/access-durable-objects-from-a-worker/#derive-ids-from-names) "foo", "bar" and "baz". +This example assumes that you have previously created 3 Durable Objects and [derived their IDs from the names](/durable-objects/api/namespace/#idfromname) "foo", "bar" and "baz". Your Worker is currently on a version that we will call version "A" and you want to gradually deploy a new version "B" of your Worker. diff --git a/src/content/partials/durable-objects/example-id-from-string.mdx b/src/content/partials/durable-objects/example-id-from-string.mdx new file mode 100644 index 000000000000000..ae7f47a1e68ca46 --- /dev/null +++ b/src/content/partials/durable-objects/example-id-from-string.mdx @@ -0,0 +1,14 @@ +--- +{} +--- + +```js +// Create a new unique ID +const id = env.MY_DURABLE_OBJECT.newUniqueId(); +// Convert the ID to a string to be saved elsewhere, e.g. a session cookie +const session_id = id.toString(); + +... +// Recreate the ID from the string +const id = env.MY_DURABLE_OBJECT.idFromString(session_id); +``` diff --git a/src/content/partials/durable-objects/example-rpc.mdx b/src/content/partials/durable-objects/example-rpc.mdx new file mode 100644 index 000000000000000..a22506ec029d614 --- /dev/null +++ b/src/content/partials/durable-objects/example-rpc.mdx @@ -0,0 +1,77 @@ +--- +{} +--- + +import { Tabs, TabItem } from "~/components"; + + + +```js +import { DurableObject } from "cloudflare:workers"; + +// Durable Object +export class MyDurableObject extends DurableObject { + constructor(ctx, env) { + super(ctx, env); + } + + async sayHello() { + return "Hello, World!"; + } +} + +// Worker +export default { + async fetch(request, env) { + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); + + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); + + // Methods on the Durable Object are invoked via the stub + const rpcResponse = await stub.sayHello(); + + return new Response(rpcResponse); + }, +}; +``` + + + +```ts +import { DurableObject } from "cloudflare:workers"; + +export interface Env { + MY_DURABLE_OBJECT: DurableObjectNamespace; +} + +// Durable Object +export class MyDurableObject extends DurableObject { + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + } + + async sayHello(): Promise { + return "Hello, World!"; + } +} + +// Worker +export default { + async fetch(request, env) { + // Every unique ID refers to an individual instance of the Durable Object class + const id = env.MY_DURABLE_OBJECT.idFromName("foo"); + + // A stub is a client used to invoke methods on the Durable Object instance + const stub = env.MY_DURABLE_OBJECT.get(id); + + // Methods on the Durable Object are invoked via the stub + const rpcResponse = await stub.sayHello(); + + return new Response(rpcResponse); + }, +} satisfies ExportedHandler; +``` + + diff --git a/src/content/partials/workers/durable_objects_pricing.mdx b/src/content/partials/workers/durable_objects_pricing.mdx index 3cc59d612cac2f3..7c9464d199a9d3e 100644 --- a/src/content/partials/workers/durable_objects_pricing.mdx +++ b/src/content/partials/workers/durable_objects_pricing.mdx @@ -1,18 +1,21 @@ --- {} - --- -import { Markdown } from "~/components" +import { Markdown } from "~/components"; [Durable Objects](/durable-objects/) are only available on the [Workers Paid plan](/workers/platform/pricing/#workers). -| | Paid plan | -| -------- | ------------------------------------------------- | -| Requests | 1 million, + $0.15/million
Includes HTTP requests, RPC sessions1, WebSocket messages2, and alarm invocations | -| Duration3 | 400,000 GB-s, + $12.50/million GB-s4,5 | +| | Paid plan | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| Requests | 1 million, + $0.15/million
Includes HTTP requests, RPC sessions1, WebSocket messages2, and alarm invocations | +| Duration3 | 400,000 GB-s, + $12.50/million GB-s4,5 | -1 Each [RPC session](/workers/runtime-apis/rpc/lifecycle/) is billed as one request to your Durable Object. Every [RPC method call](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#call-rpc-methods) on a [Durable Objects stub](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/#get-a-durable-object-stub) is its own RPC session and therefore a single billed request. +1 Each [RPC session](/workers/runtime-apis/rpc/lifecycle/) is billed +as one request to your Durable Object. Every [RPC method +call](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) +on a [Durable Objects stub](/durable-objects/) is its own RPC session and +therefore a single billed request. RPC method calls can return objects (stubs) extending [`RpcTarget`](/workers/runtime-apis/rpc/lifecycle/#lifetimes-memory-and-resource-management) and invoke calls on those stubs. Subsequent calls on the returned stub are part of the same RPC session and are not billed as separate requests. For example: @@ -23,10 +26,31 @@ await foo.baz(); // treated as part of the same RPC session created by calling b await durableObjectStub.cat(); // billed as a request ``` -2 A request is needed to create a WebSocket connection. There is no charge for outgoing WebSocket messages, nor for incoming [WebSocket protocol pings](https://www.rfc-editor.org/rfc/rfc6455#section-5.5.2). For compute requests billing-only, a 20:1 ratio is applied to incoming WebSocket messages to factor in smaller messages for real-time communication. For example, 100 WebSocket incoming messages would be charged as 5 requests for billing purposes. The 20:1 ratio does not affect Durable Object metrics and analytics, which reflect actual usage. - -3 Application level auto-response messages handled by [`state.setWebSocketAutoResponse()`](/durable-objects/api/websockets/) will not incur additional wall-clock time, and so they will not be charged. - -4 Duration is billed in wall-clock time as long as the Object is active, but is shared across all requests active on an Object at once. Once your Object finishes responding to all requests, it will stop incurring duration charges. Calling `accept()` on a WebSocket in an Object will incur duration charges for the entire time the WebSocket is connected. If you prefer, use [`state.acceptWebSocket()`](/durable-objects/api/websockets/#state-methods-for-websockets) instead, which will stop incurring duration charges once all event handlers finish running. - -5 Duration billing charges for the 128 MB of memory your Durable Object is allocated, regardless of actual usage. If your account creates many instances of a single Durable Object class, Durable Objects may run in the same isolate on the same physical machine and share the 128 MB of memory. These Durable Objects are still billed as if they are allocated a full 128 MB of memory. +2 A request is needed to create a WebSocket connection. There is no +charge for outgoing WebSocket messages, nor for incoming [WebSocket protocol +pings](https://www.rfc-editor.org/rfc/rfc6455#section-5.5.2). For compute +requests billing-only, a 20:1 ratio is applied to incoming WebSocket messages to +factor in smaller messages for real-time communication. For example, 100 +WebSocket incoming messages would be charged as 5 requests for billing purposes. +The 20:1 ratio does not affect Durable Object metrics and analytics, which +reflect actual usage. + +3 Application level auto-response messages handled by +[`state.setWebSocketAutoResponse()`](/durable-objects/api/websockets/) will not +incur additional wall-clock time, and so they will not be charged. + +4 Duration is billed in wall-clock time as long as the Object is +active, but is shared across all requests active on an Object at once. Once your +Object finishes responding to all requests, it will stop incurring duration +charges. Calling `accept()` on a WebSocket in an Object will incur duration +charges for the entire time the WebSocket is connected. If you prefer, use +[`state.acceptWebSocket()`](/durable-objects/api/websockets/#state-methods-for-websockets) +instead, which will stop incurring duration charges once all event handlers +finish running. + +5 Duration billing charges for the 128 MB of memory your Durable +Object is allocated, regardless of actual usage. If your account creates many +instances of a single Durable Object class, Durable Objects may run in the same +isolate on the same physical machine and share the 128 MB of memory. These +Durable Objects are still billed as if they are allocated a full 128 MB of +memory.