@domandigital/gbp 0.3.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -12,6 +12,29 @@ time.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.3.2] - 2026-08-16
16
+
17
+ ### Added
18
+
19
+ - `GetBusinessReviewsOptions.order`, `"shuffle" | "api"`. Default is
20
+ `"shuffle"`, unchanged from prior behaviour. `"api"` preserves the Business
21
+ Profile API's own `updateTime desc` ordering instead.
22
+
23
+ ### Fixed
24
+
25
+ - **`limit` could silently under-return.** Pagination stopped once `raw.length`
26
+ reached `limit`, but the comment/star-rating filter that determines which
27
+ reviews are actually usable ran after the loop. A page that was mostly
28
+ star-only ratings with no comment -- which the API allows -- could satisfy
29
+ the raw count while producing far fewer, or zero, usable reviews, with no
30
+ error and no further pages fetched. The stopping condition now counts
31
+ usable reviews, so `limit` means "give me N reviews you can show."
32
+
33
+ ### Changed
34
+
35
+ - `limit`'s doc comment now says what it actually counts (usable reviews, not
36
+ raw API results), matching the fix above.
37
+
15
38
  ## [0.3.1] - 2026-08-16
16
39
 
17
40
  ### Added
@@ -97,7 +120,8 @@ time.
97
120
  `filterMinStars` and a Next.js `next` cache hint. Returns empty results rather
98
121
  than throwing when unconfigured, so UI can render unconditionally.
99
122
 
100
- [Unreleased]: https://github.com/Doman-Digital/dd-gbp/compare/v0.3.1...HEAD
123
+ [Unreleased]: https://github.com/Doman-Digital/dd-gbp/compare/v0.3.2...HEAD
124
+ [0.3.2]: https://github.com/Doman-Digital/dd-gbp/compare/v0.3.1...v0.3.2
101
125
  [0.3.1]: https://github.com/Doman-Digital/dd-gbp/compare/v0.3.0...v0.3.1
102
126
  [0.3.0]: https://github.com/Doman-Digital/dd-gbp/compare/v0.2.0...v0.3.0
103
127
  [0.2.0]: https://github.com/Doman-Digital/dd-gbp/compare/v0.1.0...v0.2.0
package/README.md CHANGED
@@ -140,8 +140,9 @@ interface BusinessReview {
140
140
 
141
141
  Options:
142
142
 
143
- - `limit?: number` -- stop paginating once this many reviews are collected.
144
- Omit to fetch all of them.
143
+ - `limit?: number` -- stop paginating once this many *usable* reviews are
144
+ collected (a raw result needs both a comment and a star rating to count, and
145
+ `filterMinStars` narrows it further). Omit to fetch all of them.
145
146
  - `filterMinStars?: number` -- drop reviews below this rating. Use for a
146
147
  public testimonial feed; omit for an owner-facing surface where the point
147
148
  is to see everything. `totalReviewCount` always reflects the location's
@@ -149,6 +150,11 @@ Options:
149
150
  - `next?: { revalidate?: number; tags?: string[] }` -- passed straight through
150
151
  to `fetch`'s Next.js cache-hint augmentation. A no-op outside Next.js.
151
152
  Default: revalidate hourly.
153
+ - `order?: "shuffle" | "api"` -- `"shuffle"` (default) randomizes the returned
154
+ order, applied after `limit`. `"api"` preserves the Business Profile API's
155
+ own ordering (`updateTime desc`, most recent first). Pick `"api"` for
156
+ anything that should read as chronological, e.g. an activity feed; the
157
+ default suits a testimonial grid where a fixed order would look stale.
152
158
 
153
159
  Never throws on missing configuration -- `isBusinessProfileConfigured()` gates
154
160
  internally and returns an empty result, so UI can render unconditionally.
package/dist/index.cjs CHANGED
@@ -79,6 +79,11 @@ function shuffleArray(arr) {
79
79
  }
80
80
  return copy;
81
81
  }
82
+ function isUsableReview(r, filterMinStars) {
83
+ if (!r.comment || !r.starRating) return false;
84
+ if (filterMinStars !== void 0 && STAR_VALUES[r.starRating] < filterMinStars) return false;
85
+ return true;
86
+ }
82
87
  var EMPTY = {
83
88
  averageRating: null,
84
89
  totalReviewCount: 0,
@@ -102,7 +107,7 @@ function toBusinessReview(r) {
102
107
  return review;
103
108
  }
104
109
  async function getBusinessReviews(options = {}) {
105
- const { limit, filterMinStars, next } = options;
110
+ const { limit, filterMinStars, next, order = "shuffle" } = options;
106
111
  if (!isBusinessProfileConfigured()) return EMPTY;
107
112
  const token = await getGoogleOAuthAccessToken();
108
113
  if (!token) return EMPTY;
@@ -127,12 +132,12 @@ async function getBusinessReviews(options = {}) {
127
132
  averageRating = data.averageRating ?? averageRating;
128
133
  totalReviewCount = data.totalReviewCount ?? totalReviewCount;
129
134
  pageToken = data.nextPageToken;
130
- } while (pageToken && (limit === void 0 || raw.length < limit));
131
- const reviews = raw.filter((r) => r.comment && r.starRating).filter((r) => filterMinStars === void 0 || STAR_VALUES[r.starRating] >= filterMinStars).map(toBusinessReview).slice(0, limit);
135
+ } while (pageToken && (limit === void 0 || raw.filter((r) => isUsableReview(r, filterMinStars)).length < limit));
136
+ const reviews = raw.filter((r) => isUsableReview(r, filterMinStars)).map(toBusinessReview).slice(0, limit);
132
137
  return {
133
138
  averageRating: averageRating ?? null,
134
139
  totalReviewCount: totalReviewCount ?? reviews.length,
135
- reviews: shuffleArray(reviews)
140
+ reviews: order === "api" ? reviews : shuffleArray(reviews)
136
141
  };
137
142
  }
138
143
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -64,8 +64,11 @@ interface BusinessReviewsResult {
64
64
  }
65
65
  /** True when account + location + OAuth credentials are all configured. */
66
66
  declare function isBusinessProfileConfigured(): boolean;
67
+ type ReviewOrder = "shuffle" | "api";
67
68
  interface GetBusinessReviewsOptions {
68
- /** Max reviews to return (default: all of them, paginated). */
69
+ /** Max reviews to return (default: all of them, paginated). Counts usable
70
+ * reviews only -- see `filterMinStars` -- so this is "give me N reviews you
71
+ * can show", not "give me N raw API results". */
69
72
  limit?: number;
70
73
  /**
71
74
  * When set, only reviews at or above this star rating are returned -- for
@@ -80,6 +83,12 @@ interface GetBusinessReviewsOptions {
80
83
  revalidate?: number;
81
84
  tags?: string[];
82
85
  };
86
+ /**
87
+ * `"shuffle"` (default): randomizes the returned order, applied after
88
+ * `limit`. `"api"`: preserves the Business Profile API's own ordering
89
+ * (`updateTime desc`, most recent first).
90
+ */
91
+ order?: ReviewOrder;
83
92
  }
84
93
  /**
85
94
  * Fetch reviews for the configured location, paginating through all pages
package/dist/index.d.ts CHANGED
@@ -64,8 +64,11 @@ interface BusinessReviewsResult {
64
64
  }
65
65
  /** True when account + location + OAuth credentials are all configured. */
66
66
  declare function isBusinessProfileConfigured(): boolean;
67
+ type ReviewOrder = "shuffle" | "api";
67
68
  interface GetBusinessReviewsOptions {
68
- /** Max reviews to return (default: all of them, paginated). */
69
+ /** Max reviews to return (default: all of them, paginated). Counts usable
70
+ * reviews only -- see `filterMinStars` -- so this is "give me N reviews you
71
+ * can show", not "give me N raw API results". */
69
72
  limit?: number;
70
73
  /**
71
74
  * When set, only reviews at or above this star rating are returned -- for
@@ -80,6 +83,12 @@ interface GetBusinessReviewsOptions {
80
83
  revalidate?: number;
81
84
  tags?: string[];
82
85
  };
86
+ /**
87
+ * `"shuffle"` (default): randomizes the returned order, applied after
88
+ * `limit`. `"api"`: preserves the Business Profile API's own ordering
89
+ * (`updateTime desc`, most recent first).
90
+ */
91
+ order?: ReviewOrder;
83
92
  }
84
93
  /**
85
94
  * Fetch reviews for the configured location, paginating through all pages
package/dist/index.js CHANGED
@@ -50,6 +50,11 @@ function shuffleArray(arr) {
50
50
  }
51
51
  return copy;
52
52
  }
53
+ function isUsableReview(r, filterMinStars) {
54
+ if (!r.comment || !r.starRating) return false;
55
+ if (filterMinStars !== void 0 && STAR_VALUES[r.starRating] < filterMinStars) return false;
56
+ return true;
57
+ }
53
58
  var EMPTY = {
54
59
  averageRating: null,
55
60
  totalReviewCount: 0,
@@ -73,7 +78,7 @@ function toBusinessReview(r) {
73
78
  return review;
74
79
  }
75
80
  async function getBusinessReviews(options = {}) {
76
- const { limit, filterMinStars, next } = options;
81
+ const { limit, filterMinStars, next, order = "shuffle" } = options;
77
82
  if (!isBusinessProfileConfigured()) return EMPTY;
78
83
  const token = await getGoogleOAuthAccessToken();
79
84
  if (!token) return EMPTY;
@@ -98,12 +103,12 @@ async function getBusinessReviews(options = {}) {
98
103
  averageRating = data.averageRating ?? averageRating;
99
104
  totalReviewCount = data.totalReviewCount ?? totalReviewCount;
100
105
  pageToken = data.nextPageToken;
101
- } while (pageToken && (limit === void 0 || raw.length < limit));
102
- const reviews = raw.filter((r) => r.comment && r.starRating).filter((r) => filterMinStars === void 0 || STAR_VALUES[r.starRating] >= filterMinStars).map(toBusinessReview).slice(0, limit);
106
+ } while (pageToken && (limit === void 0 || raw.filter((r) => isUsableReview(r, filterMinStars)).length < limit));
107
+ const reviews = raw.filter((r) => isUsableReview(r, filterMinStars)).map(toBusinessReview).slice(0, limit);
103
108
  return {
104
109
  averageRating: averageRating ?? null,
105
110
  totalReviewCount: totalReviewCount ?? reviews.length,
106
- reviews: shuffleArray(reviews)
111
+ reviews: order === "api" ? reviews : shuffleArray(reviews)
107
112
  };
108
113
  }
109
114
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domandigital/gbp",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "description": "Zero-dependency Google Business Profile client: OAuth refresh-token auth and a reviews (with owner replies) fetch, shared across Doman Digital / IDS client sites.",
6
6
  "license": "Apache-2.0",