Skip to content

Releases: cloudflare/partykit

partysocket@1.3.0

Choose a tag to compare

@github-actions github-actions released this 23 Jun 08:46

Minor Changes

  • #409 6b97742 Thanks @threepointone! - Add shouldReconnectOnClose, allowing callers to stop automatic reconnects for terminal close events while preserving the existing reconnect-by-default behavior.

partyserver@0.5.8

Choose a tag to compare

@github-actions github-actions released this 14 Jun 00:27

Patch Changes

  • #407 2be6104 Thanks @threepointone! - Encode the x-partykit-props header 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 a TypeError in 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

Choose a tag to compare

@github-actions github-actions released this 13 Jun 20:45

Patch Changes

  • #405 7dbf92c Thanks @threepointone! - Accept non-hibernating WebSocket connections in half-open mode (accept({ allowHalfOpen: true })).

    On compatibility dates >= 2026-04-07 the web_socket_auto_reply_to_close flag 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 retryable Network 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 binaryType to "arraybuffer" for non-hibernating connections. On compatibility dates >= 2026-03-17 the websocket_standard_binary_type flag flips the default server-side binaryType from "arraybuffer" to "blob", so binary frames arrived as Blob instead of ArrayBuffer on the in-memory path. PartyServer (and frameworks built on it, e.g. Cloudflare Agents) have always received ArrayBuffer, so it is now pinned back in accept(). This is a no-op on older dates and corrective on newer ones; the Hibernation API is unaffected (it always delivers ArrayBuffer).
    • Stop reporting transport-teardown errors as onError. A retryable Network connection lost. / WebSocket peer disconnected error 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 is CLOSING/CLOSED (detected via the structured retryable flag, with a message fallback), so it no longer spams logs on abrupt client disconnects. Genuine mid-connection (OPEN) errors still reach onError.

partysocket@1.2.0

Choose a tag to compare

@github-actions github-actions released this 12 Jun 11:26

Minor Changes

  • #403 7e042eb Thanks @threepointone! - Make buffered messages and connection teardown reliable across socket replacement and explicit close (see cloudflare/agents#1738).

    • close() now dispatches its close event synchronously (mirroring how reconnect() 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. After close() returns, readyState reports CLOSED immediately, even while the underlying socket finishes its closing handshake. Code that attached a close listener after calling close() and relied on the event arriving asynchronously must attach the listener first.
    • send() now returns a boolean: true if the message was transmitted immediately over an open connection, false if it was buffered (delivered when the connection next opens, always before the open event is dispatched) or dropped because maxEnqueuedMessages was 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 by send() 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. only query — 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 new transferEnqueuedMessages option overrides this: true always transfers, false never does.
    • Warn when send() is called after close(). Messages sent against a permanently closed socket are buffered into a queue that nothing will ever flush (unless reconnect() 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 on reconnect().

partysocket@1.1.19

Choose a tag to compare

@github-actions github-actions released this 11 May 21:48

Patch Changes

partyserver@0.5.6

Choose a tag to compare

@github-actions github-actions released this 11 May 21:48

Patch Changes

  • #399 f772382 Thanks @threepointone! - Retry transient Durable Object routing errors from routePartykitRequest and getServerByName, and expose route-level retry configuration.

partyserver@0.5.5

Choose a tag to compare

@github-actions github-actions released this 28 Apr 18:45

Patch Changes

  • #394 9a927a3 Thanks @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 earlier 0.5.4 change ([#393](https://github.com/cloudflare/partykit/issues/393)) normalized these to 1000 and 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 with Network connection lost, escaping closeQuietly's synchronous try/catch and 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 calls ws.close() with no code on compat dates < 2026-04-07 (no auto-reply) will now observe a non-clean close instead of a clean 1000 close, because the framework no longer fabricates a reciprocation. Clients that pass an explicit close code are unaffected.

partyserver@0.5.4

Choose a tag to compare

@github-actions github-actions released this 27 Apr 23:58

Patch Changes

  • #391 6273c96 Thanks @threepointone! - Persist a __ps_name fallback for name-based Durable Objects during initialization. This lets alarm handlers recover this.name even when firing on a stale on-disk alarm record that was scheduled by an older workerd version that didn't yet persist name into the alarm record. See #390.

  • #393 5335251 Thanks @threepointone! - Complete the WebSocket close handshake when a client initiates the close. Previously, both the hibernating webSocketClose handler and the non-hibernating close-event listener forwarded to user onClose but never sent a reciprocal Close frame, leaving clients stuck in CLOSING until they timed out and reported 1006 (abnormal closure). The framework now reciprocates the peer's Close frame in a finally block on both paths — required by the Hibernation API on every compat date, and required by the standard accept() API on compat dates before 2026-04-07 (where the runtime's web_socket_auto_reply_to_close flag isn't yet active). Calling close() on an already-closed socket is a silent no-op, so user code that already calls connection.close(...) from onClose is unaffected. Reserved close codes (1005, 1006, 1015) are normalized to 1000 before reciprocation so they don't throw InvalidAccessError. See #389.

partyserver@0.5.3

Choose a tag to compare

@github-actions github-actions released this 26 Apr 00:28

Patch Changes

  • #386 8a3bc02 Thanks @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 explicit id in FacetStartupOptions inherit the parent DO's ctx.id — including ctx.id.name. PartyServer's name getter reads ctx.id.name straight through, so on an implicit-id facet this.name returns 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) to ctx.facets.get(...). The facet then gets its own native ctx.id.name === facetName and PartyServer's name getter does the right thing automatically. No setName() is required, no __ps_name storage record is written, and cold-wake recovery happens for free because the factory re-runs and idFromName is 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 id values are not a substitute for idFromName(facetName) (workerd treats string ids as idFromString-like, so the resulting facet has no ctx.id.name).
    • setName() docstring updated to clarify that facets are NOT a setName() use case — point to the explicit-id pattern instead. The original setName() ctx.id.name mismatch throw is preserved as a typo guard for the idFromName happy path.
    • End-to-end facet test coverage against the real workerd ctx.facets.get(...) API. A FacetParent / FacetChild fixture exercises both the implicit-id path (pinning the runtime contract that this.name returns 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-string id is 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 (the name getter, setName(), the legacy __ps_name hydrate inside #ensureInitialized()) is unchanged from 0.5.2.

y-partyserver@2.2.0

Choose a tag to compare

Minor Changes

  • #378 f3ab44f Thanks @threepointone! - Use native ctx.id.name to populate this.name.

    Durable Objects now expose ctx.id.name on every entry point (constructor, fetch, alarm, hibernating websocket handlers) when the DO is addressed via idFromName()/getByName(). PartyServer now uses this as the primary source of this.name, which simplifies routing, eliminates storage writes, and makes this.name available inside the constructor.

    Changes in partyserver:

    • this.name resolves from this.ctx.id.name. The apologetic workerd#2240 error message is gone.
    • this.name is now available inside the constructor and from class field initializers, not just after setName()/fetch() has run.
    • routePartykitRequest no longer issues a setName()/_initAndFetch() RPC before fetch(). The WebSocket path goes from 2 RPCs to 1; the HTTP path remains 1 RPC. Props, when supplied, are delivered to the DO via the x-partykit-props request header, set after onBeforeConnect/onBeforeRequest hooks run.
    • getServerByName continues to perform a single RPC to ensure onStart() 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 from ctx.id.name).
    • Server no longer writes the __ps_name record to storage. Existing records remain on disk for backward compatibility and are only read inside alarm() as a fallback for alarms that were scheduled before 2026-03-15 (where ctx.id.name is 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 if name does not match ctx.id.name.
    • The x-partykit-room header is still accepted as a fallback when ctx.id.name is 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 expose ctx.id.name, or direct stub.fetch() without routePartykitRequest/getServerByName).
    • When reading this.name throws, it is because ctx.id.name is undefined and no legacy fallback has populated the name: the DO was addressed via idFromString() or newUniqueId() (both unsupported), the runtime is too old to expose ctx.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-types peer dependency bumped from ^4.20240729.0 to ^4.20260424.1. The old range predates ctx.id.name in the type surface.

    Not supported: addressing PartyServer DOs via idFromString() or newUniqueId(). These paths return ctx.id.name === undefined inside the DO and will surface as a clear error from this.name. PartyServer has always assumed name-based addressing via getServerByName / routePartykitRequest; this release makes that assumption explicit.