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,108 @@
1
+ /**
2
+ * The wire and store types every consumer of this sync engine shares. The op
3
+ * envelope is the notes shape — `(origin, originSeq)` gives exactly-once application
4
+ * with no coordination — and the report is vault's cursor discipline (the reader may
5
+ * advance exactly to `cursor`, never further) merged with notes' `seen` (a duplicate
6
+ * is not an error, it is proof the dedupe ledger works).
7
+ *
8
+ * ── 🔴 "sync-kit" is a PAST NAME. The engine is `cursedbelt-server/sync` ────────────
9
+ * The retired generation published this as `@satellites/sync-kit`. It came across
10
+ * whole into `apps/vault/src/kit/sync/` when that app graduated, and came here —
11
+ * `cursedbelt-server/sync` — on 2026-09-15, before `apps/station` could graduate and fork
12
+ * it a second time. That is the move this header used to call an open question in
13
+ * `tasks/`; it is done, and the app-local copies are what is temporary now.
14
+ *
15
+ * `@satellites/sync-kit` is NOT installable and never was — it was never published
16
+ * to npm — and its design plan (`plans/01-sync-kit-adoption-design.md`) belonged to
17
+ * the retired monorepo and did not come across. Comments in consuming apps still say
18
+ * "the sync-kit adoption": that is the NAME OF AN EVENT, the 2026-08 change that
19
+ * moved vault from its own `oplog` table onto this engine's `sync_ops`, and it is
20
+ * worth keeping because the dual-compat code only makes sense in its light.
21
+ */
22
+
23
+ /** One durable mutation in the shared op log. `payload` is app-defined per `kind`. */
24
+ export interface SyncOp {
25
+ /** This instance's monotonic sequence for the op (assigned by the local log). */
26
+ seq: number;
27
+ /** The instance id that BORN the op (stable per instance, never per device). */
28
+ origin: string;
29
+ /** The origin's own monotonic counter — `(origin, originSeq)` is the identity. */
30
+ originSeq: number;
31
+ /** Wall-clock ms when the op was born. LWW inputs, display, nothing structural. */
32
+ ts: number;
33
+ /** App-defined kind, e.g. `doc.set`. The version-negotiation unit. */
34
+ kind: string;
35
+ payload: unknown;
36
+ }
37
+
38
+ /** What applying one batch did. `cursor` is the last seq FULLY applied — a stopped
39
+ * batch leaves it just before the failure so the next run resumes there. */
40
+ export interface ApplyReport {
41
+ /** Ops that changed local state. */
42
+ applied: number;
43
+ /** Ops already held (dedupe hit) — counted, never re-applied. */
44
+ seen: number;
45
+ /** Concurrent edits reconciled (however the app's applier chose to). */
46
+ conflicts: number;
47
+ cursor: number;
48
+ /** Non-null when the batch stopped early (version skew, deferred ordering…). */
49
+ error: string | null;
50
+ }
51
+
52
+ /** The peer info handshake. `opKinds` is the compatibility advertisement — a dialer
53
+ * stops BEFORE the first op a peer cannot take (notes' version-skew lesson: sending
54
+ * it wedges the push with an error that reads like corruption). */
55
+ export interface PeerInfo {
56
+ instanceId: string;
57
+ head: number;
58
+ opKinds: string[];
59
+ }
60
+
61
+ /** The transport seam. Pure interface so tests drive the engine against a REAL second
62
+ * store mounted in a Hono app — no HTTP mocks (the notes/vault discipline). */
63
+ export interface RemoteApi {
64
+ info(): Promise<PeerInfo>;
65
+ pull(
66
+ after: number,
67
+ excludeOrigin: string,
68
+ ): Promise<{ ops: SyncOp[]; cursor: number; head: number }>;
69
+ push(payload: { ops: SyncOp[]; peerHas: number }): Promise<ApplyReport>;
70
+ /** The receiver's "someone pressed Sync now here" stamp; null when unsupported. */
71
+ requestedAt?(): Promise<number | null>;
72
+ }
73
+
74
+ /** Context the applier gets per batch — vault's concurrency bound. */
75
+ export interface ApplyContext {
76
+ /** The peer's stable instance id. */
77
+ peerId: string;
78
+ /** Max LOCAL-born seq the peer already holds of OURS — an entity whose newest
79
+ * local op is beyond this AND diverges is a true concurrent edit. */
80
+ peerReceivedUpTo: number;
81
+ }
82
+
83
+ /** The app's merge logic for ONE op that passed the dedupe ledger. Return value:
84
+ * - `"applied"` — state changed
85
+ * - `"ignored"` — stale/no-op (still recorded in the ledger, cursor advances)
86
+ * - `"conflict"` — applied with a materialized conflict artifact
87
+ * Throw {@link StopBatch} to stop BEFORE this op (cursor stays short of it). */
88
+ export type ApplyOne = (op: SyncOp, ctx: ApplyContext) => "applied" | "ignored" | "conflict";
89
+
90
+ /** Stop the current batch before the offending op — deferred ordering, a payload this
91
+ * build cannot hold yet, anything where "retry after the world changes" is right. */
92
+ export class StopBatch extends Error {
93
+ constructor(reason: string) {
94
+ super(reason);
95
+ this.name = "StopBatch";
96
+ }
97
+ }
98
+
99
+ export interface SyncSummary {
100
+ remoteId: string;
101
+ pushed: number;
102
+ pulled: number;
103
+ conflicts: number;
104
+ /** Ops the export policy held back (counted, never sent). */
105
+ held: number;
106
+ /** Set when the remote runs an older schema and cannot take what comes next. */
107
+ blockedBy?: string;
108
+ }
@@ -0,0 +1,67 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { ServerWebSocket } from 'bun';
3
+ import { Hono } from 'hono';
4
+ import { createEventBus } from './eventBus';
5
+ import type { EventBusData } from './types';
6
+
7
+ function mockWs() {
8
+ return {
9
+ sent: [] as string[],
10
+ send(p: string) {
11
+ this.sent.push(p);
12
+ return p.length;
13
+ },
14
+ };
15
+ }
16
+ const asWs = (m: ReturnType<typeof mockWs>) => m as unknown as ServerWebSocket<EventBusData>;
17
+
18
+ describe('createEventBus', () => {
19
+ test('broadcasts the { event, data } envelope to every open socket', () => {
20
+ const bus = createEventBus();
21
+ const a = mockWs();
22
+ const b = mockWs();
23
+ bus.handlers.open?.(asWs(a));
24
+ bus.handlers.open?.(asWs(b));
25
+
26
+ bus.broadcast('update', { n: 1 });
27
+ expect(a.sent).toEqual(['{"event":"update","data":{"n":1}}']);
28
+ expect(b.sent).toEqual(['{"event":"update","data":{"n":1}}']);
29
+ });
30
+
31
+ test('clientCount tracks open/close', () => {
32
+ const bus = createEventBus();
33
+ const a = mockWs();
34
+ expect(bus.clientCount()).toBe(0);
35
+ bus.handlers.open?.(asWs(a));
36
+ expect(bus.clientCount()).toBe(1);
37
+ bus.handlers.close?.(asWs(a), 1000, '');
38
+ expect(bus.clientCount()).toBe(0);
39
+ });
40
+
41
+ test('a closed socket no longer receives broadcasts', () => {
42
+ const bus = createEventBus();
43
+ const a = mockWs();
44
+ bus.handlers.open?.(asWs(a));
45
+ bus.handlers.close?.(asWs(a), 1000, '');
46
+ bus.broadcast('x', {});
47
+ expect(a.sent).toEqual([]);
48
+ });
49
+
50
+ test('inbound frames are ignored (push-only no-op message handler)', () => {
51
+ const bus = createEventBus();
52
+ const a = mockWs();
53
+ bus.handlers.open?.(asWs(a));
54
+ expect(() => bus.handlers.message(asWs(a), 'whatever')).not.toThrow();
55
+ expect(a.sent).toEqual([]);
56
+ });
57
+
58
+ test('upgradeHandler returns 426 when the server cannot upgrade', async () => {
59
+ const bus = createEventBus();
60
+ const app = new Hono();
61
+ app.get('/events', bus.upgradeHandler);
62
+ const res = await app.fetch(new Request('http://localhost/events'), {
63
+ upgrade: () => false,
64
+ });
65
+ expect(res.status).toBe(426);
66
+ });
67
+ });
@@ -0,0 +1,46 @@
1
+ import type { Server, ServerWebSocket } from 'bun';
2
+ import type { MiddlewareHandler } from 'hono';
3
+ import type { EventBus, EventBusData } from './types';
4
+
5
+ /**
6
+ * The push-only subset ru uses: broadcast-to-all, no auth, no inbound routing. ru
7
+ * mounts one bus and pushes from its task queue / caSync / Forge workers
8
+ * (`bus.broadcast('taskq-update', { … })`). Same Bun-native upgrade as the hub, minus
9
+ * the channel registry, presence, and heartbeat.
10
+ *
11
+ * @example
12
+ * const bus = createEventBus();
13
+ * app.get('/events', bus.upgradeHandler);
14
+ * serveBun(app, { port, websocket: bus.handlers });
15
+ * bus.broadcast('taskq-update', { running: 3 });
16
+ */
17
+ export function createEventBus(): EventBus {
18
+ const sockets = new Set<ServerWebSocket<EventBusData>>();
19
+
20
+ const upgradeHandler: MiddlewareHandler = async (c) => {
21
+ const server = c.env as unknown as Server<EventBusData>;
22
+ const upgraded = server.upgrade(c.req.raw, { data: {} });
23
+ if (!upgraded) return c.text('Upgrade required', 426);
24
+ return c.body(null, 204);
25
+ };
26
+
27
+ return {
28
+ upgradeHandler,
29
+ handlers: {
30
+ open(ws) {
31
+ sockets.add(ws);
32
+ },
33
+ // Push-only: inbound frames are intentionally ignored. (Bun requires a `message`
34
+ // handler, so this is a documented no-op rather than an omitted key.)
35
+ message() {},
36
+ close(ws) {
37
+ sockets.delete(ws);
38
+ },
39
+ },
40
+ broadcast(event, data) {
41
+ const payload = JSON.stringify({ event, data });
42
+ for (const ws of sockets) ws.send(payload);
43
+ },
44
+ clientCount: () => sockets.size,
45
+ };
46
+ }
@@ -0,0 +1,93 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { Hono } from 'hono';
3
+ import { serveBun } from '../app';
4
+ import type { AuthUser } from '../auth/types';
5
+ import { createWsHub } from './hub';
6
+ import type { WsChannel, WsMessage } from './types';
7
+ import type { CursedbeltEnv } from '../context';
8
+
9
+ const testUser: AuthUser = { id: 'u1', roles: [], getOnly: false, sessionId: 's1' };
10
+
11
+ async function waitFor(predicate: () => boolean, timeoutMs = 2000): Promise<void> {
12
+ const deadline = Date.now() + timeoutMs;
13
+ while (!predicate()) {
14
+ if (Date.now() > deadline) throw new Error('waitFor timed out');
15
+ await new Promise((r) => setTimeout(r, 10));
16
+ }
17
+ }
18
+
19
+ function nextMessage(ws: WebSocket): Promise<WsMessage> {
20
+ return new Promise((resolve, reject) => {
21
+ const timer = setTimeout(() => reject(new Error('no message within 2s')), 2000);
22
+ ws.addEventListener(
23
+ 'message',
24
+ (e) => {
25
+ clearTimeout(timer);
26
+ resolve(JSON.parse(String((e as MessageEvent).data)));
27
+ },
28
+ { once: true },
29
+ );
30
+ });
31
+ }
32
+
33
+ describe('createWsHub — integration (real Bun.serve)', () => {
34
+ test('upgrade → message round-trip → disconnect, with getClients reflecting state', async () => {
35
+ let connectedId: string | undefined;
36
+ let disconnected = false;
37
+ const channel: WsChannel = {
38
+ name: 'test',
39
+ presence: true,
40
+ onConnect(client) {
41
+ connectedId = client.id;
42
+ },
43
+ onMessage(client, msg, hub) {
44
+ if (msg.event === 'echo') hub.send(client.id, 'echoed', msg.data);
45
+ },
46
+ onDisconnect() {
47
+ disconnected = true;
48
+ },
49
+ };
50
+ const hub = createWsHub({ channels: { test: channel }, authenticate: async () => testUser });
51
+ const app = new Hono<CursedbeltEnv>();
52
+ app.get('/ws/:channel', hub.upgradeHandler);
53
+ const server = serveBun(app, { port: 0, websocket: hub.handlers });
54
+
55
+ const ws = new WebSocket(`ws://localhost:${server.port}/ws/test`);
56
+ try {
57
+ await new Promise<void>((resolve, reject) => {
58
+ ws.addEventListener('open', () => resolve(), { once: true });
59
+ ws.addEventListener('error', () => reject(new Error('ws failed to open')), { once: true });
60
+ });
61
+
62
+ await waitFor(() => hub.getClients('test').length === 1);
63
+ expect(connectedId).toBeDefined();
64
+ expect(hub.getPresence('test')).toEqual([expect.objectContaining({ userId: 'u1' })]);
65
+
66
+ const reply = nextMessage(ws);
67
+ ws.send(JSON.stringify({ event: 'echo', data: { msg: 'hi' } }));
68
+ expect(await reply).toEqual({ event: 'echoed', data: { msg: 'hi' } });
69
+
70
+ ws.close();
71
+ await waitFor(() => disconnected && hub.getClients('test').length === 0);
72
+ } finally {
73
+ ws.close();
74
+ hub.close();
75
+ server.stop(true);
76
+ }
77
+ });
78
+
79
+ test('unauthenticated upgrade → HTTP 401 (not a WS close)', async () => {
80
+ const hub = createWsHub({ channels: {}, authenticate: async () => null });
81
+ const app = new Hono<CursedbeltEnv>();
82
+ app.get('/ws/:channel', hub.upgradeHandler);
83
+ const server = serveBun(app, { port: 0, websocket: hub.handlers });
84
+ try {
85
+ const res = await fetch(`http://localhost:${server.port}/ws/test`);
86
+ expect(res.status).toBe(401);
87
+ expect((await res.json()).error.message).toBe('Unauthorized');
88
+ } finally {
89
+ hub.close();
90
+ server.stop(true);
91
+ }
92
+ });
93
+ });
@@ -0,0 +1,282 @@
1
+ import { describe, expect, mock, test } from 'bun:test';
2
+ import type { ServerWebSocket } from 'bun';
3
+ import { Hono } from 'hono';
4
+ import type { AuthUser } from '../auth/types';
5
+ import { createWsHub, sweepStaleClients } from './hub';
6
+ import type { WsChannel, WsClient, WsData, WsHub } from './types';
7
+
8
+ const testUser: AuthUser = { id: 'u1', roles: [], getOnly: false, sessionId: 's1' };
9
+
10
+ interface MockWs extends ServerWebSocket<WsData> {
11
+ sent: string[];
12
+ closedWith?: [number, string];
13
+ pings: number;
14
+ }
15
+
16
+ function mockWs(data: WsData): MockWs {
17
+ const ws = {
18
+ data,
19
+ sent: [] as string[],
20
+ pings: 0,
21
+ closedWith: undefined as [number, string] | undefined,
22
+ send(payload: string) {
23
+ this.sent.push(payload);
24
+ return payload.length;
25
+ },
26
+ close(code?: number, reason?: string) {
27
+ this.closedWith = [code ?? 1000, reason ?? ''];
28
+ },
29
+ ping() {
30
+ this.pings += 1;
31
+ return 0;
32
+ },
33
+ };
34
+ return ws as unknown as MockWs;
35
+ }
36
+
37
+ /** Register a connected mock client through the hub's real `open` handler. */
38
+ function connect(hub: WsHub, data: WsData): MockWs {
39
+ const ws = mockWs(data);
40
+ hub.handlers.open?.(ws);
41
+ return ws;
42
+ }
43
+
44
+ const data = (over: Partial<WsData> = {}): WsData => ({
45
+ clientId: crypto.randomUUID(),
46
+ user: testUser,
47
+ channel: 'test',
48
+ ...over,
49
+ });
50
+
51
+ describe('createWsHub — message routing & lifecycle', () => {
52
+ test('routes an inbound frame to the channel.onMessage of its channel', () => {
53
+ const onMessage = mock(() => {});
54
+ const channel: WsChannel = { name: 'test', onMessage };
55
+ const hub = createWsHub({ channels: { test: channel }, authenticate: async () => testUser });
56
+
57
+ const ws = connect(hub, data({ clientId: 'c1' }));
58
+ hub.handlers.message(ws, JSON.stringify({ event: 'hello', data: { a: 1 } }));
59
+
60
+ expect(onMessage).toHaveBeenCalledTimes(1);
61
+ expect(onMessage).toHaveBeenCalledWith(
62
+ expect.objectContaining({ id: 'c1', channel: 'test' }),
63
+ { event: 'hello', data: { a: 1 } },
64
+ hub,
65
+ );
66
+ hub.close();
67
+ });
68
+
69
+ test('fires onConnect on open and onDisconnect on close, removing the client', () => {
70
+ const onConnect = mock(() => {});
71
+ const onDisconnect = mock(() => {});
72
+ const hub = createWsHub({
73
+ channels: { test: { name: 'test', onMessage: () => {}, onConnect, onDisconnect } },
74
+ authenticate: async () => testUser,
75
+ });
76
+
77
+ const ws = connect(hub, data({ clientId: 'c1' }));
78
+ expect(onConnect).toHaveBeenCalledTimes(1);
79
+ expect(hub.getClient('c1')).toBeDefined();
80
+
81
+ hub.handlers.close?.(ws, 1000, 'bye');
82
+ expect(onDisconnect).toHaveBeenCalledTimes(1);
83
+ expect(hub.getClient('c1')).toBeUndefined();
84
+ hub.close();
85
+ });
86
+
87
+ test('ignores a malformed (non-JSON) frame without throwing', () => {
88
+ const onMessage = mock(() => {});
89
+ const hub = createWsHub({
90
+ channels: { test: { name: 'test', onMessage } },
91
+ authenticate: async () => testUser,
92
+ });
93
+ const ws = connect(hub, data());
94
+ expect(() => hub.handlers.message(ws, 'not json {')).not.toThrow();
95
+ expect(onMessage).not.toHaveBeenCalled();
96
+ hub.close();
97
+ });
98
+
99
+ test('a message bumps lastPingAt; a pong does too', () => {
100
+ const hub = createWsHub({
101
+ channels: { test: { name: 'test', onMessage: () => {} } },
102
+ authenticate: async () => testUser,
103
+ });
104
+ const ws = connect(hub, data({ clientId: 'c1' }));
105
+ const before = hub.getClient('c1')?.lastPingAt ?? 0;
106
+ hub.handlers.pong?.(ws, Buffer.from(''));
107
+ expect(hub.getClient('c1')?.lastPingAt).toBeGreaterThanOrEqual(before);
108
+ hub.close();
109
+ });
110
+ });
111
+
112
+ describe('createWsHub — broadcast / send / room', () => {
113
+ const hubWith = () =>
114
+ createWsHub({
115
+ channels: {
116
+ test: { name: 'test', onMessage: () => {} },
117
+ other: { name: 'other', onMessage: () => {} },
118
+ },
119
+ authenticate: async () => testUser,
120
+ });
121
+
122
+ test('broadcast reaches only clients on the channel', () => {
123
+ const hub = hubWith();
124
+ const a = connect(hub, data({ clientId: 'a', channel: 'test' }));
125
+ const b = connect(hub, data({ clientId: 'b', channel: 'test' }));
126
+ const c = connect(hub, data({ clientId: 'c', channel: 'other' }));
127
+
128
+ hub.broadcast('test', 'ping', {});
129
+ expect(a.sent).toEqual(['{"event":"ping","data":{}}']);
130
+ expect(b.sent).toEqual(['{"event":"ping","data":{}}']);
131
+ expect(c.sent).toEqual([]);
132
+ hub.close();
133
+ });
134
+
135
+ test('broadcastToRoom matches the room applied from the upgrade ?room= data', () => {
136
+ const hub = hubWith();
137
+ const a = connect(hub, data({ clientId: 'a', channel: 'test', room: 'r1' }));
138
+ const b = connect(hub, data({ clientId: 'b', channel: 'test', room: 'r2' }));
139
+ expect(hub.getClient('a')?.meta.roomId).toBe('r1');
140
+
141
+ hub.broadcastToRoom('test', 'r1', 'm', { x: 1 });
142
+ expect(a.sent).toEqual(['{"event":"m","data":{"x":1}}']);
143
+ expect(b.sent).toEqual([]);
144
+ hub.close();
145
+ });
146
+
147
+ test('send targets a single client by id', () => {
148
+ const hub = hubWith();
149
+ const a = connect(hub, data({ clientId: 'a' }));
150
+ const b = connect(hub, data({ clientId: 'b' }));
151
+ hub.send('a', 'hi', { n: 2 });
152
+ expect(a.sent).toEqual(['{"event":"hi","data":{"n":2}}']);
153
+ expect(b.sent).toEqual([]);
154
+ hub.send('missing', 'x', {}); // no-op, no throw
155
+ hub.close();
156
+ });
157
+
158
+ test('getClients filters by channel; getPresence honors the presence flag', () => {
159
+ const hub = createWsHub({
160
+ channels: {
161
+ test: { name: 'test', onMessage: () => {}, presence: true },
162
+ other: { name: 'other', onMessage: () => {} },
163
+ },
164
+ authenticate: async () => testUser,
165
+ });
166
+ connect(hub, data({ clientId: 'a', channel: 'test', user: { ...testUser, id: 'ua' } }));
167
+ connect(hub, data({ clientId: 'b', channel: 'other', user: { ...testUser, id: 'ub' } }));
168
+
169
+ expect(hub.getClients().length).toBe(2);
170
+ expect(hub.getClients('test').map((c) => c.id)).toEqual(['a']);
171
+ expect(hub.getPresence('test')).toEqual([expect.objectContaining({ userId: 'ua' })]);
172
+ expect(hub.getPresence('other')).toEqual([]); // presence not enabled
173
+ hub.close();
174
+ });
175
+ });
176
+
177
+ describe('createWsHub — upgradeHandler', () => {
178
+ const fakeServer = (upgradeResult: boolean) => {
179
+ let captured: { data?: WsData } | undefined;
180
+ const server = {
181
+ upgrade(_req: Request, opts: { data?: WsData }) {
182
+ captured = opts;
183
+ return upgradeResult;
184
+ },
185
+ };
186
+ return { server, getData: () => captured?.data };
187
+ };
188
+
189
+ test('authenticate → null yields an HTTP 401 envelope (not a WS close), never upgrading', () => {
190
+ const hub = createWsHub({ channels: {}, authenticate: async () => null });
191
+ const app = new Hono();
192
+ app.get('/ws/:channel', hub.upgradeHandler);
193
+ const { server, getData } = fakeServer(true);
194
+
195
+ return Promise.resolve(app.fetch(new Request('http://localhost/ws/test'), server)).then(async (res) => {
196
+ expect(res.status).toBe(401);
197
+ expect((await res.json()).error.message).toBe('Unauthorized');
198
+ expect(getData()).toBeUndefined();
199
+ hub.close();
200
+ });
201
+ });
202
+
203
+ test('authenticate throwing is treated as unauthorized (401), not a 500', async () => {
204
+ const hub = createWsHub({
205
+ channels: {},
206
+ authenticate: async () => {
207
+ throw new Error('boom');
208
+ },
209
+ });
210
+ const app = new Hono();
211
+ app.get('/ws/:channel', hub.upgradeHandler);
212
+ const res = await app.fetch(new Request('http://localhost/ws/test'), fakeServer(true).server);
213
+ expect(res.status).toBe(401);
214
+ hub.close();
215
+ });
216
+
217
+ test('success: upgrades with { clientId, user, channel, room } and returns a handled response', async () => {
218
+ const hub = createWsHub({ channels: {}, authenticate: async () => testUser });
219
+ const app = new Hono();
220
+ app.get('/ws/:channel', hub.upgradeHandler);
221
+ const { server, getData } = fakeServer(true);
222
+
223
+ const res = await app.fetch(new Request('http://localhost/ws/chat?room=lobby'), server);
224
+ expect(res.status).toBe(204);
225
+ const d = getData();
226
+ expect(d?.channel).toBe('chat');
227
+ expect(d?.room).toBe('lobby');
228
+ expect(d?.user).toEqual(testUser);
229
+ expect(typeof d?.clientId).toBe('string');
230
+ hub.close();
231
+ });
232
+
233
+ test('channel falls back to ?channel= then "default"', async () => {
234
+ const hub = createWsHub({ channels: {}, authenticate: async () => testUser });
235
+ const app = new Hono();
236
+ app.get('/ws', hub.upgradeHandler);
237
+ const f1 = fakeServer(true);
238
+ await app.fetch(new Request('http://localhost/ws?channel=foo'), f1.server);
239
+ expect(f1.getData()?.channel).toBe('foo');
240
+
241
+ const f2 = fakeServer(true);
242
+ await app.fetch(new Request('http://localhost/ws'), f2.server);
243
+ expect(f2.getData()?.channel).toBe('default');
244
+ hub.close();
245
+ });
246
+
247
+ test('a failed upgrade returns 426', async () => {
248
+ const hub = createWsHub({ channels: {}, authenticate: async () => testUser });
249
+ const app = new Hono();
250
+ app.get('/ws/:channel', hub.upgradeHandler);
251
+ const res = await app.fetch(new Request('http://localhost/ws/test'), fakeServer(false).server);
252
+ expect(res.status).toBe(426);
253
+ hub.close();
254
+ });
255
+ });
256
+
257
+ describe('sweepStaleClients (heartbeat)', () => {
258
+ const client = (lastPingAt: number): WsClient & { ws: MockWs } => {
259
+ const ws = mockWs(data());
260
+ return {
261
+ id: 'x',
262
+ user: testUser,
263
+ ws,
264
+ channel: 'test',
265
+ connectedAt: 0,
266
+ lastPingAt,
267
+ meta: {},
268
+ };
269
+ };
270
+
271
+ test('pings fresh clients and closes stale ones with 1001', () => {
272
+ const now = 1_000_000;
273
+ const fresh = client(now - 1_000);
274
+ const stale = client(now - 200_000);
275
+ sweepStaleClients([fresh, stale], now, 120_000);
276
+
277
+ expect(fresh.ws.pings).toBe(1);
278
+ expect(fresh.ws.closedWith).toBeUndefined();
279
+ expect(stale.ws.pings).toBe(0);
280
+ expect(stale.ws.closedWith).toEqual([1001, 'stale']);
281
+ });
282
+ });