brainerce 1.17.0 → 1.19.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
@@ -378,11 +378,14 @@ var BrainerceClient = class {
378
378
  const controller = new AbortController();
379
379
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
380
380
  try {
381
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
381
382
  const headers = {
382
- "Content-Type": "application/json",
383
383
  "X-SDK-Version": SDK_VERSION,
384
384
  "ngrok-skip-browser-warning": "true"
385
385
  };
386
+ if (!isFormData) {
387
+ headers["Content-Type"] = "application/json";
388
+ }
386
389
  if (this.origin) {
387
390
  headers["Origin"] = this.origin;
388
391
  }
@@ -401,7 +404,7 @@ var BrainerceClient = class {
401
404
  const response = await fetch(url.toString(), {
402
405
  method,
403
406
  headers,
404
- body: body ? JSON.stringify(body) : void 0,
407
+ body: body ? isFormData ? body : JSON.stringify(body) : void 0,
405
408
  signal: controller.signal
406
409
  });
407
410
  clearTimeout(timeoutId);
@@ -455,11 +458,14 @@ var BrainerceClient = class {
455
458
  const controller = new AbortController();
456
459
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
457
460
  try {
461
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
458
462
  const headers = {
459
- "Content-Type": "application/json",
460
463
  "X-SDK-Version": SDK_VERSION,
461
464
  "ngrok-skip-browser-warning": "true"
462
465
  };
466
+ if (!isFormData) {
467
+ headers["Content-Type"] = "application/json";
468
+ }
463
469
  if (this.origin) {
464
470
  headers["Origin"] = this.origin;
465
471
  }
@@ -475,7 +481,7 @@ var BrainerceClient = class {
475
481
  const response = await fetch(url.toString(), {
476
482
  method,
477
483
  headers,
478
- body: body ? JSON.stringify(body) : void 0,
484
+ body: body ? isFormData ? body : JSON.stringify(body) : void 0,
479
485
  signal: controller.signal
480
486
  });
481
487
  clearTimeout(timeoutId);
@@ -2051,30 +2057,82 @@ var BrainerceClient = class {
2051
2057
  }
2052
2058
  /**
2053
2059
  * Get a cart by ID
2060
+ *
2061
+ * @param cartId - The cart ID
2062
+ * @param options - Optional settings. Use `include` to fetch related data in a single request.
2063
+ *
2064
+ * @example
2065
+ * ```typescript
2066
+ * // Basic cart fetch
2067
+ * const cart = await client.getCart('cart_123');
2068
+ *
2069
+ * // Fetch cart with recommendations, upgrades, and bundles in one call
2070
+ * const enriched = await client.getCart('cart_123', {
2071
+ * include: ['recommendations', 'upgrades', 'bundles'],
2072
+ * });
2073
+ * console.log(enriched.recommendations); // { recommendations: [...] }
2074
+ * console.log(enriched.upgrades); // { upgrades: { ... } }
2075
+ * console.log(enriched.bundles); // { bundles: [...] }
2076
+ * ```
2054
2077
  */
2055
- async getCart(cartId) {
2078
+ async getCart(cartId, options) {
2056
2079
  if (cartId === this.VIRTUAL_LOCAL_CART_ID) {
2057
2080
  console.warn('getCart("__local__") is deprecated. Use smartGetCart() instead.');
2058
2081
  return this.withGuards(this.localCartToCart(this.getLocalCart()), "cart");
2059
2082
  }
2083
+ const queryParams = options?.include?.length ? { include: options.include.join(",") } : void 0;
2060
2084
  if (this.isVibeCodedMode()) {
2061
- return this.withGuards(this.vibeCodedRequest("GET", `/cart/${cartId}`), "cart");
2085
+ return this.withGuards(
2086
+ this.vibeCodedRequest("GET", `/cart/${cartId}`, void 0, queryParams),
2087
+ "cart"
2088
+ );
2062
2089
  }
2063
2090
  if (this.storeId && !this.apiKey) {
2064
- return this.withGuards(this.storefrontRequest("GET", `/cart/${cartId}`), "cart");
2091
+ return this.withGuards(
2092
+ this.storefrontRequest("GET", `/cart/${cartId}`, void 0, queryParams),
2093
+ "cart"
2094
+ );
2065
2095
  }
2066
- return this.withGuards(this.adminRequest("GET", `/api/v1/cart/${cartId}`), "cart");
2096
+ return this.withGuards(
2097
+ this.adminRequest("GET", `/api/v1/cart/${cartId}`, void 0, queryParams),
2098
+ "cart"
2099
+ );
2067
2100
  }
2068
2101
  /**
2069
- * Add an item to the cart
2102
+ * Add an item to the cart.
2103
+ *
2104
+ * If the product has `customizationFields` (merchant-defined buyer input
2105
+ * like engraving text, uploaded photos, select options), pass the buyer's
2106
+ * values in `metadata`. Keys must match each field's `key`. For `IMAGE` /
2107
+ * `GALLERY` types, upload the file first via `uploadCustomizationFile()`
2108
+ * and pass the returned URL. The server validates every value against its
2109
+ * `MetafieldDefinition` and rejects the request with HTTP 400 if anything
2110
+ * fails (type mismatch, required missing, not in `enumValues`, etc.).
2111
+ *
2112
+ * Values are snapshotted onto the order line at checkout — later edits to
2113
+ * the field definition do not retroactively change existing orders.
2070
2114
  *
2071
2115
  * @example
2072
2116
  * ```typescript
2117
+ * // Product with no customization fields
2073
2118
  * const cart = await client.addToCart('cart_123', {
2074
2119
  * productId: 'prod_abc',
2075
2120
  * quantity: 2,
2076
2121
  * notes: 'Gift wrap please',
2077
2122
  * });
2123
+ *
2124
+ * // Product WITH customization fields (engraving + photo + select + multi)
2125
+ * const { url: photoUrl } = await client.uploadCustomizationFile(file);
2126
+ * const cart = await client.addToCart('cart_123', {
2127
+ * productId: 'prod_mug',
2128
+ * quantity: 1,
2129
+ * metadata: {
2130
+ * engraving_text: 'Happy Birthday!', // TEXT
2131
+ * frame_color: 'Gold', // SELECT (must be in enumValues)
2132
+ * upload_photo: photoUrl, // IMAGE
2133
+ * addons: ['Gift wrap'], // MULTI_SELECT (always array)
2134
+ * },
2135
+ * });
2078
2136
  * ```
2079
2137
  */
2080
2138
  async addToCart(cartId, item) {
@@ -2722,11 +2780,21 @@ var BrainerceClient = class {
2722
2780
  * Uses promise dedup lock to prevent race conditions on parallel calls.
2723
2781
  * @internal
2724
2782
  */
2725
- async getOrCreateSessionCart() {
2726
- if (this._sessionCartPromise) return this._sessionCartPromise;
2783
+ async getOrCreateSessionCart(options) {
2784
+ if (this._sessionCartPromise) {
2785
+ const base = await this._sessionCartPromise;
2786
+ if (options?.include?.length && base.id) {
2787
+ return this.getCart(base.id, options);
2788
+ }
2789
+ return base;
2790
+ }
2727
2791
  this._sessionCartPromise = this._getOrCreateSessionCartImpl();
2728
2792
  try {
2729
- return await this._sessionCartPromise;
2793
+ const base = await this._sessionCartPromise;
2794
+ if (options?.include?.length && base.id) {
2795
+ return this.getCart(base.id, options);
2796
+ }
2797
+ return base;
2730
2798
  } finally {
2731
2799
  this._sessionCartPromise = null;
2732
2800
  }
@@ -2859,10 +2927,10 @@ var BrainerceClient = class {
2859
2927
  * Caches the cart ID for subsequent calls
2860
2928
  * @internal
2861
2929
  */
2862
- async getOrCreateCustomerCart() {
2930
+ async getOrCreateCustomerCart(options) {
2863
2931
  if (this.customerCartId) {
2864
2932
  try {
2865
- const existingCart = await this.getCart(this.customerCartId);
2933
+ const existingCart = await this.getCart(this.customerCartId, options);
2866
2934
  if (existingCart.status === "ACTIVE") {
2867
2935
  return existingCart;
2868
2936
  }
@@ -2875,6 +2943,9 @@ var BrainerceClient = class {
2875
2943
  const cart2 = await this.fetchCustomerCart();
2876
2944
  if (cart2.status === "ACTIVE") {
2877
2945
  this.customerCartId = cart2.id;
2946
+ if (options?.include?.length) {
2947
+ return this.getCart(cart2.id, options);
2948
+ }
2878
2949
  return cart2;
2879
2950
  }
2880
2951
  } catch {
@@ -2926,7 +2997,8 @@ var BrainerceClient = class {
2926
2997
  const updated = await this.addToCart(cart.id, {
2927
2998
  productId: item.productId,
2928
2999
  variantId: item.variantId,
2929
- quantity: item.quantity
3000
+ quantity: item.quantity,
3001
+ metadata: item.metadata
2930
3002
  });
2931
3003
  return updated;
2932
3004
  } else {
@@ -2934,7 +3006,8 @@ var BrainerceClient = class {
2934
3006
  const updated = await this.addToCart(cart.id, {
2935
3007
  productId: item.productId,
2936
3008
  variantId: item.variantId,
2937
- quantity: item.quantity
3009
+ quantity: item.quantity,
3010
+ metadata: item.metadata
2938
3011
  });
2939
3012
  this.updateSessionCartItemCount(updated.items?.length ?? 0);
2940
3013
  return updated;
@@ -2947,20 +3020,28 @@ var BrainerceClient = class {
2947
3020
  * - **Guest with session**: Returns server-side session cart
2948
3021
  * - **Guest without session**: Returns empty cart (no server call, cart created lazily on add)
2949
3022
  *
3023
+ * @param options - Optional. Use `include` to fetch recommendations, upgrades, and bundles
3024
+ * in a single request instead of separate calls.
3025
+ *
2950
3026
  * @example
2951
3027
  * ```typescript
2952
3028
  * const cart = await client.smartGetCart();
2953
3029
  * console.log('Items:', cart.items.length);
3030
+ *
3031
+ * // With includes — single request for cart + extras
3032
+ * const enriched = await client.smartGetCart({
3033
+ * include: ['recommendations', 'upgrades', 'bundles'],
3034
+ * });
2954
3035
  * ```
2955
3036
  */
2956
- async smartGetCart() {
3037
+ async smartGetCart(options) {
2957
3038
  if (this.isCustomerLoggedIn()) {
2958
- return this.getOrCreateCustomerCart();
3039
+ return this.getOrCreateCustomerCart(options);
2959
3040
  } else {
2960
3041
  if (!this.sessionToken) {
2961
3042
  return this.emptyCart();
2962
3043
  }
2963
- return this.getOrCreateSessionCart();
3044
+ return this.getOrCreateSessionCart(options);
2964
3045
  }
2965
3046
  }
2966
3047
  /**
@@ -5573,6 +5654,32 @@ var BrainerceClient = class {
5573
5654
  async deleteMetafieldDefinition(definitionId) {
5574
5655
  await this.adminRequest("DELETE", `/api/v1/metafield-definitions/${definitionId}`);
5575
5656
  }
5657
+ /**
5658
+ * Replace the list of products a (customer-input) metafield definition is
5659
+ * attached to. Diff-scoped: only rows for this definition are touched, so
5660
+ * concurrent edits to other definitions on the same product are safe.
5661
+ *
5662
+ * Optionally toggles `appliesToAllProducts` in the same call. When that
5663
+ * flag is true the `productIds` list is still respected as the explicit
5664
+ * subset, but buyers will see the field on every product regardless.
5665
+ *
5666
+ * Requires Admin mode (apiKey).
5667
+ *
5668
+ * @example
5669
+ * ```typescript
5670
+ * await client.setDefinitionProducts('def_123', {
5671
+ * productIds: ['prod_a', 'prod_b'],
5672
+ * appliesToAllProducts: false,
5673
+ * });
5674
+ * ```
5675
+ */
5676
+ async setDefinitionProducts(definitionId, data) {
5677
+ return this.adminRequest(
5678
+ "PATCH",
5679
+ `/api/v1/metafield-definitions/${definitionId}/products`,
5680
+ data
5681
+ );
5682
+ }
5576
5683
  // -------------------- Metafields: Product Values --------------------
5577
5684
  // These methods require Admin mode (apiKey)
5578
5685
  /**
@@ -5637,9 +5744,35 @@ var BrainerceClient = class {
5637
5744
  );
5638
5745
  }
5639
5746
  /**
5640
- * Upload a file for product customization (e.g., customer logo for printing).
5641
- * Available in storefront and vibe-coded modes.
5642
- * Returns the uploaded file URL and key.
5747
+ * Upload a buyer-submitted file (engraving photo, custom image, etc.) for a
5748
+ * product customization field of type `IMAGE` or `GALLERY`. The returned `url`
5749
+ * is what you pass back in the add-to-cart `metadata` payload.
5750
+ *
5751
+ * Available in storefront and vibe-coded modes — no customer login required.
5752
+ *
5753
+ * Server rules:
5754
+ * - `image/*` MIME only. Other types → HTTP 400.
5755
+ * - Max 5 MB per file.
5756
+ * - Throttled to 10 uploads / minute per IP → HTTP 429.
5757
+ * - Retention: at least 7 days. If the cart never becomes an order, the file
5758
+ * is reclaimed automatically. Once the order exists, the file is kept for
5759
+ * order-history purposes.
5760
+ *
5761
+ * @example
5762
+ * ```ts
5763
+ * // On a product page with IMAGE/GALLERY customization fields:
5764
+ * const { url } = await client.uploadCustomizationFile(fileInput.files[0]);
5765
+ *
5766
+ * // Then include the URL in the add-to-cart metadata:
5767
+ * await client.addToCart(cart.id, {
5768
+ * productId: product.id,
5769
+ * quantity: 1,
5770
+ * metadata: {
5771
+ * engraving_text: 'Happy Birthday!',
5772
+ * upload_photo: url,
5773
+ * },
5774
+ * });
5775
+ * ```
5643
5776
  */
5644
5777
  async uploadCustomizationFile(file) {
5645
5778
  const formData = new FormData();
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "brainerce",
3
- "version": "1.17.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.19.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
+ }