Releases: cloudflare/partykit
Release list
partysocket@1.3.0
Minor Changes
- #409
6b97742Thanks @threepointone! - AddshouldReconnectOnClose, allowing callers to stop automatic reconnects for terminal close events while preserving the existing reconnect-by-default behavior.
partyserver@0.5.8
Patch Changes
- #407
2be6104Thanks @threepointone! - Encode thex-partykit-propsheader as base64 so props containing non-ASCII characters (e.g. accented names like "Usuário") no longer trigger workerd's "header value contains non-ASCII characters" warning, which would throw aTypeErrorin browser fetch implementations. The header is decoded back to the original Unicode payload on the server, and raw-JSON values from older callers are still accepted for backwards compatibility.
partyserver@0.5.7
Patch Changes
-
#405
7dbf92cThanks @threepointone! - Accept non-hibernating WebSocket connections in half-open mode (accept({ allowHalfOpen: true })).On compatibility dates
>= 2026-04-07theweb_socket_auto_reply_to_closeflag makes the runtime send a reciprocal Close frame and tear the socket down automatically. For a non-hibernating PartyServer (hibernate: false), the Durable Object sits on the server end of a connection that the runtime tunnels back to the client, so that auto-teardown could fire through an already-severed tunnel — surfacing as a spurious retryableNetwork connection lost.rejection (for example when a Durable Object is reset while a connection is still open). Half-open mode keeps PartyServer's existing close handling in control; it already reciprocates the peer's Close frame on every compatibility date, so client behavior is unchanged.Also in this release, two related WebSocket fixes that keep behavior consistent across all compatibility dates:
- Pin
binaryTypeto"arraybuffer"for non-hibernating connections. On compatibility dates>= 2026-03-17thewebsocket_standard_binary_typeflag flips the default server-sidebinaryTypefrom"arraybuffer"to"blob", so binary frames arrived asBlobinstead ofArrayBufferon the in-memory path. PartyServer (and frameworks built on it, e.g. Cloudflare Agents) have always receivedArrayBuffer, so it is now pinned back inaccept(). This is a no-op on older dates and corrective on newer ones; the Hibernation API is unaffected (it always deliversArrayBuffer). - Stop reporting transport-teardown errors as
onError. A retryableNetwork connection lost./WebSocket peer disconnectederror that fires on an already closing/closed connection is the socket going away during the close handshake, not an application error. It is now suppressed when the connection isCLOSING/CLOSED(detected via the structuredretryableflag, with a message fallback), so it no longer spams logs on abrupt client disconnects. Genuine mid-connection (OPEN) errors still reachonError.
- Pin
partysocket@1.2.0
Minor Changes
-
#403
7e042ebThanks @threepointone! - Make buffered messages and connection teardown reliable across socket replacement and explicit close (see cloudflare/agents#1738).close()now dispatches itscloseevent synchronously (mirroring howreconnect()already dispatched its synthetic close). Consumers that detach their listeners right after closing — like the React hooks during cleanup — previously never observed the terminal close event, leaving "connection closed" handling (pending-call rejection, state resets) to never run. Afterclose()returns,readyStatereportsCLOSEDimmediately, even while the underlying socket finishes its closing handshake. Code that attached acloselistener after callingclose()and relied on the event arriving asynchronously must attach the listener first.send()now returns aboolean:trueif the message was transmitted immediately over an open connection,falseif it was buffered (delivered when the connection next opens, always before theopenevent is dispatched) or dropped becausemaxEnqueuedMessageswas reached. Callers implementing request/response protocols can use this to know whether a request is actually in flight.- New
drainQueuedMessages()method removes and returns all messages that were buffered bysend()but never transmitted, so a socket that's being discarded can hand its unsent buffer to a replacement instead of silently losing it. - React hooks (
usePartySocket,useWebSocket) now migrate buffered messages when they replace the socket because connection options changed. By default, buffered messages transfer only when the destination is unchanged (e.g. onlyquery— credentials — changed); if destination options (room,party,path,host, URL, ...) changed, the messages are discarded with a warning rather than delivered to a destination they weren't composed for. The newtransferEnqueuedMessagesoption overrides this:truealways transfers,falsenever does. - Warn when
send()is called afterclose(). Messages sent against a permanently closed socket are buffered into a queue that nothing will ever flush (unlessreconnect()is called later), which silently strands the message — and any caller waiting on a reply. This usually indicates a stale socket reference in the caller. The message is still buffered for backwards compatibility; the warning is emitted once per close cycle and resets onreconnect().
partysocket@1.1.19
partyserver@0.5.6
Patch Changes
- #399
f772382Thanks @threepointone! - Retry transient Durable Object routing errors fromroutePartykitRequestandgetServerByName, and expose route-level retry configuration.
partyserver@0.5.5
Patch Changes
- #394
9a927a3Thanks @threepointone! - Don't reciprocate the WebSocket close handshake when the runtime delivers a reserved close code (1005,1006,1015). These codes are synthesized by the runtime when the peer didn't actually send a Close frame — there is no handshake to complete. The earlier0.5.4change ([#393](https://github.com/cloudflare/partykit/issues/393)) normalized these to1000and tried to send a reciprocating Close frame anyway; in cross-isolate transports (notably WebSocket pairs that flow back through Durable Object RPC, e.g. Cloudflare Agents sub-agents) the reciprocation would succeed synchronously but schedule an outbound write on a transport whose peer was already gone. The runtime then rejected that write asynchronously withNetwork connection lost, escapingcloseQuietly's synchronoustry/catchand surfacing as an unhandled promise rejection in tests and production logs. The fix is to skip the reciprocation for reserved codes — there's nothing to acknowledge to a peer that didn't speak. The narrow user-visible behavior change: a client that callsws.close()with no code on compat dates< 2026-04-07(no auto-reply) will now observe a non-clean close instead of a clean1000close, because the framework no longer fabricates a reciprocation. Clients that pass an explicit close code are unaffected.
partyserver@0.5.4
Patch Changes
-
#391
6273c96Thanks @threepointone! - Persist a__ps_namefallback for name-based Durable Objects during initialization. This lets alarm handlers recoverthis.nameeven when firing on a stale on-disk alarm record that was scheduled by an older workerd version that didn't yet persistnameinto the alarm record. See #390. -
#393
5335251Thanks @threepointone! - Complete the WebSocket close handshake when a client initiates the close. Previously, both the hibernatingwebSocketClosehandler and the non-hibernating close-event listener forwarded to useronClosebut never sent a reciprocal Close frame, leaving clients stuck inCLOSINGuntil they timed out and reported1006(abnormal closure). The framework now reciprocates the peer's Close frame in afinallyblock on both paths — required by the Hibernation API on every compat date, and required by the standardaccept()API on compat dates before2026-04-07(where the runtime'sweb_socket_auto_reply_to_closeflag isn't yet active). Callingclose()on an already-closed socket is a silent no-op, so user code that already callsconnection.close(...)fromonCloseis unaffected. Reserved close codes (1005,1006,1015) are normalized to1000before reciprocation so they don't throwInvalidAccessError. See #389.
partyserver@0.5.3
Patch Changes
-
#386
8a3bc02Thanks @threepointone! - Document and test the supported pattern for using PartyServer with Durable Object Facets. No runtime behavior change.Background. Facets spawned via
ctx.facets.get(name, factory)without an explicitidinFacetStartupOptionsinherit the parent DO'sctx.id— includingctx.id.name. PartyServer'snamegetter readsctx.id.namestraight through, so on an implicit-id facetthis.namereturns the parent's name rather than the facet's logical name. This is a faithful reflection of the workerd contract, but it's almost never what framework authors expect.The fix is at the call site, not in PartyServer: pass
id: someBoundDONamespace.idFromName(facetName)toctx.facets.get(...). The facet then gets its own nativectx.id.name === facetNameand PartyServer'snamegetter does the right thing automatically. NosetName()is required, no__ps_namestorage record is written, and cold-wake recovery happens for free because the factory re-runs andidFromNameis deterministic.This release adds:
- A "Using PartyServer with Durable Object Facets" section in the README that walks through the recommended pattern with a code example, calls out the implicit-id footgun explicitly, and documents that plain-string
idvalues are not a substitute foridFromName(facetName)(workerd treats string ids asidFromString-like, so the resulting facet has noctx.id.name). setName()docstring updated to clarify that facets are NOT asetName()use case — point to the explicit-idpattern instead. The originalsetName()ctx.id.namemismatch throw is preserved as a typo guard for theidFromNamehappy path.- End-to-end facet test coverage against the real workerd
ctx.facets.get(...)API. AFacetParent/FacetChildfixture exercises both the implicit-id path (pinning the runtime contract thatthis.namereturns the parent's name in that flow — i.e., behavior-as-documentation so framework authors are unsurprised) and the explicit-id path (recommended; verifies that all reasonable id-construction strategies work and that cold wake recovers without any storage record). Plain-stringidis also tested; the test asserts it does NOT carry a name, pinning the contract so callers don't get tempted by the type signature.
The runtime behavior of
Server(thenamegetter,setName(), the legacy__ps_namehydrate inside#ensureInitialized()) is unchanged from 0.5.2. - A "Using PartyServer with Durable Object Facets" section in the README that walks through the recommended pattern with a code example, calls out the implicit-id footgun explicitly, and documents that plain-string
y-partyserver@2.2.0
Minor Changes
-
#378
f3ab44fThanks @threepointone! - Use nativectx.id.nameto populatethis.name.Durable Objects now expose
ctx.id.nameon every entry point (constructor, fetch, alarm, hibernating websocket handlers) when the DO is addressed viaidFromName()/getByName(). PartyServer now uses this as the primary source ofthis.name, which simplifies routing, eliminates storage writes, and makesthis.nameavailable inside the constructor.Changes in
partyserver:this.nameresolves fromthis.ctx.id.name. The apologeticworkerd#2240error message is gone.this.nameis now available inside the constructor and from class field initializers, not just aftersetName()/fetch()has run.routePartykitRequestno longer issues asetName()/_initAndFetch()RPC beforefetch(). The WebSocket path goes from 2 RPCs to 1; the HTTP path remains 1 RPC. Props, when supplied, are delivered to the DO via thex-partykit-propsrequest header, set afteronBeforeConnect/onBeforeRequesthooks run.getServerByNamecontinues to perform a single RPC to ensureonStart()has completed before returning, so user-defined RPC methods on the returned stub can rely on initialization being done. That RPC is now cheaper internally (no storage write; name is read fromctx.id.name).Serverno longer writes the__ps_namerecord to storage. Existing records remain on disk for backward compatibility and are only read insidealarm()as a fallback for alarms that were scheduled before 2026-03-15 (wherectx.id.nameis not carried into the alarm handler — see the Durable Objects ID docs).setName()and_initAndFetch()are marked@deprecated. They continue to work for backward compatibility.setName(name)now throws ifnamedoes not matchctx.id.name.- The
x-partykit-roomheader is still accepted as a fallback whenctx.id.nameis not available. - Error message when the name cannot be resolved has been rewritten to list the three real causes (unsupported addressing via
idFromString()/newUniqueId(), runtime too old to exposectx.id.name, or directstub.fetch()withoutroutePartykitRequest/getServerByName). - When reading
this.namethrows, it is becausectx.id.nameis undefined and no legacy fallback has populated the name: the DO was addressed viaidFromString()ornewUniqueId()(both unsupported), the runtime is too old to exposectx.id.name, or a pre-2026-03-15 alarm fired before the legacy storage fallback ran.
Changes in all affected packages (
partyserver,partysub,partysync,y-partyserver,hono-party):@cloudflare/workers-typespeer dependency bumped from^4.20240729.0to^4.20260424.1. The old range predatesctx.id.namein the type surface.
Not supported: addressing PartyServer DOs via
idFromString()ornewUniqueId(). These paths returnctx.id.name === undefinedinside the DO and will surface as a clear error fromthis.name. PartyServer has always assumed name-based addressing viagetServerByName/routePartykitRequest; this release makes that assumption explicit.