brainerce 1.63.0 → 2.0.2

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
@@ -204,7 +204,7 @@ function isDevGuardsEnabled() {
204
204
  }
205
205
 
206
206
  // src/version.ts
207
- var SDK_VERSION = "1.60.0";
207
+ var SDK_VERSION = "2.0.1";
208
208
 
209
209
  // src/client.ts
210
210
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -650,7 +650,7 @@ var _BrainerceClient = class _BrainerceClient {
650
650
  */
651
651
  this.blog = /* @__PURE__ */ (() => {
652
652
  const publicBase = "/blog/posts";
653
- const adminBase = "/blog/posts";
653
+ const adminBase = "/api/blog/posts";
654
654
  const requireAdmin = (action) => {
655
655
  if (this.isVibeCodedMode() || this.storeId && !this.apiKey) {
656
656
  throw new BrainerceError(
@@ -763,7 +763,7 @@ var _BrainerceClient = class _BrainerceClient {
763
763
  }
764
764
  if (!options.salesChannelId && options.connectionId) {
765
765
  console.warn(
766
- "BrainerceClient: `connectionId` is deprecated \u2014 use `salesChannelId` instead. `connectionId` will be removed in SDK 2.0."
766
+ "BrainerceClient: `connectionId` is deprecated \u2014 use `salesChannelId` instead. `connectionId` is a permanent backward-compat alias and is not scheduled for removal."
767
767
  );
768
768
  }
769
769
  if (options.apiKey && typeof window !== "undefined") {
@@ -2484,115 +2484,97 @@ var _BrainerceClient = class _BrainerceClient {
2484
2484
  return this.request("PATCH", `/api/v1/orders/${encodePathSegment(orderId)}`, data);
2485
2485
  }
2486
2486
  /**
2487
- * Update order status
2487
+ * Update order status.
2488
+ *
2489
+ * **Not callable — use {@link updateOrder} instead.** Status changes do work
2490
+ * over the API key, just by a different route.
2491
+ *
2492
+ * @deprecated Call `updateOrder(orderId, { status })`.
2488
2493
  *
2489
2494
  * @example
2490
2495
  * ```typescript
2491
- * const order = await client.updateOrderStatus('order_123', 'shipped');
2496
+ * const order = await client.updateOrder('order_123', { status: 'SHIPPED' });
2492
2497
  * ```
2493
2498
  */
2494
2499
  async updateOrderStatus(orderId, status) {
2495
- return this.request("PATCH", `/api/v1/orders/${encodePathSegment(orderId)}/status`, {
2496
- status
2497
- });
2500
+ void orderId;
2501
+ void status;
2502
+ throw new BrainerceError(
2503
+ "updateOrderStatus is not a route on the API-key /v1 surface. Status changes go through updateOrder instead: updateOrder(orderId, { status }) PATCHes /api/v1/orders/:id and ends up in the same service call.",
2504
+ 400
2505
+ );
2498
2506
  }
2499
2507
  /**
2500
- * Update order payment method
2501
- * Note: Only WooCommerce supports syncing payment method changes back to platform
2508
+ * Update order payment method.
2502
2509
  *
2503
- * @example
2504
- * ```typescript
2505
- * const order = await client.updatePaymentMethod('order_123', 'credit_card');
2506
- * ```
2510
+ * **Not callable.** The API-key `/v1` surface has no payment-method route,
2511
+ * so this throws in every mode. Change the payment method from the
2512
+ * dashboard until the route ships.
2507
2513
  */
2508
2514
  async updatePaymentMethod(orderId, paymentMethod) {
2509
- return this.request(
2510
- "PATCH",
2511
- `/api/v1/orders/${encodePathSegment(orderId)}/payment-method`,
2512
- {
2513
- paymentMethod
2514
- }
2515
+ void orderId;
2516
+ void paymentMethod;
2517
+ throw new BrainerceError(
2518
+ "updatePaymentMethod is not a route on the API-key /v1 surface. There is no orders/:id/payment-method endpoint to call; change the payment method from the Brainerce dashboard.",
2519
+ 400
2515
2520
  );
2516
2521
  }
2517
2522
  /**
2518
- * Update order notes
2523
+ * Update order notes.
2519
2524
  *
2520
- * @example
2521
- * ```typescript
2522
- * const order = await client.updateOrderNotes('order_123', 'Customer requested gift wrapping');
2523
- * ```
2525
+ * **Not callable.** The API-key `/v1` surface has no order-notes route, so
2526
+ * this throws in every mode. Edit notes from the dashboard until the route
2527
+ * ships.
2524
2528
  */
2525
2529
  async updateOrderNotes(orderId, notes) {
2526
- return this.request("PATCH", `/api/v1/orders/${encodePathSegment(orderId)}/notes`, {
2527
- notes
2528
- });
2530
+ void orderId;
2531
+ void notes;
2532
+ throw new BrainerceError(
2533
+ "updateOrderNotes is not a route on the API-key /v1 surface. There is no orders/:id/notes endpoint to call; edit the note from the Brainerce dashboard.",
2534
+ 400
2535
+ );
2529
2536
  }
2530
2537
  /**
2531
- * Get refunds for an order
2532
- * Returns refunds from the source platform (Shopify/WooCommerce only)
2538
+ * Get refunds for an order.
2533
2539
  *
2534
- * @example
2535
- * ```typescript
2536
- * const refunds = await client.getOrderRefunds('order_123');
2537
- * console.log('Total refunds:', refunds.length);
2538
- * ```
2540
+ * **Not callable.** The API-key `/v1` surface has no refunds route, so this
2541
+ * throws in every mode. Read refunds from the dashboard until the route
2542
+ * ships.
2539
2543
  */
2540
2544
  async getOrderRefunds(orderId) {
2541
- return this.request("GET", `/api/v1/orders/${encodePathSegment(orderId)}/refunds`);
2545
+ void orderId;
2546
+ throw new BrainerceError(
2547
+ "getOrderRefunds is not a route on the API-key /v1 surface. There is no orders/:id/refunds endpoint to call; view refunds in the Brainerce dashboard.",
2548
+ 400
2549
+ );
2542
2550
  }
2543
2551
  /**
2544
- * Create a refund for an order
2545
- * Creates refund on the source platform (Shopify/WooCommerce only)
2546
- *
2547
- * @example
2548
- * ```typescript
2549
- * // Full refund
2550
- * const refund = await client.createRefund('order_123', {
2551
- * type: 'full',
2552
- * restockInventory: true,
2553
- * notifyCustomer: true,
2554
- * reason: 'Customer request',
2555
- * });
2552
+ * Create a refund for an order.
2556
2553
  *
2557
- * // Partial refund
2558
- * const partialRefund = await client.createRefund('order_123', {
2559
- * type: 'partial',
2560
- * items: [
2561
- * { lineItemId: 'item_456', quantity: 1 },
2562
- * ],
2563
- * restockInventory: true,
2564
- * });
2565
- * ```
2554
+ * **Not callable.** The API-key `/v1` surface has no refunds route, so this
2555
+ * throws in every mode. Refund from the dashboard until the route ships.
2566
2556
  */
2567
2557
  async createRefund(orderId, data) {
2568
- return this.request(
2569
- "POST",
2570
- `/api/v1/orders/${encodePathSegment(orderId)}/refunds`,
2571
- data
2558
+ void orderId;
2559
+ void data;
2560
+ throw new BrainerceError(
2561
+ "createRefund is not a route on the API-key /v1 surface. There is no orders/:id/refunds endpoint to call, so no refund was issued; refund the order from the Brainerce dashboard.",
2562
+ 400
2572
2563
  );
2573
2564
  }
2574
2565
  /**
2575
- * Update order shipping address
2576
- * Syncs to source platform (Shopify/WooCommerce only)
2566
+ * Update order shipping address.
2577
2567
  *
2578
- * @example
2579
- * ```typescript
2580
- * const order = await client.updateOrderShipping('order_123', {
2581
- * firstName: 'John',
2582
- * lastName: 'Doe',
2583
- * line1: '456 New Address',
2584
- * city: 'Los Angeles',
2585
- * state: 'CA',
2586
- * country: 'US',
2587
- * postalCode: '90001',
2588
- * });
2589
- * ```
2568
+ * **Not callable.** The API-key `/v1` surface has no order-shipping route,
2569
+ * so this throws in every mode. Correct the address from the dashboard
2570
+ * until the route ships.
2590
2571
  */
2591
2572
  async updateOrderShipping(orderId, data) {
2592
- return this.request(
2593
- "PATCH",
2594
- `/api/v1/orders/${encodePathSegment(orderId)}/shipping`,
2595
- data
2573
+ void orderId;
2574
+ void data;
2575
+ throw new BrainerceError(
2576
+ "updateOrderShipping is not a route on the API-key /v1 surface. There is no orders/:id/shipping endpoint to call, so the address was not changed; edit it in the Brainerce dashboard.",
2577
+ 400
2596
2578
  );
2597
2579
  }
2598
2580
  /**
@@ -2658,17 +2640,49 @@ var _BrainerceClient = class _BrainerceClient {
2658
2640
  return this.request("GET", `/api/v1/orders/${encodePathSegment(orderId)}/shipments`);
2659
2641
  }
2660
2642
  /**
2661
- * Cancel an order
2662
- * Works for Shopify and WooCommerce orders that haven't been fulfilled
2643
+ * Buy a return label the merchant sends to their customer to print.
2644
+ *
2645
+ * Requires admin mode (`apiKey`) with `FULFILL_ORDERS` permission — it
2646
+ * spends the store's carrier balance, same as {@link createShippingLabel}.
2647
+ *
2648
+ * Unlike {@link createShippingLabel}, this is **not** on the API-key `/v1`
2649
+ * surface — it calls the internal `/api/orders/:id/shipments/return-label`
2650
+ * route, which takes `storeId` explicitly rather than resolving it from the
2651
+ * key. There is no `rateId` in the body: a return is quoted and bought in
2652
+ * one call at the shipping app, because the carrier fixes a shipment as a
2653
+ * return when it is created and will not amend it afterwards.
2663
2654
  *
2664
2655
  * @example
2665
2656
  * ```typescript
2666
- * const order = await client.cancelOrder('order_123');
2667
- * console.log('Order status:', order.status); // 'cancelled'
2657
+ * const label = await client.createReturnLabel('store_abc', 'order_abc', {
2658
+ * reason: 'Wrong size',
2659
+ * returnForShipmentId: 'shp_original123',
2660
+ * });
2661
+ * console.log('Return label URL:', label.labelUrl);
2668
2662
  * ```
2669
2663
  */
2664
+ async createReturnLabel(storeId, orderId, data) {
2665
+ return this.adminRequest(
2666
+ "POST",
2667
+ `/api/orders/${encodePathSegment(orderId)}/shipments/return-label`,
2668
+ data,
2669
+ { storeId }
2670
+ );
2671
+ }
2672
+ /**
2673
+ * Cancel an order.
2674
+ *
2675
+ * **Not callable.** The API-key `/v1` surface has no cancel route, so this
2676
+ * throws in every mode. A status move to cancelled may be reachable through
2677
+ * {@link updateOrder} depending on what the order's state machine allows;
2678
+ * otherwise cancel from the dashboard.
2679
+ */
2670
2680
  async cancelOrder(orderId) {
2671
- return this.request("POST", `/api/v1/orders/${encodePathSegment(orderId)}/cancel`);
2681
+ void orderId;
2682
+ throw new BrainerceError(
2683
+ "cancelOrder is not a route on the API-key /v1 surface. There is no orders/:id/cancel endpoint to call, so the order was not cancelled. Try updateOrder(orderId, { status }) for a plain status move, or cancel from the Brainerce dashboard.",
2684
+ 400
2685
+ );
2672
2686
  }
2673
2687
  /**
2674
2688
  * Fulfill an order (mark as shipped), or correct the tracking of an order
@@ -2682,110 +2696,87 @@ var _BrainerceClient = class _BrainerceClient {
2682
2696
  * ship date is not rewritten, and no fulfilment event fires. That is the way
2683
2697
  * to fix a mistyped tracking number.
2684
2698
  *
2685
- * @example
2686
- * ```typescript
2687
- * // First fulfilmentemails the shopper by default.
2688
- * await client.fulfillOrder('order_123', {
2689
- * trackingNumber: '1Z999AA10123456784',
2690
- * trackingCompany: 'UPS',
2691
- * trackingUrl: 'https://www.ups.com/track?tracknum=1Z999AA10123456784',
2692
- * notifyCustomer: true,
2693
- * });
2694
- *
2695
- * // Correction — silent unless you opt back in.
2696
- * await client.fulfillOrder('order_123', {
2697
- * trackingNumber: '1Z999AA10123456785',
2698
- * });
2699
- * ```
2699
+ * **Not callable.** The API-key `/v1` surface has no fulfil route, so this
2700
+ * throws in every mode. To ship an order over the API today, buy a label
2701
+ * with {@link createShippingLabel} — the carrier's webhooks then move the
2702
+ * shipment through in-transit and delivered on their own. Otherwise fulfil
2703
+ * from the dashboard.
2700
2704
  */
2701
2705
  async fulfillOrder(orderId, data) {
2702
- return this.request(
2703
- "POST",
2704
- `/api/v1/orders/${encodePathSegment(orderId)}/fulfill`,
2705
- data || {}
2706
+ void orderId;
2707
+ void data;
2708
+ throw new BrainerceError(
2709
+ "fulfillOrder is not a route on the API-key /v1 surface. There is no orders/:id/fulfill endpoint to call, so nothing was fulfilled and no shipped email went out. Use createShippingLabel to ship over the API, or fulfil from the Brainerce dashboard.",
2710
+ 400
2706
2711
  );
2707
2712
  }
2708
2713
  /**
2709
- * Sync draft orders from connected platforms
2714
+ * Sync draft orders from connected platforms.
2710
2715
  *
2711
- * @example
2712
- * ```typescript
2713
- * const result = await client.syncDraftOrders();
2714
- * console.log('Draft orders synced');
2715
- * ```
2716
+ * **Not callable.** The API-key `/v1` surface has no draft-order routes at
2717
+ * all, so this throws in every mode. {@link triggerSync} covers a general
2718
+ * platform sync; draft orders are managed from the dashboard.
2716
2719
  */
2717
2720
  async syncDraftOrders() {
2718
- return this.request("POST", "/api/v1/orders/sync-drafts");
2721
+ throw new BrainerceError(
2722
+ "syncDraftOrders is not a route on the API-key /v1 surface. There is no orders/sync-drafts endpoint to call, so nothing was synced. Use triggerSync for a platform sync, or work with drafts in the Brainerce dashboard.",
2723
+ 400
2724
+ );
2719
2725
  }
2720
2726
  /**
2721
- * Complete a draft order (convert to regular order)
2727
+ * Complete a draft order (convert to regular order).
2722
2728
  *
2723
- * @example
2724
- * ```typescript
2725
- * const order = await client.completeDraftOrder('draft_123', {
2726
- * paymentPending: false,
2727
- * });
2728
- * ```
2729
+ * **Not callable.** The API-key `/v1` surface has no draft-order routes at
2730
+ * all, so this throws in every mode. Complete drafts from the dashboard.
2729
2731
  */
2730
2732
  async completeDraftOrder(orderId, data) {
2731
- return this.request(
2732
- "POST",
2733
- `/api/v1/orders/${encodePathSegment(orderId)}/complete-draft`,
2734
- data || {}
2733
+ void orderId;
2734
+ void data;
2735
+ throw new BrainerceError(
2736
+ "completeDraftOrder is not a route on the API-key /v1 surface. There is no orders/:id/complete-draft endpoint to call, so the draft was not converted; complete it in the Brainerce dashboard.",
2737
+ 400
2735
2738
  );
2736
2739
  }
2737
2740
  /**
2738
- * Send invoice for a draft order
2741
+ * Send invoice for a draft order.
2739
2742
  *
2740
- * @example
2741
- * ```typescript
2742
- * await client.sendDraftInvoice('draft_123', {
2743
- * to: 'customer@example.com',
2744
- * subject: 'Your Invoice',
2745
- * customMessage: 'Thank you for your order!',
2746
- * });
2747
- * ```
2743
+ * **Not callable.** The API-key `/v1` surface has no draft-order routes at
2744
+ * all, so this throws in every mode. Send the invoice from the dashboard.
2748
2745
  */
2749
2746
  async sendDraftInvoice(orderId, data) {
2750
- return this.request(
2751
- "POST",
2752
- `/api/v1/orders/${encodePathSegment(orderId)}/send-invoice`,
2753
- data || {}
2747
+ void orderId;
2748
+ void data;
2749
+ throw new BrainerceError(
2750
+ "sendDraftInvoice is not a route on the API-key /v1 surface. There is no orders/:id/send-invoice endpoint to call, so no invoice was sent; send it from the Brainerce dashboard.",
2751
+ 400
2754
2752
  );
2755
2753
  }
2756
2754
  /**
2757
- * Delete a draft order
2755
+ * Delete a draft order.
2758
2756
  *
2759
- * @example
2760
- * ```typescript
2761
- * await client.deleteDraftOrder('draft_123');
2762
- * ```
2757
+ * **Not callable.** The API-key `/v1` surface has no draft-order routes at
2758
+ * all, so this throws in every mode. Delete drafts from the dashboard.
2763
2759
  */
2764
2760
  async deleteDraftOrder(orderId) {
2765
- await this.request("DELETE", `/api/v1/orders/${encodePathSegment(orderId)}/draft`);
2761
+ void orderId;
2762
+ throw new BrainerceError(
2763
+ "deleteDraftOrder is not a route on the API-key /v1 surface. There is no orders/:id/draft endpoint to call, so nothing was deleted; delete the draft in the Brainerce dashboard.",
2764
+ 400
2765
+ );
2766
2766
  }
2767
2767
  /**
2768
- * Update a draft order
2768
+ * Update a draft order.
2769
2769
  *
2770
- * @example
2771
- * ```typescript
2772
- * const order = await client.updateDraftOrder('draft_123', {
2773
- * note: 'Updated customer note',
2774
- * email: 'newemail@example.com',
2775
- * shippingAddress: {
2776
- * firstName: 'John',
2777
- * lastName: 'Doe',
2778
- * address1: '123 Main St',
2779
- * city: 'New York',
2780
- * province: 'NY',
2781
- * country: 'US',
2782
- * zip: '10001',
2783
- * },
2784
- * });
2785
- * ```
2770
+ * **Not callable.** The API-key `/v1` surface has no draft-order routes at
2771
+ * all, so this throws in every mode. Edit drafts from the dashboard.
2786
2772
  */
2787
2773
  async updateDraftOrder(orderId, data) {
2788
- return this.request("PATCH", `/api/v1/orders/${encodePathSegment(orderId)}/draft`, data);
2774
+ void orderId;
2775
+ void data;
2776
+ throw new BrainerceError(
2777
+ "updateDraftOrder is not a route on the API-key /v1 surface. There is no orders/:id/draft endpoint to call, so the draft was not changed; edit it in the Brainerce dashboard.",
2778
+ 400
2779
+ );
2789
2780
  }
2790
2781
  // -------------------- Inventory --------------------
2791
2782
  /**
@@ -2800,82 +2791,91 @@ var _BrainerceClient = class _BrainerceClient {
2800
2791
  );
2801
2792
  }
2802
2793
  /**
2803
- * Get current inventory for a product
2794
+ * Get current inventory for a product.
2795
+ *
2796
+ * **Admin mode only** — the API key needs the `inventory:read` scope.
2797
+ *
2798
+ * This used to request `/api/v1/inventory/:productId`, which does not
2799
+ * exist and 404'd silently. The live route is product-scoped:
2800
+ * `GET /api/v1/products/:id/inventory`. A product with no inventory row
2801
+ * reads back as all zeroes rather than 404ing.
2804
2802
  */
2805
2803
  async getInventory(productId) {
2806
- return this.request("GET", `/api/v1/inventory/${encodePathSegment(productId)}`);
2804
+ return this.adminRequest(
2805
+ "GET",
2806
+ `/api/v1/products/${encodePathSegment(productId)}/inventory`
2807
+ );
2807
2808
  }
2808
2809
  /**
2809
- * Edit inventory manually with reason for audit trail
2810
+ * Edit inventory manually with a reason for the audit trail.
2810
2811
  *
2811
- * @example
2812
- * ```typescript
2813
- * const inventory = await client.editInventory({
2814
- * productId: 'prod_123',
2815
- * newTotal: 100,
2816
- * reason: 'Restocked from warehouse',
2817
- * });
2818
- * ```
2812
+ * **Not callable.** The API-key `/v1` surface carries no `inventory`
2813
+ * namespace, so this throws in every mode.
2814
+ *
2815
+ * {@link updateInventory} is the closest working call: it sets the same
2816
+ * absolute stock level over `PUT /api/v1/products/:id/inventory`, but the
2817
+ * reason is not yours to choose — the server records a generic
2818
+ * "Updated via External API" against the audit trail. If the reason text
2819
+ * matters, make the edit from the dashboard.
2819
2820
  */
2820
2821
  async editInventory(data) {
2821
- return this.request("POST", "/api/v1/inventory/edit", data);
2822
+ void data;
2823
+ throw new BrainerceError(
2824
+ "editInventory is not a route on the API-key /v1 surface. There is no inventory/edit endpoint to call, so stock was not changed. Use updateInventory(productId, { quantity }) to set the same level, though it records a generic audit reason. Edit from the dashboard when the reason text matters.",
2825
+ 400
2826
+ );
2822
2827
  }
2823
2828
  /**
2824
- * Get inventory sync status for all products in the store
2829
+ * Get inventory sync status for all products in the store.
2825
2830
  *
2826
- * @example
2827
- * ```typescript
2828
- * const status = await client.getInventorySyncStatus();
2829
- * console.log(`${status.pending} products pending sync`);
2830
- * console.log(`Last sync: ${status.lastSyncAt}`);
2831
- * ```
2831
+ * **Not callable.** The API-key `/v1` surface carries no `inventory`
2832
+ * namespace, so this throws in every mode. Sync state is visible in the
2833
+ * dashboard; {@link getSyncStatus} covers platform sync jobs.
2832
2834
  */
2833
2835
  async getInventorySyncStatus() {
2834
- return this.request("GET", "/api/v1/inventory/sync-status");
2836
+ throw new BrainerceError(
2837
+ "getInventorySyncStatus is not a route on the API-key /v1 surface. There is no inventory/sync-status endpoint to call; check inventory sync state in the Brainerce dashboard.",
2838
+ 400
2839
+ );
2835
2840
  }
2836
2841
  /**
2837
- * Get inventory for multiple products at once
2842
+ * Get inventory for multiple products at once.
2838
2843
  *
2839
- * @example
2840
- * ```typescript
2841
- * const inventories = await client.getBulkInventory(['prod_123', 'prod_456', 'prod_789']);
2842
- * inventories.forEach(inv => {
2843
- * console.log(`${inv.productId}: ${inv.available} available`);
2844
- * });
2845
- * ```
2844
+ * **Not callable.** The API-key `/v1` surface carries no `inventory`
2845
+ * namespace, so this throws in every mode. There is no bulk stock read on
2846
+ * the API key today: fall back to {@link getInventory} per product, or read
2847
+ * the stock that {@link getProducts} already returns on each product.
2846
2848
  */
2847
2849
  async getBulkInventory(productIds) {
2848
- return this.request("POST", "/api/v1/inventory/bulk", { productIds });
2850
+ void productIds;
2851
+ throw new BrainerceError(
2852
+ "getBulkInventory is not a route on the API-key /v1 surface. There is no inventory/bulk endpoint to call; read stock per product with getInventory, or off the products returned by getProducts.",
2853
+ 400
2854
+ );
2849
2855
  }
2850
2856
  /**
2851
- * Reconcile inventory between Brainerce and connected platforms
2852
- * Detects and optionally fixes discrepancies
2853
- *
2854
- * @example
2855
- * ```typescript
2856
- * // Reconcile single product (dry run)
2857
- * const result = await client.reconcileInventory({ productId: 'prod_123' });
2857
+ * Reconcile inventory between Brainerce and connected platforms.
2858
+ * Detects and optionally fixes discrepancies.
2858
2859
  *
2859
- * // Reconcile all products with auto-fix
2860
- * const summary = await client.reconcileInventory({ autoFix: true });
2861
- * console.log(`Reconciled ${summary.reconciled} products`);
2862
- * ```
2860
+ * **Not callable.** The API-key `/v1` surface carries no `inventory`
2861
+ * namespace, so this throws in every mode, `autoFix` included. Reconcile
2862
+ * from the dashboard.
2863
2863
  */
2864
2864
  async reconcileInventory(options) {
2865
- const queryParams = {};
2866
- if (options?.productId) queryParams.productId = options.productId;
2867
- if (options?.autoFix) queryParams.autoFix = "true";
2868
- return this.request(
2869
- "POST",
2870
- "/api/v1/inventory/reconcile",
2871
- void 0,
2872
- queryParams
2865
+ void options;
2866
+ throw new BrainerceError(
2867
+ "reconcileInventory is not a route on the API-key /v1 surface. There is no inventory/reconcile endpoint to call, so nothing was reconciled or fixed; run reconciliation from the Brainerce dashboard.",
2868
+ 400
2873
2869
  );
2874
2870
  }
2875
2871
  /**
2876
2872
  * Check stock availability for one or more items before adding to cart or checkout
2877
2873
  * Use this to validate stock before operations that might fail due to insufficient inventory
2878
2874
  *
2875
+ * **Vibe-coded or storefront mode only.** There is no stock-check route on
2876
+ * the API-key `/v1` surface; in admin mode this throws. The same applies to
2877
+ * {@link checkCartStock}, which routes through here.
2878
+ *
2879
2879
  * @example
2880
2880
  * ```typescript
2881
2881
  * // Check if items are available before adding to cart
@@ -2907,9 +2907,10 @@ var _BrainerceClient = class _BrainerceClient {
2907
2907
  { items }
2908
2908
  );
2909
2909
  }
2910
- return this.request("POST", "/api/v1/inventory/check-availability", {
2911
- items
2912
- });
2910
+ throw new BrainerceError(
2911
+ "checkStockAvailability is only available in vibe-coded or storefront mode. The API-key /v1 surface has no inventory/check-availability route; read stock per product with getInventory instead.",
2912
+ 400
2913
+ );
2913
2914
  }
2914
2915
  /**
2915
2916
  * Check stock availability for cart items before checkout.
@@ -3246,49 +3247,43 @@ var _BrainerceClient = class _BrainerceClient {
3246
3247
  * Request a password reset email for a customer
3247
3248
  * Works in vibe-coded, storefront, and admin mode
3248
3249
  *
3249
- * The `resetUrl` MUST be supplied explicitly in non-browser (SSR / Node)
3250
- * contexts auto-deriving it from `window.location.origin` is impossible
3251
- * there and historically resulted in `undefined` being sent to the backend,
3252
- * which then bounced the email to a broken link. In browser contexts the
3253
- * origin is still used as a fallback but the SDK logs a one-time warning
3254
- * recommending an explicit value so server-rendered + proxied dashboards
3255
- * don't silently rely on the wrong host.
3250
+ * The reset link's host is chosen by the server, not by the caller: it is
3251
+ * derived from the sales channel's own domain, falling back to the backend's
3252
+ * configured frontend URL, and the request is rejected if neither resolves.
3253
+ *
3254
+ * The SDK used to send a `resetUrl` in the request body. The backend
3255
+ * deliberately removed that field: any caller could submit an arbitrary URL
3256
+ * and have it emailed, from a Brainerce-domained sender, to the address
3257
+ * holder — a phishing-link injection. `ForgotPasswordDto` now declares
3258
+ * `email` and nothing else, and the API's global validation pipe runs with
3259
+ * `whitelist` + `forbidNonWhitelisted`, so a body carrying `resetUrl` fails
3260
+ * the whole call with `400 property resetUrl should not exist`. Only `email`
3261
+ * is sent.
3262
+ *
3263
+ * The endpoint always answers 200 so it cannot be used to enumerate
3264
+ * accounts; the mail is only sent when a matching customer exists.
3256
3265
  *
3257
3266
  * @param email - Customer email address
3258
- * @param options - Optional settings
3259
- * @param options.resetUrl - Reset URL the email links should point to.
3260
- * Required outside the browser; recommended inside it.
3267
+ * @param options - Accepted for source compatibility only. Ignored.
3261
3268
  */
3262
3269
  async forgotPassword(email, options) {
3263
- let resetUrl = options?.resetUrl;
3264
- if (!resetUrl) {
3265
- if (typeof window === "undefined") {
3266
- throw new BrainerceError(
3267
- 'forgotPassword: `resetUrl` is required outside the browser. Pass `{ resetUrl: "https://your-site.example/reset-password" }` so the email links to the right host.',
3268
- 400
3269
- );
3270
- }
3271
- console.warn(
3272
- "BrainerceClient.forgotPassword: deriving `resetUrl` from `window.location.origin` \u2014 pass `{ resetUrl }` explicitly to avoid wrong-host links behind proxies or in SSR."
3273
- );
3274
- resetUrl = `${window.location.origin}/reset-password`;
3275
- }
3270
+ void options;
3271
+ const body = { email };
3276
3272
  if (this.isVibeCodedMode()) {
3277
- return this.vibeCodedRequest("POST", "/customers/forgot-password", {
3278
- email,
3279
- resetUrl
3280
- });
3273
+ return this.vibeCodedRequest("POST", "/customers/forgot-password", body);
3281
3274
  }
3282
3275
  if (this.storeId && !this.apiKey) {
3283
- return this.storefrontRequest("POST", "/customers/forgot-password", {
3284
- email,
3285
- resetUrl
3286
- });
3276
+ return this.storefrontRequest(
3277
+ "POST",
3278
+ "/customers/forgot-password",
3279
+ body
3280
+ );
3287
3281
  }
3288
- return this.adminRequest("POST", "/api/v1/customers/forgot-password", {
3289
- email,
3290
- resetUrl
3291
- });
3282
+ return this.adminRequest(
3283
+ "POST",
3284
+ "/api/v1/customers/forgot-password",
3285
+ body
3286
+ );
3292
3287
  }
3293
3288
  /**
3294
3289
  * Reset customer password using a reset token received via email
@@ -10109,6 +10104,156 @@ var _BrainerceClient = class _BrainerceClient {
10109
10104
  `/api/v1/oauth-providers/${encodePathSegment(provider)}`
10110
10105
  );
10111
10106
  }
10107
+ // -------------------- Translations (Admin) --------------------
10108
+ // These methods require Admin mode (apiKey) with a scope-bearing key —
10109
+ // `products:read`/`products:write` cover most entity types via
10110
+ // StorePermissionGuard's STORE_PERMISSION_TO_SCOPE mapping (VIEW_PRODUCTS /
10111
+ // EDIT_PRODUCTS). Routes: /api/stores/:storeId/translations/...
10112
+ //
10113
+ // Reads and writes the SAME `translations` JSON blob the dashboard editor
10114
+ // uses — this is not a separate translation system, it's API-key access to
10115
+ // the dashboard's own persistence path. Use it for bulk-importing
10116
+ // pre-translated content or automating locale coverage without a human in
10117
+ // the dashboard.
10118
+ /**
10119
+ * Get translation completeness across every translatable entity type, for
10120
+ * one or more locales. Useful as a pre-flight before a bulk import to see
10121
+ * which entity types/locales still need coverage.
10122
+ * Requires Admin mode (apiKey).
10123
+ *
10124
+ * @param storeId - Store to inspect.
10125
+ * @param locales - BCP-47 locale codes to check (e.g. `['he', 'fr']`). Omit
10126
+ * or pass an empty array to get rows with `total` populated but no
10127
+ * locale breakdown.
10128
+ *
10129
+ * @example
10130
+ * ```typescript
10131
+ * const status = await client.getTranslationStatus('store_123', ['he', 'fr']);
10132
+ * const blogHe = status.find((s) => s.entityType === 'blogPost' && s.locale === 'he');
10133
+ * console.log(`${blogHe?.missing} blog posts still need Hebrew`);
10134
+ * ```
10135
+ */
10136
+ async getTranslationStatus(storeId, locales) {
10137
+ return this.adminRequest(
10138
+ "GET",
10139
+ `/api/stores/${encodePathSegment(storeId)}/translations/status`,
10140
+ void 0,
10141
+ locales && locales.length > 0 ? { locales: locales.join(",") } : void 0
10142
+ );
10143
+ }
10144
+ /**
10145
+ * Get every persisted translation for a single entity, keyed by locale.
10146
+ * Requires Admin mode (apiKey).
10147
+ *
10148
+ * @example
10149
+ * ```typescript
10150
+ * const translations = await client.getTranslations('store_123', 'product', 'prod_abc');
10151
+ * console.log(translations.he?.name); // Hebrew product name, if set
10152
+ * ```
10153
+ */
10154
+ async getTranslations(storeId, entityType, entityId) {
10155
+ return this.adminRequest(
10156
+ "GET",
10157
+ `/api/stores/${encodePathSegment(storeId)}/translations/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}`
10158
+ );
10159
+ }
10160
+ /**
10161
+ * Set/update one locale's translation for an entity. Only the fields valid
10162
+ * for `entityType` are persisted (e.g. `title`/`excerpt`/`content` for
10163
+ * `blogPost`, `name`/`description` for `category`) — fields outside that
10164
+ * entity's allowlist are silently ignored server-side, and omitted fields
10165
+ * leave any existing translation for them untouched (this is a merge, not
10166
+ * a replace, of the locale's fields).
10167
+ * Requires Admin mode (apiKey) with `products:write` (or the equivalent
10168
+ * scope for the target entity type).
10169
+ *
10170
+ * @example
10171
+ * ```typescript
10172
+ * // Bulk-import a pre-translated blog post
10173
+ * await client.setTranslation('store_123', 'blogPost', 'post_abc', 'fr', {
10174
+ * title: 'Le titre en français',
10175
+ * excerpt: "L'extrait en français",
10176
+ * content: '<p>Le contenu en français</p>',
10177
+ * });
10178
+ * ```
10179
+ */
10180
+ async setTranslation(storeId, entityType, entityId, locale, fields) {
10181
+ return this.adminRequest(
10182
+ "PUT",
10183
+ `/api/stores/${encodePathSegment(storeId)}/translations/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}/${encodePathSegment(locale)}`,
10184
+ fields
10185
+ );
10186
+ }
10187
+ /**
10188
+ * Delete one locale's translation for an entity. The entity's base
10189
+ * (default-locale) fields are unaffected.
10190
+ * Requires Admin mode (apiKey) with `products:write` (or the equivalent
10191
+ * scope for the target entity type).
10192
+ */
10193
+ async deleteTranslation(storeId, entityType, entityId, locale) {
10194
+ await this.adminRequest(
10195
+ "DELETE",
10196
+ `/api/stores/${encodePathSegment(storeId)}/translations/${encodePathSegment(entityType)}/${encodePathSegment(entityId)}/${encodePathSegment(locale)}`
10197
+ );
10198
+ }
10199
+ /**
10200
+ * AI-translate a single entity into one target locale and persist the
10201
+ * result inline (synchronous — the response already reflects the write).
10202
+ * Only fields that are still empty for `targetLocale` are filled; existing
10203
+ * translated values are never overwritten.
10204
+ * Requires Admin mode (apiKey) with `products:write` (or the equivalent
10205
+ * scope for the target entity type).
10206
+ *
10207
+ * @param sourceFields - Optional override of the source-language text to
10208
+ * translate from (e.g. unsaved edits from an open editor), instead of the
10209
+ * entity's persisted base fields. Keys outside the entity's translatable
10210
+ * field set are ignored.
10211
+ *
10212
+ * @example
10213
+ * ```typescript
10214
+ * const translations = await client.aiTranslateSingle('store_123', {
10215
+ * entityType: 'product',
10216
+ * entityId: 'prod_abc',
10217
+ * targetLocale: 'he',
10218
+ * });
10219
+ * ```
10220
+ */
10221
+ async aiTranslateSingle(storeId, input) {
10222
+ return this.adminRequest(
10223
+ "POST",
10224
+ `/api/stores/${encodePathSegment(storeId)}/translations/ai-translate-single`,
10225
+ input
10226
+ );
10227
+ }
10228
+ /**
10229
+ * Bulk AI-translate — enqueues a background job per entity (and, for
10230
+ * `entityType: 'attribute'`, one per attribute option too) rather than
10231
+ * translating inline. Returns the number of jobs queued, not the finished
10232
+ * translations; poll `getTranslationStatus` or `getTranslations` to see
10233
+ * results land.
10234
+ * Requires Admin mode (apiKey) with `products:write` (or the equivalent
10235
+ * scope for the target entity type).
10236
+ *
10237
+ * @param entityIds - Optional explicit ids to translate. Omit to target
10238
+ * every entity of `entityType` in the store that isn't already fully
10239
+ * translated for `targetLocale`.
10240
+ *
10241
+ * @example
10242
+ * ```typescript
10243
+ * // Translate every blog post missing French coverage
10244
+ * const { queued } = await client.aiTranslateBulk('store_123', {
10245
+ * entityType: 'blogPost',
10246
+ * targetLocale: 'fr',
10247
+ * });
10248
+ * ```
10249
+ */
10250
+ async aiTranslateBulk(storeId, input) {
10251
+ return this.adminRequest(
10252
+ "POST",
10253
+ `/api/stores/${encodePathSegment(storeId)}/translations/ai-translate`,
10254
+ input
10255
+ );
10256
+ }
10112
10257
  };
10113
10258
  /**
10114
10259
  * Fields present on `getAddressDetails().address` that the address endpoints