@edge-base/server 0.2.5 → 0.2.7

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.
Files changed (135) hide show
  1. package/admin-build/_app/immutable/chunks/{DILS_-VJ.js → B3CvhH3c.js} +1 -1
  2. package/admin-build/_app/immutable/chunks/BDYewzou.js +1 -0
  3. package/admin-build/_app/immutable/chunks/{Cdm5zBRA.js → BEM1BeVF.js} +1 -1
  4. package/admin-build/_app/immutable/chunks/{Dt4vL4Df.js → BYL_uBga.js} +1 -1
  5. package/admin-build/_app/immutable/chunks/{B94PilAN.js → BYyykAbh.js} +1 -1
  6. package/admin-build/_app/immutable/chunks/BaUG2TJ-.js +1 -0
  7. package/admin-build/_app/immutable/chunks/{C72lTcG0.js → Bcs4KYNp.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{D2j3I1VQ.js → BfpUQYr3.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/BhCO1Fpt.js +1 -0
  10. package/admin-build/_app/immutable/chunks/{B8s_s9QY.js → BkZCgsc3.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/CIOC1v_q.js +128 -0
  12. package/admin-build/_app/immutable/chunks/CjcrXziO.js +2 -0
  13. package/admin-build/_app/immutable/chunks/CvczjTXx.js +1 -0
  14. package/admin-build/_app/immutable/chunks/D1u3u7xu.js +1 -0
  15. package/admin-build/_app/immutable/chunks/{B0HRJ657.js → DOOPbWwG.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/{BqTb6Mxk.js → DaXO-sFP.js} +1 -1
  17. package/admin-build/_app/immutable/chunks/DnpbvAPi.js +1 -0
  18. package/admin-build/_app/immutable/chunks/{B6MschND.js → Dz9cUCuv.js} +1 -1
  19. package/admin-build/_app/immutable/chunks/{CaVKAiCe.js → Tea2dBJ8.js} +1 -1
  20. package/admin-build/_app/immutable/chunks/{Z41NK6i6.js → bguI1TeA.js} +1 -1
  21. package/admin-build/_app/immutable/chunks/{J2Gw0SMu.js → ejoEf2I5.js} +1 -1
  22. package/admin-build/_app/immutable/chunks/{B2TnDKF7.js → iEyeblJR.js} +1 -1
  23. package/admin-build/_app/immutable/chunks/{_teD5ji5.js → nlAMTi52.js} +1 -1
  24. package/admin-build/_app/immutable/chunks/qKdzaeX3.js +1 -0
  25. package/admin-build/_app/immutable/entry/{app.D3flihMw.js → app.DoUaxnew.js} +2 -2
  26. package/admin-build/_app/immutable/entry/start.MmZh8oBH.js +1 -0
  27. package/admin-build/_app/immutable/nodes/{0.CdczqZLK.js → 0.Dsxi8s7i.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/1.Cp2l-hol.js +1 -0
  29. package/admin-build/_app/immutable/nodes/10.4oY6m8Nz.js +1 -0
  30. package/admin-build/_app/immutable/nodes/11.DfcozD4J.js +1 -0
  31. package/admin-build/_app/immutable/nodes/12.uJgZdCIA.js +1 -0
  32. package/admin-build/_app/immutable/nodes/13.CaN1kRev.js +110 -0
  33. package/admin-build/_app/immutable/nodes/14.DQ5xIi3s.js +3 -0
  34. package/admin-build/_app/immutable/nodes/15.B_EkebTJ.js +1 -0
  35. package/admin-build/_app/immutable/nodes/{16.BR7WwQrS.js → 16.Tko1ZX8-.js} +1 -1
  36. package/admin-build/_app/immutable/nodes/{17.Cm57KKXV.js → 17.BCmWMJX9.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/18.hmGhl1O2.js +1 -0
  38. package/admin-build/_app/immutable/nodes/19.D-1infOo.js +2 -0
  39. package/admin-build/_app/immutable/nodes/{20.DnHeFlTv.js → 20.CY4KKcBL.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/21.B9lbNUQr.js +1 -0
  41. package/admin-build/_app/immutable/nodes/22.14Vd7bnt.js +1 -0
  42. package/admin-build/_app/immutable/nodes/{23.CWSGMcKJ.js → 23.Be6jK77o.js} +2 -2
  43. package/admin-build/_app/immutable/nodes/24.CSTFkr6R.js +2 -0
  44. package/admin-build/_app/immutable/nodes/25.DRTg8fHc.js +2 -0
  45. package/admin-build/_app/immutable/nodes/26.DKt-9lwQ.js +1 -0
  46. package/admin-build/_app/immutable/nodes/27.D5caPu0F.js +1 -0
  47. package/admin-build/_app/immutable/nodes/28.hJhlnlyY.js +1 -0
  48. package/admin-build/_app/immutable/nodes/29.CDYBzFyT.js +1 -0
  49. package/admin-build/_app/immutable/nodes/{3.B6q-7qr8.js → 3.DMyKwkGn.js} +1 -1
  50. package/admin-build/_app/immutable/nodes/30.BaHNeEmc.js +1 -0
  51. package/admin-build/_app/immutable/nodes/31.C6PV5L-2.js +1 -0
  52. package/admin-build/_app/immutable/nodes/4.9E118Ftm.js +1 -0
  53. package/admin-build/_app/immutable/nodes/5.D8guAl3v.js +1 -0
  54. package/admin-build/_app/immutable/nodes/6.D1u__DtT.js +1 -0
  55. package/admin-build/_app/immutable/nodes/7.DWXHnRFf.js +1 -0
  56. package/admin-build/_app/immutable/nodes/8.Dojd8krc.js +1 -0
  57. package/admin-build/_app/immutable/nodes/9.CLtrr0K_.js +1 -0
  58. package/admin-build/_app/version.json +1 -1
  59. package/admin-build/index.html +7 -7
  60. package/openapi.json +6 -1941
  61. package/package.json +3 -3
  62. package/src/__tests__/openapi-coverage.test.ts +0 -6
  63. package/src/__tests__/push-handlers.test.ts +1 -1
  64. package/src/__tests__/room-auth-state-loss.test.ts +6 -0
  65. package/src/__tests__/room-handler-context.test.ts +0 -31
  66. package/src/__tests__/room-rate-limit-scopes.test.ts +1 -5
  67. package/src/__tests__/room-runtime-routing.test.ts +24 -111
  68. package/src/__tests__/route-parser.test.ts +6 -0
  69. package/src/__tests__/schema.test.ts +15 -6
  70. package/src/__tests__/smoke-skip-report.test.ts +1 -1
  71. package/src/durable-objects/database-do.ts +7 -1
  72. package/src/durable-objects/room-runtime-base.ts +290 -57
  73. package/src/durable-objects/rooms-do.ts +212 -1336
  74. package/src/index.ts +23 -9
  75. package/src/lib/d1-handler.ts +32 -17
  76. package/src/lib/openapi.ts +1 -4
  77. package/src/lib/postgres-handler.ts +24 -12
  78. package/src/lib/route-parser.ts +3 -0
  79. package/src/lib/schemas.ts +12 -2
  80. package/src/middleware/captcha-verify.ts +16 -3
  81. package/src/middleware/error-handler.ts +1 -1
  82. package/src/middleware/rules.ts +28 -9
  83. package/src/routes/admin-auth.ts +3 -3
  84. package/src/routes/admin.ts +13 -8
  85. package/src/routes/analytics-api.ts +3 -3
  86. package/src/routes/auth.ts +1 -1
  87. package/src/routes/backup.ts +1 -1
  88. package/src/routes/d1.ts +14 -7
  89. package/src/routes/database-live.ts +13 -6
  90. package/src/routes/kv.ts +21 -10
  91. package/src/routes/oauth.ts +1 -1
  92. package/src/routes/push.ts +119 -77
  93. package/src/routes/room.ts +203 -280
  94. package/src/routes/schema-endpoint.ts +2 -2
  95. package/src/routes/sql.ts +10 -6
  96. package/src/routes/storage.ts +4 -2
  97. package/src/routes/vectorize.ts +16 -4
  98. package/src/types.ts +1 -14
  99. package/admin-build/_app/immutable/chunks/6oMK_164.js +0 -1
  100. package/admin-build/_app/immutable/chunks/BEW7Ez_g.js +0 -1
  101. package/admin-build/_app/immutable/chunks/BoOooyH6.js +0 -1
  102. package/admin-build/_app/immutable/chunks/BvHnF5tV.js +0 -1
  103. package/admin-build/_app/immutable/chunks/CoI6jjbg.js +0 -2
  104. package/admin-build/_app/immutable/chunks/CrOZMmdF.js +0 -1
  105. package/admin-build/_app/immutable/chunks/Cw6OYcq-.js +0 -1
  106. package/admin-build/_app/immutable/chunks/DPdQ7z0T.js +0 -128
  107. package/admin-build/_app/immutable/chunks/pUxw8jfq.js +0 -1
  108. package/admin-build/_app/immutable/entry/start.Cl6sLxnz.js +0 -1
  109. package/admin-build/_app/immutable/nodes/1.DxcSsEqS.js +0 -1
  110. package/admin-build/_app/immutable/nodes/10.DuAd4aIm.js +0 -1
  111. package/admin-build/_app/immutable/nodes/11.0jgHQL92.js +0 -1
  112. package/admin-build/_app/immutable/nodes/12.CKNPqmyy.js +0 -1
  113. package/admin-build/_app/immutable/nodes/13.B1p2POXS.js +0 -110
  114. package/admin-build/_app/immutable/nodes/14.Bb-REBND.js +0 -3
  115. package/admin-build/_app/immutable/nodes/15.1uBFCX0X.js +0 -1
  116. package/admin-build/_app/immutable/nodes/18.CoiwfAuQ.js +0 -1
  117. package/admin-build/_app/immutable/nodes/19.B8ZdLlXj.js +0 -2
  118. package/admin-build/_app/immutable/nodes/21.CJFaf0Ia.js +0 -1
  119. package/admin-build/_app/immutable/nodes/22.CItETFzy.js +0 -1
  120. package/admin-build/_app/immutable/nodes/24.CWbEqNMB.js +0 -2
  121. package/admin-build/_app/immutable/nodes/25.DRkLEhKi.js +0 -2
  122. package/admin-build/_app/immutable/nodes/26.BRxO8AYH.js +0 -1
  123. package/admin-build/_app/immutable/nodes/27.BLs-nVHz.js +0 -1
  124. package/admin-build/_app/immutable/nodes/28.G79qkdBK.js +0 -1
  125. package/admin-build/_app/immutable/nodes/29.BOcI6g0N.js +0 -1
  126. package/admin-build/_app/immutable/nodes/30.DAIC7dKd.js +0 -1
  127. package/admin-build/_app/immutable/nodes/31.pl0XXjXF.js +0 -1
  128. package/admin-build/_app/immutable/nodes/4.DOdvVlZj.js +0 -1
  129. package/admin-build/_app/immutable/nodes/5.BW_zlgye.js +0 -1
  130. package/admin-build/_app/immutable/nodes/6.Dxy1CAI2.js +0 -1
  131. package/admin-build/_app/immutable/nodes/7.BG98w_o7.js +0 -1
  132. package/admin-build/_app/immutable/nodes/8.DoG5R2rG.js +0 -1
  133. package/admin-build/_app/immutable/nodes/9.Dmxf6zAC.js +0 -1
  134. package/src/__tests__/cloudflare-realtime.test.ts +0 -113
  135. package/src/lib/cloudflare-realtime.ts +0 -251
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edge-base/server",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "EdgeBase runtime assets consumed by the EdgeBase CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,8 +34,8 @@
34
34
  "jose": "^6.0.0",
35
35
  "pg": "^8.16.3",
36
36
  "zod": "^4.3.6",
37
- "@edge-base/core": "0.2.5",
38
- "@edge-base/shared": "0.2.5"
37
+ "@edge-base/core": "0.2.7",
38
+ "@edge-base/shared": "0.2.7"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@cloudflare/vitest-pool-workers": "^0.8.71",
@@ -118,8 +118,6 @@ describe('OpenAPI route coverage', () => {
118
118
  '/api/sql': { post: {} },
119
119
  '/admin/api/setup': { get: {} },
120
120
  '/admin/api/data/users': { get: {} },
121
- '/api/room/media/realtime/session': { post: {} },
122
- '/api/room/media/cloudflare_realtimekit/session': { post: {} },
123
121
  },
124
122
  };
125
123
 
@@ -134,10 +132,6 @@ describe('OpenAPI route coverage', () => {
134
132
  expect(schemes).toHaveProperty('serviceKeyAuth');
135
133
  expect((normalized.paths?.['/api/auth/me'] as Record<string, { security?: unknown }>).get.security)
136
134
  .toEqual([{ userBearerAuth: [] }]);
137
- expect((normalized.paths?.['/api/room/media/realtime/session'] as Record<string, { security?: unknown }>).post.security)
138
- .toEqual([{ userBearerAuth: [] }]);
139
- expect((normalized.paths?.['/api/room/media/cloudflare_realtimekit/session'] as Record<string, { security?: unknown }>).post.security)
140
- .toEqual([{ userBearerAuth: [] }]);
141
135
  expect((normalized.paths?.['/api/sql'] as Record<string, { security?: unknown }>).post.security)
142
136
  .toEqual([{ serviceKeyAuth: [] }]);
143
137
  expect((normalized.paths?.['/admin/api/setup'] as Record<string, { security?: unknown }>).get.security)
@@ -173,7 +173,7 @@ describe('Push handlers route integration', () => {
173
173
 
174
174
  expect(response.status).toBe(400);
175
175
  await expect(response.json()).resolves.toMatchObject({
176
- message: 'beforeSend must return topic and payload',
176
+ message: 'push.hooks.beforeSend must return a topic and payload when overriding topic delivery.',
177
177
  });
178
178
  });
179
179
  });
@@ -53,6 +53,7 @@ describe('room auth-state loss recovery', () => {
53
53
  room._stateSaveAt = 33_333;
54
54
  room._emptyRoomCleanupAt = 44_444;
55
55
  room._stateTTLAlarmAt = 55_555;
56
+ room._socketHeartbeatCheckAt = 66_666;
56
57
  room.ctx = {
57
58
  storage: {
58
59
  put: putSpy,
@@ -72,6 +73,7 @@ describe('room auth-state loss recovery', () => {
72
73
  stateSaveAt: 33_333,
73
74
  emptyRoomCleanupAt: 44_444,
74
75
  stateTTLAlarmAt: 55_555,
76
+ socketHeartbeatCheckAt: 66_666,
75
77
  });
76
78
  });
77
79
 
@@ -112,6 +114,7 @@ describe('room auth-state loss recovery', () => {
112
114
  room._stateTTLAlarmAt = null;
113
115
  room._metadata = {};
114
116
  room.config = {};
117
+ room.env = {};
115
118
  room.ctx = {
116
119
  getWebSockets: vi.fn(() => []),
117
120
  };
@@ -132,6 +135,7 @@ describe('room auth-state loss recovery', () => {
132
135
 
133
136
  const room: any = Object.create(RoomRuntimeBaseDO.prototype);
134
137
  room._metaCache = new Map();
138
+ room._attachmentExtraCache = new Map();
135
139
  room.ctx = {
136
140
  getTags: vi.fn(() => [
137
141
  'conn:conn-1',
@@ -171,6 +175,7 @@ describe('room auth-state loss recovery', () => {
171
175
  authStateLost: false,
172
176
  connectionId: 'conn-1',
173
177
  }]]);
178
+ room._attachmentExtraCache = new Map();
174
179
  room.safeSend = vi.fn();
175
180
 
176
181
  await room.webSocketMessage(ws, JSON.stringify({ type: 'ping' }));
@@ -193,6 +198,7 @@ describe('room auth-state loss recovery', () => {
193
198
  authStateLost: true,
194
199
  connectionId: 'conn-1',
195
200
  }]]);
201
+ room._attachmentExtraCache = new Map();
196
202
  room.safeSend = vi.fn();
197
203
 
198
204
  await room.webSocketMessage(ws, JSON.stringify({ type: 'ping' }));
@@ -127,35 +127,4 @@ describe('RoomsDO handler context', () => {
127
127
  }),
128
128
  );
129
129
  }, 15_000);
130
-
131
- it('returns 409 when creating a Cloudflare RealtimeKit session while media is already published', async () => {
132
- const { RoomsDO } = await import('../durable-objects/rooms-do.js');
133
-
134
- const room: any = Object.create(RoomsDO.prototype);
135
- room.readJsonBody = vi.fn().mockResolvedValue({ connectionId: 'conn-1' });
136
- room.authenticateRealtimeRequest = vi.fn().mockResolvedValue({
137
- memberId: 'member-1',
138
- connectionId: 'conn-1',
139
- meta: {
140
- authenticated: true,
141
- connectionId: 'conn-1',
142
- },
143
- });
144
- room.hasPublishedTracks = vi.fn().mockReturnValue(true);
145
-
146
- const response = await room.handleCloudflareRealtimeKitSessionCreate(
147
- new Request('http://do/media/cloudflare_realtimekit/session?room=game::room-1', {
148
- method: 'POST',
149
- body: JSON.stringify({ connectionId: 'conn-1' }),
150
- headers: { 'Content-Type': 'application/json' },
151
- }),
152
- new URL('http://do/media/cloudflare_realtimekit/session?room=game::room-1'),
153
- );
154
-
155
- expect(response.status).toBe(409);
156
- await expect(response.json()).resolves.toEqual({
157
- code: 409,
158
- message: 'Unpublish existing room media before creating a new Cloudflare RealtimeKit session',
159
- });
160
- });
161
130
  });
@@ -5,7 +5,7 @@ vi.mock('cloudflare:workers', () => ({
5
5
  }));
6
6
 
7
7
  describe('room rate-limit scopes', () => {
8
- it('keeps signal/media/admin buckets independent per connection', async () => {
8
+ it('keeps signal/admin buckets independent per connection', async () => {
9
9
  const { RoomRuntimeBaseDO } = await import('../durable-objects/room-runtime-base.js');
10
10
 
11
11
  const room: any = Object.create(RoomRuntimeBaseDO.prototype);
@@ -13,7 +13,6 @@ describe('room rate-limit scopes', () => {
13
13
  rateLimit: {
14
14
  actions: 2,
15
15
  signals: 4,
16
- media: 1,
17
16
  admin: 1,
18
17
  },
19
18
  };
@@ -25,9 +24,6 @@ describe('room rate-limit scopes', () => {
25
24
  expect(room.checkRateLimit('conn-1', 'signals')).toBe(true);
26
25
  expect(room.checkRateLimit('conn-1', 'signals')).toBe(false);
27
26
 
28
- expect(room.checkRateLimit('conn-1', 'media')).toBe(true);
29
- expect(room.checkRateLimit('conn-1', 'media')).toBe(false);
30
-
31
27
  expect(room.checkRateLimit('conn-1', 'admin')).toBe(true);
32
28
  expect(room.checkRateLimit('conn-1', 'admin')).toBe(false);
33
29
 
@@ -1,4 +1,4 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
2
  import { defineConfig } from '@edge-base/shared';
3
3
  import { OpenAPIHono, type HonoEnv } from '../lib/hono.js';
4
4
  import { setConfig } from '../lib/do-router.js';
@@ -33,21 +33,6 @@ function createRoomApp() {
33
33
  return app;
34
34
  }
35
35
 
36
- function createAuthedRoomApp() {
37
- const app = new OpenAPIHono<HonoEnv>();
38
- app.use('/api/*', async (c, next) => {
39
- c.set('auth', {
40
- id: 'user-1',
41
- role: 'user',
42
- isAnonymous: false,
43
- meta: {},
44
- });
45
- await next();
46
- });
47
- app.route('/api/room', roomRoute);
48
- return app;
49
- }
50
-
51
36
  describe('room runtime selection', () => {
52
37
  afterEach(() => {
53
38
  setConfig({});
@@ -98,6 +83,29 @@ describe('room route runtime routing', () => {
98
83
  await expect(response.json()).resolves.toEqual({ runtime: 'rooms' });
99
84
  });
100
85
 
86
+ it('routes summary requests to the rooms runtime', async () => {
87
+ setConfig(defineConfig({
88
+ rooms: {
89
+ game: {
90
+ runtime: {
91
+ target: 'rooms',
92
+ },
93
+ public: {
94
+ metadata: true,
95
+ },
96
+ },
97
+ },
98
+ }));
99
+
100
+ const app = createRoomApp();
101
+ const response = await app.request('/api/room/summary?namespace=game&id=room-1', {
102
+ method: 'GET',
103
+ }, createRoomRuntimeEnv());
104
+
105
+ expect(response.status).toBe(201);
106
+ await expect(response.json()).resolves.toEqual({ runtime: 'rooms' });
107
+ });
108
+
101
109
  it('routes websocket upgrades to the rooms runtime', async () => {
102
110
  setConfig(defineConfig({
103
111
  rooms: {
@@ -172,99 +180,4 @@ describe('room route runtime routing', () => {
172
180
  });
173
181
  });
174
182
 
175
- it('routes room media requests to the rooms runtime', async () => {
176
- setConfig(defineConfig({
177
- rooms: {
178
- game: {
179
- runtime: {
180
- target: 'rooms',
181
- },
182
- },
183
- },
184
- }));
185
-
186
- const doFetch = vi.fn(async (request: Request) => new Response(JSON.stringify({
187
- runtime: 'rooms',
188
- path: new URL(request.url).pathname,
189
- auth: request.headers.get('Authorization'),
190
- body: await request.clone().json(),
191
- }), {
192
- headers: { 'Content-Type': 'application/json' },
193
- status: 201,
194
- }));
195
-
196
- const env = createRoomRuntimeEnv();
197
- env.ROOMS = {
198
- idFromName: (name: string) => name as unknown as DurableObjectId,
199
- get: () => ({ fetch: doFetch }),
200
- } as unknown as DurableObjectNamespace;
201
-
202
- const app = createAuthedRoomApp();
203
- const response = await app.request('/api/room/media/realtime/session?namespace=game&id=room-1', {
204
- method: 'POST',
205
- headers: {
206
- Authorization: 'Bearer room-token',
207
- 'Content-Type': 'application/json',
208
- },
209
- body: JSON.stringify({ connectionId: 'conn-1' }),
210
- }, env);
211
-
212
- expect(response.status).toBe(201);
213
- await expect(response.json()).resolves.toMatchObject({
214
- runtime: 'rooms',
215
- path: '/media/realtime/session',
216
- auth: 'Bearer room-token',
217
- body: { connectionId: 'conn-1' },
218
- });
219
- expect(doFetch).toHaveBeenCalledTimes(1);
220
- expect(new URL((doFetch.mock.calls[0][0] as Request).url).searchParams.get('room')).toBe('game::room-1');
221
- });
222
-
223
- it('routes room cloudflare realtimekit session requests to the rooms runtime', async () => {
224
- setConfig(defineConfig({
225
- rooms: {
226
- game: {
227
- runtime: {
228
- target: 'rooms',
229
- },
230
- },
231
- },
232
- }));
233
-
234
- const doFetch = vi.fn(async (request: Request) => new Response(JSON.stringify({
235
- runtime: 'rooms',
236
- path: new URL(request.url).pathname,
237
- auth: request.headers.get('Authorization'),
238
- body: await request.clone().json(),
239
- }), {
240
- headers: { 'Content-Type': 'application/json' },
241
- status: 201,
242
- }));
243
-
244
- const env = createRoomRuntimeEnv();
245
- env.ROOMS = {
246
- idFromName: (name: string) => name as unknown as DurableObjectId,
247
- get: () => ({ fetch: doFetch }),
248
- } as unknown as DurableObjectNamespace;
249
-
250
- const app = createAuthedRoomApp();
251
- const response = await app.request('/api/room/media/cloudflare_realtimekit/session?namespace=game&id=room-1', {
252
- method: 'POST',
253
- headers: {
254
- Authorization: 'Bearer room-token',
255
- 'Content-Type': 'application/json',
256
- },
257
- body: JSON.stringify({ connectionId: 'conn-1' }),
258
- }, env);
259
-
260
- expect(response.status).toBe(201);
261
- await expect(response.json()).resolves.toMatchObject({
262
- runtime: 'rooms',
263
- path: '/media/cloudflare_realtimekit/session',
264
- auth: 'Bearer room-token',
265
- body: { connectionId: 'conn-1' },
266
- });
267
- expect(doFetch).toHaveBeenCalledTimes(1);
268
- expect(new URL((doFetch.mock.calls[0][0] as Request).url).searchParams.get('room')).toBe('game::room-1');
269
- });
270
183
  });
@@ -360,6 +360,12 @@ describe('parseRoute — room', () => {
360
360
  expect(r.subcategory).toBe('metadata');
361
361
  expect(r.operation).toBe('getMetadata');
362
362
  });
363
+
364
+ it('GET /api/room/summary', () => {
365
+ const r = parseRoute('GET', '/api/room/summary');
366
+ expect(r.subcategory).toBe('summary');
367
+ expect(r.operation).toBe('getSummary');
368
+ });
363
369
  });
364
370
 
365
371
  // ─── K. Other feature routes ────────────────────────────────────────────────
@@ -825,10 +825,10 @@ describe('zodDefaultHook', () => {
825
825
  const c = mockContext();
826
826
  const result = zodDefaultHook({
827
827
  success: false,
828
- error: { issues: [{ message: 'field required' }, { message: 'invalid type' }] },
828
+ error: { issues: [{ message: 'field required', path: ['body', 'email'] }, { message: 'invalid type' }] },
829
829
  }, c);
830
830
  expect(result).toBeDefined();
831
- expect(c.lastJson).toEqual({ code: 400, message: 'field required, invalid type' });
831
+ expect(c.lastJson).toEqual({ code: 400, message: 'body.email: field required, invalid type' });
832
832
  expect(c.lastStatus).toBe(400);
833
833
  });
834
834
 
@@ -841,13 +841,13 @@ describe('zodDefaultHook', () => {
841
841
  expect(c.lastJson).toEqual({ code: 400, message: 'too short' });
842
842
  });
843
843
 
844
- it('handles empty issues → empty message', () => {
844
+ it('handles empty issues → default message', () => {
845
845
  const c = mockContext();
846
846
  zodDefaultHook({
847
847
  success: false,
848
848
  error: { issues: [] },
849
849
  }, c);
850
- expect(c.lastJson).toEqual({ code: 400, message: '' });
850
+ expect(c.lastJson).toEqual({ code: 400, message: 'Request validation failed.' });
851
851
  });
852
852
 
853
853
  it('handles missing error.issues and error.errors', () => {
@@ -856,7 +856,7 @@ describe('zodDefaultHook', () => {
856
856
  success: false,
857
857
  error: {},
858
858
  }, c);
859
- expect(c.lastJson).toEqual({ code: 400, message: '' });
859
+ expect(c.lastJson).toEqual({ code: 400, message: 'Request validation failed.' });
860
860
  });
861
861
 
862
862
  it('handles undefined error', () => {
@@ -864,7 +864,16 @@ describe('zodDefaultHook', () => {
864
864
  zodDefaultHook({
865
865
  success: false,
866
866
  }, c);
867
- expect(c.lastJson).toEqual({ code: 400, message: '' });
867
+ expect(c.lastJson).toEqual({ code: 400, message: 'Request validation failed.' });
868
+ });
869
+
870
+ it('formats array indexes in issue paths', () => {
871
+ const c = mockContext();
872
+ zodDefaultHook({
873
+ success: false,
874
+ error: { issues: [{ message: 'Expected string', path: ['body', 'members', 0, 'email'] }] },
875
+ }, c);
876
+ expect(c.lastJson).toEqual({ code: 400, message: 'body.members[0].email: Expected string' });
868
877
  });
869
878
  });
870
879
 
@@ -38,7 +38,7 @@ describe('smoke skip report', () => {
38
38
  {
39
39
  "skippedRouteCount": 0,
40
40
  "summaryByReason": {},
41
- "totalRoutes": 197,
41
+ "totalRoutes": 190,
42
42
  }
43
43
  `);
44
44
  });
@@ -1936,7 +1936,13 @@ export class DatabaseDO extends DurableObject<DOEnv> {
1936
1936
  return c.json(normalizedDbError.toJSON(), normalizedDbError.code as 400);
1937
1937
  }
1938
1938
  console.error('DatabaseDO Error:', err);
1939
- return c.json({ code: 500, message: 'Internal server error.' }, 500);
1939
+ return c.json(
1940
+ {
1941
+ code: 500,
1942
+ message: `Database request failed while handling '${c.req.path}'. Check the worker logs for the original exception.`,
1943
+ },
1944
+ 500,
1945
+ );
1940
1946
  });
1941
1947
 
1942
1948
  return app;