@signe/room 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signe/room",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "keywords": [],
@@ -17,7 +17,7 @@
17
17
  "dset": "^3.1.3",
18
18
  "partysocket": "^1.0.1",
19
19
  "zod": "^3.23.8",
20
- "@signe/sync": "2.0.0"
20
+ "@signe/sync": "2.0.1"
21
21
  },
22
22
  "publishConfig": {
23
23
  "access": "public"
package/readme.md CHANGED
@@ -75,7 +75,7 @@ The `@Request` decorator allows you to handle HTTP requests with specific routes
75
75
 
76
76
  ```ts
77
77
  import { z } from "zod";
78
- import { Room, Request, RequestGuard } from "@signe/room";
78
+ import { Room, Request, RequestGuard, ServerResponse } from "@signe/room";
79
79
 
80
80
  @Room({
81
81
  path: "api"
@@ -97,10 +97,10 @@ class ApiRoom {
97
97
 
98
98
  // Handle requests with path parameters
99
99
  @Request({ path: "/players/:id" })
100
- getPlayer(req: Party.Request, body: any, params: { id: string }) {
101
- const player = this.players()[params.id];
100
+ getPlayer(req: Party.Request, res: ServerResponse) {
101
+ const player = this.players()[req.params.id];
102
102
  if (!player) {
103
- return new Response(JSON.stringify({ error: "Player not found" }), { status: 404 });
103
+ return res.notFound("Player not found");
104
104
  }
105
105
  return player;
106
106
  }
@@ -113,10 +113,10 @@ class ApiRoom {
113
113
  score: z.number().min(0)
114
114
  })
115
115
  )
116
- @RequestGuard([isAuthenticated])
117
- submitScore(req: Party.Request, body: { playerId: string; score: number }) {
118
- this.scores.update(scores => [...scores, body]);
119
- return { success: true };
116
+ @Guard([isAuthenticated])
117
+ submitScore(req: Party.Request, res: ServerResponse) {
118
+ this.scores.update(scores => [...scores, req.data]);
119
+ return res.success({ success: true });
120
120
  }
121
121
  }
122
122
  ```
@@ -177,7 +177,7 @@ class AdminRoom {
177
177
  }
178
178
 
179
179
  @Request({ path: "/admin/users", method: "DELETE" })
180
- @RequestGuard([isAdmin]) // Applied only to this request handler
180
+ @Guard([isAdmin]) // Applied only to this request handler
181
181
  async deleteUserViaHttp(req: Party.Request) {
182
182
  // Only authenticated admins can access this endpoint
183
183
  }
@@ -281,7 +281,15 @@ export default class MainServer extends Server {
281
281
  }
282
282
  ```
283
283
 
284
- 2. Configure your `partykit.json` file:
284
+ 2. Add `Shard` to your server in `party/shard.ts`:
285
+
286
+ ```ts
287
+ import { Shard } from '@signe/room';
288
+
289
+ export default class ShardServer extends Shard {}
290
+ ```
291
+
292
+ 3. Configure your `partykit.json` file:
285
293
 
286
294
  ```json
287
295
  {
package/src/index.ts CHANGED
@@ -4,4 +4,5 @@ export { Server } from './server';
4
4
  export * from './testing';
5
5
  export * from './shard';
6
6
  export * from './world';
7
- export * from './interfaces';
7
+ export * from './interfaces';
8
+ export * from './request/response';
@@ -0,0 +1,54 @@
1
+ export function cors(res: Response, options: CorsOptions = {}) {
2
+ const newHeaders = new Headers(res.headers);
3
+
4
+ // Set default CORS headers
5
+ newHeaders.set('Access-Control-Allow-Origin', options.origin || '*');
6
+
7
+ if (options.credentials) {
8
+ newHeaders.set('Access-Control-Allow-Credentials', 'true');
9
+ }
10
+
11
+ if (options.exposedHeaders && options.exposedHeaders.length) {
12
+ newHeaders.set('Access-Control-Expose-Headers', options.exposedHeaders.join(', '));
13
+ }
14
+
15
+ // Handle preflight requests
16
+ if (options.methods && options.methods.length) {
17
+ newHeaders.set('Access-Control-Allow-Methods', options.methods.join(', '));
18
+ } else {
19
+ newHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
20
+ }
21
+
22
+ if (options.allowedHeaders && options.allowedHeaders.length) {
23
+ newHeaders.set('Access-Control-Allow-Headers', options.allowedHeaders.join(', '));
24
+ } else {
25
+ newHeaders.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
26
+ }
27
+
28
+ if (options.maxAge) {
29
+ newHeaders.set('Access-Control-Max-Age', options.maxAge.toString());
30
+ }
31
+
32
+ return new Response(res.body, {
33
+ status: res.status,
34
+ headers: newHeaders
35
+ });
36
+ }
37
+
38
+ export interface CorsOptions {
39
+ origin?: string;
40
+ methods?: string[];
41
+ allowedHeaders?: string[];
42
+ exposedHeaders?: string[];
43
+ credentials?: boolean;
44
+ maxAge?: number;
45
+ }
46
+
47
+ /**
48
+ * Creates a CORS interceptor with the specified options
49
+ * @param options CORS configuration options
50
+ * @returns An interceptor function that can be used with ServerResponse
51
+ */
52
+ export function createCorsInterceptor(options: CorsOptions = {}): (res: Response) => Response {
53
+ return (res: Response) => cors(res, options);
54
+ }
@@ -0,0 +1,228 @@
1
+ export class ServerResponse {
2
+ private interceptors: ((res: Response) => Promise<Response> | Response)[];
3
+ private statusCode: number = 200;
4
+ private responseBody: any = {};
5
+ private responseHeaders: Record<string, string> = {
6
+ 'Content-Type': 'application/json'
7
+ };
8
+
9
+ /**
10
+ * Creates a new ServerResponse instance
11
+ * @param interceptors Array of interceptor functions that can modify the response
12
+ */
13
+ constructor(interceptors: ((res: Response) => Promise<Response> | Response)[] = []) {
14
+ this.interceptors = interceptors;
15
+ }
16
+
17
+ /**
18
+ * Sets the status code for the response
19
+ * @param code HTTP status code
20
+ * @returns this instance for chaining
21
+ */
22
+ status(code: number): ServerResponse {
23
+ this.statusCode = code;
24
+ return this;
25
+ }
26
+
27
+ /**
28
+ * Sets the response body and returns this instance (chainable method)
29
+ *
30
+ * @param body Response body
31
+ * @returns this instance for chaining
32
+ */
33
+ body(body: any): ServerResponse {
34
+ this.responseBody = body;
35
+ return this;
36
+ }
37
+
38
+ /**
39
+ * Adds a header to the response
40
+ * @param name Header name
41
+ * @param value Header value
42
+ * @returns this instance for chaining
43
+ */
44
+ header(name: string, value: string): ServerResponse {
45
+ this.responseHeaders[name] = value;
46
+ return this;
47
+ }
48
+
49
+ /**
50
+ * Adds multiple headers to the response
51
+ * @param headers Object containing headers
52
+ * @returns this instance for chaining
53
+ */
54
+ setHeaders(headers: Record<string, string>): ServerResponse {
55
+ this.responseHeaders = { ...this.responseHeaders, ...headers };
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * Add an interceptor to the chain
61
+ * @param interceptor Function that takes a Response and returns a modified Response
62
+ * @returns this instance for chaining
63
+ */
64
+ use(interceptor: (res: Response) => Promise<Response> | Response): ServerResponse {
65
+ this.interceptors.push(interceptor);
66
+ return this;
67
+ }
68
+
69
+ /**
70
+ * Builds and returns the Response object after applying all interceptors
71
+ * @returns Promise<Response> The final Response object
72
+ * @private Internal method used by terminal methods
73
+ */
74
+ private async buildResponse(): Promise<Response> {
75
+ // Create initial response
76
+ let response = new Response(JSON.stringify(this.responseBody), {
77
+ status: this.statusCode,
78
+ headers: this.responseHeaders
79
+ });
80
+
81
+ // Apply all interceptors sequentially
82
+ for (const interceptor of this.interceptors) {
83
+ try {
84
+ const interceptedResponse = interceptor(response);
85
+ if (interceptedResponse instanceof Promise) {
86
+ response = await interceptedResponse;
87
+ } else {
88
+ response = interceptedResponse;
89
+ }
90
+ } catch (error) {
91
+ console.error('Error in interceptor:', error);
92
+ // Continue with the current response if an interceptor fails
93
+ }
94
+ }
95
+
96
+ return response;
97
+ }
98
+
99
+ /**
100
+ * Sets the response body to the JSON-stringified version of the provided value
101
+ * and sends the response (terminal method)
102
+ *
103
+ * @param body Response body to be JSON stringified
104
+ * @returns Promise<Response> The final Response object
105
+ */
106
+ async json(body: any): Promise<Response> {
107
+ this.responseBody = body;
108
+ this.responseHeaders['Content-Type'] = 'application/json';
109
+ return this.buildResponse();
110
+ }
111
+
112
+ /**
113
+ * Sends the response with the current configuration (terminal method)
114
+ *
115
+ * @param body Optional body to set before sending
116
+ * @returns Promise<Response> The final Response object
117
+ */
118
+ async send(body?: any): Promise<Response> {
119
+ if (body !== undefined) {
120
+ this.responseBody = body;
121
+ }
122
+ return this.buildResponse();
123
+ }
124
+
125
+ /**
126
+ * Sends a plain text response (terminal method)
127
+ *
128
+ * @param text Text to send
129
+ * @returns Promise<Response> The final Response object
130
+ */
131
+ async text(text: string): Promise<Response> {
132
+ this.responseBody = text;
133
+ this.responseHeaders['Content-Type'] = 'text/plain';
134
+
135
+ // Create a text response without JSON stringifying the body
136
+ let response = new Response(text, {
137
+ status: this.statusCode,
138
+ headers: this.responseHeaders
139
+ });
140
+
141
+ // Apply interceptors
142
+ for (const interceptor of this.interceptors) {
143
+ try {
144
+ const interceptedResponse = interceptor(response);
145
+ if (interceptedResponse instanceof Promise) {
146
+ response = await interceptedResponse;
147
+ } else {
148
+ response = interceptedResponse;
149
+ }
150
+ } catch (error) {
151
+ console.error('Error in interceptor:', error);
152
+ }
153
+ }
154
+
155
+ return response;
156
+ }
157
+
158
+ /**
159
+ * Redirects to the specified URL (terminal method)
160
+ *
161
+ * @param url URL to redirect to
162
+ * @param statusCode HTTP status code (default: 302)
163
+ * @returns Promise<Response> The final Response object
164
+ */
165
+ async redirect(url: string, statusCode: number = 302): Promise<Response> {
166
+ this.statusCode = statusCode;
167
+ this.responseHeaders['Location'] = url;
168
+ return this.buildResponse();
169
+ }
170
+
171
+ /**
172
+ * Creates a success response with status 200
173
+ * @param body Response body
174
+ * @returns Promise<Response> The final Response object
175
+ */
176
+ async success(body: any = {}): Promise<Response> {
177
+ return this.status(200).json(body);
178
+ }
179
+
180
+ /**
181
+ * Creates an error response with status 400
182
+ * @param message Error message
183
+ * @param details Additional error details
184
+ * @returns Promise<Response> The final Response object
185
+ */
186
+ async badRequest(message: string, details: any = {}): Promise<Response> {
187
+ return this.status(400).json({
188
+ error: message,
189
+ ...details
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Creates an error response with status 403
195
+ * @param message Error message
196
+ * @returns Promise<Response> The final Response object
197
+ */
198
+ async notPermitted(message: string = "Not permitted"): Promise<Response> {
199
+ return this.status(403).json({ error: message });
200
+ }
201
+
202
+ /**
203
+ * Creates an error response with status 401
204
+ * @param message Error message
205
+ * @returns Promise<Response> The final Response object
206
+ */
207
+ async unauthorized(message: string = "Unauthorized"): Promise<Response> {
208
+ return this.status(401).json({ error: message });
209
+ }
210
+
211
+ /**
212
+ * Creates an error response with status 404
213
+ * @param message Error message
214
+ * @returns Promise<Response> The final Response object
215
+ */
216
+ async notFound(message: string = "Not found"): Promise<Response> {
217
+ return this.status(404).json({ error: message });
218
+ }
219
+
220
+ /**
221
+ * Creates an error response with status 500
222
+ * @param message Error message
223
+ * @returns Promise<Response> The final Response object
224
+ */
225
+ async serverError(message: string = "Internal Server Error"): Promise<Response> {
226
+ return this.status(500).json({ error: message });
227
+ }
228
+ }
package/src/server.ts CHANGED
@@ -16,6 +16,8 @@ import {
16
16
  isClass,
17
17
  throttle,
18
18
  } from "./utils";
19
+ import { ServerResponse } from "./request/response";
20
+ import { createCorsInterceptor } from "./request/cors";
19
21
 
20
22
  const Message = z.object({
21
23
  action: z.string(),
@@ -843,13 +845,16 @@ export class Server implements Party.Server {
843
845
  // Check if the request is coming from a shard
844
846
  const isFromShard = req.headers.has('x-forwarded-by-shard');
845
847
  const shardId = req.headers.get('x-shard-id');
848
+ const res = new ServerResponse([
849
+ createCorsInterceptor()
850
+ ]);
846
851
 
847
852
  if (isFromShard) {
848
- return this.handleShardRequest(req, shardId);
853
+ return this.handleShardRequest(req, res, shardId);
849
854
  }
850
855
 
851
856
  // Handle regular client request
852
- return this.handleDirectRequest(req);
857
+ return this.handleDirectRequest(req, res);
853
858
  }
854
859
 
855
860
  /**
@@ -860,35 +865,27 @@ export class Server implements Party.Server {
860
865
  * @description Processes requests received directly from clients
861
866
  * @returns {Promise<Response>} The response to return to the client
862
867
  */
863
- private async handleDirectRequest(req: Party.Request): Promise<Response> {
868
+ private async handleDirectRequest(req: Party.Request, res: ServerResponse): Promise<Response> {
864
869
  const subRoom = await this.getSubRoom();
865
- const res = (body: any, status: number) => {
866
- return new Response(JSON.stringify(body), { status });
867
- };
868
-
869
870
  if (!subRoom) {
870
- return res({
871
- error: "Not found"
872
- }, 404);
871
+ return res.notFound();
873
872
  }
874
873
 
875
874
  // First try to match using the registered @Request handlers
876
- const response = await this.tryMatchRequestHandler(req, subRoom);
875
+ const response = await this.tryMatchRequestHandler(req, res, subRoom);
877
876
  if (response) {
878
877
  return response;
879
878
  }
880
879
 
881
880
  // Fall back to the legacy onRequest method if no handler matched
882
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, this.room));
881
+ const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, res));
883
882
  if (!legacyResponse) {
884
- return res({
885
- error: "Not found"
886
- }, 404);
883
+ return res.notFound();
887
884
  }
888
885
  if (legacyResponse instanceof Response) {
889
886
  return legacyResponse;
890
887
  }
891
- return res(legacyResponse, 200);
888
+ return res.success(legacyResponse);
892
889
  }
893
890
 
894
891
  /**
@@ -900,7 +897,7 @@ export class Server implements Party.Server {
900
897
  * @description Attempts to match the request to a registered @Request handler
901
898
  * @returns {Promise<Response | null>} The response or null if no handler matched
902
899
  */
903
- private async tryMatchRequestHandler(req: Party.Request, subRoom: any): Promise<Response | null> {
900
+ private async tryMatchRequestHandler(req: Party.Request, res: ServerResponse, subRoom: any): Promise<Response | null> {
904
901
  const requestHandlers = subRoom.constructor["_requestMetadata"];
905
902
  if (!requestHandlers) {
906
903
  return null;
@@ -935,7 +932,7 @@ export class Server implements Party.Server {
935
932
  return isAuthorized;
936
933
  }
937
934
  if (!isAuthorized) {
938
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 403 });
935
+ return res.notPermitted();
939
936
  }
940
937
  }
941
938
 
@@ -948,44 +945,33 @@ export class Server implements Party.Server {
948
945
  const body = await req.json();
949
946
  const validation = handler.bodyValidation.safeParse(body);
950
947
  if (!validation.success) {
951
- return new Response(
952
- JSON.stringify({ error: "Invalid request body", details: validation.error }),
953
- { status: 400 }
954
- );
948
+ return res.badRequest("Invalid request body", {
949
+ details: validation.error
950
+ });
955
951
  }
956
952
  bodyData = validation.data;
957
953
  }
958
954
  } catch (error) {
959
- return new Response(
960
- JSON.stringify({ error: "Failed to parse request body" }),
961
- { status: 400 }
962
- );
955
+ return res.badRequest("Failed to parse request body");
963
956
  }
964
957
  }
965
958
 
966
959
  // Execute the handler method
967
960
  try {
961
+ req['data'] = bodyData;
962
+ req['params'] = params;
968
963
  const result = await awaitReturn(
969
- subRoom[handler.key](req, bodyData, params, this.room)
964
+ subRoom[handler.key](req, res)
970
965
  );
971
966
 
972
967
  if (result instanceof Response) {
973
968
  return result;
974
969
  }
975
970
 
976
- return new Response(
977
- typeof result === 'string' ? result : JSON.stringify(result),
978
- {
979
- status: 200,
980
- headers: { 'Content-Type': typeof result === 'string' ? 'text/plain' : 'application/json' }
981
- }
982
- );
971
+ return res.success(result);
983
972
  } catch (error) {
984
973
  console.error('Error executing request handler:', error);
985
- return new Response(
986
- JSON.stringify({ error: "Internal server error" }),
987
- { status: 500 }
988
- );
974
+ return res.serverError();
989
975
  }
990
976
  }
991
977
  }
@@ -1058,11 +1044,11 @@ export class Server implements Party.Server {
1058
1044
  * @description Processes requests forwarded by shards, preserving client context
1059
1045
  * @returns {Promise<Response>} The response to return to the shard (which will forward it to the client)
1060
1046
  */
1061
- private async handleShardRequest(req: Party.Request, shardId: string | null): Promise<Response> {
1047
+ private async handleShardRequest(req: Party.Request, res: ServerResponse, shardId: string | null): Promise<Response> {
1062
1048
  const subRoom = await this.getSubRoom();
1063
1049
 
1064
1050
  if (!subRoom) {
1065
- return new Response(JSON.stringify({ error: "Not found" }), { status: 404 });
1051
+ return res.notFound();
1066
1052
  }
1067
1053
 
1068
1054
  // Create a context that preserves original client information
@@ -1071,26 +1057,26 @@ export class Server implements Party.Server {
1071
1057
 
1072
1058
  try {
1073
1059
  // First try to match using the registered @Request handlers
1074
- const response = await this.tryMatchRequestHandler(enhancedReq, subRoom);
1060
+ const response = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
1075
1061
  if (response) {
1076
1062
  return response;
1077
1063
  }
1078
1064
 
1079
1065
  // Fall back to the legacy onRequest handler
1080
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, this.room));
1066
+ const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, res));
1081
1067
 
1082
1068
  if (!legacyResponse) {
1083
- return new Response(JSON.stringify({ error: "Not found" }), { status: 404 });
1069
+ return res.notFound();
1084
1070
  }
1085
1071
 
1086
1072
  if (legacyResponse instanceof Response) {
1087
1073
  return legacyResponse;
1088
1074
  }
1089
1075
 
1090
- return new Response(JSON.stringify(legacyResponse), { status: 200 });
1076
+ return res.success(legacyResponse);
1091
1077
  } catch (error) {
1092
1078
  console.error(`Error processing request from shard ${shardId}:`, error);
1093
- return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500 });
1079
+ return res.serverError();
1094
1080
  }
1095
1081
  }
1096
1082
 
package/src/world.ts CHANGED
@@ -6,6 +6,7 @@ import * as Party from "./types/party";
6
6
  import { guardManageWorld } from "./world.guard";
7
7
  import { response } from "./utils";
8
8
  import { RoomInterceptorPacket, RoomOnJoin } from "./interfaces";
9
+ import { ServerResponse } from "./request/response";
9
10
 
10
11
  // Types definitions
11
12
  type BalancingStrategy = 'round-robin' | 'least-connections' | 'random';
@@ -173,13 +174,13 @@ export class WorldRoom implements RoomInterceptorPacket, RoomOnJoin {
173
174
  method: 'POST',
174
175
  })
175
176
  @Guard([guardManageWorld])
176
- async updateShardStats(req: Party.Request) {
177
+ async updateShardStats(req: Party.Request, res: ServerResponse) {
177
178
  const body: { shardId: string; connections: number; status: ShardStatus } = await req.json();
178
179
  const { shardId, connections, status } = body;
179
180
  const shard = this.shards()[shardId];
180
181
 
181
182
  if (!shard) {
182
- return { error: `Shard ${shardId} not found` };
183
+ return res.notFound(`Shard ${shardId} not found`);
183
184
  }
184
185
 
185
186
  shard.currentConnections.set(connections);
@@ -194,14 +195,14 @@ export class WorldRoom implements RoomInterceptorPacket, RoomOnJoin {
194
195
  method: 'POST',
195
196
  })
196
197
  @Guard([guardManageWorld])
197
- async scaleRoom(req: Party.Request) {
198
+ async scaleRoom(req: Party.Request, res: ServerResponse) {
198
199
  const data: z.infer<typeof ScaleRoomSchema> = await req.json();
199
200
  const { targetShardCount, shardTemplate, roomId } = data;
200
201
 
201
202
  // Validate room exists
202
203
  const room = this.rooms()[roomId];
203
204
  if (!room) {
204
- return { error: `Room ${roomId} does not exist` };
205
+ return res.notFound(`Room ${roomId} does not exist`);
205
206
  }
206
207
 
207
208
  const roomShards = Object.values(this.shards())
@@ -211,11 +212,10 @@ export class WorldRoom implements RoomInterceptorPacket, RoomOnJoin {
211
212
 
212
213
  // Check max shards constraint
213
214
  if (room.maxShards() !== undefined && targetShardCount > room.maxShards()!) {
214
- return {
215
- error: `Cannot scale beyond maximum allowed shards (${room.maxShards()})`,
215
+ return res.badRequest(`Cannot scale beyond maximum allowed shards (${room.maxShards()})`, {
216
216
  roomId,
217
217
  currentShardCount: previousShardCount
218
- };
218
+ });
219
219
  }
220
220
 
221
221
  // Handle scaling down
@@ -268,7 +268,7 @@ export class WorldRoom implements RoomInterceptorPacket, RoomOnJoin {
268
268
  path: 'connect',
269
269
  method: 'POST',
270
270
  })
271
- async connect(req: Party.Request) {
271
+ async connect(req: Party.Request, res: ServerResponse) {
272
272
  try {
273
273
  // Extract request data
274
274
  let data: { roomId: string; autoCreate?: boolean };
@@ -277,17 +277,17 @@ export class WorldRoom implements RoomInterceptorPacket, RoomOnJoin {
277
277
  // Handle potential empty body or malformed JSON
278
278
  const body = await req.text();
279
279
  if (!body || body.trim() === '') {
280
- return response(400, { error: "Request body is empty" });
280
+ return res.badRequest("Request body is empty");
281
281
  }
282
282
 
283
283
  data = JSON.parse(body);
284
284
  } catch (parseError) {
285
- return response(400, { error: "Invalid JSON in request body" });
285
+ return res.badRequest("Invalid JSON in request body");
286
286
  }
287
287
 
288
288
  // Verify roomId is provided
289
289
  if (!data.roomId) {
290
- return response(400, { error: "roomId parameter is required" });
290
+ return res.badRequest("roomId parameter is required");
291
291
  }
292
292
 
293
293
  // Determine if auto-creation is enabled (default to true)
@@ -298,18 +298,18 @@ export class WorldRoom implements RoomInterceptorPacket, RoomOnJoin {
298
298
 
299
299
  // Check for errors
300
300
  if ('error' in result) {
301
- return response(404, { error: result.error });
301
+ return res.notFound(result.error);
302
302
  }
303
303
 
304
304
  // Return shard information to the client
305
- return response(200, {
305
+ return res.success({
306
306
  success: true,
307
307
  shardId: result.shardId,
308
308
  url: result.url
309
309
  });
310
310
  } catch (error) {
311
311
  console.error('Error connecting to shard:', error);
312
- return response(500, { error: "Internal server error", details: error instanceof Error ? error.message : String(error) });
312
+ return res.serverError();
313
313
  }
314
314
  }
315
315