Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/persist-room-name-alarm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"partyserver": patch
---

`this.name` now works in `onAlarm()` for servers that have been fetched at least once.
33 changes: 25 additions & 8 deletions packages/partyserver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,11 +403,19 @@ export class Server<
Did you try connecting directly to this Durable Object? Try using getServerByName(namespace, id) instead.`);
}
await this.setName(room);
} else if (this.#status !== "started") {
// Name was set by a previous request but initialization failed.
// Retry initialization so the server can recover from transient
// onStart failures.
await this.#initialize();
} else {
const room = request.headers.get("x-partykit-room");
if (room && room !== this.#_name) {
throw new Error(
`Room name mismatch: this server is "${this.#_name}" but request has room "${room}"`
);
}
if (this.#status !== "started") {
// Name was set by a previous request but initialization failed.
// Retry initialization so the server can recover from transient
// onStart failures.
await this.#initialize();
}
}
const url = new URL(request.url);

Expand Down Expand Up @@ -559,6 +567,11 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam
async #initialize(): Promise<void> {
let error: unknown;
await this.ctx.blockConcurrencyWhile(async () => {
if (!this.#_name) {
const stored =
await this.ctx.storage.get<string>("__partyserver_name");
if (stored) this.#_name = stored;
}
this.#status = "starting";
try {
await this.onStart(this.#_props);
Expand Down Expand Up @@ -627,9 +640,6 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam
return this.#_name;
}

// We won't have an await inside this function
// but it will be called remotely,
// so we need to mark it as async
async setName(name: string) {
if (!name) {
throw new Error("A name is required.");
Expand All @@ -640,6 +650,7 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam
);
}
this.#_name = name;
await this.ctx.storage.put("__partyserver_name", name);

if (this.#status !== "started") {
await this.#initialize();
Expand Down Expand Up @@ -798,6 +809,12 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam
// so we need to hydrate it again
await this.#initialize();
}
if (!this.#_name) {
console.warn(
`${this.#ParentClass.name} alarm fired but this.name is not available. ` +
`The server must be fetched at least once (via routePartykitRequest or getServerByName) before this.name can be used in onAlarm.`
);
}
await this.onAlarm();
}
}
39 changes: 38 additions & 1 deletion packages/partyserver/src/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
createExecutionContext,
env,
runDurableObjectAlarm
runDurableObjectAlarm,
runInDurableObject
// waitOnExecutionContext
} from "cloudflare:test";
import { describe, expect, it } from "vitest";
Expand Down Expand Up @@ -532,6 +533,42 @@ describe("Alarm (initialize without redundant blockConcurrencyWhile)", () => {
});
});

describe("Alarm cold start (name persisted to storage)", () => {
it("setName persists the room name to storage", async () => {
const id = env.AlarmNameServer.idFromName("persist-write-test");
const stub = env.AlarmNameServer.get(id);

await stub.fetch(
new Request("http://example.com/", {
headers: { "x-partykit-room": "persist-write-test" }
})
);

const stored = await runInDurableObject(stub, async (_instance, state) => {
return state.storage.get("__partyserver_name");
});
expect(stored).toEqual("persist-write-test");
});

it("this.name is available in onAlarm after a cold start", async () => {
const id = env.AlarmNameServer.idFromName("alarm-name-test");
const stub = env.AlarmNameServer.get(id);

// Pre-seed storage directly — no fetch, so #_name is never set in memory.
// This simulates a DO that was previously initialized (name persisted)
// but has since been evicted and is now cold-starting via alarm.
await runInDurableObject(stub, async (_instance, state) => {
await state.storage.put("__partyserver_name", "alarm-name-test");
await state.storage.setAlarm(Date.now() + 60_000);
});

await runDurableObjectAlarm(stub);

const alarmName = await runInDurableObject(stub, (i) => i.alarmName);
expect(alarmName).toEqual("alarm-name-test");
});
});

describe("CORS", () => {
it("returns CORS headers on OPTIONS preflight for matched routes", async () => {
const ctx = createExecutionContext();
Expand Down
17 changes: 17 additions & 0 deletions packages/partyserver/src/tests/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type Env = {
OnStartServer: DurableObjectNamespace<OnStartServer>;
HibernatingOnStartServer: DurableObjectNamespace<HibernatingOnStartServer>;
AlarmServer: DurableObjectNamespace<AlarmServer>;
AlarmNameServer: DurableObjectNamespace<AlarmNameServer>;
Mixed: DurableObjectNamespace<Mixed>;
ConfigurableState: DurableObjectNamespace<ConfigurableState>;
ConfigurableStateInMemory: DurableObjectNamespace<ConfigurableStateInMemory>;
Expand Down Expand Up @@ -352,6 +353,22 @@ export class TagsServerInMemory extends Server {
}
}

/**
* Tests that this.name is available in onAlarm after an alarm-triggered
* cold start (no prior fetch).
*/
export class AlarmNameServer extends Server {
static options = {
hibernate: true
};

alarmName: string | null = null;

onAlarm() {
this.alarmName = this.name;
}
}

export class CorsServer extends Server {
onRequest(): Response | Promise<Response> {
return Response.json({ cors: true });
Expand Down
5 changes: 5 additions & 0 deletions packages/partyserver/src/tests/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
"name": "FailingOnStartServer",
"class_name": "FailingOnStartServer"
},
{
"name": "AlarmNameServer",
"class_name": "AlarmNameServer"
},
{
"name": "HibernatingNameInMessage",
"class_name": "HibernatingNameInMessage"
Expand Down Expand Up @@ -84,6 +88,7 @@
"HibernatingOnStartServer",
"AlarmServer",
"FailingOnStartServer",
"AlarmNameServer",
"HibernatingNameInMessage",
"TagsServer",
"TagsServerInMemory"
Expand Down