@edge-base/server 0.1.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 (309) hide show
  1. package/admin-build/.gitkeep +0 -0
  2. package/admin-build/_app/env.js +1 -0
  3. package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
  4. package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
  5. package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
  6. package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
  7. package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
  8. package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
  9. package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
  10. package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
  11. package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
  12. package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
  13. package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
  14. package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
  15. package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
  16. package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
  17. package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
  18. package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
  19. package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
  20. package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
  21. package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
  22. package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
  23. package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
  24. package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
  25. package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
  26. package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
  27. package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
  28. package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
  29. package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
  30. package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
  31. package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
  32. package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
  33. package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
  34. package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
  35. package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
  36. package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
  37. package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
  38. package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
  39. package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
  40. package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
  41. package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
  42. package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
  43. package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
  44. package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
  45. package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
  46. package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
  47. package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
  48. package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
  49. package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
  50. package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
  51. package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
  52. package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
  53. package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
  54. package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
  55. package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
  56. package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
  57. package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
  58. package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
  59. package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
  60. package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
  61. package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
  62. package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
  63. package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
  64. package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
  65. package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
  66. package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
  67. package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
  68. package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
  69. package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
  70. package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
  71. package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
  72. package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
  73. package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
  74. package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
  75. package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
  76. package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
  77. package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
  78. package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
  79. package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
  80. package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
  81. package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
  82. package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
  83. package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
  84. package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
  85. package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
  86. package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
  87. package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
  88. package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
  89. package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
  90. package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
  91. package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
  92. package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
  93. package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
  94. package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
  95. package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
  96. package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
  97. package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
  98. package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
  99. package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
  100. package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
  101. package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
  102. package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
  103. package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
  104. package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
  105. package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
  106. package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
  107. package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
  108. package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
  109. package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
  110. package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
  111. package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
  112. package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
  113. package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
  114. package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
  115. package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
  116. package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
  117. package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
  118. package/admin-build/_app/version.json +1 -0
  119. package/admin-build/favicon.svg +26 -0
  120. package/admin-build/index.html +45 -0
  121. package/openapi.json +19543 -0
  122. package/package.json +66 -0
  123. package/src/__tests__/admin-assets.test.ts +55 -0
  124. package/src/__tests__/admin-data-routes.test.ts +488 -0
  125. package/src/__tests__/admin-db-target.test.ts +103 -0
  126. package/src/__tests__/admin-routing.test.ts +31 -0
  127. package/src/__tests__/admin-user-management.test.ts +311 -0
  128. package/src/__tests__/analytics-query.test.ts +75 -0
  129. package/src/__tests__/auth-d1.test.ts +749 -0
  130. package/src/__tests__/auth-db-adapter.test.ts +73 -0
  131. package/src/__tests__/auth-jwt.test.ts +440 -0
  132. package/src/__tests__/auth-oauth.test.ts +389 -0
  133. package/src/__tests__/auth-password.test.ts +367 -0
  134. package/src/__tests__/auth-redirect.test.ts +87 -0
  135. package/src/__tests__/backup-restore.test.ts +711 -0
  136. package/src/__tests__/broadcast.test.ts +128 -0
  137. package/src/__tests__/cli.test.ts +178 -0
  138. package/src/__tests__/cloudflare-realtime.test.ts +113 -0
  139. package/src/__tests__/config.test.ts +469 -0
  140. package/src/__tests__/cors.test.ts +154 -0
  141. package/src/__tests__/cron.test.ts +302 -0
  142. package/src/__tests__/d1-handler.test.ts +402 -0
  143. package/src/__tests__/d1-sql.test.ts +120 -0
  144. package/src/__tests__/database-live-config.test.ts +42 -0
  145. package/src/__tests__/database-live-emitter.test.ts +56 -0
  146. package/src/__tests__/database-live-filters.test.ts +63 -0
  147. package/src/__tests__/database-live-route.test.ts +113 -0
  148. package/src/__tests__/db-sql.test.ts +163 -0
  149. package/src/__tests__/do-lifecycle.test.ts +263 -0
  150. package/src/__tests__/do-router.test.ts +729 -0
  151. package/src/__tests__/email-provider.test.ts +128 -0
  152. package/src/__tests__/email-templates.test.ts +528 -0
  153. package/src/__tests__/error-format.test.ts +250 -0
  154. package/src/__tests__/field-ops.test.ts +242 -0
  155. package/src/__tests__/functions-context.test.ts +334 -0
  156. package/src/__tests__/functions-d1-proxy.test.ts +229 -0
  157. package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
  158. package/src/__tests__/functions-route.test.ts +139 -0
  159. package/src/__tests__/internal-request.test.ts +77 -0
  160. package/src/__tests__/log-writer.test.ts +44 -0
  161. package/src/__tests__/logger.test.ts +58 -0
  162. package/src/__tests__/meta-admin-proxy.test.ts +48 -0
  163. package/src/__tests__/meta-export-coverage.test.ts +191 -0
  164. package/src/__tests__/meta-route-registration.test.ts +47 -0
  165. package/src/__tests__/namespace-dump.test.ts +28 -0
  166. package/src/__tests__/oauth-providers.test.ts +337 -0
  167. package/src/__tests__/openapi-coverage.test.ts +144 -0
  168. package/src/__tests__/pagination.test.ts +59 -0
  169. package/src/__tests__/password-policy.test.ts +191 -0
  170. package/src/__tests__/plugin-migrations.test.ts +379 -0
  171. package/src/__tests__/postgres-batch-compat.test.ts +133 -0
  172. package/src/__tests__/postgres-dialect.test.ts +328 -0
  173. package/src/__tests__/postgres-executor.test.ts +79 -0
  174. package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
  175. package/src/__tests__/postgres-schema-init.test.ts +105 -0
  176. package/src/__tests__/postgres-table-utils.test.ts +107 -0
  177. package/src/__tests__/presence.test.ts +199 -0
  178. package/src/__tests__/provider.test.ts +550 -0
  179. package/src/__tests__/public-user-profile.test.ts +339 -0
  180. package/src/__tests__/push-handlers.test.ts +179 -0
  181. package/src/__tests__/push-provider.test.ts +80 -0
  182. package/src/__tests__/push-token.test.ts +418 -0
  183. package/src/__tests__/query.test.ts +771 -0
  184. package/src/__tests__/rate-limit.test.ts +260 -0
  185. package/src/__tests__/room-access-policy.test.ts +101 -0
  186. package/src/__tests__/room-handler-context.test.ts +130 -0
  187. package/src/__tests__/room-monitoring.test.ts +138 -0
  188. package/src/__tests__/room-runtime-routing.test.ts +222 -0
  189. package/src/__tests__/room.test.ts +254 -0
  190. package/src/__tests__/route-parser.test.ts +490 -0
  191. package/src/__tests__/rules.test.ts +234 -0
  192. package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
  193. package/src/__tests__/scheduled.test.ts +80 -0
  194. package/src/__tests__/schema.test.ts +1273 -0
  195. package/src/__tests__/security-hardening.test.ts +312 -0
  196. package/src/__tests__/server.unit.test.ts +333 -0
  197. package/src/__tests__/service-key-db-proxy.test.ts +650 -0
  198. package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
  199. package/src/__tests__/service-key.test.ts +757 -0
  200. package/src/__tests__/smoke-skip-report.test.ts +72 -0
  201. package/src/__tests__/sms-provider.test.ts +39 -0
  202. package/src/__tests__/sql-route.test.ts +218 -0
  203. package/src/__tests__/storage-hook-context.test.ts +115 -0
  204. package/src/__tests__/totp.test.ts +200 -0
  205. package/src/__tests__/uuid.test.ts +144 -0
  206. package/src/__tests__/validation.test.ts +773 -0
  207. package/src/__tests__/websocket-pending.test.ts +163 -0
  208. package/src/_functions-registry.ts +51 -0
  209. package/src/bench-entry.ts +9 -0
  210. package/src/cloudflare-test.d.ts +1 -0
  211. package/src/durable-objects/auth-do.ts +49 -0
  212. package/src/durable-objects/database-do.ts +2240 -0
  213. package/src/durable-objects/database-live-do.ts +949 -0
  214. package/src/durable-objects/logs-do.ts +1200 -0
  215. package/src/durable-objects/room-runtime-base.ts +1604 -0
  216. package/src/durable-objects/rooms-do.ts +2191 -0
  217. package/src/generated-config.ts +6 -0
  218. package/src/index.ts +382 -0
  219. package/src/lib/admin-assets.ts +54 -0
  220. package/src/lib/admin-db-target.ts +301 -0
  221. package/src/lib/admin-routing.ts +35 -0
  222. package/src/lib/admin-user-management.ts +464 -0
  223. package/src/lib/analytics-adapter.ts +103 -0
  224. package/src/lib/analytics-query.ts +579 -0
  225. package/src/lib/auth-d1-service.ts +1193 -0
  226. package/src/lib/auth-d1.ts +1056 -0
  227. package/src/lib/auth-db-adapter.ts +289 -0
  228. package/src/lib/auth-redirect.ts +116 -0
  229. package/src/lib/cidr.ts +115 -0
  230. package/src/lib/client-ip.ts +51 -0
  231. package/src/lib/cloudflare-realtime.ts +251 -0
  232. package/src/lib/control-db.ts +36 -0
  233. package/src/lib/cron.ts +163 -0
  234. package/src/lib/d1-handler.ts +1425 -0
  235. package/src/lib/d1-schema-init.ts +255 -0
  236. package/src/lib/d1-sql.ts +33 -0
  237. package/src/lib/database-live-config.ts +24 -0
  238. package/src/lib/database-live-emitter.ts +111 -0
  239. package/src/lib/db-sql.ts +66 -0
  240. package/src/lib/do-retry.ts +36 -0
  241. package/src/lib/do-router.ts +270 -0
  242. package/src/lib/do-sql.ts +73 -0
  243. package/src/lib/email-provider.ts +379 -0
  244. package/src/lib/email-templates.ts +285 -0
  245. package/src/lib/email-translations.ts +422 -0
  246. package/src/lib/errors.ts +151 -0
  247. package/src/lib/functions.ts +2091 -0
  248. package/src/lib/hono.ts +56 -0
  249. package/src/lib/internal-request.ts +56 -0
  250. package/src/lib/jwt.ts +354 -0
  251. package/src/lib/log-writer.ts +272 -0
  252. package/src/lib/namespace-dump.ts +125 -0
  253. package/src/lib/oauth-providers.ts +1225 -0
  254. package/src/lib/op-parser.ts +99 -0
  255. package/src/lib/openapi.ts +146 -0
  256. package/src/lib/pagination.ts +19 -0
  257. package/src/lib/password-policy.ts +102 -0
  258. package/src/lib/password.ts +145 -0
  259. package/src/lib/plugin-migrations.ts +612 -0
  260. package/src/lib/postgres-executor.ts +203 -0
  261. package/src/lib/postgres-handler.ts +1102 -0
  262. package/src/lib/postgres-schema-init.ts +341 -0
  263. package/src/lib/postgres-table-utils.ts +87 -0
  264. package/src/lib/public-user-profile.ts +187 -0
  265. package/src/lib/push-provider.ts +409 -0
  266. package/src/lib/push-token.ts +294 -0
  267. package/src/lib/query-engine.ts +768 -0
  268. package/src/lib/room-monitoring.ts +97 -0
  269. package/src/lib/room-runtime.ts +14 -0
  270. package/src/lib/route-parser.ts +434 -0
  271. package/src/lib/schema.ts +538 -0
  272. package/src/lib/schemas.ts +152 -0
  273. package/src/lib/service-key.ts +419 -0
  274. package/src/lib/sms-provider.ts +230 -0
  275. package/src/lib/startup-config.ts +99 -0
  276. package/src/lib/totp.ts +242 -0
  277. package/src/lib/uuid.ts +87 -0
  278. package/src/lib/validation.ts +205 -0
  279. package/src/lib/version.ts +2 -0
  280. package/src/lib/websocket-pending.ts +40 -0
  281. package/src/middleware/auth.ts +169 -0
  282. package/src/middleware/captcha-verify.ts +217 -0
  283. package/src/middleware/cors.ts +159 -0
  284. package/src/middleware/error-handler.ts +54 -0
  285. package/src/middleware/internal-guard.ts +26 -0
  286. package/src/middleware/logger.ts +126 -0
  287. package/src/middleware/rate-limit.ts +283 -0
  288. package/src/middleware/rules.ts +475 -0
  289. package/src/routes/admin-auth.ts +447 -0
  290. package/src/routes/admin.ts +3501 -0
  291. package/src/routes/analytics-api.ts +290 -0
  292. package/src/routes/auth.ts +4222 -0
  293. package/src/routes/backup.ts +1466 -0
  294. package/src/routes/config.ts +53 -0
  295. package/src/routes/d1.ts +109 -0
  296. package/src/routes/database-live.ts +281 -0
  297. package/src/routes/functions.ts +155 -0
  298. package/src/routes/health.ts +32 -0
  299. package/src/routes/kv.ts +167 -0
  300. package/src/routes/oauth.ts +1055 -0
  301. package/src/routes/push.ts +1465 -0
  302. package/src/routes/room.ts +639 -0
  303. package/src/routes/schema-endpoint.ts +76 -0
  304. package/src/routes/sql.ts +176 -0
  305. package/src/routes/storage.ts +1674 -0
  306. package/src/routes/tables.ts +699 -0
  307. package/src/routes/users.ts +21 -0
  308. package/src/routes/vectorize.ts +372 -0
  309. package/src/types.ts +99 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * auth-db-adapter.ts — Abstraction layer for Auth database access.
3
+ *
4
+ * Provides a unified interface for D1 (SQLite) and PostgreSQL backends.
5
+ * All auth-service functions use AuthDb instead of D1Database directly,
6
+ * enabling transparent provider switching via config.auth.provider.
7
+ *
8
+ * Key differences handled by the adapter:
9
+ * - D1 uses `?` bind params, PostgreSQL uses `$1, $2, ...`
10
+ * - D1 `db.batch()` is atomic, PostgreSQL uses `BEGIN/COMMIT`
11
+ * - D1 `.all()` returns `{ results: T[] }`, pg `.query()` returns `{ rows: T[] }`
12
+ */
13
+ import { Client } from 'pg';
14
+ import { parseConfig } from './do-router.js';
15
+
16
+ // ─── Interface ───
17
+
18
+ export type AuthDbDialect = 'sqlite' | 'postgres';
19
+
20
+ export interface AuthDb {
21
+ /** The SQL dialect (sqlite for D1, postgres for Neon/PostgreSQL). */
22
+ readonly dialect: AuthDbDialect;
23
+
24
+ /**
25
+ * Execute a query and return all matching rows.
26
+ * SQL uses `?` placeholders — the adapter converts to `$1, $2, ...` for PostgreSQL.
27
+ */
28
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
29
+
30
+ /**
31
+ * Execute a query and return the first row, or null.
32
+ */
33
+ first<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null>;
34
+
35
+ /**
36
+ * Execute a statement that doesn't return rows (INSERT, UPDATE, DELETE).
37
+ */
38
+ run(sql: string, params?: unknown[]): Promise<void>;
39
+
40
+ /**
41
+ * Execute multiple statements atomically.
42
+ * D1: db.batch([...])
43
+ * PostgreSQL: BEGIN; ...; COMMIT;
44
+ */
45
+ batch(statements: { sql: string; params?: unknown[] }[]): Promise<void>;
46
+ }
47
+
48
+ // ─── D1 Implementation ───
49
+
50
+ export class D1AuthDb implements AuthDb {
51
+ readonly dialect: AuthDbDialect = 'sqlite';
52
+
53
+ constructor(private readonly db: D1Database) {}
54
+
55
+ async query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]> {
56
+ const stmt = params && params.length > 0
57
+ ? this.db.prepare(sql).bind(...params)
58
+ : this.db.prepare(sql);
59
+ const result = await stmt.all();
60
+ return (result.results ?? []) as T[];
61
+ }
62
+
63
+ async first<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null> {
64
+ const stmt = params && params.length > 0
65
+ ? this.db.prepare(sql).bind(...params)
66
+ : this.db.prepare(sql);
67
+ const row = await stmt.first();
68
+ return (row ?? null) as T | null;
69
+ }
70
+
71
+ async run(sql: string, params?: unknown[]): Promise<void> {
72
+ const stmt = params && params.length > 0
73
+ ? this.db.prepare(sql).bind(...params)
74
+ : this.db.prepare(sql);
75
+ await stmt.run();
76
+ }
77
+
78
+ async batch(statements: { sql: string; params?: unknown[] }[]): Promise<void> {
79
+ if (statements.length === 0) return;
80
+ await this.db.batch(
81
+ statements.map((s) =>
82
+ s.params && s.params.length > 0
83
+ ? this.db.prepare(s.sql).bind(...s.params)
84
+ : this.db.prepare(s.sql),
85
+ ),
86
+ );
87
+ }
88
+ }
89
+
90
+ // ─── PostgreSQL Implementation ───
91
+
92
+ /**
93
+ * Convert `?` placeholders to `$1, $2, ...` for PostgreSQL.
94
+ * Handles `?` inside single-quoted strings by skipping them.
95
+ */
96
+ function sqliteToPostgresParams(sql: string): string {
97
+ let idx = 0;
98
+ let inString = false;
99
+ let result = '';
100
+
101
+ for (let i = 0; i < sql.length; i++) {
102
+ const ch = sql[i];
103
+
104
+ // Track single-quoted strings
105
+ if (ch === "'") {
106
+ if (inString && sql[i + 1] === "'") {
107
+ // Escaped quote ''
108
+ result += "''";
109
+ i++;
110
+ continue;
111
+ }
112
+ inString = !inString;
113
+ result += ch;
114
+ continue;
115
+ }
116
+
117
+ if (ch === '?' && !inString) {
118
+ idx++;
119
+ result += `$${idx}`;
120
+ } else {
121
+ result += ch;
122
+ }
123
+ }
124
+
125
+ return result;
126
+ }
127
+
128
+ /**
129
+ * Convert SQLite-specific SQL constructs to PostgreSQL equivalents.
130
+ * - `INSERT OR IGNORE` → `INSERT ... ON CONFLICT DO NOTHING`
131
+ * - `INSERT OR REPLACE` → `INSERT ... ON CONFLICT (pk) DO UPDATE SET ...`
132
+ */
133
+ export function adaptSqlDialect(sql: string): string {
134
+ // 1. INSERT OR IGNORE → INSERT ... ON CONFLICT DO NOTHING
135
+ const ignoreAdapted = sql.replace(
136
+ /INSERT\s+OR\s+IGNORE\s+INTO/gi,
137
+ 'INSERT INTO',
138
+ );
139
+
140
+ if (ignoreAdapted !== sql) {
141
+ let result = ignoreAdapted.trimEnd();
142
+ if (!result.endsWith(';')) {
143
+ result += ' ON CONFLICT DO NOTHING';
144
+ }
145
+ return result;
146
+ }
147
+
148
+ // 2. INSERT OR REPLACE → INSERT ... ON CONFLICT (pk) DO UPDATE SET ...
149
+ const replaceAdapted = sql.replace(
150
+ /INSERT\s+OR\s+REPLACE\s+INTO/gi,
151
+ 'INSERT INTO',
152
+ );
153
+
154
+ if (replaceAdapted !== sql) {
155
+ // Extract column names: INSERT INTO "table" ("col1", "col2", ...) VALUES (...)
156
+ const colMatch = replaceAdapted.match(/INTO\s+"[^"]+"\s*\(([^)]+)\)/i);
157
+ if (colMatch) {
158
+ const cols = colMatch[1].split(',').map(c => c.trim().replace(/"/g, ''));
159
+ // First column is the PK (id, email, token, key, etc.)
160
+ const pk = cols[0];
161
+ const updateCols = cols.filter(c => c !== pk);
162
+
163
+ let result = replaceAdapted.trimEnd().replace(/;\s*$/, '');
164
+ if (updateCols.length > 0) {
165
+ const setClause = updateCols.map(c => `"${c}" = EXCLUDED."${c}"`).join(', ');
166
+ result += ` ON CONFLICT ("${pk}") DO UPDATE SET ${setClause}`;
167
+ } else {
168
+ result += ' ON CONFLICT DO NOTHING';
169
+ }
170
+ return result;
171
+ }
172
+ return replaceAdapted;
173
+ }
174
+
175
+ return sql;
176
+ }
177
+
178
+ export class PgAuthDb implements AuthDb {
179
+ readonly dialect: AuthDbDialect = 'postgres';
180
+
181
+ constructor(private readonly connectionString: string) {}
182
+
183
+ private adaptSql(sql: string): string {
184
+ return sqliteToPostgresParams(adaptSqlDialect(sql));
185
+ }
186
+
187
+ private async withClient<T>(fn: (client: Client) => Promise<T>): Promise<T> {
188
+ const client = new Client({ connectionString: this.connectionString });
189
+ try {
190
+ await client.connect();
191
+ return await fn(client);
192
+ } finally {
193
+ await client.end();
194
+ }
195
+ }
196
+
197
+ async query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]> {
198
+ return this.withClient(async (client) => {
199
+ const result = await client.query(this.adaptSql(sql), params ?? []);
200
+ return result.rows as T[];
201
+ });
202
+ }
203
+
204
+ async first<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null> {
205
+ return this.withClient(async (client) => {
206
+ const result = await client.query(this.adaptSql(sql), params ?? []);
207
+ return (result.rows[0] ?? null) as T | null;
208
+ });
209
+ }
210
+
211
+ async run(sql: string, params?: unknown[]): Promise<void> {
212
+ await this.withClient(async (client) => {
213
+ await client.query(this.adaptSql(sql), params ?? []);
214
+ });
215
+ }
216
+
217
+ async batch(statements: { sql: string; params?: unknown[] }[]): Promise<void> {
218
+ if (statements.length === 0) return;
219
+ await this.withClient(async (client) => {
220
+ await client.query('BEGIN');
221
+ try {
222
+ for (const stmt of statements) {
223
+ await client.query(this.adaptSql(stmt.sql), stmt.params ?? []);
224
+ }
225
+ await client.query('COMMIT');
226
+ } catch (err) {
227
+ await client.query('ROLLBACK');
228
+ throw err;
229
+ }
230
+ });
231
+ }
232
+ }
233
+
234
+ // ─── Factory ───
235
+
236
+ /**
237
+ * Resolve the Hyperdrive binding name for auth PostgreSQL.
238
+ * Convention: AUTH_POSTGRES
239
+ * The .env secret key: AUTH_POSTGRES_URL (or user-specified connectionString value)
240
+ */
241
+ export function getAuthPostgresBindingName(): string {
242
+ return 'AUTH_POSTGRES';
243
+ }
244
+
245
+ /**
246
+ * Create an AuthDb instance from the environment and config.
247
+ * - Default / provider='d1' → D1AuthDb using env.AUTH_DB
248
+ * - provider='neon'|'postgres' → PgAuthDb using Hyperdrive connection string
249
+ *
250
+ * When authProvider / connectionStringKey are omitted, the adapter resolves them
251
+ * from config.auth.provider and config.auth.connectionString.
252
+ */
253
+ export function resolveAuthDb(env: Record<string, unknown>, authProvider?: string, connectionStringKey?: string): AuthDb {
254
+ const config = parseConfig(env);
255
+ const provider = authProvider ?? config.auth?.provider ?? 'd1';
256
+ const resolvedConnectionStringKey = connectionStringKey ?? config.auth?.connectionString ?? 'AUTH_POSTGRES_URL';
257
+
258
+ if (provider === 'd1') {
259
+ const d1 = env.AUTH_DB as D1Database | undefined;
260
+ if (!d1) {
261
+ throw new Error('AUTH_DB D1 binding is not available in the environment.');
262
+ }
263
+ return new D1AuthDb(d1);
264
+ }
265
+
266
+ if (provider === 'neon' || provider === 'postgres') {
267
+ // Resolve Hyperdrive connection string
268
+ const bindingName = getAuthPostgresBindingName();
269
+ const hyperdrive = env[bindingName] as { connectionString?: string } | undefined;
270
+
271
+ if (hyperdrive?.connectionString) {
272
+ return new PgAuthDb(hyperdrive.connectionString);
273
+ }
274
+
275
+ // Fallback: direct connection string from env (local dev)
276
+ const envKey = resolvedConnectionStringKey;
277
+ const connStr = env[envKey] as string | undefined;
278
+ if (connStr) {
279
+ return new PgAuthDb(connStr);
280
+ }
281
+
282
+ throw new Error(
283
+ `Auth provider '${provider}' requires a PostgreSQL connection. ` +
284
+ `Expected Hyperdrive binding '${bindingName}' or env variable '${envKey}'.`,
285
+ );
286
+ }
287
+
288
+ throw new Error(`Unknown auth provider: '${provider}'.`);
289
+ }
@@ -0,0 +1,116 @@
1
+ import { EdgeBaseError } from '@edge-base/shared';
2
+ import { parseConfig } from './do-router.js';
3
+ import type { Env } from '../types.js';
4
+
5
+ export interface ClientRedirectInput {
6
+ redirectUrl?: string | null;
7
+ state?: string | null;
8
+ }
9
+
10
+ export interface ParsedClientRedirect {
11
+ redirectUrl: string | null;
12
+ state: string | null;
13
+ }
14
+
15
+ function normalizeUrl(value: string): string {
16
+ try {
17
+ return new URL(value).toString();
18
+ } catch {
19
+ throw new EdgeBaseError(400, 'Invalid redirect_url.');
20
+ }
21
+ }
22
+
23
+ function isAllowedRedirect(candidate: string, pattern: string): boolean {
24
+ const trimmed = pattern.trim();
25
+ if (!trimmed) return false;
26
+
27
+ if (trimmed.endsWith('*')) {
28
+ return candidate.startsWith(trimmed.slice(0, -1));
29
+ }
30
+
31
+ let allowedUrl: URL;
32
+ let candidateUrl: URL;
33
+ try {
34
+ allowedUrl = new URL(trimmed);
35
+ candidateUrl = new URL(candidate);
36
+ } catch {
37
+ return false;
38
+ }
39
+
40
+ // Origin-wide allowlist entry: https://app.example.com
41
+ if (allowedUrl.pathname === '/' && !allowedUrl.search && !allowedUrl.hash) {
42
+ return allowedUrl.origin === candidateUrl.origin;
43
+ }
44
+
45
+ return allowedUrl.toString() === candidateUrl.toString();
46
+ }
47
+
48
+ function getAllowedRedirectUrls(env: Env): string[] {
49
+ const config = parseConfig(env);
50
+ const entries = config?.auth?.allowedRedirectUrls;
51
+ if (!Array.isArray(entries)) return [];
52
+ return entries.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0);
53
+ }
54
+
55
+ export function appendRedirectParams(
56
+ redirectUrl: string,
57
+ params: Record<string, string | undefined | null>,
58
+ ): string {
59
+ const url = new URL(redirectUrl);
60
+ const fragmentParams = new URLSearchParams();
61
+ for (const [key, value] of Object.entries(params)) {
62
+ if (value !== undefined && value !== null && value !== '') {
63
+ fragmentParams.set(key, value);
64
+ }
65
+ }
66
+ const fragment = fragmentParams.toString();
67
+ if (fragment) {
68
+ url.hash = fragment;
69
+ }
70
+ return url.toString();
71
+ }
72
+
73
+ export function parseClientRedirectUrl(env: Env, value: string | null | undefined): string | null {
74
+ if (!value) return null;
75
+ const normalized = normalizeUrl(value);
76
+ const allowed = getAllowedRedirectUrls(env);
77
+ if (allowed.length > 0 && !allowed.some((pattern) => isAllowedRedirect(normalized, pattern))) {
78
+ throw new EdgeBaseError(400, 'redirect_url is not allowed.');
79
+ }
80
+ return normalized;
81
+ }
82
+
83
+ export function parseClientRedirectState(value: string | null | undefined): string | null {
84
+ if (value === undefined || value === null || value === '') return null;
85
+ if (typeof value !== 'string') {
86
+ throw new EdgeBaseError(400, 'Invalid state.');
87
+ }
88
+ if (value.length > 1024) {
89
+ throw new EdgeBaseError(400, 'state must not exceed 1024 characters.');
90
+ }
91
+ return value;
92
+ }
93
+
94
+ export function parseClientRedirectInput(env: Env, input: ClientRedirectInput | null | undefined): ParsedClientRedirect {
95
+ return {
96
+ redirectUrl: parseClientRedirectUrl(env, input?.redirectUrl),
97
+ state: parseClientRedirectState(input?.state),
98
+ };
99
+ }
100
+
101
+ export function buildEmailActionUrl(options: {
102
+ redirectUrl: string | null;
103
+ fallbackUrl: string;
104
+ token: string;
105
+ type: string;
106
+ state?: string | null;
107
+ }): string {
108
+ if (!options.redirectUrl) {
109
+ return options.fallbackUrl;
110
+ }
111
+ return appendRedirectParams(options.redirectUrl, {
112
+ token: options.token,
113
+ type: options.type,
114
+ state: options.state ?? null,
115
+ });
116
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Pure IPv4/IPv6 CIDR matching utility.
3
+ * No external dependencies — suitable for Cloudflare Workers runtime.
4
+ *
5
+ * @module cidr
6
+ */
7
+
8
+ /**
9
+ * Check if an IP address falls within a CIDR range.
10
+ * Supports IPv4 (e.g. '10.0.0.0/8') and IPv6 (e.g. '2001:db8::/32').
11
+ *
12
+ * Returns false for invalid inputs (malformed IP or CIDR).
13
+ */
14
+ export function isIpInCidr(ip: string, cidr: string): boolean {
15
+ const slashIdx = cidr.indexOf('/');
16
+ if (slashIdx === -1) return false;
17
+
18
+ const cidrIp = cidr.substring(0, slashIdx);
19
+ const prefixLen = parseInt(cidr.substring(slashIdx + 1), 10);
20
+ if (isNaN(prefixLen) || prefixLen < 0) return false;
21
+
22
+ // Determine IP version
23
+ const isIPv6Input = ip.includes(':');
24
+ const isIPv6Cidr = cidrIp.includes(':');
25
+
26
+ // Must be same version
27
+ if (isIPv6Input !== isIPv6Cidr) return false;
28
+
29
+ if (isIPv6Input) {
30
+ if (prefixLen > 128) return false;
31
+ const ipBytes = parseIPv6(ip);
32
+ const cidrBytes = parseIPv6(cidrIp);
33
+ if (!ipBytes || !cidrBytes) return false;
34
+ return matchesPrefixBits(ipBytes, cidrBytes, prefixLen);
35
+ } else {
36
+ if (prefixLen > 32) return false;
37
+ const ipBytes = parseIPv4(ip);
38
+ const cidrBytes = parseIPv4(cidrIp);
39
+ if (!ipBytes || !cidrBytes) return false;
40
+ return matchesPrefixBits(ipBytes, cidrBytes, prefixLen);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Parse an IPv4 address string to a 4-byte Uint8Array.
46
+ * Returns null for invalid input.
47
+ */
48
+ function parseIPv4(ip: string): Uint8Array | null {
49
+ const parts = ip.split('.');
50
+ if (parts.length !== 4) return null;
51
+
52
+ const bytes = new Uint8Array(4);
53
+ for (let i = 0; i < 4; i++) {
54
+ const n = parseInt(parts[i], 10);
55
+ if (isNaN(n) || n < 0 || n > 255 || parts[i] !== String(n)) return null;
56
+ bytes[i] = n;
57
+ }
58
+ return bytes;
59
+ }
60
+
61
+ /**
62
+ * Parse an IPv6 address string to a 16-byte Uint8Array.
63
+ * Handles '::' shorthand expansion.
64
+ * Returns null for invalid input.
65
+ */
66
+ function parseIPv6(ip: string): Uint8Array | null {
67
+ // Handle :: expansion
68
+ let halves: string[];
69
+ if (ip.includes('::')) {
70
+ const [left, right] = ip.split('::');
71
+ if (ip.indexOf('::', ip.indexOf('::') + 2) !== -1) return null; // multiple ::
72
+ const leftGroups = left ? left.split(':') : [];
73
+ const rightGroups = right ? right.split(':') : [];
74
+ const missing = 8 - leftGroups.length - rightGroups.length;
75
+ if (missing < 0) return null;
76
+ halves = [...leftGroups, ...Array(missing).fill('0'), ...rightGroups];
77
+ } else {
78
+ halves = ip.split(':');
79
+ }
80
+
81
+ if (halves.length !== 8) return null;
82
+
83
+ const bytes = new Uint8Array(16);
84
+ for (let i = 0; i < 8; i++) {
85
+ const val = parseInt(halves[i], 16);
86
+ if (isNaN(val) || val < 0 || val > 0xffff) return null;
87
+ // Validate hex string (no invalid chars)
88
+ if (!/^[0-9a-fA-F]{1,4}$/.test(halves[i]) && halves[i] !== '0') return null;
89
+ bytes[i * 2] = (val >> 8) & 0xff;
90
+ bytes[i * 2 + 1] = val & 0xff;
91
+ }
92
+ return bytes;
93
+ }
94
+
95
+ /**
96
+ * Compare the first `prefixLen` bits of two byte arrays.
97
+ * Returns true if they are identical in the prefix range.
98
+ */
99
+ function matchesPrefixBits(a: Uint8Array, b: Uint8Array, prefixLen: number): boolean {
100
+ const fullBytes = Math.floor(prefixLen / 8);
101
+ const remainBits = prefixLen % 8;
102
+
103
+ // Compare full bytes
104
+ for (let i = 0; i < fullBytes; i++) {
105
+ if (a[i] !== b[i]) return false;
106
+ }
107
+
108
+ // Compare remaining bits with mask
109
+ if (remainBits > 0 && fullBytes < a.length) {
110
+ const mask = 0xff << (8 - remainBits);
111
+ if ((a[fullBytes] & mask) !== (b[fullBytes] & mask)) return false;
112
+ }
113
+
114
+ return true;
115
+ }
@@ -0,0 +1,51 @@
1
+ import { parseConfig } from './do-router.js';
2
+
3
+ type HeaderReader = Request | { header: (name: string) => string | undefined; raw?: Request };
4
+
5
+ function readHeader(reader: HeaderReader, name: string): string | undefined {
6
+ if (reader instanceof Request) {
7
+ return reader.headers.get(name) ?? undefined;
8
+ }
9
+ const direct = reader.header(name);
10
+ if (direct !== undefined) {
11
+ return direct;
12
+ }
13
+ return reader.raw?.headers.get(name) ?? undefined;
14
+ }
15
+
16
+ function parseForwardedIp(value: string | undefined): string | undefined {
17
+ if (!value) return undefined;
18
+ const first = value.split(',')[0]?.trim();
19
+ return first && first.length > 0 ? first : undefined;
20
+ }
21
+
22
+ function isTrustSelfHostedProxyEnabled(env: unknown): boolean {
23
+ if (env && typeof env === 'object' && !Array.isArray(env)) {
24
+ const direct = (env as Record<string, unknown>).trustSelfHostedProxy;
25
+ if (typeof direct === 'boolean') {
26
+ return direct;
27
+ }
28
+ }
29
+
30
+ return parseConfig(env).trustSelfHostedProxy === true;
31
+ }
32
+
33
+ export function getTrustedClientIp(
34
+ env: unknown,
35
+ reader?: HeaderReader,
36
+ ): string | undefined {
37
+ if (!reader) return undefined;
38
+
39
+ const cfIp = parseForwardedIp(
40
+ readHeader(reader, 'cf-connecting-ip') ?? readHeader(reader, 'CF-Connecting-IP'),
41
+ );
42
+ if (cfIp) return cfIp;
43
+
44
+ if (!isTrustSelfHostedProxyEnabled(env)) {
45
+ return undefined;
46
+ }
47
+
48
+ return parseForwardedIp(
49
+ readHeader(reader, 'x-forwarded-for') ?? readHeader(reader, 'X-Forwarded-For'),
50
+ );
51
+ }