A facet is a child object inside a Durable Object, with a SQLite database of its own. A supervisor class starts a facet from a class that a Worker Loader provides, so generated or untrusted code gets durable storage without a Durable Object namespace. Read the Cloudflare Durable Object Facets documentation for the standard API.
The facets example creates a facet from a class that a Worker Loader provides.
ctx.facets.get(name, callback)returns a stub for the named facet. The callback returns{ class, id }, and celld runs it only to start the facet.worker.getDurableObjectClass("App")on a Worker Loader binding, orctx.exports.App, supplies theclass.ctx.exports.App({ props })creates a class handle with startup properties.ctx.facets.abort(name, reason)stops a facet and keeps its database.ctx.facets.delete(name)stops a facet and removes its database.- The stub answers
fetch()and the RPC methods of the class.
A facet is not a cell. It has no ownership record and no fencing epoch of its own, and it uses those of the root cell, which the Durable Objects page describes.
A facet runs on the owner node of the root cell. An eviction, a reset, and a move take the root object and its facets as one group, and the startup callback runs again at the next call.
As in workerd, a facet write commits in the database of the facet, so a rollback of a root transaction does not undo a facet call inside it. A facet and its root object therefore never commit atomically. celld holds an outbound effect of a facet, and the reply of a facet call, until the facet stream proves the writes of that call. The effect also passes the output gate of the root cell.
ctx.facets.get(name, callback) returns a stub, and celld runs the callback
only when the facet is not already running. The callback returns a class and
an optional id. The class comes from
worker.getDurableObjectClass("App") on a Worker Loader binding, or from
ctx.exports.App for an exported DurableObject class without a storage
migration; that facet runs in the isolate of its root.
A loaded class can extend DurableObject, or it can be a plain class with a
(state, env) constructor. A plain class answers fetch(), and its RPC
methods require the js_rpc compatibility flag, as on workerd.
id sets ctx.id in the facet. A DurableObjectId keeps its name, a string
ID stays a string, and an omitted ID inherits the parent ID and its name. An
unmigrated class handle has no idFromName(), so use a Durable Object
namespace to create a named ID.
ctx.exports.App({ props }) creates a class handle with startup properties.
The call copies the properties, so a later change to the object does not
reach ctx.props. The properties must support structured cloning. A
loopback class without properties supplies an empty object.
- The name alone selects the database, so a changed
idstill reaches the stored data. A name has a limit of 256 bytes. - A facet can start facets of its own, to a total depth of 4 that counts the root object.
ctx.facets.abort(name, reason)stops a facet and keeps its database. A call on the stopped stub throws the reason.ctx.facets.delete(name)stops the facet and removes its database and the database of every facet below it.
No name reaches a facet from outside its root object, so external traffic
must go through the supervisor. Use a separate Durable Object, addressed with
idFromName(), when children
must run on different nodes.
A facet can accept a WebSocket: its fetch() returns a 101 response with the
client end of a WebSocketPair, and the root object returns that response. The
facet can call accept() on the server end or pass it to
ctx.acceptWebSocket(). A facet can also open a WebSocket with
new WebSocket(url) or with an Upgrade: websocket fetch. celld delivers the
events of each such socket to the facet, not to the root object. Socket output
waits for the facet stream and passes the output gate of the root cell.
A facet socket does not hibernate, because celld cannot start a facet again
without its root object. A socket that the facet accepts or opens keeps the root
cell resident until it closes. The client end of a 101 response from another
Durable Object does not. getWebSockets() returns the sockets of that facet.
ctx.facets.abort() and ctx.facets.delete() close the sockets of each stopped
facet with code 1001, and the facet receives no close event. workerd drops the
connection instead, so its client sees 1006. A drain, an ownership move, or a
generation swap closes a facet socket with 1012, and an output gate failure
closes it with 1011.
- A Durable Object binding cannot supply a facet class. A class with a storage
migration cannot supply one either, because
ctx.exportsholds that class as a namespace. - Each facet has a separate SQLite database, which celld replicates as a separate stream under the bucket prefix of the root Durable Object.
- A facet cannot set an alarm.
storage.setAlarm()throws inside a facet, so the root object must hold the schedule. - A facet socket does not hibernate. It keeps the root cell resident until the socket closes.
- A facet stub is not awaitable, and a pipelined property path is unavailable, so a call must name one method.
- The
clone()method is unavailable.
The Cloudflare compatibility page lists the runtime APIs and the unsupported services.