@pkcprotocol/pkc-js 0.0.11

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 (843) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +1663 -0
  3. package/dist/browser/challenges.d.ts +1 -0
  4. package/dist/browser/challenges.js +2 -0
  5. package/dist/browser/challenges.js.map +1 -0
  6. package/dist/browser/clients/base-client-manager.d.ts +126 -0
  7. package/dist/browser/clients/base-client-manager.js +673 -0
  8. package/dist/browser/clients/base-client-manager.js.map +1 -0
  9. package/dist/browser/clients/name-resolver-client.d.ts +8 -0
  10. package/dist/browser/clients/name-resolver-client.js +10 -0
  11. package/dist/browser/clients/name-resolver-client.js.map +1 -0
  12. package/dist/browser/clients/pkc-typed-emitter.d.ts +9 -0
  13. package/dist/browser/clients/pkc-typed-emitter.js +52 -0
  14. package/dist/browser/clients/pkc-typed-emitter.js.map +1 -0
  15. package/dist/browser/clients/rpc-client/decode-rpc-response-util.d.ts +8 -0
  16. package/dist/browser/clients/rpc-client/decode-rpc-response-util.js +53 -0
  17. package/dist/browser/clients/rpc-client/decode-rpc-response-util.js.map +1 -0
  18. package/dist/browser/clients/rpc-client/pkc-rpc-client.d.ts +68 -0
  19. package/dist/browser/clients/rpc-client/pkc-rpc-client.js +404 -0
  20. package/dist/browser/clients/rpc-client/pkc-rpc-client.js.map +1 -0
  21. package/dist/browser/clients/rpc-client/rpc-schema-util.d.ts +147 -0
  22. package/dist/browser/clients/rpc-client/rpc-schema-util.js +11 -0
  23. package/dist/browser/clients/rpc-client/rpc-schema-util.js.map +1 -0
  24. package/dist/browser/clients/rpc-client/schema.d.ts +433 -0
  25. package/dist/browser/clients/rpc-client/schema.js +49 -0
  26. package/dist/browser/clients/rpc-client/schema.js.map +1 -0
  27. package/dist/browser/clients/rpc-client/types.d.ts +8 -0
  28. package/dist/browser/clients/rpc-client/types.js +2 -0
  29. package/dist/browser/clients/rpc-client/types.js.map +1 -0
  30. package/dist/browser/community/community-client-manager.d.ts +60 -0
  31. package/dist/browser/community/community-client-manager.js +717 -0
  32. package/dist/browser/community/community-client-manager.js.map +1 -0
  33. package/dist/browser/community/community-clients.d.ts +18 -0
  34. package/dist/browser/community/community-clients.js +12 -0
  35. package/dist/browser/community/community-clients.js.map +1 -0
  36. package/dist/browser/community/community-wire.d.ts +20 -0
  37. package/dist/browser/community/community-wire.js +38 -0
  38. package/dist/browser/community/community-wire.js.map +1 -0
  39. package/dist/browser/community/remote-community.d.ts +110 -0
  40. package/dist/browser/community/remote-community.js +555 -0
  41. package/dist/browser/community/remote-community.js.map +1 -0
  42. package/dist/browser/community/rpc-local-community.d.ts +41 -0
  43. package/dist/browser/community/rpc-local-community.js +289 -0
  44. package/dist/browser/community/rpc-local-community.js.map +1 -0
  45. package/dist/browser/community/rpc-remote-community.d.ts +18 -0
  46. package/dist/browser/community/rpc-remote-community.js +286 -0
  47. package/dist/browser/community/rpc-remote-community.js.map +1 -0
  48. package/dist/browser/community/schema.d.ts +4217 -0
  49. package/dist/browser/community/schema.js +289 -0
  50. package/dist/browser/community/schema.js.map +1 -0
  51. package/dist/browser/community/types.d.ts +135 -0
  52. package/dist/browser/community/types.js +2 -0
  53. package/dist/browser/community/types.js.map +1 -0
  54. package/dist/browser/constants.d.ts +6 -0
  55. package/dist/browser/constants.js +9 -0
  56. package/dist/browser/constants.js.map +1 -0
  57. package/dist/browser/decorator-util.d.ts +1 -0
  58. package/dist/browser/decorator-util.js +35 -0
  59. package/dist/browser/decorator-util.js.map +1 -0
  60. package/dist/browser/errors.d.ts +343 -0
  61. package/dist/browser/errors.js +358 -0
  62. package/dist/browser/errors.js.map +1 -0
  63. package/dist/browser/general-util/limited-set.d.ts +15 -0
  64. package/dist/browser/general-util/limited-set.js +66 -0
  65. package/dist/browser/general-util/limited-set.js.map +1 -0
  66. package/dist/browser/generated-version.d.ts +1 -0
  67. package/dist/browser/generated-version.js +3 -0
  68. package/dist/browser/generated-version.js.map +1 -0
  69. package/dist/browser/generic-state-client.d.ts +6 -0
  70. package/dist/browser/generic-state-client.js +11 -0
  71. package/dist/browser/generic-state-client.js.map +1 -0
  72. package/dist/browser/helia/helia-for-pkc.d.ts +3 -0
  73. package/dist/browser/helia/helia-for-pkc.js +255 -0
  74. package/dist/browser/helia/helia-for-pkc.js.map +1 -0
  75. package/dist/browser/helia/ipns-over-pubsub-with-fetch.d.ts +36 -0
  76. package/dist/browser/helia/ipns-over-pubsub-with-fetch.js +229 -0
  77. package/dist/browser/helia/ipns-over-pubsub-with-fetch.js.map +1 -0
  78. package/dist/browser/helia/libp2pjsClient.d.ts +27 -0
  79. package/dist/browser/helia/libp2pjsClient.js +15 -0
  80. package/dist/browser/helia/libp2pjsClient.js.map +1 -0
  81. package/dist/browser/helia/types.d.ts +19 -0
  82. package/dist/browser/helia/types.js +2 -0
  83. package/dist/browser/helia/types.js.map +1 -0
  84. package/dist/browser/helia/util.d.ts +13 -0
  85. package/dist/browser/helia/util.js +98 -0
  86. package/dist/browser/helia/util.js.map +1 -0
  87. package/dist/browser/index.d.ts +244 -0
  88. package/dist/browser/index.js +36 -0
  89. package/dist/browser/index.js.map +1 -0
  90. package/dist/browser/logger.d.ts +12 -0
  91. package/dist/browser/logger.js +11 -0
  92. package/dist/browser/logger.js.map +1 -0
  93. package/dist/browser/pages/pages-client-manager.d.ts +159 -0
  94. package/dist/browser/pages/pages-client-manager.js +334 -0
  95. package/dist/browser/pages/pages-client-manager.js.map +1 -0
  96. package/dist/browser/pages/pages-clients.d.ts +11 -0
  97. package/dist/browser/pages/pages-clients.js +10 -0
  98. package/dist/browser/pages/pages-clients.js.map +1 -0
  99. package/dist/browser/pages/pages.d.ts +107 -0
  100. package/dist/browser/pages/pages.js +262 -0
  101. package/dist/browser/pages/pages.js.map +1 -0
  102. package/dist/browser/pages/schema-util.d.ts +3 -0
  103. package/dist/browser/pages/schema-util.js +3 -0
  104. package/dist/browser/pages/schema-util.js.map +1 -0
  105. package/dist/browser/pages/schema.d.ts +719 -0
  106. package/dist/browser/pages/schema.js +32 -0
  107. package/dist/browser/pages/schema.js.map +1 -0
  108. package/dist/browser/pages/types.d.ts +44 -0
  109. package/dist/browser/pages/types.js +2 -0
  110. package/dist/browser/pages/types.js.map +1 -0
  111. package/dist/browser/pages/util.d.ts +56 -0
  112. package/dist/browser/pages/util.js +446 -0
  113. package/dist/browser/pages/util.js.map +1 -0
  114. package/dist/browser/pkc/pkc-client-manager.d.ts +44 -0
  115. package/dist/browser/pkc/pkc-client-manager.js +156 -0
  116. package/dist/browser/pkc/pkc-client-manager.js.map +1 -0
  117. package/dist/browser/pkc/pkc-clients.d.ts +11 -0
  118. package/dist/browser/pkc/pkc-clients.js +8 -0
  119. package/dist/browser/pkc/pkc-clients.js.map +1 -0
  120. package/dist/browser/pkc/pkc-with-rpc-client.d.ts +19 -0
  121. package/dist/browser/pkc/pkc-with-rpc-client.js +128 -0
  122. package/dist/browser/pkc/pkc-with-rpc-client.js.map +1 -0
  123. package/dist/browser/pkc/pkc.d.ts +137 -0
  124. package/dist/browser/pkc/pkc.js +888 -0
  125. package/dist/browser/pkc/pkc.js.map +1 -0
  126. package/dist/browser/pkc/tracked-instance-registry-util.d.ts +44 -0
  127. package/dist/browser/pkc/tracked-instance-registry-util.js +106 -0
  128. package/dist/browser/pkc/tracked-instance-registry-util.js.map +1 -0
  129. package/dist/browser/pkc/tracked-instance-registry.d.ts +18 -0
  130. package/dist/browser/pkc/tracked-instance-registry.js +134 -0
  131. package/dist/browser/pkc/tracked-instance-registry.js.map +1 -0
  132. package/dist/browser/pkc-error.d.ts +65 -0
  133. package/dist/browser/pkc-error.js +137 -0
  134. package/dist/browser/pkc-error.js.map +1 -0
  135. package/dist/browser/publications/comment/comment-client-manager.d.ts +86 -0
  136. package/dist/browser/publications/comment/comment-client-manager.js +908 -0
  137. package/dist/browser/publications/comment/comment-client-manager.js.map +1 -0
  138. package/dist/browser/publications/comment/comment-clients.d.ts +19 -0
  139. package/dist/browser/publications/comment/comment-clients.js +12 -0
  140. package/dist/browser/publications/comment/comment-clients.js.map +1 -0
  141. package/dist/browser/publications/comment/comment-util.d.ts +10 -0
  142. package/dist/browser/publications/comment/comment-util.js +202 -0
  143. package/dist/browser/publications/comment/comment-util.js.map +1 -0
  144. package/dist/browser/publications/comment/comment.d.ts +147 -0
  145. package/dist/browser/publications/comment/comment.js +1044 -0
  146. package/dist/browser/publications/comment/comment.js.map +1 -0
  147. package/dist/browser/publications/comment/schema.d.ts +1237 -0
  148. package/dist/browser/publications/comment/schema.js +184 -0
  149. package/dist/browser/publications/comment/schema.js.map +1 -0
  150. package/dist/browser/publications/comment/types.d.ts +100 -0
  151. package/dist/browser/publications/comment/types.js +2 -0
  152. package/dist/browser/publications/comment/types.js.map +1 -0
  153. package/dist/browser/publications/comment-edit/comment-edit.d.ts +41 -0
  154. package/dist/browser/publications/comment-edit/comment-edit.js +63 -0
  155. package/dist/browser/publications/comment-edit/comment-edit.js.map +1 -0
  156. package/dist/browser/publications/comment-edit/schema.d.ts +295 -0
  157. package/dist/browser/publications/comment-edit/schema.js +55 -0
  158. package/dist/browser/publications/comment-edit/schema.js.map +1 -0
  159. package/dist/browser/publications/comment-edit/types.d.ts +25 -0
  160. package/dist/browser/publications/comment-edit/types.js +2 -0
  161. package/dist/browser/publications/comment-edit/types.js.map +1 -0
  162. package/dist/browser/publications/comment-moderation/comment-moderation.d.ts +36 -0
  163. package/dist/browser/publications/comment-moderation/comment-moderation.js +53 -0
  164. package/dist/browser/publications/comment-moderation/comment-moderation.js.map +1 -0
  165. package/dist/browser/publications/comment-moderation/schema.d.ts +315 -0
  166. package/dist/browser/publications/comment-moderation/schema.js +62 -0
  167. package/dist/browser/publications/comment-moderation/schema.js.map +1 -0
  168. package/dist/browser/publications/comment-moderation/types.d.ts +22 -0
  169. package/dist/browser/publications/comment-moderation/types.js +2 -0
  170. package/dist/browser/publications/comment-moderation/types.js.map +1 -0
  171. package/dist/browser/publications/community-edit/community-edit.d.ts +35 -0
  172. package/dist/browser/publications/community-edit/community-edit.js +50 -0
  173. package/dist/browser/publications/community-edit/community-edit.js.map +1 -0
  174. package/dist/browser/publications/community-edit/schema.d.ts +467 -0
  175. package/dist/browser/publications/community-edit/schema.js +36 -0
  176. package/dist/browser/publications/community-edit/schema.js.map +1 -0
  177. package/dist/browser/publications/community-edit/types.d.ts +19 -0
  178. package/dist/browser/publications/community-edit/types.js +2 -0
  179. package/dist/browser/publications/community-edit/types.js.map +1 -0
  180. package/dist/browser/publications/publication-author.d.ts +22 -0
  181. package/dist/browser/publications/publication-author.js +66 -0
  182. package/dist/browser/publications/publication-author.js.map +1 -0
  183. package/dist/browser/publications/publication-client-manager.d.ts +62 -0
  184. package/dist/browser/publications/publication-client-manager.js +257 -0
  185. package/dist/browser/publications/publication-client-manager.js.map +1 -0
  186. package/dist/browser/publications/publication-clients.d.ts +19 -0
  187. package/dist/browser/publications/publication-clients.js +12 -0
  188. package/dist/browser/publications/publication-clients.js.map +1 -0
  189. package/dist/browser/publications/publication-community.d.ts +55 -0
  190. package/dist/browser/publications/publication-community.js +87 -0
  191. package/dist/browser/publications/publication-community.js.map +1 -0
  192. package/dist/browser/publications/publication.d.ts +120 -0
  193. package/dist/browser/publications/publication.js +950 -0
  194. package/dist/browser/publications/publication.js.map +1 -0
  195. package/dist/browser/publications/types.d.ts +26 -0
  196. package/dist/browser/publications/types.js +2 -0
  197. package/dist/browser/publications/types.js.map +1 -0
  198. package/dist/browser/publications/vote/schema.d.ts +150 -0
  199. package/dist/browser/publications/vote/schema.js +44 -0
  200. package/dist/browser/publications/vote/schema.js.map +1 -0
  201. package/dist/browser/publications/vote/types.d.ts +21 -0
  202. package/dist/browser/publications/vote/types.js +2 -0
  203. package/dist/browser/publications/vote/types.js.map +1 -0
  204. package/dist/browser/publications/vote/vote.d.ts +36 -0
  205. package/dist/browser/publications/vote/vote.js +49 -0
  206. package/dist/browser/publications/vote/vote.js.map +1 -0
  207. package/dist/browser/pubsub-messages/schema.d.ts +964 -0
  208. package/dist/browser/pubsub-messages/schema.js +98 -0
  209. package/dist/browser/pubsub-messages/schema.js.map +1 -0
  210. package/dist/browser/pubsub-messages/types.d.ts +81 -0
  211. package/dist/browser/pubsub-messages/types.js +2 -0
  212. package/dist/browser/pubsub-messages/types.js.map +1 -0
  213. package/dist/browser/rpc/src/index.d.ts +483 -0
  214. package/dist/browser/rpc/src/index.js +1267 -0
  215. package/dist/browser/rpc/src/index.js.map +1 -0
  216. package/dist/browser/rpc/src/json-rpc-util.d.ts +1 -0
  217. package/dist/browser/rpc/src/json-rpc-util.js +19 -0
  218. package/dist/browser/rpc/src/json-rpc-util.js.map +1 -0
  219. package/dist/browser/rpc/src/lib/pkc-js/index.d.ts +132 -0
  220. package/dist/browser/rpc/src/lib/pkc-js/index.js +29 -0
  221. package/dist/browser/rpc/src/lib/pkc-js/index.js.map +1 -0
  222. package/dist/browser/rpc/src/lib/pkc-js/pkc-js-mock.d.ts +1 -0
  223. package/dist/browser/rpc/src/lib/pkc-js/pkc-js-mock.js +472 -0
  224. package/dist/browser/rpc/src/lib/pkc-js/pkc-js-mock.js.map +1 -0
  225. package/dist/browser/rpc/src/schema.d.ts +843 -0
  226. package/dist/browser/rpc/src/schema.js +28 -0
  227. package/dist/browser/rpc/src/schema.js.map +1 -0
  228. package/dist/browser/rpc/src/types.d.ts +24 -0
  229. package/dist/browser/rpc/src/types.js +2 -0
  230. package/dist/browser/rpc/src/types.js.map +1 -0
  231. package/dist/browser/rpc/src/utils.d.ts +7 -0
  232. package/dist/browser/rpc/src/utils.js +58 -0
  233. package/dist/browser/rpc/src/utils.js.map +1 -0
  234. package/dist/browser/runtime/browser/community/challenges/index.d.ts +6 -0
  235. package/dist/browser/runtime/browser/community/challenges/index.js +7 -0
  236. package/dist/browser/runtime/browser/community/challenges/index.js.map +1 -0
  237. package/dist/browser/runtime/browser/community/local-community.d.ts +3 -0
  238. package/dist/browser/runtime/browser/community/local-community.js +6 -0
  239. package/dist/browser/runtime/browser/community/local-community.js.map +1 -0
  240. package/dist/browser/runtime/browser/db-handler.d.ts +4 -0
  241. package/dist/browser/runtime/browser/db-handler.js +8 -0
  242. package/dist/browser/runtime/browser/db-handler.js.map +1 -0
  243. package/dist/browser/runtime/browser/localforage-lru.d.ts +15 -0
  244. package/dist/browser/runtime/browser/localforage-lru.js +140 -0
  245. package/dist/browser/runtime/browser/localforage-lru.js.map +1 -0
  246. package/dist/browser/runtime/browser/lru-storage.d.ts +14 -0
  247. package/dist/browser/runtime/browser/lru-storage.js +34 -0
  248. package/dist/browser/runtime/browser/lru-storage.js.map +1 -0
  249. package/dist/browser/runtime/browser/native-functions.d.ts +3 -0
  250. package/dist/browser/runtime/browser/native-functions.js +6 -0
  251. package/dist/browser/runtime/browser/native-functions.js.map +1 -0
  252. package/dist/browser/runtime/browser/polyfill.d.ts +3 -0
  253. package/dist/browser/runtime/browser/polyfill.js +37 -0
  254. package/dist/browser/runtime/browser/polyfill.js.map +1 -0
  255. package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.d.ts +1 -0
  256. package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.js +4 -0
  257. package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  258. package/dist/browser/runtime/browser/storage.d.ts +13 -0
  259. package/dist/browser/runtime/browser/storage.js +37 -0
  260. package/dist/browser/runtime/browser/storage.js.map +1 -0
  261. package/dist/browser/runtime/browser/util.d.ts +14 -0
  262. package/dist/browser/runtime/browser/util.js +61 -0
  263. package/dist/browser/runtime/browser/util.js.map +1 -0
  264. package/dist/browser/runtime/node/address-rewriter-db.d.ts +31 -0
  265. package/dist/browser/runtime/node/address-rewriter-db.js +156 -0
  266. package/dist/browser/runtime/node/address-rewriter-db.js.map +1 -0
  267. package/dist/browser/runtime/node/addresses-rewriter-proxy-server.d.ts +45 -0
  268. package/dist/browser/runtime/node/addresses-rewriter-proxy-server.js +493 -0
  269. package/dist/browser/runtime/node/addresses-rewriter-proxy-server.js.map +1 -0
  270. package/dist/browser/runtime/node/community/challenges/exclude/exclude.d.ts +8 -0
  271. package/dist/browser/runtime/node/community/challenges/exclude/exclude.js +280 -0
  272. package/dist/browser/runtime/node/community/challenges/exclude/exclude.js.map +1 -0
  273. package/dist/browser/runtime/node/community/challenges/exclude/index.d.ts +3 -0
  274. package/dist/browser/runtime/node/community/challenges/exclude/index.js +4 -0
  275. package/dist/browser/runtime/node/community/challenges/exclude/index.js.map +1 -0
  276. package/dist/browser/runtime/node/community/challenges/exclude/rate-limiter.d.ts +5 -0
  277. package/dist/browser/runtime/node/community/challenges/exclude/rate-limiter.js +127 -0
  278. package/dist/browser/runtime/node/community/challenges/exclude/rate-limiter.js.map +1 -0
  279. package/dist/browser/runtime/node/community/challenges/exclude/utils.d.ts +13 -0
  280. package/dist/browser/runtime/node/community/challenges/exclude/utils.js +52 -0
  281. package/dist/browser/runtime/node/community/challenges/exclude/utils.js.map +1 -0
  282. package/dist/browser/runtime/node/community/challenges/index.d.ts +32 -0
  283. package/dist/browser/runtime/node/community/challenges/index.js +307 -0
  284. package/dist/browser/runtime/node/community/challenges/index.js.map +1 -0
  285. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/blacklist.d.ts +5 -0
  286. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/blacklist.js +118 -0
  287. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/blacklist.js.map +1 -0
  288. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/fail.d.ts +5 -0
  289. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/fail.js +26 -0
  290. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/fail.js.map +1 -0
  291. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/publication-match.d.ts +5 -0
  292. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/publication-match.js +135 -0
  293. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/publication-match.js.map +1 -0
  294. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/question.d.ts +5 -0
  295. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/question.js +66 -0
  296. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/question.js.map +1 -0
  297. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/text-math.d.ts +5 -0
  298. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/text-math.js +61 -0
  299. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/text-math.js.map +1 -0
  300. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/whitelist.d.ts +5 -0
  301. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/whitelist.js +118 -0
  302. package/dist/browser/runtime/node/community/challenges/pkc-js-challenges/whitelist.js.map +1 -0
  303. package/dist/browser/runtime/node/community/db-handler-types.d.ts +19 -0
  304. package/dist/browser/runtime/node/community/db-handler-types.js +2 -0
  305. package/dist/browser/runtime/node/community/db-handler-types.js.map +1 -0
  306. package/dist/browser/runtime/node/community/db-handler.d.ts +226 -0
  307. package/dist/browser/runtime/node/community/db-handler.js +2462 -0
  308. package/dist/browser/runtime/node/community/db-handler.js.map +1 -0
  309. package/dist/browser/runtime/node/community/db-row-parser.d.ts +19 -0
  310. package/dist/browser/runtime/node/community/db-row-parser.js +40 -0
  311. package/dist/browser/runtime/node/community/db-row-parser.js.map +1 -0
  312. package/dist/browser/runtime/node/community/keyv-better-sqlite3.d.ts +68 -0
  313. package/dist/browser/runtime/node/community/keyv-better-sqlite3.js +251 -0
  314. package/dist/browser/runtime/node/community/keyv-better-sqlite3.js.map +1 -0
  315. package/dist/browser/runtime/node/community/local-community.d.ts +129 -0
  316. package/dist/browser/runtime/node/community/local-community.js +2978 -0
  317. package/dist/browser/runtime/node/community/local-community.js.map +1 -0
  318. package/dist/browser/runtime/node/community/page-generator.d.ts +433 -0
  319. package/dist/browser/runtime/node/community/page-generator.js +441 -0
  320. package/dist/browser/runtime/node/community/page-generator.js.map +1 -0
  321. package/dist/browser/runtime/node/lru-storage.d.ts +14 -0
  322. package/dist/browser/runtime/node/lru-storage.js +40 -0
  323. package/dist/browser/runtime/node/lru-storage.js.map +1 -0
  324. package/dist/browser/runtime/node/native-functions.d.ts +3 -0
  325. package/dist/browser/runtime/node/native-functions.js +7 -0
  326. package/dist/browser/runtime/node/native-functions.js.map +1 -0
  327. package/dist/browser/runtime/node/polyfill.d.ts +3 -0
  328. package/dist/browser/runtime/node/polyfill.js +20 -0
  329. package/dist/browser/runtime/node/polyfill.js.map +1 -0
  330. package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.d.ts +4 -0
  331. package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.js +240 -0
  332. package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  333. package/dist/browser/runtime/node/sqlite-lru-cache.d.ts +52 -0
  334. package/dist/browser/runtime/node/sqlite-lru-cache.js +127 -0
  335. package/dist/browser/runtime/node/sqlite-lru-cache.js.map +1 -0
  336. package/dist/browser/runtime/node/storage.d.ts +14 -0
  337. package/dist/browser/runtime/node/storage.js +52 -0
  338. package/dist/browser/runtime/node/storage.js.map +1 -0
  339. package/dist/browser/runtime/node/test/helpers/hanging-runner.d.ts +1 -0
  340. package/dist/browser/runtime/node/test/helpers/hanging-runner.js +157 -0
  341. package/dist/browser/runtime/node/test/helpers/hanging-runner.js.map +1 -0
  342. package/dist/browser/runtime/node/test/helpers/run-hanging-node.d.ts +7 -0
  343. package/dist/browser/runtime/node/test/helpers/run-hanging-node.js +68 -0
  344. package/dist/browser/runtime/node/test/helpers/run-hanging-node.js.map +1 -0
  345. package/dist/browser/runtime/node/test/mock-http-router.d.ts +54 -0
  346. package/dist/browser/runtime/node/test/mock-http-router.js +397 -0
  347. package/dist/browser/runtime/node/test/mock-http-router.js.map +1 -0
  348. package/dist/browser/runtime/node/util.d.ts +43 -0
  349. package/dist/browser/runtime/node/util.js +384 -0
  350. package/dist/browser/runtime/node/util.js.map +1 -0
  351. package/dist/browser/schema/schema-util.d.ts +2751 -0
  352. package/dist/browser/schema/schema-util.js +562 -0
  353. package/dist/browser/schema/schema-util.js.map +1 -0
  354. package/dist/browser/schema/schema.d.ts +237 -0
  355. package/dist/browser/schema/schema.js +128 -0
  356. package/dist/browser/schema/schema.js.map +1 -0
  357. package/dist/browser/schema.d.ts +1142 -0
  358. package/dist/browser/schema.js +104 -0
  359. package/dist/browser/schema.js.map +1 -0
  360. package/dist/browser/signer/constants.d.ts +2 -0
  361. package/dist/browser/signer/constants.js +3 -0
  362. package/dist/browser/signer/constants.js.map +1 -0
  363. package/dist/browser/signer/encryption.d.ts +21 -0
  364. package/dist/browser/signer/encryption.js +122 -0
  365. package/dist/browser/signer/encryption.js.map +1 -0
  366. package/dist/browser/signer/index.d.ts +21 -0
  367. package/dist/browser/signer/index.js +49 -0
  368. package/dist/browser/signer/index.js.map +1 -0
  369. package/dist/browser/signer/signatures.d.ts +200 -0
  370. package/dist/browser/signer/signatures.js +594 -0
  371. package/dist/browser/signer/signatures.js.map +1 -0
  372. package/dist/browser/signer/types.d.ts +20 -0
  373. package/dist/browser/signer/types.js +2 -0
  374. package/dist/browser/signer/types.js.map +1 -0
  375. package/dist/browser/signer/util.d.ts +14 -0
  376. package/dist/browser/signer/util.js +156 -0
  377. package/dist/browser/signer/util.js.map +1 -0
  378. package/dist/browser/stats.d.ts +15 -0
  379. package/dist/browser/stats.js +64 -0
  380. package/dist/browser/stats.js.map +1 -0
  381. package/dist/browser/test/mock-ipfs-client.d.ts +34 -0
  382. package/dist/browser/test/mock-ipfs-client.js +208 -0
  383. package/dist/browser/test/mock-ipfs-client.js.map +1 -0
  384. package/dist/browser/test/node/hanging-test/scenarios/comment-publish-pending.scenario.d.ts +8 -0
  385. package/dist/browser/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js +21 -0
  386. package/dist/browser/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js.map +1 -0
  387. package/dist/browser/test/node/hanging-test/scenarios/comment-publish.scenario.d.ts +8 -0
  388. package/dist/browser/test/node/hanging-test/scenarios/comment-publish.scenario.js +19 -0
  389. package/dist/browser/test/node/hanging-test/scenarios/comment-publish.scenario.js.map +1 -0
  390. package/dist/browser/test/node/hanging-test/scenarios/comment-update.scenario.d.ts +8 -0
  391. package/dist/browser/test/node/hanging-test/scenarios/comment-update.scenario.js +22 -0
  392. package/dist/browser/test/node/hanging-test/scenarios/comment-update.scenario.js.map +1 -0
  393. package/dist/browser/test/node/hanging-test/scenarios/community-start.scenario.d.ts +8 -0
  394. package/dist/browser/test/node/hanging-test/scenarios/community-start.scenario.js +23 -0
  395. package/dist/browser/test/node/hanging-test/scenarios/community-start.scenario.js.map +1 -0
  396. package/dist/browser/test/node/hanging-test/scenarios/community-update.scenario.d.ts +8 -0
  397. package/dist/browser/test/node/hanging-test/scenarios/community-update.scenario.js +21 -0
  398. package/dist/browser/test/node/hanging-test/scenarios/community-update.scenario.js.map +1 -0
  399. package/dist/browser/test/node/hanging-test/scenarios/destroy-only.scenario.d.ts +7 -0
  400. package/dist/browser/test/node/hanging-test/scenarios/destroy-only.scenario.js +15 -0
  401. package/dist/browser/test/node/hanging-test/scenarios/destroy-only.scenario.js.map +1 -0
  402. package/dist/browser/test/node/hanging-test/scenarios/hanging-test-util.d.ts +30 -0
  403. package/dist/browser/test/node/hanging-test/scenarios/hanging-test-util.js +46 -0
  404. package/dist/browser/test/node/hanging-test/scenarios/hanging-test-util.js.map +1 -0
  405. package/dist/browser/test/test-util.d.ts +1019 -0
  406. package/dist/browser/test/test-util.js +1886 -0
  407. package/dist/browser/test/test-util.js.map +1 -0
  408. package/dist/browser/types.d.ts +165 -0
  409. package/dist/browser/types.js +2 -0
  410. package/dist/browser/types.js.map +1 -0
  411. package/dist/browser/util/inflight-fetch-manager.d.ts +11 -0
  412. package/dist/browser/util/inflight-fetch-manager.js +41 -0
  413. package/dist/browser/util/inflight-fetch-manager.js.map +1 -0
  414. package/dist/browser/util.d.ts +120 -0
  415. package/dist/browser/util.js +816 -0
  416. package/dist/browser/util.js.map +1 -0
  417. package/dist/browser/version.d.ts +7 -0
  418. package/dist/browser/version.js +12 -0
  419. package/dist/browser/version.js.map +1 -0
  420. package/dist/browser/zod-error-map.d.ts +1 -0
  421. package/dist/browser/zod-error-map.js +10 -0
  422. package/dist/browser/zod-error-map.js.map +1 -0
  423. package/dist/node/challenges.d.ts +1 -0
  424. package/dist/node/challenges.js +2 -0
  425. package/dist/node/challenges.js.map +1 -0
  426. package/dist/node/clients/base-client-manager.d.ts +126 -0
  427. package/dist/node/clients/base-client-manager.js +673 -0
  428. package/dist/node/clients/base-client-manager.js.map +1 -0
  429. package/dist/node/clients/name-resolver-client.d.ts +8 -0
  430. package/dist/node/clients/name-resolver-client.js +10 -0
  431. package/dist/node/clients/name-resolver-client.js.map +1 -0
  432. package/dist/node/clients/pkc-typed-emitter.d.ts +9 -0
  433. package/dist/node/clients/pkc-typed-emitter.js +52 -0
  434. package/dist/node/clients/pkc-typed-emitter.js.map +1 -0
  435. package/dist/node/clients/rpc-client/decode-rpc-response-util.d.ts +8 -0
  436. package/dist/node/clients/rpc-client/decode-rpc-response-util.js +53 -0
  437. package/dist/node/clients/rpc-client/decode-rpc-response-util.js.map +1 -0
  438. package/dist/node/clients/rpc-client/pkc-rpc-client.d.ts +68 -0
  439. package/dist/node/clients/rpc-client/pkc-rpc-client.js +404 -0
  440. package/dist/node/clients/rpc-client/pkc-rpc-client.js.map +1 -0
  441. package/dist/node/clients/rpc-client/rpc-schema-util.d.ts +147 -0
  442. package/dist/node/clients/rpc-client/rpc-schema-util.js +11 -0
  443. package/dist/node/clients/rpc-client/rpc-schema-util.js.map +1 -0
  444. package/dist/node/clients/rpc-client/schema.d.ts +433 -0
  445. package/dist/node/clients/rpc-client/schema.js +49 -0
  446. package/dist/node/clients/rpc-client/schema.js.map +1 -0
  447. package/dist/node/clients/rpc-client/types.d.ts +8 -0
  448. package/dist/node/clients/rpc-client/types.js +2 -0
  449. package/dist/node/clients/rpc-client/types.js.map +1 -0
  450. package/dist/node/community/community-client-manager.d.ts +60 -0
  451. package/dist/node/community/community-client-manager.js +717 -0
  452. package/dist/node/community/community-client-manager.js.map +1 -0
  453. package/dist/node/community/community-clients.d.ts +18 -0
  454. package/dist/node/community/community-clients.js +12 -0
  455. package/dist/node/community/community-clients.js.map +1 -0
  456. package/dist/node/community/community-wire.d.ts +20 -0
  457. package/dist/node/community/community-wire.js +38 -0
  458. package/dist/node/community/community-wire.js.map +1 -0
  459. package/dist/node/community/remote-community.d.ts +110 -0
  460. package/dist/node/community/remote-community.js +555 -0
  461. package/dist/node/community/remote-community.js.map +1 -0
  462. package/dist/node/community/rpc-local-community.d.ts +41 -0
  463. package/dist/node/community/rpc-local-community.js +289 -0
  464. package/dist/node/community/rpc-local-community.js.map +1 -0
  465. package/dist/node/community/rpc-remote-community.d.ts +18 -0
  466. package/dist/node/community/rpc-remote-community.js +286 -0
  467. package/dist/node/community/rpc-remote-community.js.map +1 -0
  468. package/dist/node/community/schema.d.ts +4217 -0
  469. package/dist/node/community/schema.js +289 -0
  470. package/dist/node/community/schema.js.map +1 -0
  471. package/dist/node/community/types.d.ts +135 -0
  472. package/dist/node/community/types.js +2 -0
  473. package/dist/node/community/types.js.map +1 -0
  474. package/dist/node/constants.d.ts +6 -0
  475. package/dist/node/constants.js +9 -0
  476. package/dist/node/constants.js.map +1 -0
  477. package/dist/node/decorator-util.d.ts +1 -0
  478. package/dist/node/decorator-util.js +35 -0
  479. package/dist/node/decorator-util.js.map +1 -0
  480. package/dist/node/errors.d.ts +343 -0
  481. package/dist/node/errors.js +358 -0
  482. package/dist/node/errors.js.map +1 -0
  483. package/dist/node/general-util/limited-set.d.ts +15 -0
  484. package/dist/node/general-util/limited-set.js +66 -0
  485. package/dist/node/general-util/limited-set.js.map +1 -0
  486. package/dist/node/generated-version.d.ts +1 -0
  487. package/dist/node/generated-version.js +3 -0
  488. package/dist/node/generated-version.js.map +1 -0
  489. package/dist/node/generic-state-client.d.ts +6 -0
  490. package/dist/node/generic-state-client.js +11 -0
  491. package/dist/node/generic-state-client.js.map +1 -0
  492. package/dist/node/helia/helia-for-pkc.d.ts +3 -0
  493. package/dist/node/helia/helia-for-pkc.js +255 -0
  494. package/dist/node/helia/helia-for-pkc.js.map +1 -0
  495. package/dist/node/helia/ipns-over-pubsub-with-fetch.d.ts +36 -0
  496. package/dist/node/helia/ipns-over-pubsub-with-fetch.js +229 -0
  497. package/dist/node/helia/ipns-over-pubsub-with-fetch.js.map +1 -0
  498. package/dist/node/helia/libp2pjsClient.d.ts +27 -0
  499. package/dist/node/helia/libp2pjsClient.js +15 -0
  500. package/dist/node/helia/libp2pjsClient.js.map +1 -0
  501. package/dist/node/helia/types.d.ts +19 -0
  502. package/dist/node/helia/types.js +2 -0
  503. package/dist/node/helia/types.js.map +1 -0
  504. package/dist/node/helia/util.d.ts +13 -0
  505. package/dist/node/helia/util.js +98 -0
  506. package/dist/node/helia/util.js.map +1 -0
  507. package/dist/node/index.d.ts +244 -0
  508. package/dist/node/index.js +36 -0
  509. package/dist/node/index.js.map +1 -0
  510. package/dist/node/logger.d.ts +12 -0
  511. package/dist/node/logger.js +11 -0
  512. package/dist/node/logger.js.map +1 -0
  513. package/dist/node/pages/pages-client-manager.d.ts +159 -0
  514. package/dist/node/pages/pages-client-manager.js +334 -0
  515. package/dist/node/pages/pages-client-manager.js.map +1 -0
  516. package/dist/node/pages/pages-clients.d.ts +11 -0
  517. package/dist/node/pages/pages-clients.js +10 -0
  518. package/dist/node/pages/pages-clients.js.map +1 -0
  519. package/dist/node/pages/pages.d.ts +107 -0
  520. package/dist/node/pages/pages.js +262 -0
  521. package/dist/node/pages/pages.js.map +1 -0
  522. package/dist/node/pages/schema-util.d.ts +3 -0
  523. package/dist/node/pages/schema-util.js +3 -0
  524. package/dist/node/pages/schema-util.js.map +1 -0
  525. package/dist/node/pages/schema.d.ts +719 -0
  526. package/dist/node/pages/schema.js +32 -0
  527. package/dist/node/pages/schema.js.map +1 -0
  528. package/dist/node/pages/types.d.ts +44 -0
  529. package/dist/node/pages/types.js +2 -0
  530. package/dist/node/pages/types.js.map +1 -0
  531. package/dist/node/pages/util.d.ts +56 -0
  532. package/dist/node/pages/util.js +446 -0
  533. package/dist/node/pages/util.js.map +1 -0
  534. package/dist/node/pkc/pkc-client-manager.d.ts +44 -0
  535. package/dist/node/pkc/pkc-client-manager.js +156 -0
  536. package/dist/node/pkc/pkc-client-manager.js.map +1 -0
  537. package/dist/node/pkc/pkc-clients.d.ts +11 -0
  538. package/dist/node/pkc/pkc-clients.js +8 -0
  539. package/dist/node/pkc/pkc-clients.js.map +1 -0
  540. package/dist/node/pkc/pkc-with-rpc-client.d.ts +19 -0
  541. package/dist/node/pkc/pkc-with-rpc-client.js +128 -0
  542. package/dist/node/pkc/pkc-with-rpc-client.js.map +1 -0
  543. package/dist/node/pkc/pkc.d.ts +137 -0
  544. package/dist/node/pkc/pkc.js +888 -0
  545. package/dist/node/pkc/pkc.js.map +1 -0
  546. package/dist/node/pkc/tracked-instance-registry-util.d.ts +44 -0
  547. package/dist/node/pkc/tracked-instance-registry-util.js +106 -0
  548. package/dist/node/pkc/tracked-instance-registry-util.js.map +1 -0
  549. package/dist/node/pkc/tracked-instance-registry.d.ts +18 -0
  550. package/dist/node/pkc/tracked-instance-registry.js +134 -0
  551. package/dist/node/pkc/tracked-instance-registry.js.map +1 -0
  552. package/dist/node/pkc-error.d.ts +65 -0
  553. package/dist/node/pkc-error.js +137 -0
  554. package/dist/node/pkc-error.js.map +1 -0
  555. package/dist/node/publications/comment/comment-client-manager.d.ts +86 -0
  556. package/dist/node/publications/comment/comment-client-manager.js +908 -0
  557. package/dist/node/publications/comment/comment-client-manager.js.map +1 -0
  558. package/dist/node/publications/comment/comment-clients.d.ts +19 -0
  559. package/dist/node/publications/comment/comment-clients.js +12 -0
  560. package/dist/node/publications/comment/comment-clients.js.map +1 -0
  561. package/dist/node/publications/comment/comment-util.d.ts +10 -0
  562. package/dist/node/publications/comment/comment-util.js +202 -0
  563. package/dist/node/publications/comment/comment-util.js.map +1 -0
  564. package/dist/node/publications/comment/comment.d.ts +147 -0
  565. package/dist/node/publications/comment/comment.js +1044 -0
  566. package/dist/node/publications/comment/comment.js.map +1 -0
  567. package/dist/node/publications/comment/schema.d.ts +1237 -0
  568. package/dist/node/publications/comment/schema.js +184 -0
  569. package/dist/node/publications/comment/schema.js.map +1 -0
  570. package/dist/node/publications/comment/types.d.ts +100 -0
  571. package/dist/node/publications/comment/types.js +2 -0
  572. package/dist/node/publications/comment/types.js.map +1 -0
  573. package/dist/node/publications/comment-edit/comment-edit.d.ts +41 -0
  574. package/dist/node/publications/comment-edit/comment-edit.js +63 -0
  575. package/dist/node/publications/comment-edit/comment-edit.js.map +1 -0
  576. package/dist/node/publications/comment-edit/schema.d.ts +295 -0
  577. package/dist/node/publications/comment-edit/schema.js +55 -0
  578. package/dist/node/publications/comment-edit/schema.js.map +1 -0
  579. package/dist/node/publications/comment-edit/types.d.ts +25 -0
  580. package/dist/node/publications/comment-edit/types.js +2 -0
  581. package/dist/node/publications/comment-edit/types.js.map +1 -0
  582. package/dist/node/publications/comment-moderation/comment-moderation.d.ts +36 -0
  583. package/dist/node/publications/comment-moderation/comment-moderation.js +53 -0
  584. package/dist/node/publications/comment-moderation/comment-moderation.js.map +1 -0
  585. package/dist/node/publications/comment-moderation/schema.d.ts +315 -0
  586. package/dist/node/publications/comment-moderation/schema.js +62 -0
  587. package/dist/node/publications/comment-moderation/schema.js.map +1 -0
  588. package/dist/node/publications/comment-moderation/types.d.ts +22 -0
  589. package/dist/node/publications/comment-moderation/types.js +2 -0
  590. package/dist/node/publications/comment-moderation/types.js.map +1 -0
  591. package/dist/node/publications/community-edit/community-edit.d.ts +35 -0
  592. package/dist/node/publications/community-edit/community-edit.js +50 -0
  593. package/dist/node/publications/community-edit/community-edit.js.map +1 -0
  594. package/dist/node/publications/community-edit/schema.d.ts +467 -0
  595. package/dist/node/publications/community-edit/schema.js +36 -0
  596. package/dist/node/publications/community-edit/schema.js.map +1 -0
  597. package/dist/node/publications/community-edit/types.d.ts +19 -0
  598. package/dist/node/publications/community-edit/types.js +2 -0
  599. package/dist/node/publications/community-edit/types.js.map +1 -0
  600. package/dist/node/publications/publication-author.d.ts +22 -0
  601. package/dist/node/publications/publication-author.js +66 -0
  602. package/dist/node/publications/publication-author.js.map +1 -0
  603. package/dist/node/publications/publication-client-manager.d.ts +62 -0
  604. package/dist/node/publications/publication-client-manager.js +257 -0
  605. package/dist/node/publications/publication-client-manager.js.map +1 -0
  606. package/dist/node/publications/publication-clients.d.ts +19 -0
  607. package/dist/node/publications/publication-clients.js +12 -0
  608. package/dist/node/publications/publication-clients.js.map +1 -0
  609. package/dist/node/publications/publication-community.d.ts +55 -0
  610. package/dist/node/publications/publication-community.js +87 -0
  611. package/dist/node/publications/publication-community.js.map +1 -0
  612. package/dist/node/publications/publication.d.ts +120 -0
  613. package/dist/node/publications/publication.js +950 -0
  614. package/dist/node/publications/publication.js.map +1 -0
  615. package/dist/node/publications/types.d.ts +26 -0
  616. package/dist/node/publications/types.js +2 -0
  617. package/dist/node/publications/types.js.map +1 -0
  618. package/dist/node/publications/vote/schema.d.ts +150 -0
  619. package/dist/node/publications/vote/schema.js +44 -0
  620. package/dist/node/publications/vote/schema.js.map +1 -0
  621. package/dist/node/publications/vote/types.d.ts +21 -0
  622. package/dist/node/publications/vote/types.js +2 -0
  623. package/dist/node/publications/vote/types.js.map +1 -0
  624. package/dist/node/publications/vote/vote.d.ts +36 -0
  625. package/dist/node/publications/vote/vote.js +49 -0
  626. package/dist/node/publications/vote/vote.js.map +1 -0
  627. package/dist/node/pubsub-messages/schema.d.ts +964 -0
  628. package/dist/node/pubsub-messages/schema.js +98 -0
  629. package/dist/node/pubsub-messages/schema.js.map +1 -0
  630. package/dist/node/pubsub-messages/types.d.ts +81 -0
  631. package/dist/node/pubsub-messages/types.js +2 -0
  632. package/dist/node/pubsub-messages/types.js.map +1 -0
  633. package/dist/node/rpc/src/index.d.ts +483 -0
  634. package/dist/node/rpc/src/index.js +1267 -0
  635. package/dist/node/rpc/src/index.js.map +1 -0
  636. package/dist/node/rpc/src/json-rpc-util.d.ts +1 -0
  637. package/dist/node/rpc/src/json-rpc-util.js +19 -0
  638. package/dist/node/rpc/src/json-rpc-util.js.map +1 -0
  639. package/dist/node/rpc/src/lib/pkc-js/index.d.ts +132 -0
  640. package/dist/node/rpc/src/lib/pkc-js/index.js +29 -0
  641. package/dist/node/rpc/src/lib/pkc-js/index.js.map +1 -0
  642. package/dist/node/rpc/src/lib/pkc-js/pkc-js-mock.d.ts +1 -0
  643. package/dist/node/rpc/src/lib/pkc-js/pkc-js-mock.js +472 -0
  644. package/dist/node/rpc/src/lib/pkc-js/pkc-js-mock.js.map +1 -0
  645. package/dist/node/rpc/src/schema.d.ts +843 -0
  646. package/dist/node/rpc/src/schema.js +28 -0
  647. package/dist/node/rpc/src/schema.js.map +1 -0
  648. package/dist/node/rpc/src/types.d.ts +24 -0
  649. package/dist/node/rpc/src/types.js +2 -0
  650. package/dist/node/rpc/src/types.js.map +1 -0
  651. package/dist/node/rpc/src/utils.d.ts +7 -0
  652. package/dist/node/rpc/src/utils.js +58 -0
  653. package/dist/node/rpc/src/utils.js.map +1 -0
  654. package/dist/node/runtime/browser/community/challenges/index.d.ts +6 -0
  655. package/dist/node/runtime/browser/community/challenges/index.js +7 -0
  656. package/dist/node/runtime/browser/community/challenges/index.js.map +1 -0
  657. package/dist/node/runtime/browser/community/local-community.d.ts +3 -0
  658. package/dist/node/runtime/browser/community/local-community.js +6 -0
  659. package/dist/node/runtime/browser/community/local-community.js.map +1 -0
  660. package/dist/node/runtime/browser/db-handler.d.ts +4 -0
  661. package/dist/node/runtime/browser/db-handler.js +8 -0
  662. package/dist/node/runtime/browser/db-handler.js.map +1 -0
  663. package/dist/node/runtime/browser/localforage-lru.d.ts +15 -0
  664. package/dist/node/runtime/browser/localforage-lru.js +140 -0
  665. package/dist/node/runtime/browser/localforage-lru.js.map +1 -0
  666. package/dist/node/runtime/browser/lru-storage.d.ts +14 -0
  667. package/dist/node/runtime/browser/lru-storage.js +34 -0
  668. package/dist/node/runtime/browser/lru-storage.js.map +1 -0
  669. package/dist/node/runtime/browser/native-functions.d.ts +3 -0
  670. package/dist/node/runtime/browser/native-functions.js +6 -0
  671. package/dist/node/runtime/browser/native-functions.js.map +1 -0
  672. package/dist/node/runtime/browser/polyfill.d.ts +3 -0
  673. package/dist/node/runtime/browser/polyfill.js +37 -0
  674. package/dist/node/runtime/browser/polyfill.js.map +1 -0
  675. package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.d.ts +1 -0
  676. package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.js +4 -0
  677. package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  678. package/dist/node/runtime/browser/storage.d.ts +13 -0
  679. package/dist/node/runtime/browser/storage.js +37 -0
  680. package/dist/node/runtime/browser/storage.js.map +1 -0
  681. package/dist/node/runtime/browser/util.d.ts +14 -0
  682. package/dist/node/runtime/browser/util.js +61 -0
  683. package/dist/node/runtime/browser/util.js.map +1 -0
  684. package/dist/node/runtime/node/address-rewriter-db.d.ts +31 -0
  685. package/dist/node/runtime/node/address-rewriter-db.js +156 -0
  686. package/dist/node/runtime/node/address-rewriter-db.js.map +1 -0
  687. package/dist/node/runtime/node/addresses-rewriter-proxy-server.d.ts +45 -0
  688. package/dist/node/runtime/node/addresses-rewriter-proxy-server.js +493 -0
  689. package/dist/node/runtime/node/addresses-rewriter-proxy-server.js.map +1 -0
  690. package/dist/node/runtime/node/community/challenges/exclude/exclude.d.ts +8 -0
  691. package/dist/node/runtime/node/community/challenges/exclude/exclude.js +280 -0
  692. package/dist/node/runtime/node/community/challenges/exclude/exclude.js.map +1 -0
  693. package/dist/node/runtime/node/community/challenges/exclude/index.d.ts +3 -0
  694. package/dist/node/runtime/node/community/challenges/exclude/index.js +4 -0
  695. package/dist/node/runtime/node/community/challenges/exclude/index.js.map +1 -0
  696. package/dist/node/runtime/node/community/challenges/exclude/rate-limiter.d.ts +5 -0
  697. package/dist/node/runtime/node/community/challenges/exclude/rate-limiter.js +127 -0
  698. package/dist/node/runtime/node/community/challenges/exclude/rate-limiter.js.map +1 -0
  699. package/dist/node/runtime/node/community/challenges/exclude/utils.d.ts +13 -0
  700. package/dist/node/runtime/node/community/challenges/exclude/utils.js +52 -0
  701. package/dist/node/runtime/node/community/challenges/exclude/utils.js.map +1 -0
  702. package/dist/node/runtime/node/community/challenges/index.d.ts +32 -0
  703. package/dist/node/runtime/node/community/challenges/index.js +307 -0
  704. package/dist/node/runtime/node/community/challenges/index.js.map +1 -0
  705. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/blacklist.d.ts +5 -0
  706. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/blacklist.js +118 -0
  707. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/blacklist.js.map +1 -0
  708. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/fail.d.ts +5 -0
  709. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/fail.js +26 -0
  710. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/fail.js.map +1 -0
  711. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/publication-match.d.ts +5 -0
  712. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/publication-match.js +135 -0
  713. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/publication-match.js.map +1 -0
  714. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/question.d.ts +5 -0
  715. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/question.js +66 -0
  716. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/question.js.map +1 -0
  717. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/text-math.d.ts +5 -0
  718. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/text-math.js +61 -0
  719. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/text-math.js.map +1 -0
  720. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/whitelist.d.ts +5 -0
  721. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/whitelist.js +118 -0
  722. package/dist/node/runtime/node/community/challenges/pkc-js-challenges/whitelist.js.map +1 -0
  723. package/dist/node/runtime/node/community/db-handler-types.d.ts +19 -0
  724. package/dist/node/runtime/node/community/db-handler-types.js +2 -0
  725. package/dist/node/runtime/node/community/db-handler-types.js.map +1 -0
  726. package/dist/node/runtime/node/community/db-handler.d.ts +226 -0
  727. package/dist/node/runtime/node/community/db-handler.js +2462 -0
  728. package/dist/node/runtime/node/community/db-handler.js.map +1 -0
  729. package/dist/node/runtime/node/community/db-row-parser.d.ts +19 -0
  730. package/dist/node/runtime/node/community/db-row-parser.js +40 -0
  731. package/dist/node/runtime/node/community/db-row-parser.js.map +1 -0
  732. package/dist/node/runtime/node/community/keyv-better-sqlite3.d.ts +68 -0
  733. package/dist/node/runtime/node/community/keyv-better-sqlite3.js +251 -0
  734. package/dist/node/runtime/node/community/keyv-better-sqlite3.js.map +1 -0
  735. package/dist/node/runtime/node/community/local-community.d.ts +129 -0
  736. package/dist/node/runtime/node/community/local-community.js +2978 -0
  737. package/dist/node/runtime/node/community/local-community.js.map +1 -0
  738. package/dist/node/runtime/node/community/page-generator.d.ts +433 -0
  739. package/dist/node/runtime/node/community/page-generator.js +441 -0
  740. package/dist/node/runtime/node/community/page-generator.js.map +1 -0
  741. package/dist/node/runtime/node/lru-storage.d.ts +14 -0
  742. package/dist/node/runtime/node/lru-storage.js +40 -0
  743. package/dist/node/runtime/node/lru-storage.js.map +1 -0
  744. package/dist/node/runtime/node/native-functions.d.ts +3 -0
  745. package/dist/node/runtime/node/native-functions.js +7 -0
  746. package/dist/node/runtime/node/native-functions.js.map +1 -0
  747. package/dist/node/runtime/node/polyfill.d.ts +3 -0
  748. package/dist/node/runtime/node/polyfill.js +20 -0
  749. package/dist/node/runtime/node/polyfill.js.map +1 -0
  750. package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.d.ts +4 -0
  751. package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.js +240 -0
  752. package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.js.map +1 -0
  753. package/dist/node/runtime/node/sqlite-lru-cache.d.ts +52 -0
  754. package/dist/node/runtime/node/sqlite-lru-cache.js +127 -0
  755. package/dist/node/runtime/node/sqlite-lru-cache.js.map +1 -0
  756. package/dist/node/runtime/node/storage.d.ts +14 -0
  757. package/dist/node/runtime/node/storage.js +52 -0
  758. package/dist/node/runtime/node/storage.js.map +1 -0
  759. package/dist/node/runtime/node/test/helpers/hanging-runner.d.ts +1 -0
  760. package/dist/node/runtime/node/test/helpers/hanging-runner.js +157 -0
  761. package/dist/node/runtime/node/test/helpers/hanging-runner.js.map +1 -0
  762. package/dist/node/runtime/node/test/helpers/run-hanging-node.d.ts +7 -0
  763. package/dist/node/runtime/node/test/helpers/run-hanging-node.js +68 -0
  764. package/dist/node/runtime/node/test/helpers/run-hanging-node.js.map +1 -0
  765. package/dist/node/runtime/node/test/mock-http-router.d.ts +54 -0
  766. package/dist/node/runtime/node/test/mock-http-router.js +397 -0
  767. package/dist/node/runtime/node/test/mock-http-router.js.map +1 -0
  768. package/dist/node/runtime/node/util.d.ts +43 -0
  769. package/dist/node/runtime/node/util.js +384 -0
  770. package/dist/node/runtime/node/util.js.map +1 -0
  771. package/dist/node/schema/schema-util.d.ts +2751 -0
  772. package/dist/node/schema/schema-util.js +562 -0
  773. package/dist/node/schema/schema-util.js.map +1 -0
  774. package/dist/node/schema/schema.d.ts +237 -0
  775. package/dist/node/schema/schema.js +128 -0
  776. package/dist/node/schema/schema.js.map +1 -0
  777. package/dist/node/schema.d.ts +1142 -0
  778. package/dist/node/schema.js +104 -0
  779. package/dist/node/schema.js.map +1 -0
  780. package/dist/node/signer/constants.d.ts +2 -0
  781. package/dist/node/signer/constants.js +3 -0
  782. package/dist/node/signer/constants.js.map +1 -0
  783. package/dist/node/signer/encryption.d.ts +21 -0
  784. package/dist/node/signer/encryption.js +122 -0
  785. package/dist/node/signer/encryption.js.map +1 -0
  786. package/dist/node/signer/index.d.ts +21 -0
  787. package/dist/node/signer/index.js +49 -0
  788. package/dist/node/signer/index.js.map +1 -0
  789. package/dist/node/signer/signatures.d.ts +200 -0
  790. package/dist/node/signer/signatures.js +594 -0
  791. package/dist/node/signer/signatures.js.map +1 -0
  792. package/dist/node/signer/types.d.ts +20 -0
  793. package/dist/node/signer/types.js +2 -0
  794. package/dist/node/signer/types.js.map +1 -0
  795. package/dist/node/signer/util.d.ts +14 -0
  796. package/dist/node/signer/util.js +156 -0
  797. package/dist/node/signer/util.js.map +1 -0
  798. package/dist/node/stats.d.ts +15 -0
  799. package/dist/node/stats.js +64 -0
  800. package/dist/node/stats.js.map +1 -0
  801. package/dist/node/test/mock-ipfs-client.d.ts +34 -0
  802. package/dist/node/test/mock-ipfs-client.js +208 -0
  803. package/dist/node/test/mock-ipfs-client.js.map +1 -0
  804. package/dist/node/test/node/hanging-test/scenarios/comment-publish-pending.scenario.d.ts +8 -0
  805. package/dist/node/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js +21 -0
  806. package/dist/node/test/node/hanging-test/scenarios/comment-publish-pending.scenario.js.map +1 -0
  807. package/dist/node/test/node/hanging-test/scenarios/comment-publish.scenario.d.ts +8 -0
  808. package/dist/node/test/node/hanging-test/scenarios/comment-publish.scenario.js +19 -0
  809. package/dist/node/test/node/hanging-test/scenarios/comment-publish.scenario.js.map +1 -0
  810. package/dist/node/test/node/hanging-test/scenarios/comment-update.scenario.d.ts +8 -0
  811. package/dist/node/test/node/hanging-test/scenarios/comment-update.scenario.js +22 -0
  812. package/dist/node/test/node/hanging-test/scenarios/comment-update.scenario.js.map +1 -0
  813. package/dist/node/test/node/hanging-test/scenarios/community-start.scenario.d.ts +8 -0
  814. package/dist/node/test/node/hanging-test/scenarios/community-start.scenario.js +23 -0
  815. package/dist/node/test/node/hanging-test/scenarios/community-start.scenario.js.map +1 -0
  816. package/dist/node/test/node/hanging-test/scenarios/community-update.scenario.d.ts +8 -0
  817. package/dist/node/test/node/hanging-test/scenarios/community-update.scenario.js +21 -0
  818. package/dist/node/test/node/hanging-test/scenarios/community-update.scenario.js.map +1 -0
  819. package/dist/node/test/node/hanging-test/scenarios/destroy-only.scenario.d.ts +7 -0
  820. package/dist/node/test/node/hanging-test/scenarios/destroy-only.scenario.js +15 -0
  821. package/dist/node/test/node/hanging-test/scenarios/destroy-only.scenario.js.map +1 -0
  822. package/dist/node/test/node/hanging-test/scenarios/hanging-test-util.d.ts +30 -0
  823. package/dist/node/test/node/hanging-test/scenarios/hanging-test-util.js +46 -0
  824. package/dist/node/test/node/hanging-test/scenarios/hanging-test-util.js.map +1 -0
  825. package/dist/node/test/test-util.d.ts +1019 -0
  826. package/dist/node/test/test-util.js +1886 -0
  827. package/dist/node/test/test-util.js.map +1 -0
  828. package/dist/node/types.d.ts +165 -0
  829. package/dist/node/types.js +2 -0
  830. package/dist/node/types.js.map +1 -0
  831. package/dist/node/util/inflight-fetch-manager.d.ts +11 -0
  832. package/dist/node/util/inflight-fetch-manager.js +41 -0
  833. package/dist/node/util/inflight-fetch-manager.js.map +1 -0
  834. package/dist/node/util.d.ts +120 -0
  835. package/dist/node/util.js +816 -0
  836. package/dist/node/util.js.map +1 -0
  837. package/dist/node/version.d.ts +7 -0
  838. package/dist/node/version.js +12 -0
  839. package/dist/node/version.js.map +1 -0
  840. package/dist/node/zod-error-map.d.ts +1 -0
  841. package/dist/node/zod-error-map.js +10 -0
  842. package/dist/node/zod-error-map.js.map +1 -0
  843. package/package.json +212 -0
@@ -0,0 +1,2462 @@
1
+ import { getEquivalentCommunityAddresses, hideClassPrivateProps, isStringDomain, removeNullUndefinedValues, timestamp } from "../../../util.js";
2
+ import { PKCError } from "../../../pkc-error.js";
3
+ import path from "path";
4
+ import assert from "assert";
5
+ import fs from "fs";
6
+ import os from "os";
7
+ import Logger from "../../../logger.js";
8
+ import { deleteOldCommunityInWindows, getDefaultCommunityDbConfig } from "../util.js";
9
+ import env from "../../../version.js";
10
+ import Database from "better-sqlite3";
11
+ import { sha256 } from "js-sha256";
12
+ import lockfile from "@pkc/proper-lock-file";
13
+ import { getPKCAddressFromPublicKey, getPKCAddressFromPublicKeySync } from "../../../signer/util.js";
14
+ import * as remeda from "remeda";
15
+ import { CommentIpfsSchema, CommentUpdateSchema } from "../../../publications/comment/schema.js";
16
+ import { verifyCommentEdit, verifyCommentIpfs } from "../../../signer/signatures.js";
17
+ import { getCommunityChallengeFromCommunityChallengeSettings, pkcJsChallenges } from "./challenges/index.js";
18
+ import KeyvBetterSqlite3 from "./keyv-better-sqlite3.js";
19
+ import { STORAGE_KEYS } from "../../../constants.js";
20
+ import { CommentEditPubsubMessagePublicationSchema } from "../../../publications/comment-edit/schema.js";
21
+ import { TIMEFRAMES_TO_SECONDS } from "../../../pages/util.js";
22
+ import { parseCommentEditsRow, parseCommentUpdateRow, parseCommentsTableRow, parsePrefixedComment, parseVoteRow } from "./db-row-parser.js";
23
+ import { ZodError } from "zod";
24
+ import { messages } from "../../../errors.js";
25
+ import { getAuthorDomainFromWire } from "../../../publications/publication-author.js";
26
+ const TABLES = Object.freeze({
27
+ COMMENTS: "comments",
28
+ COMMENT_UPDATES: "commentUpdates",
29
+ VOTES: "votes",
30
+ COMMENT_MODERATIONS: "commentModerations",
31
+ COMMENT_EDITS: "commentEdits",
32
+ PSEUDONYMITY_ALIASES: "pseudonymityAliases"
33
+ });
34
+ export class DbHandler {
35
+ constructor(community) {
36
+ this._community = community;
37
+ this._transactionDepth = 0;
38
+ this._createdTables = false;
39
+ hideClassPrivateProps(this);
40
+ }
41
+ _parsePrefixedComment(row) {
42
+ const parsed = parsePrefixedComment(row);
43
+ const comment = removeNullUndefinedValues(this._spreadExtraProps(parsed.comment));
44
+ const commentUpdate = removeNullUndefinedValues(this._spreadExtraProps(parsed.commentUpdate));
45
+ return {
46
+ comment,
47
+ commentUpdate,
48
+ extras: parsed.extras
49
+ };
50
+ }
51
+ _parseCommentsTableRow(row) {
52
+ const parsed = parseCommentsTableRow(row);
53
+ return removeNullUndefinedValues(parsed);
54
+ }
55
+ _parseCommentUpdatesRow(row) {
56
+ const parsed = parseCommentUpdateRow(row);
57
+ return removeNullUndefinedValues(parsed);
58
+ }
59
+ _parseCommentEditsRow(row) {
60
+ const parsedRow = parseCommentEditsRow(row);
61
+ const parsed = removeNullUndefinedValues(parsedRow);
62
+ if (typeof parsed.id === "string") {
63
+ const numericId = Number(parsed.id);
64
+ if (!Number.isNaN(numericId))
65
+ parsed.id = numericId;
66
+ }
67
+ return parsed;
68
+ }
69
+ _parseVoteRow(row) {
70
+ const parsed = parseVoteRow(row);
71
+ return removeNullUndefinedValues(parsed);
72
+ }
73
+ async initDbConfigIfNeeded() {
74
+ if (!this._dbConfig)
75
+ this._dbConfig = await getDefaultCommunityDbConfig(this._community.address, this._community._pkc);
76
+ }
77
+ toJSON() {
78
+ return undefined;
79
+ }
80
+ async initDbIfNeeded(dbConfigOptions) {
81
+ const log = Logger("pkc-js:local-community:db-handler:initDbIfNeeded");
82
+ assert(typeof this._community.address === "string" && this._community.address.length > 0, `DbHandler needs to be an instantiated with a Community that has a valid address, (${this._community.address}) was provided`);
83
+ await this.initDbConfigIfNeeded();
84
+ const dbFilePath = this._dbConfig.filename;
85
+ if (!this._db || !this._db.open) {
86
+ this._db = new Database(dbFilePath, { ...this._dbConfig, ...dbConfigOptions });
87
+ try {
88
+ this._db.pragma("journal_mode = WAL");
89
+ }
90
+ catch (e) {
91
+ log(`Could not set WAL journal mode for ${dbFilePath}`, e);
92
+ throw e;
93
+ }
94
+ log("initialized a new connection to db", dbFilePath);
95
+ }
96
+ if (!this._keyv)
97
+ this._keyv = new KeyvBetterSqlite3(this._db);
98
+ }
99
+ async createOrMigrateTablesIfNeeded() {
100
+ const log = Logger("pkc-js:local-community:db-handler:createOrMigrateTablesIfNeeded");
101
+ if (this._createdTables)
102
+ return;
103
+ try {
104
+ await this._createOrMigrateTablesIfNeeded();
105
+ }
106
+ catch (e) {
107
+ await this.initDbIfNeeded();
108
+ log.error(`Community (${this._community.address}) failed to create/migrate tables. Current db version (${this.getDbVersion()}), latest db version (${env.DB_VERSION}). Error`, e);
109
+ await this.destoryConnection();
110
+ throw e;
111
+ }
112
+ hideClassPrivateProps(this);
113
+ }
114
+ getDbConfig() {
115
+ return this._dbConfig;
116
+ }
117
+ keyvGet(key) {
118
+ try {
119
+ const res = this._keyv.get(key);
120
+ return res;
121
+ }
122
+ catch (e) {
123
+ e.details = { ...e.details, key };
124
+ throw e;
125
+ }
126
+ }
127
+ keyvSet(key, value, ttl) {
128
+ return this._keyv.set(key, value, ttl);
129
+ }
130
+ keyvDelete(key) {
131
+ return this._keyv.delete(key);
132
+ }
133
+ keyvHas(key) {
134
+ return this._keyv.has(key);
135
+ }
136
+ destoryConnection() {
137
+ const log = Logger("pkc-js:local-community:dbHandler:destroyConnection");
138
+ if (this._db && this._db.open) {
139
+ this._db.exec("PRAGMA checkpoint"); // write all wal to disk
140
+ this._db.close();
141
+ }
142
+ if (this._keyv)
143
+ this._keyv.disconnect();
144
+ //@ts-expect-error
145
+ this._db = this._keyv = undefined;
146
+ this._transactionDepth = 0;
147
+ log("Destroyed DB connection to community", this._community.address, "successfully");
148
+ }
149
+ createTransaction() {
150
+ if (this._transactionDepth === 0) {
151
+ this._db.exec("BEGIN");
152
+ }
153
+ this._transactionDepth++;
154
+ }
155
+ commitTransaction() {
156
+ if (this._transactionDepth > 0) {
157
+ this._transactionDepth--;
158
+ if (this._transactionDepth === 0) {
159
+ this._db.exec("COMMIT");
160
+ }
161
+ }
162
+ }
163
+ rollbackTransaction() {
164
+ const log = Logger("pkc-js:local-community:db-handler:rollbackTransaction");
165
+ if (this._transactionDepth > 0) {
166
+ if (this._transactionDepth === 1) {
167
+ try {
168
+ this._db.exec("ROLLBACK");
169
+ }
170
+ catch (e) {
171
+ log.error(`Failed to rollback transaction due to error`, e);
172
+ }
173
+ }
174
+ this._transactionDepth--;
175
+ }
176
+ else if (this._db && this._db.open && this._db.inTransaction) {
177
+ log(`Transaction depth was 0, but DB was in transaction. Attempting rollback.`);
178
+ try {
179
+ this._db.exec("ROLLBACK");
180
+ }
181
+ catch (e) {
182
+ log.error(`Failed to rollback transaction (fallback) due to error`, e);
183
+ }
184
+ }
185
+ if (this._transactionDepth < 0)
186
+ this._transactionDepth = 0;
187
+ log.trace(`Rolledback transaction, this._transactionDepth = ${this._transactionDepth}`);
188
+ }
189
+ async rollbackAllTransactions() {
190
+ const log = Logger("pkc-js:local-community:db-handler:rollbackAllTransactions");
191
+ let initialDepth = this._transactionDepth;
192
+ while (this._transactionDepth > 0) {
193
+ this.rollbackTransaction();
194
+ }
195
+ if (initialDepth > 0) {
196
+ log.trace(`Rolled back all transactions. Initial depth was ${initialDepth}, now ${this._transactionDepth}.`);
197
+ }
198
+ }
199
+ _createCommentsTable(tableName) {
200
+ this._db.exec(`
201
+ CREATE TABLE IF NOT EXISTS ${tableName} (
202
+ cid TEXT NOT NULL PRIMARY KEY UNIQUE,
203
+ authorSignerAddress TEXT NOT NULL,
204
+ author TEXT NULLABLE, -- JSON
205
+ link TEXT NULLABLE,
206
+ linkWidth INTEGER NULLABLE,
207
+ linkHeight INTEGER NULLABLE,
208
+ thumbnailUrl TEXT NULLABLE,
209
+ thumbnailUrlWidth INTEGER NULLABLE,
210
+ thumbnailUrlHeight INTEGER NULLABLE,
211
+ parentCid TEXT NULLABLE REFERENCES ${TABLES.COMMENTS}(cid),
212
+ postCid TEXT NOT NULL REFERENCES ${TABLES.COMMENTS}(cid),
213
+ previousCid TEXT NULLABLE,
214
+ communityPublicKey TEXT,
215
+ communityName TEXT,
216
+ content TEXT NULLABLE,
217
+ timestamp INTEGER NOT NULL,
218
+ signature TEXT NOT NULL, -- JSON
219
+ originalCommentSignatureEncoded TEXT NULLABLE, -- original publication signature before local anonymization
220
+ title TEXT NULLABLE,
221
+ depth INTEGER NOT NULL,
222
+ linkHtmlTagName TEXT NULLABLE,
223
+ flairs TEXT NULLABLE, -- JSON
224
+ spoiler INTEGER NULLABLE, -- BOOLEAN (0/1)
225
+ pendingApproval INTEGER NULLABLE, -- BOOLEAN (0/1)
226
+ number INTEGER NULLABLE,
227
+ postNumber INTEGER NULLABLE,
228
+ nsfw INTEGER NULLABLE, -- BOOLEAN (0/1)
229
+ pseudonymityMode TEXT NULLABLE,
230
+ quotedCids TEXT NULLABLE, -- JSON array
231
+ extraProps TEXT NULLABLE, -- JSON
232
+ protocolVersion TEXT NOT NULL,
233
+ insertedAt INTEGER NOT NULL
234
+ )
235
+ `);
236
+ }
237
+ _createCommentUpdatesTable(tableName) {
238
+ this._db.exec(`
239
+ CREATE TABLE IF NOT EXISTS ${tableName} (
240
+ cid TEXT NOT NULL PRIMARY KEY UNIQUE REFERENCES ${TABLES.COMMENTS}(cid),
241
+ edit TEXT NULLABLE, -- JSON
242
+ upvoteCount INTEGER NOT NULL,
243
+ downvoteCount INTEGER NOT NULL,
244
+ replyCount INTEGER NOT NULL,
245
+ childCount INTEGER NOT NULL,
246
+ number INTEGER NULLABLE,
247
+ postNumber INTEGER NULLABLE,
248
+ flairs TEXT NULLABLE, -- JSON
249
+ spoiler INTEGER NULLABLE, -- BOOLEAN (0/1)
250
+ nsfw INTEGER NULLABLE, -- BOOLEAN (0/1)
251
+ pinned INTEGER NULLABLE, -- BOOLEAN (0/1)
252
+ locked INTEGER NULLABLE, -- BOOLEAN (0/1)
253
+ archived INTEGER NULLABLE, -- BOOLEAN (0/1)
254
+ removed INTEGER NULLABLE, -- BOOLEAN (0/1)
255
+ approved INTEGER NULLABLE, -- BOOLEAN (0/1)
256
+ reason TEXT NULLABLE,
257
+ updatedAt INTEGER NOT NULL CHECK(updatedAt > 0),
258
+ protocolVersion TEXT NOT NULL,
259
+ signature TEXT NOT NULL, -- JSON
260
+ author TEXT NULLABLE, -- JSON
261
+ replies TEXT NULLABLE, -- JSON
262
+ lastChildCid TEXT NULLABLE,
263
+ lastReplyTimestamp INTEGER NULLABLE,
264
+ postUpdatesBucket INTEGER NULLABLE,
265
+ publishedToPostUpdatesMFS INTEGER NOT NULL, -- BOOLEAN (0/1)
266
+ insertedAt INTEGER NOT NULL
267
+ )
268
+ `);
269
+ }
270
+ _createVotesTable(tableName) {
271
+ this._db.exec(`
272
+ CREATE TABLE IF NOT EXISTS ${tableName} (
273
+ commentCid TEXT NOT NULL REFERENCES ${TABLES.COMMENTS}(cid),
274
+ authorSignerAddress TEXT NOT NULL,
275
+ timestamp INTEGER CHECK(timestamp > 0) NOT NULL,
276
+ vote INTEGER CHECK(vote BETWEEN -1 AND 1) NOT NULL,
277
+ protocolVersion TEXT NOT NULL,
278
+ insertedAt INTEGER NOT NULL,
279
+ extraProps TEXT NULLABLE, -- JSON
280
+ PRIMARY KEY (commentCid, authorSignerAddress)
281
+ )
282
+ `);
283
+ }
284
+ _createCommentEditsTable(tableName) {
285
+ this._db.exec(`
286
+ CREATE TABLE IF NOT EXISTS ${tableName} (
287
+ commentCid TEXT NOT NULL REFERENCES ${TABLES.COMMENTS}(cid),
288
+ authorSignerAddress TEXT NOT NULL,
289
+ author TEXT NULLABLE, -- JSON
290
+ signature TEXT NOT NULL, -- JSON
291
+ protocolVersion TEXT NOT NULL,
292
+ communityPublicKey TEXT,
293
+ communityName TEXT,
294
+ timestamp INTEGER CHECK(timestamp > 0) NOT NULL,
295
+ content TEXT NULLABLE,
296
+ reason TEXT NULLABLE,
297
+ deleted INTEGER NULLABLE, -- BOOLEAN (0/1)
298
+ flairs TEXT NULLABLE, -- JSON
299
+ spoiler INTEGER NULLABLE, -- BOOLEAN (0/1)
300
+ nsfw INTEGER NULLABLE, -- BOOLEAN (0/1)
301
+ isAuthorEdit INTEGER NOT NULL, -- BOOLEAN (0/1)
302
+ insertedAt INTEGER NOT NULL,
303
+ extraProps TEXT NULLABLE -- JSON
304
+ )
305
+ `);
306
+ }
307
+ _createCommentModerationsTable(tableName) {
308
+ this._db.exec(`
309
+ CREATE TABLE IF NOT EXISTS ${tableName} (
310
+ commentCid TEXT NOT NULL,
311
+ author TEXT NULLABLE, -- JSON
312
+ signature TEXT NOT NULL, -- JSON
313
+ modSignerAddress TEXT NOT NULL,
314
+ protocolVersion TEXT NOT NULL,
315
+ communityPublicKey TEXT,
316
+ communityName TEXT,
317
+ timestamp INTEGER CHECK(timestamp > 0) NOT NULL,
318
+ commentModeration TEXT NOT NULL, -- JSON
319
+ insertedAt INTEGER NOT NULL,
320
+ extraProps TEXT NULLABLE, -- JSON
321
+ targetAuthorSignerAddress TEXT NULLABLE, -- the signer address of the comment author being moderated (for bans/flairs)
322
+ targetAuthorDomain TEXT NULLABLE -- the domain address (e.g., spammer.bso) of the comment author being moderated
323
+ )
324
+ `);
325
+ }
326
+ _createPseudonymityAliasesTable(tableName) {
327
+ this._db.exec(`
328
+ CREATE TABLE IF NOT EXISTS ${tableName} (
329
+ commentCid TEXT NOT NULL PRIMARY KEY UNIQUE REFERENCES ${TABLES.COMMENTS}(cid) ON DELETE CASCADE,
330
+ aliasPrivateKey TEXT NOT NULL,
331
+ originalAuthorSignerPublicKey TEXT NOT NULL,
332
+ originalAuthorDomain TEXT NULLABLE, -- the original author's domain address (e.g., user.eth) if they used one
333
+ mode TEXT NOT NULL CHECK(mode IN ('per-post', 'per-reply', 'per-author')),
334
+ insertedAt INTEGER NOT NULL
335
+ )
336
+ `);
337
+ }
338
+ getDbVersion() {
339
+ const result = this._db.pragma("user_version", { simple: true });
340
+ return Number(result);
341
+ }
342
+ _migrateOldSettings(oldSettings) {
343
+ const fieldsToRemove = ["post", "reply", "vote"];
344
+ const newSettings = remeda.clone(oldSettings);
345
+ if (Array.isArray(newSettings.challenges)) {
346
+ // Filter out challenges that reference removed built-in names
347
+ newSettings.challenges = newSettings.challenges.filter((cs) => !cs.name || cs.path || cs.name in pkcJsChallenges);
348
+ for (const oldChallengeSetting of newSettings.challenges)
349
+ if (oldChallengeSetting.exclude)
350
+ for (const oldExcludeSetting of oldChallengeSetting.exclude)
351
+ for (const fieldToMove of fieldsToRemove)
352
+ delete oldExcludeSetting[fieldToMove];
353
+ }
354
+ return newSettings;
355
+ }
356
+ async _createOrMigrateTablesIfNeeded() {
357
+ const log = Logger("pkc-js:local-community:db-handler:createOrMigrateTablesIfNeeded");
358
+ const currentDbVersion = this.getDbVersion();
359
+ log.trace(`current db version: ${currentDbVersion}`);
360
+ if (currentDbVersion > env.DB_VERSION)
361
+ throw new Error(`DB version ${currentDbVersion} is greater than the latest version ${env.DB_VERSION}. You need to upgrade your client to accommodate the new DB version`);
362
+ const needToMigrate = currentDbVersion < env.DB_VERSION;
363
+ const dbPath = this._dbConfig.filename;
364
+ let backupDbPath;
365
+ const dbExistsAlready = fs.existsSync(dbPath);
366
+ if (needToMigrate) {
367
+ if (dbExistsAlready && currentDbVersion > 0) {
368
+ this.destoryConnection();
369
+ backupDbPath = path.join(path.dirname(dbPath), ".backup_before_migration", `${path.basename(dbPath)}.${currentDbVersion}.${timestamp()}`);
370
+ log(`Copying db ${path.basename(dbPath)} to ${backupDbPath} before migration`);
371
+ if (!fs.existsSync(path.dirname(backupDbPath)))
372
+ await fs.promises.mkdir(path.dirname(backupDbPath), { recursive: true });
373
+ const sourceDb = new Database(dbPath, { fileMustExist: true });
374
+ await sourceDb.backup(backupDbPath); // Use better-sqlite3's native backup method
375
+ sourceDb.close();
376
+ this._db = new Database(dbPath);
377
+ this._db.pragma("journal_mode = WAL");
378
+ }
379
+ this._db.exec("PRAGMA foreign_keys = OFF");
380
+ const tablesToDrop = ["challengeRequests", "challenges", "challengeAnswers", "challengeVerifications", "signers"];
381
+ for (const tableName of tablesToDrop)
382
+ this._db.exec(`DROP TABLE IF EXISTS ${tableName}`);
383
+ this._db.exec(`DROP TABLE IF EXISTS ${TABLES.COMMENT_UPDATES}`);
384
+ }
385
+ const createTableFunctions = [
386
+ this._createCommentsTable.bind(this),
387
+ this._createCommentUpdatesTable.bind(this),
388
+ this._createVotesTable.bind(this),
389
+ this._createCommentModerationsTable.bind(this),
390
+ this._createCommentEditsTable.bind(this),
391
+ this._createPseudonymityAliasesTable.bind(this)
392
+ ];
393
+ const tables = Object.values(TABLES);
394
+ for (let i = 0; i < tables.length; i++) {
395
+ const tableName = tables[i];
396
+ const tableExists = this._tableExists(tableName);
397
+ if (!tableExists) {
398
+ log(`Table ${tableName} does not exist. Will create schema`);
399
+ createTableFunctions[i](tableName);
400
+ }
401
+ else if (tableExists && needToMigrate) {
402
+ log(`Migrating table ${tableName} to new schema`);
403
+ const tempTableName = `${tableName}_${env.DB_VERSION}_new`;
404
+ this._db.exec(`DROP TABLE IF EXISTS ${tempTableName}`);
405
+ createTableFunctions[i](tempTableName);
406
+ await this._copyTable(tableName, tempTableName, currentDbVersion);
407
+ this._db.exec(`DROP TABLE ${tableName}`);
408
+ this._db.exec(`ALTER TABLE ${tempTableName} RENAME TO ${tableName}`);
409
+ }
410
+ }
411
+ if (needToMigrate) {
412
+ await this._purgeCommentsWithInvalidSchemaOrSignature();
413
+ await this._purgeCommentEditsWithInvalidSchemaOrSignature();
414
+ await this._purgePublicationTablesWithDuplicateSignatures();
415
+ if (currentDbVersion < 29)
416
+ this._backfillApprovedCommentNumbers();
417
+ if (currentDbVersion < 31)
418
+ this._backfillTargetAuthorSignerAddress();
419
+ if (currentDbVersion < 32)
420
+ this._backfillTargetAuthorDomain();
421
+ this._db.exec("PRAGMA foreign_keys = ON");
422
+ this._db.pragma(`user_version = ${env.DB_VERSION}`);
423
+ await this.initDbIfNeeded(); // to init keyv
424
+ const internalState = this.keyvHas(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY])
425
+ ? (await this.keyvGet(STORAGE_KEYS[STORAGE_KEYS.INTERNAL_COMMUNITY]))
426
+ : undefined;
427
+ if (internalState) {
428
+ const protocolVersion = internalState.protocolVersion || env.PROTOCOL_VERSION;
429
+ const _usingDefaultChallenge = "_usingDefaultChallenge" in internalState
430
+ ? internalState._usingDefaultChallenge
431
+ : //@ts-expect-error
432
+ remeda.isDeepEqual(this._community._defaultCommunityChallenges, internalState?.settings?.challenges);
433
+ const updateCid = "updateCid" in internalState && typeof internalState.updateCid === "string"
434
+ ? internalState.updateCid
435
+ : "QmYHzA8euDgUpNy3fh7JRwpPwt6jCgF35YTutYkyGGyr8f";
436
+ const newSettings = this._migrateOldSettings(internalState.settings);
437
+ const newChallenges = newSettings.challenges
438
+ ? await Promise.all(newSettings.challenges?.map((cs) => getCommunityChallengeFromCommunityChallengeSettings(cs, this._community._pkc)))
439
+ : newSettings.challenges;
440
+ await this._community._updateDbInternalState({
441
+ posts: undefined,
442
+ challenges: newChallenges,
443
+ settings: newSettings,
444
+ updateCid,
445
+ protocolVersion,
446
+ _usingDefaultChallenge
447
+ });
448
+ }
449
+ }
450
+ this._db.exec(`VACUUM;`); // Run vacuum outside transaction or after commit
451
+ const newDbVersion = this.getDbVersion();
452
+ assert.equal(newDbVersion, env.DB_VERSION);
453
+ this._createdTables = true;
454
+ if (needToMigrate)
455
+ log(`Created/migrated the tables to the latest (${newDbVersion}) version and saved to path`, this._dbConfig.filename);
456
+ if (backupDbPath)
457
+ await fs.promises.rm(backupDbPath);
458
+ }
459
+ _tableExists(tableName) {
460
+ const stmt = this._db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?");
461
+ return !!stmt.get(tableName);
462
+ }
463
+ _backfillApprovedCommentNumbers() {
464
+ const log = Logger("pkc-js:local-community:db-handler:_backfillApprovedCommentNumbers");
465
+ const comments = this._db
466
+ .prepare(`SELECT cid, depth FROM ${TABLES.COMMENTS} WHERE pendingApproval IS NULL OR pendingApproval != 1 ORDER BY rowid ASC`)
467
+ .all();
468
+ if (comments.length === 0)
469
+ return;
470
+ let nextNumber = 1;
471
+ let nextPostNumber = 1;
472
+ const updateStmt = this._db.prepare(`UPDATE ${TABLES.COMMENTS} SET number = ?, postNumber = ? WHERE cid = ?`);
473
+ const updateMany = this._db.transaction((items) => {
474
+ for (const comment of items) {
475
+ const postNumber = comment.depth === 0 ? nextPostNumber++ : null;
476
+ updateStmt.run(nextNumber++, postNumber, comment.cid);
477
+ }
478
+ });
479
+ updateMany(comments);
480
+ log(`Backfilled number/postNumber for ${comments.length} non-pending comments`);
481
+ }
482
+ _backfillTargetAuthorSignerAddress() {
483
+ const log = Logger("pkc-js:local-community:db-handler:_backfillTargetAuthorSignerAddress");
484
+ // Find comment moderations that have author-related edits (bans/flairs) but no targetAuthorSignerAddress
485
+ const moderationsToUpdate = this._db
486
+ .prepare(`
487
+ SELECT cm.rowid, cm.commentCid, c.authorSignerAddress,
488
+ pa.originalAuthorSignerPublicKey
489
+ FROM ${TABLES.COMMENT_MODERATIONS} cm
490
+ LEFT JOIN ${TABLES.COMMENTS} c ON cm.commentCid = c.cid
491
+ LEFT JOIN ${TABLES.PSEUDONYMITY_ALIASES} pa ON cm.commentCid = pa.commentCid
492
+ WHERE cm.targetAuthorSignerAddress IS NULL
493
+ AND json_extract(cm.commentModeration, '$.author') IS NOT NULL
494
+ `)
495
+ .all();
496
+ if (moderationsToUpdate.length === 0)
497
+ return;
498
+ const updateStmt = this._db.prepare(`UPDATE ${TABLES.COMMENT_MODERATIONS} SET targetAuthorSignerAddress = ? WHERE rowid = ?`);
499
+ const updateMany = this._db.transaction((items) => {
500
+ for (const mod of items) {
501
+ let targetAddress = null;
502
+ // If the comment was published with pseudonymity, use the original author's address
503
+ if (mod.originalAuthorSignerPublicKey) {
504
+ try {
505
+ targetAddress = getPKCAddressFromPublicKeySync(mod.originalAuthorSignerPublicKey);
506
+ }
507
+ catch {
508
+ // If we can't derive the address from the public key, fall back to authorSignerAddress
509
+ targetAddress = mod.authorSignerAddress;
510
+ }
511
+ }
512
+ else {
513
+ targetAddress = mod.authorSignerAddress;
514
+ }
515
+ if (targetAddress) {
516
+ updateStmt.run(targetAddress, mod.rowid);
517
+ }
518
+ }
519
+ });
520
+ updateMany(moderationsToUpdate);
521
+ log(`Backfilled targetAuthorSignerAddress for ${moderationsToUpdate.length} comment moderations`);
522
+ }
523
+ _backfillTargetAuthorDomain() {
524
+ const log = Logger("pkc-js:local-community:db-handler:_backfillTargetAuthorDomain");
525
+ // Find comment moderations that have author-related edits (bans/flairs) but no targetAuthorDomain
526
+ // and the comment author used a domain address
527
+ const moderationsToUpdate = this._db
528
+ .prepare(`
529
+ SELECT cm.rowid, c.author as commentAuthor,
530
+ pa.originalAuthorDomain
531
+ FROM ${TABLES.COMMENT_MODERATIONS} cm
532
+ LEFT JOIN ${TABLES.COMMENTS} c ON cm.commentCid = c.cid
533
+ LEFT JOIN ${TABLES.PSEUDONYMITY_ALIASES} pa ON cm.commentCid = pa.commentCid
534
+ WHERE cm.targetAuthorDomain IS NULL
535
+ AND json_extract(cm.commentModeration, '$.author') IS NOT NULL
536
+ `)
537
+ .all();
538
+ if (moderationsToUpdate.length === 0)
539
+ return;
540
+ const updateStmt = this._db.prepare(`UPDATE ${TABLES.COMMENT_MODERATIONS} SET targetAuthorDomain = ? WHERE rowid = ?`);
541
+ let updatedCount = 0;
542
+ const updateMany = this._db.transaction((items) => {
543
+ for (const mod of items) {
544
+ let targetDomain = null;
545
+ // If the comment was published with pseudonymity, use the original author's domain
546
+ if (mod.originalAuthorDomain) {
547
+ targetDomain = mod.originalAuthorDomain;
548
+ }
549
+ else if (mod.commentAuthor) {
550
+ try {
551
+ const author = JSON.parse(mod.commentAuthor);
552
+ targetDomain = getAuthorDomainFromWire(author) || null;
553
+ }
554
+ catch {
555
+ // Ignore parse errors
556
+ }
557
+ }
558
+ if (targetDomain) {
559
+ updateStmt.run(targetDomain, mod.rowid);
560
+ updatedCount++;
561
+ }
562
+ }
563
+ });
564
+ updateMany(moderationsToUpdate);
565
+ log(`Backfilled targetAuthorDomain for ${updatedCount} comment moderations`);
566
+ }
567
+ _getColumnNames(tableName) {
568
+ const results = this._db.pragma(`table_info(${tableName})`);
569
+ return results.map((col) => col.name);
570
+ }
571
+ async _copyTable(srcTable, dstTable, currentDbVersion) {
572
+ const log = Logger("pkc-js:local-community:db-handler:createTablesIfNeeded:copyTable");
573
+ const dstTableColumns = this._getColumnNames(dstTable);
574
+ // Include rowid in the SELECT to preserve it
575
+ const srcRecordsRaw = this._db.prepare(`SELECT rowid, * FROM ${srcTable} ORDER BY rowid ASC`).all();
576
+ if (srcRecordsRaw.length > 0) {
577
+ log(`Attempting to copy ${srcRecordsRaw.length} records from ${srcTable} to ${dstTable}`);
578
+ // Add rowid to the column list for insertion
579
+ const columnsWithRowid = ["rowid", ...dstTableColumns];
580
+ const insertStmt = this._db.prepare(`INSERT INTO ${dstTable} (${columnsWithRowid.join(", ")}) VALUES (${columnsWithRowid.map(() => "?").join(", ")})`);
581
+ const recordsToInsert = [];
582
+ for (let srcRecord of srcRecordsRaw) {
583
+ srcRecord = { ...srcRecord }; // Ensure mutable
584
+ // Pre-process specific migrations
585
+ if (currentDbVersion <= 11 && srcTable === TABLES.COMMENT_EDITS) {
586
+ const parsedSig = typeof srcRecord.signature === "string" ? JSON.parse(srcRecord.signature) : srcRecord.signature;
587
+ const commentToBeEdited = this.queryComment(srcRecord.commentCid);
588
+ if (!commentToBeEdited)
589
+ throw Error(`Failed to compute isAuthorEdit for ${srcRecord.commentCid}`);
590
+ srcRecord["isAuthorEdit"] = parsedSig.publicKey === commentToBeEdited.signature.publicKey;
591
+ const commentEditFieldsNotIncludedAnymore = ["removed"];
592
+ const extraProps = removeNullUndefinedValues(remeda.pick(srcRecord, commentEditFieldsNotIncludedAnymore));
593
+ if (Object.keys(extraProps).length > 0)
594
+ srcRecord.extraProps = { ...srcRecord.extraProps, ...extraProps };
595
+ }
596
+ if (currentDbVersion <= 12 && srcRecord["authorAddress"] && srcRecord["signature"]) {
597
+ const sig = typeof srcRecord.signature === "string" ? JSON.parse(srcRecord.signature) : srcRecord.signature;
598
+ srcRecord["authorSignerAddress"] = await getPKCAddressFromPublicKey(sig["publicKey"]);
599
+ }
600
+ if (srcTable === TABLES.COMMENTS) {
601
+ const commentIpfsFieldsNotIncludedAnymore = ["ipnsName"];
602
+ const extraProps = removeNullUndefinedValues(remeda.pick(srcRecord, commentIpfsFieldsNotIncludedAnymore));
603
+ if (Object.keys(extraProps).length > 0)
604
+ srcRecord.extraProps = { ...srcRecord.extraProps, ...extraProps };
605
+ }
606
+ // Migrate subplebbitAddress to communityPublicKey/communityName (v36 → v37)
607
+ if (currentDbVersion < 37 && srcRecord["subplebbitAddress"]) {
608
+ const addr = srcRecord["subplebbitAddress"];
609
+ if (isStringDomain(addr)) {
610
+ srcRecord["communityName"] = addr;
611
+ // Leave communityPublicKey as NULL for domain-based old rows
612
+ }
613
+ else {
614
+ srcRecord["communityPublicKey"] = addr;
615
+ }
616
+ // Preserve subplebbitAddress in extraProps for CID reconstruction
617
+ const existingExtra = typeof srcRecord.extraProps === "string" ? JSON.parse(srcRecord.extraProps) : srcRecord.extraProps || {};
618
+ srcRecord.extraProps = { ...existingExtra, subplebbitAddress: addr };
619
+ delete srcRecord["subplebbitAddress"];
620
+ }
621
+ // Prepare record for insertion (stringify JSONs, convert booleans)
622
+ const processedRecord = this._processRecordsForDbBeforeInsert([srcRecord])[0];
623
+ // Map values including rowid (preserve the original rowid value)
624
+ const finalRecordValues = columnsWithRowid.map((col) => {
625
+ if (col === "rowid") {
626
+ return srcRecord.rowid; // Use original rowid value
627
+ }
628
+ return processedRecord[col];
629
+ });
630
+ recordsToInsert.push(finalRecordValues);
631
+ }
632
+ if (recordsToInsert.length > 0) {
633
+ const insertMany = this._db.transaction((items) => {
634
+ for (const itemArgs of items) {
635
+ insertStmt.run(...itemArgs);
636
+ }
637
+ });
638
+ insertMany(recordsToInsert);
639
+ }
640
+ }
641
+ log(`copied table ${srcTable} to table ${dstTable}`);
642
+ }
643
+ async _purgePublicationTablesWithDuplicateSignatures() {
644
+ const log = Logger("pkc-js:local-community:db-handler:_purgePublicationTablesWithDuplicateSignatures");
645
+ const publicationTables = [TABLES.COMMENTS, TABLES.COMMENT_EDITS, TABLES.COMMENT_MODERATIONS, TABLES.COMMENT_UPDATES];
646
+ for (const tableName of publicationTables) {
647
+ const columnNames = this._getColumnNames(tableName);
648
+ if (!columnNames.includes("signature")) {
649
+ log.trace(`Skipping duplicate signature purge for ${tableName} because column signature is missing.`);
650
+ continue;
651
+ }
652
+ const jsonValidExpr = (alias) => `json_valid(${alias}.signature) = 1`;
653
+ const signatureExtractExpr = (alias) => `json_extract(${alias}.signature, '$.signature')`;
654
+ const duplicateRows = this._db
655
+ .prepare(`
656
+ SELECT newer.rowid AS rowid
657
+ FROM ${tableName} AS newer
658
+ WHERE ${jsonValidExpr("newer")}
659
+ AND ${signatureExtractExpr("newer")} IS NOT NULL
660
+ AND EXISTS (
661
+ SELECT 1
662
+ FROM ${tableName} AS older
663
+ WHERE ${jsonValidExpr("older")}
664
+ AND ${signatureExtractExpr("older")} = ${signatureExtractExpr("newer")}
665
+ AND older.rowid < newer.rowid
666
+ )
667
+ `)
668
+ .all();
669
+ if (duplicateRows.length === 0)
670
+ continue;
671
+ if (tableName === TABLES.COMMENTS) {
672
+ const duplicateCids = this._db
673
+ .prepare(`
674
+ SELECT cid
675
+ FROM ${TABLES.COMMENTS} AS newer
676
+ WHERE ${jsonValidExpr("newer")}
677
+ AND ${signatureExtractExpr("newer")} IS NOT NULL
678
+ AND EXISTS (
679
+ SELECT 1
680
+ FROM ${TABLES.COMMENTS} AS older
681
+ WHERE ${jsonValidExpr("older")}
682
+ AND ${signatureExtractExpr("older")} = ${signatureExtractExpr("newer")}
683
+ AND older.rowid < newer.rowid
684
+ )
685
+ `)
686
+ .all();
687
+ for (const { cid } of duplicateCids) {
688
+ const purgedRows = this.purgeComment(cid);
689
+ for (const row of purgedRows)
690
+ await this._community._addAllCidsUnderPurgedCommentToBeRemoved(row);
691
+ }
692
+ log(`Purged ${duplicateCids.length} duplicate comment row(s) based on signature.signature with higher rowid values.`);
693
+ continue;
694
+ }
695
+ const deleteStmt = this._db.prepare(`DELETE FROM ${tableName} WHERE rowid = ?`);
696
+ const deleteMany = this._db.transaction((rows) => {
697
+ for (const row of rows)
698
+ deleteStmt.run(row.rowid);
699
+ });
700
+ deleteMany(duplicateRows);
701
+ log(`Purged ${duplicateRows.length} duplicate row(s) from ${tableName} based on signature.signature with higher rowid values.`);
702
+ }
703
+ }
704
+ async _purgeCommentEditsWithInvalidSchemaOrSignature() {
705
+ const log = Logger("pkc-js:local-community:db-handler:_purgeCommentEditsWithInvalidSchemaOrSignature");
706
+ const commentEditsOrderedByASC = this._db
707
+ .prepare(`SELECT rowid as rowid, * FROM ${TABLES.COMMENT_EDITS} ORDER BY rowid ASC`)
708
+ .all();
709
+ for (const rawCommentEditRecord of commentEditsOrderedByASC) {
710
+ let commentEditRecord;
711
+ try {
712
+ commentEditRecord = this._parseCommentEditsRow(rawCommentEditRecord);
713
+ }
714
+ catch (error) {
715
+ if (error instanceof ZodError) {
716
+ log.error(`Comment edit (${rawCommentEditRecord.commentCid}) row ${rawCommentEditRecord.rowid} in DB failed to parse and will be purged from comment edits table.`, error);
717
+ this._deleteCommentEditRow(rawCommentEditRecord.rowid);
718
+ continue;
719
+ }
720
+ throw error;
721
+ }
722
+ try {
723
+ CommentEditPubsubMessagePublicationSchema.strip().parse(commentEditRecord);
724
+ }
725
+ catch (e) {
726
+ log.error(`Comment edit (${commentEditRecord.commentCid}) row ${rawCommentEditRecord.rowid} in DB has an invalid schema and will be purged from comment edits table.`, e);
727
+ this._deleteCommentEditRow(rawCommentEditRecord.rowid);
728
+ continue;
729
+ }
730
+ const commentEditPubsub = remeda.pick(commentEditRecord, [
731
+ ...commentEditRecord.signature.signedPropertyNames,
732
+ "signature"
733
+ ]);
734
+ const validRes = await verifyCommentEdit({
735
+ edit: commentEditPubsub,
736
+ resolveAuthorNames: false,
737
+ clientsManager: this._community._clientsManager
738
+ });
739
+ if (!validRes.valid && validRes.reason === messages.ERR_SIGNATURE_IS_INVALID) {
740
+ log.error(`Comment edit (${commentEditRecord.commentCid}) row ${rawCommentEditRecord.rowid} in DB has invalid signature due to ${validRes.reason}. Removing comment edit entry.`);
741
+ this._deleteCommentEditRow(rawCommentEditRecord.rowid);
742
+ }
743
+ }
744
+ }
745
+ async _purgeCommentsWithInvalidSchemaOrSignature() {
746
+ const log = Logger("pkc-js:local-community:db-handler:_purgeCommentsWithInvalidSchema");
747
+ const commentsOrderedByASC = this._db.prepare(`SELECT * FROM ${TABLES.COMMENTS} ORDER BY rowid ASC`).all();
748
+ const alreadyPurgedCids = new Set();
749
+ for (const rawCommentRecord of commentsOrderedByASC) {
750
+ if (alreadyPurgedCids.has(rawCommentRecord.cid))
751
+ continue;
752
+ let commentRecord;
753
+ try {
754
+ commentRecord = this._parseCommentsTableRow(rawCommentRecord);
755
+ }
756
+ catch (error) {
757
+ if (error instanceof ZodError) {
758
+ const purged = this.purgeComment(rawCommentRecord.cid);
759
+ for (const p of purged)
760
+ alreadyPurgedCids.add(p.commentTableRow.cid);
761
+ continue;
762
+ }
763
+ throw error;
764
+ }
765
+ try {
766
+ CommentIpfsSchema.strip().parse(commentRecord);
767
+ }
768
+ catch (e) {
769
+ log.error(`Comment (${commentRecord.cid}) in DB has an invalid schema, will be purged.`, e);
770
+ const purged = this.purgeComment(commentRecord.cid);
771
+ for (const p of purged)
772
+ alreadyPurgedCids.add(p.commentTableRow.cid);
773
+ continue;
774
+ }
775
+ const validRes = await verifyCommentIpfs({
776
+ comment: { ...commentRecord, ...commentRecord.extraProps },
777
+ resolveAuthorNames: false,
778
+ calculatedCommentCid: commentRecord.cid,
779
+ clientsManager: this._community._clientsManager
780
+ });
781
+ if (!validRes.valid) {
782
+ log.error(`Comment ${commentRecord.cid} in DB has invalid signature due to ${validRes.reason}. Will be purged.`);
783
+ const purged = this.purgeComment(commentRecord.cid);
784
+ for (const p of purged)
785
+ alreadyPurgedCids.add(p.commentTableRow.cid);
786
+ }
787
+ }
788
+ }
789
+ deleteVote(authorSignerAddress, commentCid) {
790
+ this._db
791
+ .prepare(`DELETE FROM ${TABLES.VOTES} WHERE commentCid = ? AND authorSignerAddress = ?`)
792
+ .run(commentCid, authorSignerAddress);
793
+ }
794
+ _deleteCommentEditRow(rowid) {
795
+ const deleteResult = this._db.prepare(`DELETE FROM ${TABLES.COMMENT_EDITS} WHERE rowid = ?`).run(rowid);
796
+ return deleteResult.changes > 0;
797
+ }
798
+ insertVotes(votes) {
799
+ if (votes.length === 0)
800
+ return;
801
+ const processedVotes = this._processRecordsForDbBeforeInsert(votes);
802
+ // Get all column names from the votes table to create defaults
803
+ const columnNames = this._getColumnNames(TABLES.VOTES);
804
+ const stmt = this._db.prepare(`
805
+ INSERT INTO ${TABLES.VOTES}
806
+ (commentCid, authorSignerAddress, timestamp, vote, protocolVersion, insertedAt, extraProps)
807
+ VALUES (@commentCid, @authorSignerAddress, @timestamp, @vote, @protocolVersion, @insertedAt, @extraProps)
808
+ `);
809
+ const insertMany = this._db.transaction((items) => {
810
+ for (const vote of items) {
811
+ // Create default object with null values for all columns
812
+ const defaults = {};
813
+ columnNames.forEach((column) => {
814
+ if (!(column in vote)) {
815
+ defaults[column] = null;
816
+ }
817
+ });
818
+ // Merge defaults with actual vote data
819
+ const completeVote = { ...defaults, ...vote };
820
+ stmt.run(completeVote);
821
+ }
822
+ });
823
+ insertMany(processedVotes);
824
+ }
825
+ insertComments(comments) {
826
+ if (comments.length === 0)
827
+ return;
828
+ const processedComments = this._processRecordsForDbBeforeInsert(comments);
829
+ // Get all column names from the comments table to create defaults
830
+ const columnNames = this._getColumnNames(TABLES.COMMENTS);
831
+ // TODO: refactor to derive column list from CommentsTableRowSchema instead of hardcoding.
832
+ // Adding a new column to the comments table requires updating this list manually, which is error-prone.
833
+ const stmt = this._db.prepare(`
834
+ INSERT INTO ${TABLES.COMMENTS}
835
+ (cid, authorSignerAddress, author, link, linkWidth, linkHeight, thumbnailUrl, thumbnailUrlWidth, thumbnailUrlHeight, parentCid, postCid, previousCid, communityPublicKey, communityName, content, timestamp, signature, originalCommentSignatureEncoded, title, depth, linkHtmlTagName, flairs, spoiler, pendingApproval, number, postNumber, nsfw, pseudonymityMode, quotedCids, extraProps, protocolVersion, insertedAt)
836
+ VALUES (@cid, @authorSignerAddress, @author, @link, @linkWidth, @linkHeight, @thumbnailUrl, @thumbnailUrlWidth, @thumbnailUrlHeight, @parentCid, @postCid, @previousCid, @communityPublicKey, @communityName, @content, @timestamp, @signature, @originalCommentSignatureEncoded, @title, @depth, @linkHtmlTagName, @flairs, @spoiler, @pendingApproval, @number, @postNumber, @nsfw, @pseudonymityMode, @quotedCids, @extraProps, @protocolVersion, @insertedAt)
837
+ `);
838
+ // Create default object with null values for all columns
839
+ const defaults = remeda.mapToObj(columnNames, (column) => [column, null]);
840
+ const insertMany = this._db.transaction((items) => {
841
+ for (const comment of items) {
842
+ // Merge defaults with actual comment data
843
+ const completeComment = { ...defaults, ...comment };
844
+ stmt.run(completeComment);
845
+ }
846
+ });
847
+ insertMany(processedComments);
848
+ }
849
+ insertPseudonymityAliases(aliases) {
850
+ if (aliases.length === 0)
851
+ return;
852
+ const processedAliases = this._processRecordsForDbBeforeInsert(aliases);
853
+ const stmt = this._db.prepare(`
854
+ INSERT OR REPLACE INTO ${TABLES.PSEUDONYMITY_ALIASES}
855
+ (commentCid, aliasPrivateKey, originalAuthorSignerPublicKey, originalAuthorDomain, mode, insertedAt)
856
+ VALUES (@commentCid, @aliasPrivateKey, @originalAuthorSignerPublicKey, @originalAuthorDomain, @mode, @insertedAt)
857
+ `);
858
+ const insertMany = this._db.transaction((items) => {
859
+ for (const alias of items)
860
+ stmt.run(alias);
861
+ });
862
+ insertMany(processedAliases);
863
+ }
864
+ upsertCommentUpdates(updates) {
865
+ const processedUpdates = this._processRecordsForDbBeforeInsert(updates);
866
+ // Get all column names from the comment_updates table to create defaults
867
+ const columnNames = this._getColumnNames(TABLES.COMMENT_UPDATES);
868
+ const stmt = this._db.prepare(`
869
+ INSERT INTO ${TABLES.COMMENT_UPDATES}
870
+ (cid, edit, upvoteCount, downvoteCount, replyCount, childCount, number, postNumber, flairs, spoiler, nsfw, pinned, locked, archived, removed, approved, reason, updatedAt, protocolVersion, signature, author, replies, lastChildCid, lastReplyTimestamp, postUpdatesBucket, publishedToPostUpdatesMFS, insertedAt)
871
+ VALUES (@cid, @edit, @upvoteCount, @downvoteCount, @replyCount, @childCount, @number, @postNumber, @flairs, @spoiler, @nsfw, @pinned, @locked, @archived, @removed, @approved, @reason, @updatedAt, @protocolVersion, @signature, @author, @replies, @lastChildCid, @lastReplyTimestamp, @postUpdatesBucket, @publishedToPostUpdatesMFS, @insertedAt)
872
+ ON CONFLICT(cid) DO UPDATE SET
873
+ edit = excluded.edit, upvoteCount = excluded.upvoteCount, downvoteCount = excluded.downvoteCount, replyCount = excluded.replyCount, childCount = excluded.childCount,
874
+ number = COALESCE(excluded.number, ${TABLES.COMMENT_UPDATES}.number),
875
+ postNumber = COALESCE(excluded.postNumber, ${TABLES.COMMENT_UPDATES}.postNumber),
876
+ flairs = excluded.flairs, spoiler = excluded.spoiler, nsfw = excluded.nsfw, pinned = excluded.pinned, locked = excluded.locked, archived = excluded.archived,
877
+ removed = excluded.removed, approved = excluded.approved, reason = excluded.reason, updatedAt = excluded.updatedAt, protocolVersion = excluded.protocolVersion,
878
+ signature = excluded.signature, author = excluded.author, replies = excluded.replies, lastChildCid = excluded.lastChildCid,
879
+ lastReplyTimestamp = excluded.lastReplyTimestamp, postUpdatesBucket = excluded.postUpdatesBucket,
880
+ publishedToPostUpdatesMFS = excluded.publishedToPostUpdatesMFS,
881
+ insertedAt = excluded.insertedAt
882
+ `);
883
+ const defaults = remeda.mapToObj(columnNames, (column) => [column, null]);
884
+ const upsertMany = this._db.transaction((items) => {
885
+ for (const update of items) {
886
+ // Create default object with null values for all columns
887
+ // Merge defaults with actual update data
888
+ const completeUpdate = { ...defaults, ...update };
889
+ stmt.run(completeUpdate);
890
+ }
891
+ });
892
+ upsertMany(processedUpdates);
893
+ }
894
+ insertCommentModerations(moderations) {
895
+ if (moderations.length === 0)
896
+ return;
897
+ const processedModerations = this._processRecordsForDbBeforeInsert(moderations);
898
+ // Get all column names from the comment_moderations table to create defaults
899
+ const columnNames = this._getColumnNames(TABLES.COMMENT_MODERATIONS);
900
+ const stmt = this._db.prepare(`
901
+ INSERT INTO ${TABLES.COMMENT_MODERATIONS}
902
+ (commentCid, author, signature, modSignerAddress, protocolVersion, communityPublicKey, communityName, timestamp, commentModeration, insertedAt, extraProps, targetAuthorSignerAddress, targetAuthorDomain)
903
+ VALUES (@commentCid, @author, @signature, @modSignerAddress, @protocolVersion, @communityPublicKey, @communityName, @timestamp, @commentModeration, @insertedAt, @extraProps, @targetAuthorSignerAddress, @targetAuthorDomain)
904
+ `);
905
+ const defaults = remeda.mapToObj(columnNames, (column) => [column, null]);
906
+ const insertMany = this._db.transaction((items) => {
907
+ for (const mod of items) {
908
+ // Create default object with null values for all columns
909
+ // Merge defaults with actual moderation data
910
+ const completeMod = { ...defaults, ...mod };
911
+ stmt.run(completeMod);
912
+ }
913
+ });
914
+ insertMany(processedModerations);
915
+ }
916
+ insertCommentEdits(edits) {
917
+ if (edits.length === 0)
918
+ return;
919
+ const processedEdits = this._processRecordsForDbBeforeInsert(edits);
920
+ // Get all column names from the comment_edits table to create defaults
921
+ const columnNames = this._getColumnNames(TABLES.COMMENT_EDITS);
922
+ const stmt = this._db.prepare(`
923
+ INSERT INTO ${TABLES.COMMENT_EDITS}
924
+ (commentCid, authorSignerAddress, author, signature, protocolVersion, communityPublicKey, communityName, timestamp, content, reason, deleted, flairs, spoiler, nsfw, isAuthorEdit, insertedAt, extraProps)
925
+ VALUES (@commentCid, @authorSignerAddress, @author, @signature, @protocolVersion, @communityPublicKey, @communityName, @timestamp, @content, @reason, @deleted, @flairs, @spoiler, @nsfw, @isAuthorEdit, @insertedAt, @extraProps)
926
+ `);
927
+ const defaults = remeda.mapToObj(columnNames, (column) => [column, null]);
928
+ const insertMany = this._db.transaction((items) => {
929
+ for (const edit of items) {
930
+ // Create default object with null values for all columns
931
+ // Merge defaults with actual edit data
932
+ const completeEdit = { ...defaults, ...edit };
933
+ stmt.run(completeEdit);
934
+ }
935
+ });
936
+ insertMany(processedEdits);
937
+ }
938
+ queryVote(commentCid, authorSignerAddress) {
939
+ const row = this._db
940
+ .prepare(`SELECT * FROM ${TABLES.VOTES} WHERE commentCid = ? AND authorSignerAddress = ?`)
941
+ .get(commentCid, authorSignerAddress);
942
+ if (!row)
943
+ return undefined;
944
+ return this._parseVoteRow(row);
945
+ }
946
+ _approvedClause(alias) {
947
+ return `(${alias}.approved IS NULL OR ${alias}.approved = 1 OR ${alias}.approved IS TRUE)`;
948
+ }
949
+ _removedClause(alias) {
950
+ return `(${alias}.removed IS NOT 1 AND ${alias}.removed IS NOT TRUE)`;
951
+ }
952
+ _deletedFromUpdatesClause(alias) {
953
+ return `(json_extract(${alias}.edit, '$.deleted') IS NULL OR json_extract(${alias}.edit, '$.deleted') != 1)`;
954
+ }
955
+ _deletedFromLookupClause(alias) {
956
+ return `(${alias}.deleted_flag IS NULL OR ${alias}.deleted_flag != 1)`;
957
+ }
958
+ _pendingApprovalClause(alias) {
959
+ return `(${alias}.pendingApproval IS NULL OR ${alias}.pendingApproval != 1)`;
960
+ }
961
+ _communityAddressClause(alias) {
962
+ const address = this._community.address;
963
+ const addresses = getEquivalentCommunityAddresses(address);
964
+ if (isStringDomain(address)) {
965
+ // Domain-based: match communityName OR communityPublicKey (domain strings and IPNS keys never overlap)
966
+ const domainPlaceholders = addresses.map(() => "?").join(", ");
967
+ return {
968
+ clause: `(${alias}.communityName IN (${domainPlaceholders}) OR ${alias}.communityPublicKey IN (${domainPlaceholders}))`,
969
+ params: [...addresses, ...addresses]
970
+ };
971
+ }
972
+ else {
973
+ // IPNS key: match communityPublicKey directly
974
+ return { clause: `${alias}.communityPublicKey = ?`, params: [address] };
975
+ }
976
+ }
977
+ _communityAddressClauseNamed(alias, paramPrefix) {
978
+ const address = this._community.address;
979
+ const addresses = getEquivalentCommunityAddresses(address);
980
+ if (isStringDomain(address)) {
981
+ // Domain-based: match communityName OR communityPublicKey (domain strings and IPNS keys never overlap)
982
+ const params = {};
983
+ const namePlaceholders = [];
984
+ const keyPlaceholders = [];
985
+ addresses.forEach((addr, i) => {
986
+ const nameParam = `${paramPrefix}CommunityName${i}`;
987
+ const keyParam = `${paramPrefix}CommunityKey${i}`;
988
+ params[nameParam] = addr;
989
+ params[keyParam] = addr;
990
+ namePlaceholders.push(`:${nameParam}`);
991
+ keyPlaceholders.push(`:${keyParam}`);
992
+ });
993
+ return {
994
+ clause: `(${alias}.communityName IN (${namePlaceholders.join(", ")}) OR ${alias}.communityPublicKey IN (${keyPlaceholders.join(", ")}))`,
995
+ params
996
+ };
997
+ }
998
+ else {
999
+ // IPNS key: match communityPublicKey directly
1000
+ const paramName = `${paramPrefix}CommunityKey`;
1001
+ return { clause: `${alias}.communityPublicKey = :${paramName}`, params: { [paramName]: address } };
1002
+ }
1003
+ }
1004
+ _buildPageQueryParts(options) {
1005
+ const commentsTable = TABLES.COMMENTS;
1006
+ const commentUpdatesTable = TABLES.COMMENT_UPDATES;
1007
+ const whereClauses = [`${commentsTable}.parentCid = ?`];
1008
+ const params = [options.parentCid];
1009
+ if (options.excludeCommentsWithDifferentCommunityAddress) {
1010
+ const { clause, params: addrParams } = this._communityAddressClause(commentsTable);
1011
+ whereClauses.push(clause);
1012
+ params.push(...addrParams);
1013
+ }
1014
+ if (options.excludeCommentPendingApproval)
1015
+ whereClauses.push(this._pendingApprovalClause(commentsTable));
1016
+ if (options.excludeRemovedComments)
1017
+ whereClauses.push(this._removedClause(commentUpdatesTable));
1018
+ if (options.excludeDeletedComments)
1019
+ whereClauses.push(this._deletedFromUpdatesClause(commentUpdatesTable));
1020
+ if (options.excludeCommentWithApprovedFalse)
1021
+ whereClauses.push(this._approvedClause(commentUpdatesTable));
1022
+ return { whereClauses, params };
1023
+ }
1024
+ queryMaximumTimestampUnderComment(comment) {
1025
+ const { clause: addrClause, params: addrParams } = this._communityAddressClause("c");
1026
+ const query = `
1027
+ WITH RECURSIVE descendants AS (
1028
+ SELECT c.cid, c.timestamp FROM ${TABLES.COMMENTS} c
1029
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON cu.cid = c.cid
1030
+ WHERE c.parentCid = ?
1031
+ AND COALESCE(cu.approved, 1) != 0
1032
+ AND (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1033
+ UNION ALL
1034
+ SELECT c.cid, c.timestamp FROM ${TABLES.COMMENTS} c
1035
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
1036
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS d ON c.cid = d.cid
1037
+ JOIN descendants desc_nodes ON c.parentCid = desc_nodes.cid
1038
+ WHERE ${addrClause} AND (cu.removed IS NOT 1 AND cu.removed IS NOT TRUE) AND (d.deleted_flag IS NULL OR d.deleted_flag != 1)
1039
+ AND COALESCE(cu.approved, 1) != 0
1040
+ AND (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1041
+ )
1042
+ SELECT MAX(timestamp) AS max_timestamp FROM descendants
1043
+ `;
1044
+ const result = this._db.prepare(query).get(comment.cid, ...addrParams);
1045
+ if (result.max_timestamp === null)
1046
+ return undefined;
1047
+ return result.max_timestamp;
1048
+ }
1049
+ queryPageComments(options) {
1050
+ const commentUpdateCols = remeda.keys.strict(options.commentUpdateFieldsToExclude
1051
+ ? remeda.omit(CommentUpdateSchema.shape, options.commentUpdateFieldsToExclude)
1052
+ : CommentUpdateSchema.shape);
1053
+ const commentUpdateSelects = commentUpdateCols.map((col) => `${TABLES.COMMENT_UPDATES}.${col} AS commentUpdate_${col}`);
1054
+ const commentIpfsCols = [...remeda.keys.strict(CommentIpfsSchema.shape), "extraProps"];
1055
+ const commentIpfsSelects = commentIpfsCols.map((col) => `${TABLES.COMMENTS}.${col} AS commentIpfs_${col}`);
1056
+ const { whereClauses, params } = this._buildPageQueryParts(options);
1057
+ const queryStr = `
1058
+ SELECT ${commentIpfsSelects.join(", ")}, ${commentUpdateSelects.join(", ")}
1059
+ FROM ${TABLES.COMMENTS} INNER JOIN ${TABLES.COMMENT_UPDATES} ON ${TABLES.COMMENTS}.cid = ${TABLES.COMMENT_UPDATES}.cid
1060
+ WHERE ${whereClauses.join(" AND ")}
1061
+ `;
1062
+ const commentsRaw = this._db.prepare(queryStr).all(...params);
1063
+ return commentsRaw.map((commentRaw) => {
1064
+ const { comment, commentUpdate } = this._parsePrefixedComment(commentRaw);
1065
+ return { comment, commentUpdate };
1066
+ });
1067
+ }
1068
+ queryFlattenedPageReplies(options) {
1069
+ const commentUpdateCols = remeda.keys.strict(options.commentUpdateFieldsToExclude
1070
+ ? remeda.omit(CommentUpdateSchema.shape, options.commentUpdateFieldsToExclude)
1071
+ : CommentUpdateSchema.shape);
1072
+ // TODO, is it omitting replies?
1073
+ const commentUpdateSelects = commentUpdateCols.map((col) => `c_updates.${col} AS commentUpdate_${col}`);
1074
+ const commentIpfsCols = [...remeda.keys.strict(CommentIpfsSchema.shape), "extraProps"];
1075
+ const commentIpfsSelects = commentIpfsCols.map((col) => `comments_alias.${col} AS commentIpfs_${col}`);
1076
+ let baseWhereClausesStr = "";
1077
+ let recursiveWhereClausesStr = "";
1078
+ const params = [options.parentCid];
1079
+ const baseFilterClauses = [];
1080
+ const recursiveFilterClauses = [];
1081
+ const commentsAlias = "comments";
1082
+ const commentUpdatesAlias = "c_updates";
1083
+ const deletedLookupAlias = "d";
1084
+ if (options.excludeCommentsWithDifferentCommunityAddress) {
1085
+ const { clause: baseClause, params: baseAddrParams } = this._communityAddressClause(commentsAlias);
1086
+ baseFilterClauses.push(baseClause);
1087
+ params.push(...baseAddrParams);
1088
+ const { clause: recClause, params: recAddrParams } = this._communityAddressClause(commentsAlias);
1089
+ recursiveFilterClauses.push(recClause);
1090
+ params.push(...recAddrParams);
1091
+ }
1092
+ if (options.excludeCommentPendingApproval) {
1093
+ const clause = this._pendingApprovalClause(commentsAlias);
1094
+ baseFilterClauses.push(clause);
1095
+ recursiveFilterClauses.push(clause);
1096
+ }
1097
+ if (options.excludeRemovedComments) {
1098
+ const clause = this._removedClause(commentUpdatesAlias);
1099
+ baseFilterClauses.push(clause);
1100
+ recursiveFilterClauses.push(clause);
1101
+ }
1102
+ if (options.excludeDeletedComments) {
1103
+ const clause = this._deletedFromLookupClause(deletedLookupAlias);
1104
+ baseFilterClauses.push(clause);
1105
+ recursiveFilterClauses.push(clause);
1106
+ }
1107
+ if (options.excludeCommentWithApprovedFalse) {
1108
+ const clause = this._approvedClause(commentUpdatesAlias);
1109
+ baseFilterClauses.push(clause);
1110
+ recursiveFilterClauses.push(clause);
1111
+ }
1112
+ baseWhereClausesStr = baseFilterClauses.length > 0 ? `AND ${baseFilterClauses.join(" AND ")}` : "";
1113
+ recursiveWhereClausesStr = recursiveFilterClauses.length > 0 ? `AND ${recursiveFilterClauses.join(" AND ")}` : "";
1114
+ const query = `
1115
+ WITH RECURSIVE comment_tree AS (
1116
+ SELECT comments.*, ${commentUpdateCols.map((c) => `c_updates.${c} AS c_updates_${c}`).join(", ")}, 0 AS tree_level
1117
+ FROM ${TABLES.COMMENTS} comments
1118
+ INNER JOIN ${TABLES.COMMENT_UPDATES} c_updates ON comments.cid = c_updates.cid
1119
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS d ON comments.cid = d.cid
1120
+ WHERE comments.parentCid = ? ${baseWhereClausesStr}
1121
+ UNION ALL
1122
+ SELECT comments.*, ${commentUpdateCols.map((c) => `c_updates.${c} AS c_updates_${c}`).join(", ")}, tree.tree_level + 1
1123
+ FROM ${TABLES.COMMENTS} comments
1124
+ INNER JOIN ${TABLES.COMMENT_UPDATES} c_updates ON comments.cid = c_updates.cid
1125
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS d ON comments.cid = d.cid
1126
+ INNER JOIN comment_tree tree ON comments.parentCid = tree.cid
1127
+ WHERE 1=1 ${recursiveWhereClausesStr}
1128
+ )
1129
+ SELECT ${commentIpfsSelects.join(", ")}, ${commentUpdateCols.map((col) => `comments_alias.c_updates_${col} AS commentUpdate_${col}`).join(", ")}
1130
+ FROM comment_tree comments_alias
1131
+ `;
1132
+ const commentsRaw = this._db.prepare(query).all(...params);
1133
+ return commentsRaw.map((commentRaw) => {
1134
+ const { comment, commentUpdate } = this._parsePrefixedComment(commentRaw);
1135
+ return { comment, commentUpdate };
1136
+ });
1137
+ }
1138
+ queryStoredCommentUpdate(comment) {
1139
+ const row = this._db.prepare(`SELECT * FROM ${TABLES.COMMENT_UPDATES} WHERE cid = ?`).get(comment.cid);
1140
+ if (!row)
1141
+ return undefined;
1142
+ return this._parseCommentUpdatesRow(row);
1143
+ }
1144
+ hasCommentWithSignatureEncoded(signatureEncoded) {
1145
+ const row = this._db
1146
+ .prepare(`SELECT 1 FROM ${TABLES.COMMENTS}
1147
+ WHERE json_extract(signature, '$.signature') = ?
1148
+ OR originalCommentSignatureEncoded = ?
1149
+ LIMIT 1`)
1150
+ .get(signatureEncoded, signatureEncoded);
1151
+ return row !== undefined;
1152
+ }
1153
+ queryCommentBySignatureEncoded(signatureEncoded) {
1154
+ const row = this._db
1155
+ .prepare(`SELECT * FROM ${TABLES.COMMENTS}
1156
+ WHERE json_extract(signature, '$.signature') = ?
1157
+ OR originalCommentSignatureEncoded = ?
1158
+ LIMIT 1`)
1159
+ .get(signatureEncoded, signatureEncoded);
1160
+ if (!row)
1161
+ return undefined;
1162
+ return this._parseCommentsTableRow(row);
1163
+ }
1164
+ hasCommentModerationWithSignatureEncoded(signatureEncoded) {
1165
+ const row = this._db
1166
+ .prepare(`SELECT 1 FROM ${TABLES.COMMENT_MODERATIONS} WHERE json_extract(signature, '$.signature') = ? LIMIT 1`)
1167
+ .get(signatureEncoded);
1168
+ return row !== undefined;
1169
+ }
1170
+ hasCommentEditWithSignatureEncoded(signatureEncoded) {
1171
+ const row = this._db
1172
+ .prepare(`SELECT 1 FROM ${TABLES.COMMENT_EDITS} WHERE json_extract(signature, '$.signature') = ? LIMIT 1`)
1173
+ .get(signatureEncoded);
1174
+ return row !== undefined;
1175
+ }
1176
+ queryParentsCids(rootComment) {
1177
+ if (!rootComment.parentCid)
1178
+ throw Error("Root comment has no parent cid");
1179
+ const query = `
1180
+ WITH RECURSIVE parent_chain AS (
1181
+ SELECT cid, parentCid, 0 AS level FROM ${TABLES.COMMENTS} WHERE cid = ?
1182
+ UNION ALL
1183
+ SELECT c.cid, c.parentCid, pc.level + 1 FROM ${TABLES.COMMENTS} c JOIN parent_chain pc ON c.cid = pc.parentCid
1184
+ ) SELECT cid FROM parent_chain ORDER BY level
1185
+ `;
1186
+ return this._db.prepare(query).all(rootComment.parentCid);
1187
+ }
1188
+ queryCommentsPendingApproval() {
1189
+ const results = this._db
1190
+ .prepare(`SELECT * FROM ${TABLES.COMMENTS} WHERE pendingApproval = 1 ORDER BY rowid DESC`)
1191
+ .all();
1192
+ return results.map((r) => this._parseCommentsTableRow(r));
1193
+ }
1194
+ queryCommentsToBeUpdated() {
1195
+ // TODO optimize this query in the future
1196
+ // Make sure tests in commentsToUpdate.db.community.test.js are passing
1197
+ const query = `
1198
+ WITH RECURSIVE
1199
+ direct_updates AS (
1200
+ SELECT c.* FROM ${TABLES.COMMENTS} c LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
1201
+ WHERE (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1202
+ AND (cu.cid IS NULL OR (cu.publishedToPostUpdatesMFS = 0 OR cu.publishedToPostUpdatesMFS IS FALSE))
1203
+ UNION
1204
+ SELECT c.* FROM ${TABLES.COMMENTS} c JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
1205
+ WHERE (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1206
+ AND (
1207
+ EXISTS (SELECT 1 FROM ${TABLES.VOTES} v WHERE v.commentCid = c.cid AND v.insertedAt >= cu.insertedAt)
1208
+ OR EXISTS (SELECT 1 FROM ${TABLES.COMMENT_EDITS} ce WHERE ce.commentCid = c.cid AND ce.insertedAt >= cu.insertedAt)
1209
+ OR EXISTS (SELECT 1 FROM ${TABLES.COMMENT_MODERATIONS} cm WHERE cm.commentCid = c.cid AND cm.insertedAt >= cu.insertedAt)
1210
+ OR EXISTS (SELECT 1 FROM ${TABLES.COMMENTS} cc WHERE cc.parentCid = c.cid AND cc.insertedAt >= cu.insertedAt)
1211
+ )
1212
+ ),
1213
+ child_counts AS (
1214
+ SELECT
1215
+ c.parentCid AS cid,
1216
+ COUNT(*) AS actual_child_count
1217
+ FROM ${TABLES.COMMENTS} c
1218
+ JOIN ${TABLES.COMMENT_UPDATES} cu_child ON c.cid = cu_child.cid
1219
+ LEFT JOIN (
1220
+ SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}
1221
+ ) deleted_lookup ON deleted_lookup.cid = c.cid
1222
+ WHERE c.parentCid IS NOT NULL
1223
+ AND (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1224
+ AND (cu_child.removed IS NOT 1 AND cu_child.removed IS NOT TRUE)
1225
+ AND (deleted_lookup.deleted_flag IS NULL OR deleted_lookup.deleted_flag != 1)
1226
+ GROUP BY c.parentCid
1227
+ ),
1228
+ filtered_children AS (
1229
+ SELECT
1230
+ c.parentCid AS cid,
1231
+ c.cid AS child_cid,
1232
+ ROW_NUMBER() OVER (PARTITION BY c.parentCid ORDER BY c.rowid DESC) AS child_rank
1233
+ FROM ${TABLES.COMMENTS} c
1234
+ JOIN ${TABLES.COMMENT_UPDATES} cu_child ON c.cid = cu_child.cid
1235
+ LEFT JOIN (
1236
+ SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}
1237
+ ) deleted_lookup ON deleted_lookup.cid = c.cid
1238
+ WHERE c.parentCid IS NOT NULL
1239
+ AND (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1240
+ AND (cu_child.removed IS NOT 1 AND cu_child.removed IS NOT TRUE)
1241
+ AND (deleted_lookup.deleted_flag IS NULL OR deleted_lookup.deleted_flag != 1)
1242
+ AND COALESCE(cu_child.approved, 1) != 0
1243
+ ),
1244
+ last_child_cids AS (
1245
+ SELECT cid, child_cid AS actual_last_child_cid
1246
+ FROM filtered_children
1247
+ WHERE child_rank = 1
1248
+ ),
1249
+ stale_child_counts AS (
1250
+ SELECT parent.cid
1251
+ FROM ${TABLES.COMMENTS} parent
1252
+ JOIN ${TABLES.COMMENT_UPDATES} cu_parent ON parent.cid = cu_parent.cid
1253
+ LEFT JOIN child_counts cc ON cc.cid = parent.cid
1254
+ WHERE (parent.pendingApproval IS NULL OR parent.pendingApproval != 1)
1255
+ AND COALESCE(cc.actual_child_count, 0) != COALESCE(cu_parent.childCount, 0)
1256
+ ),
1257
+ stale_last_child_cids AS (
1258
+ SELECT parent.cid
1259
+ FROM ${TABLES.COMMENTS} parent
1260
+ JOIN ${TABLES.COMMENT_UPDATES} cu_parent ON parent.cid = cu_parent.cid
1261
+ LEFT JOIN last_child_cids lc ON lc.cid = parent.cid
1262
+ WHERE (parent.pendingApproval IS NULL OR parent.pendingApproval != 1)
1263
+ AND COALESCE(lc.actual_last_child_cid, '') != COALESCE(cu_parent.lastChildCid, '')
1264
+ ),
1265
+ replies_json AS (
1266
+ SELECT
1267
+ cu_parent.cid AS parentCid,
1268
+ json_extract(comment_entry.value, '$.comment.cid') AS comment_child_cid,
1269
+ json_extract(comment_entry.value, '$.commentUpdate.cid') AS update_child_cid,
1270
+ json_extract(comment_entry.value, '$.commentUpdate.updatedAt') AS json_child_updated_at
1271
+ FROM ${TABLES.COMMENT_UPDATES} cu_parent
1272
+ INNER JOIN ${TABLES.COMMENTS} parent ON parent.cid = cu_parent.cid
1273
+ JOIN json_each(cu_parent.replies, '$.pages') pages
1274
+ JOIN json_each(pages.value, '$.comments') comment_entry
1275
+ WHERE cu_parent.replies IS NOT NULL
1276
+ AND json_type(cu_parent.replies, '$.pages') = 'object'
1277
+ AND (parent.pendingApproval IS NULL OR parent.pendingApproval != 1)
1278
+ ),
1279
+ stale_replies_json AS (
1280
+ SELECT r.parentCid AS cid
1281
+ FROM replies_json r
1282
+ LEFT JOIN ${TABLES.COMMENTS} existing_child ON existing_child.cid = COALESCE(r.comment_child_cid, r.update_child_cid)
1283
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} actual_child_update ON actual_child_update.cid = COALESCE(r.comment_child_cid, r.update_child_cid)
1284
+ WHERE COALESCE(r.comment_child_cid, r.update_child_cid) IS NOT NULL
1285
+ AND (
1286
+ existing_child.cid IS NULL
1287
+ OR actual_child_update.cid IS NULL
1288
+ OR (
1289
+ r.json_child_updated_at IS NOT NULL
1290
+ AND actual_child_update.cid IS NOT NULL
1291
+ AND CAST(r.json_child_updated_at AS INTEGER) < actual_child_update.updatedAt
1292
+ )
1293
+ )
1294
+ GROUP BY r.parentCid
1295
+ ),
1296
+ base_updates AS (
1297
+ SELECT * FROM direct_updates
1298
+ UNION SELECT c.* FROM ${TABLES.COMMENTS} c JOIN stale_child_counts scc ON c.cid = scc.cid
1299
+ UNION SELECT c.* FROM ${TABLES.COMMENTS} c JOIN stale_last_child_cids slc ON c.cid = slc.cid
1300
+ UNION SELECT c.* FROM ${TABLES.COMMENTS} c JOIN stale_replies_json srj ON c.cid = srj.cid
1301
+ ),
1302
+ authors_to_update AS (SELECT DISTINCT authorSignerAddress FROM base_updates),
1303
+ author_comments AS (
1304
+ SELECT c.* FROM ${TABLES.COMMENTS} c JOIN authors_to_update a ON c.authorSignerAddress = a.authorSignerAddress
1305
+ WHERE (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1306
+ ),
1307
+ comments_needing_update AS (
1308
+ SELECT * FROM base_updates
1309
+ UNION SELECT * FROM author_comments
1310
+ ),
1311
+ parent_chain AS (
1312
+ SELECT DISTINCT p.* FROM ${TABLES.COMMENTS} p JOIN comments_needing_update cnu ON p.cid = cnu.parentCid
1313
+ WHERE p.cid IS NOT NULL AND (p.pendingApproval IS NULL OR p.pendingApproval != 1)
1314
+ UNION
1315
+ SELECT DISTINCT p.* FROM ${TABLES.COMMENTS} p JOIN parent_chain pc ON p.cid = pc.parentCid
1316
+ WHERE p.cid IS NOT NULL AND (p.pendingApproval IS NULL OR p.pendingApproval != 1)
1317
+ ),
1318
+ all_updates AS (
1319
+ SELECT cid FROM comments_needing_update UNION SELECT cid FROM parent_chain
1320
+ )
1321
+ SELECT c.* FROM ${TABLES.COMMENTS} c JOIN all_updates au ON c.cid = au.cid
1322
+ WHERE (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1323
+ ORDER BY c.rowid
1324
+ `;
1325
+ const results = this._db.prepare(query).all();
1326
+ return results.map((r) => this._parseCommentsTableRow(r));
1327
+ }
1328
+ queryCommunityStats() {
1329
+ // if you change this logic, make sure to run stats.community.test.js
1330
+ const now = timestamp(); // All timestamps are in seconds
1331
+ const { clause: commentAddrClause, params: commentAddrParams } = this._communityAddressClauseNamed("comments", "statsComments");
1332
+ const { clause: votesAddrClause, params: votesAddrParams } = this._communityAddressClauseNamed("comments_for_votes", "statsVotes");
1333
+ const removedCommentsClause = this._removedClause("cu_comments");
1334
+ const deletedCommentsClause = this._deletedFromUpdatesClause("cu_comments");
1335
+ const removedVotesClause = this._removedClause("cu_votes");
1336
+ const deletedVotesClause = this._deletedFromUpdatesClause("cu_votes");
1337
+ const pendingCommentsClause = this._pendingApprovalClause("comments");
1338
+ const postAndReplyCountsQuery = `
1339
+ SELECT
1340
+ COALESCE(SUM(CASE WHEN comments.depth = 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.HOUR} THEN 1 ELSE 0 END), 0) AS hourPostCount,
1341
+ COALESCE(SUM(CASE WHEN comments.depth = 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.DAY} THEN 1 ELSE 0 END), 0) AS dayPostCount,
1342
+ COALESCE(SUM(CASE WHEN comments.depth = 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.WEEK} THEN 1 ELSE 0 END), 0) AS weekPostCount,
1343
+ COALESCE(SUM(CASE WHEN comments.depth = 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.MONTH} THEN 1 ELSE 0 END), 0) AS monthPostCount,
1344
+ COALESCE(SUM(CASE WHEN comments.depth = 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.YEAR} THEN 1 ELSE 0 END), 0) AS yearPostCount,
1345
+ COALESCE(SUM(CASE WHEN comments.depth = 0 THEN 1 ELSE 0 END), 0) AS allPostCount,
1346
+ COALESCE(SUM(CASE WHEN comments.depth > 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.HOUR} THEN 1 ELSE 0 END), 0) AS hourReplyCount,
1347
+ COALESCE(SUM(CASE WHEN comments.depth > 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.DAY} THEN 1 ELSE 0 END), 0) AS dayReplyCount,
1348
+ COALESCE(SUM(CASE WHEN comments.depth > 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.WEEK} THEN 1 ELSE 0 END), 0) AS weekReplyCount,
1349
+ COALESCE(SUM(CASE WHEN comments.depth > 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.MONTH} THEN 1 ELSE 0 END), 0) AS monthReplyCount,
1350
+ COALESCE(SUM(CASE WHEN comments.depth > 0 AND comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.YEAR} THEN 1 ELSE 0 END), 0) AS yearReplyCount,
1351
+ COALESCE(SUM(CASE WHEN comments.depth > 0 THEN 1 ELSE 0 END), 0) AS allReplyCount
1352
+ FROM ${TABLES.COMMENTS} AS comments
1353
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} AS cu_comments ON cu_comments.cid = comments.cid
1354
+ WHERE ${commentAddrClause}
1355
+ AND ${removedCommentsClause}
1356
+ AND ${deletedCommentsClause}
1357
+ AND ${pendingCommentsClause}
1358
+ `;
1359
+ const postAndReplyCounts = this._db.prepare(postAndReplyCountsQuery).get(commentAddrParams);
1360
+ const activeIdentityRowsQuery = `
1361
+ SELECT
1362
+ activity.authorSignerAddress AS authorSignerAddress,
1363
+ MAX(activity.hour_active) AS hourActive,
1364
+ MAX(activity.day_active) AS dayActive,
1365
+ MAX(activity.week_active) AS weekActive,
1366
+ MAX(activity.month_active) AS monthActive,
1367
+ MAX(activity.year_active) AS yearActive
1368
+ FROM (
1369
+ SELECT
1370
+ comments.authorSignerAddress,
1371
+ CASE WHEN comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.HOUR} THEN 1 ELSE 0 END AS hour_active,
1372
+ CASE WHEN comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.DAY} THEN 1 ELSE 0 END AS day_active,
1373
+ CASE WHEN comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.WEEK} THEN 1 ELSE 0 END AS week_active,
1374
+ CASE WHEN comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.MONTH} THEN 1 ELSE 0 END AS month_active,
1375
+ CASE WHEN comments.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.YEAR} THEN 1 ELSE 0 END AS year_active
1376
+ FROM ${TABLES.COMMENTS} AS comments
1377
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} AS cu_comments ON cu_comments.cid = comments.cid
1378
+ WHERE ${commentAddrClause}
1379
+ AND ${removedCommentsClause}
1380
+ AND ${deletedCommentsClause}
1381
+ AND ${pendingCommentsClause}
1382
+ UNION ALL
1383
+ SELECT
1384
+ votes.authorSignerAddress,
1385
+ CASE WHEN votes.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.HOUR} THEN 1 ELSE 0 END AS hour_active,
1386
+ CASE WHEN votes.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.DAY} THEN 1 ELSE 0 END AS day_active,
1387
+ CASE WHEN votes.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.WEEK} THEN 1 ELSE 0 END AS week_active,
1388
+ CASE WHEN votes.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.MONTH} THEN 1 ELSE 0 END AS month_active,
1389
+ CASE WHEN votes.timestamp >= ${now - TIMEFRAMES_TO_SECONDS.YEAR} THEN 1 ELSE 0 END AS year_active
1390
+ FROM ${TABLES.VOTES} AS votes
1391
+ INNER JOIN ${TABLES.COMMENTS} AS comments_for_votes ON comments_for_votes.cid = votes.commentCid
1392
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} AS cu_votes ON cu_votes.cid = comments_for_votes.cid
1393
+ WHERE ${votesAddrClause}
1394
+ AND ${removedVotesClause}
1395
+ AND ${deletedVotesClause}
1396
+ ) AS activity
1397
+ GROUP BY activity.authorSignerAddress
1398
+ `;
1399
+ const activeIdentityRows = this._db
1400
+ .prepare(activeIdentityRowsQuery)
1401
+ .all({ ...commentAddrParams, ...votesAddrParams });
1402
+ const canonicalAddressesByAlias = new Map();
1403
+ if (activeIdentityRows.length > 0) {
1404
+ const uniqueActiveAddresses = [...new Set(activeIdentityRows.map((row) => row.authorSignerAddress))];
1405
+ const aliasPlaceholders = uniqueActiveAddresses.map(() => "?").join(", ");
1406
+ const aliasesQuery = `
1407
+ SELECT DISTINCT
1408
+ comments.authorSignerAddress AS aliasSignerAddress,
1409
+ alias.originalAuthorSignerPublicKey AS originalAuthorSignerPublicKey
1410
+ FROM ${TABLES.PSEUDONYMITY_ALIASES} AS alias
1411
+ INNER JOIN ${TABLES.COMMENTS} AS comments ON comments.cid = alias.commentCid
1412
+ WHERE comments.authorSignerAddress IN (${aliasPlaceholders})
1413
+ `;
1414
+ const aliasRows = this._db.prepare(aliasesQuery).all(...uniqueActiveAddresses);
1415
+ for (const aliasRow of aliasRows) {
1416
+ let originalAuthorAddress;
1417
+ try {
1418
+ originalAuthorAddress = getPKCAddressFromPublicKeySync(aliasRow.originalAuthorSignerPublicKey);
1419
+ }
1420
+ catch {
1421
+ throw new Error(`Failed to resolve original author address for alias signer address ${aliasRow.aliasSignerAddress}`);
1422
+ }
1423
+ const existingCanonicalAddress = canonicalAddressesByAlias.get(aliasRow.aliasSignerAddress);
1424
+ if (existingCanonicalAddress && existingCanonicalAddress !== originalAuthorAddress) {
1425
+ throw new Error(`Inconsistent pseudonymity alias mappings for signer address ${aliasRow.aliasSignerAddress}`);
1426
+ }
1427
+ canonicalAddressesByAlias.set(aliasRow.aliasSignerAddress, originalAuthorAddress);
1428
+ }
1429
+ }
1430
+ const canonicalActivityByAddress = new Map();
1431
+ for (const activeIdentityRow of activeIdentityRows) {
1432
+ const canonicalAddress = canonicalAddressesByAlias.get(activeIdentityRow.authorSignerAddress) || activeIdentityRow.authorSignerAddress;
1433
+ const existing = canonicalActivityByAddress.get(canonicalAddress);
1434
+ if (!existing) {
1435
+ canonicalActivityByAddress.set(canonicalAddress, {
1436
+ hourActive: activeIdentityRow.hourActive,
1437
+ dayActive: activeIdentityRow.dayActive,
1438
+ weekActive: activeIdentityRow.weekActive,
1439
+ monthActive: activeIdentityRow.monthActive,
1440
+ yearActive: activeIdentityRow.yearActive
1441
+ });
1442
+ continue;
1443
+ }
1444
+ existing.hourActive = Math.max(existing.hourActive, activeIdentityRow.hourActive);
1445
+ existing.dayActive = Math.max(existing.dayActive, activeIdentityRow.dayActive);
1446
+ existing.weekActive = Math.max(existing.weekActive, activeIdentityRow.weekActive);
1447
+ existing.monthActive = Math.max(existing.monthActive, activeIdentityRow.monthActive);
1448
+ existing.yearActive = Math.max(existing.yearActive, activeIdentityRow.yearActive);
1449
+ }
1450
+ const activeUserCounts = {
1451
+ hourActiveUserCount: 0,
1452
+ dayActiveUserCount: 0,
1453
+ weekActiveUserCount: 0,
1454
+ monthActiveUserCount: 0,
1455
+ yearActiveUserCount: 0,
1456
+ allActiveUserCount: canonicalActivityByAddress.size
1457
+ };
1458
+ for (const canonicalActivity of canonicalActivityByAddress.values()) {
1459
+ if (canonicalActivity.hourActive > 0)
1460
+ activeUserCounts.hourActiveUserCount++;
1461
+ if (canonicalActivity.dayActive > 0)
1462
+ activeUserCounts.dayActiveUserCount++;
1463
+ if (canonicalActivity.weekActive > 0)
1464
+ activeUserCounts.weekActiveUserCount++;
1465
+ if (canonicalActivity.monthActive > 0)
1466
+ activeUserCounts.monthActiveUserCount++;
1467
+ if (canonicalActivity.yearActive > 0)
1468
+ activeUserCounts.yearActiveUserCount++;
1469
+ }
1470
+ return {
1471
+ ...activeUserCounts,
1472
+ ...postAndReplyCounts
1473
+ };
1474
+ }
1475
+ queryCommentsUnderComment(parentCid) {
1476
+ const results = this._db.prepare(`SELECT * FROM ${TABLES.COMMENTS} WHERE parentCid = ?`).all(parentCid);
1477
+ return results.map((r) => this._parseCommentsTableRow(r));
1478
+ }
1479
+ queryFirstCommentWithDepth(commentDepth) {
1480
+ if (!Number.isInteger(commentDepth) || commentDepth < 0)
1481
+ throw new Error("commentDepth must be a non-negative integer");
1482
+ const { clause: addrClause, params: addrParams } = this._communityAddressClauseNamed("c", "firstComment");
1483
+ const exactDepthRow = this._db
1484
+ .prepare(`SELECT c.* FROM ${TABLES.COMMENTS} c
1485
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON cu.cid = c.cid
1486
+ WHERE ${addrClause}
1487
+ AND c.depth = @commentDepth
1488
+ ORDER BY COALESCE(cu.replyCount, 0) DESC
1489
+ LIMIT 1`)
1490
+ .get({ ...addrParams, commentDepth });
1491
+ if (exactDepthRow)
1492
+ return this._parseCommentsTableRow(exactDepthRow);
1493
+ const lowerDepthRow = this._db
1494
+ .prepare(`SELECT c.* FROM ${TABLES.COMMENTS} c
1495
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON cu.cid = c.cid
1496
+ WHERE ${addrClause}
1497
+ AND c.depth < @commentDepth
1498
+ ORDER BY c.depth DESC, COALESCE(cu.replyCount, 0) DESC
1499
+ LIMIT 1`)
1500
+ .get({ ...addrParams, commentDepth });
1501
+ if (!lowerDepthRow)
1502
+ return undefined;
1503
+ return this._parseCommentsTableRow(lowerDepthRow);
1504
+ }
1505
+ queryCombinedHashOfPendingComments() {
1506
+ const rows = this._db.prepare(`SELECT cid FROM ${TABLES.COMMENTS} WHERE pendingApproval = 1 ORDER BY rowid ASC`).all();
1507
+ const concatenated = rows.map((r) => r.cid).join("");
1508
+ const hash = sha256(concatenated);
1509
+ return hash;
1510
+ }
1511
+ queryComment(cid) {
1512
+ const row = this._db.prepare(`SELECT * FROM ${TABLES.COMMENTS} WHERE cid = ?`).get(cid);
1513
+ if (!row)
1514
+ return undefined;
1515
+ return this._parseCommentsTableRow(row);
1516
+ }
1517
+ queryPseudonymityAliasByCommentCid(commentCid) {
1518
+ const row = this._db
1519
+ .prepare(`SELECT commentCid, aliasPrivateKey, originalAuthorSignerPublicKey, originalAuthorDomain, mode, insertedAt FROM ${TABLES.PSEUDONYMITY_ALIASES} WHERE commentCid = ?`)
1520
+ .get(commentCid);
1521
+ return row;
1522
+ }
1523
+ queryPseudonymityAliasForPost(originalAuthorSignerPublicKey, postCid) {
1524
+ const row = this._db
1525
+ .prepare(`
1526
+ SELECT alias.commentCid, alias.aliasPrivateKey, alias.originalAuthorSignerPublicKey, alias.originalAuthorDomain, alias.mode, alias.insertedAt
1527
+ FROM ${TABLES.PSEUDONYMITY_ALIASES} AS alias
1528
+ INNER JOIN ${TABLES.COMMENTS} AS comments ON comments.cid = alias.commentCid
1529
+ WHERE alias.mode = 'per-post' AND alias.originalAuthorSignerPublicKey = ? AND comments.postCid = ?
1530
+ ORDER BY alias.insertedAt ASC
1531
+ LIMIT 1
1532
+ `)
1533
+ .get(originalAuthorSignerPublicKey, postCid);
1534
+ return row;
1535
+ }
1536
+ queryPseudonymityAliasForAuthor(originalAuthorSignerPublicKey) {
1537
+ const row = this._db
1538
+ .prepare(`
1539
+ SELECT commentCid, aliasPrivateKey, originalAuthorSignerPublicKey, originalAuthorDomain, mode, insertedAt
1540
+ FROM ${TABLES.PSEUDONYMITY_ALIASES}
1541
+ WHERE mode = 'per-author' AND originalAuthorSignerPublicKey = ?
1542
+ ORDER BY insertedAt ASC
1543
+ LIMIT 1
1544
+ `)
1545
+ .get(originalAuthorSignerPublicKey);
1546
+ return row;
1547
+ }
1548
+ _queryCommentAuthorAndParentWithoutParsing(cid) {
1549
+ const row = this._db.prepare(`SELECT authorSignerAddress, parentCid FROM ${TABLES.COMMENTS} WHERE cid = ?`).get(cid);
1550
+ if (!row)
1551
+ return undefined;
1552
+ const authorSignerAddress = typeof row.authorSignerAddress === "string" ? row.authorSignerAddress : undefined;
1553
+ const parentCid = typeof row.parentCid === "string" ? row.parentCid : row.parentCid === null ? null : undefined;
1554
+ return { authorSignerAddress, parentCid };
1555
+ }
1556
+ _queryCommentCounts(cid) {
1557
+ const { clause: addrClause, params: addrParams } = this._communityAddressClauseNamed("c", "cc");
1558
+ const query = `
1559
+ SELECT
1560
+ (SELECT COUNT(*) FROM ${TABLES.VOTES} WHERE commentCid = :cid AND vote = 1) AS upvoteCount,
1561
+ (SELECT COUNT(*) FROM ${TABLES.VOTES} WHERE commentCid = :cid AND vote = -1) AS downvoteCount,
1562
+ (
1563
+ WITH RECURSIVE descendants AS (
1564
+ SELECT c.cid FROM ${TABLES.COMMENTS} c
1565
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
1566
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS d ON c.cid = d.cid
1567
+ WHERE c.parentCid = :cid AND ${addrClause} AND (cu.removed IS NOT 1 AND cu.removed IS NOT TRUE) AND (d.deleted_flag IS NULL OR d.deleted_flag != 1)
1568
+ UNION ALL
1569
+ SELECT c.cid FROM ${TABLES.COMMENTS} c
1570
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
1571
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS d ON c.cid = d.cid
1572
+ JOIN descendants desc_nodes ON c.parentCid = desc_nodes.cid
1573
+ WHERE ${addrClause} AND (cu.removed IS NOT 1 AND cu.removed IS NOT TRUE) AND (d.deleted_flag IS NULL OR d.deleted_flag != 1)
1574
+ ) SELECT COUNT(*) FROM descendants
1575
+ ) AS replyCount,
1576
+ (
1577
+ SELECT COUNT(*) FROM ${TABLES.COMMENTS} c
1578
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
1579
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS d ON c.cid = d.cid
1580
+ WHERE c.parentCid = :cid AND ${addrClause} AND (cu.removed IS NOT 1 AND cu.removed IS NOT TRUE) AND (d.deleted_flag IS NULL OR d.deleted_flag != 1)
1581
+ ) AS childCount
1582
+ `;
1583
+ return this._db.prepare(query).get({ cid, ...addrParams });
1584
+ }
1585
+ queryPostsWithOutdatedBuckets(buckets) {
1586
+ const currentTimestampSeconds = timestamp(); // timestamp is in seconds
1587
+ const maxBucket = Math.max(...buckets);
1588
+ const caseClauses = buckets
1589
+ .sort((a, b) => a - b)
1590
+ .map((bucket) => `WHEN (${currentTimestampSeconds} - c.timestamp) <= ${bucket} THEN ${bucket}`)
1591
+ .join(" ");
1592
+ const { clause: addrClause, params: addrParams } = this._communityAddressClause("c");
1593
+ const query = `
1594
+ WITH post_data AS (
1595
+ SELECT c.cid, c.timestamp, cu.postUpdatesBucket AS current_bucket,
1596
+ CASE ${caseClauses} ELSE ${maxBucket} END AS new_bucket
1597
+ FROM ${TABLES.COMMENTS} as c INNER JOIN ${TABLES.COMMENT_UPDATES} as cu ON c.cid = cu.cid
1598
+ WHERE ${addrClause}
1599
+ AND (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1600
+ AND cu.postUpdatesBucket IS NOT NULL AND cu.postUpdatesBucket != ?
1601
+ ) SELECT cid, timestamp, current_bucket AS currentBucket, new_bucket AS newBucket
1602
+ FROM post_data WHERE current_bucket != new_bucket
1603
+ `;
1604
+ return this._db.prepare(query).all(...addrParams, maxBucket);
1605
+ }
1606
+ _queryLatestAuthorEdit(cid, authorSignerAddress) {
1607
+ const row = this._db
1608
+ .prepare(`
1609
+ SELECT * FROM ${TABLES.COMMENT_EDITS}
1610
+ WHERE commentCid = ? AND authorSignerAddress = ? AND (isAuthorEdit = 1)
1611
+ ORDER BY rowid DESC LIMIT 1
1612
+ `)
1613
+ .get(cid, authorSignerAddress);
1614
+ if (!row)
1615
+ return undefined;
1616
+ const parsed = this._spreadExtraProps(this._parseCommentEditsRow(row));
1617
+ const signedKeys = parsed.signature.signedPropertyNames;
1618
+ const commentEditFields = remeda.keys.strict(CommentEditPubsubMessagePublicationSchema.shape);
1619
+ return remeda.pick(parsed, ["signature", ...signedKeys, ...commentEditFields]);
1620
+ }
1621
+ removeCommentFromPendingApproval(comment) {
1622
+ const log = Logger("pkc-js:local-community:db-handler:removeCommentFromPendingApproval");
1623
+ const stmt = this._db.prepare(`UPDATE ${TABLES.COMMENTS} SET pendingApproval = 0 WHERE cid = ?`);
1624
+ const res = stmt.run(comment.cid);
1625
+ log.trace(`Removed pendingApproval for cid=${comment.cid}, changes=${res.changes}`);
1626
+ }
1627
+ approvePendingComment(comment) {
1628
+ const log = Logger("pkc-js:local-community:db-handler:approvePendingComment");
1629
+ const assignNumbers = this._db.transaction((commentCid) => {
1630
+ this._db.prepare(`UPDATE ${TABLES.COMMENTS} SET pendingApproval = 0 WHERE cid = ?`).run(commentCid);
1631
+ return this._assignNumbersForComment(commentCid);
1632
+ });
1633
+ const numbers = assignNumbers(comment.cid);
1634
+ log.trace(`Approved pending comment cid=${comment.cid}`, numbers);
1635
+ return numbers;
1636
+ }
1637
+ getNextCommentNumbers(depth) {
1638
+ const pendingClause = this._pendingApprovalClause("c");
1639
+ const maxNumberRow = this._db
1640
+ .prepare(`SELECT COALESCE(MAX(number), 0) AS maxNumber FROM ${TABLES.COMMENTS} c WHERE number IS NOT NULL AND ${pendingClause}`)
1641
+ .get();
1642
+ const number = (maxNumberRow?.maxNumber || 0) + 1;
1643
+ if (depth !== 0)
1644
+ return { number };
1645
+ const maxPostNumberRow = this._db
1646
+ .prepare(`SELECT COALESCE(MAX(postNumber), 0) AS maxPostNumber FROM ${TABLES.COMMENTS} c WHERE postNumber IS NOT NULL AND depth = 0 AND ${pendingClause}`)
1647
+ .get();
1648
+ const postNumber = (maxPostNumberRow?.maxPostNumber || 0) + 1;
1649
+ return { number, postNumber };
1650
+ }
1651
+ _assignNumbersForComment(commentCid) {
1652
+ const commentRow = this._db
1653
+ .prepare(`SELECT depth, pendingApproval, number, postNumber FROM ${TABLES.COMMENTS} WHERE cid = ? LIMIT 1`)
1654
+ .get(commentCid);
1655
+ if (!commentRow)
1656
+ throw Error(`Failed to query comment row for ${commentCid}`);
1657
+ if (commentRow.pendingApproval === 1)
1658
+ return {};
1659
+ if (typeof commentRow.number === "number" && commentRow.number > 0) {
1660
+ return {
1661
+ number: commentRow.number,
1662
+ ...(typeof commentRow.postNumber === "number" && commentRow.postNumber > 0 ? { postNumber: commentRow.postNumber } : {})
1663
+ };
1664
+ }
1665
+ const pendingClause = this._pendingApprovalClause("c");
1666
+ const maxNumberRow = this._db
1667
+ .prepare(`SELECT COALESCE(MAX(number), 0) AS maxNumber FROM ${TABLES.COMMENTS} c WHERE number IS NOT NULL AND ${pendingClause}`)
1668
+ .get();
1669
+ const number = (maxNumberRow?.maxNumber || 0) + 1;
1670
+ let postNumber;
1671
+ if (commentRow.depth === 0) {
1672
+ const maxPostNumberRow = this._db
1673
+ .prepare(`SELECT COALESCE(MAX(postNumber), 0) AS maxPostNumber FROM ${TABLES.COMMENTS} c WHERE postNumber IS NOT NULL AND depth = 0 AND ${pendingClause}`)
1674
+ .get();
1675
+ postNumber = (maxPostNumberRow?.maxPostNumber || 0) + 1;
1676
+ }
1677
+ this._db
1678
+ .prepare(`UPDATE ${TABLES.COMMENTS} SET number = ?, postNumber = ? WHERE cid = ?`)
1679
+ .run(number, postNumber ?? null, commentCid);
1680
+ return { number, ...(postNumber !== undefined ? { postNumber } : {}) };
1681
+ }
1682
+ // Remove oldest comments pending approval when exceeding the configured limit
1683
+ removeOldestPendingCommentIfWeHitMaxPendingCount(maxPendingApprovalCount) {
1684
+ const log = Logger("pkc-js:local-community:db-handler:removeOldestPendingCommentIfWeHitMaxPendingCount");
1685
+ // Assume maxPendingApprovalCount is a valid integer > 0
1686
+ try {
1687
+ const { cnt } = this._db.prepare(`SELECT COUNT(1) as cnt FROM ${TABLES.COMMENTS} WHERE pendingApproval = 1`).get();
1688
+ if (cnt <= maxPendingApprovalCount)
1689
+ return;
1690
+ const toRemove = cnt - maxPendingApprovalCount;
1691
+ const oldest = this._db
1692
+ .prepare(`SELECT cid FROM ${TABLES.COMMENTS} WHERE pendingApproval = 1 ORDER BY rowid ASC LIMIT ?`)
1693
+ .all(toRemove);
1694
+ if (oldest.length === 0)
1695
+ return;
1696
+ log(`Evicting ${oldest.length} oldest pending comments (count=${cnt}, limit=${maxPendingApprovalCount})`);
1697
+ this.createTransaction();
1698
+ try {
1699
+ for (const { cid } of oldest)
1700
+ this.purgeComment(cid);
1701
+ this.commitTransaction();
1702
+ }
1703
+ catch (e) {
1704
+ this.rollbackTransaction();
1705
+ throw e;
1706
+ }
1707
+ }
1708
+ catch (e) {
1709
+ log.error("Failed to enforce maxPendingApprovalCount", e);
1710
+ }
1711
+ }
1712
+ purgeDisapprovedCommentsOlderThan(retentionSeconds) {
1713
+ const log = Logger("pkc-js:local-community:db-handler:purgeDisapprovedCommentsOlderThan");
1714
+ if (!Number.isFinite(retentionSeconds) || retentionSeconds <= 0)
1715
+ return;
1716
+ const now = timestamp();
1717
+ const cutoffTimestamp = now - retentionSeconds;
1718
+ const rows = this._db
1719
+ .prepare(`
1720
+ WITH first_disapproved AS (
1721
+ SELECT commentCid AS cid,
1722
+ MIN(timestamp) AS first_disapproved_at
1723
+ FROM ${TABLES.COMMENT_MODERATIONS}
1724
+ WHERE json_type(commentModeration, '$.approved') = 'false'
1725
+ GROUP BY commentCid
1726
+ )
1727
+ SELECT c.cid AS cid,
1728
+ c.parentCid AS parentCid,
1729
+ COALESCE(fd.first_disapproved_at, cu.updatedAt) AS firstDisapprovedAt,
1730
+ cu.postUpdatesBucket AS postUpdatesBucket
1731
+ FROM ${TABLES.COMMENT_UPDATES} cu
1732
+ INNER JOIN ${TABLES.COMMENTS} c ON c.cid = cu.cid
1733
+ LEFT JOIN first_disapproved fd ON fd.cid = cu.cid
1734
+ WHERE (COALESCE(cu.approved, 1) = 0 OR cu.approved = 'false')
1735
+ AND COALESCE(fd.first_disapproved_at, cu.updatedAt) <= ?
1736
+ `)
1737
+ .all(cutoffTimestamp);
1738
+ if (rows.length === 0)
1739
+ return;
1740
+ log(`Purging ${rows.length} disapproved comments older than ${retentionSeconds} seconds (cutoff ${cutoffTimestamp}).`);
1741
+ const purgedDetails = [];
1742
+ for (const row of rows) {
1743
+ const purgedTableRows = this.purgeComment(row.cid);
1744
+ purgedDetails.push({
1745
+ cid: row.cid,
1746
+ parentCid: row.parentCid,
1747
+ postUpdatesBucket: row.postUpdatesBucket || undefined,
1748
+ purgedTableRows
1749
+ });
1750
+ }
1751
+ return purgedDetails;
1752
+ }
1753
+ _queryLatestModeratorReason(comment) {
1754
+ const result = this._db
1755
+ .prepare(`
1756
+ SELECT json_extract(commentModeration, '$.reason') AS reason FROM ${TABLES.COMMENT_MODERATIONS}
1757
+ WHERE commentCid = ? AND json_extract(commentModeration, '$.reason') IS NOT NULL ORDER BY rowid DESC LIMIT 1
1758
+ `)
1759
+ .get(comment.cid);
1760
+ if (!result)
1761
+ return undefined;
1762
+ return result;
1763
+ }
1764
+ queryCommentFlagsSetByMod(cid) {
1765
+ const query = `
1766
+ WITH flags_with_rank AS (
1767
+ SELECT commentCid,
1768
+ json_extract(commentModeration, '$.spoiler') AS spoiler, json_extract(commentModeration, '$.pinned') AS pinned,
1769
+ json_extract(commentModeration, '$.locked') AS locked, json_extract(commentModeration, '$.archived') AS archived,
1770
+ json_extract(commentModeration, '$.removed') AS removed,
1771
+ json_extract(commentModeration, '$.nsfw') AS nsfw,
1772
+ ROW_NUMBER() OVER (PARTITION BY commentCid, CASE WHEN json_extract(commentModeration, '$.spoiler') IS NOT NULL THEN 'spoiler' ELSE NULL END ORDER BY rowid DESC) AS spoiler_rank,
1773
+ ROW_NUMBER() OVER (PARTITION BY commentCid, CASE WHEN json_extract(commentModeration, '$.pinned') IS NOT NULL THEN 'pinned' ELSE NULL END ORDER BY rowid DESC) AS pinned_rank,
1774
+ ROW_NUMBER() OVER (PARTITION BY commentCid, CASE WHEN json_extract(commentModeration, '$.locked') IS NOT NULL THEN 'locked' ELSE NULL END ORDER BY rowid DESC) AS locked_rank,
1775
+ ROW_NUMBER() OVER (PARTITION BY commentCid, CASE WHEN json_extract(commentModeration, '$.archived') IS NOT NULL THEN 'archived' ELSE NULL END ORDER BY rowid DESC) AS archived_rank,
1776
+ ROW_NUMBER() OVER (PARTITION BY commentCid, CASE WHEN json_extract(commentModeration, '$.removed') IS NOT NULL THEN 'removed' ELSE NULL END ORDER BY rowid DESC) AS removed_rank,
1777
+ ROW_NUMBER() OVER (PARTITION BY commentCid, CASE WHEN json_extract(commentModeration, '$.nsfw') IS NOT NULL THEN 'nsfw' ELSE NULL END ORDER BY rowid DESC) AS nsfw_rank
1778
+ FROM ${TABLES.COMMENT_MODERATIONS} WHERE commentCid = ?
1779
+ )
1780
+ SELECT
1781
+ MAX(CASE WHEN spoiler IS NOT NULL AND spoiler_rank = 1 THEN spoiler ELSE NULL END) AS spoiler,
1782
+ MAX(CASE WHEN pinned IS NOT NULL AND pinned_rank = 1 THEN pinned ELSE NULL END) AS pinned,
1783
+ MAX(CASE WHEN locked IS NOT NULL AND locked_rank = 1 THEN locked ELSE NULL END) AS locked,
1784
+ MAX(CASE WHEN archived IS NOT NULL AND archived_rank = 1 THEN archived ELSE NULL END) AS archived,
1785
+ MAX(CASE WHEN removed IS NOT NULL AND removed_rank = 1 THEN removed ELSE NULL END) AS removed,
1786
+ MAX(CASE WHEN nsfw IS NOT NULL AND nsfw_rank = 1 THEN nsfw ELSE NULL END) AS nsfw
1787
+ FROM flags_with_rank
1788
+ `;
1789
+ const flags = this._db.prepare(query).get(cid);
1790
+ if (!flags)
1791
+ return {};
1792
+ return remeda.mapValues(removeNullUndefinedValues(flags), Boolean);
1793
+ }
1794
+ queryAuthorEditDeleted(cid) {
1795
+ const result = this._db
1796
+ .prepare(`
1797
+ SELECT deleted FROM ${TABLES.COMMENT_EDITS}
1798
+ WHERE commentCid = ? AND (isAuthorEdit = 1 OR isAuthorEdit = TRUE) AND deleted IS NOT NULL ORDER BY rowid DESC LIMIT 1
1799
+ `)
1800
+ .get(cid);
1801
+ return result && result.deleted !== null ? { deleted: Boolean(result.deleted) } : undefined;
1802
+ }
1803
+ _queryModCommentFlairs(comment) {
1804
+ const result = this._db
1805
+ .prepare(`
1806
+ SELECT json_extract(commentModeration, '$.flairs') AS flairs FROM ${TABLES.COMMENT_MODERATIONS}
1807
+ WHERE commentCid = ? AND json_extract(commentModeration, '$.flairs') IS NOT NULL ORDER BY rowid DESC LIMIT 1
1808
+ `)
1809
+ .get(comment.cid);
1810
+ if (!result)
1811
+ return undefined;
1812
+ return { flairs: JSON.parse(result.flairs) };
1813
+ }
1814
+ _queryLastChildCidAndLastReplyTimestamp(comment) {
1815
+ const lastChildCid = this._db
1816
+ .prepare(`SELECT c.cid FROM ${TABLES.COMMENTS} c
1817
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON cu.cid = c.cid
1818
+ LEFT JOIN (
1819
+ SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}
1820
+ ) deleted_lookup ON deleted_lookup.cid = c.cid
1821
+ WHERE c.parentCid = ?
1822
+ AND (c.pendingApproval IS NULL OR c.pendingApproval != 1)
1823
+ AND COALESCE(cu.approved, 1) != 0
1824
+ AND (cu.removed IS NOT 1 AND cu.removed IS NOT TRUE)
1825
+ AND (deleted_lookup.deleted_flag IS NULL OR deleted_lookup.deleted_flag != 1)
1826
+ ORDER BY c.rowid DESC
1827
+ LIMIT 1`)
1828
+ .get(comment.cid);
1829
+ const lastReplyTimestamp = this.queryMaximumTimestampUnderComment(comment);
1830
+ return { lastChildCid: lastChildCid?.cid, lastReplyTimestamp };
1831
+ }
1832
+ _queryIsCommentApproved(comment) {
1833
+ const result = this._db
1834
+ .prepare(`
1835
+ SELECT json_extract(commentModeration, '$.approved') AS approved FROM ${TABLES.COMMENT_MODERATIONS}
1836
+ WHERE commentCid = ? AND json_extract(commentModeration, '$.approved') IS NOT NULL ORDER BY rowid DESC LIMIT 1
1837
+ `)
1838
+ .get(comment.cid);
1839
+ if (!result || result.approved === null)
1840
+ return undefined;
1841
+ return { approved: Boolean(result.approved) };
1842
+ }
1843
+ _calculateCommentNumbers(cid) {
1844
+ const commentRowMeta = this._db
1845
+ .prepare(`SELECT rowid as rowid, depth, pendingApproval, number, postNumber FROM ${TABLES.COMMENTS} WHERE cid = ?`)
1846
+ .get(cid);
1847
+ if (!commentRowMeta)
1848
+ throw Error(`Failed to query row metadata for comment ${cid}`);
1849
+ if (commentRowMeta.pendingApproval === 1)
1850
+ return {};
1851
+ let commentNumber = typeof commentRowMeta.number === "number" && commentRowMeta.number > 0 ? commentRowMeta.number : undefined;
1852
+ let postNumber = typeof commentRowMeta.postNumber === "number" && commentRowMeta.postNumber > 0 ? commentRowMeta.postNumber : undefined;
1853
+ if (commentNumber === undefined || (commentRowMeta.depth === 0 && postNumber === undefined)) {
1854
+ const existingNumbers = this._db
1855
+ .prepare(`SELECT number, postNumber FROM ${TABLES.COMMENT_UPDATES} WHERE cid = ? LIMIT 1`)
1856
+ .get(cid);
1857
+ if (commentNumber === undefined && typeof existingNumbers?.number === "number" && existingNumbers.number > 0)
1858
+ commentNumber = existingNumbers.number;
1859
+ if (commentRowMeta.depth === 0 && postNumber === undefined) {
1860
+ if (typeof existingNumbers?.postNumber === "number" && existingNumbers.postNumber > 0)
1861
+ postNumber = existingNumbers.postNumber;
1862
+ }
1863
+ }
1864
+ return {
1865
+ ...(commentNumber !== undefined ? { number: commentNumber } : undefined),
1866
+ ...(postNumber !== undefined ? { postNumber } : undefined)
1867
+ };
1868
+ }
1869
+ queryCalculatedCommentUpdate(opts) {
1870
+ const { comment, authorDomain } = opts;
1871
+ const authorCommunity = this.queryCommunityAuthorForCommentUpdate({
1872
+ authorSignerAddress: comment.authorSignerAddress,
1873
+ commentCid: comment.cid,
1874
+ authorDomain
1875
+ });
1876
+ const authorEdit = this._queryLatestAuthorEdit(comment.cid, comment.authorSignerAddress);
1877
+ const commentUpdateCounts = this._queryCommentCounts(comment.cid);
1878
+ const moderatorReason = this._queryLatestModeratorReason(comment);
1879
+ const commentFlags = this.queryCommentFlagsSetByMod(comment.cid);
1880
+ const commentModFlairs = this._queryModCommentFlairs(comment);
1881
+ const lastChildAndLastReplyTimestamp = this._queryLastChildCidAndLastReplyTimestamp(comment);
1882
+ const isThisCommentApproved = this._queryIsCommentApproved(comment);
1883
+ const removedFromApproved = isThisCommentApproved?.approved === false ? { removed: true } : undefined; // automatically add removed:true if approved=false. Will be overridden if there's commentFlags.removed
1884
+ const { number: commentNumber, postNumber } = this._calculateCommentNumbers(comment.cid);
1885
+ if (!authorCommunity)
1886
+ throw Error("Failed to query author.community in queryCalculatedCommentUpdate");
1887
+ return {
1888
+ ...(removedFromApproved ? removedFromApproved : undefined),
1889
+ cid: comment.cid,
1890
+ ...(commentNumber !== undefined ? { number: commentNumber } : undefined),
1891
+ ...(postNumber !== undefined ? { postNumber } : undefined),
1892
+ ...commentUpdateCounts,
1893
+ flairs: commentModFlairs?.flairs || authorEdit?.flairs,
1894
+ ...commentFlags,
1895
+ reason: moderatorReason?.reason,
1896
+ author: { community: authorCommunity },
1897
+ ...lastChildAndLastReplyTimestamp,
1898
+ ...(authorEdit ? { edit: authorEdit } : undefined),
1899
+ ...(isThisCommentApproved ? { approved: isThisCommentApproved.approved } : undefined)
1900
+ };
1901
+ }
1902
+ queryLatestPostCid() {
1903
+ return this._db
1904
+ .prepare(`SELECT c.cid FROM ${TABLES.COMMENTS} c
1905
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON cu.cid = c.cid
1906
+ WHERE c.depth = 0
1907
+ AND c.pendingApproval IS NOT 1
1908
+ AND COALESCE(cu.approved, 1) != 0
1909
+ ORDER BY c.rowid DESC
1910
+ LIMIT 1`)
1911
+ .get();
1912
+ }
1913
+ queryLatestCommentCid() {
1914
+ return this._db
1915
+ .prepare(`SELECT c.cid FROM ${TABLES.COMMENTS} c
1916
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON cu.cid = c.cid
1917
+ WHERE c.pendingApproval IS NOT 1
1918
+ AND COALESCE(cu.approved, 1) != 0
1919
+ ORDER BY c.rowid DESC
1920
+ LIMIT 1`)
1921
+ .get();
1922
+ }
1923
+ queryAllCommentsOrderedByIdAsc() {
1924
+ const results = this._db.prepare(`SELECT * FROM ${TABLES.COMMENTS} ORDER BY rowid ASC`).all();
1925
+ return results.map((r) => this._parseCommentsTableRow(r));
1926
+ }
1927
+ queryAuthorModEdits(opts) {
1928
+ const { authorSignerAddresses, authorDomain } = opts;
1929
+ if (authorSignerAddresses.length === 0 && !authorDomain)
1930
+ return {};
1931
+ const conditions = [];
1932
+ const params = [];
1933
+ if (authorSignerAddresses.length > 0) {
1934
+ const placeholders = authorSignerAddresses.map(() => "?").join(",");
1935
+ conditions.push(`targetAuthorSignerAddress IN (${placeholders})`);
1936
+ params.push(...authorSignerAddresses);
1937
+ }
1938
+ if (authorDomain) {
1939
+ conditions.push(`targetAuthorDomain = ?`);
1940
+ params.push(authorDomain);
1941
+ }
1942
+ // Query directly by targetAuthorSignerAddress or targetAuthorDomain to find bans/flairs even for purged comments
1943
+ const modAuthorEditsRaw = this._db
1944
+ .prepare(`
1945
+ SELECT json_extract(commentModeration, '$.author') AS commentAuthorJson FROM ${TABLES.COMMENT_MODERATIONS}
1946
+ WHERE (${conditions.join(" OR ")}) AND json_extract(commentModeration, '$.author') IS NOT NULL ORDER BY rowid DESC
1947
+ `)
1948
+ .all(...params);
1949
+ const modAuthorEdits = modAuthorEditsRaw.map((r) => JSON.parse(r.commentAuthorJson));
1950
+ const banAuthor = modAuthorEdits.find((modEdit) => typeof modEdit?.banExpiresAt === "number");
1951
+ const authorFlairsByMod = modAuthorEdits.find((modEdit) => modEdit?.flairs);
1952
+ const aggregateAuthor = {};
1953
+ if (banAuthor?.banExpiresAt)
1954
+ aggregateAuthor.banExpiresAt = banAuthor.banExpiresAt;
1955
+ if (authorFlairsByMod?.flairs)
1956
+ aggregateAuthor.flairs = authorFlairsByMod.flairs;
1957
+ return aggregateAuthor;
1958
+ }
1959
+ queryAuthorPublicationCounts(authorSignerAddress) {
1960
+ const pendingClause = this._pendingApprovalClause(TABLES.COMMENTS);
1961
+ const result = this._db
1962
+ .prepare(`
1963
+ SELECT
1964
+ COALESCE(SUM(CASE WHEN depth = 0 THEN 1 ELSE 0 END), 0) as postCount,
1965
+ COALESCE(SUM(CASE WHEN depth > 0 THEN 1 ELSE 0 END), 0) as replyCount
1966
+ FROM ${TABLES.COMMENTS}
1967
+ WHERE authorSignerAddress = ? AND ${pendingClause}
1968
+ `)
1969
+ .get(authorSignerAddress);
1970
+ return result;
1971
+ }
1972
+ queryCommunityAuthor(authorSignerAddress, authorDomain) {
1973
+ const authorSignerAddresses = new Set([authorSignerAddress]);
1974
+ // If the provided address is the original signer, include all alias signer addresses for that author.
1975
+ const aliasRowsForOriginal = this._db
1976
+ .prepare(`
1977
+ SELECT alias.commentCid, alias.originalAuthorSignerPublicKey
1978
+ FROM ${TABLES.PSEUDONYMITY_ALIASES} AS alias
1979
+ `)
1980
+ .all();
1981
+ for (const aliasRow of aliasRowsForOriginal) {
1982
+ try {
1983
+ const originalAddress = getPKCAddressFromPublicKeySync(aliasRow.originalAuthorSignerPublicKey);
1984
+ if (originalAddress === authorSignerAddress) {
1985
+ const commentRow = this._db
1986
+ .prepare(`SELECT authorSignerAddress FROM ${TABLES.COMMENTS} WHERE cid = ?`)
1987
+ .get(aliasRow.commentCid);
1988
+ if (commentRow?.authorSignerAddress)
1989
+ authorSignerAddresses.add(commentRow.authorSignerAddress);
1990
+ }
1991
+ }
1992
+ catch {
1993
+ // ignore malformed keys
1994
+ }
1995
+ }
1996
+ // If the provided address is an alias, include the original signer address for that alias.
1997
+ const aliasRowsForAliasAddress = this._db
1998
+ .prepare(`
1999
+ SELECT alias.originalAuthorSignerPublicKey
2000
+ FROM ${TABLES.PSEUDONYMITY_ALIASES} AS alias
2001
+ INNER JOIN ${TABLES.COMMENTS} AS comments ON comments.cid = alias.commentCid
2002
+ WHERE comments.authorSignerAddress = ?
2003
+ `)
2004
+ .all(authorSignerAddress);
2005
+ for (const aliasRow of aliasRowsForAliasAddress) {
2006
+ try {
2007
+ const originalAddress = getPKCAddressFromPublicKeySync(aliasRow.originalAuthorSignerPublicKey);
2008
+ authorSignerAddresses.add(originalAddress);
2009
+ }
2010
+ catch {
2011
+ // ignore malformed keys
2012
+ }
2013
+ }
2014
+ return this._queryCommunityAuthorByAddresses([...authorSignerAddresses], undefined, authorDomain);
2015
+ }
2016
+ /** Shared helper: query karma for a set of addresses, with optional separate addresses for mod edits */
2017
+ _queryCommunityAuthorByAddresses(karmaAddresses, modEditAddresses = karmaAddresses, authorDomain) {
2018
+ if (karmaAddresses.length === 0)
2019
+ return undefined;
2020
+ const placeholders = karmaAddresses.map(() => "?").join(", ");
2021
+ const modAuthorEdits = this.queryAuthorModEdits({ authorSignerAddresses: modEditAddresses, authorDomain });
2022
+ const authorCommentsData = this._db
2023
+ .prepare(`
2024
+ SELECT c.depth, c.rowid, c.timestamp, c.cid,
2025
+ COALESCE(SUM(CASE WHEN v.vote = 1 THEN 1 ELSE 0 END), 0) as upvoteCount,
2026
+ COALESCE(SUM(CASE WHEN v.vote = -1 THEN 1 ELSE 0 END), 0) as downvoteCount
2027
+ FROM ${TABLES.COMMENTS} c LEFT JOIN ${TABLES.VOTES} v ON c.cid = v.commentCid
2028
+ WHERE c.authorSignerAddress IN (${placeholders}) GROUP BY c.cid
2029
+ `)
2030
+ .all(...karmaAddresses);
2031
+ if (authorCommentsData.length === 0) {
2032
+ if (Object.keys(modAuthorEdits).length > 0) {
2033
+ return modAuthorEdits;
2034
+ }
2035
+ return undefined;
2036
+ }
2037
+ const authorPosts = authorCommentsData.filter((c) => c.depth === 0);
2038
+ const authorReplies = authorCommentsData.filter((c) => c.depth > 0);
2039
+ const postScore = remeda.sumBy(authorPosts, (p) => p.upvoteCount) - remeda.sumBy(authorPosts, (p) => p.downvoteCount);
2040
+ const replyScore = remeda.sumBy(authorReplies, (r) => r.upvoteCount) - remeda.sumBy(authorReplies, (r) => r.downvoteCount);
2041
+ const lastCommentCid = remeda.maxBy(authorCommentsData, (c) => c.rowid)?.cid;
2042
+ if (!lastCommentCid)
2043
+ throw Error("Failed to query communityAuthor.lastCommentCid");
2044
+ const firstCommentTimestamp = remeda.minBy(authorCommentsData, (c) => c.rowid)?.timestamp;
2045
+ if (typeof firstCommentTimestamp !== "number")
2046
+ throw Error("Failed to query communityAuthor.firstCommentTimestamp");
2047
+ return { postScore, replyScore, lastCommentCid, ...modAuthorEdits, firstCommentTimestamp };
2048
+ }
2049
+ /**
2050
+ * Returns author.community for CommentUpdates, respecting pseudonymity mode boundaries.
2051
+ *
2052
+ * The alias address already encodes the isolation boundary:
2053
+ * - per-reply: Each reply has a unique alias, so querying by alias = that one comment's karma
2054
+ * - per-post: All comments in a thread share an alias, so querying by alias = thread karma
2055
+ * - per-author: One alias for all comments, so querying by alias = total karma
2056
+ *
2057
+ * We query karma for ONLY the alias address (no lookup to other aliases like queryCommunityAuthor does),
2058
+ * but include mod edits from both alias and original author.
2059
+ */
2060
+ queryCommunityAuthorForCommentUpdate(opts) {
2061
+ const { authorSignerAddress, commentCid, authorDomain } = opts;
2062
+ // Check if this comment has a pseudonymity alias
2063
+ const aliasRow = this.queryPseudonymityAliasByCommentCid(commentCid);
2064
+ if (!aliasRow) {
2065
+ // No pseudonymity mode - use standard aggregated karma
2066
+ return this.queryCommunityAuthor(authorSignerAddress, authorDomain);
2067
+ }
2068
+ // Get original author's address for mod edits (bans/flairs are applied to original author)
2069
+ const modEditAddresses = [authorSignerAddress];
2070
+ try {
2071
+ const originalAddress = getPKCAddressFromPublicKeySync(aliasRow.originalAuthorSignerPublicKey);
2072
+ if (originalAddress !== authorSignerAddress) {
2073
+ modEditAddresses.push(originalAddress);
2074
+ }
2075
+ }
2076
+ catch {
2077
+ // ignore malformed keys
2078
+ }
2079
+ // For mod edits (bans/flairs), use the original author's domain if available
2080
+ const modEditDomain = aliasRow.originalAuthorDomain || authorDomain;
2081
+ // Query karma for just this alias, but mod edits from both alias and original
2082
+ return this._queryCommunityAuthorByAddresses([authorSignerAddress], modEditAddresses, modEditDomain);
2083
+ }
2084
+ _getAllDescendantCids(cid) {
2085
+ const allCids = [cid];
2086
+ const directChildren = this._db.prepare(`SELECT cid FROM ${TABLES.COMMENTS} WHERE parentCid = ?`).all(cid);
2087
+ for (const child of directChildren) {
2088
+ allCids.push(...this._getAllDescendantCids(child.cid));
2089
+ }
2090
+ return allCids;
2091
+ }
2092
+ purgeComment(cid, isNestedCall = false) {
2093
+ const log = Logger("pkc-js:local-community:db-handler:purgeComment");
2094
+ const purgedRecords = [];
2095
+ const detachedPageCids = [];
2096
+ if (!isNestedCall)
2097
+ this.createTransaction();
2098
+ try {
2099
+ // Get all CIDs that will be purged (including descendants) and their authors
2100
+ const allCidsToBeDeleted = this._getAllDescendantCids(cid);
2101
+ const allAffectedAuthors = new Set();
2102
+ const commentsToForceUpdate = new Set();
2103
+ // Collect all unique authorSignerAddresses from comments that will be purged
2104
+ if (!isNestedCall) {
2105
+ for (const cidToDelete of allCidsToBeDeleted) {
2106
+ const commentToDelete = this._queryCommentAuthorAndParentWithoutParsing(cidToDelete);
2107
+ if (!commentToDelete) {
2108
+ throw new Error(`Comment with cid ${cidToDelete} not found when attempting to purge`);
2109
+ }
2110
+ if (!commentToDelete.authorSignerAddress) {
2111
+ throw new Error(`Comment with cid ${cidToDelete} has no authorSignerAddress`);
2112
+ }
2113
+ allAffectedAuthors.add(commentToDelete.authorSignerAddress);
2114
+ // Collect comments that received votes FROM this purged comment
2115
+ const votesFromPurgedComment = this._db
2116
+ .prepare(`SELECT commentCid FROM ${TABLES.VOTES} WHERE authorSignerAddress = ?`)
2117
+ .all(commentToDelete.authorSignerAddress);
2118
+ votesFromPurgedComment.forEach((vote) => commentsToForceUpdate.add(vote.commentCid));
2119
+ // Collect comments that received edits FROM this purged comment
2120
+ const editsFromPurgedComment = this._db
2121
+ .prepare(`SELECT commentCid FROM ${TABLES.COMMENT_EDITS} WHERE authorSignerAddress = ?`)
2122
+ .all(commentToDelete.authorSignerAddress);
2123
+ editsFromPurgedComment.forEach((edit) => commentsToForceUpdate.add(edit.commentCid));
2124
+ // Collect parent comments of purged comments (for reply count updates)
2125
+ if (commentToDelete.parentCid) {
2126
+ const allAncestors = this.queryParentsCids({ parentCid: commentToDelete.parentCid });
2127
+ allAncestors.forEach((ancestor) => commentsToForceUpdate.add(ancestor.cid));
2128
+ }
2129
+ }
2130
+ }
2131
+ const directChildren = this._db.prepare(`SELECT cid FROM ${TABLES.COMMENTS} WHERE parentCid = ?`).all(cid);
2132
+ for (const child of directChildren)
2133
+ purgedRecords.push(...this.purgeComment(child.cid, true));
2134
+ const commentTableRow = this.queryComment(cid);
2135
+ this._db.prepare(`DELETE FROM ${TABLES.VOTES} WHERE commentCid = ?`).run(cid);
2136
+ this._db.prepare(`DELETE FROM ${TABLES.COMMENT_EDITS} WHERE commentCid = ?`).run(cid);
2137
+ this._db.prepare(`DELETE FROM ${TABLES.PSEUDONYMITY_ALIASES} WHERE commentCid = ?`).run(cid);
2138
+ const commentUpdate = this.queryStoredCommentUpdate({ cid });
2139
+ if (commentUpdate) {
2140
+ this._db.prepare(`DELETE FROM ${TABLES.COMMENT_UPDATES} WHERE cid = ?`).run(cid);
2141
+ }
2142
+ const deleteResult = this._db.prepare(`DELETE FROM ${TABLES.COMMENTS} WHERE cid = ?`).run(cid);
2143
+ if (deleteResult.changes > 0) {
2144
+ if (!commentTableRow)
2145
+ throw new Error(`Comment with cid ${cid} not found when attempting to purge`);
2146
+ purgedRecords.push({
2147
+ commentTableRow,
2148
+ commentUpdateTableRow: commentUpdate
2149
+ });
2150
+ }
2151
+ // Force update on all comments by all affected authors since their statistics have changed
2152
+ if (!isNestedCall && (allAffectedAuthors.size > 0 || commentsToForceUpdate.size > 0)) {
2153
+ const allAffectedAuthorCids = [];
2154
+ for (const authorSignerAddress of allAffectedAuthors) {
2155
+ const authorCommentCids = this._db
2156
+ .prepare(`SELECT cid FROM ${TABLES.COMMENTS} WHERE authorSignerAddress = ?`)
2157
+ .all(authorSignerAddress);
2158
+ allAffectedAuthorCids.push(...authorCommentCids.map((c) => c.cid));
2159
+ }
2160
+ // Combine author comments and comments that received votes/edits/replies from purged comments
2161
+ const allCommentsToUpdate = [...allAffectedAuthorCids, ...Array.from(commentsToForceUpdate)];
2162
+ // Force update on a random comment to ensure IPNS update triggers even if no other comments need updating
2163
+ // Make sure we don't select a comment that's being purged
2164
+ const placeholders = allCidsToBeDeleted.map(() => "?").join(",");
2165
+ const randomComment = this._db
2166
+ .prepare(`SELECT cid FROM ${TABLES.COMMENTS} WHERE cid NOT IN (${placeholders}) ORDER BY RANDOM() LIMIT 1`)
2167
+ .get(...allCidsToBeDeleted);
2168
+ if (randomComment) {
2169
+ allCommentsToUpdate.push(randomComment.cid);
2170
+ log(`Forcing update on random comment ${randomComment.cid} to ensure IPNS update after purge`);
2171
+ }
2172
+ else {
2173
+ log(`No comments left to force update after purge - IPNS will update to show empty community`);
2174
+ }
2175
+ if (allCommentsToUpdate.length > 0) {
2176
+ this.forceUpdateOnAllCommentsWithCid(allCommentsToUpdate);
2177
+ }
2178
+ }
2179
+ if (!isNestedCall)
2180
+ this.commitTransaction();
2181
+ const uniquePurgedRecords = remeda.uniqueBy(purgedRecords, (record) => record.commentTableRow.cid);
2182
+ return uniquePurgedRecords;
2183
+ }
2184
+ catch (error) {
2185
+ log.error(`Error during comment purge for ${cid}: ${error}`);
2186
+ if (!isNestedCall)
2187
+ this.rollbackTransaction();
2188
+ throw error;
2189
+ }
2190
+ }
2191
+ async changeDbFilename(oldDbName, newDbName) {
2192
+ const log = Logger("pkc-js:local-community:db-handler:changeDbFilename");
2193
+ if (this._db || this._keyv)
2194
+ await this.destoryConnection();
2195
+ this._transactionDepth = 0;
2196
+ const dataPath = this._community._pkc.dataPath;
2197
+ const oldPathString = path.join(dataPath, "communities", oldDbName);
2198
+ const newPathString = path.join(dataPath, "communities", newDbName);
2199
+ await fs.promises.mkdir(path.dirname(oldPathString), { recursive: true });
2200
+ await fs.promises.mkdir(path.dirname(newPathString), { recursive: true });
2201
+ try {
2202
+ // Check if oldDb exists before attempting to open for backup
2203
+ if (!fs.existsSync(oldPathString)) {
2204
+ log(`Old DB file ${oldPathString} does not exist. Cannot backup/rename.`);
2205
+ // If old doesn't exist, maybe we just want to set up the new path?
2206
+ // For now, this will mean the operation can't proceed as intended.
2207
+ }
2208
+ else {
2209
+ const sourceDb = new Database(oldPathString, { fileMustExist: true });
2210
+ await sourceDb.backup(newPathString); // backup is synchronous in better-sqlite3 v8+
2211
+ sourceDb.close();
2212
+ if (os.type() === "Windows_NT")
2213
+ await deleteOldCommunityInWindows(oldPathString, this._community._pkc);
2214
+ else
2215
+ await fs.promises.rm(oldPathString, { force: true });
2216
+ }
2217
+ }
2218
+ catch (error) {
2219
+ log.error(`Failed to backup/rename database from ${oldPathString} to ${newPathString}: `, error);
2220
+ throw error;
2221
+ }
2222
+ this._dbConfig = { ...this._dbConfig, filename: newPathString };
2223
+ log(`Changed db path from (${oldPathString}) to (${newPathString})`);
2224
+ }
2225
+ async lockCommunityStart(communityAddress = this._community.address) {
2226
+ const log = Logger("pkc-js:local-community:db-handler:lock:start");
2227
+ const lockfilePath = path.join(this._community._pkc.dataPath, "communities", `${communityAddress}.start.lock`);
2228
+ const communityDbPath = path.join(this._community._pkc.dataPath, "communities", communityAddress);
2229
+ try {
2230
+ await lockfile.lock(communityDbPath, {
2231
+ lockfilePath,
2232
+ onCompromised: () => { } // Temporary bandaid for the moment. Should be deleted later
2233
+ });
2234
+ log(`Locked the start of community (${communityAddress}) successfully`);
2235
+ }
2236
+ catch (e) {
2237
+ if (e instanceof Error && e.message === "Lock file is already being held")
2238
+ throw new PKCError("ERR_COMMUNITY_ALREADY_STARTED", { communityAddress: communityAddress, error: e });
2239
+ else {
2240
+ log(`Error while trying to lock start of community (${communityAddress}): ${e}`);
2241
+ throw e;
2242
+ }
2243
+ }
2244
+ }
2245
+ async unlockCommunityStart(communityAddress = this._community.address) {
2246
+ const log = Logger("pkc-js:local-community:db-handler:unlock:start");
2247
+ log.trace(`Attempting to unlock the start of community (${communityAddress})`);
2248
+ const lockfilePath = path.join(this._community._pkc.dataPath, "communities", `${communityAddress}.start.lock`);
2249
+ const communityDbPath = path.join(this._community._pkc.dataPath, "communities", communityAddress);
2250
+ if (!fs.existsSync(lockfilePath) || !fs.existsSync(communityDbPath))
2251
+ return;
2252
+ try {
2253
+ await lockfile.unlock(communityDbPath, { lockfilePath });
2254
+ log(`Unlocked start of community (${communityAddress})`);
2255
+ }
2256
+ catch (e) {
2257
+ log(`Error while trying to unlock start of community (${communityAddress}): ${e}`);
2258
+ throw e;
2259
+ }
2260
+ }
2261
+ async isCommunityStartLocked(communityAddress = this._community.address) {
2262
+ const lockfilePath = path.join(this._community._pkc.dataPath, "communities", `${communityAddress}.start.lock`);
2263
+ const communityDbPath = path.join(this._community._pkc.dataPath, "communities", communityAddress);
2264
+ const isLocked = await lockfile.check(communityDbPath, { lockfilePath, realpath: false, stale: 10000 });
2265
+ return isLocked;
2266
+ }
2267
+ // Community state lock
2268
+ async lockCommunityState() {
2269
+ const log = Logger("pkc-js:local-community:db-handler:lock:lockCommunityState");
2270
+ const lockfilePath = path.join(this._community._pkc.dataPath, "communities", `${this._community.address}.state.lock`);
2271
+ const communityDbPath = path.join(this._community._pkc.dataPath, "communities", this._community.address);
2272
+ try {
2273
+ await lockfile.lock(communityDbPath, {
2274
+ lockfilePath,
2275
+ retries: 5,
2276
+ onCompromised: () => { }
2277
+ });
2278
+ }
2279
+ catch (e) {
2280
+ log.error(`Error when attempting to lock community state`, this._community.address, e);
2281
+ if (e instanceof Error && e.message === "Lock file is already being held")
2282
+ throw new PKCError("ERR_COMMUNITY_STATE_LOCKED", { communityAddress: this._community.address, error: e });
2283
+ // Not sure, do we need to throw error here
2284
+ }
2285
+ }
2286
+ async unlockCommunityState() {
2287
+ const log = Logger("pkc-js:local-community:db-handler:lock:unlockCommunityState");
2288
+ const lockfilePath = path.join(this._community._pkc.dataPath, "communities", `${this._community.address}.state.lock`);
2289
+ const communityDbPath = path.join(this._community._pkc.dataPath, "communities", this._community.address);
2290
+ if (!fs.existsSync(lockfilePath))
2291
+ return;
2292
+ try {
2293
+ await lockfile.unlock(communityDbPath, { lockfilePath });
2294
+ }
2295
+ catch (e) {
2296
+ log.error(`Error when attempting to unlock community state`, this._community.address, e);
2297
+ if (e instanceof Error && "code" in e && e.code !== "ENOTACQUIRED")
2298
+ throw e;
2299
+ }
2300
+ }
2301
+ communityDbExists() {
2302
+ const communityDbPath = path.join(this._community._pkc.dataPath, "communities", this._community.address);
2303
+ return fs.existsSync(communityDbPath);
2304
+ }
2305
+ markCommentsAsPublishedToPostUpdates(commentCids) {
2306
+ if (commentCids.length === 0)
2307
+ return;
2308
+ const stmt = this._db.prepare(`UPDATE ${TABLES.COMMENT_UPDATES} SET publishedToPostUpdatesMFS = 1 WHERE cid IN (${commentCids.map(() => "?").join(",")})`);
2309
+ stmt.run(...commentCids);
2310
+ }
2311
+ forceUpdateOnAllComments() {
2312
+ this._db.prepare(`UPDATE ${TABLES.COMMENT_UPDATES} SET publishedToPostUpdatesMFS = 0`).run();
2313
+ }
2314
+ forceUpdateOnAllCommentsWithCid(commentCids) {
2315
+ if (commentCids.length === 0)
2316
+ return;
2317
+ this._db
2318
+ .prepare(`UPDATE ${TABLES.COMMENT_UPDATES} SET publishedToPostUpdatesMFS = 0 WHERE cid IN (${commentCids.map(() => "?").join(",")})`)
2319
+ .run(...commentCids);
2320
+ }
2321
+ queryAllCommentCidsAndTheirReplies() {
2322
+ const log = Logger("pkc-js:local-community:db-handler:queryAllCidsUnderThisCommunity");
2323
+ const rows = this._db
2324
+ .prepare(`SELECT
2325
+ c.cid AS cid,
2326
+ CASE
2327
+ WHEN cu.replies IS NULL THEN NULL
2328
+ ELSE json_set(
2329
+ cu.replies,
2330
+ '$.pages',
2331
+ (
2332
+ SELECT json_group_object(
2333
+ pages.key,
2334
+ json_set(pages.value, '$.comments', json('[]'))
2335
+ )
2336
+ FROM json_each(cu.replies, '$.pages') AS pages
2337
+ )
2338
+ )
2339
+ END AS replies
2340
+ FROM ${TABLES.COMMENTS} c
2341
+ LEFT JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid`)
2342
+ .all();
2343
+ return rows.map((row) => {
2344
+ let parsedReplies;
2345
+ if (typeof row.replies === "string" && row.replies.length > 0) {
2346
+ try {
2347
+ parsedReplies = JSON.parse(row.replies);
2348
+ }
2349
+ catch (e) {
2350
+ log.error(`Failed to parse replies JSON for comment ${row.cid} when collecting cids`, e);
2351
+ }
2352
+ }
2353
+ return { cid: row.cid, replies: parsedReplies };
2354
+ });
2355
+ }
2356
+ queryPostsWithActiveScore(pageOptions) {
2357
+ const { clause: rootAddrClause, params: rootAddrParams } = this._communityAddressClauseNamed("p", "asRoot");
2358
+ const { clause: descAddrClause, params: descAddrParams } = this._communityAddressClauseNamed("c", "asDesc");
2359
+ const { clause: postsAddrClause, params: postsAddrParams } = this._communityAddressClauseNamed("c", "asPosts");
2360
+ const activeScoreRootConditions = ["p.depth = 0"];
2361
+ if (pageOptions.excludeCommentsWithDifferentCommunityAddress)
2362
+ activeScoreRootConditions.push(rootAddrClause);
2363
+ if (pageOptions.excludeCommentPendingApproval)
2364
+ activeScoreRootConditions.push(this._pendingApprovalClause("p"));
2365
+ if (pageOptions.excludeRemovedComments)
2366
+ activeScoreRootConditions.push(this._removedClause("cu_root"));
2367
+ if (pageOptions.excludeDeletedComments)
2368
+ activeScoreRootConditions.push(this._deletedFromUpdatesClause("cu_root"));
2369
+ if (pageOptions.excludeCommentWithApprovedFalse)
2370
+ activeScoreRootConditions.push(this._approvedClause("cu_root"));
2371
+ const activeScoreRootWhere = `WHERE ${activeScoreRootConditions.join(" AND ")}`;
2372
+ const activeScoreDescendantConditions = [];
2373
+ if (pageOptions.excludeCommentsWithDifferentCommunityAddress)
2374
+ activeScoreDescendantConditions.push(descAddrClause);
2375
+ if (pageOptions.excludeCommentPendingApproval)
2376
+ activeScoreDescendantConditions.push(this._pendingApprovalClause("c"));
2377
+ if (pageOptions.excludeRemovedComments)
2378
+ activeScoreDescendantConditions.push(this._removedClause("cu"));
2379
+ if (pageOptions.excludeDeletedComments)
2380
+ activeScoreDescendantConditions.push(this._deletedFromLookupClause("deleted_lookup"));
2381
+ if (pageOptions.excludeCommentWithApprovedFalse)
2382
+ activeScoreDescendantConditions.push(this._approvedClause("cu"));
2383
+ const activeScoreDescendantWhere = activeScoreDescendantConditions.length > 0 ? `WHERE ${activeScoreDescendantConditions.join(" AND ")}` : "";
2384
+ const activeScoresCte = `
2385
+ WITH RECURSIVE descendants AS (
2386
+ SELECT p.cid AS post_cid, p.cid AS current_cid, p.timestamp
2387
+ FROM ${TABLES.COMMENTS} p
2388
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu_root ON p.cid = cu_root.cid
2389
+ ${activeScoreRootWhere}
2390
+ UNION ALL
2391
+ SELECT desc_tree.post_cid, c.cid AS current_cid, c.timestamp FROM ${TABLES.COMMENTS} c
2392
+ INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
2393
+ LEFT JOIN (SELECT cid, json_extract(edit, '$.deleted') AS deleted_flag FROM ${TABLES.COMMENT_UPDATES}) AS deleted_lookup ON c.cid = deleted_lookup.cid
2394
+ JOIN descendants desc_tree ON c.parentCid = desc_tree.current_cid
2395
+ ${activeScoreDescendantWhere}
2396
+ ) SELECT post_cid, MAX(timestamp) as active_score FROM descendants GROUP BY post_cid
2397
+ `;
2398
+ const commentUpdateCols = remeda.keys.strict(pageOptions.commentUpdateFieldsToExclude
2399
+ ? remeda.omit(CommentUpdateSchema.shape, pageOptions.commentUpdateFieldsToExclude)
2400
+ : CommentUpdateSchema.shape);
2401
+ const commentUpdateSelects = commentUpdateCols.map((col) => `cu.${col} AS commentUpdate_${col}`);
2402
+ const commentIpfsCols = [...remeda.keys.strict(CommentIpfsSchema.shape), "extraProps"];
2403
+ const commentIpfsSelects = commentIpfsCols.map((col) => `c.${col} AS commentIpfs_${col}`);
2404
+ let postsQueryStr = `
2405
+ SELECT ${commentIpfsSelects.join(", ")}, ${commentUpdateSelects.join(", ")}, asc_scores.active_score
2406
+ FROM ${TABLES.COMMENTS} c INNER JOIN ${TABLES.COMMENT_UPDATES} cu ON c.cid = cu.cid
2407
+ INNER JOIN (${activeScoresCte}) AS asc_scores ON c.cid = asc_scores.post_cid
2408
+ `;
2409
+ const params = { ...rootAddrParams, ...descAddrParams };
2410
+ const postsWhereClauses = ["c.depth = 0"];
2411
+ if (pageOptions.excludeCommentsWithDifferentCommunityAddress) {
2412
+ postsWhereClauses.push(postsAddrClause);
2413
+ Object.assign(params, postsAddrParams);
2414
+ }
2415
+ if (pageOptions.excludeRemovedComments)
2416
+ postsWhereClauses.push(this._removedClause("cu"));
2417
+ if (pageOptions.excludeDeletedComments)
2418
+ postsWhereClauses.push(this._deletedFromUpdatesClause("cu"));
2419
+ if (pageOptions.excludeCommentPendingApproval)
2420
+ postsWhereClauses.push(this._pendingApprovalClause("c"));
2421
+ if (pageOptions.excludeCommentWithApprovedFalse)
2422
+ postsWhereClauses.push(this._approvedClause("cu"));
2423
+ postsQueryStr += ` WHERE ${postsWhereClauses.join(" AND ")}`;
2424
+ const postsRaw = this._db.prepare(postsQueryStr).all(params);
2425
+ return postsRaw.map((postRaw) => {
2426
+ const { comment, commentUpdate } = this._parsePrefixedComment(postRaw);
2427
+ return {
2428
+ comment,
2429
+ commentUpdate,
2430
+ activeScore: postRaw.active_score
2431
+ };
2432
+ });
2433
+ }
2434
+ _processRecordsForDbBeforeInsert(records) {
2435
+ return records.map((record) => {
2436
+ const processed = { ...record };
2437
+ for (const [key, value] of remeda.entries(processed)) {
2438
+ if (remeda.isPlainObject(value) || Array.isArray(value))
2439
+ processed[key] = JSON.stringify(value);
2440
+ else if (typeof value === "boolean")
2441
+ processed[key] = value ? 1 : 0;
2442
+ }
2443
+ return processed;
2444
+ });
2445
+ }
2446
+ _spreadExtraProps(record) {
2447
+ const extraPropsNames = ["extraProps", "commentIpfs_extraProps", "commentUpdate_extaProps"];
2448
+ extraPropsNames.forEach((extraPropName) => {
2449
+ record = { ...record, ...record[extraPropName] };
2450
+ delete record[extraPropName];
2451
+ });
2452
+ // For old migrated rows (pre-wire-format-change), extraProps contains subplebbitAddress.
2453
+ // The original CommentIpfs did NOT have communityPublicKey/communityName,
2454
+ // so we must remove them to preserve CID reproducibility.
2455
+ if ("subplebbitAddress" in record) {
2456
+ delete record["communityPublicKey"];
2457
+ delete record["communityName"];
2458
+ }
2459
+ return record;
2460
+ }
2461
+ }
2462
+ //# sourceMappingURL=db-handler.js.map