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,37 @@
1
+ /**
2
+ * Where "the last place you were" is kept — one small table, in the app's own
3
+ * database, keyed by the app's own user id.
4
+ *
5
+ * ── Why server-side at all ──────────────────────────────────────────────────
6
+ * `localStorage` would be one line and would answer the wrong question. The
7
+ * owner's report is about coming BACK to an app, and he does that from another
8
+ * device — the 2026-08-13 music sign-in from a second laptop is the same shape.
9
+ * A place remembered only in the browser that saw it is not remembered.
10
+ *
11
+ * ── Why the app's database and not a fleet service ──────────────────────────
12
+ * Same reasoning as `../notifications/migrations.ts`: the SCHEMA is shared, the
13
+ * DATA is local. A resume row is one string per user per app; a central service
14
+ * would put a network hop in front of the first paint of every app in the fleet,
15
+ * and the first thing each would do is cache it.
16
+ *
17
+ * The DDL is idempotent, so calling this twice — or beside a migration runner —
18
+ * converges on one table.
19
+ */
20
+ import type { Database } from 'bun:sqlite';
21
+ /** Longer than any real place, short enough that nobody can park a payload here. */
22
+ export declare const MAX_LOCATION_LENGTH = 2048;
23
+ export interface ResumeRecord {
24
+ location: string;
25
+ updatedAt: number;
26
+ }
27
+ export interface ResumeStore {
28
+ read(userId: string, key?: string): ResumeRecord | null;
29
+ write(userId: string, location: string, key?: string): void;
30
+ /** No key clears every remembered place for that user. */
31
+ clear(userId: string, key?: string): void;
32
+ }
33
+ export interface ResumeStoreOptions {
34
+ table?: string;
35
+ now?: () => number;
36
+ }
37
+ export declare function createSqliteResumeStore(db: Database, options?: ResumeStoreOptions): ResumeStore;
@@ -0,0 +1,38 @@
1
+ /** Longer than any real place, short enough that nobody can park a payload here. */
2
+ export const MAX_LOCATION_LENGTH = 2048;
3
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
+ const DEFAULT_KEY = 'default';
5
+ export function createSqliteResumeStore(db, options = {}) {
6
+ const table = options.table ?? 'cb_resume';
7
+ if (!IDENTIFIER.test(table))
8
+ throw new Error(`invalid resume table name: ${table}`);
9
+ const now = options.now ?? Date.now;
10
+ db.run(`CREATE TABLE IF NOT EXISTS ${table} (
11
+ user_id TEXT NOT NULL,
12
+ place_key TEXT NOT NULL,
13
+ location TEXT NOT NULL,
14
+ updated_at INTEGER NOT NULL,
15
+ PRIMARY KEY (user_id, place_key)
16
+ )`);
17
+ return {
18
+ read(userId, key = DEFAULT_KEY) {
19
+ const row = db
20
+ .query(`SELECT location, updated_at FROM ${table} WHERE user_id = ? AND place_key = ?`)
21
+ .get(userId, key);
22
+ return row ? { location: row.location, updatedAt: row.updated_at } : null;
23
+ },
24
+ write(userId, location, key = DEFAULT_KEY) {
25
+ // Truncated rather than refused: a place that is somehow too long should
26
+ // cost the reader nothing, and the next navigation overwrites it anyway.
27
+ const value = location.slice(0, MAX_LOCATION_LENGTH);
28
+ db.run(`INSERT INTO ${table} (user_id, place_key, location, updated_at) VALUES (?, ?, ?, ?)
29
+ ON CONFLICT(user_id, place_key) DO UPDATE SET location = excluded.location, updated_at = excluded.updated_at`, [userId, key, value, now()]);
30
+ },
31
+ clear(userId, key) {
32
+ if (key === undefined)
33
+ db.run(`DELETE FROM ${table} WHERE user_id = ?`, [userId]);
34
+ else
35
+ db.run(`DELETE FROM ${table} WHERE user_id = ? AND place_key = ?`, [userId, key]);
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,78 @@
1
+ /** One sealed archive file. */
2
+ export interface ArchiveSegment {
3
+ /** Absolute path on this machine. */
4
+ path: string;
5
+ /** Basename — what a collector and the index line name it by. */
6
+ file: string;
7
+ rows: number;
8
+ bytes: number;
9
+ sha256: string;
10
+ at: number;
11
+ }
12
+ export interface ArchiveSink {
13
+ /** Where segments are written. */
14
+ readonly dir: string;
15
+ /** True when THIS machine is the archive's final home (no collector is owed). */
16
+ readonly terminal: boolean;
17
+ /**
18
+ * Bytes of sealed segments still waiting for a collector. Always 0 when
19
+ * `terminal` — a home archive is never "pending", it is the archive.
20
+ */
21
+ pendingBytes(): number;
22
+ /**
23
+ * Seal one segment, proven on disk. THROWS if it cannot prove it: the caller
24
+ * treats that as "these rows stay in the table".
25
+ */
26
+ write(records: ReadonlyArray<Record<string, unknown>>, at: number): ArchiveSegment;
27
+ }
28
+ /** The default budget of uncollected archive on a staged (deployed-box) home.
29
+ * Generous next to the tables it governs — notes' whole `ops` log is ~15 MB —
30
+ * and small next to a 6.7 GB root. Reached only if the collector has stopped. */
31
+ export declare const DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES: number;
32
+ /** `20260825-114233` — sortable, second-resolution, and the same shape the
33
+ * fleet's dated backup dirs already use, so one eye reads both. */
34
+ export declare function archiveStamp(date: Date): string;
35
+ export interface ArchiveHome {
36
+ dir: string;
37
+ terminal: boolean;
38
+ }
39
+ /**
40
+ * Where this app's archive lives on THIS machine, and whether the machine keeps
41
+ * it.
42
+ *
43
+ * `<app-data-home>/cursed-satellites/archive/<app>/`, reached through the same env
44
+ * ladder the retention home uses, so a test, a container and a deployed box each
45
+ * land somewhere writable rather than somewhere assumed.
46
+ */
47
+ export declare function resolveArchiveHome(app: string, env?: Record<string, string | undefined>): ArchiveHome;
48
+ export interface JsonlArchiveOptions {
49
+ app: string;
50
+ /** Stable, app-scoped resource id — the segment name's prefix. */
51
+ resource: string;
52
+ /** Overrides the resolved home. Tests and callers that already resolved it. */
53
+ home?: ArchiveHome;
54
+ env?: Record<string, string | undefined>;
55
+ }
56
+ /**
57
+ * The fleet's archive sink: newline-delimited JSON segments plus an append-only
58
+ * `index.jsonl` naming every segment ever sealed here.
59
+ *
60
+ * The index is what makes the archive answerable without reading gigabytes —
61
+ * "what is in here, how many rows, and does it still hash the same" — and it is
62
+ * appended rather than rewritten so a crash mid-write can cost at most the last
63
+ * line, never the catalogue.
64
+ */
65
+ export declare function createJsonlArchive(options: JsonlArchiveOptions): ArchiveSink;
66
+ /**
67
+ * Re-verify every segment in an archive directory against the sha256 the index
68
+ * recorded — the answer to "is this archive still real?" without a restore.
69
+ *
70
+ * Exists because an archive nobody can check is a belief, and this whole
71
+ * category is a promise that the rows still exist somewhere.
72
+ */
73
+ export declare function verifyArchive(dir: string): {
74
+ segments: number;
75
+ rows: number;
76
+ bytes: number;
77
+ bad: string[];
78
+ };
@@ -0,0 +1,284 @@
1
+ /**
2
+ * The ARCHIVE half of `archived-history` — where the rows go before they are
3
+ * allowed to leave the instance.
4
+ *
5
+ * ── The ordering rule IS the safety argument, and this fleet has broken it ───
6
+ * prod's `diskGuard.sh` rule 1 deletes the oldest `VACUUM INTO` sqlite backups
7
+ * to free space, and its own comment calls those files "NOT regenerable". A
8
+ * cleanup routine that runs before the copy is data loss wearing housekeeping's
9
+ * name, and it is the exact failure this module exists to make impossible. So:
10
+ *
11
+ * 1. serialize the rows,
12
+ * 2. write them to a NEW file, fsync it,
13
+ * 3. RE-READ that file off the disk and prove it — byte length, sha256, line
14
+ * count, and that every line parses back to an object,
15
+ * 4. only then may the caller delete those rows.
16
+ *
17
+ * `write()` throws rather than returning a failure, because the one thing a
18
+ * caller must never be able to do is treat "the archive did not happen" as
19
+ * "nothing to delete". A throw ends the sweep for that resource with the rows
20
+ * still in the table, which is the correct outcome every time.
21
+ *
22
+ * ── A segment per sweep, never an appended file ─────────────────────────────
23
+ * "Append-only" is a property of the DIRECTORY here: each sweep seals its own
24
+ * `<resource>-<stamp>.jsonl` and nothing is ever rewritten. That is not a style
25
+ * choice. On a deployed box the archive is a WAY-STATION — the Mac's
26
+ * `backups:pull-prod` copies segments down and removes the box's copy — and a
27
+ * collector that scp'd a file the app was still appending to would take a
28
+ * prefix and then delete the rest. A sealed file cannot be half a file.
29
+ *
30
+ * ── Terminal vs staged, and the staleness refusal ───────────────────────────
31
+ * On a workstation the archive is home: `<app-data-home>/cursed-satellites/
32
+ * archive/<app>/`, it is `terminal`, and it simply grows. The home is
33
+ * CONFIGURATION ({@link resolveAppDataHome}) — `$APP_DATA_HOME`, else the OS's own
34
+ * convention — never a folder this library invented in somebody's home; that was
35
+ * the published defect fixed 2026-09-13. On a deployed box nothing outside the
36
+ * app's own data dir is writable, so it resolves to `$APP_DATA_DIR/archive` and
37
+ * is `staged` — something else has to come and get it. That distinction is what lets the
38
+ * sweeper honor "refuse to trim if the last successful pull is stale" using a
39
+ * fact the box can actually observe: how many bytes of sealed segments are still
40
+ * sitting there uncollected. If the collector stops, the archive fills to its
41
+ * budget and the trim STOPS — bounded archive, complete table — rather than the
42
+ * table trimming into an archive nobody is taking away.
43
+ */
44
+ import { createHash } from "node:crypto";
45
+ import { appendFileSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, writeSync, } from "node:fs";
46
+ import { join } from "node:path";
47
+ import { APP_DATA_HOME_ENV, resolveAppDataHome } from "../paths/appDataHome";
48
+ /** The default budget of uncollected archive on a staged (deployed-box) home.
49
+ * Generous next to the tables it governs — notes' whole `ops` log is ~15 MB —
50
+ * and small next to a 6.7 GB root. Reached only if the collector has stopped. */
51
+ export const DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES = 64 * 1024 * 1024;
52
+ const SAFE_NAME = /^[a-z0-9][a-z0-9-]*$/;
53
+ /** `20260825-114233` — sortable, second-resolution, and the same shape the
54
+ * fleet's dated backup dirs already use, so one eye reads both. */
55
+ export function archiveStamp(date) {
56
+ const p = (n, w = 2) => String(n).padStart(w, "0");
57
+ return (`${date.getUTCFullYear()}${p(date.getUTCMonth() + 1)}${p(date.getUTCDate())}-` +
58
+ `${p(date.getUTCHours())}${p(date.getUTCMinutes())}${p(date.getUTCSeconds())}`);
59
+ }
60
+ /** Can we create and write inside this directory? Probed, never assumed —
61
+ * the same reasoning as `resolveRetentionHome`, and the same failure it
62
+ * prevents: a writer that silently EROFS-es every time. */
63
+ function isUsable(dir) {
64
+ try {
65
+ mkdirSync(dir, { recursive: true });
66
+ const probe = join(dir, ".write-probe");
67
+ writeFileSync(probe, "");
68
+ rmSync(probe, { force: true });
69
+ return true;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ /**
76
+ * Where this app's archive lives on THIS machine, and whether the machine keeps
77
+ * it.
78
+ *
79
+ * `<app-data-home>/cursed-satellites/archive/<app>/`, reached through the same env
80
+ * ladder the retention home uses, so a test, a container and a deployed box each
81
+ * land somewhere writable rather than somewhere assumed.
82
+ */
83
+ export function resolveArchiveHome(app, env = process.env) {
84
+ if (!SAFE_NAME.test(app))
85
+ throw new Error(`archive: app key "${app}" must be lower-kebab`);
86
+ const candidates = [];
87
+ if (env.RETENTION_ARCHIVE_HOME) {
88
+ candidates.push({ dir: join(env.RETENTION_ARCHIVE_HOME, app), terminal: true });
89
+ }
90
+ // An explicitly CONFIGURED home is used whether or not it exists yet — the
91
+ // operator naming it is the statement that it is the right place.
92
+ const configured = env[APP_DATA_HOME_ENV]?.trim();
93
+ if (configured) {
94
+ candidates.push({
95
+ dir: join(configured, "cursed-satellites", "archive", app),
96
+ terminal: true,
97
+ });
98
+ }
99
+ const conventional = resolveAppDataHome({ env });
100
+ // The OS convention is only PREFERRED when it is already there: creating it on
101
+ // a deployed box would strand the archive where no collector looks.
102
+ if (!configured && existsSync(conventional)) {
103
+ candidates.push({
104
+ dir: join(conventional, "cursed-satellites", "archive", app),
105
+ terminal: true,
106
+ });
107
+ }
108
+ // A deployed box: only the app's own data dir is writable under
109
+ // `ProtectSystem=strict`, and something else has to come and collect it.
110
+ if (env.APP_DATA_DIR)
111
+ candidates.push({ dir: join(env.APP_DATA_DIR, "archive"), terminal: false });
112
+ for (const candidate of candidates) {
113
+ if (isUsable(candidate.dir))
114
+ return candidate;
115
+ }
116
+ // Nothing writable. Return the conventional path and let `write()` throw when
117
+ // it is actually used — which, by this module's contract, means nothing is
118
+ // deleted. Never a boot failure.
119
+ return { dir: join(conventional, "cursed-satellites", "archive", app), terminal: true };
120
+ }
121
+ /**
122
+ * The fleet's archive sink: newline-delimited JSON segments plus an append-only
123
+ * `index.jsonl` naming every segment ever sealed here.
124
+ *
125
+ * The index is what makes the archive answerable without reading gigabytes —
126
+ * "what is in here, how many rows, and does it still hash the same" — and it is
127
+ * appended rather than rewritten so a crash mid-write can cost at most the last
128
+ * line, never the catalogue.
129
+ */
130
+ export function createJsonlArchive(options) {
131
+ const { app, resource } = options;
132
+ if (!SAFE_NAME.test(resource)) {
133
+ throw new Error(`archive: resource id "${resource}" must be lower-kebab`);
134
+ }
135
+ const home = options.home ?? resolveArchiveHome(app, options.env);
136
+ const dir = home.dir;
137
+ const terminal = home.terminal;
138
+ const indexPath = join(dir, "index.jsonl");
139
+ return {
140
+ dir,
141
+ terminal,
142
+ pendingBytes() {
143
+ if (terminal)
144
+ return 0;
145
+ let total = 0;
146
+ let names;
147
+ try {
148
+ names = readdirSync(dir);
149
+ }
150
+ catch {
151
+ return 0; // not created yet — nothing is owed
152
+ }
153
+ for (const name of names) {
154
+ if (!name.endsWith(".jsonl") || name === "index.jsonl")
155
+ continue;
156
+ try {
157
+ total += statSync(join(dir, name)).size;
158
+ }
159
+ catch {
160
+ // vanished between readdir and stat — the collector took it
161
+ }
162
+ }
163
+ return total;
164
+ },
165
+ write(records, at) {
166
+ if (records.length === 0)
167
+ throw new Error("archive: refusing to seal an empty segment");
168
+ const body = `${records.map((row) => JSON.stringify(row)).join("\n")}\n`;
169
+ const bytes = Buffer.byteLength(body);
170
+ const sha256 = createHash("sha256").update(body).digest("hex");
171
+ const file = `${resource}-${archiveStamp(new Date(at))}.jsonl`;
172
+ const path = join(dir, file);
173
+ const tmp = `${path}.${process.pid}.tmp`;
174
+ mkdirSync(dir, { recursive: true });
175
+ // Write + fsync THE FILE. Without the fsync the bytes can be in the page
176
+ // cache while the rows are already gone from a database that fsyncs its
177
+ // own WAL — a power cut between the two loses exactly the rows this
178
+ // module exists to keep.
179
+ const fd = openSync(tmp, "w");
180
+ try {
181
+ writeSync(fd, body);
182
+ fsyncSync(fd);
183
+ }
184
+ finally {
185
+ closeSync(fd);
186
+ }
187
+ // Prove it, off the disk, before anything is deleted. Re-reading is the
188
+ // only check that covers a short write, a full disk that reported
189
+ // success, and a filesystem that lied.
190
+ let readBack;
191
+ try {
192
+ readBack = readFileSync(tmp, "utf8");
193
+ }
194
+ catch (error) {
195
+ rmSync(tmp, { force: true });
196
+ throw new Error(`archive: could not read back ${tmp} — ${error instanceof Error ? error.message : String(error)}`);
197
+ }
198
+ const verify = (ok, what) => {
199
+ if (ok)
200
+ return;
201
+ rmSync(tmp, { force: true });
202
+ throw new Error(`archive: ${file} failed verification (${what}) — nothing was deleted`);
203
+ };
204
+ verify(Buffer.byteLength(readBack) === bytes, `expected ${bytes} bytes`);
205
+ verify(createHash("sha256").update(readBack).digest("hex") === sha256, "sha256 mismatch");
206
+ const lines = readBack.split("\n").filter((line) => line.length > 0);
207
+ verify(lines.length === records.length, `expected ${records.length} rows, read ${lines.length}`);
208
+ for (const line of lines) {
209
+ let parsed;
210
+ try {
211
+ parsed = JSON.parse(line);
212
+ }
213
+ catch {
214
+ verify(false, "a row did not parse back");
215
+ return undefined;
216
+ }
217
+ verify(typeof parsed === "object" && parsed !== null, "a row read back as a non-object");
218
+ }
219
+ // Sealed. `rename` inside one directory is atomic on every filesystem
220
+ // this fleet runs on, so a collector never sees a partial segment.
221
+ renameSync(tmp, path);
222
+ const segment = { path, file, rows: records.length, bytes, sha256, at };
223
+ try {
224
+ appendFileSync(indexPath, `${JSON.stringify({ app, resource, ...segment, path: undefined, file })}\n`);
225
+ }
226
+ catch {
227
+ // The catalogue is a convenience; the segment is the archive. A failed
228
+ // index append must never make a sealed segment look unsealed and send
229
+ // the same rows through again.
230
+ }
231
+ return segment;
232
+ },
233
+ };
234
+ }
235
+ /**
236
+ * Re-verify every segment in an archive directory against the sha256 the index
237
+ * recorded — the answer to "is this archive still real?" without a restore.
238
+ *
239
+ * Exists because an archive nobody can check is a belief, and this whole
240
+ * category is a promise that the rows still exist somewhere.
241
+ */
242
+ export function verifyArchive(dir) {
243
+ const result = { segments: 0, rows: 0, bytes: 0, bad: [] };
244
+ let index;
245
+ try {
246
+ index = readFileSync(join(dir, "index.jsonl"), "utf8");
247
+ }
248
+ catch {
249
+ return result;
250
+ }
251
+ for (const line of index.split("\n")) {
252
+ if (!line.trim())
253
+ continue;
254
+ let entry;
255
+ try {
256
+ entry = JSON.parse(line);
257
+ }
258
+ catch {
259
+ result.bad.push(`unparseable index line: ${line.slice(0, 80)}`);
260
+ continue;
261
+ }
262
+ if (!entry.file || !entry.sha256)
263
+ continue;
264
+ result.segments++;
265
+ const path = join(dir, entry.file);
266
+ let body;
267
+ try {
268
+ body = readFileSync(path, "utf8");
269
+ }
270
+ catch {
271
+ // On a STAGED archive a collected segment is the healthy outcome, so
272
+ // absence is reported as a fact rather than as corruption.
273
+ result.bad.push(`${entry.file}: absent (collected, or lost)`);
274
+ continue;
275
+ }
276
+ if (createHash("sha256").update(body).digest("hex") !== entry.sha256) {
277
+ result.bad.push(`${entry.file}: sha256 mismatch`);
278
+ continue;
279
+ }
280
+ result.rows += entry.rows ?? 0;
281
+ result.bytes += entry.bytes ?? 0;
282
+ }
283
+ return result;
284
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The read model behind the station's Retention view: every app's published
3
+ * resources joined to the owner's overrides, with the effective policy resolved
4
+ * by the SAME function the sweeps use.
5
+ *
6
+ * That last point is the whole reason this is a shared function rather than a
7
+ * few lines in the station's route handler. A UI that computes "effective days"
8
+ * its own way eventually disagrees with the sweeper, and the disagreement shows
9
+ * up as data that vanished earlier than the screen said it would — which is the
10
+ * one failure mode that would make the owner stop trusting the control.
11
+ */
12
+ import type { EffectiveRetentionPolicy, RetentionOverride } from "./policy";
13
+ import type { PublishedResource } from "./store";
14
+ export interface RetentionFleetRow extends PublishedResource {
15
+ key: string;
16
+ app: string;
17
+ policy: EffectiveRetentionPolicy;
18
+ override?: RetentionOverride;
19
+ /** The category's description, denormalized so the UI needs no second lookup. */
20
+ category: {
21
+ label: string;
22
+ defaultDays: number;
23
+ summary: string;
24
+ };
25
+ /** Default days for this resource before any override — what "Reset" returns to. */
26
+ defaultDays: number;
27
+ /** The publishing app's last heartbeat, so the UI can label a stale row. */
28
+ publishedAt: number;
29
+ instance?: string;
30
+ sweepDisabled?: boolean;
31
+ }
32
+ export interface RetentionFleetView {
33
+ home: string;
34
+ updatedAt: number;
35
+ rows: RetentionFleetRow[];
36
+ totals: {
37
+ resources: number;
38
+ apps: number;
39
+ items: number;
40
+ bytes: number;
41
+ };
42
+ /** Overrides with no matching declaration — an app renamed or retired a resource. */
43
+ orphanedOverrides: Array<{
44
+ key: string;
45
+ override: RetentionOverride;
46
+ }>;
47
+ }
48
+ export declare function buildRetentionFleetView(options?: {
49
+ home?: string;
50
+ }): RetentionFleetView;
@@ -0,0 +1,66 @@
1
+ import { RETENTION_CATEGORIES, resolveRetentionPolicy } from "./policy";
2
+ import { readRetentionOverrides, readRetentionRegistry, resolveRetentionHome } from "./store";
3
+ export function buildRetentionFleetView(options = {}) {
4
+ const home = options.home ?? resolveRetentionHome();
5
+ const publications = readRetentionRegistry(home);
6
+ const overridesFile = readRetentionOverrides(home);
7
+ const rows = [];
8
+ const claimed = new Set();
9
+ for (const publication of publications) {
10
+ for (const resource of publication.resources) {
11
+ const declaration = resource.declaration;
12
+ if (!declaration || typeof declaration.app !== "string")
13
+ continue;
14
+ const key = `${declaration.app}:${declaration.id}`;
15
+ const override = overridesFile.overrides[key];
16
+ let policy;
17
+ try {
18
+ policy = resolveRetentionPolicy(declaration, override);
19
+ }
20
+ catch {
21
+ // A publication written by an older/newer app version than this reader
22
+ // understands is skipped rather than allowed to break the whole view.
23
+ continue;
24
+ }
25
+ claimed.add(key);
26
+ const category = RETENTION_CATEGORIES[declaration.category];
27
+ rows.push({
28
+ ...resource,
29
+ key,
30
+ app: declaration.app,
31
+ policy,
32
+ ...(override ? { override } : {}),
33
+ category,
34
+ defaultDays: declaration.defaultDays ?? category.defaultDays,
35
+ publishedAt: publication.publishedAt,
36
+ ...(publication.instance ? { instance: publication.instance } : {}),
37
+ ...(publication.sweepDisabled ? { sweepDisabled: true } : {}),
38
+ });
39
+ }
40
+ }
41
+ // Biggest first: the view exists to answer "what is filling my disk".
42
+ rows.sort((a, b) => {
43
+ const bytes = (b.measurement?.bytes ?? 0) - (a.measurement?.bytes ?? 0);
44
+ if (bytes !== 0)
45
+ return bytes;
46
+ const items = (b.measurement?.items ?? 0) - (a.measurement?.items ?? 0);
47
+ if (items !== 0)
48
+ return items;
49
+ return a.key.localeCompare(b.key);
50
+ });
51
+ const orphanedOverrides = Object.entries(overridesFile.overrides)
52
+ .filter(([key]) => !claimed.has(key))
53
+ .map(([key, override]) => ({ key, override }));
54
+ return {
55
+ home,
56
+ updatedAt: overridesFile.updatedAt,
57
+ rows,
58
+ totals: {
59
+ resources: rows.length,
60
+ apps: new Set(rows.map((r) => r.app)).size,
61
+ items: rows.reduce((sum, r) => sum + (r.measurement?.items ?? 0), 0),
62
+ bytes: rows.reduce((sum, r) => sum + (r.measurement?.bytes ?? 0), 0),
63
+ },
64
+ orphanedOverrides,
65
+ };
66
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `cursedbelt-server/retention` — THE fleet's one retention standard.
3
+ *
4
+ * ── Why this lives in cursedbelt ────────────────────────────────────────────
5
+ * Retention used to be spoken in three unrelated vocabularies: this
6
+ * category-based opt-in design (born in `cursed-satellites/satellite-kit`), the
7
+ * DB-backed audit|meter WINDOWS in `cursedbelt-cc/drivers`' retentionPolicy
8
+ * (billing evidence — a genuinely different concern that keeps the name
9
+ * {@link RetentionWindows}), and `deploy-engine/backupRetention`'s artifact
10
+ * pruning. DEDUP-8(c) made THIS one canonical and moved it here, so a single
11
+ * copy serves cursed-core, cursed-satellites and every generated app, and
12
+ * `RetentionPolicy` names exactly one thing across the fleet. `satellite-kit`'s
13
+ * `./retention` subpath is now a thin re-export of this module.
14
+ *
15
+ * An app declares what it owns and starts a sweeper; the station reads and
16
+ * edits the policies. Nothing else in the fleet should contain a timer that
17
+ * deletes old rows.
18
+ *
19
+ * const sweeper = createRetentionSweeper({
20
+ * app: "orch",
21
+ * targets: [
22
+ * createSqliteTableTarget({ db, declaration: {...}, table: "events", timeColumn: "at" }),
23
+ * createFileDirTarget({ declaration: {...}, dir: attemptLogs.dir, match: /\.jsonl$/ }),
24
+ * ],
25
+ * });
26
+ * sweeper.start();
27
+ *
28
+ * See `policy.ts` for the rule that decides whether a resource may be governed
29
+ * at all — retention is opt-in by category, and no category can describe data
30
+ * the owner would miss (which is exactly why billing evidence is NOT a category
31
+ * and lives in `retentionPolicy.ts` instead).
32
+ */
33
+ export { archiveStamp, createJsonlArchive, DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES, resolveArchiveHome, verifyArchive, type ArchiveHome, type ArchiveSegment, type ArchiveSink, type JsonlArchiveOptions, } from "./archive";
34
+ export { ARCHIVING_RETENTION_CATEGORIES, assertRetentionDeclaration, categoryArchivesBeforeDelete, cutoffFor, DAY_MS, FLEET_DEFAULT_RETENTION_DAYS, RETENTION_CATEGORIES, RETENTION_CATEGORY_KEYS, RETENTION_MAX_DAYS, RETENTION_MIN_DAYS, resolveRetentionPolicy, retentionKey, type EffectiveRetentionPolicy, type RetentionPolicy, type RetentionCategory, type RetentionDeclaration, type RetentionFieldSource, type RetentionOverride, type RetentionUnit, } from "./policy";
35
+ export { publishRetention, readRetentionOverrides, readRetentionRegistry, resolveRetentionHome, setRetentionHomeForTests, writeRetentionOverride, type PublishedResource, type RetentionMeasurement, type RetentionOverridesFile, type RetentionPublication, type RetentionSweepReport, } from "./store";
36
+ export { createArchivingSqliteTableTarget, createFileDirTarget, createSqliteTableTarget, type ArchivingSqliteTableTargetOptions, type FileDirTargetOptions, type RetentionPreview, type RetentionTarget, type SqliteTableTargetOptions, } from "./target";
37
+ export { createRetentionSweeper, DEFAULT_SWEEP_INTERVAL_MS, OVERRIDES_PULSE_MS, type CreateRetentionSweeperOptions, type ResourceOutcome, type RetentionRunReport, type RetentionSweeper, } from "./sweeper";
38
+ export { buildRetentionFleetView, type RetentionFleetRow, type RetentionFleetView } from "./fleet";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `cursedbelt-server/retention` — THE fleet's one retention standard.
3
+ *
4
+ * ── Why this lives in cursedbelt ────────────────────────────────────────────
5
+ * Retention used to be spoken in three unrelated vocabularies: this
6
+ * category-based opt-in design (born in `cursed-satellites/satellite-kit`), the
7
+ * DB-backed audit|meter WINDOWS in `cursedbelt-cc/drivers`' retentionPolicy
8
+ * (billing evidence — a genuinely different concern that keeps the name
9
+ * {@link RetentionWindows}), and `deploy-engine/backupRetention`'s artifact
10
+ * pruning. DEDUP-8(c) made THIS one canonical and moved it here, so a single
11
+ * copy serves cursed-core, cursed-satellites and every generated app, and
12
+ * `RetentionPolicy` names exactly one thing across the fleet. `satellite-kit`'s
13
+ * `./retention` subpath is now a thin re-export of this module.
14
+ *
15
+ * An app declares what it owns and starts a sweeper; the station reads and
16
+ * edits the policies. Nothing else in the fleet should contain a timer that
17
+ * deletes old rows.
18
+ *
19
+ * const sweeper = createRetentionSweeper({
20
+ * app: "orch",
21
+ * targets: [
22
+ * createSqliteTableTarget({ db, declaration: {...}, table: "events", timeColumn: "at" }),
23
+ * createFileDirTarget({ declaration: {...}, dir: attemptLogs.dir, match: /\.jsonl$/ }),
24
+ * ],
25
+ * });
26
+ * sweeper.start();
27
+ *
28
+ * See `policy.ts` for the rule that decides whether a resource may be governed
29
+ * at all — retention is opt-in by category, and no category can describe data
30
+ * the owner would miss (which is exactly why billing evidence is NOT a category
31
+ * and lives in `retentionPolicy.ts` instead).
32
+ */
33
+ export { archiveStamp, createJsonlArchive, DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES, resolveArchiveHome, verifyArchive, } from "./archive";
34
+ export { ARCHIVING_RETENTION_CATEGORIES, assertRetentionDeclaration, categoryArchivesBeforeDelete, cutoffFor, DAY_MS, FLEET_DEFAULT_RETENTION_DAYS, RETENTION_CATEGORIES, RETENTION_CATEGORY_KEYS, RETENTION_MAX_DAYS, RETENTION_MIN_DAYS, resolveRetentionPolicy, retentionKey, } from "./policy";
35
+ export { publishRetention, readRetentionOverrides, readRetentionRegistry, resolveRetentionHome, setRetentionHomeForTests, writeRetentionOverride, } from "./store";
36
+ export { createArchivingSqliteTableTarget, createFileDirTarget, createSqliteTableTarget, } from "./target";
37
+ export { createRetentionSweeper, DEFAULT_SWEEP_INTERVAL_MS, OVERRIDES_PULSE_MS, } from "./sweeper";
38
+ export { buildRetentionFleetView } from "./fleet";