@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,120 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { executeD1Sql } from '../lib/d1-sql.js';
3
+
4
+ interface MockCall {
5
+ bindings: unknown[];
6
+ method: 'all' | 'run';
7
+ sql: string;
8
+ }
9
+
10
+ function createMockD1(options: {
11
+ allResult?: { results?: Record<string, unknown>[] };
12
+ runResult?: { meta?: { changes?: number } };
13
+ } = {}): D1Database & { _calls: MockCall[] } {
14
+ const calls: MockCall[] = [];
15
+
16
+ function makeStmt(sql: string) {
17
+ const call: MockCall = {
18
+ bindings: [],
19
+ method: 'run',
20
+ sql,
21
+ };
22
+ calls.push(call);
23
+
24
+ const stmt = {
25
+ bind: (...values: unknown[]) => {
26
+ call.bindings = values;
27
+ return stmt;
28
+ },
29
+ all: async () => {
30
+ call.method = 'all';
31
+ return options.allResult ?? { results: [] };
32
+ },
33
+ run: async () => {
34
+ call.method = 'run';
35
+ return options.runResult ?? { meta: { changes: 0 } };
36
+ },
37
+ };
38
+
39
+ return stmt;
40
+ }
41
+
42
+ return {
43
+ _calls: calls,
44
+ prepare: (sql: string) => makeStmt(sql),
45
+ } as unknown as D1Database & { _calls: MockCall[] };
46
+ }
47
+
48
+ describe('executeD1Sql', () => {
49
+ it('returns rows for SELECT-style queries and binds parameters', async () => {
50
+ const db = createMockD1({
51
+ allResult: {
52
+ results: [{ id: 'post_1', title: 'Hello' }],
53
+ },
54
+ });
55
+
56
+ const result = await executeD1Sql(
57
+ db,
58
+ 'SELECT * FROM posts WHERE id = ?',
59
+ ['post_1'],
60
+ );
61
+
62
+ expect(result).toEqual({
63
+ rowCount: 1,
64
+ rows: [{ id: 'post_1', title: 'Hello' }],
65
+ });
66
+ expect(db._calls).toEqual([
67
+ {
68
+ bindings: ['post_1'],
69
+ method: 'all',
70
+ sql: 'SELECT * FROM posts WHERE id = ?',
71
+ },
72
+ ]);
73
+ });
74
+
75
+ it('returns affected row count for write queries', async () => {
76
+ const db = createMockD1({
77
+ runResult: {
78
+ meta: { changes: 2 },
79
+ },
80
+ });
81
+
82
+ const result = await executeD1Sql(
83
+ db,
84
+ 'UPDATE posts SET published = ? WHERE category = ?',
85
+ [true, 'news'],
86
+ );
87
+
88
+ expect(result).toEqual({
89
+ rowCount: 2,
90
+ rows: [],
91
+ });
92
+ expect(db._calls).toEqual([
93
+ {
94
+ bindings: [true, 'news'],
95
+ method: 'run',
96
+ sql: 'UPDATE posts SET published = ? WHERE category = ?',
97
+ },
98
+ ]);
99
+ });
100
+
101
+ it('treats RETURNING queries as row-producing even when they mutate data', async () => {
102
+ const db = createMockD1({
103
+ allResult: {
104
+ results: [{ id: 'post_2', published: true }],
105
+ },
106
+ });
107
+
108
+ const result = await executeD1Sql(
109
+ db,
110
+ 'UPDATE posts SET published = 1 WHERE id = ? RETURNING id, published',
111
+ ['post_2'],
112
+ );
113
+
114
+ expect(result).toEqual({
115
+ rowCount: 1,
116
+ rows: [{ id: 'post_2', published: true }],
117
+ });
118
+ expect(db._calls[0]?.method).toBe('all');
119
+ });
120
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { EdgeBaseConfig } from '@edge-base/shared';
3
+ import {
4
+ DEFAULT_DB_LIVE_AUTH_TIMEOUT_MS,
5
+ DEFAULT_DB_LIVE_BATCH_THRESHOLD,
6
+ resolveDbLiveAuthTimeoutMs,
7
+ resolveDbLiveBatchThreshold,
8
+ } from '../lib/database-live-config.js';
9
+
10
+ describe('database-live-config helpers', () => {
11
+ it('uses configured positive database-live auth timeout', () => {
12
+ const config = {
13
+ databaseLive: { authTimeoutMs: 12000 },
14
+ } as EdgeBaseConfig;
15
+
16
+ expect(resolveDbLiveAuthTimeoutMs(config)).toBe(12000);
17
+ });
18
+
19
+ it('falls back for invalid database-live auth timeout values', () => {
20
+ const config = {
21
+ databaseLive: { authTimeoutMs: 0 },
22
+ } as EdgeBaseConfig;
23
+
24
+ expect(resolveDbLiveAuthTimeoutMs(config)).toBe(DEFAULT_DB_LIVE_AUTH_TIMEOUT_MS);
25
+ });
26
+
27
+ it('uses configured positive batch threshold', () => {
28
+ const config = {
29
+ databaseLive: { batchThreshold: 3 },
30
+ } as EdgeBaseConfig;
31
+
32
+ expect(resolveDbLiveBatchThreshold(config)).toBe(3);
33
+ });
34
+
35
+ it('falls back for invalid batch threshold values', () => {
36
+ const config = {
37
+ databaseLive: { batchThreshold: -1 },
38
+ } as EdgeBaseConfig;
39
+
40
+ expect(resolveDbLiveBatchThreshold(config)).toBe(DEFAULT_DB_LIVE_BATCH_THRESHOLD);
41
+ });
42
+ });
@@ -0,0 +1,56 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { buildDbLiveChannel, emitDbLiveBatchEvent, emitDbLiveEvent } from '../lib/database-live-emitter.js';
3
+
4
+ describe('buildDbLiveChannel', () => {
5
+ it('builds shared table channels with namespace', () => {
6
+ expect(buildDbLiveChannel('shared', 'posts')).toBe('dblive:shared:posts');
7
+ expect(buildDbLiveChannel('shared', 'posts', undefined, 'p1')).toBe('dblive:shared:posts:p1');
8
+ });
9
+
10
+ it('builds dynamic table channels with namespace and instance id', () => {
11
+ expect(buildDbLiveChannel('workspace', 'documents', 'ws-123')).toBe('dblive:workspace:ws-123:documents');
12
+ expect(buildDbLiveChannel('workspace', 'documents', 'ws-123', 'doc-9')).toBe('dblive:workspace:ws-123:documents:doc-9');
13
+ });
14
+ });
15
+
16
+ describe('database-live emitter', () => {
17
+ it('emits shared-table events to namespace-aware table and doc channels', async () => {
18
+ const fetch = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
19
+ const get = vi.fn().mockReturnValue({ fetch });
20
+ const env = {
21
+ DATABASE_LIVE: {
22
+ idFromName: vi.fn((name: string) => name),
23
+ get,
24
+ },
25
+ } as any;
26
+
27
+ emitDbLiveEvent(env, 'shared', 'posts', 'added', 'post-1', { id: 'post-1' });
28
+ await Promise.resolve();
29
+
30
+ expect(env.DATABASE_LIVE.idFromName).toHaveBeenCalledWith('database-live:hub');
31
+ expect(fetch).toHaveBeenCalledTimes(2);
32
+ const tablePayload = JSON.parse(fetch.mock.calls[0]![1].body as string);
33
+ const docPayload = JSON.parse(fetch.mock.calls[1]![1].body as string);
34
+ expect(tablePayload.channel).toBe('dblive:shared:posts');
35
+ expect(docPayload.channel).toBe('dblive:shared:posts:post-1');
36
+ });
37
+
38
+ it('emits batch events to the namespace-aware table channel', async () => {
39
+ const fetch = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
40
+ const get = vi.fn().mockReturnValue({ fetch });
41
+ const env = {
42
+ DATABASE_LIVE: {
43
+ idFromName: vi.fn((name: string) => name),
44
+ get,
45
+ },
46
+ } as any;
47
+
48
+ emitDbLiveBatchEvent(env, 'workspace', 'documents', [{ type: 'modified', docId: 'd1', data: { id: 'd1' } }], 'ws-9');
49
+ await Promise.resolve();
50
+
51
+ expect(env.DATABASE_LIVE.idFromName).toHaveBeenCalledWith('database-live:hub');
52
+ expect(fetch).toHaveBeenCalledTimes(1);
53
+ const payload = JSON.parse(fetch.mock.calls[0]![1].body as string);
54
+ expect(payload.channel).toBe('dblive:workspace:ws-9:documents');
55
+ });
56
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('cloudflare:workers', () => ({
4
+ DurableObject: class DurableObject {},
5
+ }));
6
+
7
+ describe('database-live filter helpers', () => {
8
+ it('normalizes tuple filters for contains-any and not in operators', async () => {
9
+ const { normalizeDatabaseLiveFilterPayload } = await import('../durable-objects/database-live-do.js');
10
+
11
+ const normalized = normalizeDatabaseLiveFilterPayload([
12
+ ['tags', 'contains-any', ['hot', 'featured']],
13
+ ['status', 'not in', ['archived']],
14
+ ], 'Filters');
15
+
16
+ expect(normalized.ok).toBe(true);
17
+ if (!normalized.ok) {
18
+ throw new Error(normalized.message);
19
+ }
20
+ expect(normalized.value).toEqual([
21
+ ['tags', 'contains-any', ['hot', 'featured']],
22
+ ['status', 'not in', ['archived']],
23
+ ]);
24
+ });
25
+
26
+ it('evaluates AND filters together with OR-group semantics', async () => {
27
+ const { evaluateDatabaseLiveFilters } = await import('../durable-objects/database-live-do.js');
28
+
29
+ expect(evaluateDatabaseLiveFilters(
30
+ {
31
+ score: 12,
32
+ status: 'published',
33
+ tags: ['cold'],
34
+ title: 'Hello World',
35
+ },
36
+ [
37
+ ['score', '>', 10],
38
+ ['status', 'not in', ['draft', 'archived']],
39
+ ],
40
+ [
41
+ ['tags', 'contains-any', ['hot']],
42
+ ['title', 'contains', 'World'],
43
+ ],
44
+ )).toBe(true);
45
+
46
+ expect(evaluateDatabaseLiveFilters(
47
+ {
48
+ score: 12,
49
+ status: 'published',
50
+ tags: ['cold'],
51
+ title: 'Hello there',
52
+ },
53
+ [
54
+ ['score', '>', 10],
55
+ ['status', 'not in', ['draft', 'archived']],
56
+ ],
57
+ [
58
+ ['tags', 'contains-any', ['hot']],
59
+ ['title', 'contains', 'World'],
60
+ ],
61
+ )).toBe(false);
62
+ });
63
+ });
@@ -0,0 +1,113 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import { OpenAPIHono, type HonoEnv } from '../lib/hono.js';
3
+ import { setConfig } from '../lib/do-router.js';
4
+ import { databaseLiveRoute } from '../routes/database-live.js';
5
+ import type { Env } from '../types.js';
6
+
7
+ interface MockKVStore {
8
+ data: Record<string, { value: string; options?: { expirationTtl?: number } }>;
9
+ }
10
+
11
+ function createMockKV(): KVNamespace & { _store: MockKVStore } {
12
+ const store: MockKVStore = { data: {} };
13
+
14
+ return {
15
+ get: async (key: string) => store.data[key]?.value ?? null,
16
+ put: async (key: string, value: string, options?: { expirationTtl?: number }) => {
17
+ store.data[key] = { value, options };
18
+ },
19
+ delete: async (key: string) => {
20
+ delete store.data[key];
21
+ },
22
+ list: async () => ({ keys: [], list_complete: true, cacheStatus: null }),
23
+ getWithMetadata: async () => ({ value: null, metadata: null, cacheStatus: null }),
24
+ _store: store,
25
+ } as unknown as KVNamespace & { _store: MockKVStore };
26
+ }
27
+
28
+ function createMockEnv(
29
+ kv: KVNamespace & { _store: MockKVStore },
30
+ onFetch?: (request: Request) => Response | Promise<Response>,
31
+ ): Env {
32
+ return {
33
+ KV: kv,
34
+ DATABASE_LIVE: {
35
+ idFromName: (name: string) => name as unknown as DurableObjectId,
36
+ get: () => ({
37
+ fetch: async (request: Request) => onFetch?.(request) ?? new Response(null, { status: 200 }),
38
+ }),
39
+ } as unknown as DurableObjectNamespace,
40
+ } as unknown as Env;
41
+ }
42
+
43
+ function createApp() {
44
+ const app = new OpenAPIHono<HonoEnv>();
45
+ app.route('/api/db', databaseLiveRoute);
46
+ return app;
47
+ }
48
+
49
+ describe('database live subscription route', () => {
50
+ afterEach(() => {
51
+ setConfig({});
52
+ });
53
+
54
+ it('reports connect-check readiness for explicit database params', async () => {
55
+ const kv = createMockKV();
56
+ const app = createApp();
57
+
58
+ const response = await app.request('/api/db/connect-check?namespace=shared&table=posts', {
59
+ method: 'GET',
60
+ headers: {
61
+ 'CF-Connecting-IP': '127.0.0.1',
62
+ },
63
+ }, createMockEnv(kv));
64
+
65
+ expect(response.status).toBe(200);
66
+ await expect(response.json()).resolves.toMatchObject({
67
+ ok: true,
68
+ type: 'db_connect_ready',
69
+ category: 'ready',
70
+ channel: 'dblive:shared:posts',
71
+ });
72
+ });
73
+
74
+ it('rejects non-database-live channels on database entrypoints', async () => {
75
+ const kv = createMockKV();
76
+ const app = createApp();
77
+
78
+ const response = await app.request('/api/db/connect-check?channel=dblive:presence:lobby', {
79
+ method: 'GET',
80
+ headers: {
81
+ 'CF-Connecting-IP': '127.0.0.1',
82
+ },
83
+ }, createMockEnv(kv));
84
+
85
+ expect(response.status).toBe(400);
86
+ await expect(response.json()).resolves.toMatchObject({
87
+ ok: false,
88
+ type: 'db_connect_invalid_request',
89
+ category: 'request',
90
+ });
91
+ });
92
+
93
+ it('proxies websocket subscribe requests through the database-owned path and releases pending slots', async () => {
94
+ const kv = createMockKV();
95
+ const app = createApp();
96
+ let forwardedUrl = '';
97
+
98
+ const response = await app.request('/api/db/subscribe?namespace=workspace&instanceId=ws-9&table=posts&docId=doc-1', {
99
+ method: 'GET',
100
+ headers: {
101
+ Upgrade: 'websocket',
102
+ 'CF-Connecting-IP': '127.0.0.1',
103
+ },
104
+ }, createMockEnv(kv, async (request) => {
105
+ forwardedUrl = request.url;
106
+ return new Response(null, { status: 200 });
107
+ }));
108
+
109
+ expect(response.status).toBe(200);
110
+ expect(forwardedUrl).toContain('/websocket?channel=dblive%3Aworkspace%3Aws-9%3Aposts%3Adoc-1');
111
+ expect(kv._store.data['ws:pending:127.0.0.1']).toBeUndefined();
112
+ });
113
+ });
@@ -0,0 +1,163 @@
1
+ /**
2
+ * 서버 단위 테스트 — lib/db-sql.ts
3
+ *
4
+ * 실행: cd packages/server && npx vitest run src/__tests__/db-sql.test.ts
5
+ *
6
+ * 테스트 대상:
7
+ * RawSql / raw / buildSqlQuery / createSqlTaggedTemplate
8
+ */
9
+
10
+ import { describe, it, expect, vi } from 'vitest';
11
+ import { RawSql, raw, buildSqlQuery, createSqlTaggedTemplate } from '../lib/db-sql.js';
12
+
13
+ // ─── A. RawSql ──────────────────────────────────────────────────────────────
14
+
15
+ describe('RawSql', () => {
16
+ it('stores value property', () => {
17
+ const r = new RawSql('table_name');
18
+ expect(r.value).toBe('table_name');
19
+ });
20
+
21
+ it('is instanceof RawSql', () => {
22
+ const r = new RawSql('x');
23
+ expect(r instanceof RawSql).toBe(true);
24
+ });
25
+
26
+ it('value is readonly (reflected in type)', () => {
27
+ const r = new RawSql('abc');
28
+ expect(r.value).toBe('abc');
29
+ });
30
+ });
31
+
32
+ // ─── B. raw() ───────────────────────────────────────────────────────────────
33
+
34
+ describe('raw()', () => {
35
+ it('returns a RawSql instance', () => {
36
+ const r = raw('my_table');
37
+ expect(r).toBeInstanceOf(RawSql);
38
+ expect(r.value).toBe('my_table');
39
+ });
40
+
41
+ it('empty string raw', () => {
42
+ const r = raw('');
43
+ expect(r.value).toBe('');
44
+ });
45
+ });
46
+
47
+ // ─── C. buildSqlQuery ───────────────────────────────────────────────────────
48
+
49
+ describe('buildSqlQuery', () => {
50
+ // Helper to create tagged template arrays
51
+ function sql(strings: TemplateStringsArray, ...values: unknown[]) {
52
+ return buildSqlQuery(strings, ...values);
53
+ }
54
+
55
+ it('no interpolation → just the query', () => {
56
+ const result = sql`SELECT * FROM users`;
57
+ expect(result.query).toBe('SELECT * FROM users');
58
+ expect(result.params).toEqual([]);
59
+ });
60
+
61
+ it('single parameterized value', () => {
62
+ const result = sql`SELECT * FROM users WHERE id = ${123}`;
63
+ expect(result.query).toBe('SELECT * FROM users WHERE id = ?');
64
+ expect(result.params).toEqual([123]);
65
+ });
66
+
67
+ it('multiple parameterized values', () => {
68
+ const result = sql`INSERT INTO t (a, b) VALUES (${1}, ${'hello'})`;
69
+ expect(result.query).toBe('INSERT INTO t (a, b) VALUES (?, ?)');
70
+ expect(result.params).toEqual([1, 'hello']);
71
+ });
72
+
73
+ it('RawSql bypasses parameterization', () => {
74
+ const tableName = raw('users');
75
+ const result = sql`SELECT * FROM ${tableName} WHERE id = ${42}`;
76
+ expect(result.query).toBe('SELECT * FROM users WHERE id = ?');
77
+ expect(result.params).toEqual([42]);
78
+ });
79
+
80
+ it('mixed raw and parameterized', () => {
81
+ const col = raw('"name"');
82
+ const result = sql`UPDATE ${raw('posts')} SET ${col} = ${'new title'} WHERE id = ${5}`;
83
+ expect(result.query).toBe('UPDATE posts SET "name" = ? WHERE id = ?');
84
+ expect(result.params).toEqual(['new title', 5]);
85
+ });
86
+
87
+ it('null and undefined as params', () => {
88
+ const result = sql`INSERT INTO t (a, b) VALUES (${null}, ${undefined})`;
89
+ expect(result.params).toEqual([null, undefined]);
90
+ expect(result.query).toBe('INSERT INTO t (a, b) VALUES (?, ?)');
91
+ });
92
+
93
+ it('boolean as param', () => {
94
+ const result = sql`UPDATE t SET active = ${true}`;
95
+ expect(result.params).toEqual([true]);
96
+ });
97
+
98
+ it('only raw values → no params', () => {
99
+ const result = sql`SELECT * FROM ${raw('users')} ORDER BY ${raw('"created_at"')} DESC`;
100
+ expect(result.query).toBe('SELECT * FROM users ORDER BY "created_at" DESC');
101
+ expect(result.params).toEqual([]);
102
+ });
103
+ });
104
+
105
+ // ─── D. createSqlTaggedTemplate ─────────────────────────────────────────────
106
+
107
+ describe('createSqlTaggedTemplate', () => {
108
+ it('executes query with correct params and returns rows', () => {
109
+ const mockExec = vi.fn((..._args: unknown[]) =>
110
+ [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }] as Iterable<Record<string, unknown>>,
111
+ );
112
+ const sql = createSqlTaggedTemplate(mockExec);
113
+
114
+ const result = sql`SELECT * FROM users WHERE active = ${true}`;
115
+
116
+ expect(mockExec).toHaveBeenCalledWith('SELECT * FROM users WHERE active = ?', true);
117
+ expect(result).toEqual([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
118
+ });
119
+
120
+ it('spreads multiple params correctly', () => {
121
+ const mockExec = vi.fn((..._args: unknown[]) => [] as Iterable<Record<string, unknown>>);
122
+ const sql = createSqlTaggedTemplate(mockExec);
123
+
124
+ void sql`INSERT INTO t (a, b, c) VALUES (${1}, ${'two'}, ${null})`;
125
+
126
+ expect(mockExec).toHaveBeenCalledWith(
127
+ 'INSERT INTO t (a, b, c) VALUES (?, ?, ?)',
128
+ 1, 'two', null,
129
+ );
130
+ });
131
+
132
+ it('raw values are inlined, not passed as params', () => {
133
+ const mockExec = vi.fn((..._args: unknown[]) => [] as Iterable<Record<string, unknown>>);
134
+ const sql = createSqlTaggedTemplate(mockExec);
135
+
136
+ void sql`SELECT * FROM ${raw('my_table')} WHERE id = ${42}`;
137
+
138
+ expect(mockExec).toHaveBeenCalledWith('SELECT * FROM my_table WHERE id = ?', 42);
139
+ });
140
+
141
+ it('no-param query passes only query string', () => {
142
+ const mockExec = vi.fn((..._args: unknown[]) => [] as Iterable<Record<string, unknown>>);
143
+ const sql = createSqlTaggedTemplate(mockExec);
144
+
145
+ void sql`SELECT 1`;
146
+
147
+ expect(mockExec).toHaveBeenCalledWith('SELECT 1');
148
+ });
149
+
150
+ it('iterates cursor and returns array', () => {
151
+ // Simulate a generator/iterable cursor
152
+ function* cursor() {
153
+ yield { id: 1 };
154
+ yield { id: 2 };
155
+ yield { id: 3 };
156
+ }
157
+ const mockExec = vi.fn(() => cursor());
158
+ const sql = createSqlTaggedTemplate(mockExec);
159
+
160
+ const result = sql`SELECT * FROM t`;
161
+ expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
162
+ });
163
+ });