brainerce 1.52.0 → 1.53.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/dist/index.mjs CHANGED
@@ -115,7 +115,7 @@ function isDevGuardsEnabled() {
115
115
  }
116
116
 
117
117
  // src/version.ts
118
- var SDK_VERSION = "1.52.0";
118
+ var SDK_VERSION = "1.53.0";
119
119
 
120
120
  // src/client.ts
121
121
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -155,7 +155,7 @@ function parseRetryAfterMs(response) {
155
155
  function sleep(ms) {
156
156
  return new Promise((resolve) => setTimeout(resolve, ms));
157
157
  }
158
- var BrainerceClient = class {
158
+ var _BrainerceClient = class _BrainerceClient {
159
159
  constructor(options) {
160
160
  this.customerToken = null;
161
161
  this.customerCartId = null;
@@ -174,6 +174,8 @@ var BrainerceClient = class {
174
174
  // GA4 stitch state (see `loadGoogleAnalytics()` in the Analytics section).
175
175
  this._ga4MeasurementId = null;
176
176
  this._ga4StitchPromise = null;
177
+ /** One warning per client, not per keystroke-driven address submit. */
178
+ this._warnedResolvedOnlyAddressFields = false;
177
179
  /** localStorage key for session cart reference (sessionToken + cartId) */
178
180
  this.SESSION_CART_KEY = "brainerce_session";
179
181
  /**
@@ -687,10 +689,19 @@ var BrainerceClient = class {
687
689
  * Set the customer authentication token (obtained from login/register).
688
690
  * Required for accessing customer-specific data in storefront mode.
689
691
  *
692
+ * This is a plain setter — it authenticates subsequent requests and nothing
693
+ * else. In particular it does NOT attach the shopper's existing guest cart to
694
+ * their account. Pair every sign-in with {@link syncCartOnLogin}, or the cart
695
+ * stays anonymous and every feature keyed on buyer identity degrades quietly:
696
+ * "first order only" discounts keep applying to returning customers,
697
+ * per-customer usage caps stop being enforced at cart time, and abandoned-cart
698
+ * recovery can't identify who to email.
699
+ *
690
700
  * @example
691
701
  * ```typescript
692
702
  * const auth = await client.loginCustomer('user@example.com', 'password');
693
703
  * client.setCustomerToken(auth.token);
704
+ * await client.syncCartOnLogin(); // claim the guest cart for this account
694
705
  *
695
706
  * // Now can access customer data
696
707
  * const profile = await client.getMyProfile();
@@ -1204,6 +1215,39 @@ var BrainerceClient = class {
1204
1215
  return dto;
1205
1216
  }
1206
1217
  }
1218
+ /**
1219
+ * Drop the fields `getAddressDetails()` returns that no address endpoint
1220
+ * accepts, so spreading its `address` straight into `setShippingAddress()`
1221
+ * / `setBillingAddress()` works instead of failing the whole request.
1222
+ *
1223
+ * The address endpoints validate against a strict allow-list: ONE unknown
1224
+ * property rejects the call with `400 "property lat should not exist"`, and
1225
+ * the shopper cannot check out at all. `lat`/`lng`/`formattedAddress` are
1226
+ * the only realistic way to hit that — they come out of this SDK's own
1227
+ * resolved-address shape, so this SDK cleans up after itself rather than
1228
+ * making every storefront remember to. Nothing else is stripped: a genuine
1229
+ * typo still reaches the server and still fails loudly.
1230
+ *
1231
+ * Coordinates are dropped rather than forwarded because zone matching picks
1232
+ * which shipping rate is offered and charged — the server resolves them
1233
+ * itself from `placeId`, and never takes them from the caller.
1234
+ */
1235
+ stripResolvedOnlyAddressFields(address) {
1236
+ if (!address || typeof address !== "object") return address;
1237
+ const present = _BrainerceClient.RESOLVED_ONLY_ADDRESS_FIELDS.filter(
1238
+ (field) => field in address
1239
+ );
1240
+ if (present.length === 0) return address;
1241
+ const cleaned = { ...address };
1242
+ for (const field of present) delete cleaned[field];
1243
+ if (!this._warnedResolvedOnlyAddressFields) {
1244
+ this._warnedResolvedOnlyAddressFields = true;
1245
+ console.warn(
1246
+ `BrainerceClient: dropped ${present.join("/")} from the address payload \u2014 the API does not accept coordinates from the client. Pass \`placeId\` (and \`placeSessionToken\`) instead so the server resolves them itself and matches map-drawn delivery zones against the exact location.`
1247
+ );
1248
+ }
1249
+ return cleaned;
1250
+ }
1207
1251
  // -------------------- Products --------------------
1208
1252
  /**
1209
1253
  * Get a list of products with pagination and filtering
@@ -2796,6 +2840,12 @@ var BrainerceClient = class {
2796
2840
  * if (params.get('oauth_success') === 'true' && params.get('auth_code')) {
2797
2841
  * const result = await client.exchangeOAuthCode(params.get('auth_code')!);
2798
2842
  * client.setCustomerToken(result.token);
2843
+ * // REQUIRED: setCustomerToken only stores the JWT — it does NOT attach the
2844
+ * // guest cart to the account. Without this call the cart stays anonymous,
2845
+ * // and anything keyed on the buyer's identity misbehaves: "first order
2846
+ * // only" discounts re-apply to returning customers, per-customer usage
2847
+ * // caps go unenforced, and abandoned-cart recovery can't reach them.
2848
+ * await client.syncCartOnLogin();
2799
2849
  * // result.customer, result.isNewCustomer, result.redirectUrl, ...
2800
2850
  * } else if (params.get('oauth_error')) {
2801
2851
  * // Failures land on this same page, on `redirectUrl` — never on the API
@@ -2848,6 +2898,11 @@ var BrainerceClient = class {
2848
2898
  *
2849
2899
  * @param authCode - The single-use code from the `?auth_code=` URL param.
2850
2900
  *
2901
+ * Always follow a successful exchange with `syncCartOnLogin()`. Storing the
2902
+ * token does not claim the guest cart, and an unclaimed cart has no buyer
2903
+ * identity — which silently breaks first-order discounts, per-customer usage
2904
+ * caps, and abandoned-cart recovery for everyone who signs in with OAuth.
2905
+ *
2851
2906
  * @example
2852
2907
  * ```typescript
2853
2908
  * const params = new URLSearchParams(window.location.search);
@@ -2856,6 +2911,7 @@ var BrainerceClient = class {
2856
2911
  * const { token, customer, isNewCustomer, redirectUrl } =
2857
2912
  * await client.exchangeOAuthCode(code);
2858
2913
  * client.setCustomerToken(token);
2914
+ * await client.syncCartOnLogin(); // attach the guest cart to the account
2859
2915
  * }
2860
2916
  * ```
2861
2917
  */
@@ -5035,6 +5091,11 @@ var BrainerceClient = class {
5035
5091
  * address text — which is materially less accurate and can place the
5036
5092
  * shopper in a neighbouring city's zone, or in none at all.
5037
5093
  *
5094
+ * Spreading `getAddressDetails().address` in here is safe: its `lat`, `lng`
5095
+ * and `formattedAddress` are dropped before the request goes out (the
5096
+ * endpoint rejects unknown properties outright, and coordinates are never
5097
+ * taken from the client — the server resolves them from `placeId`).
5098
+ *
5038
5099
  * @example
5039
5100
  * ```typescript
5040
5101
  * const { checkout, rates } = await client.setShippingAddress('checkout_123', {
@@ -5054,7 +5115,9 @@ var BrainerceClient = class {
5054
5115
  * ```
5055
5116
  */
5056
5117
  async setShippingAddress(checkoutId, address) {
5057
- const body = await this.withAnalyticsStitchIds(address);
5118
+ const body = await this.withAnalyticsStitchIds(
5119
+ this.stripResolvedOnlyAddressFields(address)
5120
+ );
5058
5121
  if (this.isVibeCodedMode()) {
5059
5122
  return this.vibeCodedRequest(
5060
5123
  "PATCH",
@@ -5367,24 +5430,25 @@ var BrainerceClient = class {
5367
5430
  * ```
5368
5431
  */
5369
5432
  async setBillingAddress(checkoutId, address) {
5433
+ const body = this.stripResolvedOnlyAddressFields(address);
5370
5434
  if (this.isVibeCodedMode()) {
5371
5435
  return this.vibeCodedRequest(
5372
5436
  "PATCH",
5373
5437
  `/checkout/${encodePathSegment(checkoutId)}/billing-address`,
5374
- address
5438
+ body
5375
5439
  );
5376
5440
  }
5377
5441
  if (this.storeId && !this.apiKey) {
5378
5442
  return this.storefrontRequest(
5379
5443
  "PATCH",
5380
5444
  `/checkout/${encodePathSegment(checkoutId)}/billing-address`,
5381
- address
5445
+ body
5382
5446
  );
5383
5447
  }
5384
5448
  return this.adminRequest(
5385
5449
  "PATCH",
5386
5450
  `/api/v1/checkout/${encodePathSegment(checkoutId)}/billing-address`,
5387
- address
5451
+ body
5388
5452
  );
5389
5453
  }
5390
5454
  /**
@@ -6350,7 +6414,7 @@ var BrainerceClient = class {
6350
6414
  const result = await this.vibeCodedRequest(
6351
6415
  "PATCH",
6352
6416
  `/checkout/${encodePathSegment(checkoutId)}/shipping-address`,
6353
- data.shippingAddress
6417
+ this.stripResolvedOnlyAddressFields(data.shippingAddress)
6354
6418
  );
6355
6419
  checkout = result.checkout;
6356
6420
  }
@@ -6358,7 +6422,7 @@ var BrainerceClient = class {
6358
6422
  checkout = await this.vibeCodedRequest(
6359
6423
  "PATCH",
6360
6424
  `/checkout/${encodePathSegment(checkoutId)}/billing-address`,
6361
- data.billingAddress
6425
+ this.stripResolvedOnlyAddressFields(data.billingAddress)
6362
6426
  );
6363
6427
  }
6364
6428
  if (!checkout) {
@@ -9199,6 +9263,17 @@ var BrainerceClient = class {
9199
9263
  );
9200
9264
  }
9201
9265
  };
9266
+ /**
9267
+ * Fields present on `getAddressDetails().address` that the address endpoints
9268
+ * do NOT accept — stripped by `stripResolvedOnlyAddressFields()` so a
9269
+ * `{ ...address }` spread doesn't 400 the whole checkout.
9270
+ */
9271
+ _BrainerceClient.RESOLVED_ONLY_ADDRESS_FIELDS = [
9272
+ "lat",
9273
+ "lng",
9274
+ "formattedAddress"
9275
+ ];
9276
+ var BrainerceClient = _BrainerceClient;
9202
9277
  var BrainerceError = class extends Error {
9203
9278
  constructor(message, statusCode, details) {
9204
9279
  super(message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainerce",
3
- "version": "1.52.0",
3
+ "version": "1.53.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",