@edge-base/server 0.2.5 → 0.2.6

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 (125) hide show
  1. package/admin-build/_app/immutable/chunks/{DILS_-VJ.js → B3CvhH3c.js} +1 -1
  2. package/admin-build/_app/immutable/chunks/BN_-k-Ck.js +1 -0
  3. package/admin-build/_app/immutable/chunks/{Dt4vL4Df.js → BYL_uBga.js} +1 -1
  4. package/admin-build/_app/immutable/chunks/{C72lTcG0.js → Bcs4KYNp.js} +1 -1
  5. package/admin-build/_app/immutable/chunks/{B8s_s9QY.js → BkZCgsc3.js} +1 -1
  6. package/admin-build/_app/immutable/chunks/{D2j3I1VQ.js → BvoGcDFV.js} +1 -1
  7. package/admin-build/_app/immutable/chunks/{B2TnDKF7.js → CCUxCptE.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/CLHN9MVr.js +1 -0
  9. package/admin-build/_app/immutable/chunks/{J2Gw0SMu.js → CR37B8DX.js} +1 -1
  10. package/admin-build/_app/immutable/chunks/CbfX3ELZ.js +1 -0
  11. package/admin-build/_app/immutable/chunks/CjcrXziO.js +2 -0
  12. package/admin-build/_app/immutable/chunks/CrwlCAM0.js +1 -0
  13. package/admin-build/_app/immutable/chunks/{B0HRJ657.js → DOOPbWwG.js} +1 -1
  14. package/admin-build/_app/immutable/chunks/DQVP4KC-.js +1 -0
  15. package/admin-build/_app/immutable/chunks/{B6MschND.js → DdvsFblq.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/DemDWbs-.js +128 -0
  17. package/admin-build/_app/immutable/chunks/{CaVKAiCe.js → DmDTovpg.js} +1 -1
  18. package/admin-build/_app/immutable/chunks/{B94PilAN.js → Ff90owjx.js} +1 -1
  19. package/admin-build/_app/immutable/chunks/{BqTb6Mxk.js → LL3ulaxa.js} +1 -1
  20. package/admin-build/_app/immutable/chunks/Q3vAxeY-.js +1 -0
  21. package/admin-build/_app/immutable/chunks/SQVAC3Cv.js +1 -0
  22. package/admin-build/_app/immutable/chunks/{Z41NK6i6.js → bguI1TeA.js} +1 -1
  23. package/admin-build/_app/immutable/chunks/{_teD5ji5.js → nlAMTi52.js} +1 -1
  24. package/admin-build/_app/immutable/chunks/{Cdm5zBRA.js → qBm6xof8.js} +1 -1
  25. package/admin-build/_app/immutable/entry/{app.D3flihMw.js → app.CP83Ni80.js} +2 -2
  26. package/admin-build/_app/immutable/entry/start.DY6YakU0.js +1 -0
  27. package/admin-build/_app/immutable/nodes/{0.CdczqZLK.js → 0.DiRq7puO.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/1.BFeyKLGT.js +1 -0
  29. package/admin-build/_app/immutable/nodes/10.zcee7hJx.js +1 -0
  30. package/admin-build/_app/immutable/nodes/11.BW7wLs2Y.js +1 -0
  31. package/admin-build/_app/immutable/nodes/12.CxJRlYSd.js +1 -0
  32. package/admin-build/_app/immutable/nodes/13.pp0F_5hn.js +110 -0
  33. package/admin-build/_app/immutable/nodes/14.t3AfGiGo.js +3 -0
  34. package/admin-build/_app/immutable/nodes/15.B3agc7NX.js +1 -0
  35. package/admin-build/_app/immutable/nodes/{16.BR7WwQrS.js → 16.C4uG2-i8.js} +1 -1
  36. package/admin-build/_app/immutable/nodes/{17.Cm57KKXV.js → 17.CwGxi1Bn.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/18.CrQyN_gU.js +1 -0
  38. package/admin-build/_app/immutable/nodes/19.NEPUOXl7.js +2 -0
  39. package/admin-build/_app/immutable/nodes/{20.DnHeFlTv.js → 20.DGHO8ipr.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/21.UVKBDvp4.js +1 -0
  41. package/admin-build/_app/immutable/nodes/22.Dri5It7a.js +1 -0
  42. package/admin-build/_app/immutable/nodes/{23.CWSGMcKJ.js → 23.BPQP_Zte.js} +2 -2
  43. package/admin-build/_app/immutable/nodes/24.D580FdSS.js +2 -0
  44. package/admin-build/_app/immutable/nodes/25.BMNPOZwF.js +2 -0
  45. package/admin-build/_app/immutable/nodes/26.XcpEcbiz.js +1 -0
  46. package/admin-build/_app/immutable/nodes/27.C1zHHcYv.js +1 -0
  47. package/admin-build/_app/immutable/nodes/28.CuKzzrY8.js +1 -0
  48. package/admin-build/_app/immutable/nodes/29.nLpBMXnM.js +1 -0
  49. package/admin-build/_app/immutable/nodes/{3.B6q-7qr8.js → 3.5G_aseoL.js} +1 -1
  50. package/admin-build/_app/immutable/nodes/30.CQC4nLoU.js +1 -0
  51. package/admin-build/_app/immutable/nodes/31.Bet8kxOK.js +1 -0
  52. package/admin-build/_app/immutable/nodes/4.nmJDYJpC.js +1 -0
  53. package/admin-build/_app/immutable/nodes/5.CnbYLG4E.js +1 -0
  54. package/admin-build/_app/immutable/nodes/6.KA01b-3y.js +1 -0
  55. package/admin-build/_app/immutable/nodes/7.CP9fkn1L.js +1 -0
  56. package/admin-build/_app/immutable/nodes/8.BTzDb---.js +1 -0
  57. package/admin-build/_app/immutable/nodes/9.DkNJg_J6.js +1 -0
  58. package/admin-build/_app/version.json +1 -1
  59. package/admin-build/index.html +7 -7
  60. package/package.json +3 -3
  61. package/src/__tests__/push-handlers.test.ts +1 -1
  62. package/src/__tests__/room-runtime-routing.test.ts +23 -0
  63. package/src/__tests__/route-parser.test.ts +6 -0
  64. package/src/__tests__/schema.test.ts +15 -6
  65. package/src/durable-objects/database-do.ts +7 -1
  66. package/src/durable-objects/room-runtime-base.ts +49 -40
  67. package/src/durable-objects/rooms-do.ts +32 -1
  68. package/src/index.ts +23 -9
  69. package/src/lib/d1-handler.ts +32 -17
  70. package/src/lib/postgres-handler.ts +24 -12
  71. package/src/lib/route-parser.ts +3 -0
  72. package/src/lib/schemas.ts +12 -2
  73. package/src/middleware/captcha-verify.ts +16 -3
  74. package/src/middleware/error-handler.ts +1 -1
  75. package/src/middleware/rules.ts +28 -9
  76. package/src/routes/admin-auth.ts +3 -3
  77. package/src/routes/admin.ts +13 -8
  78. package/src/routes/analytics-api.ts +3 -3
  79. package/src/routes/auth.ts +1 -1
  80. package/src/routes/backup.ts +1 -1
  81. package/src/routes/d1.ts +14 -7
  82. package/src/routes/database-live.ts +13 -6
  83. package/src/routes/kv.ts +21 -10
  84. package/src/routes/oauth.ts +1 -1
  85. package/src/routes/push.ts +119 -77
  86. package/src/routes/room.ts +215 -7
  87. package/src/routes/schema-endpoint.ts +2 -2
  88. package/src/routes/sql.ts +10 -6
  89. package/src/routes/storage.ts +4 -2
  90. package/src/routes/vectorize.ts +16 -4
  91. package/admin-build/_app/immutable/chunks/6oMK_164.js +0 -1
  92. package/admin-build/_app/immutable/chunks/BEW7Ez_g.js +0 -1
  93. package/admin-build/_app/immutable/chunks/BoOooyH6.js +0 -1
  94. package/admin-build/_app/immutable/chunks/BvHnF5tV.js +0 -1
  95. package/admin-build/_app/immutable/chunks/CoI6jjbg.js +0 -2
  96. package/admin-build/_app/immutable/chunks/CrOZMmdF.js +0 -1
  97. package/admin-build/_app/immutable/chunks/Cw6OYcq-.js +0 -1
  98. package/admin-build/_app/immutable/chunks/DPdQ7z0T.js +0 -128
  99. package/admin-build/_app/immutable/chunks/pUxw8jfq.js +0 -1
  100. package/admin-build/_app/immutable/entry/start.Cl6sLxnz.js +0 -1
  101. package/admin-build/_app/immutable/nodes/1.DxcSsEqS.js +0 -1
  102. package/admin-build/_app/immutable/nodes/10.DuAd4aIm.js +0 -1
  103. package/admin-build/_app/immutable/nodes/11.0jgHQL92.js +0 -1
  104. package/admin-build/_app/immutable/nodes/12.CKNPqmyy.js +0 -1
  105. package/admin-build/_app/immutable/nodes/13.B1p2POXS.js +0 -110
  106. package/admin-build/_app/immutable/nodes/14.Bb-REBND.js +0 -3
  107. package/admin-build/_app/immutable/nodes/15.1uBFCX0X.js +0 -1
  108. package/admin-build/_app/immutable/nodes/18.CoiwfAuQ.js +0 -1
  109. package/admin-build/_app/immutable/nodes/19.B8ZdLlXj.js +0 -2
  110. package/admin-build/_app/immutable/nodes/21.CJFaf0Ia.js +0 -1
  111. package/admin-build/_app/immutable/nodes/22.CItETFzy.js +0 -1
  112. package/admin-build/_app/immutable/nodes/24.CWbEqNMB.js +0 -2
  113. package/admin-build/_app/immutable/nodes/25.DRkLEhKi.js +0 -2
  114. package/admin-build/_app/immutable/nodes/26.BRxO8AYH.js +0 -1
  115. package/admin-build/_app/immutable/nodes/27.BLs-nVHz.js +0 -1
  116. package/admin-build/_app/immutable/nodes/28.G79qkdBK.js +0 -1
  117. package/admin-build/_app/immutable/nodes/29.BOcI6g0N.js +0 -1
  118. package/admin-build/_app/immutable/nodes/30.DAIC7dKd.js +0 -1
  119. package/admin-build/_app/immutable/nodes/31.pl0XXjXF.js +0 -1
  120. package/admin-build/_app/immutable/nodes/4.DOdvVlZj.js +0 -1
  121. package/admin-build/_app/immutable/nodes/5.BW_zlgye.js +0 -1
  122. package/admin-build/_app/immutable/nodes/6.Dxy1CAI2.js +0 -1
  123. package/admin-build/_app/immutable/nodes/7.BG98w_o7.js +0 -1
  124. package/admin-build/_app/immutable/nodes/8.DoG5R2rG.js +0 -1
  125. package/admin-build/_app/immutable/nodes/9.Dmxf6zAC.js +0 -1
@@ -117,6 +117,38 @@ function metadataExceedsByteLimit(metadata: Record<string, unknown> | undefined)
117
117
  return utf8Encoder.encode(JSON.stringify(metadata)).length > MAX_METADATA_BYTES;
118
118
  }
119
119
 
120
+ function invalidPushJsonMessage(context: string): string {
121
+ return `Invalid JSON body for ${context}. Send application/json with the expected fields.`;
122
+ }
123
+
124
+ function pushAuthRequiredMessage(context: string): string {
125
+ return `Authentication required. Sign in before trying to ${context}.`;
126
+ }
127
+
128
+ function pushServiceKeyRequiredMessage(context: string): string {
129
+ return `X-EdgeBase-Service-Key is required to ${context}.`;
130
+ }
131
+
132
+ function pushInvalidServiceKeyMessage(context: string): string {
133
+ return `Invalid X-EdgeBase-Service-Key for ${context}.`;
134
+ }
135
+
136
+ function pushNotConfiguredMessage(action: string): string {
137
+ return `Push notifications are not configured. Add push.fcm config with FCM credentials before trying to ${action}.`;
138
+ }
139
+
140
+ function pushRequiredFieldMessage(field: string, context: string): string {
141
+ return `Missing required field '${field}' for ${context}.`;
142
+ }
143
+
144
+ function pushMetadataTooLargeMessage(context: string): string {
145
+ return `metadata exceeds the ${MAX_METADATA_BYTES}-byte limit for ${context}.`;
146
+ }
147
+
148
+ function pushRuleRejectedMessage(context: string): string {
149
+ return `Push send denied by push access rules for ${context}.`;
150
+ }
151
+
120
152
  function getSharedMirrorDb(env: Env): D1Database | null {
121
153
  const config = parseConfig(env);
122
154
  if (!shouldRouteToD1('shared', config)) return null;
@@ -316,7 +348,7 @@ const pushRegister = createRoute({
316
348
  pushRoute.openapi(pushRegister, async (c) => {
317
349
  const auth = c.get('auth' as never) as { id: string } | null | undefined;
318
350
  if (!auth?.id) {
319
- return c.json({ code: 401, message: 'Authentication required to register push token' }, 401);
351
+ return c.json({ code: 401, message: pushAuthRequiredMessage('register a push token') }, 401);
320
352
  }
321
353
 
322
354
  let body: {
@@ -329,23 +361,23 @@ pushRoute.openapi(pushRegister, async (c) => {
329
361
  try {
330
362
  body = await c.req.json();
331
363
  } catch {
332
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
364
+ return c.json({ code: 400, message: invalidPushJsonMessage('push token registration') }, 400);
333
365
  }
334
366
 
335
367
  const { deviceId, token, platform } = body;
336
368
  if (!deviceId || typeof deviceId !== 'string') {
337
- return c.json({ code: 400, message: 'deviceId is required' }, 400);
369
+ return c.json({ code: 400, message: pushRequiredFieldMessage('deviceId', 'push token registration') }, 400);
338
370
  }
339
371
  if (!token || typeof token !== 'string') {
340
- return c.json({ code: 400, message: 'token is required' }, 400);
372
+ return c.json({ code: 400, message: pushRequiredFieldMessage('token', 'push token registration') }, 400);
341
373
  }
342
374
  if (!platform || typeof platform !== 'string') {
343
- return c.json({ code: 400, message: 'platform is required' }, 400);
375
+ return c.json({ code: 400, message: pushRequiredFieldMessage('platform', 'push token registration') }, 400);
344
376
  }
345
377
 
346
378
  // Validate metadata size (≤1KB)
347
379
  if (metadataExceedsByteLimit(body.metadata)) {
348
- return c.json({ code: 400, message: `metadata exceeds ${MAX_METADATA_BYTES} byte limit` }, 400);
380
+ return c.json({ code: 400, message: pushMetadataTooLargeMessage('push token registration') }, 400);
349
381
  }
350
382
 
351
383
  // All tokens are FCM Registration Tokens — store directly
@@ -394,18 +426,18 @@ const pushUnregister = createRoute({
394
426
  pushRoute.openapi(pushUnregister, async (c) => {
395
427
  const auth = c.get('auth' as never) as { id: string } | null | undefined;
396
428
  if (!auth?.id) {
397
- return c.json({ code: 401, message: 'Authentication required' }, 401);
429
+ return c.json({ code: 401, message: pushAuthRequiredMessage('unregister a push token') }, 401);
398
430
  }
399
431
 
400
432
  let body: { deviceId?: string };
401
433
  try {
402
434
  body = await c.req.json();
403
435
  } catch {
404
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
436
+ return c.json({ code: 400, message: invalidPushJsonMessage('push token unregistration') }, 400);
405
437
  }
406
438
 
407
439
  if (!body.deviceId || typeof body.deviceId !== 'string') {
408
- return c.json({ code: 400, message: 'deviceId is required' }, 400);
440
+ return c.json({ code: 400, message: pushRequiredFieldMessage('deviceId', 'push token unregistration') }, 400);
409
441
  }
410
442
 
411
443
  // Get the FCM token from user's device array BEFORE removing
@@ -461,30 +493,30 @@ pushRoute.openapi(pushSend, async (c) => {
461
493
  buildConstraintCtx(c.env, c.req),
462
494
  );
463
495
  if (skResult === 'missing') {
464
- return c.json({ code: 403, message: 'Service Key required for push send' }, 403);
496
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send a push notification') }, 403);
465
497
  }
466
498
  if (skResult === 'invalid') {
467
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
499
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push notification send') }, 401);
468
500
  }
469
501
 
470
502
  const provider = createPushProvider(config.push, c.env);
471
503
  if (!provider) {
472
- return c.json({ code: 503, message: 'Push notifications are not configured. Add push.fcm config with FCM credentials.' }, 503);
504
+ return c.json({ code: 503, message: pushNotConfiguredMessage('send a push notification') }, 503);
473
505
  }
474
506
 
475
507
  let body: { userId?: string; payload?: PushPayload };
476
508
  try {
477
509
  body = await c.req.json();
478
510
  } catch {
479
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
511
+ return c.json({ code: 400, message: invalidPushJsonMessage('push send') }, 400);
480
512
  }
481
513
 
482
514
  let { userId, payload } = body;
483
515
  if (!userId || typeof userId !== 'string') {
484
- return c.json({ code: 400, message: 'userId is required' }, 400);
516
+ return c.json({ code: 400, message: pushRequiredFieldMessage('userId', 'push send') }, 400);
485
517
  }
486
518
  if (!payload || typeof payload !== 'object') {
487
- return c.json({ code: 400, message: 'payload is required' }, 400);
519
+ return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'push send') }, 400);
488
520
  }
489
521
 
490
522
  ({ userId, payload } = await runBeforeSendHook(c, auth, {
@@ -495,12 +527,12 @@ pushRoute.openapi(pushSend, async (c) => {
495
527
  userId = asNonEmptyString(userId);
496
528
  payload = asPushPayload(payload);
497
529
  if (!userId || !payload) {
498
- return c.json({ code: 400, message: 'beforeSend must return a valid userId and payload' }, 400);
530
+ return c.json({ code: 400, message: 'push.hooks.beforeSend must return a valid userId and payload when overriding send-by-user delivery.' }, 400);
499
531
  }
500
532
 
501
533
  const payloadStr = JSON.stringify(payload);
502
534
  if (payloadStr.length > 4096) {
503
- return c.json({ code: 400, message: 'Payload exceeds 4KB limit' }, 400);
535
+ return c.json({ code: 400, message: 'Push payload exceeds the 4KB FCM limit for send-by-user delivery.' }, 400);
504
536
  }
505
537
 
506
538
  // Evaluate push send rule (if defined)
@@ -508,10 +540,10 @@ pushRoute.openapi(pushSend, async (c) => {
508
540
  if (sendRule) {
509
541
  try {
510
542
  if (!sendRule(auth, { userId })) {
511
- return c.json({ code: 403, message: 'Denied by push send rule' }, 403);
543
+ return c.json({ code: 403, message: pushRuleRejectedMessage(`user '${userId}'`) }, 403);
512
544
  }
513
545
  } catch {
514
- return c.json({ code: 403, message: 'Denied by push send rule' }, 403);
546
+ return c.json({ code: 403, message: pushRuleRejectedMessage(`user '${userId}'`) }, 403);
515
547
  }
516
548
  }
517
549
 
@@ -556,33 +588,33 @@ pushRoute.openapi(pushSendMany, async (c) => {
556
588
  buildConstraintCtx(c.env, c.req),
557
589
  );
558
590
  if (skResult === 'missing') {
559
- return c.json({ code: 403, message: 'Service Key required for push send' }, 403);
591
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send push notifications to multiple users') }, 403);
560
592
  }
561
593
  if (skResult === 'invalid') {
562
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
594
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('multi-user push send') }, 401);
563
595
  }
564
596
 
565
597
  const provider = createPushProvider(config.push, c.env);
566
598
  if (!provider) {
567
- return c.json({ code: 503, message: 'Push notifications are not configured. Add push.fcm config with FCM credentials.' }, 503);
599
+ return c.json({ code: 503, message: pushNotConfiguredMessage('send push notifications to multiple users') }, 503);
568
600
  }
569
601
 
570
602
  let body: { userIds?: string[]; payload?: PushPayload };
571
603
  try {
572
604
  body = await c.req.json();
573
605
  } catch {
574
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
606
+ return c.json({ code: 400, message: invalidPushJsonMessage('multi-user push send') }, 400);
575
607
  }
576
608
 
577
609
  let { userIds, payload } = body;
578
610
  if (!userIds || !Array.isArray(userIds) || userIds.length === 0) {
579
- return c.json({ code: 400, message: 'userIds array is required and must not be empty' }, 400);
611
+ return c.json({ code: 400, message: "Missing required field 'userIds' for multi-user push send, or the array was empty." }, 400);
580
612
  }
581
613
  if (userIds.length > 10000) {
582
- return c.json({ code: 400, message: 'userIds array must not exceed 10,000 items' }, 400);
614
+ return c.json({ code: 400, message: 'userIds for multi-user push send must not exceed 10,000 entries.' }, 400);
583
615
  }
584
616
  if (!payload || typeof payload !== 'object') {
585
- return c.json({ code: 400, message: 'payload is required' }, 400);
617
+ return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'multi-user push send') }, 400);
586
618
  }
587
619
 
588
620
  ({ userIds, payload } = await runBeforeSendHook(c, auth, {
@@ -593,12 +625,12 @@ pushRoute.openapi(pushSendMany, async (c) => {
593
625
  userIds = asStringArray(userIds);
594
626
  payload = asPushPayload(payload);
595
627
  if (!userIds || userIds.length === 0 || !payload) {
596
- return c.json({ code: 400, message: 'beforeSend must return userIds[] and payload' }, 400);
628
+ return c.json({ code: 400, message: 'push.hooks.beforeSend must return a non-empty userIds array and payload when overriding multi-user delivery.' }, 400);
597
629
  }
598
630
 
599
631
  const payloadStr = JSON.stringify(payload);
600
632
  if (payloadStr.length > 4096) {
601
- return c.json({ code: 400, message: 'Payload exceeds 4KB limit' }, 400);
633
+ return c.json({ code: 400, message: 'Push payload exceeds the 4KB FCM limit for multi-user delivery.' }, 400);
602
634
  }
603
635
 
604
636
  // Evaluate push send rule per userId (if defined)
@@ -613,7 +645,7 @@ pushRoute.openapi(pushSendMany, async (c) => {
613
645
  }
614
646
  });
615
647
  if (allowedUserIds.length === 0) {
616
- return c.json({ code: 403, message: 'Denied by push send rule' }, 403);
648
+ return c.json({ code: 403, message: pushRuleRejectedMessage('the requested user set') }, 403);
617
649
  }
618
650
  }
619
651
 
@@ -727,29 +759,29 @@ pushRoute.openapi(pushSendToToken, async (c) => {
727
759
  buildConstraintCtx(c.env, c.req),
728
760
  );
729
761
  if (skResult === 'missing') {
730
- return c.json({ code: 403, message: 'Service Key required for push send' }, 403);
762
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send a push notification to a token') }, 403);
731
763
  }
732
764
  if (skResult === 'invalid') {
733
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
765
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('direct token push send') }, 401);
734
766
  }
735
767
 
736
768
  const provider = createPushProvider(config.push, c.env);
737
769
  if (!provider) {
738
- return c.json({ code: 503, message: 'Push notifications are not configured. Add push.fcm config with FCM credentials.' }, 503);
770
+ return c.json({ code: 503, message: pushNotConfiguredMessage('send a push notification to a token') }, 503);
739
771
  }
740
772
 
741
773
  let body: { token?: string; platform?: string; payload?: PushPayload };
742
774
  try {
743
775
  body = await c.req.json();
744
776
  } catch {
745
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
777
+ return c.json({ code: 400, message: invalidPushJsonMessage('direct token push send') }, 400);
746
778
  }
747
779
 
748
780
  if (!body.token || typeof body.token !== 'string') {
749
- return c.json({ code: 400, message: 'token is required' }, 400);
781
+ return c.json({ code: 400, message: pushRequiredFieldMessage('token', 'direct token push send') }, 400);
750
782
  }
751
783
  if (!body.payload || typeof body.payload !== 'object') {
752
- return c.json({ code: 400, message: 'payload is required' }, 400);
784
+ return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'direct token push send') }, 400);
753
785
  }
754
786
 
755
787
  const hookInput = await runBeforeSendHook(c, auth, {
@@ -762,12 +794,12 @@ pushRoute.openapi(pushSendToToken, async (c) => {
762
794
  const platform = asNonEmptyString(hookInput.platform) ?? 'web';
763
795
  const payload = asPushPayload(hookInput.payload);
764
796
  if (!token || !payload) {
765
- return c.json({ code: 400, message: 'beforeSend must return token and payload' }, 400);
797
+ return c.json({ code: 400, message: 'push.hooks.beforeSend must return a token and payload when overriding direct token delivery.' }, 400);
766
798
  }
767
799
 
768
800
  const payloadStr = JSON.stringify(payload);
769
801
  if (payloadStr.length > 4096) {
770
- return c.json({ code: 400, message: 'Payload exceeds 4KB limit' }, 400);
802
+ return c.json({ code: 400, message: 'Push payload exceeds the 4KB FCM limit for direct token delivery.' }, 400);
771
803
  }
772
804
 
773
805
  const result = await provider.send({
@@ -852,29 +884,29 @@ pushRoute.openapi(pushSendToTopic, async (c) => {
852
884
  buildConstraintCtx(c.env, c.req),
853
885
  );
854
886
  if (skResult === 'missing') {
855
- return c.json({ code: 403, message: 'Service Key required for push send' }, 403);
887
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('send a push notification to a topic') }, 403);
856
888
  }
857
889
  if (skResult === 'invalid') {
858
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
890
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('topic push send') }, 401);
859
891
  }
860
892
 
861
893
  const provider = createPushProvider(config.push, c.env);
862
894
  if (!provider) {
863
- return c.json({ code: 503, message: 'Push notifications are not configured. Add push.fcm config with FCM credentials.' }, 503);
895
+ return c.json({ code: 503, message: pushNotConfiguredMessage('send a push notification to a topic') }, 503);
864
896
  }
865
897
 
866
898
  let body: { topic?: string; payload?: PushPayload };
867
899
  try {
868
900
  body = await c.req.json();
869
901
  } catch {
870
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
902
+ return c.json({ code: 400, message: invalidPushJsonMessage('topic push send') }, 400);
871
903
  }
872
904
 
873
905
  if (!body.topic || typeof body.topic !== 'string') {
874
- return c.json({ code: 400, message: 'topic is required' }, 400);
906
+ return c.json({ code: 400, message: pushRequiredFieldMessage('topic', 'topic push send') }, 400);
875
907
  }
876
908
  if (!body.payload || typeof body.payload !== 'object') {
877
- return c.json({ code: 400, message: 'payload is required' }, 400);
909
+ return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'topic push send') }, 400);
878
910
  }
879
911
 
880
912
  const hookInput = await runBeforeSendHook(c, auth, {
@@ -885,7 +917,7 @@ pushRoute.openapi(pushSendToTopic, async (c) => {
885
917
  const topic = asNonEmptyString(hookInput.topic);
886
918
  const payload = asPushPayload(hookInput.payload);
887
919
  if (!topic || !payload) {
888
- return c.json({ code: 400, message: 'beforeSend must return topic and payload' }, 400);
920
+ return c.json({ code: 400, message: 'push.hooks.beforeSend must return a topic and payload when overriding topic delivery.' }, 400);
889
921
  }
890
922
  const topicInput: PushSendInput = { kind: 'topic', topic, payload: payload as Record<string, unknown> };
891
923
  const result = await provider.sendToTopic(topic, payload);
@@ -943,26 +975,26 @@ pushRoute.openapi(pushBroadcast, async (c) => {
943
975
  buildConstraintCtx(c.env, c.req),
944
976
  );
945
977
  if (skResult === 'missing') {
946
- return c.json({ code: 403, message: 'Service Key required for push send' }, 403);
978
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('broadcast a push notification') }, 403);
947
979
  }
948
980
  if (skResult === 'invalid') {
949
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
981
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push broadcast') }, 401);
950
982
  }
951
983
 
952
984
  const provider = createPushProvider(config.push, c.env);
953
985
  if (!provider) {
954
- return c.json({ code: 503, message: 'Push notifications are not configured. Add push.fcm config with FCM credentials.' }, 503);
986
+ return c.json({ code: 503, message: pushNotConfiguredMessage('broadcast a push notification') }, 503);
955
987
  }
956
988
 
957
989
  let body: { payload?: PushPayload };
958
990
  try {
959
991
  body = await c.req.json();
960
992
  } catch {
961
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
993
+ return c.json({ code: 400, message: invalidPushJsonMessage('push broadcast') }, 400);
962
994
  }
963
995
 
964
996
  if (!body.payload || typeof body.payload !== 'object') {
965
- return c.json({ code: 400, message: 'payload is required' }, 400);
997
+ return c.json({ code: 400, message: pushRequiredFieldMessage('payload', 'push broadcast') }, 400);
966
998
  }
967
999
 
968
1000
  const hookInput = await runBeforeSendHook(c, auth, {
@@ -971,7 +1003,7 @@ pushRoute.openapi(pushBroadcast, async (c) => {
971
1003
  });
972
1004
  const payload = asPushPayload(hookInput.payload);
973
1005
  if (!payload) {
974
- return c.json({ code: 400, message: 'beforeSend must return payload' }, 400);
1006
+ return c.json({ code: 400, message: 'push.hooks.beforeSend must return a payload object when overriding broadcast delivery.' }, 400);
975
1007
  }
976
1008
  const broadcastInput: PushSendInput = { kind: 'broadcast', payload: payload as Record<string, unknown> };
977
1009
  const result = await provider.broadcast(payload);
@@ -1018,24 +1050,24 @@ const pushTopicSubscribe = createRoute({
1018
1050
  pushRoute.openapi(pushTopicSubscribe, async (c) => {
1019
1051
  const auth = c.get('auth' as never) as { id: string } | null | undefined;
1020
1052
  if (!auth?.id) {
1021
- return c.json({ code: 401, message: 'Authentication required' }, 401);
1053
+ return c.json({ code: 401, message: pushAuthRequiredMessage('subscribe push tokens to a topic') }, 401);
1022
1054
  }
1023
1055
 
1024
1056
  const config = parseConfig(c.env);
1025
1057
  const provider = createPushProvider(config.push, c.env);
1026
1058
  if (!provider) {
1027
- return c.json({ code: 503, message: 'Push notifications are not configured.' }, 503);
1059
+ return c.json({ code: 503, message: pushNotConfiguredMessage('subscribe push tokens to a topic') }, 503);
1028
1060
  }
1029
1061
 
1030
1062
  let body: { topic?: string };
1031
1063
  try {
1032
1064
  body = await c.req.json();
1033
1065
  } catch {
1034
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
1066
+ return c.json({ code: 400, message: invalidPushJsonMessage('push topic subscription') }, 400);
1035
1067
  }
1036
1068
 
1037
1069
  if (!body.topic || typeof body.topic !== 'string') {
1038
- return c.json({ code: 400, message: 'topic is required' }, 400);
1070
+ return c.json({ code: 400, message: pushRequiredFieldMessage('topic', 'push topic subscription') }, 400);
1039
1071
  }
1040
1072
 
1041
1073
  // Get user's devices and subscribe all tokens to the topic
@@ -1080,24 +1112,24 @@ const pushTopicUnsubscribe = createRoute({
1080
1112
  pushRoute.openapi(pushTopicUnsubscribe, async (c) => {
1081
1113
  const auth = c.get('auth' as never) as { id: string } | null | undefined;
1082
1114
  if (!auth?.id) {
1083
- return c.json({ code: 401, message: 'Authentication required' }, 401);
1115
+ return c.json({ code: 401, message: pushAuthRequiredMessage('unsubscribe push tokens from a topic') }, 401);
1084
1116
  }
1085
1117
 
1086
1118
  const config = parseConfig(c.env);
1087
1119
  const provider = createPushProvider(config.push, c.env);
1088
1120
  if (!provider) {
1089
- return c.json({ code: 503, message: 'Push notifications are not configured.' }, 503);
1121
+ return c.json({ code: 503, message: pushNotConfiguredMessage('unsubscribe push tokens from a topic') }, 503);
1090
1122
  }
1091
1123
 
1092
1124
  let body: { topic?: string };
1093
1125
  try {
1094
1126
  body = await c.req.json();
1095
1127
  } catch {
1096
- return c.json({ code: 400, message: 'Invalid JSON body' }, 400);
1128
+ return c.json({ code: 400, message: invalidPushJsonMessage('push topic unsubscription') }, 400);
1097
1129
  }
1098
1130
 
1099
1131
  if (!body.topic || typeof body.topic !== 'string') {
1100
- return c.json({ code: 400, message: 'topic is required' }, 400);
1132
+ return c.json({ code: 400, message: pushRequiredFieldMessage('topic', 'push topic unsubscription') }, 400);
1101
1133
  }
1102
1134
 
1103
1135
  const devices = await getDevicesForUser(await getPushTokenStore(c), auth.id);
@@ -1150,15 +1182,15 @@ pushRoute.openapi(pushLogsRoute, async (c) => {
1150
1182
  buildConstraintCtx(c.env, c.req),
1151
1183
  );
1152
1184
  if (skResult === 'missing') {
1153
- return c.json({ code: 403, message: 'Service Key required for push logs' }, 403);
1185
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('read push notification logs') }, 403);
1154
1186
  }
1155
1187
  if (skResult === 'invalid') {
1156
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
1188
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push log reads') }, 401);
1157
1189
  }
1158
1190
 
1159
1191
  const userId = c.req.query('userId');
1160
1192
  if (!userId) {
1161
- return c.json({ code: 400, message: 'userId query parameter is required' }, 400);
1193
+ return c.json({ code: 400, message: "Missing required query parameter 'userId' for push log reads." }, 400);
1162
1194
  }
1163
1195
 
1164
1196
  const limitStr = c.req.query('limit');
@@ -1201,15 +1233,15 @@ pushRoute.openapi(pushTokensRoute, async (c) => {
1201
1233
  buildConstraintCtx(c.env, c.req),
1202
1234
  );
1203
1235
  if (skResult === 'missing') {
1204
- return c.json({ code: 403, message: 'Service Key required for push tokens' }, 403);
1236
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('read registered push tokens') }, 403);
1205
1237
  }
1206
1238
  if (skResult === 'invalid') {
1207
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
1239
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push token reads') }, 401);
1208
1240
  }
1209
1241
 
1210
1242
  const userId = c.req.query('userId');
1211
1243
  if (!userId) {
1212
- return c.json({ code: 400, message: 'userId query parameter is required' }, 400);
1244
+ return c.json({ code: 400, message: "Missing required query parameter 'userId' for push token reads." }, 400);
1213
1245
  }
1214
1246
 
1215
1247
  const devices = await getDevicesForUser(await getPushTokenStore(c), userId);
@@ -1259,26 +1291,31 @@ pushRoute.openapi(putPushTokens, async (c) => {
1259
1291
  buildConstraintCtx(c.env, c.req),
1260
1292
  );
1261
1293
  if (skResult === 'missing') {
1262
- return c.json({ code: 403, message: 'Service Key required' }, 403);
1294
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('upsert a push token') }, 403);
1263
1295
  }
1264
1296
  if (skResult === 'invalid') {
1265
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
1297
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push token upsert') }, 401);
1266
1298
  }
1267
1299
 
1268
- const body = await c.req.json<{
1300
+ let body: {
1269
1301
  userId?: string;
1270
1302
  deviceId?: string;
1271
1303
  token?: string;
1272
1304
  platform?: string;
1273
1305
  deviceInfo?: { name?: string; osVersion?: string; appVersion?: string; locale?: string };
1274
1306
  metadata?: Record<string, unknown>;
1275
- }>();
1307
+ };
1308
+ try {
1309
+ body = await c.req.json();
1310
+ } catch {
1311
+ return c.json({ code: 400, message: invalidPushJsonMessage('push token upsert') }, 400);
1312
+ }
1276
1313
  if (!body.userId || !body.deviceId || !body.token || !body.platform) {
1277
- return c.json({ code: 400, message: 'userId, deviceId, token, and platform are required' }, 400);
1314
+ return c.json({ code: 400, message: "Missing required fields for push token upsert. Expected 'userId', 'deviceId', 'token', and 'platform'." }, 400);
1278
1315
  }
1279
1316
 
1280
1317
  if (metadataExceedsByteLimit(body.metadata)) {
1281
- return c.json({ code: 400, message: `metadata exceeds ${MAX_METADATA_BYTES} byte limit` }, 400);
1318
+ return c.json({ code: 400, message: pushMetadataTooLargeMessage('push token upsert') }, 400);
1282
1319
  }
1283
1320
 
1284
1321
  await registerToken(
@@ -1344,29 +1381,34 @@ pushRoute.openapi(patchPushTokens, async (c) => {
1344
1381
  buildConstraintCtx(c.env, c.req),
1345
1382
  );
1346
1383
  if (skResult === 'missing') {
1347
- return c.json({ code: 403, message: 'Service Key required' }, 403);
1384
+ return c.json({ code: 403, message: pushServiceKeyRequiredMessage('update push token metadata') }, 403);
1348
1385
  }
1349
1386
  if (skResult === 'invalid') {
1350
- return c.json({ code: 401, message: 'Unauthorized. Invalid Service Key.' }, 401);
1387
+ return c.json({ code: 401, message: pushInvalidServiceKeyMessage('push token metadata updates') }, 401);
1351
1388
  }
1352
1389
 
1353
- const body = await c.req.json<{ userId?: string; deviceId?: string; metadata?: Record<string, unknown> }>();
1390
+ let body: { userId?: string; deviceId?: string; metadata?: Record<string, unknown> };
1391
+ try {
1392
+ body = await c.req.json();
1393
+ } catch {
1394
+ return c.json({ code: 400, message: invalidPushJsonMessage('push token metadata update') }, 400);
1395
+ }
1354
1396
  if (!body.userId || !body.deviceId) {
1355
- return c.json({ code: 400, message: 'userId and deviceId are required' }, 400);
1397
+ return c.json({ code: 400, message: "Missing required fields for push token metadata update. Expected 'userId' and 'deviceId'." }, 400);
1356
1398
  }
1357
1399
  if (!body.metadata) {
1358
- return c.json({ code: 400, message: 'metadata is required' }, 400);
1400
+ return c.json({ code: 400, message: pushRequiredFieldMessage('metadata', 'push token metadata update') }, 400);
1359
1401
  }
1360
1402
 
1361
1403
  if (metadataExceedsByteLimit(body.metadata)) {
1362
- return c.json({ code: 400, message: `metadata exceeds ${MAX_METADATA_BYTES} byte limit` }, 400);
1404
+ return c.json({ code: 400, message: pushMetadataTooLargeMessage('push token metadata update') }, 400);
1363
1405
  }
1364
1406
 
1365
1407
  const pushStore = await getPushTokenStore(c);
1366
1408
  const devices = await getDevicesForUser(pushStore, body.userId);
1367
1409
  const device = devices.find(d => d.deviceId === body.deviceId);
1368
1410
  if (!device) {
1369
- return c.json({ code: 404, message: 'Device not found' }, 404);
1411
+ return c.json({ code: 404, message: `Push device '${body.deviceId}' for user '${body.userId}' was not found.` }, 404);
1370
1412
  }
1371
1413
 
1372
1414
  device.metadata = body.metadata;