@blamejs/blamejs-shop 0.3.44 → 0.3.46

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/lib/product-qa.js CHANGED
@@ -652,6 +652,45 @@ function create(opts) {
652
652
  return rows;
653
653
  }
654
654
 
655
+ // Batched answersForQuestion — every approved answer under EACH of
656
+ // `questionIds`, grouped into a map keyed by question_id. One `IN`
657
+ // query replaces the per-question `answersForQuestion` loop the PDP
658
+ // ran (one D1-bridge hop per question). The approved-only filter and
659
+ // the (pinned DESC, vote_count DESC, occurred_at ASC, id ASC) order
660
+ // are byte-for-byte the single-question method's, so the stitched
661
+ // thread renders identically. `opts.limit` caps the answers PER
662
+ // question (validated like the single method) — applied
663
+ // application-side after the grouped read so a high-fan-out question
664
+ // can't starve another. Each id is validated at entry (a malformed
665
+ // id throws TypeError, which the route catches). An empty input or a
666
+ // question with no approved answers maps to an empty array.
667
+ async function answersForQuestions(questionIds, listOpts) {
668
+ if (!Array.isArray(questionIds) || questionIds.length === 0) return {};
669
+ listOpts = listOpts || {};
670
+ var limit = _limit(listOpts.limit);
671
+ var ids = [];
672
+ var byId = {};
673
+ for (var qi = 0; qi < questionIds.length; qi += 1) {
674
+ var id = _uuid(questionIds[qi], "question_id");
675
+ if (byId[id]) continue;
676
+ byId[id] = [];
677
+ ids.push(id);
678
+ }
679
+ var placeholders = ids.map(function (_v, n) { return "?" + (n + 1); }).join(", ");
680
+ var r = await query(
681
+ "SELECT * FROM product_qa_answers " +
682
+ "WHERE question_id IN (" + placeholders + ") AND status = 'approved' " +
683
+ "ORDER BY pinned DESC, vote_count DESC, occurred_at ASC, id ASC",
684
+ ids,
685
+ );
686
+ for (var i = 0; i < r.rows.length; i += 1) {
687
+ var ans = _decodeAnswer(r.rows[i]);
688
+ var bucket = byId[ans.question_id];
689
+ if (bucket && bucket.length < limit) bucket.push(ans);
690
+ }
691
+ return byId;
692
+ }
693
+
655
694
  // Every answer under a question regardless of status — the operator
656
695
  // moderation detail needs the pending / rejected answers too, which
657
696
  // answersForQuestion (approved-only, storefront-facing) hides. Pinned
@@ -808,6 +847,7 @@ function create(opts) {
808
847
  getQuestion: getQuestion,
809
848
  topAnswerForQuestion: topAnswerForQuestion,
810
849
  answersForQuestion: answersForQuestion,
850
+ answersForQuestions: answersForQuestions,
811
851
  listAnswersForQuestion: listAnswersForQuestion,
812
852
  listQuestionsByStatus: listQuestionsByStatus,
813
853
  listAnswersByStatus: listAnswersByStatus,
@@ -559,10 +559,9 @@ function create(opts) {
559
559
  }
560
560
 
561
561
  // Owed: re-derive from per-rate taxable totals using rate_bps.
562
- // Integer math: taxable * bps / 10000, banker's-rounded so
563
- // half-cent ties don't drift the sum. (Same rounding rule as
564
- // b.tax banker's rounding is the conservative choice when the
565
- // primitive may aggregate millions of orders.)
562
+ // owed = taxable × bps / 10000 per rate, half-even via b.money
563
+ // (BigInt taxable_minor sums every order in the window and can
564
+ // exceed 2^53, so the multiply must not pass through a float).
566
565
  var owed = 0;
567
566
  var rateKeys = Object.keys(breakdown);
568
567
  for (var rk = 0; rk < rateKeys.length; rk += 1) {
@@ -571,14 +570,11 @@ function create(opts) {
571
570
  var bps = parseInt(key, 10);
572
571
  if (!Number.isFinite(bps)) continue;
573
572
  var bucket = breakdown[key];
574
- var rawOwed = bucket.taxable_minor * bps / 10000;
575
- // Banker's round
576
- var floored = Math.floor(rawOwed);
577
- var frac = rawOwed - floored;
578
- var rounded;
579
- if (frac > 0.5) rounded = floored + 1;
580
- else if (frac < 0.5) rounded = floored;
581
- else rounded = (floored % 2 === 0) ? floored : floored + 1;
573
+ var rounded = Number(
574
+ b.money.fromMinorUnits(BigInt(bucket.taxable_minor), "USD")
575
+ .multiply([BigInt(bps), 10000n], { rounding: "half-even" })
576
+ .toMinorUnits()
577
+ );
582
578
  owed += rounded;
583
579
  }
584
580
  // When no rate rows resolved (taxRatesApi absent / no rates