@reactionary/bazaarvoice 0.9.16

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 ADDED
@@ -0,0 +1,41 @@
1
+ # @reactionary/bazaarvoice
2
+
3
+ Bazaarvoice Conversations REST API v5 adapter for `@reactionary/core`.
4
+
5
+ Implements `ProductReviewsCapability` — ratings summaries, paginated review lists, and review submission — by calling the Bazaarvoice Conversations REST API directly using passKey authentication.
6
+
7
+ ## Usage
8
+
9
+ ```typescript
10
+ import { withBazaarvoiceCapabilities } from '@reactionary/bazaarvoice';
11
+
12
+ const getCapabilities = withBazaarvoiceCapabilities(
13
+ {
14
+ passKey: process.env.BV_PASS_KEY!,
15
+ // apiUrl: 'https://api.bazaarvoice.com', // production (default)
16
+ // apiUrl: 'https://stg.api.bazaarvoice.com', // staging
17
+ },
18
+ {
19
+ productReviews: { enabled: true },
20
+ },
21
+ );
22
+
23
+ // In a request handler:
24
+ const capabilities = getCapabilities(cache, requestContext);
25
+ const summary = await capabilities.productReviews.getRatingSummary({ product: { key: 'MY-PRODUCT-001' } });
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ | Field | Type | Default | Description |
31
+ | ------------ | -------- | ----------------------------- | --------------------------------------------------- |
32
+ | `passKey` | `string` | required | Bazaarvoice API passkey |
33
+ | `apiUrl` | `string` | `https://api.bazaarvoice.com` | Base API URL; use staging URL for test environments |
34
+ | `apiVersion` | `string` | `5.4` | Conversations API version |
35
+ | `locale` | `string` | `en_US` | BCP47-style locale passed to BV API |
36
+
37
+ ## Notes
38
+
39
+ - **Passkey scope**: Bazaarvoice may issue separate read and write passkeys. Ensure the passkey configured is write-enabled if `submitReview` is used.
40
+ - **Product ID alignment**: The `product.key` in queries must match the Bazaarvoice product ID (typically the same as the HCL `partNumber`).
41
+ - **Review moderation**: Submitted reviews enter the Bazaarvoice moderation queue and will not be immediately visible.
@@ -0,0 +1 @@
1
+ export * from "./product-reviews.capability.js";
@@ -0,0 +1,152 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result)
9
+ __defProp(target, key, result);
10
+ return result;
11
+ };
12
+ import {
13
+ ProductReviewPaginatedResultSchema,
14
+ ProductRatingSummarySchema,
15
+ ProductReviewSchema,
16
+ ProductReviewsCapability,
17
+ Reactionary,
18
+ error,
19
+ success
20
+ } from "@reactionary/core";
21
+ import {
22
+ BvReviewsResponseSchema,
23
+ BvStatisticsResponseSchema,
24
+ BvSubmitReviewResponseSchema
25
+ } from "../schema/bazaarvoice.schema.js";
26
+ class BazaarvoiceProductReviewsCapability extends ProductReviewsCapability {
27
+ constructor(cache, context, config, client, factory) {
28
+ super(cache, context);
29
+ this.config = config;
30
+ this.client = client;
31
+ this.factory = factory;
32
+ }
33
+ async getRatingSummary(query) {
34
+ const response = await this.client.callGet(
35
+ this.getStatisticsUrl(),
36
+ this.getStatisticsParams(query.product.key),
37
+ BvStatisticsResponseSchema
38
+ );
39
+ if (response.HasErrors || response.Results.length === 0) {
40
+ return success(
41
+ this.createEmptyProductRatingSummary({
42
+ product: query.product
43
+ })
44
+ );
45
+ }
46
+ return success(this.factory.parseRatingSummary(this.context, response));
47
+ }
48
+ async findReviews(query) {
49
+ const pageSize = query.paginationOptions?.pageSize ?? 10;
50
+ const pageNumber = query.paginationOptions?.pageNumber ?? 1;
51
+ const offset = (pageNumber - 1) * pageSize;
52
+ const response = await this.client.callGet(
53
+ this.getReviewsUrl(),
54
+ this.getReviewsParams(query.product.key, pageSize, offset),
55
+ BvReviewsResponseSchema
56
+ );
57
+ return success(
58
+ this.factory.parseReviewPaginatedResult(this.context, {
59
+ ...response,
60
+ Limit: pageSize,
61
+ Offset: offset
62
+ })
63
+ );
64
+ }
65
+ async submitReview(mutation) {
66
+ if (this.context.session.identityContext.identity.type !== "Registered") {
67
+ return error({
68
+ type: "InvalidInput",
69
+ error: "Only registered users can submit reviews."
70
+ });
71
+ }
72
+ const response = await this.client.callPost(
73
+ this.getSubmitReviewUrl(),
74
+ this.getSubmitReviewBody(mutation),
75
+ BvSubmitReviewResponseSchema
76
+ );
77
+ if (response.HasErrors || !response.Review) {
78
+ const fieldErrors = response.FormErrors?.FieldErrors ? Object.values(response.FormErrors.FieldErrors).map((e) => e.Message).join("; ") : response.Errors?.[0]?.Message ?? "Review submission failed.";
79
+ return error({
80
+ type: "InvalidInput",
81
+ error: fieldErrors
82
+ });
83
+ }
84
+ return success(this.factory.parseReview(this.context, response.Review));
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Extension points — override in subclasses to customise API calls
88
+ // ---------------------------------------------------------------------------
89
+ getStatisticsUrl() {
90
+ return `${this.client.baseUrl}/data/statistics.json`;
91
+ }
92
+ getStatisticsParams(productId) {
93
+ const params = new URLSearchParams();
94
+ params.set("Filter", `ProductId:eq:${productId}`);
95
+ params.set("stats", "Reviews");
96
+ return params;
97
+ }
98
+ getReviewsUrl() {
99
+ return `${this.client.baseUrl}/data/reviews.json`;
100
+ }
101
+ getReviewsParams(productId, limit, offset) {
102
+ const params = new URLSearchParams();
103
+ params.set("Filter", `ProductId:eq:${productId}`);
104
+ params.set("Limit", String(limit));
105
+ params.set("Offset", String(offset));
106
+ params.set("Sort", "SubmissionTime:desc");
107
+ return params;
108
+ }
109
+ getSubmitReviewUrl() {
110
+ return `${this.client.baseUrl}/data/submitreview.json`;
111
+ }
112
+ getSubmitReviewBody(mutation) {
113
+ const body = new URLSearchParams();
114
+ body.set("Action", "Submit");
115
+ body.set("ProductId", mutation.product.key);
116
+ body.set("Rating", String(mutation.rating));
117
+ body.set("Title", mutation.title);
118
+ body.set("ReviewText", mutation.content);
119
+ body.set("UserNickname", mutation.authorName);
120
+ return body;
121
+ }
122
+ }
123
+ __decorateClass([
124
+ Reactionary({
125
+ outputSchema: ProductRatingSummarySchema,
126
+ cache: true,
127
+ cacheTimeToLiveInSeconds: 300,
128
+ currencyDependentCaching: false,
129
+ localeDependentCaching: false
130
+ })
131
+ ], BazaarvoiceProductReviewsCapability.prototype, "getRatingSummary", 1);
132
+ __decorateClass([
133
+ Reactionary({
134
+ outputSchema: ProductReviewPaginatedResultSchema,
135
+ cache: true,
136
+ cacheTimeToLiveInSeconds: 60,
137
+ currencyDependentCaching: false,
138
+ localeDependentCaching: false
139
+ })
140
+ ], BazaarvoiceProductReviewsCapability.prototype, "findReviews", 1);
141
+ __decorateClass([
142
+ Reactionary({
143
+ outputSchema: ProductReviewSchema,
144
+ cache: false,
145
+ cacheTimeToLiveInSeconds: 0,
146
+ currencyDependentCaching: false,
147
+ localeDependentCaching: false
148
+ })
149
+ ], BazaarvoiceProductReviewsCapability.prototype, "submitReview", 1);
150
+ export {
151
+ BazaarvoiceProductReviewsCapability
152
+ };
package/core/client.js ADDED
@@ -0,0 +1,73 @@
1
+ class BazaarvoiceClient {
2
+ constructor(config, context) {
3
+ this.config = config;
4
+ this.context = context;
5
+ this.baseUrl = config.apiUrl.replace(/\/+$/, "");
6
+ }
7
+ /** Base API URL, e.g. https://api.bazaarvoice.com */
8
+ baseUrl;
9
+ async callGet(url, params, schema) {
10
+ const merged = this.buildParams(params);
11
+ const query = merged.toString() ? `?${merged.toString()}` : "";
12
+ const response = await fetch(`${url}${query}`);
13
+ if (!response.ok) {
14
+ throw new Error(
15
+ `Bazaarvoice GET ${url} error ${response.status} ${response.statusText}`
16
+ );
17
+ }
18
+ const json = await response.json();
19
+ return schema.parse(json);
20
+ }
21
+ async callPost(url, body, schema) {
22
+ const merged = this.buildBody(body);
23
+ const response = await fetch(url, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
26
+ body: merged.toString()
27
+ });
28
+ if (!response.ok) {
29
+ throw new Error(
30
+ `Bazaarvoice POST ${url} error ${response.status} ${response.statusText}`
31
+ );
32
+ }
33
+ const json = await response.json();
34
+ return schema.parse(json);
35
+ }
36
+ /**
37
+ * Merges caller-supplied params with the default BV params (passKey,
38
+ * apiVersion, Locale). Caller-supplied values always win.
39
+ */
40
+ buildParams(params) {
41
+ const merged = new URLSearchParams(params);
42
+ if (!merged.has("passKey"))
43
+ merged.set("passKey", this.config.passKey);
44
+ if (!merged.has("apiVersion"))
45
+ merged.set("apiVersion", this.config.apiVersion);
46
+ if (!merged.has("Locale"))
47
+ merged.set("Locale", this.bvLocale());
48
+ return merged;
49
+ }
50
+ /**
51
+ * Merges caller-supplied POST body with the default BV fields (passKey,
52
+ * apiVersion, Locale). Caller-supplied values always win.
53
+ */
54
+ buildBody(body) {
55
+ const merged = new URLSearchParams(body);
56
+ if (!merged.has("passKey"))
57
+ merged.set("passKey", this.config.passKey);
58
+ if (!merged.has("apiVersion"))
59
+ merged.set("apiVersion", this.config.apiVersion);
60
+ if (!merged.has("Locale"))
61
+ merged.set("Locale", this.bvLocale());
62
+ return merged;
63
+ }
64
+ /**
65
+ * Converts a BCP 47 locale (e.g. `en-US`) to BV's expected `en_US` format.
66
+ */
67
+ bvLocale() {
68
+ return this.context.languageContext.locale.replace("-", "_");
69
+ }
70
+ }
71
+ export {
72
+ BazaarvoiceClient
73
+ };
@@ -0,0 +1,51 @@
1
+ import {
2
+ ProductRatingSummarySchema,
3
+ ProductReviewPaginatedResultSchema,
4
+ ProductReviewSchema
5
+ } from "@reactionary/core";
6
+ import { BazaarvoiceProductReviewsCapability } from "../capabilities/product-reviews.capability.js";
7
+ import { BazaarvoiceProductReviewsFactory } from "../factories/product-reviews/product-reviews.factory.js";
8
+ import { BazaarvoiceCapabilitiesSchema } from "../schema/capabilities.schema.js";
9
+ import { BazaarvoiceConfigurationSchema } from "../schema/configuration.schema.js";
10
+ import { BazaarvoiceClient } from "./client.js";
11
+ import {
12
+ resolveCapabilityWithFactory
13
+ } from "./initialize.types.js";
14
+ function withBazaarvoiceCapabilities(configuration, capabilities) {
15
+ return (cache, context) => {
16
+ const client = {};
17
+ const config = BazaarvoiceConfigurationSchema.parse(configuration);
18
+ const caps = BazaarvoiceCapabilitiesSchema.parse(capabilities);
19
+ const bvClient = new BazaarvoiceClient(config, context);
20
+ if (caps.productReviews?.enabled) {
21
+ client.productReviews = resolveCapabilityWithFactory(
22
+ capabilities.productReviews,
23
+ {
24
+ factory: new BazaarvoiceProductReviewsFactory(
25
+ ProductRatingSummarySchema,
26
+ ProductReviewSchema,
27
+ ProductReviewPaginatedResultSchema
28
+ ),
29
+ capability: (args) => new BazaarvoiceProductReviewsCapability(
30
+ args.cache,
31
+ args.context,
32
+ args.config,
33
+ args.client,
34
+ args.factory
35
+ )
36
+ },
37
+ (factory) => ({
38
+ cache,
39
+ context,
40
+ config,
41
+ client: bvClient,
42
+ factory
43
+ })
44
+ );
45
+ }
46
+ return client;
47
+ };
48
+ }
49
+ export {
50
+ withBazaarvoiceCapabilities
51
+ };
@@ -0,0 +1,8 @@
1
+ function resolveCapabilityWithFactory(capability, defaults, buildCapabilityArgs) {
2
+ const factory = capability?.factory ?? defaults.factory;
3
+ const capabilityFactory = capability?.capability ?? defaults.capability;
4
+ return capabilityFactory(buildCapabilityArgs(factory));
5
+ }
6
+ export {
7
+ resolveCapabilityWithFactory
8
+ };
@@ -0,0 +1 @@
1
+ export * from "./product-reviews/product-reviews.factory.js";
@@ -0,0 +1,65 @@
1
+ class BazaarvoiceProductReviewsFactory {
2
+ constructor(ratingSummarySchema, reviewSchema, reviewPaginatedSchema) {
3
+ this.ratingSummarySchema = ratingSummarySchema;
4
+ this.reviewSchema = reviewSchema;
5
+ this.reviewPaginatedSchema = reviewPaginatedSchema;
6
+ }
7
+ parseRatingSummary(_context, data) {
8
+ const result = data.Results[0];
9
+ const stats = result?.ProductStatistics?.ReviewStatistics;
10
+ if (!result || !stats) {
11
+ return this.ratingSummarySchema.parse({
12
+ identifier: { product: { key: "" } },
13
+ averageRating: 0,
14
+ totalRatings: void 0,
15
+ ratingDistribution: void 0
16
+ });
17
+ }
18
+ const distribution = stats.RatingDistribution ? Object.fromEntries(
19
+ stats.RatingDistribution.map((entry) => [
20
+ String(entry.RatingValue),
21
+ entry.Count
22
+ ])
23
+ ) : void 0;
24
+ return this.ratingSummarySchema.parse({
25
+ identifier: { product: { key: result.ProductStatistics.ProductId } },
26
+ averageRating: stats.AverageOverallRating,
27
+ totalRatings: stats.TotalReviewCount,
28
+ ratingDistribution: distribution
29
+ });
30
+ }
31
+ parseReview(_context, data) {
32
+ return this.reviewSchema.parse({
33
+ identifier: { key: data.Id },
34
+ product: { key: data.ProductId ?? "" },
35
+ authorName: data.UserNickname ?? "Anonymous",
36
+ authorId: data.AuthorId,
37
+ rating: data.Rating,
38
+ title: data.Title ?? "",
39
+ content: data.ReviewText ?? "",
40
+ reply: void 0,
41
+ repliedAt: void 0,
42
+ createdAt: data.SubmissionTime,
43
+ updatedAt: data.LastModificationTime,
44
+ verified: data.IsVerifiedPurchaser ?? false
45
+ });
46
+ }
47
+ parseReviewPaginatedResult(context, data) {
48
+ const totalResults = data.TotalResults ?? data.Results.length;
49
+ const limit = data.Limit ?? data.Results.length;
50
+ const offset = data.Offset ?? 0;
51
+ const pageSize = limit > 0 ? limit : 10;
52
+ const pageNumber = pageSize > 0 ? Math.floor(offset / pageSize) + 1 : 1;
53
+ const totalPages = pageSize > 0 ? Math.ceil(totalResults / pageSize) : 1;
54
+ return this.reviewPaginatedSchema.parse({
55
+ items: data.Results.map((review) => this.parseReview(context, review)),
56
+ totalCount: totalResults,
57
+ pageSize,
58
+ pageNumber,
59
+ totalPages
60
+ });
61
+ }
62
+ }
63
+ export {
64
+ BazaarvoiceProductReviewsFactory
65
+ };
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./core/initialize.js";
2
+ export * from "./core/initialize.types.js";
3
+ export * from "./capabilities/index.js";
4
+ export * from "./factories/index.js";
5
+ export * from "./schema/configuration.schema.js";
6
+ export * from "./schema/capabilities.schema.js";
7
+ export * from "./schema/bazaarvoice.schema.js";
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@reactionary/bazaarvoice",
3
+ "version": "0.9.16",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "types": "./src/index.d.ts",
7
+ "dependencies": {
8
+ "@reactionary/core": "0.9.16",
9
+ "zod": "4.1.9"
10
+ },
11
+ "devDependencies": {
12
+ "@nx/vite": "22.4.5",
13
+ "vitest": "^4.0.9"
14
+ },
15
+ "sideEffects": false
16
+ }
@@ -0,0 +1,63 @@
1
+ import * as z from "zod";
2
+ const BvRatingDistributionEntrySchema = z.looseObject({
3
+ RatingValue: z.number(),
4
+ Count: z.number()
5
+ });
6
+ const BvReviewStatisticsSchema = z.looseObject({
7
+ TotalReviewCount: z.number(),
8
+ AverageOverallRating: z.number(),
9
+ OverallRatingRange: z.number().optional(),
10
+ RatingDistribution: z.array(BvRatingDistributionEntrySchema).optional()
11
+ });
12
+ const BvProductStatisticsSchema = z.looseObject({
13
+ ProductId: z.string(),
14
+ ReviewStatistics: BvReviewStatisticsSchema
15
+ });
16
+ const BvStatisticsResultSchema = z.looseObject({
17
+ ProductStatistics: BvProductStatisticsSchema
18
+ });
19
+ const BvStatisticsResponseSchema = z.looseObject({
20
+ HasErrors: z.boolean(),
21
+ TotalResults: z.number().nullable().optional(),
22
+ Results: z.array(BvStatisticsResultSchema),
23
+ Errors: z.array(z.looseObject({ Message: z.string(), Code: z.string() })).optional()
24
+ });
25
+ const BvReviewSchema = z.looseObject({
26
+ Id: z.string(),
27
+ AuthorId: z.string().optional(),
28
+ UserNickname: z.string().optional(),
29
+ Rating: z.number(),
30
+ Title: z.string().optional(),
31
+ ReviewText: z.string().optional(),
32
+ SubmissionTime: z.string(),
33
+ LastModificationTime: z.string().optional(),
34
+ IsVerifiedPurchaser: z.boolean().optional(),
35
+ ModerationStatus: z.string().optional(),
36
+ ProductId: z.string().optional()
37
+ });
38
+ const BvReviewsResponseSchema = z.looseObject({
39
+ HasErrors: z.boolean(),
40
+ TotalResults: z.number().nullable().optional(),
41
+ Offset: z.number().nullable().optional(),
42
+ Limit: z.number().nullable().optional(),
43
+ Results: z.array(BvReviewSchema),
44
+ Errors: z.array(z.looseObject({ Message: z.string(), Code: z.string() })).optional()
45
+ });
46
+ const BvSubmitReviewResponseSchema = z.looseObject({
47
+ HasErrors: z.boolean(),
48
+ SubmissionId: z.string().optional(),
49
+ Review: BvReviewSchema.optional(),
50
+ FormErrors: z.looseObject({
51
+ FieldErrors: z.record(
52
+ z.string(),
53
+ z.looseObject({ Message: z.string(), Code: z.string() })
54
+ ).optional()
55
+ }).optional(),
56
+ Errors: z.array(z.looseObject({ Message: z.string(), Code: z.string() })).optional()
57
+ });
58
+ export {
59
+ BvReviewSchema,
60
+ BvReviewsResponseSchema,
61
+ BvStatisticsResponseSchema,
62
+ BvSubmitReviewResponseSchema
63
+ };
@@ -0,0 +1,15 @@
1
+ import { CapabilitiesSchema } from "@reactionary/core";
2
+ import * as z from "zod";
3
+ const ProductReviewsCapabilitySchema = z.looseObject({
4
+ enabled: z.boolean(),
5
+ factory: z.unknown().optional(),
6
+ capability: z.unknown().optional()
7
+ });
8
+ const BazaarvoiceCapabilitiesSchema = CapabilitiesSchema.pick({
9
+ productReviews: true
10
+ }).extend({
11
+ productReviews: ProductReviewsCapabilitySchema.optional()
12
+ }).partial();
13
+ export {
14
+ BazaarvoiceCapabilitiesSchema
15
+ };
@@ -0,0 +1,11 @@
1
+ import * as z from "zod";
2
+ const BazaarvoiceConfigurationSchema = z.looseObject({
3
+ passKey: z.string().meta({ description: "Bazaarvoice API passkey." }),
4
+ apiUrl: z.string().default("https://api.bazaarvoice.com").meta({
5
+ description: "Base API URL. Use https://stg.api.bazaarvoice.com for staging."
6
+ }),
7
+ apiVersion: z.string().default("5.4").meta({ description: "Conversations API version." })
8
+ });
9
+ export {
10
+ BazaarvoiceConfigurationSchema
11
+ };
@@ -0,0 +1 @@
1
+ export * from './product-reviews.capability.js';
@@ -0,0 +1,19 @@
1
+ import { ProductReviewsCapability, type Cache, type ProductReviewMutationSubmit, type ProductReviewsFactory, type ProductReviewsFactoryRatingOutput, type ProductReviewsFactoryReviewOutput, type ProductReviewsFactoryReviewPaginatedOutput, type ProductReviewsFactoryWithOutput, type ProductReviewsGetRatingSummaryQuery, type ProductReviewsListQuery, type RequestContext, type Result } from '@reactionary/core';
2
+ import type { BazaarvoiceConfiguration } from '../schema/configuration.schema.js';
3
+ import type { BazaarvoiceClient } from '../core/client.js';
4
+ import type { BazaarvoiceProductReviewsFactory } from '../factories/product-reviews/product-reviews.factory.js';
5
+ export declare class BazaarvoiceProductReviewsCapability<TFactory extends ProductReviewsFactory = BazaarvoiceProductReviewsFactory> extends ProductReviewsCapability<ProductReviewsFactoryRatingOutput<TFactory>, ProductReviewsFactoryReviewPaginatedOutput<TFactory>, ProductReviewsFactoryReviewOutput<TFactory>> {
6
+ protected readonly config: BazaarvoiceConfiguration;
7
+ protected readonly client: BazaarvoiceClient;
8
+ protected readonly factory: ProductReviewsFactoryWithOutput<TFactory>;
9
+ constructor(cache: Cache, context: RequestContext, config: BazaarvoiceConfiguration, client: BazaarvoiceClient, factory: ProductReviewsFactoryWithOutput<TFactory>);
10
+ getRatingSummary(query: ProductReviewsGetRatingSummaryQuery): Promise<Result<ProductReviewsFactoryRatingOutput<TFactory>>>;
11
+ findReviews(query: ProductReviewsListQuery): Promise<Result<ProductReviewsFactoryReviewPaginatedOutput<TFactory>>>;
12
+ submitReview(mutation: ProductReviewMutationSubmit): Promise<Result<ProductReviewsFactoryReviewOutput<TFactory>>>;
13
+ protected getStatisticsUrl(): string;
14
+ protected getStatisticsParams(productId: string): URLSearchParams;
15
+ protected getReviewsUrl(): string;
16
+ protected getReviewsParams(productId: string, limit: number, offset: number): URLSearchParams;
17
+ protected getSubmitReviewUrl(): string;
18
+ protected getSubmitReviewBody(mutation: ProductReviewMutationSubmit): URLSearchParams;
19
+ }
@@ -0,0 +1,39 @@
1
+ import type { RequestContext } from '@reactionary/core';
2
+ import type { BazaarvoiceConfiguration } from '../schema/configuration.schema.js';
3
+ /**
4
+ * Thin HTTP client for the Bazaarvoice Conversations REST API.
5
+ *
6
+ * Handles only transport concerns — authentication params (passKey, apiVersion)
7
+ * and locale (derived from the request context) are injected automatically on
8
+ * every request via `buildParams` / `buildBody`.
9
+ *
10
+ * URL construction and request-specific parameters are the responsibility of
11
+ * capability subclasses (extension-point pattern).
12
+ */
13
+ export declare class BazaarvoiceClient {
14
+ protected readonly config: BazaarvoiceConfiguration;
15
+ protected readonly context: RequestContext;
16
+ /** Base API URL, e.g. https://api.bazaarvoice.com */
17
+ readonly baseUrl: string;
18
+ constructor(config: BazaarvoiceConfiguration, context: RequestContext);
19
+ callGet<T>(url: string, params: URLSearchParams, schema: {
20
+ parse(data: unknown): T;
21
+ }): Promise<T>;
22
+ callPost<T>(url: string, body: URLSearchParams, schema: {
23
+ parse(data: unknown): T;
24
+ }): Promise<T>;
25
+ /**
26
+ * Merges caller-supplied params with the default BV params (passKey,
27
+ * apiVersion, Locale). Caller-supplied values always win.
28
+ */
29
+ protected buildParams(params: URLSearchParams): URLSearchParams;
30
+ /**
31
+ * Merges caller-supplied POST body with the default BV fields (passKey,
32
+ * apiVersion, Locale). Caller-supplied values always win.
33
+ */
34
+ protected buildBody(body: URLSearchParams): URLSearchParams;
35
+ /**
36
+ * Converts a BCP 47 locale (e.g. `en-US`) to BV's expected `en_US` format.
37
+ */
38
+ protected bvLocale(): string;
39
+ }
@@ -0,0 +1,5 @@
1
+ import type { Cache, RequestContext } from '@reactionary/core';
2
+ import type { BazaarvoiceCapabilities } from '../schema/capabilities.schema.js';
3
+ import type { BazaarvoiceConfiguration } from '../schema/configuration.schema.js';
4
+ import { type BazaarvoiceClientFromCapabilities } from './initialize.types.js';
5
+ export declare function withBazaarvoiceCapabilities<T extends BazaarvoiceCapabilities>(configuration: BazaarvoiceConfiguration, capabilities: T): (cache: Cache, context: RequestContext) => BazaarvoiceClientFromCapabilities<T>;
@@ -0,0 +1,34 @@
1
+ import type { ClientFromCapabilities, ProductReviewsFactory } from '@reactionary/core';
2
+ import type { BazaarvoiceCapabilities } from '../schema/capabilities.schema.js';
3
+ import type { BazaarvoiceProductReviewsFactory } from '../factories/product-reviews/product-reviews.factory.js';
4
+ import type { BazaarvoiceProductReviewsCapability } from '../capabilities/product-reviews.capability.js';
5
+ type EnabledCapability<TCapability> = TCapability extends {
6
+ enabled: true;
7
+ } ? true : false;
8
+ type NormalizeConfiguredCapabilities<T extends BazaarvoiceCapabilities> = Omit<T, 'productReviews'> & {
9
+ productReviews?: EnabledCapability<T['productReviews']>;
10
+ };
11
+ type ExtractCapabilityFactory<TCapability, TContract, TDefaultFactory> = TCapability extends {
12
+ enabled: true;
13
+ factory?: infer TFactory;
14
+ } ? TFactory extends TContract ? TFactory : TDefaultFactory : TDefaultFactory;
15
+ type ExtractCapabilityImplementation<TCapability, TDefaultCapability> = TCapability extends {
16
+ enabled: true;
17
+ capability?: infer TCapabilityFactory;
18
+ } ? TCapabilityFactory extends (...args: unknown[]) => infer TResolvedCapability ? TResolvedCapability : TDefaultCapability : TDefaultCapability;
19
+ type CapabilityOverride<TCapability, TKey extends string, TResolvedCapability> = TCapability extends {
20
+ enabled: true;
21
+ } ? {
22
+ [K in TKey]: TResolvedCapability;
23
+ } : Record<never, never>;
24
+ type ProductReviewsFactoryFor<T extends BazaarvoiceCapabilities> = ExtractCapabilityFactory<T['productReviews'], ProductReviewsFactory, BazaarvoiceProductReviewsFactory>;
25
+ type ProductReviewsCapabilityFor<T extends BazaarvoiceCapabilities> = ExtractCapabilityImplementation<T['productReviews'], BazaarvoiceProductReviewsCapability<ProductReviewsFactoryFor<T>>>;
26
+ export type BazaarvoiceClientFromCapabilities<T extends BazaarvoiceCapabilities> = Omit<ClientFromCapabilities<NormalizeConfiguredCapabilities<T>>, 'productReviews'> & CapabilityOverride<T['productReviews'], 'productReviews', ProductReviewsCapabilityFor<T>>;
27
+ export declare function resolveCapabilityWithFactory<TFactory, TResolvedCapability, TCapabilityArgs>(capability: {
28
+ factory?: TFactory;
29
+ capability?: (args: TCapabilityArgs) => TResolvedCapability;
30
+ } | undefined, defaults: {
31
+ factory: TFactory;
32
+ capability: (args: TCapabilityArgs) => TResolvedCapability;
33
+ }, buildCapabilityArgs: (factory: TFactory) => TCapabilityArgs): TResolvedCapability;
34
+ export {};
@@ -0,0 +1 @@
1
+ export * from './product-reviews/product-reviews.factory.js';
@@ -0,0 +1,12 @@
1
+ import type * as z from 'zod';
2
+ import type { AnyProductRatingSummarySchema, AnyProductReviewPaginatedSchema, AnyProductReviewSchema, ProductReviewsFactory, RequestContext, ProductRatingSummarySchema, ProductReviewSchema, ProductReviewPaginatedResultSchema } from '@reactionary/core';
3
+ import type { BvReview, BvReviewsResponse, BvStatisticsResponse } from '../../schema/bazaarvoice.schema.js';
4
+ export declare class BazaarvoiceProductReviewsFactory<TRatingSummarySchema extends AnyProductRatingSummarySchema = typeof ProductRatingSummarySchema, TReviewSchema extends AnyProductReviewSchema = typeof ProductReviewSchema, TReviewPaginatedSchema extends AnyProductReviewPaginatedSchema = typeof ProductReviewPaginatedResultSchema> implements ProductReviewsFactory<TRatingSummarySchema, TReviewSchema, TReviewPaginatedSchema> {
5
+ readonly ratingSummarySchema: TRatingSummarySchema;
6
+ readonly reviewSchema: TReviewSchema;
7
+ readonly reviewPaginatedSchema: TReviewPaginatedSchema;
8
+ constructor(ratingSummarySchema: TRatingSummarySchema, reviewSchema: TReviewSchema, reviewPaginatedSchema: TReviewPaginatedSchema);
9
+ parseRatingSummary(_context: RequestContext, data: BvStatisticsResponse): z.output<TRatingSummarySchema>;
10
+ parseReview(_context: RequestContext, data: BvReview): z.output<TReviewSchema>;
11
+ parseReviewPaginatedResult(context: RequestContext, data: BvReviewsResponse): z.output<TReviewPaginatedSchema>;
12
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './core/initialize.js';
2
+ export * from './core/initialize.types.js';
3
+ export * from './capabilities/index.js';
4
+ export * from './factories/index.js';
5
+ export * from './schema/configuration.schema.js';
6
+ export * from './schema/capabilities.schema.js';
7
+ export * from './schema/bazaarvoice.schema.js';
@@ -0,0 +1,101 @@
1
+ import * as z from 'zod';
2
+ declare const BvReviewStatisticsSchema: z.ZodObject<{
3
+ TotalReviewCount: z.ZodNumber;
4
+ AverageOverallRating: z.ZodNumber;
5
+ OverallRatingRange: z.ZodOptional<z.ZodNumber>;
6
+ RatingDistribution: z.ZodOptional<z.ZodArray<z.ZodObject<{
7
+ RatingValue: z.ZodNumber;
8
+ Count: z.ZodNumber;
9
+ }, z.core.$loose>>>;
10
+ }, z.core.$loose>;
11
+ export declare const BvStatisticsResponseSchema: z.ZodObject<{
12
+ HasErrors: z.ZodBoolean;
13
+ TotalResults: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
14
+ Results: z.ZodArray<z.ZodObject<{
15
+ ProductStatistics: z.ZodObject<{
16
+ ProductId: z.ZodString;
17
+ ReviewStatistics: z.ZodObject<{
18
+ TotalReviewCount: z.ZodNumber;
19
+ AverageOverallRating: z.ZodNumber;
20
+ OverallRatingRange: z.ZodOptional<z.ZodNumber>;
21
+ RatingDistribution: z.ZodOptional<z.ZodArray<z.ZodObject<{
22
+ RatingValue: z.ZodNumber;
23
+ Count: z.ZodNumber;
24
+ }, z.core.$loose>>>;
25
+ }, z.core.$loose>;
26
+ }, z.core.$loose>;
27
+ }, z.core.$loose>>;
28
+ Errors: z.ZodOptional<z.ZodArray<z.ZodObject<{
29
+ Message: z.ZodString;
30
+ Code: z.ZodString;
31
+ }, z.core.$loose>>>;
32
+ }, z.core.$loose>;
33
+ export type BvStatisticsResponse = z.infer<typeof BvStatisticsResponseSchema>;
34
+ export type BvReviewStatistics = z.infer<typeof BvReviewStatisticsSchema>;
35
+ export declare const BvReviewSchema: z.ZodObject<{
36
+ Id: z.ZodString;
37
+ AuthorId: z.ZodOptional<z.ZodString>;
38
+ UserNickname: z.ZodOptional<z.ZodString>;
39
+ Rating: z.ZodNumber;
40
+ Title: z.ZodOptional<z.ZodString>;
41
+ ReviewText: z.ZodOptional<z.ZodString>;
42
+ SubmissionTime: z.ZodString;
43
+ LastModificationTime: z.ZodOptional<z.ZodString>;
44
+ IsVerifiedPurchaser: z.ZodOptional<z.ZodBoolean>;
45
+ ModerationStatus: z.ZodOptional<z.ZodString>;
46
+ ProductId: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$loose>;
48
+ export declare const BvReviewsResponseSchema: z.ZodObject<{
49
+ HasErrors: z.ZodBoolean;
50
+ TotalResults: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
51
+ Offset: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
52
+ Limit: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
53
+ Results: z.ZodArray<z.ZodObject<{
54
+ Id: z.ZodString;
55
+ AuthorId: z.ZodOptional<z.ZodString>;
56
+ UserNickname: z.ZodOptional<z.ZodString>;
57
+ Rating: z.ZodNumber;
58
+ Title: z.ZodOptional<z.ZodString>;
59
+ ReviewText: z.ZodOptional<z.ZodString>;
60
+ SubmissionTime: z.ZodString;
61
+ LastModificationTime: z.ZodOptional<z.ZodString>;
62
+ IsVerifiedPurchaser: z.ZodOptional<z.ZodBoolean>;
63
+ ModerationStatus: z.ZodOptional<z.ZodString>;
64
+ ProductId: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$loose>>;
66
+ Errors: z.ZodOptional<z.ZodArray<z.ZodObject<{
67
+ Message: z.ZodString;
68
+ Code: z.ZodString;
69
+ }, z.core.$loose>>>;
70
+ }, z.core.$loose>;
71
+ export type BvReview = z.infer<typeof BvReviewSchema>;
72
+ export type BvReviewsResponse = z.infer<typeof BvReviewsResponseSchema>;
73
+ export declare const BvSubmitReviewResponseSchema: z.ZodObject<{
74
+ HasErrors: z.ZodBoolean;
75
+ SubmissionId: z.ZodOptional<z.ZodString>;
76
+ Review: z.ZodOptional<z.ZodObject<{
77
+ Id: z.ZodString;
78
+ AuthorId: z.ZodOptional<z.ZodString>;
79
+ UserNickname: z.ZodOptional<z.ZodString>;
80
+ Rating: z.ZodNumber;
81
+ Title: z.ZodOptional<z.ZodString>;
82
+ ReviewText: z.ZodOptional<z.ZodString>;
83
+ SubmissionTime: z.ZodString;
84
+ LastModificationTime: z.ZodOptional<z.ZodString>;
85
+ IsVerifiedPurchaser: z.ZodOptional<z.ZodBoolean>;
86
+ ModerationStatus: z.ZodOptional<z.ZodString>;
87
+ ProductId: z.ZodOptional<z.ZodString>;
88
+ }, z.core.$loose>>;
89
+ FormErrors: z.ZodOptional<z.ZodObject<{
90
+ FieldErrors: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
91
+ Message: z.ZodString;
92
+ Code: z.ZodString;
93
+ }, z.core.$loose>>>;
94
+ }, z.core.$loose>>;
95
+ Errors: z.ZodOptional<z.ZodArray<z.ZodObject<{
96
+ Message: z.ZodString;
97
+ Code: z.ZodString;
98
+ }, z.core.$loose>>>;
99
+ }, z.core.$loose>;
100
+ export type BvSubmitReviewResponse = z.infer<typeof BvSubmitReviewResponseSchema>;
101
+ export {};
@@ -0,0 +1,26 @@
1
+ import type { Cache, ProductReviewsCapability, ProductReviewsFactory, ProductReviewsFactoryWithOutput, RequestContext } from '@reactionary/core';
2
+ import type { BazaarvoiceConfiguration } from './configuration.schema.js';
3
+ import type { BazaarvoiceClient } from '../core/client.js';
4
+ import * as z from 'zod';
5
+ export declare const BazaarvoiceCapabilitiesSchema: z.ZodObject<{
6
+ productReviews: z.ZodOptional<z.ZodOptional<z.ZodObject<{
7
+ enabled: z.ZodBoolean;
8
+ factory: z.ZodOptional<z.ZodUnknown>;
9
+ capability: z.ZodOptional<z.ZodUnknown>;
10
+ }, z.core.$loose>>>;
11
+ }, z.core.$loose>;
12
+ export type BazaarvoiceCapabilities = z.infer<typeof BazaarvoiceCapabilitiesSchema>;
13
+ export interface BazaarvoiceCapabilityFactoryArgs {
14
+ cache: Cache;
15
+ context: RequestContext;
16
+ config: BazaarvoiceConfiguration;
17
+ }
18
+ export interface BazaarvoiceProductReviewsCapabilityFactoryArgs<TFactory extends ProductReviewsFactory = ProductReviewsFactory> extends BazaarvoiceCapabilityFactoryArgs {
19
+ client: BazaarvoiceClient;
20
+ factory: ProductReviewsFactoryWithOutput<TFactory>;
21
+ }
22
+ export interface BazaarvoiceProductReviewsCapabilityConfig<TFactory extends ProductReviewsFactory = ProductReviewsFactory, TCapability extends ProductReviewsCapability = ProductReviewsCapability> {
23
+ enabled: boolean;
24
+ factory?: ProductReviewsFactoryWithOutput<TFactory>;
25
+ capability?: (args: BazaarvoiceProductReviewsCapabilityFactoryArgs<TFactory>) => TCapability;
26
+ }
@@ -0,0 +1,7 @@
1
+ import * as z from 'zod';
2
+ export declare const BazaarvoiceConfigurationSchema: z.ZodObject<{
3
+ passKey: z.ZodString;
4
+ apiUrl: z.ZodDefault<z.ZodString>;
5
+ apiVersion: z.ZodDefault<z.ZodString>;
6
+ }, z.core.$loose>;
7
+ export type BazaarvoiceConfiguration = z.infer<typeof BazaarvoiceConfigurationSchema>;