@pkcprotocol/pkc-js 0.0.107 → 0.0.108

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 (351) hide show
  1. package/README.md +15 -0
  2. package/dist/browser/clients/base-client-manager.d.ts +1 -1
  3. package/dist/browser/clients/rpc-client/rpc-schema-util.d.ts +16 -0
  4. package/dist/browser/clients/rpc-client/schema.d.ts +16 -0
  5. package/dist/browser/community/remote-community.d.ts +1 -0
  6. package/dist/browser/community/remote-community.js +1 -0
  7. package/dist/browser/community/remote-community.js.map +1 -1
  8. package/dist/browser/community/rpc-local-community.js +13 -0
  9. package/dist/browser/community/rpc-local-community.js.map +1 -1
  10. package/dist/browser/community/schema.d.ts +1369 -1
  11. package/dist/browser/community/schema.js +90 -4
  12. package/dist/browser/community/schema.js.map +1 -1
  13. package/dist/browser/community/types.d.ts +13 -2
  14. package/dist/browser/constants.d.ts +1 -0
  15. package/dist/browser/constants.js +6 -0
  16. package/dist/browser/constants.js.map +1 -1
  17. package/dist/browser/errors.d.ts +15 -1
  18. package/dist/browser/errors.js +15 -0
  19. package/dist/browser/errors.js.map +1 -1
  20. package/dist/browser/generated-version.d.ts +1 -1
  21. package/dist/browser/generated-version.js +1 -1
  22. package/dist/browser/helia/helia-for-pkc.js +41 -4
  23. package/dist/browser/helia/helia-for-pkc.js.map +1 -1
  24. package/dist/browser/helia/util.js +4 -1
  25. package/dist/browser/helia/util.js.map +1 -1
  26. package/dist/browser/index-with-compile-cache.d.ts +559 -0
  27. package/dist/browser/index.d.ts +1120 -0
  28. package/dist/browser/index.js +5 -0
  29. package/dist/browser/index.js.map +1 -1
  30. package/dist/browser/logger.js +10 -1
  31. package/dist/browser/logger.js.map +1 -1
  32. package/dist/browser/object-key-util.d.ts +2 -0
  33. package/dist/browser/object-key-util.js +12 -0
  34. package/dist/browser/object-key-util.js.map +1 -0
  35. package/dist/browser/pages/page-sort-options.d.ts +14 -0
  36. package/dist/browser/pages/page-sort-options.js +92 -0
  37. package/dist/browser/pages/page-sort-options.js.map +1 -0
  38. package/dist/browser/pages/page-sort-scoring.d.ts +35 -0
  39. package/dist/browser/pages/page-sort-scoring.js +124 -0
  40. package/dist/browser/pages/page-sort-scoring.js.map +1 -0
  41. package/dist/browser/pages/pages-client-manager.d.ts +1 -0
  42. package/dist/browser/pages/pages-client-manager.js +16 -4
  43. package/dist/browser/pages/pages-client-manager.js.map +1 -1
  44. package/dist/browser/pages/pkc-js-page-sorts/active.d.ts +3 -0
  45. package/dist/browser/pages/pkc-js-page-sorts/active.js +15 -0
  46. package/dist/browser/pages/pkc-js-page-sorts/active.js.map +1 -0
  47. package/dist/browser/pages/pkc-js-page-sorts/best.d.ts +3 -0
  48. package/dist/browser/pages/pkc-js-page-sorts/best.js +10 -0
  49. package/dist/browser/pages/pkc-js-page-sorts/best.js.map +1 -0
  50. package/dist/browser/pages/pkc-js-page-sorts/controversial.d.ts +3 -0
  51. package/dist/browser/pages/pkc-js-page-sorts/controversial.js +10 -0
  52. package/dist/browser/pages/pkc-js-page-sorts/controversial.js.map +1 -0
  53. package/dist/browser/pages/pkc-js-page-sorts/hot.d.ts +3 -0
  54. package/dist/browser/pages/pkc-js-page-sorts/hot.js +10 -0
  55. package/dist/browser/pages/pkc-js-page-sorts/hot.js.map +1 -0
  56. package/dist/browser/pages/pkc-js-page-sorts/index.d.ts +2 -0
  57. package/dist/browser/pages/pkc-js-page-sorts/index.js +37 -0
  58. package/dist/browser/pages/pkc-js-page-sorts/index.js.map +1 -0
  59. package/dist/browser/pages/pkc-js-page-sorts/new-flat.d.ts +3 -0
  60. package/dist/browser/pages/pkc-js-page-sorts/new-flat.js +12 -0
  61. package/dist/browser/pages/pkc-js-page-sorts/new-flat.js.map +1 -0
  62. package/dist/browser/pages/pkc-js-page-sorts/new.d.ts +3 -0
  63. package/dist/browser/pages/pkc-js-page-sorts/new.js +10 -0
  64. package/dist/browser/pages/pkc-js-page-sorts/new.js.map +1 -0
  65. package/dist/browser/pages/pkc-js-page-sorts/old-flat.d.ts +3 -0
  66. package/dist/browser/pages/pkc-js-page-sorts/old-flat.js +12 -0
  67. package/dist/browser/pages/pkc-js-page-sorts/old-flat.js.map +1 -0
  68. package/dist/browser/pages/pkc-js-page-sorts/old.d.ts +3 -0
  69. package/dist/browser/pages/pkc-js-page-sorts/old.js +10 -0
  70. package/dist/browser/pages/pkc-js-page-sorts/old.js.map +1 -0
  71. package/dist/browser/pages/pkc-js-page-sorts/top-all.d.ts +3 -0
  72. package/dist/browser/pages/pkc-js-page-sorts/top-all.js +10 -0
  73. package/dist/browser/pages/pkc-js-page-sorts/top-all.js.map +1 -0
  74. package/dist/browser/pages/pkc-js-page-sorts/top-day.d.ts +3 -0
  75. package/dist/browser/pages/pkc-js-page-sorts/top-day.js +11 -0
  76. package/dist/browser/pages/pkc-js-page-sorts/top-day.js.map +1 -0
  77. package/dist/browser/pages/pkc-js-page-sorts/top-hour.d.ts +3 -0
  78. package/dist/browser/pages/pkc-js-page-sorts/top-hour.js +11 -0
  79. package/dist/browser/pages/pkc-js-page-sorts/top-hour.js.map +1 -0
  80. package/dist/browser/pages/pkc-js-page-sorts/top-month.d.ts +3 -0
  81. package/dist/browser/pages/pkc-js-page-sorts/top-month.js +11 -0
  82. package/dist/browser/pages/pkc-js-page-sorts/top-month.js.map +1 -0
  83. package/dist/browser/pages/pkc-js-page-sorts/top-week.d.ts +3 -0
  84. package/dist/browser/pages/pkc-js-page-sorts/top-week.js +11 -0
  85. package/dist/browser/pages/pkc-js-page-sorts/top-week.js.map +1 -0
  86. package/dist/browser/pages/pkc-js-page-sorts/top-year.d.ts +3 -0
  87. package/dist/browser/pages/pkc-js-page-sorts/top-year.js +11 -0
  88. package/dist/browser/pages/pkc-js-page-sorts/top-year.js.map +1 -0
  89. package/dist/browser/pages/pkc-js-page-sorts/top.d.ts +3 -0
  90. package/dist/browser/pages/pkc-js-page-sorts/top.js +12 -0
  91. package/dist/browser/pages/pkc-js-page-sorts/top.js.map +1 -0
  92. package/dist/browser/pages/pkc-js-page-sorts/util.d.ts +3 -0
  93. package/dist/browser/pages/pkc-js-page-sorts/util.js +7 -0
  94. package/dist/browser/pages/pkc-js-page-sorts/util.js.map +1 -0
  95. package/dist/browser/pages/schema.d.ts +1 -0
  96. package/dist/browser/pages/schema.js +10 -3
  97. package/dist/browser/pages/schema.js.map +1 -1
  98. package/dist/browser/pages/types.d.ts +8 -0
  99. package/dist/browser/pages/util.d.ts +2 -1
  100. package/dist/browser/pages/util.js +11 -10
  101. package/dist/browser/pages/util.js.map +1 -1
  102. package/dist/browser/pkc/pkc.d.ts +2 -1
  103. package/dist/browser/pkc/pkc.js +2 -1
  104. package/dist/browser/pkc/pkc.js.map +1 -1
  105. package/dist/browser/pkc-error.d.ts +1 -1
  106. package/dist/browser/publications/comment/comment-client-manager.d.ts +2 -2
  107. package/dist/browser/publications/comment/comment-client-manager.js +3 -1
  108. package/dist/browser/publications/comment/comment-client-manager.js.map +1 -1
  109. package/dist/browser/publications/community-edit/schema.d.ts +64 -0
  110. package/dist/browser/pubsub-messages/schema.d.ts +32 -0
  111. package/dist/browser/rpc/src/index.js +14 -1
  112. package/dist/browser/rpc/src/index.js.map +1 -1
  113. package/dist/browser/rpc/src/lib/pkc-js/index.d.ts +559 -0
  114. package/dist/browser/rpc/src/schema.d.ts +3371 -0
  115. package/dist/browser/rpc/src/schema.js +6 -2
  116. package/dist/browser/rpc/src/schema.js.map +1 -1
  117. package/dist/browser/runtime/browser/community/page-sorts/index.d.ts +1 -0
  118. package/dist/browser/runtime/browser/community/page-sorts/index.js +6 -0
  119. package/dist/browser/runtime/browser/community/page-sorts/index.js.map +1 -0
  120. package/dist/browser/runtime/node/community/db-handler.d.ts +78 -20
  121. package/dist/browser/runtime/node/community/db-handler.js +1151 -734
  122. package/dist/browser/runtime/node/community/db-handler.js.map +1 -1
  123. package/dist/browser/runtime/node/community/db-row-parser.d.ts +13 -0
  124. package/dist/browser/runtime/node/community/db-row-parser.js +58 -1
  125. package/dist/browser/runtime/node/community/db-row-parser.js.map +1 -1
  126. package/dist/browser/runtime/node/community/local-community/cleanup.js +23 -10
  127. package/dist/browser/runtime/node/community/local-community/cleanup.js.map +1 -1
  128. package/dist/browser/runtime/node/community/local-community/comment-updates.d.ts +12 -2
  129. package/dist/browser/runtime/node/community/local-community/comment-updates.js +101 -70
  130. package/dist/browser/runtime/node/community/local-community/comment-updates.js.map +1 -1
  131. package/dist/browser/runtime/node/community/local-community/db-state.js +8 -0
  132. package/dist/browser/runtime/node/community/local-community/db-state.js.map +1 -1
  133. package/dist/browser/runtime/node/community/local-community/editing.d.ts +9 -2
  134. package/dist/browser/runtime/node/community/local-community/editing.js +32 -5
  135. package/dist/browser/runtime/node/community/local-community/editing.js.map +1 -1
  136. package/dist/browser/runtime/node/community/local-community/ipns-publishing.d.ts +2 -2
  137. package/dist/browser/runtime/node/community/local-community/ipns-publishing.js +49 -27
  138. package/dist/browser/runtime/node/community/local-community/ipns-publishing.js.map +1 -1
  139. package/dist/browser/runtime/node/community/local-community/lifecycle.d.ts +1 -0
  140. package/dist/browser/runtime/node/community/local-community/lifecycle.js +49 -18
  141. package/dist/browser/runtime/node/community/local-community/lifecycle.js.map +1 -1
  142. package/dist/browser/runtime/node/community/local-community.d.ts +5 -1
  143. package/dist/browser/runtime/node/community/local-community.js +13 -1
  144. package/dist/browser/runtime/node/community/local-community.js.map +1 -1
  145. package/dist/browser/runtime/node/community/page-generator.d.ts +44 -413
  146. package/dist/browser/runtime/node/community/page-generator.js +531 -308
  147. package/dist/browser/runtime/node/community/page-generator.js.map +1 -1
  148. package/dist/browser/runtime/node/community/page-sorts/index.d.ts +64 -0
  149. package/dist/browser/runtime/node/community/page-sorts/index.js +264 -0
  150. package/dist/browser/runtime/node/community/page-sorts/index.js.map +1 -0
  151. package/dist/browser/runtime/node/util.js +1 -1
  152. package/dist/browser/runtime/node/util.js.map +1 -1
  153. package/dist/browser/schema/schema-util.d.ts +142 -1
  154. package/dist/browser/schema/schema-util.js +11 -0
  155. package/dist/browser/schema/schema-util.js.map +1 -1
  156. package/dist/browser/schema/schema.js +1 -1
  157. package/dist/browser/schema/schema.js.map +1 -1
  158. package/dist/browser/schema.d.ts +4469 -0
  159. package/dist/browser/schema.js +2 -1
  160. package/dist/browser/schema.js.map +1 -1
  161. package/dist/browser/signer/signatures.d.ts +1 -1
  162. package/dist/browser/signer/signatures.js +45 -1
  163. package/dist/browser/signer/signatures.js.map +1 -1
  164. package/dist/browser/test/test-util.d.ts +63 -0
  165. package/dist/browser/test/test-util.js +66 -11
  166. package/dist/browser/test/test-util.js.map +1 -1
  167. package/dist/browser/util.d.ts +4 -0
  168. package/dist/browser/util.js +87 -0
  169. package/dist/browser/util.js.map +1 -1
  170. package/dist/browser/version.js +1 -1
  171. package/dist/bundled/bundle-manifest.json +235 -211
  172. package/dist/bundled/chunks/{helia-for-pkc-BCVI3rjp.js → helia-for-pkc-BY1wF9W8.js} +58 -34
  173. package/dist/bundled/chunks/{local-community-t5S6o-0o.js → local-community-B6i3MUzw.js} +1 -1
  174. package/dist/bundled/chunks/{local-community-CK7TUwwd.js → local-community-Dm02mfn3.js} +6719 -6055
  175. package/dist/bundled/chunks/{node-BpoCiJjL.js → node-B8rEQ_rA.js} +1201 -15
  176. package/dist/bundled/chunks/{rpc-local-community-CR1TnLfm.js → rpc-local-community-DHS2R2Vk.js} +13631 -14131
  177. package/dist/bundled/chunks/{util-bxE0EcD4.js → util-B-wz3mKY.js} +1 -1
  178. package/dist/bundled/chunks/{schema-DWRg9_bS.js → util-C6zDjhpr.js} +751 -601
  179. package/dist/bundled/index.js +2 -2
  180. package/dist/bundled/rpc/src/index.js +22 -9
  181. package/dist/node/clients/base-client-manager.d.ts +1 -1
  182. package/dist/node/clients/rpc-client/rpc-schema-util.d.ts +16 -0
  183. package/dist/node/clients/rpc-client/schema.d.ts +16 -0
  184. package/dist/node/community/remote-community.d.ts +1 -0
  185. package/dist/node/community/remote-community.js +1 -0
  186. package/dist/node/community/remote-community.js.map +1 -1
  187. package/dist/node/community/rpc-local-community.js +13 -0
  188. package/dist/node/community/rpc-local-community.js.map +1 -1
  189. package/dist/node/community/schema.d.ts +1369 -1
  190. package/dist/node/community/schema.js +90 -4
  191. package/dist/node/community/schema.js.map +1 -1
  192. package/dist/node/community/types.d.ts +13 -2
  193. package/dist/node/constants.d.ts +1 -0
  194. package/dist/node/constants.js +6 -0
  195. package/dist/node/constants.js.map +1 -1
  196. package/dist/node/errors.d.ts +15 -1
  197. package/dist/node/errors.js +15 -0
  198. package/dist/node/errors.js.map +1 -1
  199. package/dist/node/generated-version.d.ts +1 -1
  200. package/dist/node/generated-version.js +1 -1
  201. package/dist/node/helia/helia-for-pkc.js +41 -4
  202. package/dist/node/helia/helia-for-pkc.js.map +1 -1
  203. package/dist/node/helia/util.js +4 -1
  204. package/dist/node/helia/util.js.map +1 -1
  205. package/dist/node/index-with-compile-cache.d.ts +559 -0
  206. package/dist/node/index.d.ts +1120 -0
  207. package/dist/node/index.js +5 -0
  208. package/dist/node/index.js.map +1 -1
  209. package/dist/node/logger.js +10 -1
  210. package/dist/node/logger.js.map +1 -1
  211. package/dist/node/object-key-util.d.ts +2 -0
  212. package/dist/node/object-key-util.js +12 -0
  213. package/dist/node/object-key-util.js.map +1 -0
  214. package/dist/node/pages/page-sort-options.d.ts +14 -0
  215. package/dist/node/pages/page-sort-options.js +92 -0
  216. package/dist/node/pages/page-sort-options.js.map +1 -0
  217. package/dist/node/pages/page-sort-scoring.d.ts +35 -0
  218. package/dist/node/pages/page-sort-scoring.js +124 -0
  219. package/dist/node/pages/page-sort-scoring.js.map +1 -0
  220. package/dist/node/pages/pages-client-manager.d.ts +1 -0
  221. package/dist/node/pages/pages-client-manager.js +16 -4
  222. package/dist/node/pages/pages-client-manager.js.map +1 -1
  223. package/dist/node/pages/pkc-js-page-sorts/active.d.ts +3 -0
  224. package/dist/node/pages/pkc-js-page-sorts/active.js +15 -0
  225. package/dist/node/pages/pkc-js-page-sorts/active.js.map +1 -0
  226. package/dist/node/pages/pkc-js-page-sorts/best.d.ts +3 -0
  227. package/dist/node/pages/pkc-js-page-sorts/best.js +10 -0
  228. package/dist/node/pages/pkc-js-page-sorts/best.js.map +1 -0
  229. package/dist/node/pages/pkc-js-page-sorts/controversial.d.ts +3 -0
  230. package/dist/node/pages/pkc-js-page-sorts/controversial.js +10 -0
  231. package/dist/node/pages/pkc-js-page-sorts/controversial.js.map +1 -0
  232. package/dist/node/pages/pkc-js-page-sorts/hot.d.ts +3 -0
  233. package/dist/node/pages/pkc-js-page-sorts/hot.js +10 -0
  234. package/dist/node/pages/pkc-js-page-sorts/hot.js.map +1 -0
  235. package/dist/node/pages/pkc-js-page-sorts/index.d.ts +2 -0
  236. package/dist/node/pages/pkc-js-page-sorts/index.js +37 -0
  237. package/dist/node/pages/pkc-js-page-sorts/index.js.map +1 -0
  238. package/dist/node/pages/pkc-js-page-sorts/new-flat.d.ts +3 -0
  239. package/dist/node/pages/pkc-js-page-sorts/new-flat.js +12 -0
  240. package/dist/node/pages/pkc-js-page-sorts/new-flat.js.map +1 -0
  241. package/dist/node/pages/pkc-js-page-sorts/new.d.ts +3 -0
  242. package/dist/node/pages/pkc-js-page-sorts/new.js +10 -0
  243. package/dist/node/pages/pkc-js-page-sorts/new.js.map +1 -0
  244. package/dist/node/pages/pkc-js-page-sorts/old-flat.d.ts +3 -0
  245. package/dist/node/pages/pkc-js-page-sorts/old-flat.js +12 -0
  246. package/dist/node/pages/pkc-js-page-sorts/old-flat.js.map +1 -0
  247. package/dist/node/pages/pkc-js-page-sorts/old.d.ts +3 -0
  248. package/dist/node/pages/pkc-js-page-sorts/old.js +10 -0
  249. package/dist/node/pages/pkc-js-page-sorts/old.js.map +1 -0
  250. package/dist/node/pages/pkc-js-page-sorts/top-all.d.ts +3 -0
  251. package/dist/node/pages/pkc-js-page-sorts/top-all.js +10 -0
  252. package/dist/node/pages/pkc-js-page-sorts/top-all.js.map +1 -0
  253. package/dist/node/pages/pkc-js-page-sorts/top-day.d.ts +3 -0
  254. package/dist/node/pages/pkc-js-page-sorts/top-day.js +11 -0
  255. package/dist/node/pages/pkc-js-page-sorts/top-day.js.map +1 -0
  256. package/dist/node/pages/pkc-js-page-sorts/top-hour.d.ts +3 -0
  257. package/dist/node/pages/pkc-js-page-sorts/top-hour.js +11 -0
  258. package/dist/node/pages/pkc-js-page-sorts/top-hour.js.map +1 -0
  259. package/dist/node/pages/pkc-js-page-sorts/top-month.d.ts +3 -0
  260. package/dist/node/pages/pkc-js-page-sorts/top-month.js +11 -0
  261. package/dist/node/pages/pkc-js-page-sorts/top-month.js.map +1 -0
  262. package/dist/node/pages/pkc-js-page-sorts/top-week.d.ts +3 -0
  263. package/dist/node/pages/pkc-js-page-sorts/top-week.js +11 -0
  264. package/dist/node/pages/pkc-js-page-sorts/top-week.js.map +1 -0
  265. package/dist/node/pages/pkc-js-page-sorts/top-year.d.ts +3 -0
  266. package/dist/node/pages/pkc-js-page-sorts/top-year.js +11 -0
  267. package/dist/node/pages/pkc-js-page-sorts/top-year.js.map +1 -0
  268. package/dist/node/pages/pkc-js-page-sorts/top.d.ts +3 -0
  269. package/dist/node/pages/pkc-js-page-sorts/top.js +12 -0
  270. package/dist/node/pages/pkc-js-page-sorts/top.js.map +1 -0
  271. package/dist/node/pages/pkc-js-page-sorts/util.d.ts +3 -0
  272. package/dist/node/pages/pkc-js-page-sorts/util.js +7 -0
  273. package/dist/node/pages/pkc-js-page-sorts/util.js.map +1 -0
  274. package/dist/node/pages/schema.d.ts +1 -0
  275. package/dist/node/pages/schema.js +10 -3
  276. package/dist/node/pages/schema.js.map +1 -1
  277. package/dist/node/pages/types.d.ts +8 -0
  278. package/dist/node/pages/util.d.ts +2 -1
  279. package/dist/node/pages/util.js +11 -10
  280. package/dist/node/pages/util.js.map +1 -1
  281. package/dist/node/pkc/pkc.d.ts +2 -1
  282. package/dist/node/pkc/pkc.js +2 -1
  283. package/dist/node/pkc/pkc.js.map +1 -1
  284. package/dist/node/pkc-error.d.ts +1 -1
  285. package/dist/node/publications/comment/comment-client-manager.d.ts +2 -2
  286. package/dist/node/publications/comment/comment-client-manager.js +3 -1
  287. package/dist/node/publications/comment/comment-client-manager.js.map +1 -1
  288. package/dist/node/publications/community-edit/schema.d.ts +64 -0
  289. package/dist/node/pubsub-messages/schema.d.ts +32 -0
  290. package/dist/node/rpc/src/index.js +14 -1
  291. package/dist/node/rpc/src/index.js.map +1 -1
  292. package/dist/node/rpc/src/lib/pkc-js/index.d.ts +559 -0
  293. package/dist/node/rpc/src/schema.d.ts +3371 -0
  294. package/dist/node/rpc/src/schema.js +6 -2
  295. package/dist/node/rpc/src/schema.js.map +1 -1
  296. package/dist/node/runtime/browser/community/page-sorts/index.d.ts +1 -0
  297. package/dist/node/runtime/browser/community/page-sorts/index.js +6 -0
  298. package/dist/node/runtime/browser/community/page-sorts/index.js.map +1 -0
  299. package/dist/node/runtime/node/community/db-handler.d.ts +78 -20
  300. package/dist/node/runtime/node/community/db-handler.js +1151 -734
  301. package/dist/node/runtime/node/community/db-handler.js.map +1 -1
  302. package/dist/node/runtime/node/community/db-row-parser.d.ts +13 -0
  303. package/dist/node/runtime/node/community/db-row-parser.js +58 -1
  304. package/dist/node/runtime/node/community/db-row-parser.js.map +1 -1
  305. package/dist/node/runtime/node/community/local-community/cleanup.js +23 -10
  306. package/dist/node/runtime/node/community/local-community/cleanup.js.map +1 -1
  307. package/dist/node/runtime/node/community/local-community/comment-updates.d.ts +12 -2
  308. package/dist/node/runtime/node/community/local-community/comment-updates.js +101 -70
  309. package/dist/node/runtime/node/community/local-community/comment-updates.js.map +1 -1
  310. package/dist/node/runtime/node/community/local-community/db-state.js +8 -0
  311. package/dist/node/runtime/node/community/local-community/db-state.js.map +1 -1
  312. package/dist/node/runtime/node/community/local-community/editing.d.ts +9 -2
  313. package/dist/node/runtime/node/community/local-community/editing.js +32 -5
  314. package/dist/node/runtime/node/community/local-community/editing.js.map +1 -1
  315. package/dist/node/runtime/node/community/local-community/ipns-publishing.d.ts +2 -2
  316. package/dist/node/runtime/node/community/local-community/ipns-publishing.js +49 -27
  317. package/dist/node/runtime/node/community/local-community/ipns-publishing.js.map +1 -1
  318. package/dist/node/runtime/node/community/local-community/lifecycle.d.ts +1 -0
  319. package/dist/node/runtime/node/community/local-community/lifecycle.js +49 -18
  320. package/dist/node/runtime/node/community/local-community/lifecycle.js.map +1 -1
  321. package/dist/node/runtime/node/community/local-community.d.ts +5 -1
  322. package/dist/node/runtime/node/community/local-community.js +13 -1
  323. package/dist/node/runtime/node/community/local-community.js.map +1 -1
  324. package/dist/node/runtime/node/community/page-generator.d.ts +44 -413
  325. package/dist/node/runtime/node/community/page-generator.js +531 -308
  326. package/dist/node/runtime/node/community/page-generator.js.map +1 -1
  327. package/dist/node/runtime/node/community/page-sorts/index.d.ts +64 -0
  328. package/dist/node/runtime/node/community/page-sorts/index.js +264 -0
  329. package/dist/node/runtime/node/community/page-sorts/index.js.map +1 -0
  330. package/dist/node/runtime/node/util.js +1 -1
  331. package/dist/node/runtime/node/util.js.map +1 -1
  332. package/dist/node/schema/schema-util.d.ts +142 -1
  333. package/dist/node/schema/schema-util.js +11 -0
  334. package/dist/node/schema/schema-util.js.map +1 -1
  335. package/dist/node/schema/schema.js +1 -1
  336. package/dist/node/schema/schema.js.map +1 -1
  337. package/dist/node/schema.d.ts +4469 -0
  338. package/dist/node/schema.js +2 -1
  339. package/dist/node/schema.js.map +1 -1
  340. package/dist/node/signer/signatures.d.ts +1 -1
  341. package/dist/node/signer/signatures.js +45 -1
  342. package/dist/node/signer/signatures.js.map +1 -1
  343. package/dist/node/test/test-util.d.ts +63 -0
  344. package/dist/node/test/test-util.js +66 -11
  345. package/dist/node/test/test-util.js.map +1 -1
  346. package/dist/node/util.d.ts +4 -0
  347. package/dist/node/util.js +87 -0
  348. package/dist/node/util.js.map +1 -1
  349. package/dist/node/version.js +1 -1
  350. package/package.json +2 -2
  351. package/dist/bundled/chunks/{src-Dz9wtvrK.js → src-B07ldGHS.js} +1 -1
@@ -1,18 +1,193 @@
1
- import { calculateStringSizeSameAsIpfsAddCidV0, hideClassPrivateProps, retryKuboIpfsAddAndProvide, timestamp } from "../../../util.js";
1
+ import { calculateUnixFsDagSizeCidV0, hideClassPrivateProps, retryKuboIpfsAddAndProvide, timestamp } from "../../../util.js";
2
+ import v8 from "node:v8";
3
+ import { Buffer } from "buffer";
2
4
  import assert from "assert";
3
- import { keys, omit } from "remeda";
4
5
  import { stringify as deterministicStringify } from "safe-stable-stringify";
5
6
  import env from "../../../version.js";
6
- import { POSTS_SORT_TYPES, POST_REPLIES_SORT_TYPES, TIMEFRAMES_TO_SECONDS, REPLY_REPLIES_SORT_TYPES } from "../../../pages/util.js";
7
7
  import { PKCError } from "../../../pkc-error.js";
8
+ import { applyPageSortExclusions, createDescendantsLookup, orderPageCommentsByScore, partitionPageCommentsForSort, scorePageCommentsWithFile, stripRepliesFromPageComment } from "../../../pages/page-sort-scoring.js";
8
9
  import Logger from "../../../logger.js";
9
10
  import { cleanUpBeforePublishing, signCommentUpdateForChallengeVerification } from "../../../signer/signatures.js";
10
11
  import { deriveCommentIpfsFromCommentTableRow } from "../util.js";
11
12
  import { sha256 } from "js-sha256";
12
- async function getSerializedCommentsSize(comments, hasNextCid) {
13
- const payload = hasNextCid ? { comments, nextCid: "QmXsYKgNH7XoZXdLko5uDvtWSRNE2AXuQ4u8KxVpCacrZx" } : { comments };
14
- const serializedPayload = JSON.stringify(payload);
15
- return await calculateStringSizeSameAsIpfsAddCidV0(serializedPayload);
13
+ // The wire shape (`{ pages, pageCids? }`) of either generation result. `pages` holds only the preloaded sorts'
14
+ // embedded first pages; a preloaded sort that overflowed into more chunks continues through pages[sort].nextCid,
15
+ // and a sort that is not embedded is reachable through pageCids only.
16
+ export function wirePagesFromGeneration(generated) {
17
+ if ("singlePreloadedPage" in generated)
18
+ return { pages: generated.singlePreloadedPage };
19
+ return { pages: generated.pages, ...(generated.pageCids ? { pageCids: generated.pageCids } : {}) };
20
+ }
21
+ const MB = 1024 * 1024;
22
+ const NON_PRELOADED_FIRST_PAGE_SIZE = MB; // pageCids first pages are always capped at 1mib, regardless of the preload budget
23
+ const SAFETY_MARGIN = 1024; // 1KiB under every page limit when chunking
24
+ const NEXT_CID_PLACEHOLDER = "QmXsYKgNH7XoZXdLko5uDvtWSRNE2AXuQ4u8KxVpCacrZx"; // any CIDv0: what a nextCid costs
25
+ // A page is its entries' canonical JSON joined inside this wrapper: keys sorted, and "comments" sorts before
26
+ // "nextCid", so the join is byte-identical to safe-stable-stringify of the page object (issue #351).
27
+ const PAGE_OPEN = '{"comments":[';
28
+ const PAGE_CLOSE = "]}";
29
+ const pageCloseWithNextCid = (nextCid) => `],"nextCid":${JSON.stringify(nextCid)}}`;
30
+ const PAGE_OPEN_BYTES = Buffer.byteLength(PAGE_OPEN);
31
+ const PAGE_CLOSE_BYTES = Buffer.byteLength(PAGE_CLOSE);
32
+ const PAGE_CLOSE_WITH_CID_BYTES = Buffer.byteLength(pageCloseWithNextCid(NEXT_CID_PLACEHOLDER));
33
+ // The UTF-8 size of a page holding entries of the given sizes
34
+ function pageBytes(entrySizes, hasNextCid) {
35
+ let bytes = PAGE_OPEN_BYTES + (hasNextCid ? PAGE_CLOSE_WITH_CID_BYTES : PAGE_CLOSE_BYTES) + Math.max(entrySizes.length - 1, 0);
36
+ for (const size of entrySizes)
37
+ bytes += size;
38
+ return bytes;
39
+ }
40
+ // What `ipfs add` reports for a page of these entries
41
+ const pageDagSize = (entrySizes, hasNextCid) => calculateUnixFsDagSizeCidV0(pageBytes(entrySizes, hasNextCid));
42
+ const SERIALIZE_BATCH = 256; // entries resolved and serialized per database round trip
43
+ // How many bytes of serialized entries a community keeps between generations: an eighth of the heap limit (so
44
+ // --max-old-space-size scales it), never under 32 MiB or over 512 MiB. Past it, pages re-serialize what they need.
45
+ function serializedEntryCacheBudgetBytes() {
46
+ const heapLimit = v8.getHeapStatistics().heap_size_limit;
47
+ return Math.min(Math.max(Math.floor(heapLimit / 8), 32 * MB), 512 * MB);
48
+ }
49
+ // Serialized entries kept across generations, by cid and shape, valid while the CommentUpdate's updatedAt is
50
+ // unchanged: a CommentUpdate is recalculated (new updatedAt) whenever anything under the comment changes, so the
51
+ // same (cid, updatedAt) serializes to the same bytes. The shape is whether the entry carries `replies`: one
52
+ // generation loads the same reply twice when a post has nested and flat sorts, with its reply tree for the nested
53
+ // sorts and without `replies` for the flat ones (queryFlattenedPageReplies excludes the column), and the two must
54
+ // not share bytes, or a flat page would embed the nested JSON. Bounded by a byte budget; a changed entry replaces
55
+ // its old bytes, a new entry is kept only while the budget allows, and entries a generation did not see are
56
+ // dropped after it, so a steady board costs a serialization per changed post and the budget's worth of memory
57
+ // (issue #351).
58
+ class EntryJsonCache {
59
+ constructor(_budgetBytes) {
60
+ this._budgetBytes = _budgetBytes;
61
+ this._byKey = new Map();
62
+ this._bytes = 0;
63
+ }
64
+ static _key(entry) {
65
+ return entry.commentUpdate.replies === undefined ? entry.commentUpdate.cid : `${entry.commentUpdate.cid}+replies`;
66
+ }
67
+ get(entry) {
68
+ const cached = this._byKey.get(EntryJsonCache._key(entry));
69
+ return cached && cached.updatedAt === entry.commentUpdate.updatedAt ? cached : undefined;
70
+ }
71
+ offer(entry, json, bytes) {
72
+ const key = EntryJsonCache._key(entry);
73
+ const previous = this._byKey.get(key);
74
+ const budgetAfter = this._bytes - (previous?.bytes ?? 0) + bytes;
75
+ if (budgetAfter > this._budgetBytes) {
76
+ if (previous)
77
+ this._drop(key);
78
+ return;
79
+ }
80
+ this._byKey.set(key, { cid: entry.commentUpdate.cid, updatedAt: entry.commentUpdate.updatedAt, json, bytes });
81
+ this._bytes = budgetAfter;
82
+ }
83
+ // Keep only the cids a generation listed (a purged or excluded post frees its bytes)
84
+ retainOnly(cids) {
85
+ for (const [key, cached] of [...this._byKey.entries()])
86
+ if (!cids.has(cached.cid))
87
+ this._drop(key);
88
+ }
89
+ _drop(key) {
90
+ const cached = this._byKey.get(key);
91
+ if (!cached)
92
+ return;
93
+ this._byKey.delete(key);
94
+ this._bytes -= cached.bytes;
95
+ }
96
+ }
97
+ // Admission by bytes for the pages being built at once: sorts add their pages in parallel, but a page's string (its
98
+ // entries fetched plus the join) lives in memory until its add returns, and the doubling page sizes make late pages
99
+ // of a large board hundreds of MiB. A page waits until the bytes in flight fit the budget; one that exceeds the
100
+ // budget alone runs when nothing else is in flight, so generation always makes progress (issue #351).
101
+ class PageBytesBudget {
102
+ constructor(_budgetBytes) {
103
+ this._budgetBytes = _budgetBytes;
104
+ this._inFlight = 0;
105
+ this._waiting = [];
106
+ }
107
+ _fits(bytes) {
108
+ return this._inFlight === 0 || this._inFlight + bytes <= this._budgetBytes;
109
+ }
110
+ async acquire(bytes) {
111
+ if (this._fits(bytes))
112
+ this._inFlight += bytes;
113
+ else
114
+ await new Promise((resume) => this._waiting.push({ bytes, resume })); // counted by the release that admits it
115
+ let released = false;
116
+ return () => {
117
+ if (released)
118
+ return;
119
+ released = true;
120
+ this._inFlight -= bytes;
121
+ // Admit waiters in arrival order while they fit, counting each before it resumes so a caller that
122
+ // releases and acquires again in the same tick cannot slip in ahead of it
123
+ for (let i = 0; i < this._waiting.length;) {
124
+ if (!this._fits(this._waiting[i].bytes)) {
125
+ i++;
126
+ continue;
127
+ }
128
+ const [next] = this._waiting.splice(i, 1);
129
+ this._inFlight += next.bytes;
130
+ next.resume();
131
+ }
132
+ };
133
+ }
134
+ }
135
+ // The serialized form of one generation's entries: each entry's canonical page JSON and that JSON's UTF-8 size.
136
+ // Sizes are kept for every entry, since every sort's chunking needs them; the strings live in the cache, so a
137
+ // board's resolved reply trees are never all in memory. Sizing streams: entries the cache does not hold are
138
+ // resolved and serialized a batch at a time; a page fetches what the cache does not hold in one batch when it is
139
+ // built (issue #351).
140
+ class SerializedEntries {
141
+ constructor(_source, _cache) {
142
+ this._source = _source;
143
+ this._cache = _cache;
144
+ this._sizes = new Map();
145
+ this.sizeOf = (entry) => {
146
+ const size = this._sizes.get(entry);
147
+ assert(size !== undefined, "entry was not serialized before chunking");
148
+ return size;
149
+ };
150
+ }
151
+ sizeAll(entries) {
152
+ const pending = [];
153
+ for (const entry of entries) {
154
+ if (this._sizes.has(entry))
155
+ continue;
156
+ const cached = this._cache.get(entry);
157
+ if (cached)
158
+ this._sizes.set(entry, cached.bytes);
159
+ else
160
+ pending.push(entry);
161
+ }
162
+ for (let start = 0; start < pending.length; start += SERIALIZE_BATCH) {
163
+ const batch = pending.slice(start, start + SERIALIZE_BATCH);
164
+ const strings = this._source.serialize(batch);
165
+ for (let i = 0; i < batch.length; i++) {
166
+ const bytes = Buffer.byteLength(strings[i]);
167
+ this._sizes.set(batch[i], bytes);
168
+ this._cache.offer(batch[i], strings[i], bytes);
169
+ }
170
+ }
171
+ }
172
+ // The JSON of each entry, in the given order
173
+ stringsOf(entries) {
174
+ const strings = new Array(entries.length);
175
+ const missing = [];
176
+ for (let i = 0; i < entries.length; i++) {
177
+ const cached = this._cache.get(entries[i]);
178
+ if (cached)
179
+ strings[i] = cached.json;
180
+ else
181
+ missing.push(i);
182
+ }
183
+ for (let start = 0; start < missing.length; start += SERIALIZE_BATCH) {
184
+ const indexes = missing.slice(start, start + SERIALIZE_BATCH);
185
+ const serialized = this._source.serialize(indexes.map((i) => entries[i]));
186
+ for (let k = 0; k < indexes.length; k++)
187
+ strings[indexes[k]] = serialized[k];
188
+ }
189
+ return strings;
190
+ }
16
191
  }
17
192
  export class PageGenerator {
18
193
  constructor(community) {
@@ -23,7 +198,7 @@ export class PageGenerator {
23
198
  const ipfsClient = this._community._clientsManager.getDefaultKuboRpcClient();
24
199
  const listOfPage = new Array(chunks.length);
25
200
  const cids = new Array(chunks.length);
26
- let expectedSize = 1024 * 1024 * Math.pow(2, chunks.length - 1); // expected size of last page
201
+ let expectedSize = MB * Math.pow(2, chunks.length - 1); // expected size of last page
27
202
  for (let i = chunks.length - 1; i >= 0; i--) {
28
203
  const modQueuePageIpfs = { nextCid: cids[i + 1], comments: chunks[i] };
29
204
  if (!modQueuePageIpfs.nextCid)
@@ -50,134 +225,90 @@ export class PageGenerator {
50
225
  }
51
226
  return { pages: listOfPage, cids };
52
227
  }
53
- async addCommentChunksToIpfs(chunks, sortName) {
228
+ // Add one sort's chunks, last page first so each page knows its nextCid. Every page is the join of its entries'
229
+ // serialized JSON, sized exactly before the add; a preloaded sort's first chunk is not added but returned as the
230
+ // page object the record embeds (its entries materialized from the database, nested replies resolved).
231
+ async _addChunksToIpfs({ chunks, sortName, serialized, embedFirst, source, budget }) {
54
232
  assert(chunks.length > 0);
55
- const ipfsClient = this._community._clientsManager.getDefaultKuboRpcClient();
56
- const listOfPage = new Array(chunks.length);
57
233
  const cids = new Array(chunks.length);
58
- let curMaxPageSize = 1024 * 1024 * Math.pow(2, chunks.length - 1); // expected size of last page
59
- for (let pageNum = chunks.length - 1; pageNum >= 0; pageNum--) {
60
- const pageIpfs = { nextCid: cids[pageNum + 1], comments: chunks[pageNum] };
61
- if (!pageIpfs.nextCid)
62
- delete pageIpfs.nextCid; // we don't to include undefined anywhere in the protocol
63
- const stringifiedPageIpfs = deterministicStringify(pageIpfs);
64
- const calculatedSizeOfStringifedPageIpfs = await calculateStringSizeSameAsIpfsAddCidV0(stringifiedPageIpfs);
65
- if (calculatedSizeOfStringifedPageIpfs > curMaxPageSize)
234
+ for (let pageNum = chunks.length - 1; pageNum >= (embedFirst ? 1 : 0); pageNum--) {
235
+ const nextCid = cids[pageNum + 1];
236
+ // pageCids pages may grow 1, 2, 4... MiB; the pages after an embedded first page start at 1 MiB
237
+ const maximumPageSize = MB * Math.pow(2, embedFirst ? Math.max(pageNum - 1, 0) : pageNum);
238
+ const entries = chunks[pageNum];
239
+ const entrySizes = entries.map(serialized.sizeOf);
240
+ const calculatedSize = pageDagSize(entrySizes, nextCid !== undefined);
241
+ if (calculatedSize > maximumPageSize)
66
242
  throw new PKCError("ERR_PAGE_GENERATED_IS_OVER_EXPECTED_SIZE", {
67
- calculatedSizeOfStringifedPageIpfs,
68
- pageIpfs,
69
- expectedSize: curMaxPageSize,
243
+ calculatedSizeOfStringifedPageIpfs: calculatedSize,
244
+ expectedSize: maximumPageSize,
245
+ commentCount: entries.length,
70
246
  sortName,
71
247
  pageNum
72
248
  });
73
- const addRes = await retryKuboIpfsAddAndProvide({
74
- ipfsClient: ipfsClient._client,
75
- log: Logger("pkc-js:page-generator:addCommentChunksToIpfs"),
76
- content: stringifiedPageIpfs,
77
- addOptions: { pin: true },
78
- provideOptions: { recursive: true },
79
- provideInBackground: true
80
- });
81
- if (addRes.size > curMaxPageSize)
82
- throw new PKCError("ERR_PAGE_GENERATED_IS_OVER_EXPECTED_SIZE", {
83
- addRes,
84
- pageIpfs,
85
- expectedSize: curMaxPageSize,
86
- sortName,
87
- pageNum
88
- });
89
- cids[pageNum] = addRes.path;
90
- listOfPage[pageNum] = pageIpfs;
91
- curMaxPageSize = curMaxPageSize / 2; // we're going backward now
249
+ // the entries' strings plus their join are both held until the add returns
250
+ const release = await budget.acquire(2 * pageBytes(entrySizes, nextCid !== undefined));
251
+ try {
252
+ cids[pageNum] = await this._buildAndAddPage({ entries, nextCid, serialized, maximumPageSize, sortName, pageNum });
253
+ }
254
+ finally {
255
+ release();
256
+ }
92
257
  }
93
- return { [sortName]: { pages: listOfPage, cids } };
258
+ if (!embedFirst)
259
+ return { [sortName]: { cids: cids } };
260
+ const firstPage = { comments: source.resolve(chunks[0]) };
261
+ if (cids[1])
262
+ firstPage.nextCid = cids[1]; // else every comment fit in the embedded page, nothing to continue to
263
+ return { [sortName]: { firstPage, cids: cids } };
94
264
  }
95
- async addPreloadedCommentChunksToIpfs(chunks, sortName) {
96
- const listOfPage = new Array(chunks.length);
97
- const cids = [undefined]; // pageCids will never have the cid of preloaded page
98
- const ipfsClient = this._community._clientsManager.getDefaultKuboRpcClient();
99
- for (let pageNum = chunks.length - 1; pageNum >= 1; pageNum--) {
100
- const pageIpfs = { nextCid: cids[pageNum + 1], comments: chunks[pageNum] };
101
- if (!pageIpfs.nextCid)
102
- delete pageIpfs.nextCid; // we don't to include undefined anywhere in the protocol
103
- const maximumPageSize = 1024 * 1024 * Math.pow(2, Math.max(pageNum - 1, 0));
104
- const stringifiedPageIpfs = deterministicStringify(pageIpfs);
105
- const calculatedSizeOfStringifedPageIpfs = await calculateStringSizeSameAsIpfsAddCidV0(stringifiedPageIpfs);
106
- if (calculatedSizeOfStringifedPageIpfs > maximumPageSize)
107
- throw new PKCError("ERR_PAGE_GENERATED_IS_OVER_EXPECTED_SIZE", {
108
- calculatedSizeOfStringifedPageIpfs,
109
- pageIpfs,
110
- maximumPageSize,
111
- sortName,
112
- pageNum
113
- });
114
- const addRes = await retryKuboIpfsAddAndProvide({
115
- ipfsClient: ipfsClient._client,
116
- log: Logger("pkc-js:page-generator:addPreloadedCommentChunksToIpfs"),
117
- content: stringifiedPageIpfs,
118
- addOptions: { pin: true },
119
- provideOptions: { recursive: true },
120
- provideInBackground: true
265
+ // Build one page's string and add it. Its own function on purpose: a suspended async function keeps its
266
+ // registers, so a page string built inside the loop of _addChunksToIpfs would stay alive while the loop waits for
267
+ // the next page's budget; here it dies with this frame as soon as the add returns (issue #351).
268
+ async _buildAndAddPage({ entries, nextCid, serialized, maximumPageSize, sortName, pageNum }) {
269
+ const content = PAGE_OPEN + serialized.stringsOf(entries).join(",") + (nextCid ? pageCloseWithNextCid(nextCid) : PAGE_CLOSE);
270
+ const addRes = await retryKuboIpfsAddAndProvide({
271
+ ipfsClient: this._community._clientsManager.getDefaultKuboRpcClient()._client,
272
+ log: Logger("pkc-js:page-generator:addChunksToIpfs"),
273
+ content,
274
+ addOptions: { pin: true },
275
+ provideOptions: { recursive: true },
276
+ provideInBackground: true
277
+ });
278
+ if (addRes.size > maximumPageSize)
279
+ throw new PKCError("ERR_PAGE_GENERATED_IS_OVER_EXPECTED_SIZE", {
280
+ addRes,
281
+ expectedSize: maximumPageSize,
282
+ commentCount: entries.length,
283
+ sortName,
284
+ pageNum
121
285
  });
122
- if (addRes.size > maximumPageSize)
123
- throw new PKCError("ERR_PAGE_GENERATED_IS_OVER_EXPECTED_SIZE", {
124
- addRes,
125
- pageIpfs,
126
- maximumPageSize,
127
- sortName,
128
- pageNum
129
- });
130
- cids[pageNum] = addRes.path;
131
- listOfPage[pageNum] = pageIpfs;
132
- }
133
- const firstPage = { comments: chunks[0], nextCid: cids[1] };
134
- if (!firstPage.nextCid)
135
- throw Error("First page should have nextCid");
136
- listOfPage[0] = firstPage;
137
- return { [sortName]: { pages: listOfPage, cids } };
286
+ return addRes.path;
138
287
  }
139
- async _chunkComments({ comments, firstPageSizeBytes }) {
288
+ // Split comments into pages: the first under firstPageSizeBytes, then 1, 2, 4... MiB, each 1 KiB under its limit.
289
+ // A comment counts as what `ipfs add` would report for its JSON alone, so the sum overestimates the page by a few
290
+ // bytes per comment; `sizeOf` gives an entry's UTF-8 JSON size when the caller already serialized it (the page
291
+ // generator does, once per entry for every sort), else each comment is stringified here.
292
+ _chunkComments({ comments, firstPageSizeBytes, sizeOf }) {
140
293
  const FIRST_PAGE_SIZE = firstPageSizeBytes; // dynamic page size for preloaded sorts, 1MB for others
141
- const SAFETY_MARGIN = 1024; // Use 1KiB margin
142
- // Calculate overhead with and without nextCid
143
- const OBJECT_WRAPPER_WITH_CID = (await calculateStringSizeSameAsIpfsAddCidV0(JSON.stringify({
144
- comments: [],
145
- nextCid: "QmXsYKgNH7XoZXdLko5uDvtWSRNE2AXuQ4u8KxVpCacrZx" // random cid as a place holder
146
- }))) - 2; // Subtract 2 for empty array "[]"
147
- const OBJECT_WRAPPER_WITHOUT_CID = (await calculateStringSizeSameAsIpfsAddCidV0(JSON.stringify({
148
- comments: []
149
- }))) - 2; // Subtract 2 for empty array "[]"
294
+ const bytesOf = sizeOf ?? ((entry) => Buffer.byteLength(JSON.stringify(entry)));
295
+ const entryBytes = new Array(comments.length);
296
+ for (let i = 0; i < comments.length; i++)
297
+ entryBytes[i] = bytesOf(comments[i]);
150
298
  // Quick check for small arrays - if everything fits in one page, no nextCid needed
151
- const totalSizeWithoutCid = await calculateStringSizeSameAsIpfsAddCidV0(JSON.stringify({ comments }));
152
- if (totalSizeWithoutCid <= FIRST_PAGE_SIZE) {
299
+ if (pageDagSize(entryBytes, false) <= FIRST_PAGE_SIZE)
153
300
  return [comments]; // Single page, no chunking needed
154
- }
301
+ // The wrapper's cost with and without nextCid, minus the 2 bytes of the empty array
302
+ const OBJECT_WRAPPER_WITH_CID = pageDagSize([], true) - 2;
303
+ const OBJECT_WRAPPER_WITHOUT_CID = pageDagSize([], false) - 2;
155
304
  const chunks = [];
156
305
  let currentChunk = [];
157
306
  let chunkIndex = 0;
158
307
  let accumulatedSize = OBJECT_WRAPPER_WITH_CID;
159
- // Pre-calculate sizes to avoid repeated stringification
160
- const commentSizes = new Map();
161
- async function getCommentSize(index) {
162
- if (!commentSizes.has(index)) {
163
- const size = await calculateStringSizeSameAsIpfsAddCidV0(JSON.stringify(comments[index]));
164
- commentSizes.set(index, size);
165
- }
166
- return commentSizes.get(index);
167
- }
168
- function getCurrentMaxSize(index) {
169
- if (index === 0) {
170
- return FIRST_PAGE_SIZE; // First page is dynamic for preloaded
171
- }
172
- else {
173
- const MB = 1024 * 1024;
174
- // For preloaded: dynamic page size, 1MB, 2MB, 4MB, etc.
175
- // For non-preloaded: 1MB, 2MB, 4MB, 8MB, etc.
176
- return MB * Math.pow(2, index - 1); // index-1 because we want to start with 1MB
177
- }
178
- }
308
+ // The first page is dynamic; the pages after it 1MB, 2MB, 4MB...
309
+ const getCurrentMaxSize = (index) => (index === 0 ? FIRST_PAGE_SIZE : MB * Math.pow(2, index - 1));
179
310
  for (let i = 0; i < comments.length; i++) {
180
- const commentSize = await getCommentSize(i);
311
+ const commentSize = calculateUnixFsDagSizeCidV0(entryBytes[i]);
181
312
  const maxSize = getCurrentMaxSize(chunkIndex);
182
313
  const isLastItem = i === comments.length - 1;
183
314
  // Add comma if needed
@@ -188,12 +319,7 @@ export class PageGenerator {
188
319
  chunks.push(currentChunk);
189
320
  currentChunk = [];
190
321
  chunkIndex++;
191
- if (isLastItem) {
192
- accumulatedSize = OBJECT_WRAPPER_WITHOUT_CID;
193
- }
194
- else {
195
- accumulatedSize = OBJECT_WRAPPER_WITH_CID;
196
- }
322
+ accumulatedSize = isLastItem ? OBJECT_WRAPPER_WITHOUT_CID : OBJECT_WRAPPER_WITH_CID;
197
323
  }
198
324
  else if (commentSize > maxSize - SAFETY_MARGIN) {
199
325
  const log = Logger("pkc-js:page-generator:_chunkComments");
@@ -204,111 +330,272 @@ export class PageGenerator {
204
330
  currentChunk.push(comments[i]);
205
331
  accumulatedSize += commaSize + commentSize;
206
332
  }
207
- if (currentChunk.length > 0) {
333
+ if (currentChunk.length > 0)
208
334
  chunks.push(currentChunk);
209
- }
210
335
  return chunks;
211
336
  }
212
- async sortAndChunkComments(unsortedComments, sortName, options) {
337
+ // Apply one sort to a loaded comment set: pinned placement, the maxAge window, then the file's `score` per
338
+ // comment, which drops what it declines.
339
+ // The exclusions were already applied by the SQL that loaded the set, and to `replies` (the scope's descendants,
340
+ // loaded once per generation and only for a file that declares requireReplies).
341
+ sortComments(comments, sort, baseTimestamp, replies, stripEntry, streamReplies) {
342
+ const { options } = sort;
343
+ const { pinned, unpinned } = partitionPageCommentsForSort({ comments, reserved: sort, baseTimestamp });
344
+ if (pinned.length + unpinned.length === 0)
345
+ return [];
346
+ // a requireReplies post sort scores pinned then unpinned in this order, and the provider streams subtrees along it
347
+ const descendantsOf = streamReplies && sort.file.requireReplies ? streamReplies([...pinned, ...unpinned]) : undefined;
348
+ return orderPageCommentsByScore({
349
+ pinned,
350
+ unpinned,
351
+ sortName: sort.sortName,
352
+ scoreOf: scorePageCommentsWithFile({ file: sort.file, options, baseTimestamp, replies, stripEntry, descendantsOf })
353
+ });
354
+ }
355
+ async sortAndChunkComments(unsortedComments, sort, options) {
213
356
  if (unsortedComments.length === 0)
214
357
  throw Error("Should not provide empty array of comments to sort");
215
- const sortProps = options.parentCid
216
- ? POST_REPLIES_SORT_TYPES[sortName]
217
- : POSTS_SORT_TYPES[sortName];
218
- if (typeof sortProps.score !== "function")
219
- throw Error(`SortProps[${sortName}] score function is not defined`);
220
- const scoreSort = (obj1, obj2) => {
221
- // calculated from DB
222
- if (sortName === "active") {
223
- if (typeof obj1.activeScore !== "number")
224
- throw Error("Active score is not defined");
225
- if (typeof obj2.activeScore !== "number")
226
- throw Error("Active score is not defined");
227
- return obj2.activeScore - obj1.activeScore;
228
- }
229
- else {
230
- const score1 = sortProps.score(obj1);
231
- const score2 = sortProps.score(obj2);
232
- return score2 - score1;
233
- }
234
- };
235
- const pinnedComments = unsortedComments.filter((obj) => obj.commentUpdate.pinned === true).sort(scoreSort);
236
- let unpinnedComments = unsortedComments.filter((obj) => !obj.commentUpdate.pinned).sort(scoreSort);
237
- if (sortProps.timeframe) {
238
- const timestampLower = options.baseTimestamp - TIMEFRAMES_TO_SECONDS[sortProps.timeframe];
239
- unpinnedComments = unpinnedComments.filter((obj) => obj.comment.timestamp >= timestampLower);
240
- }
241
- const commentsSorted = pinnedComments.concat(unpinnedComments).map((comment) => omit(comment, ["activeScore"]));
358
+ const commentsSorted = this.sortComments(unsortedComments, sort, options.baseTimestamp, options.replies, options.stripEntry, options.streamReplies);
242
359
  if (commentsSorted.length === 0)
243
360
  return [];
244
- const commentsChunks = await this._chunkComments({
245
- comments: commentsSorted,
246
- firstPageSizeBytes: options.firstPageSizeBytes
247
- });
248
- return commentsChunks;
361
+ return this._chunkComments({ comments: commentsSorted, firstPageSizeBytes: options.firstPageSizeBytes, sizeOf: options.sizeOf });
249
362
  }
250
- // Resolves to sortedComments
251
- // this is for non preloaded sorts
252
- async sortChunkAddIpfsNonPreloaded(comments, sortName, options) {
253
- const commentsChunks = await this.sortAndChunkComments(comments, sortName, options);
254
- if (commentsChunks.length === 0)
255
- return undefined;
256
- const res = await this.addCommentChunksToIpfs(commentsChunks, sortName);
257
- return res;
363
+ // The reply set requireReplies sorts score over: one unfiltered query per generation, shared by every such sort,
364
+ // each filtering it with its own exclusion options in JS. Nothing runs unless a sort asks.
365
+ _createRepliesLoader(load) {
366
+ let all;
367
+ const filtered = new Map();
368
+ return (sort) => {
369
+ all ??= load();
370
+ const key = JSON.stringify(sort.exclusions);
371
+ if (!filtered.has(key))
372
+ filtered.set(key, applyPageSortExclusions({ comments: all, exclusions: sort.exclusions, communityAddress: this._community.address }));
373
+ return filtered.get(key);
374
+ };
258
375
  }
259
- _generationResToPages(res) {
260
- const filteredGeneratedPages = res.filter(Boolean); // Take out undefined values
261
- if (filteredGeneratedPages.length === 0)
262
- return undefined;
263
- const mergedObject = Object.assign({}, ...filteredGeneratedPages);
264
- return {
265
- pages: Object.assign({}, ...Object.entries(mergedObject).map(([sortName, pages]) => ({ [sortName]: pages.pages[0] }))),
266
- pageCids: Object.assign({}, ...Object.entries(mergedObject).map(([sortName, pages]) => (pages.cids?.[0] ? { [sortName]: pages.cids[0] } : undefined))),
267
- allPageCids: Object.assign({}, ...Object.entries(mergedObject).map(([sortName, data]) => data.cids ? { [sortName]: data.cids.filter((c) => typeof c === "string") } : undefined))
376
+ // The reply set a requireReplies post sort scores over, streamed: the provider walks the posts in the order the
377
+ // scorer asks for them and loads one batch of posts' subtrees at a time (lean entries, the sort's exclusions
378
+ // applied), so a board's replies are never in memory at once; the file still receives each post's whole subtree
379
+ // (issue #351). A post asked out of order (never, in practice) just loads its own batch.
380
+ _createStreamingRepliesProvider(sort) {
381
+ const db = this._community._dbHandler;
382
+ const communityAddress = this._community.address;
383
+ return (ordered) => {
384
+ const indexOf = new Map(ordered.map((entry, index) => [entry.commentUpdate.cid, index]));
385
+ let batchStart = -1;
386
+ let batchEnd = -1;
387
+ let descendantsOf;
388
+ return (cid) => {
389
+ const index = indexOf.get(cid);
390
+ if (index === undefined)
391
+ return [];
392
+ if (index < batchStart || index >= batchEnd || !descendantsOf) {
393
+ batchStart = index;
394
+ batchEnd = Math.min(index + SERIALIZE_BATCH, ordered.length);
395
+ const replies = db.queryAllRepliesForPageSort({
396
+ postCids: ordered.slice(batchStart, batchEnd).map((entry) => entry.commentUpdate.cid)
397
+ });
398
+ descendantsOf = createDescendantsLookup(applyPageSortExclusions({ comments: replies, exclusions: sort.exclusions, communityAddress }));
399
+ }
400
+ return descendantsOf(cid);
401
+ };
268
402
  };
269
403
  }
270
- async generateCommunityPosts(preloadedPageSortName, preloadedPageSizeBytes) {
271
- const pageOptions = {
272
- excludeCommentsWithDifferentCommunityAddress: true,
273
- excludeDeletedComments: true,
274
- excludeRemovedComments: true,
275
- excludeCommentPendingApproval: true,
276
- excludeCommentWithApprovedFalse: true,
277
- parentCid: null,
278
- preloadedPage: preloadedPageSortName,
279
- baseTimestamp: timestamp(),
280
- firstPageSizeBytes: preloadedPageSizeBytes
404
+ // Load the comment set each sort runs over. Sorts that share the same exclusion options (all of them, unless an
405
+ // owner set an exclude* option on one entry) share one query; flat sorts have their own query shape.
406
+ _createCommentLoader(load) {
407
+ const cache = new Map();
408
+ const loader = (sort) => {
409
+ const exclusions = sort.exclusions;
410
+ const key = `${sort.flat}:${JSON.stringify(exclusions)}`;
411
+ if (!cache.has(key))
412
+ cache.set(key, load(exclusions, sort.flat));
413
+ return cache.get(key);
281
414
  };
282
- // Sorting posts on a community level
283
- const rawPostsUnresolved = this._community._dbHandler.queryPostsWithActiveScore(pageOptions);
284
- if (rawPostsUnresolved.length === 0)
415
+ loader.loaded = () => [...cache.values()].flat(); // every entry any sort of this generation loaded
416
+ return loader;
417
+ }
418
+ // Generic generation for one scope (community posts, or one comment's replies) from its configured sorts.
419
+ //
420
+ // 1. Every sort is applied to its comment set; a sort whose file throws is recorded in failedSorts and skipped so
421
+ // the remaining sorts still publish. When that leaves no pages at all the generation throws
422
+ // ERR_ALL_PAGE_SORTS_FAILED_TO_GENERATE (failedSorts in its details) instead of returning "nothing to
423
+ // paginate": publishing an empty scope would replace a live feed on a sort file's bug. Loading the comment
424
+ // set is pkc-js's own code, so an error there (a busy sqlite, a malformed row) propagates as well. Either
425
+ // way the cycle aborts, the last good record stays published and the sync loop retries next cycle.
426
+ // 2. Preloaded sorts split preloadedPageSizeBytes equally. One whose first chunk does not fit its share drops to
427
+ // pageCids (it is generated like a non-preloaded sort) while the others still embed.
428
+ // 3. If every preloaded sort fits in a single chunk, the single-page shortcut applies: only those pages are returned
429
+ // and nothing else is generated (docs/protocol/pages.md, "single-chunk shortcut"). Skipping the other sorts is
430
+ // only sound when a client holding the embedded pages can build them locally, so when any sort would be
431
+ // skipped (a non-preloaded or demoted one) every embedded page must hold the whole comment set: a windowed or
432
+ // filtered preloaded sort next to other sorts takes the full path instead. With no preloaded sort at all there
433
+ // is no shortcut and every sort goes to pageCids.
434
+ // 4. Otherwise preloaded sorts embed their first chunk and add the rest to IPFS; the others add every chunk.
435
+ // Results keep the configured order so `pages` keys tell a client which preloaded sort is the default.
436
+ //
437
+ // The comment sets are lean: an entry carries its DB-format reply refs, which `score` never sees since `replies`
438
+ // is stripped. What a page holds for an entry comes from `source`, which resolves a batch's nested reply pages
439
+ // from rows; entries are sized through SerializedEntries (serialized a batch at a time, strings kept in the
440
+ // entry cache while its budget allows) and fetched per page when built, so a sort costs an ordered list of
441
+ // references, and a board's reply trees stream through instead of being held at once (issue #351).
442
+ async _generatePagesForSorts({ scope, sorts, loadComments, loadReplies, streamReplies, source, entryCache, preloadedPageSizeBytes, baseTimestamp, parentCid }) {
443
+ if (sorts.length === 0)
285
444
  return undefined;
286
- // Resolve CID-ref replies for each post so pages have full nested reply trees
287
- const rawPosts = this._community._dbHandler.resolveRepliesCidRefsForEntries(rawPostsUnresolved);
288
- const preloadedChunk = await this.sortAndChunkComments(rawPosts, preloadedPageSortName, pageOptions);
289
- const firstChunkSize = await getSerializedCommentsSize(preloadedChunk[0], preloadedChunk.length > 1);
290
- if (firstChunkSize > preloadedPageSizeBytes)
291
- throw new PKCError("ERR_PAGE_GENERATED_IS_OVER_EXPECTED_SIZE", {
292
- firstChunkSize,
293
- preloadedPageSizeBytes,
294
- sortName: preloadedPageSortName
445
+ const failedSorts = {};
446
+ const fail = (sort, error) => {
447
+ failedSorts[sort.sortName] = new PKCError("ERR_PAGE_SORT_FAILED_TO_GENERATE", {
448
+ sortName: sort.sortName,
449
+ scope,
450
+ pageSortName: sort.settings.name ?? sort.settings.path,
451
+ error
295
452
  });
296
- if (preloadedChunk.length === 1)
297
- return { singlePreloadedPage: { [preloadedPageSortName]: { comments: preloadedChunk[0] } } }; // all comments fit in one preloaded page
298
- // we're gonna have pages for each sort type, they don't fit in a single preloaded chunk
299
- const sortResults = [];
300
- sortResults.push(await this.addPreloadedCommentChunksToIpfs(preloadedChunk, preloadedPageSortName));
301
- const nonPreloadedSorts = keys(POSTS_SORT_TYPES).filter((sortName) => sortName !== preloadedPageSortName);
302
- await Promise.all(nonPreloadedSorts.map(async (sortName) => {
303
- sortResults.push(await this.sortChunkAddIpfsNonPreloaded(rawPosts, sortName, {
304
- ...pageOptions,
305
- firstPageSizeBytes: 1024 * 1024
306
- }));
307
- }));
308
- const generatedPages = this._generationResToPages(sortResults);
453
+ };
454
+ // Load first so the preload budget is split only among preloaded sorts that have something to embed. Not
455
+ // inside the per-sort net: only the sort file's own code may fail a single sort (see 1. above).
456
+ const loaded = [];
457
+ for (const sort of sorts) {
458
+ const comments = loadComments(sort);
459
+ if (comments.length > 0)
460
+ loaded.push({ sort, comments, ...(sort.file.requireReplies && loadReplies ? { replies: loadReplies(sort) } : {}) });
461
+ }
462
+ if (loaded.length === 0)
463
+ return undefined;
464
+ const serialized = new SerializedEntries(source, entryCache);
465
+ for (const { comments } of loaded)
466
+ serialized.sizeAll(comments); // one serialization per entry, shared by every sort
467
+ const strippedEntries = new Map(); // what `score` receives, built once per entry
468
+ const stripEntry = (entry) => {
469
+ let stripped = strippedEntries.get(entry);
470
+ if (!stripped)
471
+ strippedEntries.set(entry, (stripped = stripRepliesFromPageComment(entry)));
472
+ return stripped;
473
+ };
474
+ const preloadedCount = loaded.filter(({ sort }) => sort.preloaded).length;
475
+ const share = preloadedCount > 0 ? Math.floor(preloadedPageSizeBytes / preloadedCount) : 0;
476
+ const preloaded = [];
477
+ const nonPreloaded = [];
478
+ for (const { sort, comments, replies } of loaded) {
479
+ let chunks;
480
+ try {
481
+ chunks = await this.sortAndChunkComments(comments, sort, {
482
+ baseTimestamp,
483
+ parentCid,
484
+ firstPageSizeBytes: sort.preloaded ? share : NON_PRELOADED_FIRST_PAGE_SIZE,
485
+ replies,
486
+ sizeOf: serialized.sizeOf,
487
+ stripEntry,
488
+ streamReplies: streamReplies?.(sort)
489
+ });
490
+ }
491
+ catch (e) {
492
+ fail(sort, e);
493
+ continue;
494
+ }
495
+ if (chunks.length === 0)
496
+ continue; // every comment filtered out
497
+ if (!sort.preloaded) {
498
+ nonPreloaded.push({ sort, chunks });
499
+ continue;
500
+ }
501
+ const firstChunkSize = pageDagSize(chunks[0].map(serialized.sizeOf), chunks.length > 1);
502
+ if (firstChunkSize > share) {
503
+ // Its share of the budget is not enough even for its first chunk: degrade this sort to pageCids
504
+ const rechunked = this._chunkComments({
505
+ comments: chunks.flat(),
506
+ firstPageSizeBytes: NON_PRELOADED_FIRST_PAGE_SIZE,
507
+ sizeOf: serialized.sizeOf
508
+ });
509
+ nonPreloaded.push({ sort, chunks: rechunked });
510
+ }
511
+ else
512
+ preloaded.push({
513
+ sort,
514
+ chunks,
515
+ holdsWholeSet: chunks.length === 1 && chunks[0].length === comments.length
516
+ });
517
+ }
518
+ if (preloaded.length === 0 && nonPreloaded.length === 0) {
519
+ // Nothing to publish for this scope. With a sort file to blame that is not a verdict on the feed (the sort may
520
+ // throw on every comment), so the cycle aborts and the last published pages stay up (see 1. above).
521
+ if (Object.keys(failedSorts).length > 0)
522
+ throw new PKCError("ERR_ALL_PAGE_SORTS_FAILED_TO_GENERATE", { scope, parentCid, failedSorts });
523
+ return undefined;
524
+ }
525
+ const everyPreloadedIsSingleChunk = preloaded.length > 0 && preloaded.every(({ chunks }) => chunks.length === 1);
526
+ const skippingOtherSortsIsSound = nonPreloaded.length === 0 || preloaded.every(({ holdsWholeSet }) => holdsWholeSet);
527
+ if (everyPreloadedIsSingleChunk && skippingOtherSortsIsSound) {
528
+ const singlePreloadedPage = {};
529
+ for (const { sort, chunks } of preloaded)
530
+ singlePreloadedPage[sort.sortName] = { comments: source.resolve(chunks[0]) };
531
+ return { singlePreloadedPage, failedSorts };
532
+ }
533
+ // Full generation. Preloaded first, then the others, each in configured order; IPFS adds run in parallel
534
+ // under one byte budget for the pages in flight.
535
+ const budget = new PageBytesBudget(serializedEntryCacheBudgetBytes());
536
+ const results = await Promise.all([
537
+ ...preloaded.map(({ sort, chunks }) => this._addChunksToIpfs({ chunks, sortName: sort.sortName, serialized, embedFirst: true, source, budget })),
538
+ ...nonPreloaded.map(({ sort, chunks }) => this._addChunksToIpfs({ chunks, sortName: sort.sortName, serialized, embedFirst: false, source, budget }))
539
+ ]);
540
+ const generatedPages = this._generationResToPages(results);
309
541
  if (!generatedPages)
310
542
  return undefined;
311
- return generatedPages;
543
+ return { ...generatedPages, failedSorts };
544
+ }
545
+ _generationResToPages(res) {
546
+ const filteredGeneratedPages = res.filter(Boolean); // Take out undefined values
547
+ if (filteredGeneratedPages.length === 0)
548
+ return undefined;
549
+ const mergedObject = Object.assign({}, ...filteredGeneratedPages);
550
+ const pages = {};
551
+ const pageCids = {};
552
+ const allPageCids = {};
553
+ for (const [sortName, data] of Object.entries(mergedObject)) {
554
+ if (!data)
555
+ continue;
556
+ const firstCid = data.cids[0];
557
+ if (firstCid)
558
+ pageCids[sortName] = firstCid; // pageCids never carries a preloaded sort's first page
559
+ else {
560
+ assert(data.firstPage, `preloaded sort ${sortName} has no embedded first page`);
561
+ pages[sortName] = data.firstPage;
562
+ }
563
+ const cids = data.cids;
564
+ allPageCids[sortName] = cids.filter((c) => typeof c === "string");
565
+ }
566
+ return { pages, ...(Object.keys(pageCids).length > 0 ? { pageCids } : {}), allPageCids };
567
+ }
568
+ async _pageSortsFor(scope) {
569
+ return (await this._community._ensurePageSortsLoaded())[scope];
570
+ }
571
+ // Page entries out of the community database: each batch's nested reply pages resolved from rows. Plain
572
+ // JSON.stringify is canonical here: the row mapper builds every object with sorted keys
573
+ // (createPositionalCommentRowMapper), so this is byte-identical to safe-stable-stringify at native speed.
574
+ _dbPageEntrySource() {
575
+ const db = this._community._dbHandler;
576
+ const resolve = (entries) => db.resolveRepliesCidRefsForEntries(entries);
577
+ return { resolve, serialize: (entries) => resolve(entries).map((entry) => JSON.stringify(entry)) };
578
+ }
579
+ async generateCommunityPosts({ preloadedPageSizeBytes }) {
580
+ const baseTimestamp = timestamp();
581
+ const sorts = await this._pageSortsFor("posts");
582
+ // Posts are loaded lean, their reply pages still as CID refs; the page entry source resolves them per batch
583
+ // (issue #351)
584
+ const loadComments = this._createCommentLoader((exclusions) => this._community._dbHandler.queryPosts({ ...exclusions, parentCid: null }));
585
+ this._postEntryCache ??= new EntryJsonCache(serializedEntryCacheBudgetBytes());
586
+ const generated = await this._generatePagesForSorts({
587
+ scope: "posts",
588
+ sorts,
589
+ loadComments,
590
+ streamReplies: (sort) => this._createStreamingRepliesProvider(sort), // a requireReplies post sort streams each post's subtree
591
+ source: this._dbPageEntrySource(),
592
+ entryCache: this._postEntryCache,
593
+ preloadedPageSizeBytes,
594
+ baseTimestamp,
595
+ parentCid: null
596
+ });
597
+ this._postEntryCache.retainOnly(new Set(loadComments.loaded().map((entry) => entry.commentUpdate.cid)));
598
+ return generated;
312
599
  }
313
600
  async _bundleLatestCommentUpdateWithQueuedComments(queuedComment) {
314
601
  const communityAuthor = this._community._dbHandler.queryCommunityAuthor(queuedComment.authorSignerAddress);
@@ -340,107 +627,43 @@ export class PageGenerator {
340
627
  return undefined;
341
628
  const queuedComments = await Promise.all(commentsPendingApproval.map((comment) => this._bundleLatestCommentUpdateWithQueuedComments(comment)));
342
629
  const combinedHashOfCids = sha256(queuedComments.map((comment) => comment.commentUpdate.cid).join(""));
343
- const chunkedQueuedComments = await this._chunkComments({ comments: queuedComments, firstPageSizeBytes });
630
+ const chunkedQueuedComments = this._chunkComments({ comments: queuedComments, firstPageSizeBytes });
344
631
  const pages = await this.addQueuedCommentChunksToIpfs(chunkedQueuedComments, "pendingApproval");
345
632
  return { pageCids: { pendingApproval: pages.cids[0] }, combinedHashOfCids };
346
633
  }
347
- async generatePostPages(comment, preloadedReplyPageSortName, preloadedPageSizeBytes) {
348
- const pageOptions = {
349
- excludeCommentsWithDifferentCommunityAddress: true,
350
- excludeDeletedComments: false,
351
- excludeRemovedComments: false,
352
- excludeCommentWithApprovedFalse: false,
353
- excludeCommentPendingApproval: true,
354
- parentCid: comment.cid,
355
- preloadedPage: preloadedReplyPageSortName,
356
- baseTimestamp: timestamp()
357
- };
358
- // Use recursive query that follows CID-ref lists in DB replies to build nested trees
359
- const hierarchalReplies = this._community._dbHandler.queryPageCommentsWithResolvedReplies(pageOptions);
360
- if (hierarchalReplies.length === 0)
361
- return undefined;
362
- const preloadedChunk = await this.sortAndChunkComments(hierarchalReplies, preloadedReplyPageSortName, {
363
- ...pageOptions,
364
- firstPageSizeBytes: preloadedPageSizeBytes
634
+ // Reply pages of one comment. Flat sorts (flattened descendant subtree) are generated for post replies only:
635
+ // depth-1+ comments have never had them and one settings.pages.replies list applies at every depth.
636
+ async _generateRepliesPages(comment, preloadedPageSizeBytes, includeFlatSorts) {
637
+ const baseTimestamp = timestamp();
638
+ const sorts = (await this._pageSortsFor("replies")).filter((sort) => includeFlatSorts || !sort.flat);
639
+ const loadComments = this._createCommentLoader((exclusions, flat) => {
640
+ const pageOptions = { ...exclusions, parentCid: comment.cid, baseTimestamp };
641
+ return flat
642
+ ? this._community._dbHandler.queryFlattenedPageReplies({ ...pageOptions, commentUpdateFieldsToExclude: ["replies"] })
643
+ : this._community._dbHandler.queryPageCommentsWithResolvedReplies(pageOptions); // recursive query following CID-ref lists in DB replies to build nested trees
365
644
  });
366
- const firstChunkSize = await getSerializedCommentsSize(preloadedChunk[0], preloadedChunk.length > 1);
367
- const disablePreload = firstChunkSize > preloadedPageSizeBytes;
368
- if (!disablePreload && preloadedChunk.length === 1)
369
- return { singlePreloadedPage: { [preloadedReplyPageSortName]: { comments: preloadedChunk[0] } } }; // all comments fit in one page
370
- const sortResults = [];
371
- if (disablePreload) {
372
- sortResults.push(await this.sortChunkAddIpfsNonPreloaded(hierarchalReplies, preloadedReplyPageSortName, {
373
- ...pageOptions,
374
- firstPageSizeBytes: 1024 * 1024
375
- }));
376
- }
377
- else {
378
- sortResults.push(await this.addPreloadedCommentChunksToIpfs(preloadedChunk, preloadedReplyPageSortName));
379
- }
380
- const nonPreloadedSorts = keys(POST_REPLIES_SORT_TYPES).filter((sortName) => sortName !== preloadedReplyPageSortName);
381
- const flattenedReplies = this._community._dbHandler.queryFlattenedPageReplies({
382
- ...pageOptions,
383
- commentUpdateFieldsToExclude: ["replies"]
645
+ // Every reply under the comment's post, for a requireReplies reply sort; each reply's own subtree is sliced from it
646
+ const loadReplies = this._createRepliesLoader(() => {
647
+ const postCid = comment.postCid ?? this._community._dbHandler.queryComment(comment.cid)?.postCid ?? comment.cid;
648
+ return this._community._dbHandler.queryAllRepliesForPageSort({ postCid });
384
649
  });
385
- await Promise.all(nonPreloadedSorts.map(async (sortName) => {
386
- const replies = POST_REPLIES_SORT_TYPES[sortName].flat ? flattenedReplies : hierarchalReplies;
387
- sortResults.push(await this.sortChunkAddIpfsNonPreloaded(replies, sortName, { ...pageOptions, firstPageSizeBytes: 1024 * 1024 }));
388
- }));
389
- const generatedPages = this._generationResToPages(sortResults);
390
- if (!generatedPages)
391
- return undefined;
392
- if (disablePreload)
393
- return { pageCids: generatedPages.pageCids, pages: {}, allPageCids: generatedPages.allPageCids };
394
- else
395
- return generatedPages;
396
- }
397
- async generateReplyPages(comment, preloadedReplyPageSortName, preloadedPageSizeBytes) {
398
- const pageOptions = {
399
- excludeCommentsWithDifferentCommunityAddress: true,
400
- excludeDeletedComments: false,
401
- excludeRemovedComments: false,
402
- excludeCommentPendingApproval: true,
403
- parentCid: comment.cid,
404
- preloadedPage: preloadedReplyPageSortName,
405
- baseTimestamp: timestamp(),
406
- excludeCommentWithApprovedFalse: false
407
- };
408
- // Use recursive query that follows CID-ref lists in DB replies to build nested trees
409
- const hierarchalReplies = this._community._dbHandler.queryPageCommentsWithResolvedReplies(pageOptions);
410
- if (hierarchalReplies.length === 0)
411
- return undefined;
412
- const preloadedChunk = await this.sortAndChunkComments(hierarchalReplies, preloadedReplyPageSortName, {
413
- ...pageOptions,
414
- firstPageSizeBytes: preloadedPageSizeBytes
650
+ return this._generatePagesForSorts({
651
+ scope: "replies",
652
+ sorts,
653
+ loadComments,
654
+ loadReplies,
655
+ source: this._dbPageEntrySource(),
656
+ entryCache: new EntryJsonCache(serializedEntryCacheBudgetBytes()), // one comment's replies, dropped with the generation
657
+ preloadedPageSizeBytes,
658
+ baseTimestamp,
659
+ parentCid: comment.cid
415
660
  });
416
- const firstChunkSize = await getSerializedCommentsSize(preloadedChunk[0], preloadedChunk.length > 1);
417
- const disablePreload = firstChunkSize > preloadedPageSizeBytes;
418
- if (!disablePreload && preloadedChunk.length === 1)
419
- return { singlePreloadedPage: { [preloadedReplyPageSortName]: { comments: preloadedChunk[0] } } }; // all comments fit in one page
420
- const nonPreloadedSorts = keys(REPLY_REPLIES_SORT_TYPES).filter((sortName) => sortName !== preloadedReplyPageSortName);
421
- const sortResults = [];
422
- if (disablePreload) {
423
- sortResults.push(await this.sortChunkAddIpfsNonPreloaded(hierarchalReplies, preloadedReplyPageSortName, {
424
- ...pageOptions,
425
- firstPageSizeBytes: 1024 * 1024
426
- }));
427
- }
428
- else {
429
- sortResults.push(await this.addPreloadedCommentChunksToIpfs(preloadedChunk, preloadedReplyPageSortName));
430
- }
431
- await Promise.all(nonPreloadedSorts.map(async (hierarchalSortName) => {
432
- sortResults.push(await this.sortChunkAddIpfsNonPreloaded(hierarchalReplies, hierarchalSortName, {
433
- ...pageOptions,
434
- firstPageSizeBytes: 1024 * 1024 // pageCids will always have first pages with limit of 1mib, regardless of preloadedPageSizeBytes
435
- }));
436
- }));
437
- const generatedPages = this._generationResToPages(sortResults);
438
- if (!generatedPages)
439
- return undefined;
440
- if (disablePreload)
441
- return { pageCids: generatedPages.pageCids, pages: {}, allPageCids: generatedPages.allPageCids };
442
- else
443
- return generatedPages;
661
+ }
662
+ async generatePostPages(comment, preloadedPageSizeBytes) {
663
+ return this._generateRepliesPages(comment, preloadedPageSizeBytes, true);
664
+ }
665
+ async generateReplyPages(comment, preloadedPageSizeBytes) {
666
+ return this._generateRepliesPages(comment, preloadedPageSizeBytes, false);
444
667
  }
445
668
  toJSON() {
446
669
  return undefined;