cursedbelt-server 1.0.0

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 (606) hide show
  1. package/LICENSE +15 -0
  2. package/dist/server/activity/index.d.ts +36 -0
  3. package/dist/server/activity/index.js +36 -0
  4. package/dist/server/activity/migrations.d.ts +21 -0
  5. package/dist/server/activity/migrations.js +55 -0
  6. package/dist/server/activity/shareSource.d.ts +11 -0
  7. package/dist/server/activity/shareSource.js +45 -0
  8. package/dist/server/activity/store.d.ts +123 -0
  9. package/dist/server/activity/store.js +178 -0
  10. package/dist/server/app.d.ts +56 -0
  11. package/dist/server/app.js +45 -0
  12. package/dist/server/audit/index.d.ts +2 -0
  13. package/dist/server/audit/index.js +2 -0
  14. package/dist/server/audit/migrations.d.ts +2 -0
  15. package/dist/server/audit/migrations.js +79 -0
  16. package/dist/server/audit/sqliteSink.d.ts +36 -0
  17. package/dist/server/audit/sqliteSink.js +95 -0
  18. package/dist/server/audit/sqliteSink.test.d.ts +1 -0
  19. package/dist/server/audit/sqliteSink.test.js +180 -0
  20. package/dist/server/auth/cookie.d.ts +22 -0
  21. package/dist/server/auth/cookie.js +35 -0
  22. package/dist/server/auth/csrf.d.ts +42 -0
  23. package/dist/server/auth/csrf.js +36 -0
  24. package/dist/server/auth/fileTokenSigner.d.ts +10 -0
  25. package/dist/server/auth/fileTokenSigner.js +22 -0
  26. package/dist/server/auth/getOnly.d.ts +20 -0
  27. package/dist/server/auth/getOnly.js +27 -0
  28. package/dist/server/auth/index.d.ts +12 -0
  29. package/dist/server/auth/index.js +15 -0
  30. package/dist/server/auth/jwt.d.ts +18 -0
  31. package/dist/server/auth/jwt.js +56 -0
  32. package/dist/server/auth/loginThrottle.d.ts +118 -0
  33. package/dist/server/auth/loginThrottle.js +204 -0
  34. package/dist/server/auth/middleware.d.ts +14 -0
  35. package/dist/server/auth/middleware.js +47 -0
  36. package/dist/server/auth/password.d.ts +14 -0
  37. package/dist/server/auth/password.js +65 -0
  38. package/dist/server/auth/sessionStore.d.ts +19 -0
  39. package/dist/server/auth/sessionStore.js +221 -0
  40. package/dist/server/auth/totp.d.ts +34 -0
  41. package/dist/server/auth/totp.js +81 -0
  42. package/dist/server/auth/types.d.ts +133 -0
  43. package/dist/server/auth/types.js +1 -0
  44. package/dist/server/auth/vault.d.ts +27 -0
  45. package/dist/server/auth/vault.js +57 -0
  46. package/dist/server/context.d.ts +35 -0
  47. package/dist/server/context.js +1 -0
  48. package/dist/server/ctgr.d.ts +32 -0
  49. package/dist/server/ctgr.js +69 -0
  50. package/dist/server/db/kysely.d.ts +14 -0
  51. package/dist/server/db/kysely.js +16 -0
  52. package/dist/server/db/plumbingTypes.d.ts +116 -0
  53. package/dist/server/db/plumbingTypes.js +12 -0
  54. package/dist/server/dropzone/duplicates.d.ts +82 -0
  55. package/dist/server/dropzone/duplicates.js +90 -0
  56. package/dist/server/dropzone/index.d.ts +13 -0
  57. package/dist/server/dropzone/index.js +13 -0
  58. package/dist/server/dropzone/scan.d.ts +166 -0
  59. package/dist/server/dropzone/scan.js +272 -0
  60. package/dist/server/errors.d.ts +7 -0
  61. package/dist/server/errors.js +7 -0
  62. package/dist/server/guard/argon2Binding.d.ts +97 -0
  63. package/dist/server/guard/argon2Binding.js +138 -0
  64. package/dist/server/guard/index.d.ts +4 -0
  65. package/dist/server/guard/index.js +6 -0
  66. package/dist/server/guard/kdfWorker.d.ts +101 -0
  67. package/dist/server/guard/kdfWorker.js +301 -0
  68. package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
  69. package/dist/server/guard/kdfWorkerEntry.js +15 -0
  70. package/dist/server/guard/passwordVerifier.d.ts +65 -0
  71. package/dist/server/guard/passwordVerifier.js +122 -0
  72. package/dist/server/guard/revocationStore.d.ts +132 -0
  73. package/dist/server/guard/revocationStore.js +211 -0
  74. package/dist/server/guard/sessionSigner.d.ts +132 -0
  75. package/dist/server/guard/sessionSigner.js +303 -0
  76. package/dist/server/guard/verifier.d.ts +21 -0
  77. package/dist/server/guard/verifier.js +1 -0
  78. package/dist/server/incidents/channels.d.ts +161 -0
  79. package/dist/server/incidents/channels.js +357 -0
  80. package/dist/server/incidents/checks.d.ts +61 -0
  81. package/dist/server/incidents/checks.js +55 -0
  82. package/dist/server/incidents/index.d.ts +51 -0
  83. package/dist/server/incidents/index.js +51 -0
  84. package/dist/server/incidents/migrations.d.ts +20 -0
  85. package/dist/server/incidents/migrations.js +58 -0
  86. package/dist/server/incidents/notifier.d.ts +79 -0
  87. package/dist/server/incidents/notifier.js +141 -0
  88. package/dist/server/incidents/reporter.d.ts +60 -0
  89. package/dist/server/incidents/reporter.js +49 -0
  90. package/dist/server/incidents/routes.d.ts +91 -0
  91. package/dist/server/incidents/routes.js +202 -0
  92. package/dist/server/incidents/store.d.ts +75 -0
  93. package/dist/server/incidents/store.js +176 -0
  94. package/dist/server/incidents/types.d.ts +107 -0
  95. package/dist/server/incidents/types.js +32 -0
  96. package/dist/server/index.d.ts +25 -0
  97. package/dist/server/index.js +53 -0
  98. package/dist/server/jobs/fireQueue.d.ts +12 -0
  99. package/dist/server/jobs/fireQueue.js +37 -0
  100. package/dist/server/jobs/index.d.ts +6 -0
  101. package/dist/server/jobs/index.js +10 -0
  102. package/dist/server/jobs/logger.d.ts +10 -0
  103. package/dist/server/jobs/logger.js +14 -0
  104. package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
  105. package/dist/server/jobs/plainjobAdapter.js +48 -0
  106. package/dist/server/jobs/polling.d.ts +17 -0
  107. package/dist/server/jobs/polling.js +79 -0
  108. package/dist/server/jobs/runner.d.ts +24 -0
  109. package/dist/server/jobs/runner.js +186 -0
  110. package/dist/server/jobs/timeout.d.ts +28 -0
  111. package/dist/server/jobs/timeout.js +47 -0
  112. package/dist/server/jobs/types.d.ts +167 -0
  113. package/dist/server/jobs/types.js +12 -0
  114. package/dist/server/master-lock/guard.d.ts +53 -0
  115. package/dist/server/master-lock/guard.js +205 -0
  116. package/dist/server/master-lock/index.d.ts +21 -0
  117. package/dist/server/master-lock/index.js +21 -0
  118. package/dist/server/master-lock/lockPage.d.ts +34 -0
  119. package/dist/server/master-lock/lockPage.js +281 -0
  120. package/dist/server/master-lock/masterLock.d.ts +294 -0
  121. package/dist/server/master-lock/masterLock.js +523 -0
  122. package/dist/server/master-lock/principals.d.ts +76 -0
  123. package/dist/server/master-lock/principals.js +157 -0
  124. package/dist/server/master-lock/seed.d.ts +47 -0
  125. package/dist/server/master-lock/seed.js +93 -0
  126. package/dist/server/master-lock/store.d.ts +19 -0
  127. package/dist/server/master-lock/store.js +20 -0
  128. package/dist/server/media-bun/image.d.ts +7 -0
  129. package/dist/server/media-bun/image.js +96 -0
  130. package/dist/server/media-bun/index.d.ts +4 -0
  131. package/dist/server/media-bun/index.js +10 -0
  132. package/dist/server/media-bun/streaming.d.ts +86 -0
  133. package/dist/server/media-bun/streaming.js +237 -0
  134. package/dist/server/media-bun/video.d.ts +35 -0
  135. package/dist/server/media-bun/video.js +181 -0
  136. package/dist/server/metrics/metricsBuffer.d.ts +39 -0
  137. package/dist/server/metrics/metricsBuffer.js +36 -0
  138. package/dist/server/metrics/normalizeRoute.d.ts +5 -0
  139. package/dist/server/metrics/normalizeRoute.js +58 -0
  140. package/dist/server/middleware/correlationId.d.ts +9 -0
  141. package/dist/server/middleware/correlationId.js +20 -0
  142. package/dist/server/middleware/corsAllowlist.d.ts +38 -0
  143. package/dist/server/middleware/corsAllowlist.js +37 -0
  144. package/dist/server/middleware/errorEnvelope.d.ts +17 -0
  145. package/dist/server/middleware/errorEnvelope.js +32 -0
  146. package/dist/server/middleware/index.d.ts +9 -0
  147. package/dist/server/middleware/index.js +10 -0
  148. package/dist/server/middleware/localOrigin.d.ts +71 -0
  149. package/dist/server/middleware/localOrigin.js +119 -0
  150. package/dist/server/middleware/rateLimit.d.ts +56 -0
  151. package/dist/server/middleware/rateLimit.js +86 -0
  152. package/dist/server/middleware/requestLogger.d.ts +29 -0
  153. package/dist/server/middleware/requestLogger.js +47 -0
  154. package/dist/server/middleware/securityHeaders.d.ts +29 -0
  155. package/dist/server/middleware/securityHeaders.js +44 -0
  156. package/dist/server/middleware/zValidator.d.ts +29 -0
  157. package/dist/server/middleware/zValidator.js +57 -0
  158. package/dist/server/migration/runner.d.ts +42 -0
  159. package/dist/server/migration/runner.js +70 -0
  160. package/dist/server/migration/types.d.ts +16 -0
  161. package/dist/server/migration/types.js +1 -0
  162. package/dist/server/net/outboundGuard.d.ts +30 -0
  163. package/dist/server/net/outboundGuard.js +199 -0
  164. package/dist/server/notifications/hub.d.ts +67 -0
  165. package/dist/server/notifications/hub.js +187 -0
  166. package/dist/server/notifications/index.d.ts +47 -0
  167. package/dist/server/notifications/index.js +46 -0
  168. package/dist/server/notifications/migrations.d.ts +13 -0
  169. package/dist/server/notifications/migrations.js +20 -0
  170. package/dist/server/notifications/routes.d.ts +58 -0
  171. package/dist/server/notifications/routes.js +113 -0
  172. package/dist/server/notifications/service.d.ts +58 -0
  173. package/dist/server/notifications/service.js +41 -0
  174. package/dist/server/paths/appDataHome.d.ts +19 -0
  175. package/dist/server/paths/appDataHome.js +68 -0
  176. package/dist/server/private-media/crypto.d.ts +37 -0
  177. package/dist/server/private-media/crypto.js +111 -0
  178. package/dist/server/private-media/index-store.d.ts +31 -0
  179. package/dist/server/private-media/index-store.js +36 -0
  180. package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
  181. package/dist/server/private-media/mountPrivateRoutes.js +219 -0
  182. package/dist/server/private-media/pipeline.d.ts +37 -0
  183. package/dist/server/private-media/pipeline.js +97 -0
  184. package/dist/server/private-media/unlock.d.ts +52 -0
  185. package/dist/server/private-media/unlock.js +55 -0
  186. package/dist/server/resume/index.d.ts +2 -0
  187. package/dist/server/resume/index.js +2 -0
  188. package/dist/server/resume/routes.d.ts +41 -0
  189. package/dist/server/resume/routes.js +50 -0
  190. package/dist/server/resume/store.d.ts +37 -0
  191. package/dist/server/resume/store.js +38 -0
  192. package/dist/server/retention/archive.d.ts +78 -0
  193. package/dist/server/retention/archive.js +284 -0
  194. package/dist/server/retention/fleet.d.ts +50 -0
  195. package/dist/server/retention/fleet.js +66 -0
  196. package/dist/server/retention/index.d.ts +38 -0
  197. package/dist/server/retention/index.js +38 -0
  198. package/dist/server/retention/policy.d.ts +263 -0
  199. package/dist/server/retention/policy.js +264 -0
  200. package/dist/server/retention/store.d.ts +101 -0
  201. package/dist/server/retention/store.js +251 -0
  202. package/dist/server/retention/sweeper.d.ts +69 -0
  203. package/dist/server/retention/sweeper.js +289 -0
  204. package/dist/server/retention/target.d.ts +143 -0
  205. package/dist/server/retention/target.js +438 -0
  206. package/dist/server/search/index.d.ts +2 -0
  207. package/dist/server/search/index.js +6 -0
  208. package/dist/server/search/pgSearchProvider.d.ts +91 -0
  209. package/dist/server/search/pgSearchProvider.js +330 -0
  210. package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
  211. package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
  212. package/dist/server/search/pgSearchSchema.d.ts +81 -0
  213. package/dist/server/search/pgSearchSchema.js +89 -0
  214. package/dist/server/sharing/index.d.ts +18 -0
  215. package/dist/server/sharing/index.js +16 -0
  216. package/dist/server/sharing/migrations.d.ts +6 -0
  217. package/dist/server/sharing/migrations.js +79 -0
  218. package/dist/server/sharing/router.d.ts +111 -0
  219. package/dist/server/sharing/router.js +93 -0
  220. package/dist/server/sharing/sql.d.ts +67 -0
  221. package/dist/server/sharing/sql.js +88 -0
  222. package/dist/server/sharing/store.d.ts +173 -0
  223. package/dist/server/sharing/store.js +484 -0
  224. package/dist/server/sqlite/backup.d.ts +48 -0
  225. package/dist/server/sqlite/backup.js +97 -0
  226. package/dist/server/sqlite/browse.d.ts +42 -0
  227. package/dist/server/sqlite/browse.js +84 -0
  228. package/dist/server/sqlite/export.d.ts +32 -0
  229. package/dist/server/sqlite/export.js +42 -0
  230. package/dist/server/sqlite/fts5.d.ts +106 -0
  231. package/dist/server/sqlite/fts5.js +154 -0
  232. package/dist/server/sqlite/health.d.ts +44 -0
  233. package/dist/server/sqlite/health.js +52 -0
  234. package/dist/server/sqlite/import.d.ts +36 -0
  235. package/dist/server/sqlite/import.js +108 -0
  236. package/dist/server/sqlite/index.d.ts +9 -0
  237. package/dist/server/sqlite/index.js +15 -0
  238. package/dist/server/sqlite/readonlySql.d.ts +15 -0
  239. package/dist/server/sqlite/readonlySql.js +65 -0
  240. package/dist/server/sqlite/stats.d.ts +22 -0
  241. package/dist/server/sqlite/stats.js +32 -0
  242. package/dist/server/sqlite/viewer.d.ts +17 -0
  243. package/dist/server/sqlite/viewer.js +56 -0
  244. package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
  245. package/dist/server/storage/binaryServerProcessing.js +81 -0
  246. package/dist/server/storage/binaryServerStore.d.ts +3 -0
  247. package/dist/server/storage/binaryServerStore.js +95 -0
  248. package/dist/server/storage/diskStore.d.ts +2 -0
  249. package/dist/server/storage/diskStore.js +88 -0
  250. package/dist/server/storage/files/adapter.d.ts +33 -0
  251. package/dist/server/storage/files/adapter.js +203 -0
  252. package/dist/server/storage/files/catalogue.d.ts +72 -0
  253. package/dist/server/storage/files/catalogue.js +1033 -0
  254. package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
  255. package/dist/server/storage/files/chunkedUpload.js +70 -0
  256. package/dist/server/storage/files/fileRoutes.d.ts +51 -0
  257. package/dist/server/storage/files/fileRoutes.js +405 -0
  258. package/dist/server/storage/files/index.d.ts +9 -0
  259. package/dist/server/storage/files/index.js +11 -0
  260. package/dist/server/storage/files/migrations.d.ts +2 -0
  261. package/dist/server/storage/files/migrations.js +163 -0
  262. package/dist/server/storage/files/mimeByExt.d.ts +1 -0
  263. package/dist/server/storage/files/mimeByExt.js +13 -0
  264. package/dist/server/storage/files/stack.d.ts +136 -0
  265. package/dist/server/storage/files/stack.js +132 -0
  266. package/dist/server/storage/files/storageOps.d.ts +54 -0
  267. package/dist/server/storage/files/storageOps.js +110 -0
  268. package/dist/server/storage/files/types.d.ts +261 -0
  269. package/dist/server/storage/files/types.js +22 -0
  270. package/dist/server/storage/imageThumbnail.d.ts +3 -0
  271. package/dist/server/storage/imageThumbnail.js +50 -0
  272. package/dist/server/storage/index.d.ts +15 -0
  273. package/dist/server/storage/index.js +27 -0
  274. package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
  275. package/dist/server/storage/mountMediaRoutes.js +141 -0
  276. package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
  277. package/dist/server/storage/objectStoreMediaStore.js +57 -0
  278. package/dist/server/storage/s3ObjectStore.d.ts +63 -0
  279. package/dist/server/storage/s3ObjectStore.js +187 -0
  280. package/dist/server/storage/sources.d.ts +47 -0
  281. package/dist/server/storage/sources.js +148 -0
  282. package/dist/server/storage/streamRange.d.ts +19 -0
  283. package/dist/server/storage/streamRange.js +69 -0
  284. package/dist/server/storage/streamToken.d.ts +16 -0
  285. package/dist/server/storage/streamToken.js +54 -0
  286. package/dist/server/storage/transcodeJobs.d.ts +65 -0
  287. package/dist/server/storage/transcodeJobs.js +104 -0
  288. package/dist/server/storage/types.d.ts +193 -0
  289. package/dist/server/storage/types.js +41 -0
  290. package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
  291. package/dist/server/storage/videoDerivativeKeys.js +149 -0
  292. package/dist/server/storage/videoProcessor.d.ts +30 -0
  293. package/dist/server/storage/videoProcessor.js +148 -0
  294. package/dist/server/sync/alarm.d.ts +34 -0
  295. package/dist/server/sync/alarm.js +91 -0
  296. package/dist/server/sync/commands.d.ts +87 -0
  297. package/dist/server/sync/commands.js +241 -0
  298. package/dist/server/sync/engine.d.ts +62 -0
  299. package/dist/server/sync/engine.js +184 -0
  300. package/dist/server/sync/http.d.ts +106 -0
  301. package/dist/server/sync/http.js +243 -0
  302. package/dist/server/sync/index.d.ts +41 -0
  303. package/dist/server/sync/index.js +41 -0
  304. package/dist/server/sync/opLog.d.ts +61 -0
  305. package/dist/server/sync/opLog.js +96 -0
  306. package/dist/server/sync/planner.d.ts +31 -0
  307. package/dist/server/sync/planner.js +30 -0
  308. package/dist/server/sync/status.d.ts +63 -0
  309. package/dist/server/sync/status.js +49 -0
  310. package/dist/server/sync/timer.d.ts +52 -0
  311. package/dist/server/sync/timer.js +170 -0
  312. package/dist/server/sync/tokens.d.ts +25 -0
  313. package/dist/server/sync/tokens.js +51 -0
  314. package/dist/server/sync/types.d.ts +101 -0
  315. package/dist/server/sync/types.js +29 -0
  316. package/dist/server/ws/eventBus.d.ts +14 -0
  317. package/dist/server/ws/eventBus.js +42 -0
  318. package/dist/server/ws/hub.d.ts +20 -0
  319. package/dist/server/ws/hub.js +154 -0
  320. package/dist/server/ws/index.d.ts +5 -0
  321. package/dist/server/ws/index.js +7 -0
  322. package/dist/server/ws/presence.d.ts +11 -0
  323. package/dist/server/ws/presence.js +20 -0
  324. package/dist/server/ws/room.d.ts +17 -0
  325. package/dist/server/ws/room.js +23 -0
  326. package/dist/server/ws/types.d.ts +122 -0
  327. package/dist/server/ws/types.js +1 -0
  328. package/package.json +234 -0
  329. package/src/declaredDepsAreImported.spec.ts +87 -0
  330. package/src/leafSubpathsImportNothing.spec.ts +358 -0
  331. package/src/lockfileIsTracked.spec.ts +50 -0
  332. package/src/namedSubpathsResolve.spec.ts +122 -0
  333. package/src/noPathDeps.spec.ts +24 -0
  334. package/src/publishShape.spec.ts +89 -0
  335. package/src/server/activity/index.ts +59 -0
  336. package/src/server/activity/migrations.ts +67 -0
  337. package/src/server/activity/shareSource.ts +57 -0
  338. package/src/server/activity/store.spec.ts +255 -0
  339. package/src/server/activity/store.ts +368 -0
  340. package/src/server/app.spec.ts +243 -0
  341. package/src/server/app.ts +89 -0
  342. package/src/server/audit/index.ts +2 -0
  343. package/src/server/audit/migrations.ts +88 -0
  344. package/src/server/audit/sqliteSink.test.ts +200 -0
  345. package/src/server/audit/sqliteSink.ts +162 -0
  346. package/src/server/auth/cookie.spec.ts +46 -0
  347. package/src/server/auth/cookie.ts +42 -0
  348. package/src/server/auth/csrf.spec.ts +81 -0
  349. package/src/server/auth/csrf.ts +83 -0
  350. package/src/server/auth/fileTokenSigner.ts +40 -0
  351. package/src/server/auth/getOnly.spec.ts +53 -0
  352. package/src/server/auth/getOnly.ts +32 -0
  353. package/src/server/auth/index.ts +51 -0
  354. package/src/server/auth/jwt.spec.ts +106 -0
  355. package/src/server/auth/jwt.ts +75 -0
  356. package/src/server/auth/loginThrottle.spec.ts +213 -0
  357. package/src/server/auth/loginThrottle.ts +261 -0
  358. package/src/server/auth/middleware.spec.ts +148 -0
  359. package/src/server/auth/middleware.ts +56 -0
  360. package/src/server/auth/password.spec.ts +48 -0
  361. package/src/server/auth/password.ts +71 -0
  362. package/src/server/auth/sessionStore.spec.ts +189 -0
  363. package/src/server/auth/sessionStore.ts +261 -0
  364. package/src/server/auth/totp.spec.ts +73 -0
  365. package/src/server/auth/totp.ts +92 -0
  366. package/src/server/auth/types.ts +146 -0
  367. package/src/server/auth/vault.spec.ts +51 -0
  368. package/src/server/auth/vault.ts +71 -0
  369. package/src/server/context.ts +37 -0
  370. package/src/server/ctgr.spec.ts +138 -0
  371. package/src/server/ctgr.ts +108 -0
  372. package/src/server/db/kysely.spec.ts +62 -0
  373. package/src/server/db/kysely.ts +21 -0
  374. package/src/server/db/plumbingTypes.ts +126 -0
  375. package/src/server/dropzone/duplicates.ts +121 -0
  376. package/src/server/dropzone/index.ts +30 -0
  377. package/src/server/dropzone/scan.spec.ts +255 -0
  378. package/src/server/dropzone/scan.ts +342 -0
  379. package/src/server/errors.spec.ts +76 -0
  380. package/src/server/errors.ts +22 -0
  381. package/src/server/guard/argon2Binding.spec.ts +117 -0
  382. package/src/server/guard/argon2Binding.ts +195 -0
  383. package/src/server/guard/argon2Kat.spec.ts +250 -0
  384. package/src/server/guard/guard.spec.ts +390 -0
  385. package/src/server/guard/index.ts +6 -0
  386. package/src/server/guard/kdfWorker.spec.ts +283 -0
  387. package/src/server/guard/kdfWorker.ts +367 -0
  388. package/src/server/guard/kdfWorkerEntry.ts +16 -0
  389. package/src/server/guard/passwordVerifier.spec.ts +71 -0
  390. package/src/server/guard/passwordVerifier.ts +175 -0
  391. package/src/server/guard/revocationStore.spec.ts +135 -0
  392. package/src/server/guard/revocationStore.ts +291 -0
  393. package/src/server/guard/sessionSigner.ts +425 -0
  394. package/src/server/guard/verifier.ts +27 -0
  395. package/src/server/incidents/channels.ts +487 -0
  396. package/src/server/incidents/checks.ts +120 -0
  397. package/src/server/incidents/incidents.spec.ts +463 -0
  398. package/src/server/incidents/index.ts +96 -0
  399. package/src/server/incidents/migrations.ts +66 -0
  400. package/src/server/incidents/notifier.ts +215 -0
  401. package/src/server/incidents/reporter.ts +110 -0
  402. package/src/server/incidents/routes.ts +314 -0
  403. package/src/server/incidents/store.ts +329 -0
  404. package/src/server/incidents/types.ts +116 -0
  405. package/src/server/index.ts +248 -0
  406. package/src/server/jobs/fireQueue.spec.ts +85 -0
  407. package/src/server/jobs/fireQueue.ts +37 -0
  408. package/src/server/jobs/index.ts +26 -0
  409. package/src/server/jobs/logger.ts +16 -0
  410. package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
  411. package/src/server/jobs/plainjobAdapter.ts +54 -0
  412. package/src/server/jobs/polling.spec.ts +219 -0
  413. package/src/server/jobs/polling.ts +81 -0
  414. package/src/server/jobs/runner.spec.ts +213 -0
  415. package/src/server/jobs/runner.ts +213 -0
  416. package/src/server/jobs/timeout.spec.ts +35 -0
  417. package/src/server/jobs/timeout.ts +53 -0
  418. package/src/server/jobs/types.ts +188 -0
  419. package/src/server/master-lock/guard.spec.ts +347 -0
  420. package/src/server/master-lock/guard.ts +290 -0
  421. package/src/server/master-lock/index.ts +44 -0
  422. package/src/server/master-lock/lockPage.spec.ts +88 -0
  423. package/src/server/master-lock/lockPage.ts +313 -0
  424. package/src/server/master-lock/masterLock.spec.ts +338 -0
  425. package/src/server/master-lock/masterLock.ts +627 -0
  426. package/src/server/master-lock/principals.spec.ts +378 -0
  427. package/src/server/master-lock/principals.ts +175 -0
  428. package/src/server/master-lock/seed.spec.ts +69 -0
  429. package/src/server/master-lock/seed.ts +112 -0
  430. package/src/server/master-lock/store.ts +35 -0
  431. package/src/server/media-bun/image.spec.ts +115 -0
  432. package/src/server/media-bun/image.ts +134 -0
  433. package/src/server/media-bun/index.ts +52 -0
  434. package/src/server/media-bun/streaming.spec.ts +134 -0
  435. package/src/server/media-bun/streaming.ts +347 -0
  436. package/src/server/media-bun/video.spec.ts +115 -0
  437. package/src/server/media-bun/video.ts +232 -0
  438. package/src/server/metrics/metricsBuffer.ts +92 -0
  439. package/src/server/metrics/normalizeRoute.spec.ts +51 -0
  440. package/src/server/metrics/normalizeRoute.ts +63 -0
  441. package/src/server/middleware/correlationId.ts +24 -0
  442. package/src/server/middleware/corsAllowlist.spec.ts +118 -0
  443. package/src/server/middleware/corsAllowlist.ts +69 -0
  444. package/src/server/middleware/errorEnvelope.ts +38 -0
  445. package/src/server/middleware/index.ts +19 -0
  446. package/src/server/middleware/localOrigin.spec.ts +168 -0
  447. package/src/server/middleware/localOrigin.ts +139 -0
  448. package/src/server/middleware/rateLimit.spec.ts +72 -0
  449. package/src/server/middleware/rateLimit.ts +125 -0
  450. package/src/server/middleware/requestLogger.ts +95 -0
  451. package/src/server/middleware/securityHeaders.ts +78 -0
  452. package/src/server/middleware/zValidator.spec.ts +251 -0
  453. package/src/server/middleware/zValidator.ts +114 -0
  454. package/src/server/migration/runner.spec.ts +88 -0
  455. package/src/server/migration/runner.ts +87 -0
  456. package/src/server/migration/types.ts +17 -0
  457. package/src/server/net/outboundGuard.spec.ts +90 -0
  458. package/src/server/net/outboundGuard.ts +205 -0
  459. package/src/server/notifications/hub.ts +245 -0
  460. package/src/server/notifications/index.ts +77 -0
  461. package/src/server/notifications/migrations.ts +39 -0
  462. package/src/server/notifications/routes.ts +177 -0
  463. package/src/server/notifications/service.spec.ts +305 -0
  464. package/src/server/notifications/service.ts +112 -0
  465. package/src/server/paths/appDataHome.spec.ts +87 -0
  466. package/src/server/paths/appDataHome.ts +84 -0
  467. package/src/server/private-media/blobRoute.spec.ts +165 -0
  468. package/src/server/private-media/crypto.spec.ts +81 -0
  469. package/src/server/private-media/crypto.ts +155 -0
  470. package/src/server/private-media/gatePolicy.spec.ts +98 -0
  471. package/src/server/private-media/index-store.ts +83 -0
  472. package/src/server/private-media/mountPrivateRoutes.ts +265 -0
  473. package/src/server/private-media/pipeline.spec.ts +111 -0
  474. package/src/server/private-media/pipeline.ts +170 -0
  475. package/src/server/private-media/routes.spec.ts +265 -0
  476. package/src/server/private-media/unlock.ts +112 -0
  477. package/src/server/resume/index.ts +8 -0
  478. package/src/server/resume/resume.spec.ts +118 -0
  479. package/src/server/resume/routes.ts +92 -0
  480. package/src/server/resume/store.ts +82 -0
  481. package/src/server/retention/archive.spec.ts +181 -0
  482. package/src/server/retention/archive.ts +353 -0
  483. package/src/server/retention/fleet.ts +104 -0
  484. package/src/server/retention/index.ts +101 -0
  485. package/src/server/retention/policy.spec.ts +132 -0
  486. package/src/server/retention/policy.ts +392 -0
  487. package/src/server/retention/store.spec.ts +236 -0
  488. package/src/server/retention/store.ts +338 -0
  489. package/src/server/retention/sweeper.spec.ts +321 -0
  490. package/src/server/retention/sweeper.ts +367 -0
  491. package/src/server/retention/target.spec.ts +592 -0
  492. package/src/server/retention/target.ts +613 -0
  493. package/src/server/search/index.ts +12 -0
  494. package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
  495. package/src/server/search/pgSearchProvider.ts +421 -0
  496. package/src/server/search/pgSearchSchema.ts +147 -0
  497. package/src/server/sharing/index.ts +65 -0
  498. package/src/server/sharing/migrations.ts +128 -0
  499. package/src/server/sharing/router.spec.ts +167 -0
  500. package/src/server/sharing/router.ts +194 -0
  501. package/src/server/sharing/sql.ts +109 -0
  502. package/src/server/sharing/store.spec.ts +408 -0
  503. package/src/server/sharing/store.ts +737 -0
  504. package/src/server/sqlite/backup.spec.ts +114 -0
  505. package/src/server/sqlite/backup.ts +154 -0
  506. package/src/server/sqlite/browse.spec.ts +70 -0
  507. package/src/server/sqlite/browse.ts +120 -0
  508. package/src/server/sqlite/export.spec.ts +43 -0
  509. package/src/server/sqlite/export.ts +74 -0
  510. package/src/server/sqlite/fts5.spec.ts +175 -0
  511. package/src/server/sqlite/fts5.ts +255 -0
  512. package/src/server/sqlite/health.spec.ts +79 -0
  513. package/src/server/sqlite/health.ts +105 -0
  514. package/src/server/sqlite/import.spec.ts +75 -0
  515. package/src/server/sqlite/import.ts +141 -0
  516. package/src/server/sqlite/index.ts +64 -0
  517. package/src/server/sqlite/readonlySql.spec.ts +58 -0
  518. package/src/server/sqlite/readonlySql.ts +67 -0
  519. package/src/server/sqlite/stats.spec.ts +21 -0
  520. package/src/server/sqlite/stats.ts +61 -0
  521. package/src/server/sqlite/viewer.spec.ts +127 -0
  522. package/src/server/sqlite/viewer.ts +72 -0
  523. package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
  524. package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
  525. package/src/server/storage/binaryServerProcessing.ts +127 -0
  526. package/src/server/storage/binaryServerStore.ts +102 -0
  527. package/src/server/storage/chunked.integration.spec.ts +185 -0
  528. package/src/server/storage/diskStore.ts +83 -0
  529. package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
  530. package/src/server/storage/files/adapter.photo.spec.ts +128 -0
  531. package/src/server/storage/files/adapter.spec.ts +121 -0
  532. package/src/server/storage/files/adapter.ts +255 -0
  533. package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
  534. package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
  535. package/src/server/storage/files/catalogue.private.spec.ts +106 -0
  536. package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
  537. package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
  538. package/src/server/storage/files/catalogue.spec.ts +133 -0
  539. package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
  540. package/src/server/storage/files/catalogue.ts +1326 -0
  541. package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
  542. package/src/server/storage/files/chunkedUpload.ts +146 -0
  543. package/src/server/storage/files/collections.spec.ts +294 -0
  544. package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
  545. package/src/server/storage/files/fileRoutes.spec.ts +327 -0
  546. package/src/server/storage/files/fileRoutes.ts +462 -0
  547. package/src/server/storage/files/index.ts +66 -0
  548. package/src/server/storage/files/migrations.ts +187 -0
  549. package/src/server/storage/files/mimeByExt.ts +14 -0
  550. package/src/server/storage/files/stack.spec.ts +258 -0
  551. package/src/server/storage/files/stack.ts +284 -0
  552. package/src/server/storage/files/storageOps.spec.ts +126 -0
  553. package/src/server/storage/files/storageOps.ts +213 -0
  554. package/src/server/storage/files/types.ts +285 -0
  555. package/src/server/storage/imageThumbnail.spec.ts +81 -0
  556. package/src/server/storage/imageThumbnail.ts +60 -0
  557. package/src/server/storage/index.ts +122 -0
  558. package/src/server/storage/mountMediaRoutes.ts +231 -0
  559. package/src/server/storage/objectStore.spec.ts +272 -0
  560. package/src/server/storage/objectStoreMediaStore.ts +63 -0
  561. package/src/server/storage/s3ObjectStore.ts +239 -0
  562. package/src/server/storage/sources.ts +245 -0
  563. package/src/server/storage/storage.spec.ts +219 -0
  564. package/src/server/storage/streamRange.ts +92 -0
  565. package/src/server/storage/streamToken.ts +76 -0
  566. package/src/server/storage/transcode.integration.spec.ts +134 -0
  567. package/src/server/storage/transcodeJobs.spec.ts +152 -0
  568. package/src/server/storage/transcodeJobs.ts +171 -0
  569. package/src/server/storage/types.ts +230 -0
  570. package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
  571. package/src/server/storage/videoDerivativeKeys.ts +172 -0
  572. package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
  573. package/src/server/storage/videoProcessor.ts +229 -0
  574. package/src/server/sync/alarm.spec.ts +149 -0
  575. package/src/server/sync/alarm.ts +137 -0
  576. package/src/server/sync/commands.spec.ts +145 -0
  577. package/src/server/sync/commands.ts +361 -0
  578. package/src/server/sync/engine.spec.ts +496 -0
  579. package/src/server/sync/engine.ts +255 -0
  580. package/src/server/sync/http.ts +316 -0
  581. package/src/server/sync/httpRemote.spec.ts +158 -0
  582. package/src/server/sync/index.ts +90 -0
  583. package/src/server/sync/opLog.spec.ts +110 -0
  584. package/src/server/sync/opLog.ts +189 -0
  585. package/src/server/sync/planner.spec.ts +53 -0
  586. package/src/server/sync/planner.ts +62 -0
  587. package/src/server/sync/status.spec.ts +92 -0
  588. package/src/server/sync/status.ts +108 -0
  589. package/src/server/sync/timer.spec.ts +150 -0
  590. package/src/server/sync/timer.ts +207 -0
  591. package/src/server/sync/tokens.spec.ts +38 -0
  592. package/src/server/sync/tokens.ts +94 -0
  593. package/src/server/sync/types.ts +108 -0
  594. package/src/server/ws/eventBus.spec.ts +67 -0
  595. package/src/server/ws/eventBus.ts +46 -0
  596. package/src/server/ws/hub.integration.spec.ts +93 -0
  597. package/src/server/ws/hub.spec.ts +282 -0
  598. package/src/server/ws/hub.ts +168 -0
  599. package/src/server/ws/index.ts +20 -0
  600. package/src/server/ws/presence.spec.ts +27 -0
  601. package/src/server/ws/presence.ts +26 -0
  602. package/src/server/ws/room.spec.ts +36 -0
  603. package/src/server/ws/room.ts +29 -0
  604. package/src/server/ws/types.ts +134 -0
  605. package/src/skippedTestsAreAnswered.spec.ts +215 -0
  606. package/src/testTmpRoot.spec.ts +102 -0
@@ -0,0 +1,251 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { Hono } from 'hono';
3
+ import { z } from 'zod';
4
+ import { ChunkStore } from 'cursedbelt-core/ctgr/chunk-store';
5
+ import { encodeRequest } from 'cursedbelt-core/ctgr';
6
+ import type { CursedbeltEnv } from '../context';
7
+ import { CTGR_ROUTE_PATTERN, unGETify } from '../ctgr';
8
+ import {
9
+ toValidationIssues,
10
+ VALIDATION_ERROR_CODE,
11
+ type ValidationIssue,
12
+ validationEnvelope,
13
+ } from '../errors';
14
+ import { zValidator } from './zValidator';
15
+
16
+ const ORIGIN = 'http://localhost';
17
+ const json = (path: string, body: unknown) =>
18
+ new Request(ORIGIN + path, {
19
+ method: 'POST',
20
+ headers: { 'content-type': 'application/json' },
21
+ body: JSON.stringify(body),
22
+ });
23
+
24
+ interface ValidationEnvelope {
25
+ error: { message: string; code?: string; context?: { issues?: ValidationIssue[] } };
26
+ }
27
+
28
+ describe('zValidator — acceptance contract', () => {
29
+ // The spec's headline scenarios: a bad body maps every zod issue to context.issues,
30
+ // a good body passes through, an all-optional patch accepts the empty object.
31
+ const schema = z.object({ name: z.string(), age: z.number() });
32
+ const app = new Hono<CursedbeltEnv>().post('/test', zValidator(schema), (c) =>
33
+ c.json(c.get('validatedBody') as object),
34
+ );
35
+
36
+ test('maps each zod issue to a validation issue in the canonical envelope', async () => {
37
+ const res = await app.fetch(json('/test', { name: 123, age: 'old' }));
38
+ expect(res.status).toBe(400);
39
+ const body = (await res.json()) as ValidationEnvelope;
40
+ expect(body.error.context?.issues).toHaveLength(2);
41
+ expect(body.error.context?.issues?.[0].path).toBe('name');
42
+ expect(body.error.context?.issues?.[1].path).toBe('age');
43
+ expect(body.error.code).toBe('validation');
44
+ // Every issue carries a human-readable, zod-authored message.
45
+ expect(body.error.context?.issues?.[0].message).toBeTruthy();
46
+ });
47
+
48
+ test('a valid body passes through unchanged', async () => {
49
+ const res = await app.fetch(json('/test', { name: 'Alice', age: 30 }));
50
+ expect(res.status).toBe(200);
51
+ expect(await res.json()).toEqual({ name: 'Alice', age: 30 });
52
+ });
53
+
54
+ test('a partial (all-optional) patch schema accepts the empty object', async () => {
55
+ const patchApp = new Hono<CursedbeltEnv>().post('/p', zValidator(schema.partial()), (c) =>
56
+ c.json({ ok: true, body: c.get('validatedBody') as object }),
57
+ );
58
+ const res = await patchApp.fetch(json('/p', {}));
59
+ expect(res.status).toBe(200);
60
+ expect(await res.json()).toEqual({ ok: true, body: {} });
61
+ });
62
+
63
+ test('produces the envelope without an errorEnvelope onError handler mounted', async () => {
64
+ // A bare Hono app (no onError) still gets the 400 envelope — zValidator returns
65
+ // the response directly, it never throws.
66
+ const bare = new Hono().post('/x', zValidator(z.object({ a: z.string() })), (c) =>
67
+ c.json({ ok: true }),
68
+ );
69
+ const res = await bare.fetch(json('/x', { a: 1 }));
70
+ expect(res.status).toBe(400);
71
+ expect(((await res.json()) as ValidationEnvelope).error.code).toBe('validation');
72
+ });
73
+ });
74
+
75
+ describe('zValidator — typed access', () => {
76
+ test('the parsed value is readable via both c.req.valid and c.get', async () => {
77
+ const schema = z.object({ name: z.string() });
78
+ const app = new Hono<CursedbeltEnv>().post('/x', zValidator(schema), (c) => {
79
+ const viaValid = c.req.valid('json'); // typed z.infer<typeof schema>
80
+ const viaGet = c.get('validatedBody');
81
+ return c.json({ viaValid, equal: viaValid === viaGet });
82
+ });
83
+ const res = await app.fetch(json('/x', { name: 'Bo' }));
84
+ expect(await res.json()).toEqual({ viaValid: { name: 'Bo' }, equal: true });
85
+ });
86
+
87
+ test('zod coerces, defaults, and strips before the handler sees the body', async () => {
88
+ const schema = z.object({
89
+ title: z.string(),
90
+ pinned: z.boolean().default(false),
91
+ });
92
+ const app = new Hono<CursedbeltEnv>().post('/x', zValidator(schema), (c) =>
93
+ c.json(c.req.valid('json')),
94
+ );
95
+ const res = await app.fetch(json('/x', { title: 'hi', junk: 'dropped' }));
96
+ // .default() filled `pinned`; unknown `junk` stripped (default zod object mode).
97
+ expect(await res.json()).toEqual({ title: 'hi', pinned: false });
98
+ });
99
+
100
+ test('.passthrough()/looseObject keeps unknown keys for dynamic-data fields', async () => {
101
+ const schema = z.looseObject({ kind: z.string() });
102
+ const app = new Hono<CursedbeltEnv>().post('/x', zValidator(schema), (c) =>
103
+ c.json(c.req.valid('json')),
104
+ );
105
+ const res = await app.fetch(json('/x', { kind: 'step', extra: { nested: 1 } }));
106
+ expect(await res.json()).toEqual({ kind: 'step', extra: { nested: 1 } });
107
+ });
108
+ });
109
+
110
+ describe('zValidator — coercion boundary (the #1 ajv→zod regression)', () => {
111
+ test('a numeric query param uses z.coerce.number() and arrives as a number', async () => {
112
+ const query = z.object({
113
+ limit: z.coerce.number().default(20),
114
+ page: z.coerce.number().optional(),
115
+ });
116
+ const app = new Hono<CursedbeltEnv>().get('/list', zValidator(query, 'query'), (c) =>
117
+ c.json(c.req.valid('query')),
118
+ );
119
+ const res = await app.fetch(new Request(`${ORIGIN}/list?limit=5&page=3`));
120
+ expect(res.status).toBe(200);
121
+ expect(await res.json()).toEqual({ limit: 5, page: 3 });
122
+ });
123
+
124
+ test('a JSON body number is strict — a stringified number is rejected, not coerced', async () => {
125
+ const schema = z.object({ amount: z.number() });
126
+ const app = new Hono<CursedbeltEnv>().post('/x', zValidator(schema), (c) =>
127
+ c.json(c.req.valid('json')),
128
+ );
129
+ const res = await app.fetch(json('/x', { amount: '123' }));
130
+ expect(res.status).toBe(400);
131
+ expect(((await res.json()) as ValidationEnvelope).error.context?.issues?.[0].path).toBe(
132
+ 'amount',
133
+ );
134
+ });
135
+ });
136
+
137
+ describe('zValidator — date-time enforcement', () => {
138
+ // zod's z.iso.datetime() ENFORCES (ajv often only annotated); a partial date is a 400.
139
+ const schema = z.object({ startsAt: z.iso.datetime() });
140
+ const app = new Hono<CursedbeltEnv>().post('/ev', zValidator(schema), (c) =>
141
+ c.json(c.req.valid('json')),
142
+ );
143
+
144
+ test('rejects a partial date like "2024-01"', async () => {
145
+ const res = await app.fetch(json('/ev', { startsAt: '2024-01' }));
146
+ expect(res.status).toBe(400);
147
+ expect(((await res.json()) as ValidationEnvelope).error.context?.issues?.[0].path).toBe(
148
+ 'startsAt',
149
+ );
150
+ });
151
+
152
+ test('accepts a full ISO datetime', async () => {
153
+ const res = await app.fetch(json('/ev', { startsAt: '2024-01-15T10:00:00Z' }));
154
+ expect(res.status).toBe(200);
155
+ });
156
+ });
157
+
158
+ describe('zValidator — param target + nested paths', () => {
159
+ test('validates a path param', async () => {
160
+ const schema = z.object({ id: z.coerce.number() });
161
+ const app = new Hono<CursedbeltEnv>().get('/n/:id', zValidator(schema, 'param'), (c) =>
162
+ c.json(c.req.valid('param')),
163
+ );
164
+ expect(await (await app.fetch(new Request(`${ORIGIN}/n/42`))).json()).toEqual({ id: 42 });
165
+ });
166
+
167
+ test('a nested-object issue gets a dotted path', async () => {
168
+ const schema = z.object({ address: z.object({ city: z.string() }) });
169
+ const app = new Hono<CursedbeltEnv>().post('/x', zValidator(schema), (c) =>
170
+ c.json({ ok: true }),
171
+ );
172
+ const res = await app.fetch(json('/x', { address: { city: 9 } }));
173
+ expect(((await res.json()) as ValidationEnvelope).error.context?.issues?.[0].path).toBe(
174
+ 'address.city',
175
+ );
176
+ });
177
+ });
178
+
179
+ describe('zValidator — ctgr GET-tunnel integration (the protection-critical path)', () => {
180
+ // A mutation tunnels through a `/ctgr<XX>` GET twin; unGETify decodes the real body
181
+ // onto c.get('ctgrDecoded'). zValidator MUST validate that, not the empty GET body.
182
+ const schema = z.object({ body: z.string(), pinned: z.boolean().default(false) });
183
+ const makeApp = () => {
184
+ const app = new Hono<CursedbeltEnv>();
185
+ app.use(CTGR_ROUTE_PATTERN, unGETify(new ChunkStore()));
186
+ // Both the real verb route AND its GET twin run the same validated handler.
187
+ const handler = zValidator(schema);
188
+ app.on('POST', '/notes', handler, (c) => c.json({ ok: true, body: c.req.valid('json') }));
189
+ app.on('GET', '/ctgrpo/notes', handler, (c) => c.json({ ok: true, body: c.req.valid('json') }));
190
+ return app;
191
+ };
192
+
193
+ test('a tunnelled POST validates the decoded body (not the empty GET body)', async () => {
194
+ const enc = await encodeRequest('POST', '/notes', { body: 'hi' });
195
+ const res = await makeApp().fetch(new Request(ORIGIN + enc.chunks[0].url));
196
+ expect(res.status).toBe(200);
197
+ // Decoded body validated + defaulted, despite the transport being a body-less GET.
198
+ expect(await res.json()).toEqual({ ok: true, body: { body: 'hi', pinned: false } });
199
+ });
200
+
201
+ test('a tunnelled POST with an invalid decoded body returns the 400 envelope', async () => {
202
+ const enc = await encodeRequest('POST', '/notes', { body: 123 });
203
+ const res = await makeApp().fetch(new Request(ORIGIN + enc.chunks[0].url));
204
+ expect(res.status).toBe(400);
205
+ const env = (await res.json()) as ValidationEnvelope;
206
+ expect(env.error.code).toBe('validation');
207
+ expect(env.error.context?.issues?.[0].path).toBe('body');
208
+ });
209
+
210
+ test('the direct (non-tunnelled) verb route still validates the live body', async () => {
211
+ const res = await makeApp().fetch(json('/notes', { body: 'direct' }));
212
+ expect(res.status).toBe(200);
213
+ expect(await res.json()).toEqual({ ok: true, body: { body: 'direct', pinned: false } });
214
+ });
215
+ });
216
+
217
+ describe('validationEnvelope — shared builder contract (cursedbelt-core/errors)', () => {
218
+ test('toValidationIssues maps zod issues to { path, message }', () => {
219
+ const result = z.object({ a: z.string(), b: z.number() }).safeParse({ a: 1, b: 'x' });
220
+ expect(result.success).toBe(false);
221
+ if (result.success) return;
222
+ expect(toValidationIssues(result.error)).toEqual([
223
+ { path: 'a', message: expect.any(String) },
224
+ { path: 'b', message: expect.any(String) },
225
+ ]);
226
+ });
227
+
228
+ test('validationEnvelope builds the canonical rich { error } shape from issues', () => {
229
+ const issues: ValidationIssue[] = [{ path: 'x', message: 'bad' }];
230
+ const { error } = validationEnvelope(issues);
231
+ expect(error).toMatchObject({
232
+ name: 'ApiError',
233
+ message: 'Validation failed',
234
+ code: 'validation',
235
+ status: 400,
236
+ context: { issues },
237
+ isOperational: true,
238
+ });
239
+ expect(typeof error.timestamp).toBe('string');
240
+ expect(validationEnvelope(issues, 'Custom').error.message).toBe('Custom');
241
+ expect(VALIDATION_ERROR_CODE).toBe('validation');
242
+ });
243
+
244
+ test('validationEnvelope accepts a ZodError directly (the middleware path)', () => {
245
+ const result = z.object({ name: z.string() }).safeParse({ name: 1 });
246
+ if (result.success) return;
247
+ const env = validationEnvelope(result.error);
248
+ expect(env.error.code).toBe('validation');
249
+ expect(env.error.context?.issues).toEqual([{ path: 'name', message: expect.any(String) }]);
250
+ });
251
+ });
@@ -0,0 +1,114 @@
1
+ /*
2
+ * zValidator — the cursedbelt request-validation middleware. ONE zod schema per
3
+ * endpoint drives server validation, the client form resolver, and the inferred TS
4
+ * types; this is the server half. A zod failure is mapped to the canonical
5
+ * `{ error: { message, code, context: { issues } } }` envelope via the shared
6
+ * `validationEnvelope` builder (cursedbelt-core/errors) — the SAME builder cursedshell's
7
+ * service-router uses — so clients parse field-level errors identically everywhere,
8
+ * with no per-route (or per-layer) validation drift.
9
+ *
10
+ * Two things make this a cursedbelt primitive rather than a bare `@hono/zod-validator`
11
+ * call:
12
+ *
13
+ * 1. **ctgr-awareness.** A mutating request tunnels through a `/ctgr<XX>` GET twin
14
+ * (task 25); `unGETify` decodes the real body onto `c.get('ctgrDecoded').body`.
15
+ * The raw request that reaches this middleware is a *GET with no body*, so reading
16
+ * `c.req.json()` would validate `undefined` and every tunneled call would 400.
17
+ * We therefore read the ctgr-decoded body first and fall back to the live request
18
+ * only for direct (non-tunnelled) calls. This is exactly why a route must use
19
+ * `zValidator` and never the raw `@hono/zod-validator`.
20
+ * 2. **The canonical envelope.** `@hono/zod-validator`'s default failure body is its
21
+ * own shape; cursedbelt maps every zod issue to a `ValidationIssue` under
22
+ * `context.issues` with the `validation` error code — the shared contract the
23
+ * client data layer parses.
24
+ *
25
+ * On success the parsed value (zod has coerced `z.coerce.*`, applied `.default()`s,
26
+ * and stripped unknown keys) is exposed two ways so handlers can use whichever reads
27
+ * best: the idiomatic typed `c.req.valid('json')`, and the convenience
28
+ * `c.get('validatedBody')`.
29
+ */
30
+ import type { Env, MiddlewareHandler } from 'hono';
31
+ import type { ZodType, z } from 'zod';
32
+ import type { CursedbeltEnv } from '../context';
33
+ import { validationEnvelope } from '../errors';
34
+
35
+ /** The request locations a schema can validate. Mirrors Hono's body/non-body split. */
36
+ export type ZValidatorTarget = 'json' | 'form' | 'query' | 'param';
37
+
38
+ /**
39
+ * Read the value a target validates against. The body targets (`json`/`form`) prefer
40
+ * the ctgr-decoded body so a tunnelled mutation validates its real payload; the
41
+ * URL-bound targets (`query`/`param`) are never tunnelled (GETs go direct) and read
42
+ * from the live request. A query object collapses single-valued keys to a string so
43
+ * `z.string()`/`z.coerce.number()` field schemas see a scalar (repeated keys stay an
44
+ * array) — matching Hono's own validator semantics.
45
+ */
46
+ async function readTarget(
47
+ c: Parameters<MiddlewareHandler<CursedbeltEnv>>[0],
48
+ target: ZValidatorTarget,
49
+ ): Promise<unknown> {
50
+ if (target === 'json' || target === 'form') {
51
+ const decoded = c.get('ctgrDecoded');
52
+ if (decoded) return decoded.body;
53
+ if (target === 'json') return c.req.json().catch(() => undefined);
54
+ return c.req.parseBody();
55
+ }
56
+ if (target === 'param') return c.req.param();
57
+ return Object.fromEntries(
58
+ Object.entries(c.req.queries()).map(([key, values]) =>
59
+ values.length === 1 ? [key, values[0]] : [key, values],
60
+ ),
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Validate a request `target` against a zod `schema`. Mount per route ahead of the
66
+ * handler:
67
+ *
68
+ * ```ts
69
+ * register('post', '/notes', zValidator(CreateNoteSchema), (c) => {
70
+ * const body = c.req.valid('json'); // typed z.infer<typeof CreateNoteSchema>
71
+ * // …
72
+ * });
73
+ * ```
74
+ *
75
+ * On failure responds `400` with the canonical envelope (field errors under
76
+ * `context.issues`); never throws, so it produces the same body whether or not the
77
+ * app mounts the {@link errorEnvelope} `onError` handler. On success the parsed value
78
+ * is available as both `c.req.valid(target)` and `c.get('validatedBody')`.
79
+ */
80
+ export function zValidator<
81
+ Schema extends ZodType,
82
+ Target extends ZValidatorTarget = 'json',
83
+ E extends Env = CursedbeltEnv,
84
+ P extends string = string,
85
+ >(
86
+ schema: Schema,
87
+ target: Target = 'json' as Target,
88
+ ): MiddlewareHandler<
89
+ E,
90
+ P,
91
+ { in: { [K in Target]: z.input<Schema> }; out: { [K in Target]: z.output<Schema> } }
92
+ > {
93
+ const handler: MiddlewareHandler<CursedbeltEnv> = async (c, next) => {
94
+ const raw = await readTarget(c, target);
95
+ const result = schema.safeParse(raw);
96
+ if (!result.success) {
97
+ return c.json(validationEnvelope(result.error), 400);
98
+ }
99
+ // Expose the parsed value both ways (typed `c.req.valid` + convenience getter).
100
+ c.req.addValidatedData(target, result.data as Record<string, unknown>);
101
+ c.set('validatedBody', result.data);
102
+ await next();
103
+ };
104
+ // The body is written against the concrete `CursedbeltEnv` (so the ctgr/validatedBody
105
+ // accessors type-check); the public signature is the generic, schema-typed
106
+ // MiddlewareHandler that flows `c.req.valid(target)` into the handler. Hono's Context
107
+ // is invariant in its Env, so this widening goes through `unknown` — the same cast
108
+ // a hand-rolled typed middleware (e.g. @hono/zod-validator) performs.
109
+ return handler as unknown as MiddlewareHandler<
110
+ E,
111
+ P,
112
+ { in: { [K in Target]: z.input<Schema> }; out: { [K in Target]: z.output<Schema> } }
113
+ >;
114
+ }
@@ -0,0 +1,88 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { addColumnIfMissing, applyRecommendedPragmas, columnExists, runMigrations } from './runner';
4
+ import type { Migration } from './types';
5
+
6
+ describe('runMigrations', () => {
7
+ test('applies pending migrations once, in order, and is idempotent', () => {
8
+ const db = new Database(':memory:');
9
+ const migs: Migration[] = [
10
+ { name: '0001_users', up: (d) => d.run('CREATE TABLE users (id TEXT PRIMARY KEY)') },
11
+ { name: '0002_email', up: (d) => addColumnIfMissing(d, 'users', 'email', 'TEXT') },
12
+ ];
13
+
14
+ expect(runMigrations(db, migs)).toEqual(['0001_users', '0002_email']);
15
+ expect(columnExists(db, 'users', 'email')).toBe(true);
16
+ // _migrations records exactly the applied set.
17
+ const recorded = db
18
+ .query('SELECT name FROM _migrations ORDER BY name')
19
+ .all()
20
+ .map((r) => (r as { name: string }).name);
21
+ expect(recorded).toEqual(['0001_users', '0002_email']);
22
+
23
+ // Re-running is a no-op.
24
+ expect(runMigrations(db, migs)).toEqual([]);
25
+
26
+ // Appending a new migration runs only the new one.
27
+ const migs2: Migration[] = [
28
+ ...migs,
29
+ { name: '0003_name', up: (d) => addColumnIfMissing(d, 'users', 'name', 'TEXT') },
30
+ ];
31
+ expect(runMigrations(db, migs2)).toEqual(['0003_name']);
32
+ });
33
+
34
+ test('a throwing migration rolls back its transaction and is not recorded', () => {
35
+ const db = new Database(':memory:');
36
+ const migs: Migration[] = [
37
+ {
38
+ name: '0001_boom',
39
+ up: (d) => {
40
+ d.run('CREATE TABLE t (id TEXT)');
41
+ throw new Error('boom');
42
+ },
43
+ },
44
+ ];
45
+ expect(() => runMigrations(db, migs)).toThrow('boom');
46
+ // The CREATE TABLE inside the failed transaction was rolled back...
47
+ const tables = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='t'").all();
48
+ expect(tables).toHaveLength(0);
49
+ // ...and the migration was not recorded, so it retries next time.
50
+ const recorded = db.query("SELECT name FROM _migrations WHERE name='0001_boom'").all();
51
+ expect(recorded).toHaveLength(0);
52
+ });
53
+
54
+ test('addColumnIfMissing / columnExists are idempotent', () => {
55
+ const db = new Database(':memory:');
56
+ db.run('CREATE TABLE t (id TEXT)');
57
+ expect(columnExists(db, 't', 'x')).toBe(false);
58
+ expect(addColumnIfMissing(db, 't', 'x', 'INTEGER')).toBe(true);
59
+ expect(addColumnIfMissing(db, 't', 'x', 'INTEGER')).toBe(false);
60
+ expect(columnExists(db, 't', 'x')).toBe(true);
61
+ });
62
+ });
63
+
64
+ describe('applyRecommendedPragmas', () => {
65
+ test('turns on foreign keys + busy timeout (set outside any transaction)', () => {
66
+ const db = new Database(':memory:');
67
+ applyRecommendedPragmas(db);
68
+ expect((db.query('PRAGMA foreign_keys').get() as { foreign_keys: number }).foreign_keys).toBe(
69
+ 1,
70
+ );
71
+ expect((db.query('PRAGMA busy_timeout').get() as { timeout: number }).timeout).toBe(5000);
72
+ });
73
+
74
+ test('foreign keys are actually enforced after the pragma', () => {
75
+ const db = new Database(':memory:');
76
+ applyRecommendedPragmas(db);
77
+ runMigrations(db, [
78
+ {
79
+ name: '0001',
80
+ up: (d) => {
81
+ d.run('CREATE TABLE parent (id TEXT PRIMARY KEY)');
82
+ d.run('CREATE TABLE child (id TEXT PRIMARY KEY, parent_id TEXT REFERENCES parent(id))');
83
+ },
84
+ },
85
+ ]);
86
+ expect(() => db.run("INSERT INTO child (id, parent_id) VALUES ('c', 'ghost')")).toThrow();
87
+ });
88
+ });
@@ -0,0 +1,87 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { applyCcPragmas } from 'cwip/sqlite';
3
+ import type { Migration } from './types';
4
+
5
+ /**
6
+ * The versioned SQLite migration runner — a tiny `_migrations`-sentinel runner, NOT
7
+ * a schema-first ORM. It replaces the old scattered guarded-`ALTER` /
8
+ * `addColumnIfMissing` pattern with ordered, once-only migrations whose names are
9
+ * recorded once applied. Business tables stay raw (`db.query` + `data JSON`); only
10
+ * the *plumbing* tables get Kysely types (see `../db/kysely`).
11
+ */
12
+
13
+ /**
14
+ * Apply the recommended connection PRAGMAs. **Call this BEFORE {@link runMigrations}
15
+ * and OUTSIDE any transaction** — `PRAGMA foreign_keys` is silently ignored inside a
16
+ * transaction, and the runner wraps each migration in one. Idempotent; safe on boot.
17
+ *
18
+ * This is now a straight alias of cwip's `applyCcPragmas`: the CC storage tier pins
19
+ * ONE pragma profile monorepo-wide (`CC_PRAGMAS` — WAL + NORMAL + busy_timeout 5000 +
20
+ * foreign_keys ON + wal_autocheckpoint 1000), and the belt-local copy that used to
21
+ * live here was the second implementation the spec forbids. The per-pragma
22
+ * `PragmaOptions` overrides are gone with it — a handle that drifts from the profile
23
+ * is a `pragma-drift` violation, not a configuration choice.
24
+ *
25
+ * @deprecated Prefer cwip's `assertCcPragmas`, which additionally verifies each
26
+ * pragma took and installs the WAL transaction-lifetime guard.
27
+ */
28
+ export function applyRecommendedPragmas(db: Database): void {
29
+ applyCcPragmas(db);
30
+ }
31
+
32
+ /**
33
+ * Run every pending migration exactly once, in array order, each inside its own
34
+ * transaction, recording its `name` in `_migrations`. Returns the names actually
35
+ * applied this call (empty when already up to date — re-running is a no-op).
36
+ *
37
+ * Set the connection PRAGMAs first via {@link applyRecommendedPragmas} (foreign-key
38
+ * enforcement cannot be turned on inside the per-migration transaction).
39
+ */
40
+ export function runMigrations(db: Database, migrations: readonly Migration[]): string[] {
41
+ db.run(
42
+ 'CREATE TABLE IF NOT EXISTS _migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)',
43
+ );
44
+
45
+ const applied = new Set(
46
+ (db.query('SELECT name FROM _migrations').all() as Array<{ name: string }>).map((r) => r.name),
47
+ );
48
+ const insert = db.prepare('INSERT INTO _migrations (name, applied_at) VALUES (?, ?)');
49
+ const ran: string[] = [];
50
+
51
+ for (const m of migrations) {
52
+ if (applied.has(m.name)) continue;
53
+ const apply = db.transaction(() => {
54
+ m.up(db);
55
+ insert.run(m.name, new Date().toISOString());
56
+ });
57
+ apply();
58
+ ran.push(m.name);
59
+ }
60
+ return ran;
61
+ }
62
+
63
+ function quoteIdent(name: string): string {
64
+ return `"${name.replace(/"/g, '""')}"`;
65
+ }
66
+
67
+ /** Does `table` have a column named `column`? (via `PRAGMA table_info`). */
68
+ export function columnExists(db: Database, table: string, column: string): boolean {
69
+ const rows = db.query(`PRAGMA table_info(${quoteIdent(table)})`).all() as Array<{ name: string }>;
70
+ return rows.some((r) => r.name === column);
71
+ }
72
+
73
+ /**
74
+ * Add a column if it is missing (idempotent). Returns true when it was added. A
75
+ * migration helper for the rare evolve-in-place case; the canonical schema is built
76
+ * fresh by `0001_initial`, so new migrations normally `CREATE` rather than `ALTER`.
77
+ */
78
+ export function addColumnIfMissing(
79
+ db: Database,
80
+ table: string,
81
+ column: string,
82
+ ddl: string,
83
+ ): boolean {
84
+ if (columnExists(db, table, column)) return false;
85
+ db.run(`ALTER TABLE ${quoteIdent(table)} ADD COLUMN ${quoteIdent(column)} ${ddl}`);
86
+ return true;
87
+ }
@@ -0,0 +1,17 @@
1
+ import type { Database } from 'bun:sqlite';
2
+
3
+ /**
4
+ * One ordered, immutable schema migration. `name` is recorded in the `_migrations`
5
+ * sentinel once applied, so a migration's `up()` runs exactly once across the life
6
+ * of a database. Corrections land as NEW migrations — never edit a shipped one, and
7
+ * never rename one that has been applied (the name is its identity).
8
+ *
9
+ * `up()` runs inside the runner's per-migration transaction; throw to abort + roll
10
+ * back that migration (the sentinel row is not written, so it retries next boot).
11
+ */
12
+ export interface Migration {
13
+ /** Unique, lexically-ordered name, e.g. `0001_initial`. Immutable once applied. */
14
+ readonly name: string;
15
+ /** Apply the migration. Runs inside the runner's transaction. */
16
+ up(db: Database): void;
17
+ }
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { assertPublicOutboundUrl, assertResolvedPublicUrl, OUTBOUND_URL_BLOCKED } from './outboundGuard';
3
+
4
+ const code = (fn: () => unknown): string | undefined => {
5
+ try {
6
+ fn();
7
+ } catch (error) {
8
+ return (error as { code?: string }).code;
9
+ }
10
+ return undefined;
11
+ };
12
+
13
+ describe('assertPublicOutboundUrl — SSRF guard', () => {
14
+ it('allows ordinary public http/https targets', () => {
15
+ expect(assertPublicOutboundUrl('https://example.com/api').hostname).toBe('example.com');
16
+ expect(assertPublicOutboundUrl('http://93.184.216.34/').hostname).toBe('93.184.216.34'); // a public IP literal
17
+ expect(assertPublicOutboundUrl('https://8.8.8.8/').hostname).toBe('8.8.8.8');
18
+ });
19
+
20
+ it('blocks the cloud metadata address (the headline SSRF)', () => {
21
+ expect(code(() => assertPublicOutboundUrl('http://169.254.169.254/latest/meta-data/'))).toBe(OUTBOUND_URL_BLOCKED);
22
+ });
23
+
24
+ it('blocks loopback and localhost in every common form', () => {
25
+ for (const url of [
26
+ 'http://127.0.0.1/',
27
+ 'http://127.1/',
28
+ 'http://localhost:4747/',
29
+ 'http://LOCALHOST/',
30
+ 'http://[::1]/',
31
+ 'http://0.0.0.0/',
32
+ 'http://2130706433/', // decimal 127.0.0.1
33
+ 'http://0x7f000001/', // hex 127.0.0.1
34
+ ])
35
+ expect(code(() => assertPublicOutboundUrl(url))).toBe(OUTBOUND_URL_BLOCKED);
36
+ });
37
+
38
+ it('blocks the RFC1918 private ranges and CGNAT', () => {
39
+ for (const url of [
40
+ 'http://10.0.0.5/',
41
+ 'http://172.16.9.9/',
42
+ 'http://172.31.255.255/',
43
+ 'http://192.168.1.1/',
44
+ 'http://100.64.0.1/',
45
+ ])
46
+ expect(code(() => assertPublicOutboundUrl(url))).toBe(OUTBOUND_URL_BLOCKED);
47
+ });
48
+
49
+ it('allows a public IP adjacent to a blocked range', () => {
50
+ expect(() => assertPublicOutboundUrl('http://172.32.0.1/')).not.toThrow(); // just outside 172.16/12
51
+ expect(() => assertPublicOutboundUrl('http://11.0.0.1/')).not.toThrow();
52
+ });
53
+
54
+ it('blocks IPv6 loopback, unique-local and link-local', () => {
55
+ for (const url of ['http://[::1]/', 'http://[fc00::1]/', 'http://[fd00:ec2::254]/', 'http://[fe80::1]/', 'http://[::ffff:127.0.0.1]/'])
56
+ expect(code(() => assertPublicOutboundUrl(url))).toBe(OUTBOUND_URL_BLOCKED);
57
+ });
58
+
59
+ it('blocks internal-only hostnames and cloud metadata name', () => {
60
+ for (const url of ['http://metadata.google.internal/', 'http://db.internal/', 'http://svc.cluster.local/', 'http://foo.localhost/'])
61
+ expect(code(() => assertPublicOutboundUrl(url))).toBe(OUTBOUND_URL_BLOCKED);
62
+ });
63
+
64
+ it('rejects non-http schemes, credentials, and malformed URLs', () => {
65
+ expect(code(() => assertPublicOutboundUrl('file:///etc/passwd'))).toBe(OUTBOUND_URL_BLOCKED);
66
+ expect(code(() => assertPublicOutboundUrl('gopher://example.com/'))).toBe(OUTBOUND_URL_BLOCKED);
67
+ expect(code(() => assertPublicOutboundUrl('http://user:pass@example.com/'))).toBe(OUTBOUND_URL_BLOCKED);
68
+ expect(code(() => assertPublicOutboundUrl('not a url'))).toBe(OUTBOUND_URL_BLOCKED);
69
+ });
70
+
71
+ it('honors an explicit host allowlist for a configured internal integration', () => {
72
+ expect(() => assertPublicOutboundUrl('http://localhost:4747/', { allowHosts: ['localhost'] })).not.toThrow();
73
+ // …but only the named host; a different internal host is still refused.
74
+ expect(code(() => assertPublicOutboundUrl('http://127.0.0.1/', { allowHosts: ['localhost'] }))).toBe(OUTBOUND_URL_BLOCKED);
75
+ });
76
+ });
77
+
78
+ describe('assertResolvedPublicUrl — DNS-aware guard', () => {
79
+ it('passes an IP literal without a DNS lookup', async () => {
80
+ expect((await assertResolvedPublicUrl('https://8.8.8.8/')).hostname).toBe('8.8.8.8');
81
+ });
82
+
83
+ it('still blocks a private IP literal', async () => {
84
+ let blocked = false;
85
+ await assertResolvedPublicUrl('http://169.254.169.254/').catch((e) => {
86
+ blocked = (e as { code?: string }).code === OUTBOUND_URL_BLOCKED;
87
+ });
88
+ expect(blocked).toBe(true);
89
+ });
90
+ });