@edge-base/server 0.1.5 → 0.2.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.
Files changed (100) hide show
  1. package/admin-build/_app/immutable/assets/19.4Si2ZFC_.css +1 -0
  2. package/admin-build/_app/immutable/assets/{3.Dg81Pgmd.css → 3.BtHYobTg.css} +1 -1
  3. package/admin-build/_app/immutable/assets/SqlEditor.Bbp1RIk0.css +1 -0
  4. package/admin-build/_app/immutable/assets/TableSqlTab.yeNZfhgG.css +1 -0
  5. package/admin-build/_app/immutable/chunks/B0QyxC2M.js +128 -0
  6. package/admin-build/_app/immutable/chunks/{Bsp3uE8m.js → BCKr7yKd.js} +1 -1
  7. package/admin-build/_app/immutable/chunks/{CWHVkYdi.js → BFs_qStz.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{CKIubXVC.js → BTJcQFEp.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/BY07qVPA.js +1 -0
  10. package/admin-build/_app/immutable/chunks/{Cn5ZQY9O.js → BcIUK2sk.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/{CKFIU4S8.js → BsFiK_FJ.js} +1 -1
  12. package/admin-build/_app/immutable/chunks/{COYZ6F_d.js → CSGrwS7E.js} +1 -1
  13. package/admin-build/_app/immutable/chunks/{DLc6L4xD.js → CqUxCvs_.js} +1 -1
  14. package/admin-build/_app/immutable/chunks/{DLTo2HQr.js → D-x55wdW.js} +1 -1
  15. package/admin-build/_app/immutable/chunks/D755Tqat.js +1 -0
  16. package/admin-build/_app/immutable/chunks/{Jxx0jGlP.js → DjOEv9M9.js} +1 -1
  17. package/admin-build/_app/immutable/chunks/{DLRcaFHo.js → DnLqc9L1.js} +1 -1
  18. package/admin-build/_app/immutable/chunks/{BnlxEqP4.js → Dqk2TGNU.js} +1 -1
  19. package/admin-build/_app/immutable/chunks/{7FYfV8UU.js → k0CIJkw4.js} +1 -1
  20. package/admin-build/_app/immutable/chunks/lSpxLU5p.js +2 -0
  21. package/admin-build/_app/immutable/chunks/m9QZTyVV.js +1 -0
  22. package/admin-build/_app/immutable/entry/{app.C4kLStKR.js → app.BTsq3_xq.js} +2 -2
  23. package/admin-build/_app/immutable/entry/start.zXCirpgY.js +1 -0
  24. package/admin-build/_app/immutable/nodes/0.BZ00WDYH.js +1 -0
  25. package/admin-build/_app/immutable/nodes/{1.s1kW8gyv.js → 1.RzSJ3yyr.js} +1 -1
  26. package/admin-build/_app/immutable/nodes/{10.Cr9ml-GD.js → 10.D-rsiquF.js} +1 -1
  27. package/admin-build/_app/immutable/nodes/{11.DXTOyMEn.js → 11.l7-bgtFD.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/{12.DzhitECA.js → 12.Dkq0H7B5.js} +1 -1
  29. package/admin-build/_app/immutable/nodes/{13.U_1VNA5x.js → 13.DtK_4oRz.js} +1 -1
  30. package/admin-build/_app/immutable/nodes/{14.x0SzmV0P.js → 14.BKo7-AMx.js} +1 -1
  31. package/admin-build/_app/immutable/nodes/{15.BJI1Z1hk.js → 15.CQAj_6lq.js} +1 -1
  32. package/admin-build/_app/immutable/nodes/{16.CUvRFqLW.js → 16.XVIG-Ffr.js} +1 -1
  33. package/admin-build/_app/immutable/nodes/{17.sDFD-Vbm.js → 17.g6raZLCM.js} +1 -1
  34. package/admin-build/_app/immutable/nodes/{18.DdoTnAXc.js → 18.IQz6a3T6.js} +1 -1
  35. package/admin-build/_app/immutable/nodes/19.CAAZ8i8h.js +2 -0
  36. package/admin-build/_app/immutable/nodes/{20.fxudyPKp.js → 20.BPcX3KPj.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/21.DoPabrY_.js +1 -0
  38. package/admin-build/_app/immutable/nodes/{22.CY6ICoyn.js → 22.Br5AG_5Z.js} +1 -1
  39. package/admin-build/_app/immutable/nodes/{23.DmEaHsZw.js → 23.KjbrdXoE.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/{24.Cqerdln2.js → 24.C3n2-hgw.js} +1 -1
  41. package/admin-build/_app/immutable/nodes/25.SFDSBzHd.js +2 -0
  42. package/admin-build/_app/immutable/nodes/26.D95vui6E.js +1 -0
  43. package/admin-build/_app/immutable/nodes/{27.DAeKhnG9.js → 27.FgLgdjwB.js} +1 -1
  44. package/admin-build/_app/immutable/nodes/{28.D_lJZpNR.js → 28.B9sYYm1F.js} +1 -1
  45. package/admin-build/_app/immutable/nodes/{29.Dx7iQCpT.js → 29.DyqZ_wbN.js} +1 -1
  46. package/admin-build/_app/immutable/nodes/3.Bzo2yVIO.js +2 -0
  47. package/admin-build/_app/immutable/nodes/{30.DcO-bjQZ.js → 30.c1CiNwiS.js} +1 -1
  48. package/admin-build/_app/immutable/nodes/{31.DesWAzIF.js → 31.CXty66Vh.js} +1 -1
  49. package/admin-build/_app/immutable/nodes/{4.B2rm4_4a.js → 4.BgQaXZ27.js} +1 -1
  50. package/admin-build/_app/immutable/nodes/{5.4Os1aYeW.js → 5.BuJrHvxH.js} +1 -1
  51. package/admin-build/_app/immutable/nodes/{6.BzR--5Dj.js → 6.CkBBC94k.js} +1 -1
  52. package/admin-build/_app/immutable/nodes/{7.QDC-47H1.js → 7.D2YBvNFM.js} +1 -1
  53. package/admin-build/_app/immutable/nodes/{8.DDPWEc96.js → 8.D8qQWo_z.js} +1 -1
  54. package/admin-build/_app/immutable/nodes/{9.fyfVIJPa.js → 9.BLDLX5hV.js} +1 -1
  55. package/admin-build/_app/version.json +1 -1
  56. package/admin-build/index.html +7 -7
  57. package/openapi.json +6710 -5866
  58. package/package.json +2 -2
  59. package/src/__tests__/functions-route.test.ts +153 -0
  60. package/src/__tests__/internal-request.test.ts +5 -5
  61. package/src/__tests__/meta-export-coverage.test.ts +3 -2
  62. package/src/__tests__/meta-route-registration.test.ts +3 -2
  63. package/src/__tests__/openapi-coverage.test.ts +5 -1
  64. package/src/__tests__/pagination.test.ts +12 -8
  65. package/src/__tests__/postgres-dialect.test.ts +2 -2
  66. package/src/__tests__/query.test.ts +7 -7
  67. package/src/__tests__/rate-limit.test.ts +0 -1
  68. package/src/__tests__/room-handler-context.test.ts +31 -0
  69. package/src/__tests__/room-runtime-routing.test.ts +48 -0
  70. package/src/__tests__/runtime-surface-accounting.test.ts +5 -4
  71. package/src/__tests__/smoke-skip-report.test.ts +3 -2
  72. package/src/durable-objects/database-do.ts +9 -4
  73. package/src/durable-objects/database-live-do.ts +22 -10
  74. package/src/durable-objects/logs-do.ts +2 -2
  75. package/src/durable-objects/rooms-do.ts +202 -0
  76. package/src/lib/auth-d1-service.ts +1 -1
  77. package/src/lib/auth-d1.ts +10 -0
  78. package/src/lib/d1-handler.ts +23 -4
  79. package/src/lib/internal-request.ts +5 -8
  80. package/src/lib/openapi.ts +1 -0
  81. package/src/lib/pagination.ts +3 -3
  82. package/src/lib/postgres-handler.ts +2 -2
  83. package/src/lib/query-engine.ts +2 -2
  84. package/src/middleware/rate-limit.ts +11 -11
  85. package/src/routes/admin.ts +30 -3
  86. package/src/routes/auth.ts +74 -33
  87. package/src/routes/room.ts +42 -0
  88. package/src/types.ts +6 -0
  89. package/admin-build/_app/immutable/assets/TableSqlTab.BHquaMBM.css +0 -1
  90. package/admin-build/_app/immutable/chunks/BPXFNSAT.js +0 -128
  91. package/admin-build/_app/immutable/chunks/DKjA1S1a.js +0 -1
  92. package/admin-build/_app/immutable/chunks/fBE8lw-R.js +0 -1
  93. package/admin-build/_app/immutable/chunks/gE9fQ_Ff.js +0 -2
  94. package/admin-build/_app/immutable/entry/start.CAAH6ztW.js +0 -1
  95. package/admin-build/_app/immutable/nodes/0.BywaJfpH.js +0 -1
  96. package/admin-build/_app/immutable/nodes/19.CUvRFqLW.js +0 -1
  97. package/admin-build/_app/immutable/nodes/21.N2QN0lbw.js +0 -1
  98. package/admin-build/_app/immutable/nodes/25.x3hL7Y-O.js +0 -2
  99. package/admin-build/_app/immutable/nodes/26.CEzfjTSO.js +0 -1
  100. package/admin-build/_app/immutable/nodes/3.CKC_yDnF.js +0 -2
@@ -29,7 +29,7 @@ import {
29
29
  } from '../lib/auth-redirect.js';
30
30
  import {
31
31
  signAccessToken, signRefreshToken, verifyRefreshTokenWithFallback,
32
- parseDuration, decodeTokenUnsafe, TokenExpiredError,
32
+ parseDuration, TokenExpiredError,
33
33
  } from '../lib/jwt.js';
34
34
  import { generateId } from '../lib/uuid.js';
35
35
  import { captchaMiddleware } from '../middleware/captcha-verify.js';
@@ -108,6 +108,15 @@ authRoute.onError((err, c) => {
108
108
 
109
109
  // ─── Helpers ───
110
110
 
111
+ function isReleaseMode(env: Env): boolean {
112
+ return parseConfig(env)?.release ?? false;
113
+ }
114
+
115
+ function shouldExposeAuthTestSecrets(env: Env): boolean {
116
+ return env.EDGEBASE_TEST === '1'
117
+ || env.EDGEBASE_TEST === 'true';
118
+ }
119
+
111
120
  function requireAuth(auth: AuthContext | null): string {
112
121
  if (!auth) {
113
122
  throw new EdgeBaseError(401, 'Authentication required.', undefined, 'unauthenticated');
@@ -500,6 +509,13 @@ async function createSessionAndTokens(
500
509
  metadata,
501
510
  });
502
511
 
512
+ // Update lastSignedInAt on the user record
513
+ try {
514
+ await db.run('UPDATE _users SET lastSignedInAt = ? WHERE id = ?', [now, userId]);
515
+ } catch {
516
+ // Non-critical: don't block sign-in if this column doesn't exist yet
517
+ }
518
+
503
519
  return { accessToken, refreshToken, sessionId };
504
520
  }
505
521
 
@@ -1288,6 +1304,7 @@ authRoute.openapi(signinMagicLink, async (c) => {
1288
1304
  const record = await lookupEmail(getAuthDb(c), body.email);
1289
1305
 
1290
1306
  const db = getAuthDb(c);
1307
+ const exposeTestSecrets = shouldExposeAuthTestSecrets(c.env);
1291
1308
  let debugToken: string | undefined;
1292
1309
  let debugActionUrl: string | undefined;
1293
1310
 
@@ -1327,9 +1344,12 @@ authRoute.openapi(signinMagicLink, async (c) => {
1327
1344
  type: 'magic-link',
1328
1345
  state: redirect.state,
1329
1346
  });
1347
+ if (exposeTestSecrets) {
1348
+ debugToken = token;
1349
+ debugActionUrl = magicLinkUrl;
1350
+ }
1330
1351
  if (!provider) {
1331
- const release = config?.release ?? false;
1332
- if (!release) {
1352
+ if (!isReleaseMode(c.env)) {
1333
1353
  console.warn('[MagicLink] Email provider not configured. Token:', token);
1334
1354
  debugToken = token;
1335
1355
  debugActionUrl = magicLinkUrl;
@@ -1417,6 +1437,10 @@ authRoute.openapi(signinMagicLink, async (c) => {
1417
1437
  type: 'magic-link',
1418
1438
  state: redirect.state,
1419
1439
  });
1440
+ if (exposeTestSecrets) {
1441
+ debugToken = token;
1442
+ debugActionUrl = magicLinkUrl;
1443
+ }
1420
1444
  if (provider) {
1421
1445
  const locale = resolveEmailLocale(c.env, reqLocale);
1422
1446
  const html = renderMagicLink({
@@ -1431,8 +1455,7 @@ authRoute.openapi(signinMagicLink, async (c) => {
1431
1455
  resolveSubject(c.env, 'magicLink', defaultSubject, locale), html, locale,
1432
1456
  ).catch(() => {});
1433
1457
  } else {
1434
- const release = config?.release ?? false;
1435
- if (!release) {
1458
+ if (!isReleaseMode(c.env)) {
1436
1459
  debugToken = token;
1437
1460
  debugActionUrl = magicLinkUrl;
1438
1461
  }
@@ -1591,6 +1614,7 @@ authRoute.openapi(signinPhone, async (c) => {
1591
1614
  // Look up phone in D1
1592
1615
  const record = await lookupPhone(getAuthDb(c), phone);
1593
1616
 
1617
+ const exposeTestSecrets = shouldExposeAuthTestSecrets(c.env);
1594
1618
  let devCode: string | undefined;
1595
1619
 
1596
1620
  const db = getAuthDb(c);
@@ -1602,6 +1626,7 @@ authRoute.openapi(signinPhone, async (c) => {
1602
1626
  if (!user) return c.json({ ok: true });
1603
1627
 
1604
1628
  const code = generateOTP();
1629
+ if (exposeTestSecrets) devCode = code;
1605
1630
 
1606
1631
  // Store OTP in KV with 5 min TTL
1607
1632
  await c.env.KV.put(
@@ -1623,8 +1648,7 @@ authRoute.openapi(signinPhone, async (c) => {
1623
1648
  `Your ${appName} verification code is: ${code}. Valid for 5 minutes.`,
1624
1649
  );
1625
1650
  } else {
1626
- const release = parseConfig(c.env)?.release ?? false;
1627
- if (!release) {
1651
+ if (!isReleaseMode(c.env)) {
1628
1652
  console.warn('[Phone] SMS provider not configured. OTP:', code);
1629
1653
  devCode = code;
1630
1654
  }
@@ -1654,6 +1678,7 @@ authRoute.openapi(signinPhone, async (c) => {
1654
1678
  await authService.updateUser(db, userId, { phone, phoneVerified: false });
1655
1679
 
1656
1680
  const code = generateOTP();
1681
+ if (exposeTestSecrets) devCode = code;
1657
1682
 
1658
1683
  // Store OTP in KV
1659
1684
  await c.env.KV.put(
@@ -1675,8 +1700,7 @@ authRoute.openapi(signinPhone, async (c) => {
1675
1700
  `Your ${appName} verification code is: ${code}. Valid for 5 minutes.`,
1676
1701
  );
1677
1702
  } else {
1678
- const release = parseConfig(c.env)?.release ?? false;
1679
- if (!release) {
1703
+ if (!isReleaseMode(c.env)) {
1680
1704
  console.warn('[Phone] SMS provider not configured. OTP:', code);
1681
1705
  devCode = code;
1682
1706
  }
@@ -1690,8 +1714,7 @@ authRoute.openapi(signinPhone, async (c) => {
1690
1714
  }
1691
1715
 
1692
1716
  // Return OTP code only in dev mode (SMS provider not configured) for testing
1693
- const release = parseConfig(c.env)?.release ?? false;
1694
- return c.json(devCode && !release ? { ok: true, code: devCode } : { ok: true });
1717
+ return c.json(devCode ? { ok: true, code: devCode } : { ok: true });
1695
1718
  });
1696
1719
 
1697
1720
  // POST /verify-phone — verify OTP → create session
@@ -1871,6 +1894,7 @@ authRoute.openapi(linkPhone, async (c) => {
1871
1894
  }
1872
1895
 
1873
1896
  const code = generateOTP();
1897
+ const exposeTestSecrets = shouldExposeAuthTestSecrets(c.env);
1874
1898
 
1875
1899
  // Store link OTP in KV (separate key pattern)
1876
1900
  await c.env.KV.put(
@@ -1891,14 +1915,13 @@ authRoute.openapi(linkPhone, async (c) => {
1891
1915
  phone,
1892
1916
  `Your ${appName} phone linking code is: ${code}. Valid for 5 minutes.`,
1893
1917
  );
1894
- return c.json({ ok: true });
1918
+ return c.json(exposeTestSecrets ? { ok: true, code } : { ok: true });
1895
1919
  } else {
1896
- const release = parseConfig(c.env)?.release ?? false;
1897
- if (!release) {
1920
+ if (!isReleaseMode(c.env)) {
1898
1921
  console.warn('[Phone] SMS provider not configured. Link OTP:', code);
1899
1922
  return c.json({ ok: true, code });
1900
1923
  }
1901
- return c.json({ ok: true });
1924
+ return c.json(exposeTestSecrets ? { ok: true, code } : { ok: true });
1902
1925
  }
1903
1926
  });
1904
1927
 
@@ -2048,6 +2071,7 @@ authRoute.openapi(signinEmailOtp, async (c) => {
2048
2071
  // Look up email in D1
2049
2072
  const record = await lookupEmail(getAuthDb(c), email);
2050
2073
 
2074
+ const exposeTestSecrets = shouldExposeAuthTestSecrets(c.env);
2051
2075
  let devCode: string | undefined;
2052
2076
 
2053
2077
  const db = getAuthDb(c);
@@ -2059,6 +2083,7 @@ authRoute.openapi(signinEmailOtp, async (c) => {
2059
2083
  if (!user) return c.json({ ok: true });
2060
2084
 
2061
2085
  const code = generateOTP();
2086
+ if (exposeTestSecrets) devCode = code;
2062
2087
 
2063
2088
  // Store OTP in KV with 5 min TTL
2064
2089
  await c.env.KV.put(
@@ -2079,8 +2104,7 @@ authRoute.openapi(signinEmailOtp, async (c) => {
2079
2104
  resolveSubject(c.env, 'emailOtp', defaultSubject, locale), html, locale,
2080
2105
  );
2081
2106
  } else {
2082
- const release = parseConfig(c.env)?.release ?? false;
2083
- if (!release) {
2107
+ if (!isReleaseMode(c.env)) {
2084
2108
  console.warn('[EmailOTP] Email provider not configured. OTP:', code);
2085
2109
  devCode = code;
2086
2110
  }
@@ -2117,6 +2141,7 @@ authRoute.openapi(signinEmailOtp, async (c) => {
2117
2141
  });
2118
2142
 
2119
2143
  const code = generateOTP();
2144
+ if (exposeTestSecrets) devCode = code;
2120
2145
 
2121
2146
  // Store OTP in KV
2122
2147
  await c.env.KV.put(
@@ -2137,8 +2162,7 @@ authRoute.openapi(signinEmailOtp, async (c) => {
2137
2162
  resolveSubject(c.env, 'emailOtp', defaultSubject, locale), html, locale,
2138
2163
  );
2139
2164
  } else {
2140
- const release = parseConfig(c.env)?.release ?? false;
2141
- if (!release) {
2165
+ if (!isReleaseMode(c.env)) {
2142
2166
  console.warn('[EmailOTP] Email provider not configured. OTP:', code);
2143
2167
  devCode = code;
2144
2168
  }
@@ -2152,8 +2176,7 @@ authRoute.openapi(signinEmailOtp, async (c) => {
2152
2176
  }
2153
2177
 
2154
2178
  // Return OTP code only in dev mode (email provider not configured) for testing
2155
- const release = parseConfig(c.env)?.release ?? false;
2156
- return c.json(devCode && !release ? { ok: true, code: devCode } : { ok: true });
2179
+ return c.json(devCode ? { ok: true, code: devCode } : { ok: true });
2157
2180
  });
2158
2181
 
2159
2182
  // POST /verify-email-otp — verify OTP → create session
@@ -2754,11 +2777,27 @@ authRoute.openapi(signout, async (c) => {
2754
2777
  refreshToken: body.refreshToken,
2755
2778
  });
2756
2779
 
2757
- const payload = decodeTokenUnsafe(body.refreshToken);
2758
- if (!payload?.sub) throw new EdgeBaseError(401, 'Invalid refresh token.', undefined, 'invalid-refresh-token');
2759
-
2760
2780
  const db = getAuthDb(c);
2761
- const userId = payload.sub as string;
2781
+ let tokenPayload;
2782
+ try {
2783
+ tokenPayload = await verifyRefreshTokenWithFallback(
2784
+ body.refreshToken,
2785
+ getUserSecret(c.env),
2786
+ c.env.JWT_USER_SECRET_OLD,
2787
+ c.env.JWT_USER_SECRET_OLD_AT,
2788
+ );
2789
+ } catch (err) {
2790
+ if (err instanceof TokenExpiredError) {
2791
+ throw new EdgeBaseError(401, 'Refresh token expired.', undefined, 'refresh-token-expired');
2792
+ }
2793
+ throw new EdgeBaseError(401, 'Invalid refresh token.', undefined, 'invalid-refresh-token');
2794
+ }
2795
+
2796
+ const userId = tokenPayload.sub;
2797
+ const sessionResult = await authService.getSessionByRefreshToken(db, body.refreshToken, userId);
2798
+ if (!sessionResult) {
2799
+ throw new EdgeBaseError(401, 'Invalid refresh token.', undefined, 'invalid-refresh-token');
2800
+ }
2762
2801
 
2763
2802
  // beforeSignOut hook — blocking
2764
2803
  await executeAuthHook(c.env, c.executionCtx, 'beforeSignOut', { userId }, { blocking: true, workerUrl: getWorkerUrl(c.req.url, c.env) });
@@ -2991,8 +3030,8 @@ authRoute.openapi(changeEmail, async (c) => {
2991
3030
  }
2992
3031
  }
2993
3032
 
2994
- const release = parseConfig(c.env)?.release ?? false;
2995
- if (!release) {
3033
+ const exposeTestSecrets = shouldExposeAuthTestSecrets(c.env);
3034
+ if (exposeTestSecrets || !isReleaseMode(c.env)) {
2996
3035
  const emailCfg = getEmailConfig(c.env);
2997
3036
  const fallbackVerifyUrl = emailCfg?.emailChangeUrl
2998
3037
  ? emailCfg.emailChangeUrl.replace('{token}', token)
@@ -3970,8 +4009,7 @@ authRoute.openapi(requestEmailVerification, async (c) => {
3970
4009
 
3971
4010
  const provider = createEmailProvider(getEmailConfig(c.env), c.env);
3972
4011
  if (!provider) {
3973
- const release = parseConfig(c.env)?.release ?? false;
3974
- if (!release) {
4012
+ if (shouldExposeAuthTestSecrets(c.env) || !isReleaseMode(c.env)) {
3975
4013
  console.warn('[VerifyEmail] Email provider not configured. Verification email not sent. Token:', token);
3976
4014
  return c.json({ ok: true, message: 'Email provider not configured.', token, actionUrl: verifyUrl });
3977
4015
  }
@@ -3992,7 +4030,9 @@ authRoute.openapi(requestEmailVerification, async (c) => {
3992
4030
  resolveSubject(c.env, 'verification', defaultSubject, locale), html, locale,
3993
4031
  );
3994
4032
 
3995
- return c.json({ ok: result.success, messageId: result.messageId });
4033
+ return c.json(shouldExposeAuthTestSecrets(c.env)
4034
+ ? { ok: result.success, messageId: result.messageId, token, actionUrl: verifyUrl }
4035
+ : { ok: result.success, messageId: result.messageId });
3996
4036
  });
3997
4037
 
3998
4038
  // POST /verify-email — KV token→shardId lookup → direct Shard call
@@ -4123,8 +4163,7 @@ authRoute.openapi(requestPasswordReset, async (c) => {
4123
4163
 
4124
4164
  const provider = createEmailProvider(getEmailConfig(c.env), c.env);
4125
4165
  if (!provider) {
4126
- const release = parseConfig(c.env)?.release ?? false;
4127
- if (!release) {
4166
+ if (shouldExposeAuthTestSecrets(c.env) || !isReleaseMode(c.env)) {
4128
4167
  console.warn('[Auth] Email provider not configured. Reset email not sent. Token:', token);
4129
4168
  return c.json({ ok: true, message: 'Email provider not configured.', token, actionUrl: resetUrl });
4130
4169
  }
@@ -4145,7 +4184,9 @@ authRoute.openapi(requestPasswordReset, async (c) => {
4145
4184
  resolveSubject(c.env, 'passwordReset', defaultSubject, locale), html, locale,
4146
4185
  );
4147
4186
 
4148
- return c.json({ ok: result.success, messageId: result.messageId });
4187
+ return c.json(shouldExposeAuthTestSecrets(c.env)
4188
+ ? { ok: result.success, messageId: result.messageId, token, actionUrl: resetUrl }
4189
+ : { ok: result.success, messageId: result.messageId });
4149
4190
  });
4150
4191
 
4151
4192
  // POST /reset-password — KV token→shardId lookup → direct Shard call
@@ -70,6 +70,12 @@ const roomRealtimeCreateSessionBodySchema = z.object({
70
70
  thirdparty: z.boolean().optional().openapi({ description: 'Forward Cloudflare Realtime thirdparty mode' }),
71
71
  sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
72
72
  });
73
+ const roomCloudflareRealtimeKitCreateSessionBodySchema = z.object({
74
+ connectionId: z.string().optional().openapi({ description: 'Specific room connection ID to bind the Cloudflare RealtimeKit participant to' }),
75
+ customParticipantId: z.string().optional().openapi({ description: 'Optional custom participant identifier for the provisioned RealtimeKit participant' }),
76
+ name: z.string().optional().openapi({ description: 'Optional display name for the provisioned RealtimeKit participant' }),
77
+ picture: z.string().optional().openapi({ description: 'Optional avatar URL for the provisioned RealtimeKit participant' }),
78
+ });
73
79
  const roomRealtimeCreateSessionResponseSchema = z.object({
74
80
  sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
75
81
  sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
@@ -78,6 +84,15 @@ const roomRealtimeCreateSessionResponseSchema = z.object({
78
84
  connectionId: z.string().optional().openapi({ description: 'Room connection ID associated with the session' }),
79
85
  reused: z.boolean().optional().openapi({ description: 'Whether an existing provider session was reused' }),
80
86
  });
87
+ const roomCloudflareRealtimeKitCreateSessionResponseSchema = z.object({
88
+ sessionId: z.string().openapi({ description: 'Cloudflare RealtimeKit participant ID' }),
89
+ meetingId: z.string().openapi({ description: 'Cloudflare RealtimeKit meeting ID backing the room session' }),
90
+ participantId: z.string().openapi({ description: 'Cloudflare RealtimeKit participant ID' }),
91
+ authToken: z.string().openapi({ description: 'RealtimeKit auth token for the provisioned participant' }),
92
+ presetName: z.string().optional().openapi({ description: 'RealtimeKit preset used for the provisioned participant' }),
93
+ connectionId: z.string().optional().openapi({ description: 'Room connection ID associated with the session' }),
94
+ reused: z.boolean().optional().openapi({ description: 'Whether an existing provider participant was reused' }),
95
+ });
81
96
  const roomRealtimeSessionStateSchema = z.object({
82
97
  sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
83
98
  connectionId: z.string().optional().openapi({ description: 'Room connection ID associated with the session' }),
@@ -525,6 +540,27 @@ const createRoomRealtimeSession = createRoute({
525
540
  },
526
541
  });
527
542
 
543
+ const createRoomCloudflareRealtimeKitSession = createRoute({
544
+ operationId: 'createRoomCloudflareRealtimeKitSession',
545
+ method: 'post',
546
+ path: '/media/cloudflare_realtimekit/session',
547
+ tags: ['client'],
548
+ summary: 'Create a room Cloudflare RealtimeKit session',
549
+ description: 'Creates a Cloudflare RealtimeKit session for the authenticated room member.',
550
+ request: {
551
+ query: roomQuerySchema,
552
+ body: { content: { 'application/json': { schema: roomCloudflareRealtimeKitCreateSessionBodySchema } }, required: false },
553
+ },
554
+ responses: {
555
+ 200: { description: 'Cloudflare RealtimeKit session created', content: { 'application/json': { schema: roomCloudflareRealtimeKitCreateSessionResponseSchema } } },
556
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
557
+ 401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
558
+ 403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
559
+ 404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
560
+ 409: { description: 'Conflicting existing published media', content: { 'application/json': { schema: errorResponseSchema } } },
561
+ },
562
+ });
563
+
528
564
  const createRoomRealtimeIceServers = createRoute({
529
565
  operationId: 'createRoomRealtimeIceServers',
530
566
  method: 'post',
@@ -637,3 +673,9 @@ roomRoute.openapi(closeRoomRealtimeTracks, async (c) =>
637
673
  requireAuth: true,
638
674
  validatedJson: c.req.valid('json'),
639
675
  }));
676
+
677
+ roomRoute.openapi(createRoomCloudflareRealtimeKitSession, async (c) =>
678
+ proxyRoomDoRequest(c, '/media/cloudflare_realtimekit/session', 'POST', {
679
+ requireAuth: true,
680
+ validatedJson: c.req.valid('json'),
681
+ }));
package/src/types.ts CHANGED
@@ -68,6 +68,8 @@ export interface Env {
68
68
  CAPTCHA_SITE_KEY?: string;
69
69
  /** Cloudflare Realtime app ID for SFU session control. */
70
70
  CF_REALTIME_APP_ID?: string;
71
+ /** Cloudflare RealtimeKit preset name used when creating participant tokens. */
72
+ CF_REALTIME_PRESET_NAME?: string;
71
73
  /** Cloudflare Realtime app secret for SFU session control. */
72
74
  CF_REALTIME_APP_SECRET?: string;
73
75
  /** Optional override for the Cloudflare Realtime API base URL. */
@@ -92,6 +94,10 @@ export interface Env {
92
94
  EDGEBASE_EMAIL_API_URL?: string;
93
95
  /** Optional override for SMS delivery in deployed/local mock environments. */
94
96
  EDGEBASE_SMS_API_URL?: string;
97
+ /** Test-only flag set by wrangler.test.toml for SDK E2E flows. */
98
+ EDGEBASE_TEST?: string;
99
+ /** Test-only flag that prefers the bundled test config at startup. */
100
+ EDGEBASE_USE_TEST_CONFIG?: string;
95
101
 
96
102
  // ─── Dev Mode ───
97
103
  /** Schema Editor sidecar port — set by CLI dev command via --var */
@@ -1 +0,0 @@
1
- .sql-editor-wrap.svelte-392xt8{border:1px solid var(--color-border);border-radius:var(--radius-md);overflow:hidden}.sql-editor-wrap.svelte-392xt8 .cm-editor{min-height:120px}.sql-editor-wrap.svelte-392xt8 .cm-editor.cm-focused{border-color:var(--color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-primary) 20%,transparent)}.table-sql.svelte-1syrthj{display:flex;flex-direction:column;gap:var(--space-4)}.table-sql__toolbar.svelte-1syrthj{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}.table-sql__target.svelte-1syrthj{display:inline-flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-secondary);font-size:12px}.table-sql__target-label.svelte-1syrthj{color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.04em}.table-sql__target.svelte-1syrthj code:where(.svelte-1syrthj){font-family:var(--font-mono);color:var(--color-text)}.table-sql__shortcut.svelte-1syrthj{font-size:12px;color:var(--color-text-tertiary);margin-left:auto}.table-sql__result-tabs.svelte-1syrthj{display:flex;flex-wrap:wrap;gap:var(--space-2)}.table-sql__result-tab.svelte-1syrthj{display:inline-flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-secondary);color:var(--color-text-secondary);cursor:pointer}.table-sql__result-tab--active.svelte-1syrthj{border-color:var(--color-primary);color:var(--color-primary)}.table-sql__result-open.svelte-1syrthj{border:none;background:transparent;color:inherit;cursor:pointer;padding:0;font:inherit}.table-sql__result-close.svelte-1syrthj{border:none;background:transparent;color:inherit;cursor:pointer;padding:0;line-height:1}.table-sql__result-panel.svelte-1syrthj{border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-4);background:var(--color-bg)}.table-sql__result-meta.svelte-1syrthj{margin-bottom:var(--space-3);font-size:12px;color:var(--color-text-secondary)}.table-sql__result-error.svelte-1syrthj{color:var(--color-danger, #ef4444)}.table-sql__error-block.svelte-1syrthj,.table-sql__empty.svelte-1syrthj{margin:0;padding:var(--space-4);border-radius:var(--radius-md);background:var(--color-bg-secondary);color:var(--color-text-secondary);font-size:13px}