diff --git a/.changeset/facet-name-resolution.md b/.changeset/facet-name-resolution.md deleted file mode 100644 index 7f1894b1..00000000 --- a/.changeset/facet-name-resolution.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"partyserver": patch ---- - -Document and test the supported pattern for using PartyServer with [Durable Object Facets](https://developers.cloudflare.com/dynamic-workers/usage/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. diff --git a/fixtures/chat/package.json b/fixtures/chat/package.json index 4110fada..68c215cf 100644 --- a/fixtures/chat/package.json +++ b/fixtures/chat/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "nanoid": "^5.1.9", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5" diff --git a/fixtures/globe/package.json b/fixtures/globe/package.json index 6511a648..3c84cf78 100644 --- a/fixtures/globe/package.json +++ b/fixtures/globe/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "cobe": "^2.0.1", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5" diff --git a/fixtures/hono/package.json b/fixtures/hono/package.json index b7673443..013d8f35 100644 --- a/fixtures/hono/package.json +++ b/fixtures/hono/package.json @@ -10,7 +10,7 @@ "dependencies": { "hono": "^4.12.15", "hono-party": "^2.1.0", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5" diff --git a/fixtures/tiptap-yjs/package.json b/fixtures/tiptap-yjs/package.json index 49498b9e..6016f749 100644 --- a/fixtures/tiptap-yjs/package.json +++ b/fixtures/tiptap-yjs/package.json @@ -11,7 +11,7 @@ "@tiptap/extension-collaboration": "^3.22.4", "@tiptap/react": "^3.22.4", "@tiptap/starter-kit": "^3.22.4", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "react": "^19.2.5", "react-dom": "^19.2.5", "tailwindcss": "^4.2.4", diff --git a/fixtures/tldraw/package.json b/fixtures/tldraw/package.json index b5f1669a..0b0ff5a2 100644 --- a/fixtures/tldraw/package.json +++ b/fixtures/tldraw/package.json @@ -7,7 +7,7 @@ "start": "vite dev" }, "dependencies": { - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5", diff --git a/fixtures/todo-sync/package.json b/fixtures/todo-sync/package.json index e1f78c84..a5b750ec 100644 --- a/fixtures/todo-sync/package.json +++ b/fixtures/todo-sync/package.json @@ -10,7 +10,7 @@ "dependencies": { "nanoid": "^5.1.9", "partyfn": "^0.1.0", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "partysync": "^2.1.0", "react": "^19.2.5", diff --git a/package-lock.json b/package-lock.json index 971181de..5fe2d6ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "version": "0.0.11", "dependencies": { "nanoid": "^5.1.9", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5" @@ -76,7 +76,7 @@ "version": "0.0.11", "dependencies": { "cobe": "^2.0.1", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5" @@ -93,7 +93,7 @@ "dependencies": { "hono": "^4.12.15", "hono-party": "^2.1.0", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5" @@ -190,7 +190,7 @@ "@tiptap/extension-collaboration": "^3.22.4", "@tiptap/react": "^3.22.4", "@tiptap/starter-kit": "^3.22.4", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "react": "^19.2.5", "react-dom": "^19.2.5", "tailwindcss": "^4.2.4", @@ -205,7 +205,7 @@ "name": "@partyserver/fixture-tldraw", "version": "0.0.11", "dependencies": { - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "react": "^19.2.5", "react-dom": "^19.2.5", @@ -222,7 +222,7 @@ "dependencies": { "nanoid": "^5.1.9", "partyfn": "^0.1.0", - "partyserver": "^0.5.2", + "partyserver": "^0.5.3", "partysocket": "^1.1.18", "partysync": "^2.1.0", "react": "^19.2.5", @@ -11385,7 +11385,7 @@ "license": "ISC" }, "packages/partyserver": { - "version": "0.5.2", + "version": "0.5.3", "license": "ISC", "dependencies": { "nanoid": "^5.1.9" diff --git a/packages/partyserver/CHANGELOG.md b/packages/partyserver/CHANGELOG.md index ea0aced5..eb5d2410 100644 --- a/packages/partyserver/CHANGELOG.md +++ b/packages/partyserver/CHANGELOG.md @@ -1,5 +1,23 @@ # partyflare +## 0.5.3 + +### Patch Changes + +- [#386](https://github.com/cloudflare/partykit/pull/386) [`8a3bc02`](https://github.com/cloudflare/partykit/commit/8a3bc02d805ab802dc80d4a2e14f2ee0d6ccda1a) Thanks [@threepointone](https://github.com/threepointone)! - Document and test the supported pattern for using PartyServer with [Durable Object Facets](https://developers.cloudflare.com/dynamic-workers/usage/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. + ## 0.5.2 ### Patch Changes @@ -22,6 +40,7 @@ ``` Backward compatible: + - For DOs addressed via `idFromName()` / `getByName()` (the happy path), `setName()` continues to NOT write storage — `ctx.id.name` is the source of truth and `setName()` 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. @@ -36,6 +55,7 @@ 0.5.0 moved the legacy storage hydrate into `alarm()` only, breaking Cloudflare Agents facets and any other framework that writes `__ps_name` directly before calling `__unsafe_ensureInitialized()`. Facet DOs are spawned via `ctx.facets.get(...)` rather than `idFromName()` and therefore have `ctx.id.name === undefined`; they relied on PartyServer reading the storage record back to populate `this.name` before `onStart()`. Changes: + - Move the legacy `__ps_name` hydrate from `alarm()` into `#ensureInitialized()`, still gated on `!ctx.id.name && !#_name` so it costs nothing on the happy path (normal `idFromName()`/`getByName()` DOs skip the storage read entirely). - `Server.fetch()` now delegates to `#ensureInitialized()` for the hydrate instead of doing its own. The `x-partykit-room` header fallback remains as a last resort when neither `ctx.id.name` nor a legacy storage record is available. - `Server.alarm()` is simplified — it no longer needs its own hydrate call since `#ensureInitialized()` handles it. @@ -50,6 +70,7 @@ 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. @@ -61,6 +82,7 @@ - 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. @@ -381,12 +403,14 @@ ### Patch Changes - [`528adea`](https://github.com/threepointone/partyserver/commit/528adeaced6dce6e888d2f54cc75c3569bf2c277) Thanks [@threepointone](https://github.com/threepointone)! - some fixes and tweaks + - getServerByName was throwing on all requests - `Env` is now an optional arg when defining `Server` - `y-partyserver/provider` can now take an optional `prefix` arg to use a custom url to connect - `routePartyKitRequest`/`getServerByName` now accepts `jurisdiction` bonus: + - added a bunch of fixtures - added stubs for docs diff --git a/packages/partyserver/package.json b/packages/partyserver/package.json index a32f45b7..8a2a7ee0 100644 --- a/packages/partyserver/package.json +++ b/packages/partyserver/package.json @@ -1,6 +1,6 @@ { "name": "partyserver", - "version": "0.5.2", + "version": "0.5.3", "repository": { "type": "git", "url": "git://github.com/cloudflare/partykit.git"