brainerce 1.15.0 → 1.16.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.js CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  getCartTotals: () => getCartTotals,
42
42
  getDescriptionContent: () => getDescriptionContent,
43
43
  getPriceDisplay: () => formatPrice,
44
+ getProductCustomizationFields: () => getProductCustomizationFields,
44
45
  getProductMetafield: () => getProductMetafield,
45
46
  getProductMetafieldValue: () => getProductMetafieldValue,
46
47
  getProductMetafieldsByType: () => getProductMetafieldsByType,
@@ -241,6 +242,41 @@ var BrainerceClient = class {
241
242
  this.onAuthError = options.onAuthError;
242
243
  this.hydrateSessionCart();
243
244
  }
245
+ // -------------------- Locale --------------------
246
+ /**
247
+ * Set the active locale for content translation.
248
+ * When set, all content endpoints (products, categories, brands) will return
249
+ * translated content for this locale, falling back to the store's default language.
250
+ *
251
+ * @param locale - Locale code (e.g., "he", "es", "fr") or undefined to clear
252
+ */
253
+ setLocale(locale) {
254
+ this.locale = locale;
255
+ }
256
+ /**
257
+ * Get the currently active locale.
258
+ */
259
+ getLocale() {
260
+ return this.locale;
261
+ }
262
+ /**
263
+ * Get the supported locales for this store.
264
+ * Fetches store info and returns the i18n config.
265
+ * Returns `[storeLanguage]` if multi-language is not enabled.
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * const locales = await client.getSupportedLocales();
270
+ * // ["en", "he", "es"]
271
+ * ```
272
+ */
273
+ async getSupportedLocales() {
274
+ const info = await this.getStoreInfo();
275
+ if (info.i18n?.enabled && info.i18n.supportedLocales.length > 0) {
276
+ return info.i18n.supportedLocales;
277
+ }
278
+ return [info.language];
279
+ }
244
280
  withGuards(result, type) {
245
281
  if (!isDevGuardsEnabled()) return result;
246
282
  if (result && typeof result.then === "function") {
@@ -580,6 +616,7 @@ var BrainerceClient = class {
580
616
  maxPrice: params?.maxPrice,
581
617
  sortBy: params?.sortBy,
582
618
  sortOrder: params?.sortOrder,
619
+ locale: params?.locale || this.locale,
583
620
  // Admin-only params
584
621
  type: params?.type
585
622
  };
@@ -610,12 +647,23 @@ var BrainerceClient = class {
610
647
  * Get a single product by ID
611
648
  * Works in vibe-coded, storefront (public), and admin mode
612
649
  */
613
- async getProduct(productId) {
650
+ async getProduct(productId, options) {
651
+ const localeParams = { locale: options?.locale || this.locale };
614
652
  if (this.isVibeCodedMode()) {
615
- return this.vibeCodedRequest("GET", `/products/${productId}`);
653
+ return this.vibeCodedRequest(
654
+ "GET",
655
+ `/products/${productId}`,
656
+ void 0,
657
+ localeParams
658
+ );
616
659
  }
617
660
  if (this.storeId && !this.apiKey) {
618
- return this.storefrontRequest("GET", `/products/${productId}`);
661
+ return this.storefrontRequest(
662
+ "GET",
663
+ `/products/${productId}`,
664
+ void 0,
665
+ localeParams
666
+ );
619
667
  }
620
668
  return this.adminRequest("GET", `/api/v1/products/${productId}`);
621
669
  }
@@ -629,12 +677,23 @@ var BrainerceClient = class {
629
677
  * const product = await client.getProductBySlug('awesome-product-name');
630
678
  * ```
631
679
  */
632
- async getProductBySlug(slug) {
680
+ async getProductBySlug(slug, options) {
681
+ const localeParams = { locale: options?.locale || this.locale };
633
682
  if (this.isVibeCodedMode()) {
634
- return this.vibeCodedRequest("GET", `/products/slug/${slug}`);
683
+ return this.vibeCodedRequest(
684
+ "GET",
685
+ `/products/slug/${slug}`,
686
+ void 0,
687
+ localeParams
688
+ );
635
689
  }
636
690
  if (this.storeId && !this.apiKey) {
637
- return this.storefrontRequest("GET", `/products/slug/${slug}`);
691
+ return this.storefrontRequest(
692
+ "GET",
693
+ `/products/slug/${slug}`,
694
+ void 0,
695
+ localeParams
696
+ );
638
697
  }
639
698
  return this.adminRequest("GET", `/api/v1/products/by-slug/${slug}`);
640
699
  }
@@ -648,9 +707,10 @@ var BrainerceClient = class {
648
707
  * // categories is a tree structure with children
649
708
  * ```
650
709
  */
651
- async getCategories() {
710
+ async getCategories(options) {
711
+ const localeParams = { locale: options?.locale || this.locale };
652
712
  if (this.isVibeCodedMode()) {
653
- return this.vibeCodedRequest("GET", "/categories");
713
+ return this.vibeCodedRequest("GET", "/categories", void 0, localeParams);
654
714
  }
655
715
  throw new BrainerceError("getCategories is only available in vibe-coded mode", 400);
656
716
  }
@@ -664,9 +724,10 @@ var BrainerceClient = class {
664
724
  * // Use brand IDs in getProducts({ brands: ['brand_id'] })
665
725
  * ```
666
726
  */
667
- async getBrands() {
727
+ async getBrands(options) {
728
+ const localeParams = { locale: options?.locale || this.locale };
668
729
  if (this.isVibeCodedMode()) {
669
- return this.vibeCodedRequest("GET", "/brands");
730
+ return this.vibeCodedRequest("GET", "/brands", void 0, localeParams);
670
731
  }
671
732
  throw new BrainerceError("getBrands is only available in vibe-coded mode", 400);
672
733
  }
@@ -682,7 +743,9 @@ var BrainerceClient = class {
682
743
  */
683
744
  async getTags() {
684
745
  if (this.isVibeCodedMode()) {
685
- return this.vibeCodedRequest("GET", "/tags");
746
+ return this.vibeCodedRequest("GET", "/tags", void 0, {
747
+ locale: this.locale
748
+ });
686
749
  }
687
750
  throw new BrainerceError("getTags is only available in vibe-coded mode", 400);
688
751
  }
@@ -706,11 +769,27 @@ var BrainerceClient = class {
706
769
  * const suggestions = await client.getSearchSuggestions('dress', 3);
707
770
  * ```
708
771
  */
772
+ /**
773
+ * Get locale alternates for a product (for SEO hreflang tags).
774
+ * Returns the slug for each available locale.
775
+ *
776
+ * @example
777
+ * ```typescript
778
+ * const { alternates } = await client.getProductAlternates('product_id');
779
+ * // alternates = [{ locale: "en", slug: "running-shoes" }, { locale: "he", slug: "נעלי-ריצה" }]
780
+ * ```
781
+ */
782
+ async getProductAlternates(productId) {
783
+ if (this.storeId && !this.apiKey) {
784
+ return this.storefrontRequest("GET", `/products/${productId}/alternates`);
785
+ }
786
+ throw new BrainerceError("getProductAlternates is only available in storefront mode", 400);
787
+ }
709
788
  async getSearchSuggestions(query, limit) {
710
789
  if (!query || query.trim().length === 0) {
711
790
  return { products: [], categories: [] };
712
791
  }
713
- const queryParams = { q: query, limit };
792
+ const queryParams = { q: query, limit, locale: this.locale };
714
793
  if (this.isVibeCodedMode()) {
715
794
  return this.vibeCodedRequest(
716
795
  "GET",
@@ -2492,14 +2571,16 @@ var BrainerceClient = class {
2492
2571
  *
2493
2572
  * @param cartId - Cart ID
2494
2573
  * @param bumpConfigId - Order bump config ID
2574
+ * @param variantId - Optional variant ID (required when bump product has variants and no admin-locked variant)
2495
2575
  * @returns Updated cart
2496
2576
  */
2497
- async addOrderBump(cartId, bumpConfigId) {
2577
+ async addOrderBump(cartId, bumpConfigId, variantId) {
2578
+ const body = { bumpConfigId, ...variantId && { variantId } };
2498
2579
  if (this.isVibeCodedMode()) {
2499
- return this.vibeCodedRequest("POST", `/cart/${cartId}/bump`, { bumpConfigId });
2580
+ return this.vibeCodedRequest("POST", `/cart/${cartId}/bump`, body);
2500
2581
  }
2501
2582
  if (this.storeId && !this.apiKey) {
2502
- return this.storefrontRequest("POST", `/cart/${cartId}/bump`, { bumpConfigId });
2583
+ return this.storefrontRequest("POST", `/cart/${cartId}/bump`, body);
2503
2584
  }
2504
2585
  throw new BrainerceError("addOrderBump() requires vibe-coded or storefront mode", 400);
2505
2586
  }
@@ -2524,20 +2605,25 @@ var BrainerceClient = class {
2524
2605
  *
2525
2606
  * @param cartId - Cart ID
2526
2607
  * @param bundleOfferId - Bundle offer ID
2608
+ * @param variantId - Optional variant ID (required when bundle product has variants and no admin-locked variant)
2527
2609
  * @returns Updated cart
2528
2610
  *
2529
2611
  * @example
2530
2612
  * ```typescript
2531
2613
  * const { bundles } = await client.getCartBundles('cart_123');
2614
+ * // Simple product or locked variant:
2532
2615
  * const cart = await client.addBundleToCart('cart_123', bundles[0].id);
2616
+ * // Product with variants (customer selects):
2617
+ * const cart = await client.addBundleToCart('cart_123', bundles[0].id, selectedVariantId);
2533
2618
  * ```
2534
2619
  */
2535
- async addBundleToCart(cartId, bundleOfferId) {
2620
+ async addBundleToCart(cartId, bundleOfferId, variantId) {
2621
+ const body = { bundleOfferId, ...variantId && { variantId } };
2536
2622
  if (this.isVibeCodedMode()) {
2537
- return this.vibeCodedRequest("POST", `/cart/${cartId}/bundle`, { bundleOfferId });
2623
+ return this.vibeCodedRequest("POST", `/cart/${cartId}/bundle`, body);
2538
2624
  }
2539
2625
  if (this.storeId && !this.apiKey) {
2540
- return this.storefrontRequest("POST", `/cart/${cartId}/bundle`, { bundleOfferId });
2626
+ return this.storefrontRequest("POST", `/cart/${cartId}/bundle`, body);
2541
2627
  }
2542
2628
  throw new BrainerceError("addBundleToCart() requires vibe-coded or storefront mode", 400);
2543
2629
  }
@@ -3459,6 +3545,67 @@ var BrainerceClient = class {
3459
3545
  this.clearActiveCheckoutStorage();
3460
3546
  return result;
3461
3547
  }
3548
+ // -------------------- Checkout Custom Fields --------------------
3549
+ /**
3550
+ * Get applicable custom field definitions for a checkout.
3551
+ * Returns fields filtered by visibility conditions (delivery type, products in cart).
3552
+ * Use these to render dynamic input fields in the checkout flow.
3553
+ */
3554
+ async getCheckoutCustomFields(checkoutId) {
3555
+ if (this.isVibeCodedMode()) {
3556
+ return this.vibeCodedRequest(
3557
+ "GET",
3558
+ `/checkout/${checkoutId}/custom-fields`
3559
+ );
3560
+ }
3561
+ if (this.storeId && !this.apiKey) {
3562
+ return this.storefrontRequest(
3563
+ "GET",
3564
+ `/checkout/${checkoutId}/custom-fields`
3565
+ );
3566
+ }
3567
+ return this.adminRequest(
3568
+ "GET",
3569
+ `/api/v1/checkouts/${checkoutId}/custom-fields`
3570
+ );
3571
+ }
3572
+ /**
3573
+ * Set checkout custom field values and recalculate surcharges.
3574
+ * The checkout total is automatically updated to include surcharges.
3575
+ *
3576
+ * @example
3577
+ * ```typescript
3578
+ * const checkout = await client.setCheckoutCustomFields(checkoutId, {
3579
+ * gift_wrapping: 'premium', // SELECT field
3580
+ * floor_number: 5, // NUMBER field
3581
+ * installation: true, // BOOLEAN field
3582
+ * });
3583
+ * // checkout.surchargeAmount reflects the new surcharges
3584
+ * // checkout.total is recalculated
3585
+ * ```
3586
+ */
3587
+ async setCheckoutCustomFields(checkoutId, fields) {
3588
+ const data = { fields };
3589
+ if (this.isVibeCodedMode()) {
3590
+ return this.vibeCodedRequest(
3591
+ "PATCH",
3592
+ `/checkout/${checkoutId}/custom-fields`,
3593
+ data
3594
+ );
3595
+ }
3596
+ if (this.storeId && !this.apiKey) {
3597
+ return this.storefrontRequest(
3598
+ "PATCH",
3599
+ `/checkout/${checkoutId}/custom-fields`,
3600
+ data
3601
+ );
3602
+ }
3603
+ return this.adminRequest(
3604
+ "PATCH",
3605
+ `/api/v1/checkouts/${checkoutId}/custom-fields`,
3606
+ data
3607
+ );
3608
+ }
3462
3609
  /**
3463
3610
  * Delete a checkout session. Releases inventory reservations and removes
3464
3611
  * payment records not yet linked to an order.
@@ -5502,6 +5649,53 @@ var BrainerceClient = class {
5502
5649
  `/api/v1/products/${productId}/metafields/${definitionId}`
5503
5650
  );
5504
5651
  }
5652
+ // -------------------- Product Customization Fields (Admin) --------------------
5653
+ /**
5654
+ * Get customization fields assigned to a product.
5655
+ * Requires Admin mode (apiKey).
5656
+ */
5657
+ async getProductCustomizationFields(productId) {
5658
+ return this.adminRequest(
5659
+ "GET",
5660
+ `/api/v1/metafield-definitions/products/${productId}/customization-fields`
5661
+ );
5662
+ }
5663
+ /**
5664
+ * Set customization fields for a product (replaces all existing assignments).
5665
+ * Only definitions marked as `isCustomerInput: true` can be assigned.
5666
+ * Requires Admin mode (apiKey).
5667
+ */
5668
+ async setProductCustomizationFields(productId, definitionIds) {
5669
+ return this.adminRequest(
5670
+ "PATCH",
5671
+ `/api/v1/metafield-definitions/products/${productId}/customization-fields`,
5672
+ { definitionIds }
5673
+ );
5674
+ }
5675
+ /**
5676
+ * Upload a file for product customization (e.g., customer logo for printing).
5677
+ * Available in storefront and vibe-coded modes.
5678
+ * Returns the uploaded file URL and key.
5679
+ */
5680
+ async uploadCustomizationFile(file) {
5681
+ const formData = new FormData();
5682
+ formData.append("file", file);
5683
+ if (this.isVibeCodedMode()) {
5684
+ return this.vibeCodedRequest(
5685
+ "POST",
5686
+ "/customization-upload",
5687
+ formData
5688
+ );
5689
+ }
5690
+ if (this.storeId && !this.apiKey) {
5691
+ return this.storefrontRequest(
5692
+ "POST",
5693
+ "/customization-upload",
5694
+ formData
5695
+ );
5696
+ }
5697
+ throw new Error("uploadCustomizationFile requires storefront or vibe-coded mode");
5698
+ }
5505
5699
  // -------------------- Team Management (Admin) - DEPRECATED --------------------
5506
5700
  // These account-level methods are deprecated. Use Store Team Management methods instead.
5507
5701
  /**
@@ -6065,14 +6259,15 @@ function getVariantOptions(variant) {
6065
6259
  if (!variant?.attributes) return [];
6066
6260
  const internalKeys = /* @__PURE__ */ new Set([
6067
6261
  "id",
6068
- "shopifyVariantId",
6069
- "shopify_variant_id",
6070
- "shopifyInventoryItemId",
6071
- "woocommerceVariantId",
6072
- "metaVariantId",
6073
- "externalId"
6262
+ "externalId",
6263
+ "externalVariantId",
6264
+ "externalInventoryItemId",
6265
+ "platformVariantId"
6074
6266
  ]);
6075
- return Object.entries(variant.attributes).filter(([key, value]) => typeof value === "string" && !internalKeys.has(key)).map(([name, value]) => ({ name, value }));
6267
+ const isPlatformKey = (key) => /^(shopify|woocommerce|tiktok|meta)[A-Z_]/.test(key) || key.includes("VariantId") || key.includes("InventoryItemId");
6268
+ return Object.entries(variant.attributes).filter(
6269
+ ([key, value]) => typeof value === "string" && !internalKeys.has(key) && !isPlatformKey(key)
6270
+ ).map(([name, value]) => ({ name, value }));
6076
6271
  }
6077
6272
  function getProductSwatches(product) {
6078
6273
  if (!product?.productAttributeOptions) return [];
@@ -6122,6 +6317,9 @@ function getProductMetafieldValue(product, key) {
6122
6317
  function getProductMetafieldsByType(product, type) {
6123
6318
  return product.metafields?.filter((m) => m.type === type) || [];
6124
6319
  }
6320
+ function getProductCustomizationFields(product) {
6321
+ return product.customizationFields ?? [];
6322
+ }
6125
6323
  function isCouponApplicableToProduct(coupon, productId) {
6126
6324
  const getIds = (refs) => refs?.map((ref) => ref.id) ?? [];
6127
6325
  const applicableProductIds = getIds(coupon.applicableProducts);
@@ -6143,6 +6341,7 @@ function isCouponApplicableToProduct(coupon, productId) {
6143
6341
  getCartTotals,
6144
6342
  getDescriptionContent,
6145
6343
  getPriceDisplay,
6344
+ getProductCustomizationFields,
6146
6345
  getProductMetafield,
6147
6346
  getProductMetafieldValue,
6148
6347
  getProductMetafieldsByType,