Skip to content

setName() as the sanctioned bootstrap API for non-idFromName DOs#383

Merged
threepointone merged 3 commits into
mainfrom
setname-bootstrap
Apr 24, 2026
Merged

setName() as the sanctioned bootstrap API for non-idFromName DOs#383
threepointone merged 3 commits into
mainfrom
setname-bootstrap

Conversation

@threepointone
Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #381 (the 0.5.1 facet hydrate fix). Where #381 made the runtime path tolerate frameworks bootstrapping via direct __ps_name storage writes, this PR makes the API path clean: setName() itself now persists the name, so frameworks no longer need to reach into PartyServer's private storage layout.

When ctx.id.name is undefined (e.g. Cloudflare Agents facets, spawned via ctx.facets.get(...) rather than idFromName()), setName(name) now:

  1. Stashes the name in #_name (existing behavior)
  2. Persists it to __ps_name storage (new), so cold-wake invocations recover the name through #ensureInitialized()'s legacy storage fallback

When ctx.id.name is defined (the happy path for idFromName/getByName DOs), setName() continues to NOT write storage — ctx.id.name is the source of truth.

Migration for framework authors

Cloudflare Agents currently does (in _cf_initAsFacet):

await Promise.all([
  this.ctx.storage.put("cf_agents_is_facet", true),
  this.ctx.storage.put("__ps_name", name),
  this.ctx.storage.put("cf_agents_parent_path", parentPath)
]);
await this.__unsafe_ensureInitialized();

Once Agents bumps to this release, they can simplify to:

await Promise.all([
  this.ctx.storage.put("cf_agents_is_facet", true),
  this.ctx.storage.put("cf_agents_parent_path", parentPath)
]);
await this.setName(name);   // writes __ps_name + sets #_name + runs onStart

Migration is optional. The existing direct-storage-write pattern keeps working — the storage write just becomes idempotent with what setName() would do.

Test plan

  • New SetNameBootstrapServer + 3 tests:
    • setName() on a newUniqueId() DO persists __ps_name to storage (verified by reading storage back)
    • Cold-wake fetch recovers the name via the storage fallback
    • setName() does NOT write storage when ctx.id.name is set (regression guard for the 0.5.0 zero-storage-write win)
  • All 57 partyserver tests pass
  • Full repo test suite passes (partyserver, partysub, partywhen, partytracks, y-partyserver — 402 tests total)
  • Types, lint, format clean
  • Follow-up: open an issue / PR in cloudflare/agents tracking the optional setName() migration

Long-term context

Workerd-team work (Option F in the discussion thread) will eventually expose ctx.id.name for facets natively, at which point setName() becomes redundant for facets too. Until then, setName() is the supported bootstrap primitive.

Made with Cursor

For DOs where `ctx.id.name` is undefined (Cloudflare Agents facets and
similar framework-level bootstrap patterns), `setName(name)` now writes
the name to the legacy `__ps_name` storage key in addition to stashing
it in memory. This makes `setName()` the sanctioned bootstrap API:
frameworks no longer need to write `__ps_name` directly themselves —
PartyServer manages its own storage layout.

For DOs addressed via idFromName/getByName, behavior is unchanged:
ctx.id.name carries the name, no storage write happens.

Migration for framework authors who currently do:

    await this.ctx.storage.put("__ps_name", name);
    await this.__unsafe_ensureInitialized();

becomes:

    await this.setName(name);

Backward compatible — the existing direct-storage pattern keeps working
since the storage write becomes idempotent.

Adds SetNameBootstrapServer + 3 tests covering:
1. setName() persists __ps_name for newUniqueId DOs
2. Cold-wake fetch recovers the name via the storage fallback
3. setName() does NOT write storage when ctx.id.name is set (regression
   guard for the 0.5.0 zero-storage-write win on the happy path)

Made-with: Cursor
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

🦋 Changeset detected

Latest commit: 4db230a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
partyserver Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 24, 2026

Open in StackBlitz

hono-party

npm i https://pkg.pr.new/cloudflare/partykit/hono-party@383

partyfn

npm i https://pkg.pr.new/cloudflare/partykit/partyfn@383

partyserver

npm i https://pkg.pr.new/cloudflare/partykit/partyserver@383

partysocket

npm i https://pkg.pr.new/cloudflare/partykit/partysocket@383

partysub

npm i https://pkg.pr.new/cloudflare/partykit/partysub@383

partysync

npm i https://pkg.pr.new/cloudflare/partykit/partysync@383

partytracks

npm i https://pkg.pr.new/cloudflare/partykit/partytracks@383

partywhen

npm i https://pkg.pr.new/cloudflare/partykit/partywhen@383

y-partyserver

npm i https://pkg.pr.new/cloudflare/partykit/y-partyserver@383

commit: 4db230a

If `ctx.storage.put(NAME_STORAGE_KEY, name)` throws, the previous
ordering (set #_name first, then write) left an inconsistent in-memory
state: `this.name` returned the new name but storage was empty. A retry
of `setName(name)` would silently no-op the storage write (because the
already-set #_name caused the bootstrap branch to be skipped), leaving
the DO inconsistent until eviction.

Reorder: write storage first, then assign #_name. If storage throws,
Made-with: Cursor
#_name stays undefined and the retry re-attempts the storage write.
@threepointone threepointone merged commit d86c61c into main Apr 24, 2026
6 checks passed
@threepointone threepointone deleted the setname-bootstrap branch April 24, 2026 22:19
@github-actions github-actions Bot mentioned this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant