Releases: cloudflare/partykit
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.
partywhen@0.1.5
Patch Changes
- Updated dependencies [
f3ab44f]:- partyserver@0.5.0
partysync@2.1.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.
partysub@2.1.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.
partyserver@0.5.2
Patch Changes
-
#383
fe030f6Thanks @threepointone! -setName()is now the sanctioned bootstrap API for non-idFromNameDOs.When
ctx.id.nameis undefined (e.g. Cloudflare Agents facets, which are spawned viactx.facets.get(...)rather thanidFromName()),setName(name)now persists the name to the legacy__ps_namestorage key in addition to stashing it in memory. This means cold-wake invocations of the DO recover the name through#ensureInitialized()'s legacy storage fallback without the framework having to reach into PartyServer's private storage layout.Frameworks that previously did:
await this.ctx.storage.put("__ps_name", name); await this.__unsafe_ensureInitialized();
can now do:
await this.setName(name);
Backward compatible:
- For DOs addressed via
idFromName()/getByName()(the happy path),setName()continues to NOT write storage —ctx.id.nameis the source of truth andsetName()is just a no-op-plus-onStart. - The pre-existing direct-storage-write pattern keeps working — the storage write becomes idempotent with what
setName()would do.
setName()'s@deprecateddocblock has been clarified: it remains the supported API for framework-level bootstrap of non-idFromNameDOs and for delivering initialpropstoonStart(). The deprecation only applies to redundant calls on DOs that were addressed viaidFromName(). - For DOs addressed via
partyserver@0.5.1
Patch Changes
-
#381
0274658Thanks @threepointone! - Fix: restore legacy__ps_namestorage fallback for framework bootstrap patterns.0.5.0 moved the legacy storage hydrate into
alarm()only, breaking Cloudflare Agents facets and any other framework that writes__ps_namedirectly before calling__unsafe_ensureInitialized(). Facet DOs are spawned viactx.facets.get(...)rather thanidFromName()and therefore havectx.id.name === undefined; they relied on PartyServer reading the storage record back to populatethis.namebeforeonStart().Changes:
- Move the legacy
__ps_namehydrate fromalarm()into#ensureInitialized(), still gated on!ctx.id.name && !#_nameso it costs nothing on the happy path (normalidFromName()/getByName()DOs skip the storage read entirely). Server.fetch()now delegates to#ensureInitialized()for the hydrate instead of doing its own. Thex-partykit-roomheader fallback remains as a last resort when neitherctx.id.namenor a legacy storage record is available.Server.alarm()is simplified — it no longer needs its own hydrate call since#ensureInitialized()handles it.setName()'s@deprecateddocblock is softened to clarify that it remains appropriate for framework-level bootstrap of non-idFromNameDOs (e.g. Agents facets), not just a deprecated compatibility shim.
- Move the legacy
partyserver@0.5.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.