@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,139 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import { OpenAPIHono } from '../lib/hono.js';
3
+ import { setConfig } from '../lib/do-router.js';
4
+ import {
5
+ clearFunctionRegistry,
6
+ clearMiddlewareRegistry,
7
+ rebuildCompiledRoutes,
8
+ registerFunction,
9
+ registerMiddleware,
10
+ } from '../lib/functions.js';
11
+ import { functionsRoute } from '../routes/functions.js';
12
+ import type { Env } from '../types.js';
13
+
14
+ class ForeignFunctionError extends Error {
15
+ code: string;
16
+ httpStatus: number;
17
+ details?: Record<string, unknown>;
18
+
19
+ constructor(
20
+ code: string,
21
+ message: string,
22
+ httpStatus: number,
23
+ details?: Record<string, unknown>,
24
+ ) {
25
+ super(message);
26
+ this.name = 'FunctionError';
27
+ this.code = code;
28
+ this.httpStatus = httpStatus;
29
+ this.details = details;
30
+ }
31
+
32
+ toJSON() {
33
+ return {
34
+ code: this.code,
35
+ message: this.message,
36
+ status: this.httpStatus,
37
+ ...(this.details ? { details: this.details } : {}),
38
+ };
39
+ }
40
+ }
41
+
42
+ function createEnv(overrides: Partial<Env> = {}): Env {
43
+ return {
44
+ DATABASE: {
45
+ idFromName: (name: string) => name as unknown as DurableObjectId,
46
+ get: () => ({
47
+ fetch: async () => new Response('unexpected database fetch', { status: 500 }),
48
+ }),
49
+ } as unknown as DurableObjectNamespace,
50
+ AUTH: {
51
+ idFromName: (name: string) => name as unknown as DurableObjectId,
52
+ get: () => ({
53
+ fetch: async () => new Response('unexpected auth fetch', { status: 500 }),
54
+ }),
55
+ } as unknown as DurableObjectNamespace,
56
+ AUTH_DB: {} as D1Database,
57
+ KV: {} as KVNamespace,
58
+ ...overrides,
59
+ } as Env;
60
+ }
61
+
62
+ function createApp() {
63
+ const app = new OpenAPIHono();
64
+ app.route('/api/functions', functionsRoute);
65
+ return app;
66
+ }
67
+
68
+ function createExecutionContext(): ExecutionContext {
69
+ return {
70
+ waitUntil() {},
71
+ passThroughOnException() {},
72
+ props: {},
73
+ } as unknown as ExecutionContext;
74
+ }
75
+
76
+ afterEach(() => {
77
+ clearFunctionRegistry();
78
+ clearMiddlewareRegistry();
79
+ rebuildCompiledRoutes();
80
+ setConfig({});
81
+ });
82
+
83
+ describe('functionsRoute FunctionError compatibility', () => {
84
+ it('returns structured JSON when directory middleware throws a foreign FunctionError', async () => {
85
+ registerMiddleware('', async () => {
86
+ throw new ForeignFunctionError('unauthenticated', 'Login required for secure routes.', 401, {
87
+ source: 'middleware',
88
+ });
89
+ });
90
+ registerFunction('secure/profile', {
91
+ trigger: { type: 'http', method: 'GET' },
92
+ handler: async () => ({ ok: true }),
93
+ });
94
+ rebuildCompiledRoutes();
95
+
96
+ const response = await createApp().fetch(
97
+ new Request('http://localhost/api/functions/secure/profile', { method: 'GET' }),
98
+ createEnv(),
99
+ createExecutionContext(),
100
+ );
101
+
102
+ expect(response.status).toBe(401);
103
+ await expect(response.json()).resolves.toEqual({
104
+ code: 'unauthenticated',
105
+ message: 'Login required for secure routes.',
106
+ status: 401,
107
+ details: { source: 'middleware' },
108
+ });
109
+ });
110
+
111
+ it('returns structured JSON when a handler throws a foreign FunctionError', async () => {
112
+ registerFunction('call-chain', {
113
+ trigger: { type: 'http', method: 'POST' },
114
+ handler: async () => {
115
+ throw new ForeignFunctionError(
116
+ 'failed-precondition',
117
+ 'Function call depth exceeded (max 5).',
118
+ 412,
119
+ { depth: 6 },
120
+ );
121
+ },
122
+ });
123
+ rebuildCompiledRoutes();
124
+
125
+ const response = await createApp().fetch(
126
+ new Request('http://localhost/api/functions/call-chain', { method: 'POST' }),
127
+ createEnv(),
128
+ createExecutionContext(),
129
+ );
130
+
131
+ expect(response.status).toBe(412);
132
+ await expect(response.json()).resolves.toEqual({
133
+ code: 'failed-precondition',
134
+ message: 'Function call depth exceeded (max 5).',
135
+ status: 412,
136
+ details: { depth: 6 },
137
+ });
138
+ });
139
+ });
@@ -0,0 +1,77 @@
1
+ import type { Context } from 'hono';
2
+ import { describe, expect, it } from 'vitest';
3
+ import type { HonoEnv } from '../lib/hono.js';
4
+ import {
5
+ buildInternalHandlerContext,
6
+ isTrustedInternalContext,
7
+ isTrustedInternalRequestUrl,
8
+ } from '../lib/internal-request.js';
9
+ import type { Env } from '../types.js';
10
+
11
+ function makeContext(
12
+ url: string,
13
+ isInternalRequest = false,
14
+ ): Pick<Context<HonoEnv>, 'get' | 'req'> {
15
+ return {
16
+ get(key: string) {
17
+ return key === 'isInternalRequest' ? isInternalRequest : undefined;
18
+ },
19
+ req: { url } as Context<HonoEnv>['req'],
20
+ };
21
+ }
22
+
23
+ describe('internal request helpers', () => {
24
+ it('isTrustedInternalRequestUrl only trusts worker-internal hosts', () => {
25
+ expect(isTrustedInternalRequestUrl('http://internal/api/db/shared')).toBe(true);
26
+ expect(isTrustedInternalRequestUrl('http://do/internal/functions/reindex')).toBe(true);
27
+ expect(isTrustedInternalRequestUrl('http://localhost:8787/api/db/shared')).toBe(false);
28
+ expect(isTrustedInternalRequestUrl('not-a-url')).toBe(false);
29
+ });
30
+
31
+ it('isTrustedInternalContext accepts explicit internal flags or trusted internal hosts', () => {
32
+ expect(isTrustedInternalContext(makeContext('http://localhost/api/db/shared', true))).toBe(true);
33
+ expect(isTrustedInternalContext(makeContext('http://do/api/db/shared'))).toBe(true);
34
+ expect(isTrustedInternalContext(makeContext('http://example.com/api/db/shared'))).toBe(false);
35
+ });
36
+
37
+ it('buildInternalHandlerContext marks requests as internal and exposes request helpers', async () => {
38
+ const request = new Request('http://do/api/functions/feed-summary?limit=5', {
39
+ method: 'POST',
40
+ headers: { 'X-Test': 'yes' },
41
+ });
42
+
43
+ const ctx = buildInternalHandlerContext({
44
+ env: {} as Env,
45
+ request,
46
+ body: { ok: true },
47
+ });
48
+
49
+ expect(ctx.req.url).toBe(request.url);
50
+ expect(ctx.req.header('X-Test')).toBe('yes');
51
+ await expect(ctx.req.json()).resolves.toEqual({ ok: true });
52
+ expect(ctx.req.query('limit')).toBe('5');
53
+ expect(ctx.req.query()).toEqual({ limit: '5' });
54
+ expect(ctx.get('isInternalRequest' as never)).toBe(true);
55
+ expect(ctx.get('auth' as never)).toBeNull();
56
+ expect(ctx.get('isServiceKey' as never)).toBe(false);
57
+ expect(typeof ctx.executionCtx.waitUntil).toBe('function');
58
+
59
+ const response = ctx.json({ ok: true }, 201);
60
+ expect(response.status).toBe(201);
61
+ await expect(response.json()).resolves.toEqual({ ok: true });
62
+ });
63
+
64
+ it('buildInternalHandlerContext reflects explicit X-Is-Service-Key for privileged internal calls', () => {
65
+ const request = new Request('http://internal/api/db/shared/tables/users', {
66
+ headers: { 'X-Is-Service-Key': 'true' },
67
+ });
68
+
69
+ const ctx = buildInternalHandlerContext({
70
+ env: {} as Env,
71
+ request,
72
+ });
73
+
74
+ expect(ctx.get('isInternalRequest' as never)).toBe(true);
75
+ expect(ctx.get('isServiceKey' as never)).toBe(true);
76
+ });
77
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { SQLiteLogWriter, type LogEntry } from '../lib/log-writer.js';
3
+
4
+ function makeEntry(index: number): LogEntry {
5
+ return {
6
+ method: 'GET',
7
+ path: `/logs/${index}`,
8
+ status: 200,
9
+ duration: 1,
10
+ timestamp: Date.now(),
11
+ };
12
+ }
13
+
14
+ describe('SQLiteLogWriter', () => {
15
+ it('requeues durable object reset failures without noisy warnings', async () => {
16
+ const firstError = Object.assign(new Error('reset'), { durableObjectReset: true });
17
+ const fetchMock = vi
18
+ .fn(async (_input: RequestInfo) => new Response(null, { status: 200 }))
19
+ .mockRejectedValueOnce(firstError);
20
+ const pending: Promise<unknown>[] = [];
21
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
22
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
23
+ const writer = new SQLiteLogWriter(
24
+ { fetch: fetchMock },
25
+ { waitUntil(promise) { pending.push(promise); } },
26
+ );
27
+
28
+ for (let index = 0; index < 50; index++) {
29
+ writer.write(makeEntry(index));
30
+ }
31
+ await Promise.all(pending.splice(0));
32
+
33
+ expect(warnSpy).not.toHaveBeenCalled();
34
+ expect(errorSpy).not.toHaveBeenCalled();
35
+
36
+ writer.write(makeEntry(50));
37
+ await Promise.all(pending.splice(0));
38
+
39
+ expect(fetchMock.mock.calls.length).toBeGreaterThanOrEqual(2);
40
+
41
+ warnSpy.mockRestore();
42
+ errorSpy.mockRestore();
43
+ });
44
+ });
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { createLogWriterMock, writeMock } = vi.hoisted(() => {
4
+ const writeMock = vi.fn();
5
+
6
+ return {
7
+ createLogWriterMock: vi.fn(() => ({
8
+ write: writeMock,
9
+ query: vi.fn(),
10
+ })),
11
+ writeMock,
12
+ };
13
+ });
14
+
15
+ vi.mock('../lib/log-writer.js', () => ({
16
+ createLogWriter: createLogWriterMock,
17
+ }));
18
+
19
+ describe('loggerMiddleware', () => {
20
+ beforeEach(() => {
21
+ vi.resetModules();
22
+ writeMock.mockReset();
23
+ createLogWriterMock.mockReset();
24
+ createLogWriterMock.mockImplementation(() => ({
25
+ write: writeMock,
26
+ query: vi.fn(),
27
+ }));
28
+ });
29
+
30
+ it('logs relative request URLs without throwing', async () => {
31
+ const { loggerMiddleware } = await import('../middleware/logger.js');
32
+ const header = vi.fn(() => null);
33
+ const get = vi.fn(() => undefined);
34
+ const ctx = {
35
+ req: {
36
+ url: '/relative-only?hello=1',
37
+ method: 'GET',
38
+ header,
39
+ raw: {},
40
+ },
41
+ res: {
42
+ status: 204,
43
+ headers: new Headers(),
44
+ },
45
+ get,
46
+ env: {},
47
+ } as any;
48
+
49
+ await expect(loggerMiddleware(ctx, async () => {})).resolves.toBeUndefined();
50
+
51
+ expect(createLogWriterMock).toHaveBeenCalledWith({}, undefined);
52
+ expect(writeMock).toHaveBeenCalledWith(expect.objectContaining({
53
+ method: 'GET',
54
+ path: '/relative-only',
55
+ status: 204,
56
+ }));
57
+ });
58
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Meta-test: admin.ts DO proxy route count.
3
+ *
4
+ * Counts stub.fetch() and fetchDOWithRetry(stub,) calls in admin.ts.
5
+ * When a new proxy route is added, this count must be updated —
6
+ * forcing the developer to also add corresponding contract tests.
7
+ */
8
+ import { readFileSync } from 'fs';
9
+ import { describe, it, expect } from 'vitest';
10
+
11
+ describe('admin.ts DO proxy count', () => {
12
+ const source = readFileSync(
13
+ new URL('../routes/admin.ts', import.meta.url),
14
+ 'utf-8',
15
+ );
16
+
17
+ // Expected counts — update when adding new proxy routes
18
+ // stub.fetch() direct calls + fetchDOWithRetry(stub, ...) wrapped calls
19
+ // Auth shard.fetch() calls removed: user management now uses D1 service directly
20
+ // databaseLiveDO.fetch() calls removed: broadcast now uses /api/db/broadcast worker route
21
+ const EXPECTED_STUB_FETCH = 4;
22
+ const EXPECTED_STUB_RETRY = 4;
23
+ const EXPECTED_SHARD_FETCH = 0;
24
+ const EXPECTED_TOTAL = EXPECTED_STUB_FETCH + EXPECTED_STUB_RETRY + EXPECTED_SHARD_FETCH; // 8
25
+
26
+ it(`stub.fetch() count = ${EXPECTED_STUB_FETCH}`, () => {
27
+ const count = (source.match(/stub\.fetch\(/g) || []).length;
28
+ expect(count).toBe(EXPECTED_STUB_FETCH);
29
+ });
30
+
31
+ it(`fetchDOWithRetry(stub, ...) count = ${EXPECTED_STUB_RETRY}`, () => {
32
+ const count = (source.match(/fetchDOWithRetry\(stub,/g) || []).length;
33
+ expect(count).toBe(EXPECTED_STUB_RETRY);
34
+ });
35
+
36
+ it(`shard.fetch() count = ${EXPECTED_SHARD_FETCH}`, () => {
37
+ const count = (source.match(/shard\.fetch\(/g) || []).length;
38
+ expect(count).toBe(EXPECTED_SHARD_FETCH);
39
+ });
40
+
41
+ it(`total DO proxy routes = ${EXPECTED_TOTAL}`, () => {
42
+ const stubCount = (source.match(/stub\.fetch\(/g) || []).length;
43
+ const retryCount = (source.match(/fetchDOWithRetry\(stub,/g) || []).length;
44
+ const shardCount = (source.match(/shard\.fetch\(/g) || []).length;
45
+ const total = stubCount + retryCount + shardCount;
46
+ expect(total).toBe(EXPECTED_TOTAL);
47
+ });
48
+ });
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Meta-test: export coverage scan.
3
+ *
4
+ * Verifies that every exported function/class/const in tested lib files
5
+ * is referenced (imported or called) in at least one test file.
6
+ *
7
+ * Lib files without a dedicated test file are listed in UNTESTED_LIBS.
8
+ * When you add a new test file for one of them, remove it from the list.
9
+ */
10
+ import { readFileSync, readdirSync } from 'fs';
11
+ import { resolve } from 'path';
12
+ import { describe, it, expect } from 'vitest';
13
+
14
+ const LIB_DIR = resolve(new URL('../lib', import.meta.url).pathname);
15
+ const TEST_DIR = resolve(new URL('.', import.meta.url).pathname);
16
+
17
+ // ─── Lib files that do NOT yet have dedicated tests ─────────────────────────
18
+ // Remove entries as tests are added — each removal is a net win.
19
+ const UNTESTED_LIBS = new Set([
20
+ 'analytics-adapter.ts',
21
+ 'analytics-query.ts',
22
+ 'auth-d1-service.ts', // D1 auth service — used by auth-do + admin routes, tested via integration tests
23
+ 'auth-db-adapter.ts', // Auth DB adapter (D1/PostgreSQL) — used by routes, tested via integration
24
+ 'control-db.ts',
25
+ 'd1-handler.ts', // tested via integration tests only
26
+ 'do-retry.ts',
27
+ 'email-provider.ts',
28
+ 'email-translations.ts', // email i18n strings — used by auth email flows, tested via integration
29
+ 'functions.ts',
30
+ 'hono.ts',
31
+ 'log-writer.ts',
32
+ 'plugin-migrations.ts',
33
+ 'postgres-handler.ts',
34
+ 'postgres-schema-init.ts',
35
+ 'push-provider.ts',
36
+ 'database-live-emitter.ts', // internal helpers tested via integration tests only
37
+ 'sms-provider.ts',
38
+ 'version.ts',
39
+ ]);
40
+
41
+ // ─── Exports in tested files that are not yet covered ───────────────────────
42
+ // These are known gaps. When you add a test for one, remove it here.
43
+ // Adding a NEW export without test coverage will fail CI.
44
+ const KNOWN_UNCOVERED_EXPORTS = new Set([
45
+ 'schemas.ts:listResponseSchema',
46
+ 'schemas.ts:errorResponseSchema',
47
+ 'schemas.ts:recordResponseSchema',
48
+ 'schemas.ts:successResponseSchema',
49
+ 'schemas.ts:healthResponseSchema',
50
+ // OpenAPI route definition schemas — used by route files, not directly tested
51
+ 'schemas.ts:idParamSchema',
52
+ 'schemas.ts:nameParamSchema',
53
+ 'schemas.ts:bucketParamSchema',
54
+ 'schemas.ts:providerParamSchema',
55
+ 'schemas.ts:jsonResponseSchema',
56
+ 'schemas.ts:okResponseSchema',
57
+ 'schemas.ts:d1BodySchema',
58
+ 'schemas.ts:sqlBodySchema',
59
+ 'schemas.ts:kvBodySchema',
60
+ 'schemas.ts:vectorizeBodySchema',
61
+ 'schemas.ts:broadcastBodySchema',
62
+ 'schemas.ts:trackEventsBodySchema',
63
+ // Admin management — used by admin routes, not yet directly tested
64
+ 'auth-d1.ts:listAdmins',
65
+ 'auth-d1.ts:deleteAdmin',
66
+ // Database-live emitter and D1 handler — moved to UNTESTED_LIBS (integration-test only)
67
+ // Auth D1 — _users_public helpers used by auth-do, admin routes, backup (tested via integration)
68
+ 'auth-d1.ts:upsertUserPublic',
69
+ 'auth-d1.ts:deleteUserPublic',
70
+ 'auth-d1.ts:batchDeleteUserPublic',
71
+ 'auth-d1.ts:getUserPublic',
72
+ 'auth-d1.ts:listUsersPublic',
73
+ // Auth D1 — provider constants used by adapter tests, not directly referenced
74
+ 'auth-d1.ts:AUTH_PG_SCHEMA',
75
+ 'auth-d1.ts:AUTH_SHARD_COUNT',
76
+ ]);
77
+
78
+ // ─── Extract named exports from a file ──────────────────────────────────────
79
+
80
+ function extractExports(source: string): string[] {
81
+ const names: string[] = [];
82
+ // export function foo / export async function foo
83
+ for (const m of source.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)) {
84
+ names.push(m[1]);
85
+ }
86
+ // export const foo / export let foo
87
+ for (const m of source.matchAll(/export\s+(?:const|let)\s+(\w+)/g)) {
88
+ names.push(m[1]);
89
+ }
90
+ // export class Foo
91
+ for (const m of source.matchAll(/export\s+class\s+(\w+)/g)) {
92
+ names.push(m[1]);
93
+ }
94
+ // export enum Foo
95
+ for (const m of source.matchAll(/export\s+enum\s+(\w+)/g)) {
96
+ names.push(m[1]);
97
+ }
98
+ return names;
99
+ }
100
+
101
+ // ─── Read all test file source at once ──────────────────────────────────────
102
+
103
+ function getAllTestSource(): string {
104
+ const testFiles = readdirSync(TEST_DIR).filter((f) => f.endsWith('.test.ts'));
105
+ return testFiles
106
+ .map((f) => readFileSync(resolve(TEST_DIR, f), 'utf-8'))
107
+ .join('\n');
108
+ }
109
+
110
+ /**
111
+ * Check if a name is meaningfully referenced in test source.
112
+ * Uses word-boundary matching to avoid false positives from substrings.
113
+ */
114
+ function isReferenced(name: string, source: string): boolean {
115
+ // Word boundary: the name appears as a standalone identifier
116
+ const re = new RegExp(`\\b${name}\\b`);
117
+ return re.test(source);
118
+ }
119
+
120
+ // ─── Tests ──────────────────────────────────────────────────────────────────
121
+
122
+ describe('export coverage scan', () => {
123
+ const allTestSource = getAllTestSource();
124
+
125
+ const testedLibFiles = readdirSync(LIB_DIR)
126
+ .filter((f) => f.endsWith('.ts') && !UNTESTED_LIBS.has(f));
127
+
128
+ it('untested lib count is tracked', () => {
129
+ const allLibFiles = readdirSync(LIB_DIR).filter((f) => f.endsWith('.ts'));
130
+ // If a file is in UNTESTED_LIBS but no longer exists, fail.
131
+ for (const name of UNTESTED_LIBS) {
132
+ expect(
133
+ allLibFiles.includes(name),
134
+ `UNTESTED_LIBS contains '${name}' but file does not exist. Remove it.`,
135
+ ).toBe(true);
136
+ }
137
+ // Track: total = tested + untested. If you add a new lib file, decide where it goes.
138
+ const expectedTotal = testedLibFiles.length + UNTESTED_LIBS.size;
139
+ expect(
140
+ allLibFiles.length,
141
+ `New lib file detected. Add it to UNTESTED_LIBS or write tests for it.`,
142
+ ).toBe(expectedTotal);
143
+ });
144
+
145
+ it('known uncovered exports are still valid', () => {
146
+ // Ensure KNOWN_UNCOVERED_EXPORTS entries still match real exports.
147
+ // If an export is removed or a test is added, this list must be updated.
148
+ for (const entry of KNOWN_UNCOVERED_EXPORTS) {
149
+ const [file, name] = entry.split(':');
150
+ const filePath = resolve(LIB_DIR, file);
151
+ const source = readFileSync(filePath, 'utf-8');
152
+ const exports = extractExports(source);
153
+ expect(
154
+ exports.includes(name),
155
+ `KNOWN_UNCOVERED_EXPORTS has '${entry}' but export '${name}' no longer exists in lib/${file}. Remove it.`,
156
+ ).toBe(true);
157
+
158
+ // Also check it's still NOT referenced (if it is, remove from list)
159
+ // Use import-style check: the name must appear in an import or direct call context
160
+ const importPattern = new RegExp(`import\\s*\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from`);
161
+ const directCallPattern = new RegExp(`\\b${name}\\s*\\(`);
162
+ const isTestedNow = importPattern.test(allTestSource) || directCallPattern.test(allTestSource);
163
+ expect(
164
+ isTestedNow,
165
+ `KNOWN_UNCOVERED_EXPORTS has '${entry}' but it IS imported/called in tests now. Remove it from the list.`,
166
+ ).toBe(false);
167
+ }
168
+ });
169
+
170
+ for (const libFile of testedLibFiles) {
171
+ describe(`lib/${libFile}`, () => {
172
+ const source = readFileSync(resolve(LIB_DIR, libFile), 'utf-8');
173
+ const exports = extractExports(source);
174
+
175
+ // Skip type/interface-only files
176
+ if (exports.length === 0) return;
177
+
178
+ for (const name of exports) {
179
+ // Skip known uncovered exports
180
+ if (KNOWN_UNCOVERED_EXPORTS.has(`${libFile}:${name}`)) continue;
181
+
182
+ it(`export '${name}' is referenced in tests`, () => {
183
+ expect(
184
+ isReferenced(name, allTestSource),
185
+ `Export '${name}' from lib/${libFile} is not referenced in any test file. Add a test or add to KNOWN_UNCOVERED_EXPORTS.`,
186
+ ).toBe(true);
187
+ });
188
+ }
189
+ });
190
+ }
191
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Meta-test: route registration completeness.
3
+ *
4
+ * Ensures every route file imported in index.ts is also registered via app.route().
5
+ * The expected route exports are derived from the current route files.
6
+ */
7
+ import { readFileSync, readdirSync } from 'fs';
8
+ import { resolve } from 'path';
9
+ import { describe, it, expect } from 'vitest';
10
+
11
+ describe('index.ts route registration completeness', () => {
12
+ const source = readFileSync(
13
+ new URL('../index.ts', import.meta.url),
14
+ 'utf-8',
15
+ );
16
+ const routesDir = resolve(new URL('../routes', import.meta.url).pathname);
17
+ const EXPECTED_ROUTES = readdirSync(routesDir)
18
+ .filter((fileName) => fileName.endsWith('.ts'))
19
+ .sort()
20
+ .flatMap((fileName) => {
21
+ const routeSource = readFileSync(resolve(routesDir, fileName), 'utf-8');
22
+ const directExports = [...routeSource.matchAll(/export const (\w+)\s*=\s*new OpenAPIHono/g)].map(
23
+ (match) => match[1],
24
+ );
25
+ const aliasExports = [...routeSource.matchAll(/export \{ \w+ as (\w+) \}/g)].map(
26
+ (match) => match[1],
27
+ );
28
+ return [...directExports, ...aliasExports];
29
+ });
30
+
31
+ const EXPECTED_COUNT = EXPECTED_ROUTES.length;
32
+
33
+ it(`total route imports = ${EXPECTED_COUNT}`, () => {
34
+ const importMatches = source.match(/import \{ \w+ \} from '\.\/routes\//g) || [];
35
+ expect(importMatches.length).toBe(EXPECTED_COUNT);
36
+ });
37
+
38
+ for (const routeVar of EXPECTED_ROUTES) {
39
+ it(`${routeVar} is imported`, () => {
40
+ expect(source).toMatch(new RegExp(`import\\s*\\{[^}]*\\b${routeVar}\\b[^}]*\\}\\s*from '\\./routes/`));
41
+ });
42
+
43
+ it(`${routeVar} is registered via app.route()`, () => {
44
+ expect(source).toMatch(new RegExp(`app\\.route\\([^)]+,\\s*${routeVar}\\)`));
45
+ });
46
+ }
47
+ });
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { defineConfig } from '@edge-base/shared';
3
+ import { dumpNamespaceTables } from '../lib/namespace-dump.js';
4
+
5
+ describe('namespace dump helpers', () => {
6
+ it('throws when the requested namespace is missing from config', async () => {
7
+ const config = defineConfig({
8
+ databases: {
9
+ shared: {
10
+ tables: {
11
+ posts: {
12
+ schema: {
13
+ title: { type: 'string' },
14
+ },
15
+ },
16
+ },
17
+ },
18
+ },
19
+ });
20
+
21
+ await expect(
22
+ dumpNamespaceTables({} as never, config, 'missing'),
23
+ ).rejects.toMatchObject({
24
+ code: 404,
25
+ message: "Namespace 'missing' not found in config.",
26
+ });
27
+ });
28
+ });