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,75 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { UnknownTableError } from './browse';
4
+ import { exportSqliteSnapshot } from './export';
5
+ import { importRowsIntoSqliteTable, restoreSqliteSnapshot } from './import';
6
+
7
+ function seed(withRows = true): Database {
8
+ const db = new Database(':memory:');
9
+ db.run('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
10
+ if (withRows) db.run("INSERT INTO notes (id, body) VALUES (1, 'a'), (2, 'b')");
11
+ return db;
12
+ }
13
+
14
+ const count = (db: Database) =>
15
+ db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM notes').get()?.n ?? 0;
16
+
17
+ describe('importRowsIntoSqliteTable', () => {
18
+ test('append adds rows and reports the count', () => {
19
+ const db = seed();
20
+ const res = importRowsIntoSqliteTable(db, 'notes', [{ id: 3, body: 'c' }], { mode: 'append' });
21
+ expect(res.inserted).toBe(1);
22
+ expect(count(db)).toBe(3);
23
+ });
24
+
25
+ test('replace clears the table first', () => {
26
+ const db = seed();
27
+ const res = importRowsIntoSqliteTable(db, 'notes', [{ id: 9, body: 'z' }], { mode: 'replace' });
28
+ expect(res.inserted).toBe(1);
29
+ expect(count(db)).toBe(1);
30
+ expect(db.query('SELECT body FROM notes').get()).toEqual({ body: 'z' });
31
+ });
32
+
33
+ test('drops keys that are not real columns', () => {
34
+ const db = seed(false);
35
+ importRowsIntoSqliteTable(db, 'notes', [{ id: 1, body: 'ok', bogus: 'x' }]);
36
+ expect(db.query('SELECT * FROM notes').get()).toEqual({ id: 1, body: 'ok' });
37
+ });
38
+
39
+ test('replace with zero rows just empties the table', () => {
40
+ const db = seed();
41
+ expect(importRowsIntoSqliteTable(db, 'notes', [], { mode: 'replace' }).inserted).toBe(0);
42
+ expect(count(db)).toBe(0);
43
+ });
44
+
45
+ test('throws UnknownTableError for a bad table', () => {
46
+ expect(() => importRowsIntoSqliteTable(seed(), 'nope', [])).toThrow(UnknownTableError);
47
+ });
48
+ });
49
+
50
+ describe('restoreSqliteSnapshot', () => {
51
+ test('replaces target rows with the snapshot for shared tables', () => {
52
+ const source = seed(); // notes: 1,2
53
+ const snap = exportSqliteSnapshot(source);
54
+
55
+ const target = seed(false);
56
+ target.run("INSERT INTO notes (id, body) VALUES (7, 'old')");
57
+ const res = restoreSqliteSnapshot(target, snap.bytes);
58
+
59
+ expect(res.restoredTables).toEqual(['notes']);
60
+ expect(res.totalRows).toBe(2);
61
+ expect(target.query('SELECT id FROM notes ORDER BY id').all()).toEqual([{ id: 1 }, { id: 2 }]);
62
+ });
63
+
64
+ test('leaves tables absent from the target untouched', () => {
65
+ const source = new Database(':memory:');
66
+ source.run('CREATE TABLE extra (id INTEGER PRIMARY KEY)');
67
+ source.run('INSERT INTO extra (id) VALUES (1)');
68
+ const snap = exportSqliteSnapshot(source);
69
+
70
+ const target = seed();
71
+ const res = restoreSqliteSnapshot(target, snap.bytes);
72
+ expect(res.restoredTables).toEqual([]); // no shared tables
73
+ expect(count(target)).toBe(2); // notes untouched
74
+ });
75
+ });
@@ -0,0 +1,141 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { withForeignKeysOff } from 'cwip/sqlite';
3
+ import { rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { isKnownTable, quoteIdent, UnknownTableError } from './browse';
7
+
8
+ /**
9
+ * Write-side SQLite primitives — the counterpart to the read-only `browse.ts` /
10
+ * `export.ts`. Both surfaces are deliberately scoped: row import into an EXISTING
11
+ * table and whole-DB snapshot restore. No arbitrary DDL, no schema mutation — the
12
+ * table must already exist, and only its real columns are written.
13
+ *
14
+ * Every operation runs inside a transaction so a mid-operation failure rolls the DB
15
+ * back atomically (stronger than a copy-aside snapshot). Foreign-key enforcement is
16
+ * suspended for the bulk replace/restore and always restored afterward.
17
+ */
18
+
19
+ export type ImportMode = 'append' | 'replace';
20
+
21
+ export interface ImportResult {
22
+ inserted: number;
23
+ }
24
+
25
+ /** Real column names of a table, in declared order. */
26
+ function tableColumns(db: Database, table: string): string[] {
27
+ return db
28
+ .query<{ name: string }, []>(`PRAGMA table_info(${quoteIdent(table)})`)
29
+ .all()
30
+ .map((r) => r.name);
31
+ }
32
+
33
+ /**
34
+ * Insert already-parsed rows into an existing table. Unknown keys are dropped (only
35
+ * columns that actually exist on the table are written); `replace` clears the table
36
+ * first. Returns the number of rows inserted. Throws {@link UnknownTableError} for a
37
+ * table not in `sqlite_master`.
38
+ */
39
+ export function importRowsIntoSqliteTable(
40
+ db: Database,
41
+ table: string,
42
+ rows: Record<string, unknown>[],
43
+ opts: { mode?: ImportMode } = {},
44
+ ): ImportResult {
45
+ if (!isKnownTable(db, table)) throw new UnknownTableError(table);
46
+ const mode = opts.mode ?? 'append';
47
+ const valid = new Set(tableColumns(db, table));
48
+ // Columns to write = table columns present in at least one row (preserve order).
49
+ const cols = tableColumns(db, table).filter((c) => rows.some((r) => c in r && valid.has(c)));
50
+
51
+ const runImport = db.transaction(() => {
52
+ if (mode === 'replace') db.run(`DELETE FROM ${quoteIdent(table)}`);
53
+ if (rows.length === 0 || cols.length === 0) return 0;
54
+ const placeholders = cols.map(() => '?').join(', ');
55
+ const colList = cols.map(quoteIdent).join(', ');
56
+ const stmt = db.prepare(
57
+ `INSERT INTO ${quoteIdent(table)} (${colList}) VALUES (${placeholders})`,
58
+ );
59
+ let inserted = 0;
60
+ for (const row of rows) {
61
+ stmt.run(...cols.map((c) => normalizeValue(row[c])));
62
+ inserted++;
63
+ }
64
+ return inserted;
65
+ });
66
+
67
+ const inserted = mode === 'replace' ? withForeignKeysOff(db, runImport) : runImport();
68
+ return { inserted };
69
+ }
70
+
71
+ /** bun:sqlite binds string/number/bigint/boolean/null/Uint8Array; coerce the rest. */
72
+ function normalizeValue(v: unknown): string | number | bigint | boolean | null | Uint8Array {
73
+ if (v === undefined || v === null) return null;
74
+ if (
75
+ typeof v === 'string' ||
76
+ typeof v === 'number' ||
77
+ typeof v === 'bigint' ||
78
+ typeof v === 'boolean' ||
79
+ v instanceof Uint8Array
80
+ ) {
81
+ return v;
82
+ }
83
+ // Objects/arrays → JSON text (the pragmatic choice for a foreign-format import).
84
+ return JSON.stringify(v);
85
+ }
86
+
87
+ export interface RestoreResult {
88
+ restoredTables: string[];
89
+ totalRows: number;
90
+ }
91
+
92
+ /**
93
+ * Restore a `.sqlite` snapshot into the live DB, table by table. For every table
94
+ * present in BOTH the snapshot and the target, the target's rows are replaced by the
95
+ * snapshot's (via `ATTACH` + `INSERT … SELECT`). Tables that exist only in the
96
+ * snapshot (or only in the target) are left untouched — this is a data restore, not
97
+ * a schema migration. Atomic: any failure rolls the whole restore back.
98
+ */
99
+ export function restoreSqliteSnapshot(db: Database, bytes: Uint8Array): RestoreResult {
100
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
101
+ const tmp = join(tmpdir(), `cb-restore-${stamp}-${Math.random().toString(36).slice(2)}.sqlite`);
102
+ writeFileSync(tmp, bytes);
103
+ try {
104
+ // Validate it opens as SQLite and enumerate its user tables.
105
+ const snap = new Database(tmp, { readonly: true });
106
+ let snapTables: string[];
107
+ try {
108
+ snapTables = snap
109
+ .query<{ name: string }, []>(
110
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
111
+ )
112
+ .all()
113
+ .map((r) => r.name);
114
+ } finally {
115
+ snap.close();
116
+ }
117
+
118
+ const targets = snapTables.filter((t) => isKnownTable(db, t));
119
+ let totalRows = 0;
120
+ withForeignKeysOff(db, () => {
121
+ db.run('ATTACH DATABASE ? AS restore_src', [tmp]);
122
+ try {
123
+ const run = db.transaction(() => {
124
+ for (const t of targets) {
125
+ const q = quoteIdent(t);
126
+ db.run(`DELETE FROM main.${q}`);
127
+ db.run(`INSERT INTO main.${q} SELECT * FROM restore_src.${q}`);
128
+ totalRows +=
129
+ db.query<{ n: number }, []>(`SELECT COUNT(*) AS n FROM main.${q}`).get()?.n ?? 0;
130
+ }
131
+ });
132
+ run();
133
+ } finally {
134
+ db.run('DETACH DATABASE restore_src');
135
+ }
136
+ });
137
+ return { restoredTables: targets, totalRows };
138
+ } finally {
139
+ rmSync(tmp, { force: true });
140
+ }
141
+ }
@@ -0,0 +1,64 @@
1
+ // cursedbelt SQLite toolkit extras (task 30) — bun:sqlite-native primitives layered
2
+ // on the migration runner (task 24): FTS5 ranked search, a read-only DB viewer, the
3
+ // VACUUM-INTO backup service, DB-layer health probes, plus the Hono-free browse /
4
+ // export / import / stats primitives that back both the viewer middleware and any
5
+ // bespoke-transport consumer (a cursedshell plugin, the encrypted-GET "f" app). All
6
+ // app-agnostic; the apps supply table names, paths, and thresholds.
7
+
8
+ export {
9
+ type BackupInfo,
10
+ createSqliteBackupService,
11
+ snapshotSqlite,
12
+ type SqliteBackupOpts,
13
+ type SqliteBackupS3Opts,
14
+ type SqliteBackupService,
15
+ } from './backup';
16
+ export {
17
+ clampLimit,
18
+ clampOffset,
19
+ isKnownTable,
20
+ listSqliteTables,
21
+ NotReadOnlyError,
22
+ quoteIdent,
23
+ readSqliteTable,
24
+ runReadonlySqlite,
25
+ type SqliteTableInfo,
26
+ type SqliteTablePage,
27
+ UnknownTableError,
28
+ } from './browse';
29
+ export {
30
+ exportSqliteSnapshot,
31
+ exportSqliteTable,
32
+ type SnapshotExport,
33
+ type TableExport,
34
+ type TableExportFormat,
35
+ } from './export';
36
+ export {
37
+ applyFtsSchema,
38
+ buildFtsSchema,
39
+ createFts5Search,
40
+ type Fts5Search,
41
+ type FtsHighlightOpts,
42
+ type FtsResult,
43
+ type FtsSearchOpts,
44
+ type FtsSnippetOpts,
45
+ type FtsTableSpec,
46
+ sanitizeFtsQuery,
47
+ } from './fts5';
48
+ export {
49
+ createSqliteHealthChecks,
50
+ type SqliteHealthCheck,
51
+ type SqliteHealthOpts,
52
+ type SqliteHealthResult,
53
+ type SqliteHealthStatus,
54
+ } from './health';
55
+ export {
56
+ type ImportMode,
57
+ type ImportResult,
58
+ importRowsIntoSqliteTable,
59
+ type RestoreResult,
60
+ restoreSqliteSnapshot,
61
+ } from './import';
62
+ export { isReadOnlySql } from './readonlySql';
63
+ export { collectSqliteStats, type DbStats, type TableStat } from './stats';
64
+ export { createSqliteViewerMiddleware } from './viewer';
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { isReadOnlySql } from './readonlySql';
3
+
4
+ describe('isReadOnlySql', () => {
5
+ test('allows SELECT and WITH … SELECT', () => {
6
+ expect(isReadOnlySql('SELECT * FROM notes')).toBe(true);
7
+ expect(isReadOnlySql(' select 1 ')).toBe(true);
8
+ expect(isReadOnlySql('WITH x AS (SELECT 1) SELECT * FROM x')).toBe(true);
9
+ expect(isReadOnlySql('SELECT * FROM notes WHERE body LIKE ? -- comment')).toBe(true);
10
+ expect(isReadOnlySql('SELECT * FROM notes;')).toBe(true); // single trailing terminator
11
+ });
12
+
13
+ test('rejects writes and DDL', () => {
14
+ for (const sql of [
15
+ 'INSERT INTO notes (body) VALUES (1)',
16
+ 'UPDATE notes SET body = 1',
17
+ 'DELETE FROM notes',
18
+ 'DROP TABLE notes',
19
+ 'ALTER TABLE notes ADD COLUMN x TEXT',
20
+ 'CREATE TABLE x (id INT)',
21
+ 'REPLACE INTO notes VALUES (1)',
22
+ 'TRUNCATE notes',
23
+ ]) {
24
+ expect(isReadOnlySql(sql)).toBe(false);
25
+ }
26
+ });
27
+
28
+ test('rejects SQLite-specific write vectors and transactions', () => {
29
+ for (const sql of [
30
+ "ATTACH DATABASE 'evil.db' AS evil",
31
+ 'PRAGMA user_version = 9',
32
+ 'VACUUM',
33
+ 'REINDEX',
34
+ 'BEGIN',
35
+ 'COMMIT',
36
+ 'SAVEPOINT s',
37
+ ]) {
38
+ expect(isReadOnlySql(sql)).toBe(false);
39
+ }
40
+ });
41
+
42
+ test('rejects stacked statements and SELECT … INTO', () => {
43
+ expect(isReadOnlySql('SELECT 1; DROP TABLE notes')).toBe(false);
44
+ expect(isReadOnlySql('SELECT 1; SELECT 2')).toBe(false);
45
+ expect(isReadOnlySql('SELECT * INTO copy FROM notes')).toBe(false);
46
+ });
47
+
48
+ test('rejects empty / non-statement input', () => {
49
+ expect(isReadOnlySql('')).toBe(false);
50
+ expect(isReadOnlySql(' ')).toBe(false);
51
+ expect(isReadOnlySql('-- just a comment')).toBe(false);
52
+ });
53
+
54
+ test('does not false-positive on column names containing keywords', () => {
55
+ // "created_at" / "update_count" embed keywords but only as substrings, not words.
56
+ expect(isReadOnlySql('SELECT created_at, update_count FROM notes')).toBe(true);
57
+ });
58
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Defense-in-depth read-only guard for the SQLite viewer's ad-hoc query endpoint. A
3
+ * conservative *lexical* check, NOT a parser: it rejects the obvious foot-guns
4
+ * (writes, DDL, PRAGMA, ATTACH, stacked statements) before SQL reaches the DB. The
5
+ * real guarantees are the owner-only mount + this allowlist, layered.
6
+ *
7
+ * cursedbelt keeps its own copy rather than depending on cwip — the toolkit's server
8
+ * surface stays dependency-light, and the rule set here is tuned for SQLite (PRAGMA /
9
+ * ATTACH are SQLite-specific write vectors).
10
+ */
11
+
12
+ const FORBIDDEN = [
13
+ 'insert',
14
+ 'update',
15
+ 'delete',
16
+ 'drop',
17
+ 'alter',
18
+ 'truncate',
19
+ 'create',
20
+ 'replace',
21
+ 'attach',
22
+ 'detach',
23
+ 'reindex',
24
+ 'vacuum',
25
+ 'pragma',
26
+ 'begin',
27
+ 'commit',
28
+ 'rollback',
29
+ 'savepoint',
30
+ 'release',
31
+ 'grant',
32
+ 'revoke',
33
+ ];
34
+
35
+ /** Strip `--` line comments and block comments, collapse whitespace, lowercase. */
36
+ function normalize(sql: string): string {
37
+ return sql
38
+ .replace(/--[^\n]*/g, ' ')
39
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
40
+ .replace(/\s+/g, ' ')
41
+ .trim()
42
+ .toLowerCase();
43
+ }
44
+
45
+ /**
46
+ * True only for a single read-only statement: a `SELECT`, or a `WITH … SELECT` CTE.
47
+ * Rejects stacked statements, `SELECT … INTO`, and any forbidden keyword as a word.
48
+ */
49
+ export function isReadOnlySql(sql: string): boolean {
50
+ const s = normalize(sql);
51
+ if (!s) return false;
52
+
53
+ // Only a trailing `;` terminator is allowed — no stacked statements.
54
+ const body = s.replace(/;\s*$/, '');
55
+ if (body.includes(';')) return false;
56
+
57
+ // Must begin with SELECT or a CTE.
58
+ if (!/^(select|with)\b/.test(body)) return false;
59
+
60
+ // `SELECT … INTO new_table` writes a table — block it.
61
+ if (/\bselect\b[\s\S]*\binto\b/.test(body)) return false;
62
+
63
+ for (const kw of FORBIDDEN) {
64
+ if (new RegExp(`\\b${kw}\\b`).test(body)) return false;
65
+ }
66
+ return true;
67
+ }
@@ -0,0 +1,21 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { collectSqliteStats } from './stats';
4
+
5
+ describe('collectSqliteStats', () => {
6
+ test('reports per-table rows, total rows, and a positive file size', () => {
7
+ const db = new Database(':memory:');
8
+ db.run('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
9
+ db.run('CREATE TABLE contacts (id INTEGER PRIMARY KEY)');
10
+ db.run("INSERT INTO notes (id, body) VALUES (1, 'a'), (2, 'b'), (3, 'c')");
11
+
12
+ const stats = collectSqliteStats(db);
13
+ expect(stats.totalRows).toBe(3);
14
+ expect(stats.fileBytes).toBeGreaterThan(0);
15
+ expect(stats.tables.find((t) => t.name === 'notes')?.rows).toBe(3);
16
+ expect(stats.tables.find((t) => t.name === 'contacts')?.rows).toBe(0);
17
+ // dbstat is available in bun:sqlite, so bytes should be a number (>= 0), not null.
18
+ const notes = stats.tables.find((t) => t.name === 'notes');
19
+ expect(notes?.bytes === null || typeof notes?.bytes === 'number').toBe(true);
20
+ });
21
+ });
@@ -0,0 +1,61 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { listSqliteTables } from './browse';
3
+
4
+ /**
5
+ * DB usage stats for `bun:sqlite` — per-table row counts and on-disk byte sizes
6
+ * plus the total database file size. Feeds both StatTiles and the rows/bytes charts
7
+ * in an admin surface. Per-table bytes use the `dbstat` virtual table when the build
8
+ * supports it (it does in `bun:sqlite`); if a query for it ever fails, byte sizes
9
+ * degrade to `null` rather than throwing — row counts and the file size still work.
10
+ */
11
+
12
+ export interface TableStat {
13
+ name: string;
14
+ rows: number;
15
+ /** On-disk bytes for this table's pages, or `null` when `dbstat` is unavailable. */
16
+ bytes: number | null;
17
+ }
18
+
19
+ export interface DbStats {
20
+ /** Total database file size in bytes (`page_count * page_size`). */
21
+ fileBytes: number;
22
+ /** Sum of every table's row count. */
23
+ totalRows: number;
24
+ tables: TableStat[];
25
+ }
26
+
27
+ /** Whole-database size from the page pragmas (works without `dbstat`). */
28
+ function fileBytes(db: Database): number {
29
+ const pageCount =
30
+ db.query<{ page_count: number }, []>('PRAGMA page_count').get()?.page_count ?? 0;
31
+ const pageSize = db.query<{ page_size: number }, []>('PRAGMA page_size').get()?.page_size ?? 0;
32
+ return pageCount * pageSize;
33
+ }
34
+
35
+ /** Per-table byte sizes from `dbstat`, or `null` for all tables if it's unavailable. */
36
+ function bytesByTable(db: Database): Map<string, number> | null {
37
+ try {
38
+ const rows = db
39
+ .query<{ name: string; bytes: number }, []>(
40
+ 'SELECT name, SUM(pgsize) AS bytes FROM dbstat GROUP BY name',
41
+ )
42
+ .all();
43
+ return new Map(rows.map((r) => [r.name, r.bytes]));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export function collectSqliteStats(db: Database): DbStats {
50
+ const byTable = bytesByTable(db);
51
+ const tables: TableStat[] = listSqliteTables(db).map(({ name, count }) => ({
52
+ name,
53
+ rows: count,
54
+ bytes: byTable ? (byTable.get(name) ?? 0) : null,
55
+ }));
56
+ return {
57
+ fileBytes: fileBytes(db),
58
+ totalRows: tables.reduce((sum, t) => sum + t.rows, 0),
59
+ tables,
60
+ };
61
+ }
@@ -0,0 +1,127 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { Hono } from 'hono';
4
+ import { createSqliteViewerMiddleware } from './viewer';
5
+
6
+ function makeApp(): { app: Hono; db: Database } {
7
+ const db = new Database(':memory:');
8
+ db.run('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
9
+ db.run('CREATE TABLE contacts (id INTEGER PRIMARY KEY, name TEXT)');
10
+ for (let i = 1; i <= 5; i++)
11
+ db.run('INSERT INTO notes (id, body) VALUES (?, ?)', [i, `note ${i}`]);
12
+ const app = new Hono().route('/admin/sqlite', createSqliteViewerMiddleware(db));
13
+ return { app, db };
14
+ }
15
+
16
+ const get = (app: Hono, path: string) => app.fetch(new Request(`http://localhost${path}`));
17
+ const post = (app: Hono, path: string, body: unknown) =>
18
+ app.fetch(
19
+ new Request(`http://localhost${path}`, {
20
+ method: 'POST',
21
+ body: JSON.stringify(body),
22
+ headers: { 'Content-Type': 'application/json' },
23
+ }),
24
+ );
25
+
26
+ describe('createSqliteViewerMiddleware /tables', () => {
27
+ test('lists user tables with row counts', async () => {
28
+ const { app } = makeApp();
29
+ const res = await get(app, '/admin/sqlite/tables');
30
+ expect(res.status).toBe(200);
31
+ const { items } = (await res.json()) as { items: { name: string; count: number }[] };
32
+ expect(items.map((t) => t.name).sort()).toEqual(['contacts', 'notes']);
33
+ expect(items.find((t) => t.name === 'notes')?.count).toBe(5);
34
+ expect(items.find((t) => t.name === 'contacts')?.count).toBe(0);
35
+ });
36
+
37
+ test('excludes sqlite_* internal tables', async () => {
38
+ const { app, db } = makeApp();
39
+ db.run('CREATE TABLE x (id INTEGER PRIMARY KEY AUTOINCREMENT)'); // creates sqlite_sequence
40
+ const res = await get(app, '/admin/sqlite/tables');
41
+ const { items } = (await res.json()) as { items: { name: string }[] };
42
+ expect(items.some((t) => t.name.startsWith('sqlite_'))).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe('createSqliteViewerMiddleware /tables/:table', () => {
47
+ test('paginates rows with total/limit/offset', async () => {
48
+ const { app } = makeApp();
49
+ const res = await get(app, '/admin/sqlite/tables/notes?limit=2&offset=1');
50
+ expect(res.status).toBe(200);
51
+ const body = (await res.json()) as {
52
+ items: unknown[];
53
+ total: number;
54
+ limit: number;
55
+ offset: number;
56
+ };
57
+ expect(body.items).toHaveLength(2);
58
+ expect(body.total).toBe(5);
59
+ expect(body.limit).toBe(2);
60
+ expect(body.offset).toBe(1);
61
+ });
62
+
63
+ test('404s an unknown table (no SQL runs)', async () => {
64
+ const { app } = makeApp();
65
+ const res = await get(app, '/admin/sqlite/tables/does_not_exist');
66
+ expect(res.status).toBe(404);
67
+ expect(((await res.json()) as { error: { code: string } }).error.code).toBe('unknown_table');
68
+ });
69
+
70
+ test('a SQL-injection table name cannot escape (treated as unknown)', async () => {
71
+ const { app, db } = makeApp();
72
+ const res = await get(app, '/admin/sqlite/tables/notes%22%3B%20DROP%20TABLE%20notes%3B--');
73
+ expect(res.status).toBe(404);
74
+ expect(db.query("SELECT name FROM sqlite_master WHERE name='notes'").all()).toHaveLength(1);
75
+ });
76
+ });
77
+
78
+ describe('createSqliteViewerMiddleware /query', () => {
79
+ test('runs a read-only SELECT with bound params', async () => {
80
+ const { app } = makeApp();
81
+ const res = await post(app, '/admin/sqlite/query', {
82
+ sql: 'SELECT id, body FROM notes WHERE id = ?',
83
+ params: [3],
84
+ });
85
+ expect(res.status).toBe(200);
86
+ const { items } = (await res.json()) as { items: { id: number; body: string }[] };
87
+ expect(items).toEqual([{ id: 3, body: 'note 3' }]);
88
+ });
89
+
90
+ test('rejects a non-SELECT statement', async () => {
91
+ const { app, db } = makeApp();
92
+ const res = await post(app, '/admin/sqlite/query', { sql: 'DROP TABLE notes' });
93
+ expect(res.status).toBe(400);
94
+ expect(((await res.json()) as { error: { code: string } }).error.code).toBe('not_read_only');
95
+ // The table is untouched.
96
+ expect(db.query("SELECT name FROM sqlite_master WHERE name='notes'").all()).toHaveLength(1);
97
+ });
98
+
99
+ test('rejects stacked statements and PRAGMA', async () => {
100
+ const { app } = makeApp();
101
+ expect(
102
+ (await post(app, '/admin/sqlite/query', { sql: 'SELECT 1; DELETE FROM notes' })).status,
103
+ ).toBe(400);
104
+ expect((await post(app, '/admin/sqlite/query', { sql: 'PRAGMA user_version' })).status).toBe(
105
+ 400,
106
+ );
107
+ });
108
+
109
+ test('returns 400 with the SQLite message for a malformed SELECT', async () => {
110
+ const { app } = makeApp();
111
+ const res = await post(app, '/admin/sqlite/query', { sql: 'SELECT * FROM nope_table' });
112
+ expect(res.status).toBe(400);
113
+ expect(((await res.json()) as { error: { code: string } }).error.code).toBe('query_failed');
114
+ });
115
+
116
+ test('returns 400 on an invalid JSON body', async () => {
117
+ const { app } = makeApp();
118
+ const res = await app.fetch(
119
+ new Request('http://localhost/admin/sqlite/query', {
120
+ method: 'POST',
121
+ body: 'not json',
122
+ headers: { 'Content-Type': 'application/json' },
123
+ }),
124
+ );
125
+ expect(res.status).toBe(400);
126
+ });
127
+ });