@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 +25 -1
- package/README.md +8 -2
- package/dist/index.cjs +9 -4
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +9 -4
- package/package.json +1 -1
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.
|
|
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
|
|
144
|
-
|
|
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) =>
|
|
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) =>
|
|
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.
|
|
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",
|