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,106 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { derivePasswordSetKey, generateUserSalt, openMeta } from '../../private-media/crypto';
8
+ import type { PrivateIndex } from '../../private-media/index-store';
9
+ import { createDiskStore } from '../diskStore';
10
+ import type { MediaCtx } from '../types';
11
+ import { createSqliteFileCatalogue } from './catalogue';
12
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
13
+ import type { PrivateFileCatalogue } from './types';
14
+
15
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
16
+ const stream = (s: string) =>
17
+ new Response(new TextEncoder().encode(s)).body as ReadableStream<Uint8Array>;
18
+ const OWNER = CTX.userId; // private owner axis = userId (matches mountPrivateRoutes)
19
+
20
+ // Seal/reveal/deniability is ffmpeg-independent: the descriptor is sealed regardless of transcode
21
+ // success, so these run everywhere. (Content-key unwrap needs a real transcode → ffmpeg-gated e2e.)
22
+ describe('FileCatalogue private media — ingest + PrivateIndex view + deniability', () => {
23
+ let db: Database;
24
+ let root: string;
25
+ let cat: PrivateFileCatalogue;
26
+ let index: PrivateIndex;
27
+ const salt = generateUserSalt();
28
+ const keyA = derivePasswordSetKey('alpha-pass', salt);
29
+ const keyB = derivePasswordSetKey('bravo-pass', salt);
30
+
31
+ beforeAll(async () => {
32
+ db = new Database(':memory:');
33
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
34
+ root = await mkdtemp(path.join(tmpdir(), 'fc-priv-'));
35
+ let clock = 1;
36
+ // owner = userId so private rows + mountPrivateRoutes agree on the scope.
37
+ cat = createSqliteFileCatalogue({
38
+ db,
39
+ store: createDiskStore(root),
40
+ workDir: path.join(root, 'w'),
41
+ owner: (c) => c.userId,
42
+ now: () => ++clock,
43
+ });
44
+ index = cat.asPrivateIndex();
45
+ });
46
+ afterAll(async () => {
47
+ db.close();
48
+ await rm(root, { recursive: true, force: true });
49
+ });
50
+
51
+ test('ingests private rows under two disjoint passwords', async () => {
52
+ await cat.ingestPrivate(
53
+ CTX,
54
+ { name: 'secret-a.mp4', mime: 'video/mp4', body: stream('a-bytes'), tags: ['setA'] },
55
+ { setKey: keyA, keyMode: 'deniable' },
56
+ );
57
+ await cat.ingestPrivate(
58
+ CTX,
59
+ { name: 'secret-b.mp4', mime: 'video/mp4', body: stream('b-bytes'), tags: ['setB'] },
60
+ { setKey: keyB, keyMode: 'deniable' },
61
+ );
62
+ expect((await index.list(OWNER)).length).toBe(2);
63
+ });
64
+
65
+ test('a set key reveals only its own items (deniability) via the index envelopes', () => {
66
+ const reveal = async (key: Buffer) => {
67
+ const names: string[] = [];
68
+ for (const item of await index.list(OWNER)) {
69
+ const meta = openMeta<{ name: string }>(key, item.sealedMeta);
70
+ if (meta) names.push(meta.name);
71
+ }
72
+ return names.sort();
73
+ };
74
+ return Promise.all([
75
+ reveal(keyA),
76
+ reveal(keyB),
77
+ reveal(derivePasswordSetKey('nope', salt)),
78
+ ]).then(([a, b, wrong]) => {
79
+ expect(a).toEqual(['secret-a.mp4']);
80
+ expect(b).toEqual(['secret-b.mp4']);
81
+ expect(wrong).toEqual([]); // an unused password shows nothing — no hint the sets exist
82
+ });
83
+ });
84
+
85
+ test('the clear index exposes no set identifier or key mode difference', async () => {
86
+ // Both rows are keyMode 'deniable' here; the public projection (toPublic) that the /index route
87
+ // returns strips keyMode entirely — asserted by mountPrivateRoutes' own tests. Here we just
88
+ // confirm the raw items carry the wrapped key + sealed meta (opaque without a key).
89
+ for (const item of await index.list(OWNER)) {
90
+ expect(item.sealedMeta.length).toBeGreaterThan(0);
91
+ expect(openMeta(Buffer.alloc(32), item.sealedMeta)).toBeNull(); // a random key opens nothing
92
+ }
93
+ });
94
+
95
+ test('index.remove drops the row', async () => {
96
+ const items = await index.list(OWNER);
97
+ await index.remove(OWNER, items[0].id);
98
+ expect((await index.list(OWNER)).length).toBe(1);
99
+ expect(await index.get(OWNER, items[0].id)).toBeNull();
100
+ });
101
+
102
+ test('private rows never appear in the public feed', async () => {
103
+ const publicCat = cat as unknown as { list: (c: MediaCtx) => Promise<{ items: unknown[] }> };
104
+ expect((await publicCat.list(CTX)).items).toEqual([]);
105
+ });
106
+ });
@@ -0,0 +1,131 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { createDiskStore } from '../diskStore';
8
+ import { createSqliteFileCatalogue } from './catalogue';
9
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
10
+ import type { FileCatalogue } from './types';
11
+
12
+ // recoverProcessing (ported from ca's recoverOnBoot): a restart mid-transcode leaves video rows
13
+ // stuck in processing/pending; public rows get re-kicked (source persists), private rows are failed.
14
+ describe('FileCatalogue.recoverProcessing', () => {
15
+ let db: Database;
16
+ let root: string;
17
+ let cat: FileCatalogue;
18
+
19
+ beforeAll(async () => {
20
+ db = new Database(':memory:');
21
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
22
+ root = await mkdtemp(path.join(tmpdir(), 'fc-recover-'));
23
+ cat = createSqliteFileCatalogue({
24
+ db,
25
+ store: createDiskStore(root),
26
+ workDir: path.join(root, 'w'),
27
+ });
28
+ // Simulate two rows a crash left mid-transcode: one public, one private.
29
+ const insert = db.query(
30
+ `INSERT INTO files (id, owner, name, mime, size, file_type, is_private, processing_status, processing_progress, storage_backend, created_at, updated_at)
31
+ VALUES ($id, 'u1', $name, 'video/mp4', 10, 'video', $priv, 'processing', 40, 'disk', 1, 1)`,
32
+ );
33
+ insert.run({ $id: 'pub1', $name: 'a.mp4', $priv: 0 });
34
+ insert.run({ $id: 'prv1', $name: '', $priv: 1 });
35
+ });
36
+ afterAll(async () => {
37
+ await Bun.sleep(150); // let the re-kicked public job settle before closing the db
38
+ db.close();
39
+ await rm(root, { recursive: true, force: true });
40
+ });
41
+
42
+ test('re-queues stuck public videos and fails stuck private ones', () => {
43
+ const res = cat.recoverProcessing();
44
+ expect(res.requeued).toBe(1); // pub1 re-kicked
45
+ expect(res.failed).toBe(1); // prv1 can't be re-kicked at boot → failed
46
+
47
+ const status = (id: string) =>
48
+ db
49
+ .query<{ processing_status: string }, { $id: string }>(
50
+ 'SELECT processing_status FROM files WHERE id = $id',
51
+ )
52
+ .get({ $id: id })?.processing_status;
53
+ expect(status('prv1')).toBe('failed');
54
+ // pub1 was re-kicked (its final status is settled by the async pipeline; here we just assert it
55
+ // left the recover call counted as requeued, not failed).
56
+ });
57
+
58
+ test('re-kick bumps processing_attempts', () => {
59
+ const attempts = db
60
+ .query<{ processing_attempts: number }, { $id: string }>(
61
+ 'SELECT processing_attempts FROM files WHERE id = $id',
62
+ )
63
+ .get({ $id: 'pub1' })?.processing_attempts;
64
+ expect(attempts).toBe(1);
65
+ });
66
+ });
67
+
68
+ // The boot-loop guard: when the transcode itself keeps killing the process (ffmpeg OOM →
69
+ // restart → re-kick), the attempt cap must land the row on `failed` instead of re-kicking forever.
70
+ describe('FileCatalogue.recoverProcessing attempt cap', () => {
71
+ test('marks a public video failed once processing_attempts reaches the cap', async () => {
72
+ const db = new Database(':memory:');
73
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
74
+ const root = await mkdtemp(path.join(tmpdir(), 'fc-recover-cap-'));
75
+ const cat = createSqliteFileCatalogue({
76
+ db,
77
+ store: createDiskStore(root),
78
+ workDir: path.join(root, 'w'),
79
+ maxProcessingAttempts: 2,
80
+ });
81
+ db.query(
82
+ `INSERT INTO files (id, owner, name, mime, size, file_type, is_private, processing_status, processing_progress, storage_backend, processing_attempts, created_at, updated_at)
83
+ VALUES ('loop1', 'u1', 'l.mp4', 'video/mp4', 10, 'video', 0, 'processing', 40, 'disk', 2, 1, 1)`,
84
+ ).run();
85
+
86
+ const res = cat.recoverProcessing();
87
+ expect(res.requeued).toBe(0);
88
+ expect(res.failed).toBe(1);
89
+ const row = db
90
+ .query<{ processing_status: string }, []>(
91
+ `SELECT processing_status FROM files WHERE id = 'loop1'`,
92
+ )
93
+ .get();
94
+ expect(row?.processing_status).toBe('failed');
95
+ db.close();
96
+ await rm(root, { recursive: true, force: true });
97
+ });
98
+ });
99
+
100
+ // transcodeVideos: false — the memory-constrained-host mode: no ffmpeg, videos go straight to
101
+ // ready and play raw. Recovery under this mode must not re-enter the transcode pipeline either.
102
+ describe('FileCatalogue transcodeVideos: false', () => {
103
+ test('video rows go ready with no derivatives and no attempt bump', async () => {
104
+ const db = new Database(':memory:');
105
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
106
+ const root = await mkdtemp(path.join(tmpdir(), 'fc-notranscode-'));
107
+ const cat = createSqliteFileCatalogue({
108
+ db,
109
+ store: createDiskStore(root),
110
+ workDir: path.join(root, 'w'),
111
+ transcodeVideos: false,
112
+ });
113
+ db.query(
114
+ `INSERT INTO files (id, owner, name, mime, size, file_type, is_private, processing_status, processing_progress, storage_backend, created_at, updated_at)
115
+ VALUES ('raw1', 'u1', 'r.mp4', 'video/mp4', 10, 'video', 0, 'processing', 40, 'disk', 1, 1)`,
116
+ ).run();
117
+
118
+ const res = cat.recoverProcessing();
119
+ expect(res.requeued).toBe(1); // counted as requeued, but the "kick" resolves synchronously
120
+ const row = db
121
+ .query<{ processing_status: string; processing_attempts: number; has_hls: number }, []>(
122
+ `SELECT processing_status, processing_attempts, has_hls FROM files WHERE id = 'raw1'`,
123
+ )
124
+ .get();
125
+ expect(row?.processing_status).toBe('ready');
126
+ expect(row?.processing_attempts).toBe(0);
127
+ expect(row?.has_hls).toBe(0);
128
+ db.close();
129
+ await rm(root, { recursive: true, force: true });
130
+ });
131
+ });
@@ -0,0 +1,154 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { createDiskStore } from '../diskStore';
8
+ import type { MediaCtx } from '../types';
9
+ import { createSqliteFileCatalogue } from './catalogue';
10
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
11
+ import type { FileCatalogue, FileSort } from './types';
12
+
13
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 'sorter' };
14
+
15
+ describe('FileCatalogue — sort orders + pagination', () => {
16
+ let db: Database;
17
+ let root: string;
18
+ let cat: FileCatalogue;
19
+ let clock = 5000;
20
+
21
+ // Names/sizes chosen so each sort produces a DIFFERENT order, with a duplicate size to
22
+ // exercise the id tie-break. Bodies of length n ⇒ size n.
23
+ const FILES: Array<{ name: string; size: number }> = [
24
+ { name: 'delta.png', size: 10 },
25
+ { name: 'alpha.png', size: 30 },
26
+ { name: 'echo.png', size: 20 },
27
+ { name: 'bravo.png', size: 20 }, // duplicate size — tie-break path
28
+ { name: 'charlie.png', size: 50 },
29
+ ];
30
+
31
+ beforeAll(async () => {
32
+ db = new Database(':memory:');
33
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
34
+ root = await mkdtemp(path.join(tmpdir(), 'fc-sort-'));
35
+ cat = createSqliteFileCatalogue({
36
+ db,
37
+ store: createDiskStore(root),
38
+ workDir: path.join(root, 'work'),
39
+ now: () => ++clock,
40
+ });
41
+ for (const f of FILES) {
42
+ const body = new Response('x'.repeat(f.size)).body as ReadableStream<Uint8Array>;
43
+ await cat.ingest(CTX, { name: f.name, mime: 'image/png', body });
44
+ }
45
+ });
46
+ afterAll(async () => {
47
+ db.close();
48
+ await rm(root, { recursive: true, force: true });
49
+ });
50
+
51
+ test("default / 'recent' sorts newest-first (unchanged behavior)", async () => {
52
+ const expected = [...FILES].reverse().map((f) => f.name); // insertion order reversed
53
+ expect((await cat.list(CTX)).items.map((f) => f.name)).toEqual(expected);
54
+ expect((await cat.list(CTX, { sort: 'recent' })).items.map((f) => f.name)).toEqual(expected);
55
+ });
56
+
57
+ test("'oldest' sorts oldest-first (insertion order)", async () => {
58
+ const page = await cat.list(CTX, { sort: 'oldest' });
59
+ expect(page.items.map((f) => f.name)).toEqual(FILES.map((f) => f.name));
60
+ });
61
+
62
+ test("'name' sorts A→Z", async () => {
63
+ const page = await cat.list(CTX, { sort: 'name' });
64
+ expect(page.items.map((f) => f.name)).toEqual([
65
+ 'alpha.png',
66
+ 'bravo.png',
67
+ 'charlie.png',
68
+ 'delta.png',
69
+ 'echo.png',
70
+ ]);
71
+ });
72
+
73
+ test("'size' sorts largest-first", async () => {
74
+ const page = await cat.list(CTX, { sort: 'size' });
75
+ expect(page.items.map((f) => f.size)).toEqual([50, 30, 20, 20, 10]);
76
+ expect(page.items[0].name).toBe('charlie.png');
77
+ });
78
+
79
+ // Walk the whole pool page-by-page under a sort; assert no gaps, no repeats, and that the
80
+ // concatenated pages equal the single-page order.
81
+ const paginate = async (sort: FileSort, limit: number): Promise<string[]> => {
82
+ const all: string[] = [];
83
+ const seen = new Set<string>();
84
+ let cursor: string | undefined;
85
+ let guard = 0;
86
+ do {
87
+ const page = await cat.list(CTX, { sort, limit, cursor });
88
+ for (const f of page.items) {
89
+ expect(seen.has(f.id)).toBe(false); // no repeats across pages
90
+ seen.add(f.id);
91
+ all.push(f.name);
92
+ }
93
+ cursor = page.nextCursor;
94
+ guard++;
95
+ } while (cursor && guard < 10);
96
+ return all;
97
+ };
98
+
99
+ test.each([
100
+ 'recent',
101
+ 'oldest',
102
+ 'name',
103
+ 'size',
104
+ ] as const)("cursor pagination under sort '%s' walks the pool without gaps or repeats", async (sort) => {
105
+ const whole = (await cat.list(CTX, { sort })).items.map((f) => f.name);
106
+ const paged = await paginate(sort, 2);
107
+ expect(paged).toEqual(whole); // page-by-page equals the one-shot order
108
+ expect(paged.length).toBe(FILES.length);
109
+ });
110
+
111
+ test('size pagination splits the duplicate-size pair across pages correctly (tie-break)', async () => {
112
+ // limit 3 puts the boundary INSIDE the size=20 duplicate pair.
113
+ const p1 = await cat.list(CTX, { sort: 'size', limit: 3 });
114
+ expect(p1.items.map((f) => f.size)).toEqual([50, 30, 20]);
115
+ expect(p1.nextCursor).toBeDefined();
116
+ const p2 = await cat.list(CTX, { sort: 'size', limit: 3, cursor: p1.nextCursor });
117
+ expect(p2.items.map((f) => f.size)).toEqual([20, 10]);
118
+ // The two size-20 files are different files.
119
+ expect(p1.items[2].id).not.toBe(p2.items[0].id);
120
+ });
121
+
122
+ test('a name containing the cursor separator paginates safely', async () => {
123
+ const CTX2: MediaCtx = { userId: 'u2', tenantId: 'sorter2' };
124
+ const mk = (name: string) =>
125
+ cat.ingest(CTX2, {
126
+ name,
127
+ mime: 'image/png',
128
+ body: new Response('x').body as ReadableStream<Uint8Array>,
129
+ });
130
+ await mk('a|weird|name.png');
131
+ await mk('b|also|weird.png');
132
+ await mk('c.png');
133
+ const p1 = await cat.list(CTX2, { sort: 'name', limit: 1 });
134
+ expect(p1.items[0].name).toBe('a|weird|name.png');
135
+ const p2 = await cat.list(CTX2, { sort: 'name', limit: 1, cursor: p1.nextCursor });
136
+ expect(p2.items[0].name).toBe('b|also|weird.png');
137
+ const p3 = await cat.list(CTX2, { sort: 'name', limit: 1, cursor: p2.nextCursor });
138
+ expect(p3.items[0].name).toBe('c.png');
139
+ });
140
+
141
+ test('sort composes with filters (collection + sort)', async () => {
142
+ const coll = await cat.createCollection(CTX, { name: 'SortColl' });
143
+ const all = await cat.list(CTX);
144
+ await cat.addToCollection(
145
+ CTX,
146
+ coll.id,
147
+ all.items.slice(0, 3).map((f) => f.id),
148
+ );
149
+ const page = await cat.list(CTX, { collectionId: coll.id, sort: 'name' });
150
+ const names = page.items.map((f) => f.name);
151
+ expect(names).toEqual([...names].sort());
152
+ expect(names.length).toBe(3);
153
+ });
154
+ });
@@ -0,0 +1,133 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { createDiskStore } from '../diskStore';
8
+ import type { MediaCtx } from '../types';
9
+ import { fsPosterKey } from '../types';
10
+ import { createSqliteFileCatalogue, fileBinaryKey } from './catalogue';
11
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
12
+ import type { FileCatalogue } from './types';
13
+
14
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
15
+ const bytes = (s: string) => new TextEncoder().encode(s);
16
+ const stream = (s: string) => new Response(bytes(s)).body as ReadableStream<Uint8Array>;
17
+
18
+ describe('FileCatalogue (SQLite)', () => {
19
+ let db: Database;
20
+ let root: string;
21
+ let cat: FileCatalogue;
22
+ let store: ReturnType<typeof createDiskStore>;
23
+ let clock = 1000;
24
+
25
+ beforeAll(async () => {
26
+ db = new Database(':memory:');
27
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
28
+ root = await mkdtemp(path.join(tmpdir(), 'fc-store-'));
29
+ store = createDiskStore(root);
30
+ cat = createSqliteFileCatalogue({
31
+ db,
32
+ store,
33
+ workDir: path.join(root, 'work'),
34
+ now: () => ++clock, // deterministic, increasing createdAt
35
+ });
36
+ });
37
+ afterAll(async () => {
38
+ db.close();
39
+ await rm(root, { recursive: true, force: true });
40
+ });
41
+
42
+ const ingest = (name: string, mime: string, tags: string[] = []) =>
43
+ cat.ingest(CTX, { name, mime, body: stream(`bytes-of-${name}`), tags });
44
+
45
+ test('ingests non-video files as ready, classifies + tags them', async () => {
46
+ const a = await ingest('a.png', 'image/png', ['photo']);
47
+ expect(a.fileType).toBe('image');
48
+ expect(a.processingStatus).toBe('ready');
49
+ expect(a.tags).toEqual(['photo']);
50
+ expect(a.size).toBe(bytes('bytes-of-a.png').length);
51
+
52
+ await ingest('b.pdf', 'application/pdf', ['work']);
53
+ await ingest('c.jpg', 'image/jpeg', ['photo', 'work']);
54
+ });
55
+
56
+ test('lists newest-first with smart-view + tag + query filters', async () => {
57
+ const all = await cat.list(CTX);
58
+ expect(all.items.map((f) => f.name)).toEqual(['c.jpg', 'b.pdf', 'a.png']); // newest first
59
+
60
+ expect((await cat.list(CTX, { type: 'image' })).items.map((f) => f.name)).toEqual([
61
+ 'c.jpg',
62
+ 'a.png',
63
+ ]);
64
+ expect((await cat.list(CTX, { type: 'doc' })).items.map((f) => f.name)).toEqual(['b.pdf']);
65
+ expect((await cat.list(CTX, { tag: 'photo' })).items.map((f) => f.name)).toEqual([
66
+ 'c.jpg',
67
+ 'a.png',
68
+ ]);
69
+ expect((await cat.list(CTX, { query: 'b.' })).items.map((f) => f.name)).toEqual(['b.pdf']);
70
+ });
71
+
72
+ test('tagFacets counts tags across the pool', async () => {
73
+ const facets = await cat.tagFacets(CTX);
74
+ expect(facets).toContainEqual({ tag: 'photo', count: 2 });
75
+ expect(facets).toContainEqual({ tag: 'work', count: 2 });
76
+ });
77
+
78
+ test('cursor pagination walks the whole pool without gaps or repeats', async () => {
79
+ const seen = new Set<string>();
80
+ let cursor: string | undefined;
81
+ let pages = 0;
82
+ do {
83
+ const page = await cat.list(CTX, { limit: 2, cursor });
84
+ for (const f of page.items) {
85
+ expect(seen.has(f.id)).toBe(false); // no repeats
86
+ seen.add(f.id);
87
+ }
88
+ cursor = page.nextCursor;
89
+ pages++;
90
+ } while (cursor && pages < 10);
91
+ expect(seen.size).toBe(3); // all files, paged 2 at a time
92
+ expect(pages).toBe(2);
93
+ });
94
+
95
+ test('get + update (rename + retag) + remove', async () => {
96
+ const { items } = await cat.list(CTX, { type: 'doc' });
97
+ const id = items[0].id;
98
+ expect((await cat.get(CTX, id))?.name).toBe('b.pdf');
99
+
100
+ const updated = await cat.update(CTX, id, { name: 'renamed.pdf', tags: ['archive'] });
101
+ expect(updated?.name).toBe('renamed.pdf');
102
+ expect(updated?.tags).toEqual(['archive']);
103
+ expect((await cat.tagFacets(CTX)).find((f) => f.tag === 'work')?.count).toBe(1); // 'work' dropped from b
104
+
105
+ await cat.remove(CTX, id);
106
+ expect(await cat.get(CTX, id)).toBeNull();
107
+ expect((await cat.list(CTX)).items.length).toBe(2);
108
+ });
109
+
110
+ test('remove deletes an image’s binary AND its poster thumbnail from the store', async () => {
111
+ const img = await ingest('orphan.png', 'image/png');
112
+ const owner = CTX.tenantId; // catalogue's default ownerOf
113
+ // Simulate the thumbnail the async kickImageThumbnail pipeline writes.
114
+ await store.put(fsPosterKey(owner, img.id), bytes('thumb'), 'image/jpeg');
115
+ expect(await store.stat(fileBinaryKey(owner, img.id))).not.toBeNull();
116
+
117
+ await cat.remove(CTX, img.id);
118
+ expect(await cat.get(CTX, img.id)).toBeNull();
119
+ expect(await store.stat(fileBinaryKey(owner, img.id))).toBeNull();
120
+ expect(await store.stat(fsPosterKey(owner, img.id))).toBeNull(); // was orphaned before the fix
121
+ });
122
+
123
+ test('one tenant never sees another tenant’s files', async () => {
124
+ await cat.ingest(
125
+ { userId: 'u2', tenantId: 't2' },
126
+ { name: 'other.png', mime: 'image/png', body: stream('x') },
127
+ );
128
+ expect((await cat.list(CTX)).items.every((f) => f.name !== 'other.png')).toBe(true);
129
+ expect((await cat.list({ userId: 'u2', tenantId: 't2' })).items.map((f) => f.name)).toEqual([
130
+ 'other.png',
131
+ ]);
132
+ });
133
+ });
@@ -0,0 +1,125 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { createDiskStore } from '../diskStore';
8
+ import type { MediaCtx } from '../types';
9
+ import { createSqliteFileCatalogue } from './catalogue';
10
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
11
+ import type { FileCatalogue } from './types';
12
+
13
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
14
+ const bytes = (s: string) => new TextEncoder().encode(s);
15
+ const stream = (s: string) => new Response(bytes(s)).body as ReadableStream<Uint8Array>;
16
+
17
+ describe('FileCatalogue trash (soft-delete)', () => {
18
+ let db: Database;
19
+ let root: string;
20
+ let cat: FileCatalogue;
21
+
22
+ beforeAll(async () => {
23
+ db = new Database(':memory:');
24
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
25
+ root = await mkdtemp(path.join(tmpdir(), 'fc-trash-'));
26
+ cat = createSqliteFileCatalogue({ db, store: createDiskStore(root), workDir: path.join(root, 'work') });
27
+ });
28
+ afterAll(async () => {
29
+ db.close();
30
+ await rm(root, { recursive: true, force: true });
31
+ });
32
+
33
+ test('trash hides a file from list() but keeps its row + bytes', async () => {
34
+ const f = await cat.ingest(CTX, { name: 'a.png', mime: 'image/png', body: stream('a') });
35
+ await cat.trash(CTX, f.id);
36
+
37
+ expect((await cat.list(CTX)).items.some((r) => r.id === f.id)).toBe(false);
38
+ const row = await cat.get(CTX, f.id);
39
+ expect(row?.trashedAt).toBeGreaterThan(0);
40
+ });
41
+
42
+ test('trashedOnly lists exactly the trashed set', async () => {
43
+ const live = await cat.ingest(CTX, { name: 'live.png', mime: 'image/png', body: stream('l') });
44
+ const gone = await cat.ingest(CTX, { name: 'gone.png', mime: 'image/png', body: stream('g') });
45
+ await cat.trash(CTX, gone.id);
46
+
47
+ const trashPage = await cat.list(CTX, { trashedOnly: true });
48
+ expect(trashPage.items.map((r) => r.id)).toContain(gone.id);
49
+ expect(trashPage.items.map((r) => r.id)).not.toContain(live.id);
50
+ });
51
+
52
+ test('restore undoes trash — the file reappears in list()', async () => {
53
+ const f = await cat.ingest(CTX, { name: 'b.png', mime: 'image/png', body: stream('b') });
54
+ await cat.trash(CTX, f.id);
55
+ await cat.restore(CTX, f.id);
56
+
57
+ expect((await cat.list(CTX)).items.some((r) => r.id === f.id)).toBe(true);
58
+ expect((await cat.get(CTX, f.id))?.trashedAt).toBeUndefined();
59
+ });
60
+
61
+ test('remove() is still a real hard-delete, independent of trash', async () => {
62
+ const f = await cat.ingest(CTX, { name: 'c.png', mime: 'image/png', body: stream('c') });
63
+ await cat.trash(CTX, f.id);
64
+ await cat.remove(CTX, f.id);
65
+ expect(await cat.get(CTX, f.id)).toBeNull();
66
+ });
67
+ });
68
+
69
+ describe('FileCatalogue.importFromPath (local-disk import)', () => {
70
+ let db: Database;
71
+ let root: string;
72
+ let importRoot: string;
73
+ let cat: FileCatalogue;
74
+
75
+ beforeAll(async () => {
76
+ db = new Database(':memory:');
77
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
78
+ root = await mkdtemp(path.join(tmpdir(), 'fc-import-store-'));
79
+ importRoot = await mkdtemp(path.join(tmpdir(), 'fc-import-src-'));
80
+ cat = createSqliteFileCatalogue({ db, store: createDiskStore(root), workDir: path.join(root, 'work') });
81
+ });
82
+ afterAll(async () => {
83
+ db.close();
84
+ await rm(root, { recursive: true, force: true });
85
+ await rm(importRoot, { recursive: true, force: true });
86
+ });
87
+
88
+ test('imports a single file', async () => {
89
+ const filePath = path.join(importRoot, 'single.jpg');
90
+ await writeFile(filePath, bytes('single-file-bytes'));
91
+
92
+ const { imported, skipped } = await cat.importFromPath(CTX, filePath);
93
+ expect(skipped).toEqual([]);
94
+ expect(imported).toHaveLength(1);
95
+ expect(imported[0].name).toBe('single.jpg');
96
+ expect(imported[0].mime).toBe('image/jpeg');
97
+ expect(imported[0].fileType).toBe('image');
98
+ });
99
+
100
+ test('imports a mixed-media folder recursively, tagging by containing folder', async () => {
101
+ const folder = path.join(importRoot, 'vacation');
102
+ const sub = path.join(folder, 'day1');
103
+ await mkdir(sub, { recursive: true });
104
+ await writeFile(path.join(folder, 'cover.png'), bytes('cover'));
105
+ await writeFile(path.join(sub, 'clip.mp4'), bytes('clip'));
106
+
107
+ const { imported, skipped } = await cat.importFromPath(CTX, folder, { tags: ['import-batch'] });
108
+ expect(skipped).toEqual([]);
109
+ expect(imported).toHaveLength(2);
110
+
111
+ const cover = imported.find((f) => f.name === 'cover.png');
112
+ const clip = imported.find((f) => f.name === 'clip.mp4');
113
+ expect(cover?.fileType).toBe('image');
114
+ expect(cover?.tags).toEqual(['import-batch']); // top-level entries get no folder tag
115
+ expect(clip?.fileType).toBe('video');
116
+ expect(clip?.tags).toEqual(['import-batch', 'day1']);
117
+ });
118
+
119
+ test('reports a missing path as skipped, not a throw', async () => {
120
+ const { imported, skipped } = await cat.importFromPath(CTX, path.join(importRoot, 'nope.png'));
121
+ expect(imported).toEqual([]);
122
+ expect(skipped).toHaveLength(1);
123
+ expect(skipped[0].path).toContain('nope.png');
124
+ });
125
+ });