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,392 @@
1
+ // search-writer-allow-file: schema-lifecycle suite — it DROPs and rebuilds the saas
2
+ // search schema per run and TRUNCATEs between tests, because the schema and its RLS
3
+ // policy ARE the subject here. No statement below writes an index row outside the
4
+ // provider.
5
+ /**
6
+ * The saas search tier against a REAL Postgres (04 §3).
7
+ *
8
+ * Everything this provider claims is a claim about Postgres, not about
9
+ * TypeScript: that a `GENERATED ALWAYS AS … STORED` tsvector stays in step
10
+ * through an `ON CONFLICT DO UPDATE`, that `websearch_to_tsquery` cannot be made
11
+ * to raise on user input, that `ts_rank` with the 4× title weighting orders a
12
+ * corpus the way the SQLite side does, that `FORCE ROW LEVEL SECURITY` binds the
13
+ * owning role, and that `bytea` round-trips an embedding bit-for-bit. A fake
14
+ * pool can prove the plumbing and nothing else — it would be answering those
15
+ * questions with our own assumptions, which is how the previous arc shipped a
16
+ * search tier that had never run.
17
+ *
18
+ * bun run test:pg (starts a disposable container, runs, tears down)
19
+ * CC_PG_URL=postgres://… (or point it at any throwaway database)
20
+ *
21
+ * SKIPPED, loudly, when `CC_PG_URL` is absent — the skip is a missing proof,
22
+ * never a passing one. It runs nightly (`.github/workflows/nightly.yml`).
23
+ */
24
+ import { SQL } from 'bun';
25
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
26
+ import type { SaasConnection, SaasPool } from 'cwip/storage';
27
+ import { createPgSearchProvider } from './pgSearchProvider';
28
+ import { ensureSaasSearchSchema } from './pgSearchSchema';
29
+
30
+ const ADMIN_URL = process.env.CC_PG_URL;
31
+
32
+ /**
33
+ * A dedicated NON-SUPERUSER owner, and the reason is the finding this suite
34
+ * produced.
35
+ *
36
+ * Connecting as the container's default `postgres` role, the RLS test passed
37
+ * with BOTH tenants' rows: a **superuser bypasses row-level security
38
+ * unconditionally**, and `FORCE ROW LEVEL SECURITY` does not change that — FORCE
39
+ * subjects the table's OWNER to its policy, not a superuser. So the whole
40
+ * isolation guarantee is invisible to a suite that connects as one, and it would
41
+ * be equally invisible in a deployment whose application connects as one. This
42
+ * suite therefore runs as the shape a deployment should have (a non-superuser
43
+ * role that owns its own schema), which makes the policy load-bearing here
44
+ * rather than decorative.
45
+ */
46
+ const ROLE = 'cc_search_rls';
47
+ const PASSWORD = 'cc_search_rls_pw';
48
+ const DATABASE = 'cc_search_rls_db';
49
+
50
+ const appUrl = (): string => {
51
+ const admin = new URL(ADMIN_URL as string);
52
+ admin.username = ROLE;
53
+ admin.password = PASSWORD;
54
+ admin.pathname = `/${DATABASE}`;
55
+ return admin.toString();
56
+ };
57
+
58
+ let sql: SQL;
59
+ let pool: SaasPool;
60
+
61
+ const TENANT = 't_saas';
62
+ const OTHER = 't_other';
63
+ const CORPUS = 'knowledge';
64
+ const MODEL = 'vocab-v1';
65
+
66
+ /** Deterministic token-count embedder — see the local provider's suite for why. */
67
+ const VOCAB = ['canine', 'puppy', 'feline', 'kitten', 'ledger', 'invoice'] as const;
68
+ const embed = async (text: string): Promise<Float32Array> => {
69
+ const lower = text.toLowerCase();
70
+ return Float32Array.from(VOCAB.map((word) => lower.split(word).length - 1));
71
+ };
72
+
73
+ const doc = (id: string, title: string, body: string, tenantId = TENANT) => ({
74
+ corpus: CORPUS,
75
+ id,
76
+ tenantId,
77
+ title,
78
+ body,
79
+ });
80
+
81
+ // SKIPPED unless CC_PG_URL names a throwaway Postgres — this suite's subject IS the engine. `bun run test:pg` supplies one.
82
+ describe.skipIf(!ADMIN_URL)('createPgSearchProvider against a real Postgres (04 §3)', () => {
83
+ // 120s for the same reason the control-plane suite gives: this hook drops and
84
+ // rebuilds a whole role, database and schema, and on a shared CI runner a 5s
85
+ // default times out mid-migration and reports the failure as though the SQL
86
+ // were wrong.
87
+ beforeAll(async () => {
88
+ const admin = new SQL(ADMIN_URL as string);
89
+ await admin.unsafe(`DROP DATABASE IF EXISTS ${DATABASE}`);
90
+ await admin.unsafe(`DROP ROLE IF EXISTS ${ROLE}`);
91
+ await admin.unsafe(`CREATE ROLE ${ROLE} LOGIN PASSWORD '${PASSWORD}' NOSUPERUSER NOBYPASSRLS`);
92
+ await admin.unsafe(`CREATE DATABASE ${DATABASE} OWNER ${ROLE}`);
93
+ await admin.close();
94
+
95
+ // `max: 1` is load-bearing, not tuning. The provider issues
96
+ // BEGIN / set_config / … / COMMIT, and a driver free to move the next
97
+ // statement onto a different physical connection would scope one connection
98
+ // and query another — which is the exact bug the transaction-local setting
99
+ // exists to prevent, arriving from the test harness instead of the code.
100
+ sql = new SQL(appUrl(), { max: 1 });
101
+ const connection: SaasConnection = {
102
+ async query<R>(text: string, params: readonly unknown[] = []): Promise<R[]> {
103
+ return (await sql.unsafe(text, [...params])) as R[];
104
+ },
105
+ async execute(text: string, params: readonly unknown[] = []) {
106
+ const result = await sql.unsafe(text, [...params]);
107
+ return { rowCount: (result as { count?: number }).count ?? 0 };
108
+ },
109
+ };
110
+ pool = {
111
+ async withConnection(fn) {
112
+ return await fn(connection);
113
+ },
114
+ };
115
+
116
+ await sql.unsafe('DROP TABLE IF EXISTS search_chunks');
117
+ await sql.unsafe('DROP TABLE IF EXISTS search_docs');
118
+ await ensureSaasSearchSchema(pool);
119
+ }, 120_000);
120
+
121
+ afterAll(async () => {
122
+ await sql?.end();
123
+ });
124
+
125
+ beforeEach(async () => {
126
+ // TRUNCATE, not DELETE — and the difference is the whole point of running as
127
+ // a non-superuser. A `DELETE` issued outside a tenant scope is checked
128
+ // against the policy, matches nothing, and SILENTLY LEAVES EVERY ROW, so
129
+ // state leaks from test to test and the failures read as ranking bugs.
130
+ // TRUNCATE requires ownership rather than a policy match, so it is the one
131
+ // cleanup that is honest under FORCE RLS.
132
+ await sql.unsafe('TRUNCATE search_chunks, search_docs RESTART IDENTITY');
133
+ });
134
+
135
+ const keywordOnly = () => createPgSearchProvider({ pool });
136
+ const hybrid = () => createPgSearchProvider({ pool, embed, embedModel: MODEL });
137
+
138
+ describe('schema', () => {
139
+ it('creates itself idempotently — a second deployment run is not a hard failure', async () => {
140
+ // `CREATE POLICY` has no `IF NOT EXISTS`, so this is the assertion that the
141
+ // skip-if-present path works rather than throwing 42710.
142
+ await ensureSaasSearchSchema(pool);
143
+ const policies = (await sql.unsafe(
144
+ "SELECT policyname FROM pg_policies WHERE tablename = 'search_docs'",
145
+ )) as { policyname: string }[];
146
+ expect(policies.map((row) => row.policyname)).toEqual(['tenant_isolation']);
147
+ });
148
+
149
+ it('FORCES row level security, so the owning role is subject to it too', async () => {
150
+ const [row] = (await sql.unsafe(
151
+ "SELECT relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname = 'search_docs'",
152
+ )) as { relrowsecurity: boolean; relforcerowsecurity: boolean }[];
153
+ // ENABLE alone is decoration for the role that owns the schema — which, in
154
+ // this platform, is the role that serves every request.
155
+ expect(row?.relrowsecurity).toBe(true);
156
+ expect(row?.relforcerowsecurity).toBe(true);
157
+ });
158
+
159
+ it('keeps the tsvector in step through an ON CONFLICT DO UPDATE', async () => {
160
+ const search = keywordOnly();
161
+ await search.index(doc('d1', 'First title', 'canine body'));
162
+ await search.index(doc('d1', 'Second title', 'feline body'));
163
+
164
+ // A trigger written for INSERT only would leave the old postings here. A
165
+ // generated column cannot, because no code path writes it — which is the
166
+ // reason the schema uses one.
167
+ await expect(search.query('feline', { tenantId: TENANT })).resolves.toHaveLength(1);
168
+ await expect(search.query('canine', { tenantId: TENANT })).resolves.toEqual([]);
169
+ });
170
+ });
171
+
172
+ describe('the keyword tier', () => {
173
+ it('finds a document and reports a higher-is-better score', async () => {
174
+ const search = keywordOnly();
175
+ await search.index(doc('d1', 'Dogs', 'canine puppy notes'));
176
+ const hits = await search.query('canine', { tenantId: TENANT });
177
+ expect(hits).toHaveLength(1);
178
+ expect(hits[0]?.id).toBe('d1');
179
+ // `ts_rank` is already higher-is-better. The available mistake is copying
180
+ // the SQLite provider's sign flip, which would invert every ranking.
181
+ expect(hits[0]?.score).toBeGreaterThan(0);
182
+ expect(hits[0]?.snippet.length).toBeGreaterThan(0);
183
+ });
184
+
185
+ it('weights a title hit above a body hit, exactly as the SQLite profile does', async () => {
186
+ const search = keywordOnly();
187
+ await search.index(doc('body', 'Notes', 'ledger ledger ledger canine'));
188
+ await search.index(doc('title', 'Canine', 'ledger ledger ledger ledger'));
189
+ const hits = await search.query('canine', { tenantId: TENANT });
190
+ // Two profiles that ranked one corpus differently would make "search ranks
191
+ // wrong in production" unreproducible on a laptop.
192
+ expect(hits.map((hit) => hit.id)).toEqual(['title', 'body']);
193
+ });
194
+
195
+ it('survives every punctuation a real person types into a search box', async () => {
196
+ const search = keywordOnly();
197
+ await search.index(doc('d1', 'Quotes', 'canine puppy'));
198
+ // The SQLite side needs a phrase-literal retry for these because FTS5
199
+ // raises. `websearch_to_tsquery` is total, so a 500 on a hyphen — a product
200
+ // defect, not user error — is not reachable here at all.
201
+ for (const query of ['"canine', 'canine -', 'AND OR NEAR', 'canine)("', '""""', '-canine']) {
202
+ await expect(search.query(query, { tenantId: TENANT })).resolves.toBeArray();
203
+ }
204
+ });
205
+
206
+ it('returns nothing for an empty box rather than the whole corpus', async () => {
207
+ const search = keywordOnly();
208
+ await search.index(doc('d1', 'Any', 'canine'));
209
+ await expect(search.query(' ', { tenantId: TENANT })).resolves.toEqual([]);
210
+ });
211
+ });
212
+
213
+ describe('tenancy', () => {
214
+ it('never returns another tenant’s document', async () => {
215
+ const search = keywordOnly();
216
+ await search.index(doc('mine', 'Mine', 'canine puppy'));
217
+ await search.index(doc('theirs', 'Theirs', 'canine puppy', OTHER));
218
+
219
+ await expect(search.count(CORPUS, TENANT)).resolves.toBe(1);
220
+ const hits = await search.query('canine', { tenantId: TENANT });
221
+ expect(hits.map((hit) => hit.id)).toEqual(['mine']);
222
+ });
223
+
224
+ it('is protected by the DATABASE, not only by the predicate', async () => {
225
+ const search = keywordOnly();
226
+ await search.index(doc('mine', 'Mine', 'canine'));
227
+ await search.index(doc('theirs', 'Theirs', 'canine', OTHER));
228
+
229
+ // The same statement the provider runs, minus its tenant predicate, inside
230
+ // the same tenant scope. If the policy were merely ENABLEd (not FORCEd) or
231
+ // the predicate were the only control, this would return both rows.
232
+ const rows = await pool.withConnection(async (connection) => {
233
+ await connection.execute('BEGIN');
234
+ await connection.execute('SELECT set_config($1, $2, true)', ['app.tenant_id', TENANT]);
235
+ const all = await connection.query<{ source_id: string }>('SELECT source_id FROM search_docs');
236
+ await connection.execute('COMMIT');
237
+ return all;
238
+ });
239
+ expect(rows.map((row) => row.source_id)).toEqual(['mine']);
240
+ });
241
+
242
+ it('refuses an operation with no tenant', async () => {
243
+ const search = keywordOnly();
244
+ await expect(search.query('canine', { tenantId: '' })).rejects.toThrow(/tenantId/);
245
+ });
246
+ });
247
+
248
+ describe('the vector tier', () => {
249
+ it('writes chunks only when an embedder is wired', async () => {
250
+ await keywordOnly().index(doc('kw', 'Keyword', 'canine puppy'));
251
+ await expect(keywordOnly().countChunks(CORPUS, TENANT)).resolves.toBe(0);
252
+
253
+ const search = hybrid();
254
+ await search.index(doc('vec', 'Vector', 'canine puppy'));
255
+ await expect(search.countChunks(CORPUS, TENANT)).resolves.toBe(1);
256
+ });
257
+
258
+ it('round-trips an embedding through bytea bit-for-bit', async () => {
259
+ const search = hybrid();
260
+ await search.index(doc('d1', 'Dogs', 'canine puppy'));
261
+ const [row] = (await sql.unsafe('SELECT embedding, dims, model FROM search_chunks')) as {
262
+ embedding: Uint8Array;
263
+ dims: number;
264
+ model: string;
265
+ }[];
266
+ const expected = await embed('Dogs\n\ncanine puppy');
267
+ // Decoded through the same little-endian codec the reader uses. A driver
268
+ // that handed back a hex string or a differently-offset view would fail
269
+ // here rather than silently ranking by noise.
270
+ const { decodeEmbedding } = await import('cwip/search');
271
+ expect([...decodeEmbedding(row?.embedding as Uint8Array)]).toEqual([...expected]);
272
+ expect(row?.dims).toBe(VOCAB.length);
273
+ expect(row?.model).toBe(MODEL);
274
+ });
275
+
276
+ it('ranks the semantically nearest document above a keyword-only match', async () => {
277
+ const search = hybrid();
278
+ // Same fixture shape as the SQLite suite, and for the same reason — read
279
+ // that suite's comment before simplifying this to two documents.
280
+ await search.index(
281
+ doc('decoy', 'Canine Puppy Ledger', 'ledger invoice ledger invoice ledger invoice ledger invoice canine puppy'),
282
+ );
283
+ await search.index(doc('near', 'Notes', 'canine puppy canine puppy canine puppy'));
284
+ await search.index(doc('canines', 'Canines', 'canine canine canine'));
285
+ await search.index(doc('pups', 'Pups', 'puppy puppy puppy'));
286
+
287
+ const keyword = await keywordOnly().query('canine puppy', { tenantId: TENANT });
288
+ const fused = await search.query('canine puppy', { tenantId: TENANT });
289
+
290
+ // The negative control first: BM25/ts_rank alone puts `decoy` on top.
291
+ expect(keyword[0]?.id).toBe('decoy');
292
+ expect(fused[0]?.id).toBe('near');
293
+ expect(fused.map((hit) => hit.id)).toContain('decoy');
294
+ });
295
+
296
+ it('surfaces a document the keyword tier cannot find, with a hydrated title', async () => {
297
+ const search = hybrid();
298
+ await search.index(doc('semantic', 'Puppy notes', 'canine puppy'));
299
+
300
+ await expect(keywordOnly().query('kitten feline', { tenantId: TENANT })).resolves.toEqual([]);
301
+ const fused = await search.query('canine kitten', { tenantId: TENANT });
302
+ expect(fused.map((hit) => hit.id)).toContain('semantic');
303
+ expect(fused.find((hit) => hit.id === 'semantic')?.title).toBe('Puppy notes');
304
+ });
305
+
306
+ it('never ranks another tenant’s chunks', async () => {
307
+ const search = hybrid();
308
+ await search.index(doc('mine', 'Mine', 'canine puppy'));
309
+ await search.index(doc('theirs', 'Theirs', 'canine puppy', OTHER));
310
+ const hits = await search.query('canine puppy', { tenantId: TENANT });
311
+ expect(hits.map((hit) => hit.id)).toEqual(['mine']);
312
+ });
313
+
314
+ it('replaces a document’s chunks on re-index rather than accumulating them', async () => {
315
+ const search = hybrid();
316
+ await search.index(doc('r1', 'First', 'canine puppy'));
317
+ await search.index(doc('r1', 'Second', 'feline kitten'));
318
+ await expect(search.countChunks(CORPUS, TENANT)).resolves.toBe(1);
319
+ await expect(search.count(CORPUS, TENANT)).resolves.toBe(1);
320
+ });
321
+
322
+ it('degrades to the keyword tier when the embedder is down', async () => {
323
+ const phases: string[] = [];
324
+ const flaky = createPgSearchProvider({
325
+ pool,
326
+ embed: async () => {
327
+ throw new Error('embedding provider is down');
328
+ },
329
+ onVectorError: (_error, phase) => phases.push(phase),
330
+ });
331
+ await flaky.index(doc('d1', 'Dogs', 'canine puppy'));
332
+ // The document still landed — a failing embedder must not look like a
333
+ // write failure to the CDC tap, which would retry into an outage.
334
+ await expect(flaky.count(CORPUS, TENANT)).resolves.toBe(1);
335
+ await expect(flaky.query('canine', { tenantId: TENANT })).resolves.toHaveLength(1);
336
+ expect(phases).toEqual(['index', 'query']);
337
+ });
338
+
339
+ it('reports a truncated candidate scan', async () => {
340
+ const truncations: unknown[] = [];
341
+ const search = createPgSearchProvider({
342
+ pool,
343
+ embed,
344
+ embedModel: MODEL,
345
+ vectorCandidateLimit: 1,
346
+ onVectorTruncated: (detail) => truncations.push(detail),
347
+ });
348
+ await search.index(doc('t1', 'One', 'canine puppy'));
349
+ await search.index(doc('t2', 'Two', 'canine kitten'));
350
+ await search.query('canine', { tenantId: TENANT });
351
+ expect(truncations).toHaveLength(1);
352
+ });
353
+ });
354
+
355
+ describe('rebuild operations', () => {
356
+ it('reads its epoch from the DATABASE clock, in the index’s own format', async () => {
357
+ const epoch = await keywordOnly().now();
358
+ // Same format the SQLite provider's `strftime` produces — the reindexer
359
+ // compares this string against `indexed_at`, and two formats would make
360
+ // that comparison quietly wrong rather than loudly broken.
361
+ expect(epoch).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
362
+ });
363
+
364
+ it('sweeps documents older than the epoch, and their chunks with them', async () => {
365
+ const search = hybrid();
366
+ await search.index(doc('old', 'Old', 'canine puppy'));
367
+ await Bun.sleep(5);
368
+ const epoch = await search.now();
369
+ await search.index(doc('new', 'New', 'canine puppy'));
370
+
371
+ // Counted as DOCUMENTS. Reading the delete's row count would report the
372
+ // cascaded chunks too, and the reindex job's log would lie about how much
373
+ // it removed.
374
+ await expect(search.sweepStale(CORPUS, TENANT, epoch)).resolves.toBe(1);
375
+ await expect(search.count(CORPUS, TENANT)).resolves.toBe(1);
376
+ await expect(search.countChunks(CORPUS, TENANT)).resolves.toBe(1);
377
+ await expect(search.query('canine', { tenantId: TENANT })).resolves.toHaveLength(1);
378
+ });
379
+
380
+ it('removes a document and its chunks together', async () => {
381
+ const search = hybrid();
382
+ await search.index(doc('gone', 'Gone', 'canine puppy'));
383
+ await search.remove(CORPUS, 'gone', TENANT);
384
+ await expect(search.count(CORPUS, TENANT)).resolves.toBe(0);
385
+ await expect(search.countChunks(CORPUS, TENANT)).resolves.toBe(0);
386
+ });
387
+
388
+ it('does not treat removing an unindexed document as an error', async () => {
389
+ await expect(keywordOnly().remove(CORPUS, 'never-indexed', TENANT)).resolves.toBeUndefined();
390
+ });
391
+ });
392
+ });