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,243 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, describe, expect, test } from 'bun:test';
3
+ import { Hono } from 'hono';
4
+ import { createApp, serveBun } from './app';
5
+ import type { CursedbeltEnv } from './context';
6
+ import { ApiError } from './errors';
7
+ import { createMetricsBuffer } from './metrics/metricsBuffer';
8
+ import { correlationId } from './middleware/correlationId';
9
+ import { errorEnvelope } from './middleware/errorEnvelope';
10
+ import { createRateLimiter } from './middleware/rateLimit';
11
+ import { requestLogger } from './middleware/requestLogger';
12
+ import { securityHeaders } from './middleware/securityHeaders';
13
+
14
+ const REQ = (path: string, init?: RequestInit) => new Request(`http://localhost${path}`, init);
15
+
16
+ describe('createApp', () => {
17
+ test('GET /health returns 200', async () => {
18
+ const app = createApp();
19
+ const res = await app.fetch(REQ('/health'));
20
+ expect(res.status).toBe(200);
21
+ expect(await res.json()).toEqual({ status: 'ok' });
22
+ });
23
+
24
+ test('health route can be disabled', async () => {
25
+ const app = createApp({ health: false });
26
+ expect((await app.fetch(REQ('/health'))).status).toBe(404);
27
+ });
28
+
29
+ test('mounts feature routers under a prefix', async () => {
30
+ const app = createApp();
31
+ const notes = new Hono().get('/notes', (c) => c.json([{ id: 'n1' }]));
32
+ app.route('/api/v1', notes);
33
+ const res = await app.fetch(REQ('/api/v1/notes'));
34
+ expect(res.status).toBe(200);
35
+ expect(await res.json()).toEqual([{ id: 'n1' }]);
36
+ });
37
+
38
+ test('stamps security + correlation headers on every response', async () => {
39
+ const app = createApp();
40
+ const res = await app.fetch(REQ('/health'));
41
+ expect(res.headers.get('x-content-type-options')).toBe('nosniff');
42
+ expect(res.headers.get('x-frame-options')).toBe('DENY');
43
+ expect(res.headers.get('content-security-policy')).toContain("default-src 'self'");
44
+ expect(res.headers.get('x-correlation-id')).toBeTruthy();
45
+ });
46
+ });
47
+
48
+ describe('correlationId()', () => {
49
+ test('mints an id and echoes it on the response', async () => {
50
+ const app = new Hono<CursedbeltEnv>().use('*', correlationId());
51
+ app.get('/x', (c) => c.json({ id: c.get('correlationId') }));
52
+ const res = await app.fetch(REQ('/x'));
53
+ const header = res.headers.get('x-correlation-id');
54
+ expect(header).toBeTruthy();
55
+ expect((await res.json()).id).toBe(header);
56
+ });
57
+
58
+ test('reuses a safe inbound id, rejects an unsafe one', async () => {
59
+ const app = new Hono<CursedbeltEnv>().use('*', correlationId());
60
+ app.get('/x', (c) => c.text('ok'));
61
+
62
+ const good = await app.fetch(REQ('/x', { headers: { 'x-correlation-id': 'trace-123' } }));
63
+ expect(good.headers.get('x-correlation-id')).toBe('trace-123');
64
+
65
+ const bad = await app.fetch(REQ('/x', { headers: { 'x-correlation-id': 'has space!' } }));
66
+ expect(bad.headers.get('x-correlation-id')).not.toBe('has space!');
67
+ expect(bad.headers.get('x-correlation-id')).toBeTruthy();
68
+ });
69
+ });
70
+
71
+ describe('errorEnvelope', () => {
72
+ const app = new Hono<CursedbeltEnv>().use('*', correlationId());
73
+ app.get('/notfound', () => {
74
+ throw new ApiError(404, 'not found');
75
+ });
76
+ app.get('/validation', () => {
77
+ throw new ApiError(422, 'invalid', { code: 'validation_failed', context: { errors: ['x'] } });
78
+ });
79
+ app.get('/boom', () => {
80
+ throw new Error('internal detail that must not leak');
81
+ });
82
+ app.onError(errorEnvelope);
83
+
84
+ test('ApiError → its status + rich envelope, correlationId echoed', async () => {
85
+ const res = await app.fetch(REQ('/notfound'));
86
+ expect(res.status).toBe(404);
87
+ const body = (await res.json()) as { error: Record<string, unknown>; correlationId?: string };
88
+ expect(body.error).toMatchObject({ name: 'ApiError', message: 'not found', status: 404, isOperational: true });
89
+ expect(typeof body.error.timestamp).toBe('string');
90
+ expect(res.headers.get('x-correlation-id')).toBeTruthy();
91
+ expect(body.correlationId).toBe(res.headers.get('x-correlation-id') ?? undefined);
92
+ });
93
+
94
+ test('ApiError carries code + context', async () => {
95
+ const res = await app.fetch(REQ('/validation'));
96
+ expect(res.status).toBe(422);
97
+ const body = (await res.json()) as { error: Record<string, unknown> };
98
+ expect(body.error).toMatchObject({
99
+ name: 'ApiError',
100
+ message: 'invalid',
101
+ code: 'validation_failed',
102
+ status: 422,
103
+ context: { errors: ['x'] },
104
+ isOperational: true,
105
+ });
106
+ });
107
+
108
+ test('unexpected error → sanitized 500 (no internal detail leaked)', async () => {
109
+ const res = await app.fetch(REQ('/boom'));
110
+ expect(res.status).toBe(500);
111
+ const body = (await res.json()) as { error: Record<string, unknown> };
112
+ expect(body.error).toMatchObject({ message: 'Internal Server Error', status: 500, isOperational: false });
113
+ expect(JSON.stringify(body)).not.toContain('internal detail that must not leak');
114
+ });
115
+ });
116
+
117
+ describe('securityHeaders()', () => {
118
+ test('app CSP overrides merge with the strict defaults per directive', async () => {
119
+ const app = new Hono().use(
120
+ '*',
121
+ securityHeaders({ csp: { connectSrc: ["'self'", 'https:', 'wss:'] } }),
122
+ );
123
+ app.get('/x', (c) => c.text('ok'));
124
+ const csp = (await app.fetch(REQ('/x'))).headers.get('content-security-policy') ?? '';
125
+ expect(csp).toContain("default-src 'self'"); // default kept
126
+ expect(csp).toContain("connect-src 'self' https: wss:"); // override applied
127
+ expect(csp).toContain("object-src 'none'");
128
+ });
129
+
130
+ test('csp:false omits the CSP header', async () => {
131
+ const app = new Hono().use('*', securityHeaders({ csp: false }));
132
+ app.get('/x', (c) => c.text('ok'));
133
+ const res = await app.fetch(REQ('/x'));
134
+ expect(res.headers.get('content-security-policy')).toBeNull();
135
+ expect(res.headers.get('x-frame-options')).toBe('DENY');
136
+ });
137
+ });
138
+
139
+ describe('createRateLimiter()', () => {
140
+ test('allows up to the limit, then 429 with Retry-After + envelope', async () => {
141
+ const app = new Hono().use('*', createRateLimiter({ limit: 5, windowMs: 60_000 }));
142
+ app.get('/x', (c) => c.text('ok'));
143
+
144
+ for (let i = 0; i < 5; i++) {
145
+ expect((await app.fetch(REQ('/x'))).status).toBe(200);
146
+ }
147
+ const blocked = await app.fetch(REQ('/x')); // the 6th
148
+ expect(blocked.status).toBe(429);
149
+ expect(Number(blocked.headers.get('retry-after'))).toBeGreaterThan(0);
150
+ const body = (await blocked.json()) as { error: Record<string, unknown> };
151
+ expect(body.error).toMatchObject({ message: expect.any(String), code: 'rate_limited', status: 429 });
152
+ });
153
+
154
+ test('separate keys have independent budgets', async () => {
155
+ let n = 0;
156
+ const app = new Hono().use(
157
+ '*',
158
+ createRateLimiter({ limit: 1, windowMs: 60_000, keyFn: () => `k${n}` }),
159
+ );
160
+ app.get('/x', (c) => c.text('ok'));
161
+ n = 0;
162
+ expect((await app.fetch(REQ('/x'))).status).toBe(200);
163
+ n = 1; // different key → fresh budget
164
+ expect((await app.fetch(REQ('/x'))).status).toBe(200);
165
+ });
166
+ });
167
+
168
+ describe('requestLogger()', () => {
169
+ const setup = () => {
170
+ const db = new Database(':memory:');
171
+ db.run(
172
+ `CREATE TABLE request_metrics (id TEXT PRIMARY KEY, ts INTEGER NOT NULL, method TEXT, route TEXT,
173
+ status INTEGER, duration_ms REAL, bytes_out INTEGER, user_id TEXT)`,
174
+ );
175
+ db.run(
176
+ `CREATE TABLE event_logs (id TEXT PRIMARY KEY, correlation_id TEXT, event_name TEXT NOT NULL,
177
+ type TEXT NOT NULL DEFAULT 'info', message TEXT, details TEXT, duration_ms REAL, user_id TEXT,
178
+ created_at TEXT NOT NULL)`,
179
+ );
180
+ const buffer = createMetricsBuffer({ db, flushIntervalMs: 1_000_000 });
181
+ const app = new Hono<CursedbeltEnv>().use('*', correlationId());
182
+ app.use('*', requestLogger(db, { buffer, slowMs: 1_000_000 }));
183
+ app.get('/ok', (c) => c.json({ ok: true }));
184
+ app.get('/missing', () => {
185
+ throw new ApiError(404, 'nope');
186
+ });
187
+ app.onError(errorEnvelope);
188
+ return { db, buffer, app };
189
+ };
190
+
191
+ const count = (db: Database, table: string) =>
192
+ (db.query(`SELECT COUNT(*) AS n FROM ${table}`).get() as { n: number }).n;
193
+
194
+ test('writes a request_metrics row per request (with the normalized route)', async () => {
195
+ const { db, buffer, app } = setup();
196
+ expect((await app.fetch(REQ('/ok'))).status).toBe(200);
197
+ buffer.flush();
198
+ expect(count(db, 'request_metrics')).toBe(1);
199
+ const row = db.query('SELECT method, route, status FROM request_metrics').get() as {
200
+ method: string;
201
+ route: string;
202
+ status: number;
203
+ };
204
+ expect(row).toEqual({ method: 'GET', route: '/ok', status: 200 });
205
+ buffer.stop();
206
+ });
207
+
208
+ test('records metrics + an event_logs row even when the handler throws', async () => {
209
+ const { db, buffer, app } = setup();
210
+ expect((await app.fetch(REQ('/missing'))).status).toBe(404);
211
+ buffer.flush();
212
+ expect(count(db, 'request_metrics')).toBe(1);
213
+ const metric = db.query('SELECT status FROM request_metrics').get() as { status: number };
214
+ expect(metric.status).toBe(404);
215
+ expect(count(db, 'event_logs')).toBe(1);
216
+ const evt = db.query('SELECT event_name, type FROM event_logs').get() as {
217
+ event_name: string;
218
+ type: string;
219
+ };
220
+ expect(evt).toEqual({ event_name: 'http_error', type: 'warn' });
221
+ buffer.stop();
222
+ });
223
+ });
224
+
225
+ describe('serveBun', () => {
226
+ // `ReturnType<typeof serveBun>`, not a hand-written subset: the subset was
227
+ // narrower than what `serveBun` actually returns, so it did not typecheck at
228
+ // all — and a widening of the real return would have gone unnoticed.
229
+ let server: ReturnType<typeof serveBun> | null = null;
230
+ afterEach(() => {
231
+ server?.stop(true);
232
+ server = null;
233
+ });
234
+
235
+ test('boots on Bun.serve and serves /health over real HTTP', async () => {
236
+ const app = createApp();
237
+ const booted = serveBun(app, { port: 0 }); // 0 → an ephemeral free port
238
+ server = booted;
239
+ const res = await fetch(`http://localhost:${booted.port}/health`);
240
+ expect(res.status).toBe(200);
241
+ expect(await res.json()).toEqual({ status: 'ok' });
242
+ });
243
+ });
@@ -0,0 +1,89 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { Server, TLSOptions, WebSocketHandler } from 'bun';
3
+ import { Hono } from 'hono';
4
+ import type { CursedbeltEnv } from './context';
5
+ import { correlationId } from './middleware/correlationId';
6
+ import { type CorsOpts, corsAllowlist } from './middleware/corsAllowlist';
7
+ import { errorEnvelope } from './middleware/errorEnvelope';
8
+ import { createRateLimiter, type RateLimitOpts } from './middleware/rateLimit';
9
+ import { requestLogger } from './middleware/requestLogger';
10
+ import { type SecurityHeadersOpts, securityHeaders } from './middleware/securityHeaders';
11
+
12
+ export interface CreateAppOpts {
13
+ /** When provided, the request logger writes metrics/events to this DB. */
14
+ db?: Database;
15
+ /**
16
+ * CORS for a split deploy (SPA origin ≠ API origin). Pass `{ siteDomain }` /
17
+ * `{ origins }` (see {@link CorsOpts}) or `corsOptsFromEnv()` to read the
18
+ * conventional `CORS_SITE_DOMAIN` / `CORS_ORIGINS` env vars. Omit for a
19
+ * same-origin deploy. Mounted FIRST so every response — preflight, rate-limit
20
+ * 429, error envelope — carries the CORS headers a browser needs to read it.
21
+ */
22
+ cors?: CorsOpts;
23
+ /** Security-header options, or `false` to skip the security-headers middleware. */
24
+ security?: SecurityHeadersOpts | false;
25
+ /** Mount a global rate limiter. (Per-route limiters: use `createRateLimiter` directly.) */
26
+ rateLimit?: RateLimitOpts;
27
+ /**
28
+ * How many trusted reverse proxies sit in front of this app, for the client-IP
29
+ * rate-limit key. Default `0` — `x-forwarded-for` is caller-controlled, so
30
+ * believing it by default let anyone mint a fresh rate-limit bucket per
31
+ * request. Set to the real hop count you operate (nginx in front ⇒ `1`).
32
+ */
33
+ trustedProxyHops?: number;
34
+ /** Mount the default `GET /health` liveness route. Default: true. */
35
+ health?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Build the configured cursedbelt Hono app: correlation id → (optional) CORS →
40
+ * security headers →
41
+ * (optional) rate limit → (optional) request logger, a default `/health`, and the
42
+ * canonical error-envelope `onError`. The caller mounts feature routers on the
43
+ * returned app (`app.route('/api/v1', notesRouter)`).
44
+ */
45
+ export function createApp(opts: CreateAppOpts = {}): Hono<CursedbeltEnv> {
46
+ const app = new Hono<CursedbeltEnv>();
47
+
48
+ app.use('*', correlationId());
49
+ if (opts.cors) app.use('*', corsAllowlist(opts.cors));
50
+ if (opts.security !== false) app.use('*', securityHeaders(opts.security ?? {}));
51
+ if (opts.rateLimit) {
52
+ app.use('*', createRateLimiter({ trustedProxyHops: opts.trustedProxyHops ?? 0, ...opts.rateLimit }));
53
+ }
54
+ if (opts.db) app.use('*', requestLogger(opts.db));
55
+
56
+ if (opts.health !== false) app.get('/health', (c) => c.json({ status: 'ok' }));
57
+
58
+ app.onError(errorEnvelope);
59
+ return app;
60
+ }
61
+
62
+ export interface ServeBunOpts<T = unknown> {
63
+ /** Listen port. Default: 3000. */
64
+ port?: number;
65
+ /** Bind hostname (e.g. `'127.0.0.1'`). Default: Bun's default (all interfaces). */
66
+ hostname?: string;
67
+ /** WebSocket handler from the WS hub (task 28). Only set when the app upgrades. */
68
+ websocket?: WebSocketHandler<T>;
69
+ /** TLS options for HTTPS. */
70
+ tls?: TLSOptions;
71
+ }
72
+
73
+ /**
74
+ * Serve a Hono app on `Bun.serve` (`fetch: app.fetch`, zero adapter). Pass
75
+ * `websocket` only when the app actually calls `server.upgrade()` (task 28) — Bun
76
+ * rejects a `websocket` handler that is never used.
77
+ */
78
+ export function serveBun<T = unknown>(
79
+ app: Hono<CursedbeltEnv>,
80
+ opts: ServeBunOpts<T> = {},
81
+ ): Server<T> {
82
+ const port = opts.port ?? 3000;
83
+ const { hostname, tls, websocket } = opts;
84
+ // Two concrete call shapes (not a spread) so Bun.serve picks the right overload.
85
+ if (websocket) {
86
+ return Bun.serve({ port, hostname, fetch: app.fetch, tls, websocket });
87
+ }
88
+ return Bun.serve({ port, hostname, fetch: app.fetch, tls }) as Server<T>;
89
+ }
@@ -0,0 +1,2 @@
1
+ export { AUDIT_LOG_MIGRATIONS } from './migrations';
2
+ export { auditStateAt, auditStateAtVersion, auditStep, createSqliteAuditSink } from './sqliteSink';
@@ -0,0 +1,88 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { Migration } from '../migration/types';
3
+
4
+ // ONE shared append-only table backs every namespace's audit trail — not a
5
+ // shadow table per business table. cursedshell's `defineService` mutations
6
+ // are dispatched against host-injected Store interfaces (`NotesStore`,
7
+ // `AliasStore`, …), never raw SQL the host's DB owns, so there is no single
8
+ // business table a `CREATE TRIGGER` could attach to. `(namespace, target_id)`
9
+ // plays the role a per-table shadow table would: history for cursedshell's
10
+ // `todo` plugin and an app's own `todo` table can coexist without collision.
11
+ export const AUDIT_LOG_MIGRATIONS: readonly Migration[] = [
12
+ {
13
+ name: '0001_audit_log',
14
+ up(db: Database): void {
15
+ db.run(`
16
+ CREATE TABLE IF NOT EXISTS audit_log (
17
+ audit_id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ namespace TEXT NOT NULL,
19
+ mutation_key TEXT NOT NULL,
20
+ target_id TEXT NOT NULL,
21
+ version_key INTEGER NOT NULL,
22
+ operation TEXT NOT NULL CHECK (operation IN ('insert', 'update', 'delete')),
23
+ tenant_id TEXT NOT NULL,
24
+ changed_by TEXT,
25
+ changed_at TEXT NOT NULL,
26
+ snapshot_data TEXT,
27
+ UNIQUE(tenant_id, namespace, target_id, version_key)
28
+ )
29
+ `);
30
+ // Time-travel + timeline() reads key on (tenant_id, namespace, target_id);
31
+ // changed_at additionally covers the "state as of a timestamp" query.
32
+ db.run(
33
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_lookup ON audit_log(tenant_id, namespace, target_id, changed_at)`,
34
+ );
35
+ },
36
+ },
37
+ {
38
+ // 05 §A1, the repair half. `0001` above was corrected in place so a fresh
39
+ // database is right from the start — but `CREATE TABLE IF NOT EXISTS` means
40
+ // an EXISTING `audit_log` keeps the old `UNIQUE(namespace, target_id,
41
+ // version_key)`, which is cross-tenant: two tenants whose ids collide
42
+ // (`target_id` is namespace-scoped, not minted per tenant — a plugin's
43
+ // `todo` row "5" is "5" for everybody) contend for the same version
44
+ // sequence, and one tenant's write fails against a row it cannot read.
45
+ //
46
+ // A table-level UNIQUE cannot be altered in SQLite, so this rebuilds. It is
47
+ // ordered AFTER `0001` and guarded on the old constraint actually being
48
+ // present, so on a fresh database it is a no-op rather than a needless copy
49
+ // of the whole audit trail.
50
+ name: '0002_tenant_scoped_version',
51
+ up(db: Database): void {
52
+ const schema = (
53
+ db
54
+ .query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'audit_log'")
55
+ .get() as { sql: string } | null
56
+ )?.sql;
57
+ if (!schema || !/UNIQUE\s*\(\s*namespace/i.test(schema)) return;
58
+
59
+ db.run(`
60
+ CREATE TABLE audit_log_0002 (
61
+ audit_id INTEGER PRIMARY KEY AUTOINCREMENT,
62
+ namespace TEXT NOT NULL,
63
+ mutation_key TEXT NOT NULL,
64
+ target_id TEXT NOT NULL,
65
+ version_key INTEGER NOT NULL,
66
+ operation TEXT NOT NULL CHECK (operation IN ('insert', 'update', 'delete')),
67
+ tenant_id TEXT NOT NULL,
68
+ changed_by TEXT,
69
+ changed_at TEXT NOT NULL,
70
+ snapshot_data TEXT,
71
+ UNIQUE(tenant_id, namespace, target_id, version_key)
72
+ )
73
+ `);
74
+ db.run(`
75
+ INSERT INTO audit_log_0002
76
+ (audit_id, namespace, mutation_key, target_id, version_key, operation, tenant_id, changed_by, changed_at, snapshot_data)
77
+ SELECT audit_id, namespace, mutation_key, target_id, version_key, operation, tenant_id, changed_by, changed_at, snapshot_data
78
+ FROM audit_log
79
+ `);
80
+ db.run('DROP TABLE audit_log');
81
+ db.run('ALTER TABLE audit_log_0002 RENAME TO audit_log');
82
+ // The rename does not carry the index — it was dropped with the old table.
83
+ db.run(
84
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_lookup ON audit_log(tenant_id, namespace, target_id, changed_at)`,
85
+ );
86
+ },
87
+ },
88
+ ];
@@ -0,0 +1,200 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { beforeEach, describe, expect, it } from 'bun:test';
3
+ import { auditStateAt, auditStateAtVersion, auditStep, createSqliteAuditSink } from './sqliteSink';
4
+
5
+ let db: Database;
6
+
7
+ beforeEach(() => {
8
+ db = new Database(':memory:');
9
+ });
10
+
11
+ describe('createSqliteAuditSink', () => {
12
+ it('assigns sequential version keys per (namespace, targetId) and persists snapshots', async () => {
13
+ const sink = createSqliteAuditSink(db);
14
+ const v1 = await sink.record({
15
+ namespace: 'todo',
16
+ mutationKey: 'add',
17
+ targetId: '5',
18
+ operation: 'insert',
19
+ snapshot: { id: '5', title: 'a' },
20
+ tenantId: 't1',
21
+ changedBy: 'u1',
22
+ });
23
+ const v2 = await sink.record({
24
+ namespace: 'todo',
25
+ mutationKey: 'update',
26
+ targetId: '5',
27
+ operation: 'update',
28
+ snapshot: { id: '5', title: 'b' },
29
+ tenantId: 't1',
30
+ });
31
+ expect(v1.versionKey).toBe(1);
32
+ expect(v2.versionKey).toBe(2);
33
+ expect(v1.snapshot).toEqual({ id: '5', title: 'a' });
34
+ });
35
+
36
+ it('stores a null snapshot for delete tombstones', async () => {
37
+ const sink = createSqliteAuditSink(db);
38
+ const deleted = await sink.record({
39
+ namespace: 'todo',
40
+ mutationKey: 'remove',
41
+ targetId: '5',
42
+ operation: 'delete',
43
+ snapshot: null,
44
+ tenantId: 't1',
45
+ });
46
+ expect(deleted.snapshot).toBeNull();
47
+ });
48
+
49
+ it('is idempotent to construct twice against the same db (migration re-run is a no-op)', () => {
50
+ createSqliteAuditSink(db);
51
+ expect(() => createSqliteAuditSink(db)).not.toThrow();
52
+ });
53
+
54
+ it('timeline() returns full history oldest-first', async () => {
55
+ const sink = createSqliteAuditSink(db);
56
+ await sink.record({
57
+ namespace: 'todo',
58
+ mutationKey: 'add',
59
+ targetId: '5',
60
+ operation: 'insert',
61
+ snapshot: { title: 'a' },
62
+ tenantId: 't1',
63
+ });
64
+ await sink.record({
65
+ namespace: 'todo',
66
+ mutationKey: 'remove',
67
+ targetId: '5',
68
+ operation: 'delete',
69
+ snapshot: null,
70
+ tenantId: 't1',
71
+ });
72
+ const timeline = await sink.timeline!('t1', 'todo', '5');
73
+ expect(timeline.map((r) => r.operation)).toEqual(['insert', 'delete']);
74
+ });
75
+
76
+ // 05 §A1. `targetId` is a namespace-local business key, not a minted id, so
77
+ // two tenants routinely mutate "5". Before the tenant joined the UNIQUE and
78
+ // the MAX() that feeds it, tenant B's first write took version 2 — or was
79
+ // refused outright by tenant A's row — and B's timeline read A's history.
80
+ it('versions and reads per TENANT, not just per (namespace, targetId)', async () => {
81
+ const sink = createSqliteAuditSink(db);
82
+ const entry = (tenantId: string, title: string) => ({
83
+ namespace: 'todo',
84
+ mutationKey: 'add',
85
+ targetId: '5',
86
+ operation: 'insert' as const,
87
+ snapshot: { title },
88
+ tenantId,
89
+ });
90
+ const first = await sink.record(entry('t1', 'a'));
91
+ const second = await sink.record(entry('t2', 'b'));
92
+
93
+ expect(first.versionKey).toBe(1);
94
+ expect(second.versionKey).toBe(1);
95
+ expect((await sink.timeline!('t2', 'todo', '5')).map((r) => r.snapshot)).toEqual([{ title: 'b' }]);
96
+ expect(auditStateAtVersion(db, 't1', 'todo', '5', 1)?.snapshot).toEqual({ title: 'a' });
97
+ });
98
+
99
+ // The repair half: a database created before this landing carries
100
+ // `UNIQUE(namespace, target_id, version_key)` and `CREATE TABLE IF NOT EXISTS`
101
+ // would never correct it, so `0002` rebuilds — preserving the rows.
102
+ it('rebuilds a pre-existing audit_log that carries the cross-tenant UNIQUE', () => {
103
+ db.run(`
104
+ CREATE TABLE audit_log (
105
+ audit_id INTEGER PRIMARY KEY AUTOINCREMENT,
106
+ namespace TEXT NOT NULL,
107
+ mutation_key TEXT NOT NULL,
108
+ target_id TEXT NOT NULL,
109
+ version_key INTEGER NOT NULL,
110
+ operation TEXT NOT NULL CHECK (operation IN ('insert', 'update', 'delete')),
111
+ tenant_id TEXT NOT NULL,
112
+ changed_by TEXT,
113
+ changed_at TEXT NOT NULL,
114
+ snapshot_data TEXT,
115
+ UNIQUE(namespace, target_id, version_key)
116
+ )
117
+ `);
118
+ db.run(
119
+ `INSERT INTO audit_log (namespace, mutation_key, target_id, version_key, operation, tenant_id, changed_at, snapshot_data)
120
+ VALUES ('todo', 'add', '5', 1, 'insert', 't1', '2026-01-01T00:00:00.000Z', '{"title":"kept"}')`,
121
+ );
122
+
123
+ const sink = createSqliteAuditSink(db);
124
+
125
+ const schema = (
126
+ db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'audit_log'").get() as { sql: string }
127
+ ).sql;
128
+ expect(schema).toContain('UNIQUE(tenant_id, namespace, target_id, version_key)');
129
+ // The history survived the rebuild …
130
+ expect(auditStateAtVersion(db, 't1', 'todo', '5', 1)?.snapshot).toEqual({ title: 'kept' });
131
+ // … and the constraint the rebuild exists for now holds.
132
+ const other = sink.record({
133
+ namespace: 'todo',
134
+ mutationKey: 'add',
135
+ targetId: '5',
136
+ operation: 'insert',
137
+ snapshot: { title: 'theirs' },
138
+ tenantId: 't2',
139
+ });
140
+ expect(other.versionKey).toBe(1);
141
+ });
142
+ });
143
+
144
+ describe('time-travel queries', () => {
145
+ const seed = async () => {
146
+ const sink = createSqliteAuditSink(db);
147
+ await sink.record({
148
+ namespace: 'todo',
149
+ mutationKey: 'add',
150
+ targetId: '5',
151
+ operation: 'insert',
152
+ snapshot: { title: 'v1' },
153
+ tenantId: 't1',
154
+ changedAt: '2026-01-01T00:00:00.000Z',
155
+ });
156
+ await sink.record({
157
+ namespace: 'todo',
158
+ mutationKey: 'update',
159
+ targetId: '5',
160
+ operation: 'update',
161
+ snapshot: { title: 'v2' },
162
+ tenantId: 't1',
163
+ changedAt: '2026-01-02T00:00:00.000Z',
164
+ });
165
+ await sink.record({
166
+ namespace: 'todo',
167
+ mutationKey: 'update',
168
+ targetId: '5',
169
+ operation: 'update',
170
+ snapshot: { title: 'v3' },
171
+ tenantId: 't1',
172
+ changedAt: '2026-01-03T00:00:00.000Z',
173
+ });
174
+ };
175
+
176
+ it('auditStateAt returns the last version at or before the given time', async () => {
177
+ await seed();
178
+ const state = auditStateAt(db, 't1', 'todo', '5', '2026-01-02T12:00:00.000Z');
179
+ expect(state?.snapshot).toEqual({ title: 'v2' });
180
+ });
181
+
182
+ it('auditStateAt returns null before any history exists', async () => {
183
+ await seed();
184
+ expect(auditStateAt(db, 't1', 'todo', '5', '2025-12-31T00:00:00.000Z')).toBeNull();
185
+ });
186
+
187
+ it('auditStateAtVersion jumps directly to a version', async () => {
188
+ await seed();
189
+ expect(auditStateAtVersion(db, 't1', 'todo', '5', 1)?.snapshot).toEqual({ title: 'v1' });
190
+ expect(auditStateAtVersion(db, 't1', 'todo', '5', 99)).toBeNull();
191
+ });
192
+
193
+ it('auditStep walks back and forward one version', async () => {
194
+ await seed();
195
+ expect(auditStep(db, 't1', 'todo', '5', 2, 'back')?.versionKey).toBe(1);
196
+ expect(auditStep(db, 't1', 'todo', '5', 2, 'forward')?.versionKey).toBe(3);
197
+ expect(auditStep(db, 't1', 'todo', '5', 1, 'back')).toBeNull();
198
+ expect(auditStep(db, 't1', 'todo', '5', 3, 'forward')).toBeNull();
199
+ });
200
+ });