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.mjs CHANGED
@@ -181,6 +181,41 @@ var BrainerceClient = class {
181
181
  this.onAuthError = options.onAuthError;
182
182
  this.hydrateSessionCart();
183
183
  }
184
+ // -------------------- Locale --------------------
185
+ /**
186
+ * Set the active locale for content translation.
187
+ * When set, all content endpoints (products, categories, brands) will return
188
+ * translated content for this locale, falling back to the store's default language.
189
+ *
190
+ * @param locale - Locale code (e.g., "he", "es", "fr") or undefined to clear
191
+ */
192
+ setLocale(locale) {
193
+ this.locale = locale;
194
+ }
195
+ /**
196
+ * Get the currently active locale.
197
+ */
198
+ getLocale() {
199
+ return this.locale;
200
+ }
201
+ /**
202
+ * Get the supported locales for this store.
203
+ * Fetches store info and returns the i18n config.
204
+ * Returns `[storeLanguage]` if multi-language is not enabled.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const locales = await client.getSupportedLocales();
209
+ * // ["en", "he", "es"]
210
+ * ```
211
+ */
212
+ async getSupportedLocales() {
213
+ const info = await this.getStoreInfo();
214
+ if (info.i18n?.enabled && info.i18n.supportedLocales.length > 0) {
215
+ return info.i18n.supportedLocales;
216
+ }
217
+ return [info.language];
218
+ }
184
219
  withGuards(result, type) {
185
220
  if (!isDevGuardsEnabled()) return result;
186
221
  if (result && typeof result.then === "function") {
@@ -520,6 +555,7 @@ var BrainerceClient = class {
520
555
  maxPrice: params?.maxPrice,
521
556
  sortBy: params?.sortBy,
522
557
  sortOrder: params?.sortOrder,
558
+ locale: params?.locale || this.locale,
523
559
  // Admin-only params
524
560
  type: params?.type
525
561
  };
@@ -550,12 +586,23 @@ var BrainerceClient = class {
550
586
  * Get a single product by ID
551
587
  * Works in vibe-coded, storefront (public), and admin mode
552
588
  */
553
- async getProduct(productId) {
589
+ async getProduct(productId, options) {
590
+ const localeParams = { locale: options?.locale || this.locale };
554
591
  if (this.isVibeCodedMode()) {
555
- return this.vibeCodedRequest("GET", `/products/${productId}`);
592
+ return this.vibeCodedRequest(
593
+ "GET",
594
+ `/products/${productId}`,
595
+ void 0,
596
+ localeParams
597
+ );
556
598
  }
557
599
  if (this.storeId && !this.apiKey) {
558
- return this.storefrontRequest("GET", `/products/${productId}`);
600
+ return this.storefrontRequest(
601
+ "GET",
602
+ `/products/${productId}`,
603
+ void 0,
604
+ localeParams
605
+ );
559
606
  }
560
607
  return this.adminRequest("GET", `/api/v1/products/${productId}`);
561
608
  }
@@ -569,12 +616,23 @@ var BrainerceClient = class {
569
616
  * const product = await client.getProductBySlug('awesome-product-name');
570
617
  * ```
571
618
  */
572
- async getProductBySlug(slug) {
619
+ async getProductBySlug(slug, options) {
620
+ const localeParams = { locale: options?.locale || this.locale };
573
621
  if (this.isVibeCodedMode()) {
574
- return this.vibeCodedRequest("GET", `/products/slug/${slug}`);
622
+ return this.vibeCodedRequest(
623
+ "GET",
624
+ `/products/slug/${slug}`,
625
+ void 0,
626
+ localeParams
627
+ );
575
628
  }
576
629
  if (this.storeId && !this.apiKey) {
577
- return this.storefrontRequest("GET", `/products/slug/${slug}`);
630
+ return this.storefrontRequest(
631
+ "GET",
632
+ `/products/slug/${slug}`,
633
+ void 0,
634
+ localeParams
635
+ );
578
636
  }
579
637
  return this.adminRequest("GET", `/api/v1/products/by-slug/${slug}`);
580
638
  }
@@ -588,9 +646,10 @@ var BrainerceClient = class {
588
646
  * // categories is a tree structure with children
589
647
  * ```
590
648
  */
591
- async getCategories() {
649
+ async getCategories(options) {
650
+ const localeParams = { locale: options?.locale || this.locale };
592
651
  if (this.isVibeCodedMode()) {
593
- return this.vibeCodedRequest("GET", "/categories");
652
+ return this.vibeCodedRequest("GET", "/categories", void 0, localeParams);
594
653
  }
595
654
  throw new BrainerceError("getCategories is only available in vibe-coded mode", 400);
596
655
  }
@@ -604,9 +663,10 @@ var BrainerceClient = class {
604
663
  * // Use brand IDs in getProducts({ brands: ['brand_id'] })
605
664
  * ```
606
665
  */
607
- async getBrands() {
666
+ async getBrands(options) {
667
+ const localeParams = { locale: options?.locale || this.locale };
608
668
  if (this.isVibeCodedMode()) {
609
- return this.vibeCodedRequest("GET", "/brands");
669
+ return this.vibeCodedRequest("GET", "/brands", void 0, localeParams);
610
670
  }
611
671
  throw new BrainerceError("getBrands is only available in vibe-coded mode", 400);
612
672
  }
@@ -622,7 +682,9 @@ var BrainerceClient = class {
622
682
  */
623
683
  async getTags() {
624
684
  if (this.isVibeCodedMode()) {
625
- return this.vibeCodedRequest("GET", "/tags");
685
+ return this.vibeCodedRequest("GET", "/tags", void 0, {
686
+ locale: this.locale
687
+ });
626
688
  }
627
689
  throw new BrainerceError("getTags is only available in vibe-coded mode", 400);
628
690
  }
@@ -646,11 +708,27 @@ var BrainerceClient = class {
646
708
  * const suggestions = await client.getSearchSuggestions('dress', 3);
647
709
  * ```
648
710
  */
711
+ /**
712
+ * Get locale alternates for a product (for SEO hreflang tags).
713
+ * Returns the slug for each available locale.
714
+ *
715
+ * @example
716
+ * ```typescript
717
+ * const { alternates } = await client.getProductAlternates('product_id');
718
+ * // alternates = [{ locale: "en", slug: "running-shoes" }, { locale: "he", slug: "נעלי-ריצה" }]
719
+ * ```
720
+ */
721
+ async getProductAlternates(productId) {
722
+ if (this.storeId && !this.apiKey) {
723
+ return this.storefrontRequest("GET", `/products/${productId}/alternates`);
724
+ }
725
+ throw new BrainerceError("getProductAlternates is only available in storefront mode", 400);
726
+ }
649
727
  async getSearchSuggestions(query, limit) {
650
728
  if (!query || query.trim().length === 0) {
651
729
  return { products: [], categories: [] };
652
730
  }
653
- const queryParams = { q: query, limit };
731
+ const queryParams = { q: query, limit, locale: this.locale };
654
732
  if (this.isVibeCodedMode()) {
655
733
  return this.vibeCodedRequest(
656
734
  "GET",
@@ -2432,14 +2510,16 @@ var BrainerceClient = class {
2432
2510
  *
2433
2511
  * @param cartId - Cart ID
2434
2512
  * @param bumpConfigId - Order bump config ID
2513
+ * @param variantId - Optional variant ID (required when bump product has variants and no admin-locked variant)
2435
2514
  * @returns Updated cart
2436
2515
  */
2437
- async addOrderBump(cartId, bumpConfigId) {
2516
+ async addOrderBump(cartId, bumpConfigId, variantId) {
2517
+ const body = { bumpConfigId, ...variantId && { variantId } };
2438
2518
  if (this.isVibeCodedMode()) {
2439
- return this.vibeCodedRequest("POST", `/cart/${cartId}/bump`, { bumpConfigId });
2519
+ return this.vibeCodedRequest("POST", `/cart/${cartId}/bump`, body);
2440
2520
  }
2441
2521
  if (this.storeId && !this.apiKey) {
2442
- return this.storefrontRequest("POST", `/cart/${cartId}/bump`, { bumpConfigId });
2522
+ return this.storefrontRequest("POST", `/cart/${cartId}/bump`, body);
2443
2523
  }
2444
2524
  throw new BrainerceError("addOrderBump() requires vibe-coded or storefront mode", 400);
2445
2525
  }
@@ -2464,20 +2544,25 @@ var BrainerceClient = class {
2464
2544
  *
2465
2545
  * @param cartId - Cart ID
2466
2546
  * @param bundleOfferId - Bundle offer ID
2547
+ * @param variantId - Optional variant ID (required when bundle product has variants and no admin-locked variant)
2467
2548
  * @returns Updated cart
2468
2549
  *
2469
2550
  * @example
2470
2551
  * ```typescript
2471
2552
  * const { bundles } = await client.getCartBundles('cart_123');
2553
+ * // Simple product or locked variant:
2472
2554
  * const cart = await client.addBundleToCart('cart_123', bundles[0].id);
2555
+ * // Product with variants (customer selects):
2556
+ * const cart = await client.addBundleToCart('cart_123', bundles[0].id, selectedVariantId);
2473
2557
  * ```
2474
2558
  */
2475
- async addBundleToCart(cartId, bundleOfferId) {
2559
+ async addBundleToCart(cartId, bundleOfferId, variantId) {
2560
+ const body = { bundleOfferId, ...variantId && { variantId } };
2476
2561
  if (this.isVibeCodedMode()) {
2477
- return this.vibeCodedRequest("POST", `/cart/${cartId}/bundle`, { bundleOfferId });
2562
+ return this.vibeCodedRequest("POST", `/cart/${cartId}/bundle`, body);
2478
2563
  }
2479
2564
  if (this.storeId && !this.apiKey) {
2480
- return this.storefrontRequest("POST", `/cart/${cartId}/bundle`, { bundleOfferId });
2565
+ return this.storefrontRequest("POST", `/cart/${cartId}/bundle`, body);
2481
2566
  }
2482
2567
  throw new BrainerceError("addBundleToCart() requires vibe-coded or storefront mode", 400);
2483
2568
  }
@@ -3399,6 +3484,67 @@ var BrainerceClient = class {
3399
3484
  this.clearActiveCheckoutStorage();
3400
3485
  return result;
3401
3486
  }
3487
+ // -------------------- Checkout Custom Fields --------------------
3488
+ /**
3489
+ * Get applicable custom field definitions for a checkout.
3490
+ * Returns fields filtered by visibility conditions (delivery type, products in cart).
3491
+ * Use these to render dynamic input fields in the checkout flow.
3492
+ */
3493
+ async getCheckoutCustomFields(checkoutId) {
3494
+ if (this.isVibeCodedMode()) {
3495
+ return this.vibeCodedRequest(
3496
+ "GET",
3497
+ `/checkout/${checkoutId}/custom-fields`
3498
+ );
3499
+ }
3500
+ if (this.storeId && !this.apiKey) {
3501
+ return this.storefrontRequest(
3502
+ "GET",
3503
+ `/checkout/${checkoutId}/custom-fields`
3504
+ );
3505
+ }
3506
+ return this.adminRequest(
3507
+ "GET",
3508
+ `/api/v1/checkouts/${checkoutId}/custom-fields`
3509
+ );
3510
+ }
3511
+ /**
3512
+ * Set checkout custom field values and recalculate surcharges.
3513
+ * The checkout total is automatically updated to include surcharges.
3514
+ *
3515
+ * @example
3516
+ * ```typescript
3517
+ * const checkout = await client.setCheckoutCustomFields(checkoutId, {
3518
+ * gift_wrapping: 'premium', // SELECT field
3519
+ * floor_number: 5, // NUMBER field
3520
+ * installation: true, // BOOLEAN field
3521
+ * });
3522
+ * // checkout.surchargeAmount reflects the new surcharges
3523
+ * // checkout.total is recalculated
3524
+ * ```
3525
+ */
3526
+ async setCheckoutCustomFields(checkoutId, fields) {
3527
+ const data = { fields };
3528
+ if (this.isVibeCodedMode()) {
3529
+ return this.vibeCodedRequest(
3530
+ "PATCH",
3531
+ `/checkout/${checkoutId}/custom-fields`,
3532
+ data
3533
+ );
3534
+ }
3535
+ if (this.storeId && !this.apiKey) {
3536
+ return this.storefrontRequest(
3537
+ "PATCH",
3538
+ `/checkout/${checkoutId}/custom-fields`,
3539
+ data
3540
+ );
3541
+ }
3542
+ return this.adminRequest(
3543
+ "PATCH",
3544
+ `/api/v1/checkouts/${checkoutId}/custom-fields`,
3545
+ data
3546
+ );
3547
+ }
3402
3548
  /**
3403
3549
  * Delete a checkout session. Releases inventory reservations and removes
3404
3550
  * payment records not yet linked to an order.
@@ -5442,6 +5588,53 @@ var BrainerceClient = class {
5442
5588
  `/api/v1/products/${productId}/metafields/${definitionId}`
5443
5589
  );
5444
5590
  }
5591
+ // -------------------- Product Customization Fields (Admin) --------------------
5592
+ /**
5593
+ * Get customization fields assigned to a product.
5594
+ * Requires Admin mode (apiKey).
5595
+ */
5596
+ async getProductCustomizationFields(productId) {
5597
+ return this.adminRequest(
5598
+ "GET",
5599
+ `/api/v1/metafield-definitions/products/${productId}/customization-fields`
5600
+ );
5601
+ }
5602
+ /**
5603
+ * Set customization fields for a product (replaces all existing assignments).
5604
+ * Only definitions marked as `isCustomerInput: true` can be assigned.
5605
+ * Requires Admin mode (apiKey).
5606
+ */
5607
+ async setProductCustomizationFields(productId, definitionIds) {
5608
+ return this.adminRequest(
5609
+ "PATCH",
5610
+ `/api/v1/metafield-definitions/products/${productId}/customization-fields`,
5611
+ { definitionIds }
5612
+ );
5613
+ }
5614
+ /**
5615
+ * Upload a file for product customization (e.g., customer logo for printing).
5616
+ * Available in storefront and vibe-coded modes.
5617
+ * Returns the uploaded file URL and key.
5618
+ */
5619
+ async uploadCustomizationFile(file) {
5620
+ const formData = new FormData();
5621
+ formData.append("file", file);
5622
+ if (this.isVibeCodedMode()) {
5623
+ return this.vibeCodedRequest(
5624
+ "POST",
5625
+ "/customization-upload",
5626
+ formData
5627
+ );
5628
+ }
5629
+ if (this.storeId && !this.apiKey) {
5630
+ return this.storefrontRequest(
5631
+ "POST",
5632
+ "/customization-upload",
5633
+ formData
5634
+ );
5635
+ }
5636
+ throw new Error("uploadCustomizationFile requires storefront or vibe-coded mode");
5637
+ }
5445
5638
  // -------------------- Team Management (Admin) - DEPRECATED --------------------
5446
5639
  // These account-level methods are deprecated. Use Store Team Management methods instead.
5447
5640
  /**
@@ -6005,14 +6198,15 @@ function getVariantOptions(variant) {
6005
6198
  if (!variant?.attributes) return [];
6006
6199
  const internalKeys = /* @__PURE__ */ new Set([
6007
6200
  "id",
6008
- "shopifyVariantId",
6009
- "shopify_variant_id",
6010
- "shopifyInventoryItemId",
6011
- "woocommerceVariantId",
6012
- "metaVariantId",
6013
- "externalId"
6201
+ "externalId",
6202
+ "externalVariantId",
6203
+ "externalInventoryItemId",
6204
+ "platformVariantId"
6014
6205
  ]);
6015
- return Object.entries(variant.attributes).filter(([key, value]) => typeof value === "string" && !internalKeys.has(key)).map(([name, value]) => ({ name, value }));
6206
+ const isPlatformKey = (key) => /^(shopify|woocommerce|tiktok|meta)[A-Z_]/.test(key) || key.includes("VariantId") || key.includes("InventoryItemId");
6207
+ return Object.entries(variant.attributes).filter(
6208
+ ([key, value]) => typeof value === "string" && !internalKeys.has(key) && !isPlatformKey(key)
6209
+ ).map(([name, value]) => ({ name, value }));
6016
6210
  }
6017
6211
  function getProductSwatches(product) {
6018
6212
  if (!product?.productAttributeOptions) return [];
@@ -6062,6 +6256,9 @@ function getProductMetafieldValue(product, key) {
6062
6256
  function getProductMetafieldsByType(product, type) {
6063
6257
  return product.metafields?.filter((m) => m.type === type) || [];
6064
6258
  }
6259
+ function getProductCustomizationFields(product) {
6260
+ return product.customizationFields ?? [];
6261
+ }
6065
6262
  function isCouponApplicableToProduct(coupon, productId) {
6066
6263
  const getIds = (refs) => refs?.map((ref) => ref.id) ?? [];
6067
6264
  const applicableProductIds = getIds(coupon.applicableProducts);
@@ -6082,6 +6279,7 @@ export {
6082
6279
  getCartTotals,
6083
6280
  getDescriptionContent,
6084
6281
  formatPrice as getPriceDisplay,
6282
+ getProductCustomizationFields,
6085
6283
  getProductMetafield,
6086
6284
  getProductMetafieldValue,
6087
6285
  getProductMetafieldsByType,
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "brainerce",
3
- "version": "1.15.0",
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
- "main": "dist/index.js",
6
- "module": "dist/index.mjs",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "require": "./dist/index.js",
12
- "import": "./dist/index.mjs"
13
- }
14
- },
15
- "files": [
16
- "dist",
17
- "README.md"
18
- ],
19
- "scripts": {
20
- "build": "tsup src/index.ts --format cjs,esm --dts",
21
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
- "lint": "eslint \"src/**/*.ts\"",
23
- "test": "vitest run",
24
- "test:watch": "vitest",
25
- "prepublishOnly": "pnpm build"
26
- },
27
- "keywords": [
28
- "brainerce",
29
- "e-commerce",
30
- "ecommerce",
31
- "sdk",
32
- "vibe-coding",
33
- "vibe-coded",
34
- "ai-commerce",
35
- "storefront",
36
- "headless-commerce",
37
- "multi-platform",
38
- "shopify",
39
- "tiktok",
40
- "cursor",
41
- "lovable",
42
- "v0",
43
- "cart",
44
- "checkout",
45
- "products",
46
- "sync"
47
- ],
48
- "author": "Brainerce",
49
- "license": "MIT",
50
- "repository": {
51
- "type": "git",
52
- "url": "https://github.com/brainerce/brainerce.git",
53
- "directory": "packages/sdk"
54
- },
55
- "homepage": "https://brainerce.com",
56
- "bugs": {
57
- "url": "https://github.com/brainerce/brainerce/issues"
58
- },
59
- "devDependencies": {
60
- "@types/node": "^25.0.3",
61
- "@typescript-eslint/eslint-plugin": "^8.50.1",
62
- "@typescript-eslint/parser": "^8.50.1",
63
- "eslint": "^9.39.2",
64
- "tsup": "^8.0.0",
65
- "typescript": "^5.3.0",
66
- "vitest": "^1.0.0"
67
- },
68
- "peerDependencies": {
69
- "typescript": ">=4.7.0"
70
- },
71
- "peerDependenciesMeta": {
72
- "typescript": {
73
- "optional": true
74
- }
75
- }
76
- }
1
+ {
2
+ "name": "brainerce",
3
+ "version": "1.16.0",
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
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts",
21
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
+ "lint": "eslint \"src/**/*.ts\"",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "prepublishOnly": "pnpm build"
26
+ },
27
+ "keywords": [
28
+ "brainerce",
29
+ "e-commerce",
30
+ "ecommerce",
31
+ "sdk",
32
+ "vibe-coding",
33
+ "vibe-coded",
34
+ "ai-commerce",
35
+ "storefront",
36
+ "headless-commerce",
37
+ "multi-platform",
38
+ "shopify",
39
+ "tiktok",
40
+ "cursor",
41
+ "lovable",
42
+ "v0",
43
+ "cart",
44
+ "checkout",
45
+ "products",
46
+ "sync"
47
+ ],
48
+ "author": "Brainerce",
49
+ "license": "MIT",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/brainerce/brainerce.git",
53
+ "directory": "packages/sdk"
54
+ },
55
+ "homepage": "https://brainerce.com",
56
+ "bugs": {
57
+ "url": "https://github.com/brainerce/brainerce/issues"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^25.0.3",
61
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
62
+ "@typescript-eslint/parser": "^8.50.1",
63
+ "eslint": "^9.39.2",
64
+ "tsup": "^8.0.0",
65
+ "typescript": "^5.3.0",
66
+ "vitest": "^1.0.0"
67
+ },
68
+ "peerDependencies": {
69
+ "typescript": ">=4.7.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "typescript": {
73
+ "optional": true
74
+ }
75
+ }
76
+ }