Skip to content
Merged
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/fix-lobby-party-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"partyserver": patch
---

Add `lobby.className` to `onBeforeConnect`/`onBeforeRequest` callbacks, providing the Durable Object class name (e.g. `"MyAgent"`). The existing `lobby.party` field is now deprecated (it returns the kebab-case URL namespace) and will be changed to return the class name in the next major version.
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/hono-party/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@
"devDependencies": {
"@cloudflare/workers-types": "^4.20251218.0",
"hono": "^4.11.1",
"partyserver": "^0.1.4"
"partyserver": "^0.1.5"
}
}
87 changes: 55 additions & 32 deletions packages/partyserver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const serverMapCache = new WeakMap<
Record<string, DurableObjectNamespace>
>();

// Maps kebab-case namespace -> original env binding name (e.g. "my-agent" -> "MyAgent")
const bindingNameCache = new WeakMap<object, Record<string, string>>();

/**
* For a given server namespace, create a server with a name.
*/
Expand Down Expand Up @@ -87,8 +90,20 @@ function camelCaseToKebabCase(str: string): string {
// Convert any remaining underscores to hyphens and remove trailing -'s
return kebabified.replace(/_/g, "-").replace(/-$/, "");
}
export interface Lobby<Env = Cloudflare.Env> {
/**
* The kebab-case namespace from the URL path (e.g. `"my-agent"`).
* @deprecated Use `className` instead, which returns the Durable Object class name.
* In the next major version, `party` will return the class name instead of the kebab-case namespace.
*/
party: string;
/** The Durable Object class name / env binding name (e.g. `"MyAgent"`). */
className: Extract<keyof Env, string>;
/** The room / instance name extracted from the URL. */
name: string;
}

export interface PartyServerOptions<
// biome-ignore lint/correctness/noUnusedVariables: it's ok, we'll remove this in the next major
Env = Cloudflare.Env,
Props = Record<string, unknown>
> {
Expand Down Expand Up @@ -122,17 +137,11 @@ export interface PartyServerOptions<
cors?: boolean | HeadersInit;
onBeforeConnect?: (
req: Request,
lobby: {
party: string;
name: string;
}
lobby: Lobby<Env>
) => Response | Request | void | Promise<Response | Request | void>;
onBeforeRequest?: (
req: Request,
lobby: {
party: string;
name: string;
}
lobby: Lobby<Env>
) =>
| Response
| Request
Expand Down Expand Up @@ -175,26 +184,28 @@ export async function routePartykitRequest<
options?: PartyServerOptions<Env, Props>
): Promise<Response | null> {
if (!serverMapCache.has(env)) {
serverMapCache.set(
env,
Object.entries(env).reduce((acc, [k, v]) => {
if (
v &&
typeof v === "object" &&
"idFromName" in v &&
typeof v.idFromName === "function"
) {
Object.assign(acc, { [camelCaseToKebabCase(k)]: v });
return acc;
}
return acc;
}, {})
);
const namespaceMap: Record<string, DurableObjectNamespace> = {};
const bindingNames: Record<string, string> = {};
for (const [k, v] of Object.entries(env)) {
if (
v &&
typeof v === "object" &&
"idFromName" in v &&
typeof v.idFromName === "function"
) {
const kebab = camelCaseToKebabCase(k);
namespaceMap[kebab] = v as DurableObjectNamespace;
bindingNames[kebab] = k;
}
}
serverMapCache.set(env, namespaceMap);
bindingNameCache.set(env, bindingNames);
}
const map = serverMapCache.get(env) as unknown as Record<
string,
DurableObjectNamespace<T>
>;
const bindingNames = bindingNameCache.get(env) as Record<string, string>;

const prefix = options?.prefix || "parties";
const prefixParts = prefix.split("/");
Expand Down Expand Up @@ -271,12 +282,27 @@ Did you forget to add a durable object binding to the class ${namespace[0].toUpp
req.headers.set("x-partykit-props", JSON.stringify(options?.props));
}

const className = bindingNames[namespace] as Extract<keyof Env, string>;
let partyDeprecationWarned = false;
const lobby: Lobby<Env> = {
get party() {
if (!partyDeprecationWarned) {
partyDeprecationWarned = true;
console.warn(
'lobby.party is deprecated and currently returns the kebab-case namespace (e.g. "my-agent"). ' +
'Use lobby.className instead to get the Durable Object class name (e.g. "MyAgent"). ' +
"In the next major version, lobby.party will return the class name."
);
}
return namespace;
},
className,
name
};

if (isWebSocket) {
if (options?.onBeforeConnect) {
const reqOrRes = await options.onBeforeConnect(req, {
party: namespace,
name
});
const reqOrRes = await options.onBeforeConnect(req, lobby);
if (reqOrRes instanceof Request) {
req = reqOrRes;
} else if (reqOrRes instanceof Response) {
Expand All @@ -285,10 +311,7 @@ Did you forget to add a durable object binding to the class ${namespace[0].toUpp
}
} else {
if (options?.onBeforeRequest) {
const reqOrRes = await options.onBeforeRequest(req, {
party: namespace,
name
});
const reqOrRes = await options.onBeforeRequest(req, lobby);
if (reqOrRes instanceof Request) {
req = reqOrRes;
} else if (reqOrRes instanceof Response) {
Expand Down
13 changes: 13 additions & 0 deletions packages/partyserver/src/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ describe("Server", () => {
expect(response.headers.get("Location")).toBe("https://example3.com");
});

it("provides className with the Durable Object class name", async () => {
const ctx = createExecutionContext();
const request = new Request(
"http://example.com/parties/on-start-server/lobby-info"
);
const response = await worker.fetch(request, env, ctx);
expect(response.status).toBe(200);
expect(await response.json()).toEqual({
className: "OnStartServer",
name: "lobby-info"
});
});

it("ignores foreign hibernated websockets when broadcasting", async () => {
const ctx = createExecutionContext();

Expand Down
18 changes: 12 additions & 6 deletions packages/partyserver/src/tests/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ export default {

return (
(await routePartykitRequest(request, env, {
onBeforeConnect: async (_request, { party, name }) => {
if (party === "on-start-server") {
onBeforeConnect: async (_request, { className, name }) => {
if (className === "OnStartServer") {
if (name === "is-error") {
return new Response("Error", { status: 503 });
} else if (name === "is-redirect") {
Expand All @@ -365,17 +365,23 @@ export default {
}
}
},
onBeforeRequest: async (_request, { party, name }) => {
if (party === "on-start-server") {
if (name === "is-error") {
onBeforeRequest: async (_request, lobby) => {
if (lobby.className === "OnStartServer") {
if (lobby.name === "is-error") {
return new Response("Error", { status: 504 });
} else if (name === "is-redirect") {
} else if (lobby.name === "is-redirect") {
return new Response("Redirect", {
status: 302,
headers: { Location: "https://example3.com" }
});
}
}
if (lobby.name === "lobby-info") {
return Response.json({
className: lobby.className,
name: lobby.name
});
}
}
})) || new Response("Not Found", { status: 404 })
);
Expand Down
2 changes: 1 addition & 1 deletion packages/partysub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20251218.0",
"partyserver": "^0.1.4",
"partyserver": "^0.1.5",
"partysocket": "^1.1.13"
}
}
2 changes: 1 addition & 1 deletion packages/partysync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20251218.0",
"partyserver": "^0.1.4"
"partyserver": "^0.1.5"
}
}
2 changes: 1 addition & 1 deletion packages/partywhen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@
"description": "A library for scheduling and running tasks in Cloudflare Workers",
"dependencies": {
"cron-parser": "^5.4.0",
"partyserver": "^0.1.4"
"partyserver": "^0.1.5"
}
}
2 changes: 1 addition & 1 deletion packages/y-partyserver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"devDependencies": {
"@cloudflare/workers-types": "^4.20251218.0",
"@types/lodash.debounce": "^4.0.9",
"partyserver": "^0.1.4",
"partyserver": "^0.1.5",
"ws": "^8.18.3",
"yjs": "^13.6.28"
}
Expand Down
Loading