@pkcprotocol/pkc-js 0.0.107 → 0.0.109
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.
- package/README.md +15 -0
- package/dist/browser/clients/base-client-manager.d.ts +1 -1
- package/dist/browser/clients/base-client-manager.js +6 -2
- package/dist/browser/clients/base-client-manager.js.map +1 -1
- package/dist/browser/clients/rpc-client/rpc-schema-util.d.ts +16 -0
- package/dist/browser/clients/rpc-client/schema.d.ts +16 -0
- package/dist/browser/community/remote-community.d.ts +1 -0
- package/dist/browser/community/remote-community.js +1 -0
- package/dist/browser/community/remote-community.js.map +1 -1
- package/dist/browser/community/rpc-local-community.js +13 -0
- package/dist/browser/community/rpc-local-community.js.map +1 -1
- package/dist/browser/community/schema.d.ts +1369 -1
- package/dist/browser/community/schema.js +90 -4
- package/dist/browser/community/schema.js.map +1 -1
- package/dist/browser/community/types.d.ts +13 -2
- package/dist/browser/constants.d.ts +1 -0
- package/dist/browser/constants.js +6 -0
- package/dist/browser/constants.js.map +1 -1
- package/dist/browser/errors.d.ts +15 -1
- package/dist/browser/errors.js +15 -0
- package/dist/browser/errors.js.map +1 -1
- package/dist/browser/generated-version.d.ts +1 -1
- package/dist/browser/generated-version.js +1 -1
- package/dist/browser/helia/helia-for-pkc.js +41 -4
- package/dist/browser/helia/helia-for-pkc.js.map +1 -1
- package/dist/browser/helia/util.js +4 -1
- package/dist/browser/helia/util.js.map +1 -1
- package/dist/browser/index-with-compile-cache.d.ts +559 -0
- package/dist/browser/index.d.ts +1120 -0
- package/dist/browser/index.js +5 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/logger.js +10 -1
- package/dist/browser/logger.js.map +1 -1
- package/dist/browser/object-key-util.d.ts +2 -0
- package/dist/browser/object-key-util.js +12 -0
- package/dist/browser/object-key-util.js.map +1 -0
- package/dist/browser/pages/page-sort-options.d.ts +14 -0
- package/dist/browser/pages/page-sort-options.js +92 -0
- package/dist/browser/pages/page-sort-options.js.map +1 -0
- package/dist/browser/pages/page-sort-scoring.d.ts +35 -0
- package/dist/browser/pages/page-sort-scoring.js +124 -0
- package/dist/browser/pages/page-sort-scoring.js.map +1 -0
- package/dist/browser/pages/pages-client-manager.d.ts +1 -0
- package/dist/browser/pages/pages-client-manager.js +16 -4
- package/dist/browser/pages/pages-client-manager.js.map +1 -1
- package/dist/browser/pages/pkc-js-page-sorts/active.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/active.js +15 -0
- package/dist/browser/pages/pkc-js-page-sorts/active.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/best.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/best.js +10 -0
- package/dist/browser/pages/pkc-js-page-sorts/best.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/controversial.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/controversial.js +10 -0
- package/dist/browser/pages/pkc-js-page-sorts/controversial.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/hot.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/hot.js +10 -0
- package/dist/browser/pages/pkc-js-page-sorts/hot.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/index.d.ts +2 -0
- package/dist/browser/pages/pkc-js-page-sorts/index.js +37 -0
- package/dist/browser/pages/pkc-js-page-sorts/index.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/new-flat.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/new-flat.js +12 -0
- package/dist/browser/pages/pkc-js-page-sorts/new-flat.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/new.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/new.js +10 -0
- package/dist/browser/pages/pkc-js-page-sorts/new.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/old-flat.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/old-flat.js +12 -0
- package/dist/browser/pages/pkc-js-page-sorts/old-flat.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/old.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/old.js +10 -0
- package/dist/browser/pages/pkc-js-page-sorts/old.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-all.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-all.js +10 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-all.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-day.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-day.js +11 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-day.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-hour.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-hour.js +11 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-hour.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-month.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-month.js +11 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-month.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-week.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-week.js +11 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-week.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-year.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-year.js +11 -0
- package/dist/browser/pages/pkc-js-page-sorts/top-year.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/top.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/top.js +12 -0
- package/dist/browser/pages/pkc-js-page-sorts/top.js.map +1 -0
- package/dist/browser/pages/pkc-js-page-sorts/util.d.ts +3 -0
- package/dist/browser/pages/pkc-js-page-sorts/util.js +7 -0
- package/dist/browser/pages/pkc-js-page-sorts/util.js.map +1 -0
- package/dist/browser/pages/schema.d.ts +1 -0
- package/dist/browser/pages/schema.js +10 -3
- package/dist/browser/pages/schema.js.map +1 -1
- package/dist/browser/pages/types.d.ts +8 -0
- package/dist/browser/pages/util.d.ts +2 -1
- package/dist/browser/pages/util.js +11 -10
- package/dist/browser/pages/util.js.map +1 -1
- package/dist/browser/pkc/pkc.d.ts +2 -1
- package/dist/browser/pkc/pkc.js +2 -1
- package/dist/browser/pkc/pkc.js.map +1 -1
- package/dist/browser/pkc-error.d.ts +1 -1
- package/dist/browser/publications/comment/comment-client-manager.d.ts +2 -2
- package/dist/browser/publications/comment/comment-client-manager.js +3 -1
- package/dist/browser/publications/comment/comment-client-manager.js.map +1 -1
- package/dist/browser/publications/community-edit/schema.d.ts +64 -0
- package/dist/browser/pubsub-messages/schema.d.ts +32 -0
- package/dist/browser/rpc/src/index.js +14 -1
- package/dist/browser/rpc/src/index.js.map +1 -1
- package/dist/browser/rpc/src/lib/pkc-js/index.d.ts +559 -0
- package/dist/browser/rpc/src/schema.d.ts +3371 -0
- package/dist/browser/rpc/src/schema.js +6 -2
- package/dist/browser/rpc/src/schema.js.map +1 -1
- package/dist/browser/runtime/browser/community/page-sorts/index.d.ts +1 -0
- package/dist/browser/runtime/browser/community/page-sorts/index.js +6 -0
- package/dist/browser/runtime/browser/community/page-sorts/index.js.map +1 -0
- package/dist/browser/runtime/node/community/db-handler.d.ts +78 -20
- package/dist/browser/runtime/node/community/db-handler.js +1151 -734
- package/dist/browser/runtime/node/community/db-handler.js.map +1 -1
- package/dist/browser/runtime/node/community/db-row-parser.d.ts +13 -0
- package/dist/browser/runtime/node/community/db-row-parser.js +58 -1
- package/dist/browser/runtime/node/community/db-row-parser.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community/cleanup.js +23 -10
- package/dist/browser/runtime/node/community/local-community/cleanup.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community/comment-updates.d.ts +12 -2
- package/dist/browser/runtime/node/community/local-community/comment-updates.js +101 -70
- package/dist/browser/runtime/node/community/local-community/comment-updates.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community/db-state.js +8 -0
- package/dist/browser/runtime/node/community/local-community/db-state.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community/editing.d.ts +9 -2
- package/dist/browser/runtime/node/community/local-community/editing.js +32 -5
- package/dist/browser/runtime/node/community/local-community/editing.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community/ipns-publishing.d.ts +2 -2
- package/dist/browser/runtime/node/community/local-community/ipns-publishing.js +49 -27
- package/dist/browser/runtime/node/community/local-community/ipns-publishing.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community/lifecycle.d.ts +1 -0
- package/dist/browser/runtime/node/community/local-community/lifecycle.js +49 -18
- package/dist/browser/runtime/node/community/local-community/lifecycle.js.map +1 -1
- package/dist/browser/runtime/node/community/local-community.d.ts +5 -1
- package/dist/browser/runtime/node/community/local-community.js +13 -1
- package/dist/browser/runtime/node/community/local-community.js.map +1 -1
- package/dist/browser/runtime/node/community/page-generator.d.ts +44 -413
- package/dist/browser/runtime/node/community/page-generator.js +531 -308
- package/dist/browser/runtime/node/community/page-generator.js.map +1 -1
- package/dist/browser/runtime/node/community/page-sorts/index.d.ts +64 -0
- package/dist/browser/runtime/node/community/page-sorts/index.js +264 -0
- package/dist/browser/runtime/node/community/page-sorts/index.js.map +1 -0
- package/dist/browser/runtime/node/util.js +1 -1
- package/dist/browser/runtime/node/util.js.map +1 -1
- package/dist/browser/schema/schema-util.d.ts +142 -1
- package/dist/browser/schema/schema-util.js +11 -0
- package/dist/browser/schema/schema-util.js.map +1 -1
- package/dist/browser/schema/schema.js +1 -1
- package/dist/browser/schema/schema.js.map +1 -1
- package/dist/browser/schema.d.ts +4469 -0
- package/dist/browser/schema.js +2 -1
- package/dist/browser/schema.js.map +1 -1
- package/dist/browser/signer/signatures.d.ts +1 -1
- package/dist/browser/signer/signatures.js +45 -1
- package/dist/browser/signer/signatures.js.map +1 -1
- package/dist/browser/test/test-util.d.ts +63 -0
- package/dist/browser/test/test-util.js +66 -11
- package/dist/browser/test/test-util.js.map +1 -1
- package/dist/browser/util.d.ts +4 -0
- package/dist/browser/util.js +87 -0
- package/dist/browser/util.js.map +1 -1
- package/dist/browser/version.js +1 -1
- package/dist/bundled/bundle-manifest.json +235 -211
- package/dist/bundled/chunks/{helia-for-pkc-BCVI3rjp.js → helia-for-pkc-BFRxfgOR.js} +58 -34
- package/dist/bundled/chunks/{local-community-t5S6o-0o.js → local-community-B9vbrS8T.js} +1 -1
- package/dist/bundled/chunks/{local-community-CK7TUwwd.js → local-community-C8TU8XWc.js} +6719 -6055
- package/dist/bundled/chunks/{node-BpoCiJjL.js → node-BbSBdhh8.js} +1201 -15
- package/dist/bundled/chunks/{rpc-local-community-CR1TnLfm.js → rpc-local-community-BIyZJsOt.js} +13632 -14131
- package/dist/bundled/chunks/{util-bxE0EcD4.js → util-BJFQrwth.js} +1 -1
- package/dist/bundled/chunks/{schema-DWRg9_bS.js → util-CdbKTOCa.js} +751 -601
- package/dist/bundled/index.js +2 -2
- package/dist/bundled/rpc/src/index.js +22 -9
- package/dist/node/clients/base-client-manager.d.ts +1 -1
- package/dist/node/clients/base-client-manager.js +6 -2
- package/dist/node/clients/base-client-manager.js.map +1 -1
- package/dist/node/clients/rpc-client/rpc-schema-util.d.ts +16 -0
- package/dist/node/clients/rpc-client/schema.d.ts +16 -0
- package/dist/node/community/remote-community.d.ts +1 -0
- package/dist/node/community/remote-community.js +1 -0
- package/dist/node/community/remote-community.js.map +1 -1
- package/dist/node/community/rpc-local-community.js +13 -0
- package/dist/node/community/rpc-local-community.js.map +1 -1
- package/dist/node/community/schema.d.ts +1369 -1
- package/dist/node/community/schema.js +90 -4
- package/dist/node/community/schema.js.map +1 -1
- package/dist/node/community/types.d.ts +13 -2
- package/dist/node/constants.d.ts +1 -0
- package/dist/node/constants.js +6 -0
- package/dist/node/constants.js.map +1 -1
- package/dist/node/errors.d.ts +15 -1
- package/dist/node/errors.js +15 -0
- package/dist/node/errors.js.map +1 -1
- package/dist/node/generated-version.d.ts +1 -1
- package/dist/node/generated-version.js +1 -1
- package/dist/node/helia/helia-for-pkc.js +41 -4
- package/dist/node/helia/helia-for-pkc.js.map +1 -1
- package/dist/node/helia/util.js +4 -1
- package/dist/node/helia/util.js.map +1 -1
- package/dist/node/index-with-compile-cache.d.ts +559 -0
- package/dist/node/index.d.ts +1120 -0
- package/dist/node/index.js +5 -0
- package/dist/node/index.js.map +1 -1
- package/dist/node/logger.js +10 -1
- package/dist/node/logger.js.map +1 -1
- package/dist/node/object-key-util.d.ts +2 -0
- package/dist/node/object-key-util.js +12 -0
- package/dist/node/object-key-util.js.map +1 -0
- package/dist/node/pages/page-sort-options.d.ts +14 -0
- package/dist/node/pages/page-sort-options.js +92 -0
- package/dist/node/pages/page-sort-options.js.map +1 -0
- package/dist/node/pages/page-sort-scoring.d.ts +35 -0
- package/dist/node/pages/page-sort-scoring.js +124 -0
- package/dist/node/pages/page-sort-scoring.js.map +1 -0
- package/dist/node/pages/pages-client-manager.d.ts +1 -0
- package/dist/node/pages/pages-client-manager.js +16 -4
- package/dist/node/pages/pages-client-manager.js.map +1 -1
- package/dist/node/pages/pkc-js-page-sorts/active.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/active.js +15 -0
- package/dist/node/pages/pkc-js-page-sorts/active.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/best.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/best.js +10 -0
- package/dist/node/pages/pkc-js-page-sorts/best.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/controversial.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/controversial.js +10 -0
- package/dist/node/pages/pkc-js-page-sorts/controversial.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/hot.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/hot.js +10 -0
- package/dist/node/pages/pkc-js-page-sorts/hot.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/index.d.ts +2 -0
- package/dist/node/pages/pkc-js-page-sorts/index.js +37 -0
- package/dist/node/pages/pkc-js-page-sorts/index.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/new-flat.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/new-flat.js +12 -0
- package/dist/node/pages/pkc-js-page-sorts/new-flat.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/new.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/new.js +10 -0
- package/dist/node/pages/pkc-js-page-sorts/new.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/old-flat.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/old-flat.js +12 -0
- package/dist/node/pages/pkc-js-page-sorts/old-flat.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/old.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/old.js +10 -0
- package/dist/node/pages/pkc-js-page-sorts/old.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top-all.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top-all.js +10 -0
- package/dist/node/pages/pkc-js-page-sorts/top-all.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top-day.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top-day.js +11 -0
- package/dist/node/pages/pkc-js-page-sorts/top-day.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top-hour.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top-hour.js +11 -0
- package/dist/node/pages/pkc-js-page-sorts/top-hour.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top-month.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top-month.js +11 -0
- package/dist/node/pages/pkc-js-page-sorts/top-month.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top-week.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top-week.js +11 -0
- package/dist/node/pages/pkc-js-page-sorts/top-week.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top-year.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top-year.js +11 -0
- package/dist/node/pages/pkc-js-page-sorts/top-year.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/top.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/top.js +12 -0
- package/dist/node/pages/pkc-js-page-sorts/top.js.map +1 -0
- package/dist/node/pages/pkc-js-page-sorts/util.d.ts +3 -0
- package/dist/node/pages/pkc-js-page-sorts/util.js +7 -0
- package/dist/node/pages/pkc-js-page-sorts/util.js.map +1 -0
- package/dist/node/pages/schema.d.ts +1 -0
- package/dist/node/pages/schema.js +10 -3
- package/dist/node/pages/schema.js.map +1 -1
- package/dist/node/pages/types.d.ts +8 -0
- package/dist/node/pages/util.d.ts +2 -1
- package/dist/node/pages/util.js +11 -10
- package/dist/node/pages/util.js.map +1 -1
- package/dist/node/pkc/pkc.d.ts +2 -1
- package/dist/node/pkc/pkc.js +2 -1
- package/dist/node/pkc/pkc.js.map +1 -1
- package/dist/node/pkc-error.d.ts +1 -1
- package/dist/node/publications/comment/comment-client-manager.d.ts +2 -2
- package/dist/node/publications/comment/comment-client-manager.js +3 -1
- package/dist/node/publications/comment/comment-client-manager.js.map +1 -1
- package/dist/node/publications/community-edit/schema.d.ts +64 -0
- package/dist/node/pubsub-messages/schema.d.ts +32 -0
- package/dist/node/rpc/src/index.js +14 -1
- package/dist/node/rpc/src/index.js.map +1 -1
- package/dist/node/rpc/src/lib/pkc-js/index.d.ts +559 -0
- package/dist/node/rpc/src/schema.d.ts +3371 -0
- package/dist/node/rpc/src/schema.js +6 -2
- package/dist/node/rpc/src/schema.js.map +1 -1
- package/dist/node/runtime/browser/community/page-sorts/index.d.ts +1 -0
- package/dist/node/runtime/browser/community/page-sorts/index.js +6 -0
- package/dist/node/runtime/browser/community/page-sorts/index.js.map +1 -0
- package/dist/node/runtime/node/community/db-handler.d.ts +78 -20
- package/dist/node/runtime/node/community/db-handler.js +1151 -734
- package/dist/node/runtime/node/community/db-handler.js.map +1 -1
- package/dist/node/runtime/node/community/db-row-parser.d.ts +13 -0
- package/dist/node/runtime/node/community/db-row-parser.js +58 -1
- package/dist/node/runtime/node/community/db-row-parser.js.map +1 -1
- package/dist/node/runtime/node/community/local-community/cleanup.js +23 -10
- package/dist/node/runtime/node/community/local-community/cleanup.js.map +1 -1
- package/dist/node/runtime/node/community/local-community/comment-updates.d.ts +12 -2
- package/dist/node/runtime/node/community/local-community/comment-updates.js +101 -70
- package/dist/node/runtime/node/community/local-community/comment-updates.js.map +1 -1
- package/dist/node/runtime/node/community/local-community/db-state.js +8 -0
- package/dist/node/runtime/node/community/local-community/db-state.js.map +1 -1
- package/dist/node/runtime/node/community/local-community/editing.d.ts +9 -2
- package/dist/node/runtime/node/community/local-community/editing.js +32 -5
- package/dist/node/runtime/node/community/local-community/editing.js.map +1 -1
- package/dist/node/runtime/node/community/local-community/ipns-publishing.d.ts +2 -2
- package/dist/node/runtime/node/community/local-community/ipns-publishing.js +49 -27
- package/dist/node/runtime/node/community/local-community/ipns-publishing.js.map +1 -1
- package/dist/node/runtime/node/community/local-community/lifecycle.d.ts +1 -0
- package/dist/node/runtime/node/community/local-community/lifecycle.js +49 -18
- package/dist/node/runtime/node/community/local-community/lifecycle.js.map +1 -1
- package/dist/node/runtime/node/community/local-community.d.ts +5 -1
- package/dist/node/runtime/node/community/local-community.js +13 -1
- package/dist/node/runtime/node/community/local-community.js.map +1 -1
- package/dist/node/runtime/node/community/page-generator.d.ts +44 -413
- package/dist/node/runtime/node/community/page-generator.js +531 -308
- package/dist/node/runtime/node/community/page-generator.js.map +1 -1
- package/dist/node/runtime/node/community/page-sorts/index.d.ts +64 -0
- package/dist/node/runtime/node/community/page-sorts/index.js +264 -0
- package/dist/node/runtime/node/community/page-sorts/index.js.map +1 -0
- package/dist/node/runtime/node/util.js +1 -1
- package/dist/node/runtime/node/util.js.map +1 -1
- package/dist/node/schema/schema-util.d.ts +142 -1
- package/dist/node/schema/schema-util.js +11 -0
- package/dist/node/schema/schema-util.js.map +1 -1
- package/dist/node/schema/schema.js +1 -1
- package/dist/node/schema/schema.js.map +1 -1
- package/dist/node/schema.d.ts +4469 -0
- package/dist/node/schema.js +2 -1
- package/dist/node/schema.js.map +1 -1
- package/dist/node/signer/signatures.d.ts +1 -1
- package/dist/node/signer/signatures.js +45 -1
- package/dist/node/signer/signatures.js.map +1 -1
- package/dist/node/test/test-util.d.ts +63 -0
- package/dist/node/test/test-util.js +66 -11
- package/dist/node/test/test-util.js.map +1 -1
- package/dist/node/util.d.ts +4 -0
- package/dist/node/util.js +87 -0
- package/dist/node/util.js.map +1 -1
- package/dist/node/version.js +1 -1
- package/package.json +2 -2
- package/dist/bundled/chunks/{src-Dz9wtvrK.js → src-B07ldGHS.js} +1 -1
|
@@ -1,18 +1,193 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
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
|
-
|
|
69
|
-
|
|
243
|
+
calculatedSizeOfStringifedPageIpfs: calculatedSize,
|
|
244
|
+
expectedSize: maximumPageSize,
|
|
245
|
+
commentCount: entries.length,
|
|
70
246
|
sortName,
|
|
71
247
|
pageNum
|
|
72
248
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
160
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
//
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
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 =
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
return {
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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;
|