@peektravel/app-utilities 0.9.1 → 0.9.2

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/dist/index.cjs CHANGED
@@ -1112,6 +1112,9 @@ var QUESTION_TYPE_SELECT_ONE = "SELECT_ONE";
1112
1112
  var QUESTION_TYPE_LOCATION = "LOCATION";
1113
1113
  var QUESTION_ID_REGEX = /^cq_[a-z0-9]+$/;
1114
1114
  var OPTION_ID_REGEX = /^cqao_[a-z0-9]+$/;
1115
+ function isCustomQuestionId(idOrText) {
1116
+ return QUESTION_ID_REGEX.test(idOrText);
1117
+ }
1115
1118
  var CHECKBOX_TRUE = /* @__PURE__ */ new Set(["yes", "true"]);
1116
1119
  var CHECKBOX_VALID = /* @__PURE__ */ new Set(["yes", "no", "true", "false"]);
1117
1120
  var CHECKBOX_TEXT_TRUE = "Yes";
@@ -1129,10 +1132,13 @@ var errMissingRequired = (text, id) => `Custom question "${text}" (${id}) is req
1129
1132
  function normalize(value) {
1130
1133
  return value.toLowerCase().replace(/[^a-z0-9]/g, "");
1131
1134
  }
1132
- function resolveQuestion(idOrText, questions) {
1135
+ function resolveQuestion(idOrText, questions, acceptAllIds) {
1133
1136
  if (QUESTION_ID_REGEX.test(idOrText)) {
1134
1137
  const byId = questions.find((question) => question.id === idOrText);
1135
- if (!byId) throw new Error(errUnknownQuestionId(idOrText));
1138
+ if (!byId) {
1139
+ if (acceptAllIds) return null;
1140
+ throw new Error(errUnknownQuestionId(idOrText));
1141
+ }
1136
1142
  return byId;
1137
1143
  }
1138
1144
  const target = normalize(idOrText);
@@ -1157,8 +1163,11 @@ function resolveOption(value, question) {
1157
1163
  if (matches.length > 1) throw new Error(errAmbiguousOption(value, question.id));
1158
1164
  return matches[0];
1159
1165
  }
1160
- function resolveOne(answer, questions) {
1161
- const question = resolveQuestion(answer.questionIdOrText, questions);
1166
+ function resolveOne(answer, questions, acceptAllIds) {
1167
+ const question = resolveQuestion(answer.questionIdOrText, questions, acceptAllIds);
1168
+ if (!question) {
1169
+ return { questionId: answer.questionIdOrText, questionAnswerText: answer.value };
1170
+ }
1162
1171
  if (question.perGuest) {
1163
1172
  throw new Error(errPerGuestUnsupported(question.id));
1164
1173
  }
@@ -1191,7 +1200,8 @@ function resolveOne(answer, questions) {
1191
1200
  }
1192
1201
  }
1193
1202
  function resolveCustomQuestionAnswers(answers, questions, options) {
1194
- const resolved = answers.map((answer) => resolveOne(answer, questions));
1203
+ const acceptAllIds = options?.acceptAllIds ?? false;
1204
+ const resolved = answers.map((answer) => resolveOne(answer, questions, acceptAllIds));
1195
1205
  if (options?.requireRequired) {
1196
1206
  assertRequiredAnswered(resolved, questions);
1197
1207
  }
@@ -1799,19 +1809,22 @@ ${note}`;
1799
1809
  * Resolves the input's custom-question answers into the quote's
1800
1810
  * `questionAnswers` payload (each tagged with a fresh `refid`), or `null` when
1801
1811
  * none were supplied. Fetches the activity's custom questions to validate
1802
- * against; throws on the first unmatched/ambiguous/invalid answer.
1812
+ * against; throws on the first unmatched/ambiguous/invalid answer. When
1813
+ * `acceptAllCustomQuestionIds` is set the fetch is skipped if every answer is
1814
+ * given by id (a by-name answer still needs the list to resolve).
1803
1815
  */
1804
1816
  async buildQuestionAnswers(input) {
1805
1817
  const answers = input.customQuestionAnswers ?? [];
1806
1818
  const requireRequired = input.requireRequiredQuestions ?? false;
1819
+ const acceptAllIds = input.acceptAllCustomQuestionIds ?? false;
1807
1820
  if (answers.length === 0 && !requireRequired) {
1808
1821
  return null;
1809
1822
  }
1810
- const questions = await this.deps.productService.getCustomQuestions(
1811
- input.activityId
1812
- );
1823
+ const needsQuestions = !acceptAllIds || requireRequired || answers.some((answer) => !isCustomQuestionId(answer.questionIdOrText));
1824
+ const questions = needsQuestions ? await this.deps.productService.getCustomQuestions(input.activityId) : [];
1813
1825
  const resolved = resolveCustomQuestionAnswers(answers, questions, {
1814
- requireRequired
1826
+ requireRequired,
1827
+ acceptAllIds
1815
1828
  });
1816
1829
  if (resolved.length === 0) {
1817
1830
  return null;
@@ -2184,7 +2197,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
2184
2197
  // src/internal/sdk-headers.ts
2185
2198
  var SDK_HEADER_NAME = "x-peek-sdk";
2186
2199
  var SDK_PLATFORM = "js";
2187
- var SDK_VERSION = "0.9.1";
2200
+ var SDK_VERSION = "0.9.2";
2188
2201
  var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
2189
2202
 
2190
2203
  // src/internal/peek/graphql-client.ts
@@ -2609,6 +2622,23 @@ function fromReviewNode(node) {
2609
2622
  comment: node.comment ?? null
2610
2623
  };
2611
2624
  }
2625
+ function fromActivityReviewSummaryNode(node) {
2626
+ const meta = node.reviewMeta;
2627
+ return {
2628
+ productId: node.id,
2629
+ productName: node.name,
2630
+ avgRating: meta?.avgRating ?? null,
2631
+ countTotal: meta?.count ?? 0,
2632
+ countOneStar: meta?.oneStar ?? 0,
2633
+ countTwoStar: meta?.twoStar ?? 0,
2634
+ countThreeStar: meta?.threeStar ?? 0,
2635
+ countFourStar: meta?.fourStar ?? 0,
2636
+ countFiveStar: meta?.fiveStar ?? 0
2637
+ };
2638
+ }
2639
+ function fromActivityReviewSummaryNodes(nodes) {
2640
+ return nodes.map(fromActivityReviewSummaryNode);
2641
+ }
2612
2642
 
2613
2643
  // src/internal/peek/reviews/review-cursor.ts
2614
2644
  function encodeCursor(offset, pageSize) {
@@ -2644,6 +2674,23 @@ function buildReviewsQuery(fullCustomerAccess) {
2644
2674
  }
2645
2675
  `;
2646
2676
  }
2677
+ var REVIEW_SUMMARIES_QUERY = `
2678
+ query ReviewSummaries {
2679
+ activities {
2680
+ id
2681
+ name
2682
+ reviewMeta {
2683
+ avgRating
2684
+ count
2685
+ fiveStar
2686
+ fourStar
2687
+ threeStar
2688
+ twoStar
2689
+ oneStar
2690
+ }
2691
+ }
2692
+ }
2693
+ `;
2647
2694
  function buildReviewsVariables(params) {
2648
2695
  return {
2649
2696
  first: params.first,
@@ -2703,6 +2750,29 @@ var ReviewService = class {
2703
2750
  const edges = body.data?.reviews?.edges ?? [];
2704
2751
  return edges.map((edge) => fromReviewNode(edge.node));
2705
2752
  }
2753
+ /**
2754
+ * Returns an aggregate review summary for **every** activity — its average
2755
+ * rating and the count of reviews at each star level. This reads the same
2756
+ * activity list as `getAllProducts`; it carries no per-review detail or PII, so
2757
+ * it is unaffected by `fullCustomerAccess`. An activity with no reviews comes
2758
+ * back with a `null` `avgRating` and zero counts.
2759
+ *
2760
+ * @example
2761
+ * ```ts
2762
+ * const summaries = await peek.getReviewService().getAllReviewSummaries();
2763
+ * for (const s of summaries) {
2764
+ * console.log(s.productName, s.avgRating, s.countTotal);
2765
+ * }
2766
+ * ```
2767
+ */
2768
+ async getAllReviewSummaries() {
2769
+ const body = await this.client.request(
2770
+ SALES_ENDPOINT,
2771
+ REVIEW_SUMMARIES_QUERY,
2772
+ {}
2773
+ );
2774
+ return fromActivityReviewSummaryNodes(body.data?.activities ?? []);
2775
+ }
2706
2776
  validate(productId, reviewCount, reviewOffset) {
2707
2777
  if (!productId) {
2708
2778
  throw new Error(ERROR_PRODUCT_ID_REQUIRED);