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,47 @@
1
+ /**
2
+ * `cursedbelt-server/notifications` — the SERVER half of the fleet's one
3
+ * notification primitive.
4
+ *
5
+ * ── What was already here, and what was missing ─────────────────────────────
6
+ * The dispatch engine has lived in `cwip/notifications` for months (a
7
+ * persistent per-user row, `groupKey` coalescing, unread counts, deep links, a
8
+ * live-delivery sink) and the whole client has lived in
9
+ * `cursedbelt/react/notifications` (store, bell, feed, inbox, poll transport).
10
+ * What did NOT exist was a way to MOUNT any of it: the only mounted surface in
11
+ * the fleet was `orch-ui/services/notifications`, written against
12
+ * `@satellites/satellite-rpc` inside a package only the orchestrator consumes.
13
+ * So a satellite wanting a bell had two options, and both were wrong — adopt
14
+ * orch's rpc layer, or write the routes again.
15
+ *
16
+ * This module is the third: ordinary Hono routes, an SSE hub behind the sink
17
+ * the center already had, and a migration for apps using cursedbelt's runner.
18
+ *
19
+ * const notifications = createNotificationService({ db });
20
+ * notifications.mount(app, { authorize: (c) => userOf(c)?.id ?? null });
21
+ *
22
+ * and on the client:
23
+ *
24
+ * const { store } = createNotificationsBinding(); // cursedbelt/react/notifications
25
+ * <NotificationBell store={store} />
26
+ *
27
+ * ── The three properties worth knowing ──────────────────────────────────────
28
+ * 1. **A notification is a row before it is an event.** Delivery is a cache of
29
+ * who is listening; the poll transport is the backstop and is not optional.
30
+ * Nothing is lost by a closed laptop.
31
+ * 2. **Coalescing is a database constraint, not a debounce.** The partial
32
+ * unique index on `(user_id, group_key) WHERE group_key IS NOT NULL AND
33
+ * done = 0` is what makes "twenty of the same thing" one row with a count,
34
+ * even under concurrent producers.
35
+ * 3. **The routes address the session's user and nothing else.** There is no
36
+ * route that notifies somebody else; producing is server-side code calling
37
+ * `center.notify*`.
38
+ */
39
+ export { createNotificationHub } from "./hub";
40
+ export type { NotificationHub, NotificationHubEvent, NotificationHubListener, NotificationHubOptions, } from "./hub";
41
+ export { NOTIFICATION_MIGRATIONS, NOTIFICATIONS_TABLE } from "./migrations";
42
+ export { mountNotificationRoutes } from "./routes";
43
+ export type { NotificationRouteCenter, NotificationRoutesOptions } from "./routes";
44
+ export { createNotificationService } from "./service";
45
+ export type { CreateNotificationServiceOptions, NotificationService, NotificationServiceMountOptions, } from "./service";
46
+ export { createNotificationCenter, createMemoryNotificationStore, createSqliteNotificationStore, migrateNotificationsSchema, } from "cwip/notifications";
47
+ export type { AppNotification, ListNotificationsQuery, NotificationAudience, NotificationCenter, NotificationCenterOptions, NotificationDraft, NotificationFilter, NotificationPage, NotificationSeverity, NotificationSink, NotificationStore, Notifier, } from "cwip/notifications";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `cursedbelt-server/notifications` — the SERVER half of the fleet's one
3
+ * notification primitive.
4
+ *
5
+ * ── What was already here, and what was missing ─────────────────────────────
6
+ * The dispatch engine has lived in `cwip/notifications` for months (a
7
+ * persistent per-user row, `groupKey` coalescing, unread counts, deep links, a
8
+ * live-delivery sink) and the whole client has lived in
9
+ * `cursedbelt/react/notifications` (store, bell, feed, inbox, poll transport).
10
+ * What did NOT exist was a way to MOUNT any of it: the only mounted surface in
11
+ * the fleet was `orch-ui/services/notifications`, written against
12
+ * `@satellites/satellite-rpc` inside a package only the orchestrator consumes.
13
+ * So a satellite wanting a bell had two options, and both were wrong — adopt
14
+ * orch's rpc layer, or write the routes again.
15
+ *
16
+ * This module is the third: ordinary Hono routes, an SSE hub behind the sink
17
+ * the center already had, and a migration for apps using cursedbelt's runner.
18
+ *
19
+ * const notifications = createNotificationService({ db });
20
+ * notifications.mount(app, { authorize: (c) => userOf(c)?.id ?? null });
21
+ *
22
+ * and on the client:
23
+ *
24
+ * const { store } = createNotificationsBinding(); // cursedbelt/react/notifications
25
+ * <NotificationBell store={store} />
26
+ *
27
+ * ── The three properties worth knowing ──────────────────────────────────────
28
+ * 1. **A notification is a row before it is an event.** Delivery is a cache of
29
+ * who is listening; the poll transport is the backstop and is not optional.
30
+ * Nothing is lost by a closed laptop.
31
+ * 2. **Coalescing is a database constraint, not a debounce.** The partial
32
+ * unique index on `(user_id, group_key) WHERE group_key IS NOT NULL AND
33
+ * done = 0` is what makes "twenty of the same thing" one row with a count,
34
+ * even under concurrent producers.
35
+ * 3. **The routes address the session's user and nothing else.** There is no
36
+ * route that notifies somebody else; producing is server-side code calling
37
+ * `center.notify*`.
38
+ */
39
+ export { createNotificationHub } from "./hub";
40
+ export { NOTIFICATION_MIGRATIONS, NOTIFICATIONS_TABLE } from "./migrations";
41
+ export { mountNotificationRoutes } from "./routes";
42
+ export { createNotificationService } from "./service";
43
+ // Re-exported so a server consumer needs ONE import for the whole contract —
44
+ // the same courtesy `server/sharing` extends over `cursedbelt-core/sharing`. The
45
+ // engine remains cwip's; this is a door, not a copy.
46
+ export { createNotificationCenter, createMemoryNotificationStore, createSqliteNotificationStore, migrateNotificationsSchema, } from "cwip/notifications";
@@ -0,0 +1,13 @@
1
+ import type { Migration } from "../migration/types";
2
+ export declare const NOTIFICATIONS_TABLE = "notifications";
3
+ /**
4
+ * Ordered migrations for the notification table.
5
+ *
6
+ * applyRecommendedPragmas(db);
7
+ * runMigrations(db, [...APP_MIGRATIONS, ...NOTIFICATION_MIGRATIONS]);
8
+ *
9
+ * An app that instead constructs `createSqliteNotificationStore(db)` gets the
10
+ * same table without this — the store migrates on construction. Running both is
11
+ * harmless and is the normal state of an app that adopted the store first.
12
+ */
13
+ export declare const NOTIFICATION_MIGRATIONS: readonly Migration[];
@@ -0,0 +1,20 @@
1
+ import { migrateNotificationsSchema } from "cwip/notifications";
2
+ export const NOTIFICATIONS_TABLE = "notifications";
3
+ /**
4
+ * Ordered migrations for the notification table.
5
+ *
6
+ * applyRecommendedPragmas(db);
7
+ * runMigrations(db, [...APP_MIGRATIONS, ...NOTIFICATION_MIGRATIONS]);
8
+ *
9
+ * An app that instead constructs `createSqliteNotificationStore(db)` gets the
10
+ * same table without this — the store migrates on construction. Running both is
11
+ * harmless and is the normal state of an app that adopted the store first.
12
+ */
13
+ export const NOTIFICATION_MIGRATIONS = [
14
+ {
15
+ name: "0001_notifications",
16
+ up(db) {
17
+ migrateNotificationsSchema(db, NOTIFICATIONS_TABLE);
18
+ },
19
+ },
20
+ ];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The plain-HTTP surface for the notification center.
3
+ *
4
+ * ── Why this exists (2026-08-11) ────────────────────────────────────────────
5
+ * The engine (`cwip/notifications`), the store and the whole client
6
+ * (`cursedbelt/react/notifications`) were already fleet-wide. The only mounted
7
+ * SERVER surface was `orch-ui/services/notifications`, written against
8
+ * `@satellites/satellite-rpc` and living inside a package only the orchestrator
9
+ * consumes — so every other satellite's route to a notification was "adopt
10
+ * orch's rpc layer" or "write it again". This is that surface as ordinary
11
+ * routes on an ordinary Hono app, one mount call, no rpc.
12
+ *
13
+ * The route table mirrors the rpc one 1:1 on purpose: the two are the same
14
+ * contract in two dialects, and a client written against either sees the same
15
+ * fields. `mutations` are POST/DELETE and `queries` are GET, so the GET-only
16
+ * enforcement middleware some satellites run stays meaningful.
17
+ *
18
+ * GET {base} → { items, hasMore } ?filter=&limit=&offset=
19
+ * GET {base}/unread-count → { count }
20
+ * GET {base}/stream → SSE (live delivery; needs a hub)
21
+ * GET {base}/:id → { notification }
22
+ * POST {base}/read-all → { changed }
23
+ * POST {base}/done-all → { changed }
24
+ * POST {base}/:id/read → { changed } body { read?: boolean }
25
+ * POST {base}/:id/done → { changed } body { done?: boolean }
26
+ * DELETE {base}/:id → { changed }
27
+ *
28
+ * 🔴 **Every handler addresses `authorize(c)`'s user and never a body field.**
29
+ * The center is user-scoped at the store level, but a route that took a
30
+ * `userId` parameter would make that scoping decorative — so there is no such
31
+ * parameter anywhere below, and there is deliberately no route that notifies
32
+ * somebody else. Producing notifications is server-side business logic calling
33
+ * `center.notify*` directly; a browser can only ever read and acknowledge its
34
+ * OWN inbox.
35
+ */
36
+ import type { Context, Hono } from "hono";
37
+ import type { NotificationCenter } from "cwip/notifications";
38
+ import type { NotificationHub } from "./hub";
39
+ /** The slice of the center these routes use — `createNotificationCenter(...)`
40
+ * satisfies it, and so does any app-specific facade over one. */
41
+ export type NotificationRouteCenter = Pick<NotificationCenter, "list" | "get" | "unreadCount" | "markRead" | "markAllRead" | "setDone" | "doneAll" | "remove">;
42
+ export interface NotificationRoutesOptions {
43
+ center: NotificationRouteCenter;
44
+ /**
45
+ * Resolve the caller to a user id, or null to refuse with 401. This is the
46
+ * app's session model and the ONLY thing that decides whose inbox is served.
47
+ */
48
+ authorize: (c: Context) => string | null | Promise<string | null>;
49
+ /** Live delivery. Omit it and `{base}/stream` is not mounted at all — a
50
+ * route that answers 200 with a stream that never emits is worse than a
51
+ * 404 the client can fall back from. */
52
+ hub?: NotificationHub;
53
+ /** Path prefix. Default `/api/notifications`. */
54
+ basePath?: string;
55
+ /** Largest page a caller may ask for. Default 200. */
56
+ maxLimit?: number;
57
+ }
58
+ export declare function mountNotificationRoutes(app: Hono, options: NotificationRoutesOptions): void;
@@ -0,0 +1,113 @@
1
+ const NO_STORE = { "cache-control": "no-store" };
2
+ const FILTERS = ["unread", "all", "done"];
3
+ const parseFilter = (raw) => FILTERS.find((f) => f === raw);
4
+ /** Query integers are untrusted text: a NaN or a negative offset must become
5
+ * the default, never reach SQL, and never be an error the caller has to handle. */
6
+ const parseInt_ = (raw, min, max) => {
7
+ if (raw === undefined || raw === "")
8
+ return undefined;
9
+ const value = Number.parseInt(raw, 10);
10
+ if (!Number.isFinite(value))
11
+ return undefined;
12
+ return Math.min(max, Math.max(min, value));
13
+ };
14
+ /** A body that is absent, malformed, or oversized is `{}` — every field these
15
+ * routes read is optional with a documented default, so there is nothing a
16
+ * caller could usefully be told about a bad one. */
17
+ const readFlag = async (c, key) => {
18
+ try {
19
+ const raw = await c.req.text();
20
+ if (!raw || raw.length > 1024)
21
+ return true;
22
+ const body = JSON.parse(raw);
23
+ const value = body?.[key];
24
+ return typeof value === "boolean" ? value : true;
25
+ }
26
+ catch {
27
+ return true;
28
+ }
29
+ };
30
+ export function mountNotificationRoutes(app, options) {
31
+ const base = options.basePath ?? "/api/notifications";
32
+ const maxLimit = options.maxLimit ?? 200;
33
+ const { center, hub } = options;
34
+ /** Every handler starts here. Returns the user id, or the 401 to return. */
35
+ const whoOr401 = async (c) => {
36
+ const userId = await options.authorize(c);
37
+ if (!userId)
38
+ return c.json({ error: "unauthorized" }, 401, NO_STORE);
39
+ return userId;
40
+ };
41
+ app.get(base, async (c) => {
42
+ const who = await whoOr401(c);
43
+ if (typeof who !== "string")
44
+ return who;
45
+ const page = await center.list({
46
+ userId: who,
47
+ filter: parseFilter(c.req.query("filter")),
48
+ limit: parseInt_(c.req.query("limit"), 1, maxLimit),
49
+ offset: parseInt_(c.req.query("offset"), 0, Number.MAX_SAFE_INTEGER),
50
+ });
51
+ return c.json(page, 200, NO_STORE);
52
+ });
53
+ app.get(`${base}/unread-count`, async (c) => {
54
+ const who = await whoOr401(c);
55
+ if (typeof who !== "string")
56
+ return who;
57
+ return c.json({ count: await center.unreadCount(who) }, 200, NO_STORE);
58
+ });
59
+ // Mounted only when there is something to stream — see the option's doc.
60
+ if (hub) {
61
+ app.get(`${base}/stream`, async (c) => {
62
+ const who = await whoOr401(c);
63
+ if (typeof who !== "string")
64
+ return who;
65
+ // The request's own abort signal is what ends the stream when the tab
66
+ // closes; without it the subscription outlives the socket.
67
+ return hub.stream(who, { signal: c.req.raw.signal });
68
+ });
69
+ }
70
+ app.post(`${base}/read-all`, async (c) => {
71
+ const who = await whoOr401(c);
72
+ if (typeof who !== "string")
73
+ return who;
74
+ return c.json({ changed: await center.markAllRead(who) }, 200, NO_STORE);
75
+ });
76
+ app.post(`${base}/done-all`, async (c) => {
77
+ const who = await whoOr401(c);
78
+ if (typeof who !== "string")
79
+ return who;
80
+ return c.json({ changed: await center.doneAll(who) }, 200, NO_STORE);
81
+ });
82
+ app.post(`${base}/:id/read`, async (c) => {
83
+ const who = await whoOr401(c);
84
+ if (typeof who !== "string")
85
+ return who;
86
+ const read = await readFlag(c, "read");
87
+ return c.json({ changed: await center.markRead(who, c.req.param("id"), read) }, 200, NO_STORE);
88
+ });
89
+ app.post(`${base}/:id/done`, async (c) => {
90
+ const who = await whoOr401(c);
91
+ if (typeof who !== "string")
92
+ return who;
93
+ const done = await readFlag(c, "done");
94
+ return c.json({ changed: await center.setDone(who, c.req.param("id"), done) }, 200, NO_STORE);
95
+ });
96
+ app.delete(`${base}/:id`, async (c) => {
97
+ const who = await whoOr401(c);
98
+ if (typeof who !== "string")
99
+ return who;
100
+ return c.json({ changed: await center.remove(who, c.req.param("id")) }, 200, NO_STORE);
101
+ });
102
+ // LAST: `/:id` would otherwise swallow `/unread-count` and `/stream`, and
103
+ // the failure is a 404-shaped `{ notification: null }` rather than an error.
104
+ app.get(`${base}/:id`, async (c) => {
105
+ const who = await whoOr401(c);
106
+ if (typeof who !== "string")
107
+ return who;
108
+ const notification = await center.get(who, c.req.param("id"));
109
+ if (!notification)
110
+ return c.json({ error: "not found" }, 404, NO_STORE);
111
+ return c.json({ notification }, 200, NO_STORE);
112
+ });
113
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * One call that gives an app the whole server half: a migrated table, the
3
+ * dispatch center, live delivery, and the routes.
4
+ *
5
+ * const notifications = createNotificationService({ db });
6
+ * notifications.mount(app, { authorize: (c) => userOf(c)?.id ?? null });
7
+ * // …anywhere in the app's business logic:
8
+ * await notifications.center.notify(userId, { type: "auth.login", title: "New sign-in" });
9
+ *
10
+ * Everything it composes is separately usable — an app that already has a
11
+ * center, or that wants the routes on a store of its own, calls
12
+ * `mountNotificationRoutes` directly. This exists so the common case is one
13
+ * import and no wiring to get wrong.
14
+ */
15
+ import type { Database } from "bun:sqlite";
16
+ import { type NotificationCenter, type NotificationStore } from "cwip/notifications";
17
+ import type { Context, Hono } from "hono";
18
+ import { type NotificationHub } from "./hub";
19
+ export interface CreateNotificationServiceOptions {
20
+ /** The app's own database — the table lands beside its rows. */
21
+ db: Database;
22
+ /** Table name override (identifier-validated by the store). */
23
+ table?: string;
24
+ /** Resolve a role audience (`notifyAudience({ role })`) to user ids. */
25
+ resolveRole?: (role: string) => Promise<string[]>;
26
+ /**
27
+ * Live delivery. `false` builds no hub — the client's poll transport is the
28
+ * whole delivery story then, which is a perfectly good one for an app whose
29
+ * notifications are not time-critical.
30
+ */
31
+ live?: boolean | {
32
+ heartbeatMs?: number;
33
+ maxStreamsPerUser?: number;
34
+ };
35
+ /** Producer-path failures and sink failures land here. Defaults to stderr. */
36
+ onError?: (error: unknown, context: string) => void;
37
+ /** Test seams, forwarded to the center. */
38
+ now?: () => number;
39
+ newId?: () => string;
40
+ }
41
+ export interface NotificationServiceMountOptions {
42
+ /** Resolve the caller to a user id, or null to refuse. */
43
+ authorize: (c: Context) => string | null | Promise<string | null>;
44
+ /** Path prefix. Default `/api/notifications`. */
45
+ basePath?: string;
46
+ /** Largest page a caller may ask for. Default 200. */
47
+ maxLimit?: number;
48
+ }
49
+ export interface NotificationService {
50
+ readonly center: NotificationCenter;
51
+ readonly store: NotificationStore;
52
+ /** Null when `live: false` — there is then nothing to stream. */
53
+ readonly hub: NotificationHub | null;
54
+ mount(app: Hono, options: NotificationServiceMountOptions): void;
55
+ /** Drop live subscribers (shutdown, tests). Safe to call with no hub. */
56
+ close(): void;
57
+ }
58
+ export declare function createNotificationService(options: CreateNotificationServiceOptions): NotificationService;
@@ -0,0 +1,41 @@
1
+ import { createNotificationCenter, createSqliteNotificationStore, } from "cwip/notifications";
2
+ import { createNotificationHub } from "./hub";
3
+ import { mountNotificationRoutes } from "./routes";
4
+ export function createNotificationService(options) {
5
+ const onError = options.onError ??
6
+ ((error, context) => console.error(`[notifications] ${context}:`, error instanceof Error ? error.message : error));
7
+ const store = createSqliteNotificationStore(options.db, {
8
+ table: options.table,
9
+ });
10
+ const hub = options.live === false
11
+ ? null
12
+ : createNotificationHub({
13
+ ...(typeof options.live === "object" ? options.live : {}),
14
+ onError,
15
+ });
16
+ const center = createNotificationCenter({
17
+ store,
18
+ sink: hub?.sink,
19
+ resolveRole: options.resolveRole,
20
+ now: options.now,
21
+ newId: options.newId,
22
+ onError,
23
+ });
24
+ return {
25
+ center,
26
+ store,
27
+ hub,
28
+ mount(app, mountOptions) {
29
+ mountNotificationRoutes(app, {
30
+ center,
31
+ hub: hub ?? undefined,
32
+ authorize: mountOptions.authorize,
33
+ basePath: mountOptions.basePath,
34
+ maxLimit: mountOptions.maxLimit,
35
+ });
36
+ },
37
+ close() {
38
+ hub?.close();
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,19 @@
1
+ /** The one env var. A machine with a shared data root sets this and nothing else. */
2
+ export declare const APP_DATA_HOME_ENV = "APP_DATA_HOME";
3
+ /** The cross-desktop convention this honors ahead of the platform default. */
4
+ export declare const XDG_DATA_HOME_ENV = "XDG_DATA_HOME";
5
+ export interface ResolveAppDataHomeOptions {
6
+ /** An explicit answer from the caller. Wins outright — this is the "argument" half. */
7
+ readonly override?: string;
8
+ /** Environment to read. Injected by tests; defaults to the process's own. */
9
+ readonly env?: Record<string, string | undefined>;
10
+ /**
11
+ * Platform to resolve for. Injected by tests so both branches are proved on one
12
+ * machine; defaults to the running platform.
13
+ */
14
+ readonly platform?: string;
15
+ }
16
+ /**
17
+ * The machine's app-data home. See the module note for the ordering rationale.
18
+ */
19
+ export declare function resolveAppDataHome(options?: ResolveAppDataHomeOptions): string;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Where a machine keeps an app's data — as CONFIGURATION, never as a convention
3
+ * baked into the library.
4
+ *
5
+ * ## What this replaces, and why it is a defect rather than untidiness
6
+ *
7
+ * Until 2026-09-13 three live modules in this package joined the ambient home
8
+ * directory to a hardcoded dotfolder belonging to the previous generation: the
9
+ * retention store, the archive, and `resolveAppDataRoot`. This package is
10
+ * PUBLISHED. Anybody who installed it got a library that went looking for a
11
+ * directory in their home that has nothing to do with them, and — worse — wrote
12
+ * into it if it happened to exist.
13
+ *
14
+ * A library may not know the name of a directory in somebody's home. It may know
15
+ * two things: what the caller told it, and what the operating system says the
16
+ * convention is. That is the whole ladder below.
17
+ *
18
+ * ## The ladder
19
+ *
20
+ * 1. an explicit `override` — a `--data-root` flag, a host's own resolution;
21
+ * 2. `$APP_DATA_HOME` — the ONE env var, for a machine that keeps every app's
22
+ * data under one root so a fleet-wide tool can see all of it at once;
23
+ * 3. `$XDG_DATA_HOME` — the user already told the OS where data goes;
24
+ * 4. the platform default: `~/Library/Application Support` on macOS,
25
+ * `~/.local/share` everywhere else.
26
+ *
27
+ * `$XDG_DATA_HOME` is honored on macOS too. It is not the platform's own
28
+ * convention there, but a user who sets it has stated a preference, and a
29
+ * preference outranks a default on any platform.
30
+ *
31
+ * Nothing here touches the filesystem: it is a pure mapping from environment to
32
+ * path, so a caller can reason about the answer before creating anything, and a
33
+ * test can assert both platforms on one machine.
34
+ */
35
+ import { homedir } from "node:os";
36
+ import { join } from "node:path";
37
+ /** The one env var. A machine with a shared data root sets this and nothing else. */
38
+ export const APP_DATA_HOME_ENV = "APP_DATA_HOME";
39
+ /** The cross-desktop convention this honors ahead of the platform default. */
40
+ export const XDG_DATA_HOME_ENV = "XDG_DATA_HOME";
41
+ /** Trimmed, or undefined — an env var set to whitespace is not a configuration. */
42
+ function configured(value) {
43
+ const trimmed = value?.trim();
44
+ return trimmed ? trimmed : undefined;
45
+ }
46
+ /**
47
+ * The machine's app-data home. See the module note for the ordering rationale.
48
+ */
49
+ export function resolveAppDataHome(options = {}) {
50
+ const env = options.env ?? process.env;
51
+ const override = configured(options.override);
52
+ if (override)
53
+ return override;
54
+ const appDataHome = configured(env[APP_DATA_HOME_ENV]);
55
+ if (appDataHome)
56
+ return appDataHome;
57
+ const xdg = configured(env[XDG_DATA_HOME_ENV]);
58
+ if (xdg)
59
+ return xdg;
60
+ // `env.HOME` rather than `homedir()` alone, so a caller passing an env object
61
+ // gets a resolution it can actually control — every test below depends on it,
62
+ // and so does any host that boots an app under a different account.
63
+ const home = configured(env.HOME) ?? homedir();
64
+ const platform = options.platform ?? process.platform;
65
+ return platform === "darwin"
66
+ ? join(home, "Library", "Application Support")
67
+ : join(home, ".local", "share");
68
+ }
@@ -0,0 +1,37 @@
1
+ export type KeyMode = 'standard' | 'deniable';
2
+ /** A random per-user salt (non-secret, stored plaintext, stable once created). Domain-separates
3
+ * a user's password-derived keys so the same password yields different keys across users. */
4
+ export declare const generateUserSalt: () => string;
5
+ /** Zero-knowledge set key: scrypt(password, per-user salt). The server never stores this (or a
6
+ * hash of it); it exists only transiently after an unlock. Different passwords → disjoint sets. */
7
+ export declare const derivePasswordSetKey: (password: string, userSalt: string) => Buffer;
8
+ /** Recoverable set key: scrypt(serverSecret, userId). The server can always derive it, so
9
+ * `standard`-mode media is decryptable once private is unlocked (session-gated) — protects a
10
+ * stolen disk / Finder / backups, but a compromised running server can decrypt. */
11
+ export declare const deriveServerSetKey: (serverSecret: string, userId: string) => Buffer;
12
+ export declare const sealMeta: (setKey: Buffer, meta: unknown) => string;
13
+ /** Open a sealed envelope, or null if this key doesn't own it (wrong password / mode key).
14
+ * The null return IS the membership signal — no stored set identifier is needed. */
15
+ export declare function openMeta<T = unknown>(setKey: Buffer, sealed: string): T | null;
16
+ export type ContentKey = {
17
+ key: Buffer;
18
+ iv: Buffer;
19
+ };
20
+ export declare const generateContentKey: () => ContentKey;
21
+ export declare const wrapContentKey: (setKey: Buffer, content: ContentKey) => string;
22
+ /** Unwrap a content key, or null if the set key can't open it (wrong password / mode key). */
23
+ export declare function unwrapContentKey(setKey: Buffer, wrapped: string): ContentKey | null;
24
+ /** Pipe a byte stream through the item's AES-128-CTR keystream. Symmetric: pass ciphertext to
25
+ * decrypt, plaintext to encrypt. */
26
+ /** Big-endian increment of a 16-byte CTR IV by `blocks` — the counter arithmetic that lets a
27
+ * decryptor start mid-stream (genapp-100: Range/seek on private blobs). */
28
+ export declare function incrementIv(iv: Buffer, blocks: number): Buffer;
29
+ /**
30
+ * Decrypt a CTR ciphertext stream that starts at the BLOCK-ALIGNED offset covering plaintext
31
+ * byte `startOffset`, dropping the intra-block lead-in — the seek half of {@link cryptBlobStream}.
32
+ * CTR is length-preserving and positionally keyed, so byte N of plaintext only needs ciphertext
33
+ * from block ⌊N/16⌋ onward with the IV counter advanced by that many blocks (genapp-100: this is
34
+ * what makes big private videos scrub on a remote-store host with no local derivatives).
35
+ */
36
+ export declare function cryptBlobRangeStream(content: ContentKey, stream: ReadableStream<Uint8Array>, startOffset: number): ReadableStream<Uint8Array>;
37
+ export declare function cryptBlobStream(content: ContentKey, stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
@@ -0,0 +1,111 @@
1
+ import { createCipheriv, randomBytes } from 'node:crypto';
2
+ import { decryptSecret, deriveKey, encryptSecret } from 'cwip/node';
3
+ /** A random per-user salt (non-secret, stored plaintext, stable once created). Domain-separates
4
+ * a user's password-derived keys so the same password yields different keys across users. */
5
+ export const generateUserSalt = () => randomBytes(16).toString('base64url');
6
+ /** Zero-knowledge set key: scrypt(password, per-user salt). The server never stores this (or a
7
+ * hash of it); it exists only transiently after an unlock. Different passwords → disjoint sets. */
8
+ export const derivePasswordSetKey = (password, userSalt) => deriveKey(password, `pmedia:${userSalt}`);
9
+ /** Recoverable set key: scrypt(serverSecret, userId). The server can always derive it, so
10
+ * `standard`-mode media is decryptable once private is unlocked (session-gated) — protects a
11
+ * stolen disk / Finder / backups, but a compromised running server can decrypt. */
12
+ export const deriveServerSetKey = (serverSecret, userId) => deriveKey(serverSecret, `pmedia-srv:${userId}`);
13
+ // ── Sealed metadata envelope (title / thumbnail key / notes / tags) ──────────────
14
+ // Encrypted under the set key; NEVER stored in the plaintext index. A successful decrypt is
15
+ // also the deniable MEMBERSHIP TEST — trying a password reveals only the items it opens.
16
+ export const sealMeta = (setKey, meta) => encryptSecret(JSON.stringify(meta), setKey);
17
+ /** Open a sealed envelope, or null if this key doesn't own it (wrong password / mode key).
18
+ * The null return IS the membership signal — no stored set identifier is needed. */
19
+ export function openMeta(setKey, sealed) {
20
+ try {
21
+ return JSON.parse(decryptSecret(sealed, setKey));
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ export const generateContentKey = () => ({
28
+ key: randomBytes(16), // AES-128
29
+ iv: randomBytes(16),
30
+ });
31
+ export const wrapContentKey = (setKey, content) => encryptSecret(`${content.key.toString('base64')}.${content.iv.toString('base64')}`, setKey);
32
+ /** Unwrap a content key, or null if the set key can't open it (wrong password / mode key). */
33
+ export function unwrapContentKey(setKey, wrapped) {
34
+ try {
35
+ const [k, iv] = decryptSecret(wrapped, setKey).split('.');
36
+ if (!k || !iv)
37
+ return null;
38
+ return { key: Buffer.from(k, 'base64'), iv: Buffer.from(iv, 'base64') };
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ // ── Whole-file blob cipher (non-video private files: photos, docs) ────────────────
45
+ // AES-128-CTR under the item's content key — the same key/IV material the HLS path wraps, so
46
+ // wrap/unwrap is shared. CTR is length-preserving and streaming (no padding, no buffering), and
47
+ // encrypt/decrypt are the same operation. Tamper-evidence comes from the GCM-sealed descriptor +
48
+ // the gated serve route (parity with the encrypted-HLS segments, which are CBC-without-MAC too).
49
+ /** Pipe a byte stream through the item's AES-128-CTR keystream. Symmetric: pass ciphertext to
50
+ * decrypt, plaintext to encrypt. */
51
+ /** Big-endian increment of a 16-byte CTR IV by `blocks` — the counter arithmetic that lets a
52
+ * decryptor start mid-stream (genapp-100: Range/seek on private blobs). */
53
+ export function incrementIv(iv, blocks) {
54
+ const out = Buffer.from(iv);
55
+ let carry = BigInt(blocks);
56
+ for (let i = out.length - 1; i >= 0 && carry > 0n; i--) {
57
+ const sum = BigInt(out[i]) + (carry & 0xffn);
58
+ out[i] = Number(sum & 0xffn);
59
+ carry = (carry >> 8n) + (sum >> 8n);
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * Decrypt a CTR ciphertext stream that starts at the BLOCK-ALIGNED offset covering plaintext
65
+ * byte `startOffset`, dropping the intra-block lead-in — the seek half of {@link cryptBlobStream}.
66
+ * CTR is length-preserving and positionally keyed, so byte N of plaintext only needs ciphertext
67
+ * from block ⌊N/16⌋ onward with the IV counter advanced by that many blocks (genapp-100: this is
68
+ * what makes big private videos scrub on a remote-store host with no local derivatives).
69
+ */
70
+ export function cryptBlobRangeStream(content, stream, startOffset) {
71
+ const BLOCK = 16;
72
+ const blocks = Math.floor(startOffset / BLOCK);
73
+ let drop = startOffset % BLOCK;
74
+ const cipher = createCipheriv('aes-128-ctr', content.key, incrementIv(content.iv, blocks));
75
+ return stream.pipeThrough(new TransformStream({
76
+ transform(chunk, controller) {
77
+ let out = cipher.update(chunk);
78
+ if (drop > 0) {
79
+ const skip = Math.min(drop, out.byteLength);
80
+ out = out.subarray(skip);
81
+ drop -= skip;
82
+ }
83
+ if (out.byteLength > 0) {
84
+ controller.enqueue(new Uint8Array(out.buffer, out.byteOffset, out.byteLength));
85
+ }
86
+ },
87
+ flush(controller) {
88
+ const out = cipher.final(); // empty for CTR — kept for cipher-contract completeness
89
+ if (out.byteLength > 0) {
90
+ controller.enqueue(new Uint8Array(out.buffer, out.byteOffset, out.byteLength));
91
+ }
92
+ },
93
+ }));
94
+ }
95
+ export function cryptBlobStream(content, stream) {
96
+ const cipher = createCipheriv('aes-128-ctr', content.key, content.iv);
97
+ return stream.pipeThrough(new TransformStream({
98
+ transform(chunk, controller) {
99
+ const out = cipher.update(chunk);
100
+ if (out.byteLength > 0) {
101
+ controller.enqueue(new Uint8Array(out.buffer, out.byteOffset, out.byteLength));
102
+ }
103
+ },
104
+ flush(controller) {
105
+ const out = cipher.final(); // empty for CTR — kept for cipher-contract completeness
106
+ if (out.byteLength > 0) {
107
+ controller.enqueue(new Uint8Array(out.buffer, out.byteOffset, out.byteLength));
108
+ }
109
+ },
110
+ }));
111
+ }