brainerce 1.53.1 → 1.54.0

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 CHANGED
@@ -280,7 +280,7 @@ The SDK exports these utility functions for common UI tasks:
280
280
  | `getProductPriceInfo(product)` | Get price + sale info + discount % (falls back to `priceMin` when `basePrice=0` on VARIABLE) | `{ price, isOnSale, discountPercent }` |
281
281
  | `getVariantPrice(variant, basePrice)` | Get variant price with fallback | `getVariantPrice(variant, '29.99')` → `34.99` |
282
282
  | `getCartTotals(cart, shippingPrice?)` | Calculate cart subtotal/discount/total | `{ subtotal, discount, shipping, total }` |
283
- | `getCartItemName(item)` | Get name from nested cart item | `getCartItemName(item)` → `"Blue T-Shirt"` |
283
+ | `getCartItemName(item)` | Get name from nested cart item (product + variant) | `getCartItemName(item)` → `"Blue T-Shirt - Large"` |
284
284
  | `getCartItemImage(item)` | Get image URL from cart item | `getCartItemImage(item)` → `"https://..."` |
285
285
  | `getVariantOptions(variant)` | Get variant attributes as array | `[{ name: "Color", value: "Red" }]` |
286
286
  | `isCouponApplicableToProduct(coupon, product)` | Check if coupon applies | `isCouponApplicableToProduct(coupon, product)` |
@@ -4542,6 +4542,20 @@ await client.updateRegionPaymentProviders(eu.id, ['app_inst_stripe', 'app_inst_p
4542
4542
  // Which installed providers can serve this region's countries?
4543
4543
  const compatible = await client.getRegionCompatibleProviders(eu.id);
4544
4544
 
4545
+ // Regional pricing — hand-set the price pair per product/variant in the
4546
+ // REGION's currency ($24.99 instead of the converted $24.63). A manual pair
4547
+ // replaces the whole converted pair; no entry = automatic FX conversion.
4548
+ // Applies only when the region actually charges its currency (presentment) —
4549
+ // then storefronts see it in displayPrice/displaySalePrice and checkout
4550
+ // charges pinned lines at it verbatim. salePrice must be lower than price.
4551
+ const { data: prices } = await client.getRegionPrices(eu.id, { page: 1 });
4552
+ await client.upsertRegionPrices(eu.id, [
4553
+ { productId: 'prod_tshirt', price: 24.99, salePrice: 19.99 },
4554
+ { productId: 'prod_tshirt', variantId: 'var_l', price: 26.99 },
4555
+ { productId: 'prod_mug', remove: true }, // back to automatic conversion
4556
+ ]);
4557
+ await client.deleteRegionPrice(eu.id, 'rp_123');
4558
+
4545
4559
  // Pure client-side helper (no network) — pick a region for a country code
4546
4560
  const dest = client.detectRegion('DE', regions); // → the EU region, or the default, or null
4547
4561
 
package/dist/index.d.mts CHANGED
@@ -1131,8 +1131,14 @@ declare function getCartTotals(cart: Pick<Cart, 'subtotal' | 'discountAmount'> |
1131
1131
  };
1132
1132
  /**
1133
1133
  * Get the display name for a cart item.
1134
- * Handles the nested product/variant structure - uses variant name if available,
1135
- * otherwise falls back to product name.
1134
+ * Handles the nested product/variant structure - the product name identifies the
1135
+ * line, with the variant name appended as a qualifier: `"Blue T-Shirt - Large"`.
1136
+ *
1137
+ * The suffix is omitted for products with no variant, and for variants whose name
1138
+ * was copied from the product (so the name never renders twice).
1139
+ *
1140
+ * Need the two parts separately — e.g. to render the variant on its own line —
1141
+ * read `item.product.name` and `item.variant?.name` directly.
1136
1142
  *
1137
1143
  * @param item - The cart item
1138
1144
  * @returns The display name for the item
@@ -1140,7 +1146,7 @@ declare function getCartTotals(cart: Pick<Cart, 'subtotal' | 'discountAmount'> |
1140
1146
  * @example
1141
1147
  * ```typescript
1142
1148
  * cart.items.forEach(item => {
1143
- * const name = getCartItemName(item);
1149
+ * const name = getCartItemName(item); // "Blue T-Shirt - Large"
1144
1150
  * console.log(`${name} x ${item.quantity}`);
1145
1151
  * });
1146
1152
  * ```
@@ -4630,6 +4636,37 @@ interface AutoRegionResponse {
4630
4636
  * still runs at checkout against the full shipping address (state / postal /
4631
4637
  * per-class). Render with an "Estimate" affordance.
4632
4638
  */
4639
+ /**
4640
+ * Manual per-region price override (regional pricing) — the merchant's
4641
+ * hand-set price pair for one product or variant in one region, denominated
4642
+ * in the REGION's currency. Replaces the automatic FX conversion for regions
4643
+ * that actually charge their currency (presentment). Admin (api-key) surface.
4644
+ */
4645
+ interface RegionPrice {
4646
+ id: string;
4647
+ productId: string;
4648
+ /** null = the product-level price (covers every variant without its own row). */
4649
+ variantId: string | null;
4650
+ /** Regular price in the region currency (Decimal string). */
4651
+ price: string;
4652
+ /** Sale price in the region currency — always lower than price; null = no regional sale. */
4653
+ salePrice: string | null;
4654
+ updatedAt: string;
4655
+ }
4656
+ /** One bulk entry for `upsertRegionPrices`. `remove: true` deletes the row. */
4657
+ interface RegionPriceEntry {
4658
+ productId: string;
4659
+ variantId?: string;
4660
+ /** Required unless `remove` — regular price in the region currency. */
4661
+ price?: number;
4662
+ /** Optional sale price — must be lower than `price`. */
4663
+ salePrice?: number;
4664
+ remove?: boolean;
4665
+ }
4666
+ interface UpsertRegionPricesResult {
4667
+ upserted: number;
4668
+ removed: number;
4669
+ }
4633
4670
  interface TaxEstimateResponse {
4634
4671
  appliesTax: boolean;
4635
4672
  /** Percent — e.g. 18 for 18%. `null` when no matching rule. */
@@ -10474,6 +10511,24 @@ declare class BrainerceClient {
10474
10511
  appId: string;
10475
10512
  name?: string | null;
10476
10513
  }>>;
10514
+ /**
10515
+ * List a region's manual price overrides (regional pricing, admin). Prices
10516
+ * are in the region's currency; `variantId: null` rows are product-level.
10517
+ */
10518
+ getRegionPrices(regionId: string, params?: {
10519
+ productId?: string;
10520
+ page?: number;
10521
+ limit?: number;
10522
+ }): Promise<PaginatedResponse<RegionPrice>>;
10523
+ /**
10524
+ * Bulk upsert/remove manual price overrides for a region (admin). Each
10525
+ * entry: `price` = regular, `salePrice` = sale (must be lower), in the
10526
+ * REGION's currency; `remove: true` deletes. A product/variant with no
10527
+ * entry keeps automatic FX conversion.
10528
+ */
10529
+ upsertRegionPrices(regionId: string, entries: RegionPriceEntry[]): Promise<UpsertRegionPricesResult>;
10530
+ /** Delete one manual price override by id (admin). */
10531
+ deleteRegionPrice(regionId: string, priceId: string): Promise<void>;
10477
10532
  /**
10478
10533
  * List the store's ACTIVE regions (public, no apiKey). Works in storeId and
10479
10534
  * vibe-coded modes. Returns only storefront-safe fields (no internal flags).
@@ -11126,7 +11181,7 @@ declare class BrainerceError extends Error {
11126
11181
  constructor(message: string, statusCode: number, details?: unknown);
11127
11182
  }
11128
11183
 
11129
- declare const SDK_VERSION = "1.53.1";
11184
+ declare const SDK_VERSION = "1.54.0";
11130
11185
 
11131
11186
  /**
11132
11187
  * Verify a webhook signature from Brainerce
package/dist/index.d.ts CHANGED
@@ -1131,8 +1131,14 @@ declare function getCartTotals(cart: Pick<Cart, 'subtotal' | 'discountAmount'> |
1131
1131
  };
1132
1132
  /**
1133
1133
  * Get the display name for a cart item.
1134
- * Handles the nested product/variant structure - uses variant name if available,
1135
- * otherwise falls back to product name.
1134
+ * Handles the nested product/variant structure - the product name identifies the
1135
+ * line, with the variant name appended as a qualifier: `"Blue T-Shirt - Large"`.
1136
+ *
1137
+ * The suffix is omitted for products with no variant, and for variants whose name
1138
+ * was copied from the product (so the name never renders twice).
1139
+ *
1140
+ * Need the two parts separately — e.g. to render the variant on its own line —
1141
+ * read `item.product.name` and `item.variant?.name` directly.
1136
1142
  *
1137
1143
  * @param item - The cart item
1138
1144
  * @returns The display name for the item
@@ -1140,7 +1146,7 @@ declare function getCartTotals(cart: Pick<Cart, 'subtotal' | 'discountAmount'> |
1140
1146
  * @example
1141
1147
  * ```typescript
1142
1148
  * cart.items.forEach(item => {
1143
- * const name = getCartItemName(item);
1149
+ * const name = getCartItemName(item); // "Blue T-Shirt - Large"
1144
1150
  * console.log(`${name} x ${item.quantity}`);
1145
1151
  * });
1146
1152
  * ```
@@ -4630,6 +4636,37 @@ interface AutoRegionResponse {
4630
4636
  * still runs at checkout against the full shipping address (state / postal /
4631
4637
  * per-class). Render with an "Estimate" affordance.
4632
4638
  */
4639
+ /**
4640
+ * Manual per-region price override (regional pricing) — the merchant's
4641
+ * hand-set price pair for one product or variant in one region, denominated
4642
+ * in the REGION's currency. Replaces the automatic FX conversion for regions
4643
+ * that actually charge their currency (presentment). Admin (api-key) surface.
4644
+ */
4645
+ interface RegionPrice {
4646
+ id: string;
4647
+ productId: string;
4648
+ /** null = the product-level price (covers every variant without its own row). */
4649
+ variantId: string | null;
4650
+ /** Regular price in the region currency (Decimal string). */
4651
+ price: string;
4652
+ /** Sale price in the region currency — always lower than price; null = no regional sale. */
4653
+ salePrice: string | null;
4654
+ updatedAt: string;
4655
+ }
4656
+ /** One bulk entry for `upsertRegionPrices`. `remove: true` deletes the row. */
4657
+ interface RegionPriceEntry {
4658
+ productId: string;
4659
+ variantId?: string;
4660
+ /** Required unless `remove` — regular price in the region currency. */
4661
+ price?: number;
4662
+ /** Optional sale price — must be lower than `price`. */
4663
+ salePrice?: number;
4664
+ remove?: boolean;
4665
+ }
4666
+ interface UpsertRegionPricesResult {
4667
+ upserted: number;
4668
+ removed: number;
4669
+ }
4633
4670
  interface TaxEstimateResponse {
4634
4671
  appliesTax: boolean;
4635
4672
  /** Percent — e.g. 18 for 18%. `null` when no matching rule. */
@@ -10474,6 +10511,24 @@ declare class BrainerceClient {
10474
10511
  appId: string;
10475
10512
  name?: string | null;
10476
10513
  }>>;
10514
+ /**
10515
+ * List a region's manual price overrides (regional pricing, admin). Prices
10516
+ * are in the region's currency; `variantId: null` rows are product-level.
10517
+ */
10518
+ getRegionPrices(regionId: string, params?: {
10519
+ productId?: string;
10520
+ page?: number;
10521
+ limit?: number;
10522
+ }): Promise<PaginatedResponse<RegionPrice>>;
10523
+ /**
10524
+ * Bulk upsert/remove manual price overrides for a region (admin). Each
10525
+ * entry: `price` = regular, `salePrice` = sale (must be lower), in the
10526
+ * REGION's currency; `remove: true` deletes. A product/variant with no
10527
+ * entry keeps automatic FX conversion.
10528
+ */
10529
+ upsertRegionPrices(regionId: string, entries: RegionPriceEntry[]): Promise<UpsertRegionPricesResult>;
10530
+ /** Delete one manual price override by id (admin). */
10531
+ deleteRegionPrice(regionId: string, priceId: string): Promise<void>;
10477
10532
  /**
10478
10533
  * List the store's ACTIVE regions (public, no apiKey). Works in storeId and
10479
10534
  * vibe-coded modes. Returns only storefront-safe fields (no internal flags).
@@ -11126,7 +11181,7 @@ declare class BrainerceError extends Error {
11126
11181
  constructor(message: string, statusCode: number, details?: unknown);
11127
11182
  }
11128
11183
 
11129
- declare const SDK_VERSION = "1.53.1";
11184
+ declare const SDK_VERSION = "1.54.0";
11130
11185
 
11131
11186
  /**
11132
11187
  * Verify a webhook signature from Brainerce
package/dist/index.js CHANGED
@@ -98,7 +98,7 @@ var CART_GUARDS = [
98
98
  var CART_ITEM_GUARDS = [
99
99
  {
100
100
  property: "name",
101
- message: 'CartItem has no "name" field. Use item.product.name or getCartItemName(item).\nImport: import { getCartItemName } from "brainerce";'
101
+ message: 'CartItem has no "name" field. Use getCartItemName(item) for the full\n"Product - Variant" label, or item.product.name for the product name alone.\nImport: import { getCartItemName } from "brainerce";'
102
102
  },
103
103
  {
104
104
  property: "price",
@@ -201,7 +201,7 @@ function isDevGuardsEnabled() {
201
201
  }
202
202
 
203
203
  // src/version.ts
204
- var SDK_VERSION = "1.53.1";
204
+ var SDK_VERSION = "1.54.0";
205
205
 
206
206
  // src/client.ts
207
207
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -8260,6 +8260,42 @@ var _BrainerceClient = class _BrainerceClient {
8260
8260
  `/api/v1/regions/${encodePathSegment(regionId)}/compatible-providers`
8261
8261
  );
8262
8262
  }
8263
+ /**
8264
+ * List a region's manual price overrides (regional pricing, admin). Prices
8265
+ * are in the region's currency; `variantId: null` rows are product-level.
8266
+ */
8267
+ async getRegionPrices(regionId, params = {}) {
8268
+ return this.adminRequest(
8269
+ "GET",
8270
+ `/api/v1/regions/${encodePathSegment(regionId)}/prices`,
8271
+ void 0,
8272
+ {
8273
+ productId: params.productId,
8274
+ page: params.page,
8275
+ limit: params.limit
8276
+ }
8277
+ );
8278
+ }
8279
+ /**
8280
+ * Bulk upsert/remove manual price overrides for a region (admin). Each
8281
+ * entry: `price` = regular, `salePrice` = sale (must be lower), in the
8282
+ * REGION's currency; `remove: true` deletes. A product/variant with no
8283
+ * entry keeps automatic FX conversion.
8284
+ */
8285
+ async upsertRegionPrices(regionId, entries) {
8286
+ return this.adminRequest(
8287
+ "PUT",
8288
+ `/api/v1/regions/${encodePathSegment(regionId)}/prices`,
8289
+ { entries }
8290
+ );
8291
+ }
8292
+ /** Delete one manual price override by id (admin). */
8293
+ async deleteRegionPrice(regionId, priceId) {
8294
+ await this.adminRequest(
8295
+ "DELETE",
8296
+ `/api/v1/regions/${encodePathSegment(regionId)}/prices/${encodePathSegment(priceId)}`
8297
+ );
8298
+ }
8263
8299
  // -------------------- Regions (Storefront + vibe-coded, public — no apiKey) --------------------
8264
8300
  // storeId- or connectionId-based, no auth. Call these from a storefront to
8265
8301
  // detect the buyer's region (story S1), then pair with detectRegion(). Then
@@ -10209,7 +10245,10 @@ function getCartTotals(cart, shippingPrice) {
10209
10245
  return { subtotal, discount, shipping, total };
10210
10246
  }
10211
10247
  function getCartItemName(item) {
10212
- return item.variant?.name || item.product.name;
10248
+ const productName = item.product.name;
10249
+ const variantName = item.variant?.name?.trim();
10250
+ if (!variantName || variantName === productName) return productName;
10251
+ return `${productName} - ${variantName}`;
10213
10252
  }
10214
10253
  function getCartItemImage(item) {
10215
10254
  if (item.variant?.image) {
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ var CART_GUARDS = [
12
12
  var CART_ITEM_GUARDS = [
13
13
  {
14
14
  property: "name",
15
- message: 'CartItem has no "name" field. Use item.product.name or getCartItemName(item).\nImport: import { getCartItemName } from "brainerce";'
15
+ message: 'CartItem has no "name" field. Use getCartItemName(item) for the full\n"Product - Variant" label, or item.product.name for the product name alone.\nImport: import { getCartItemName } from "brainerce";'
16
16
  },
17
17
  {
18
18
  property: "price",
@@ -115,7 +115,7 @@ function isDevGuardsEnabled() {
115
115
  }
116
116
 
117
117
  // src/version.ts
118
- var SDK_VERSION = "1.53.1";
118
+ var SDK_VERSION = "1.54.0";
119
119
 
120
120
  // src/client.ts
121
121
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -8174,6 +8174,42 @@ var _BrainerceClient = class _BrainerceClient {
8174
8174
  `/api/v1/regions/${encodePathSegment(regionId)}/compatible-providers`
8175
8175
  );
8176
8176
  }
8177
+ /**
8178
+ * List a region's manual price overrides (regional pricing, admin). Prices
8179
+ * are in the region's currency; `variantId: null` rows are product-level.
8180
+ */
8181
+ async getRegionPrices(regionId, params = {}) {
8182
+ return this.adminRequest(
8183
+ "GET",
8184
+ `/api/v1/regions/${encodePathSegment(regionId)}/prices`,
8185
+ void 0,
8186
+ {
8187
+ productId: params.productId,
8188
+ page: params.page,
8189
+ limit: params.limit
8190
+ }
8191
+ );
8192
+ }
8193
+ /**
8194
+ * Bulk upsert/remove manual price overrides for a region (admin). Each
8195
+ * entry: `price` = regular, `salePrice` = sale (must be lower), in the
8196
+ * REGION's currency; `remove: true` deletes. A product/variant with no
8197
+ * entry keeps automatic FX conversion.
8198
+ */
8199
+ async upsertRegionPrices(regionId, entries) {
8200
+ return this.adminRequest(
8201
+ "PUT",
8202
+ `/api/v1/regions/${encodePathSegment(regionId)}/prices`,
8203
+ { entries }
8204
+ );
8205
+ }
8206
+ /** Delete one manual price override by id (admin). */
8207
+ async deleteRegionPrice(regionId, priceId) {
8208
+ await this.adminRequest(
8209
+ "DELETE",
8210
+ `/api/v1/regions/${encodePathSegment(regionId)}/prices/${encodePathSegment(priceId)}`
8211
+ );
8212
+ }
8177
8213
  // -------------------- Regions (Storefront + vibe-coded, public — no apiKey) --------------------
8178
8214
  // storeId- or connectionId-based, no auth. Call these from a storefront to
8179
8215
  // detect the buyer's region (story S1), then pair with detectRegion(). Then
@@ -10123,7 +10159,10 @@ function getCartTotals(cart, shippingPrice) {
10123
10159
  return { subtotal, discount, shipping, total };
10124
10160
  }
10125
10161
  function getCartItemName(item) {
10126
- return item.variant?.name || item.product.name;
10162
+ const productName = item.product.name;
10163
+ const variantName = item.variant?.name?.trim();
10164
+ if (!variantName || variantName === productName) return productName;
10165
+ return `${productName} - ${variantName}`;
10127
10166
  }
10128
10167
  function getCartItemImage(item) {
10129
10168
  if (item.variant?.image) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainerce",
3
- "version": "1.53.1",
3
+ "version": "1.54.0",
4
4
  "description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",