brainerce 1.44.1 → 1.46.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/README.md CHANGED
@@ -32,28 +32,28 @@ yarn add brainerce
32
32
 
33
33
  Every Brainerce storefront must include **all mandatory features** below. Features auto-hide when the underlying capability is disabled, so build them all anyway — they'll appear the moment the store owner enables them.
34
34
 
35
- | Feature | SDK entry point | Mandatory |
36
- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------- |
37
- | Product list with search, filter, pagination | `client.getProducts()`, `client.getSearchSuggestions(query)` | ✅ |
38
- | Product detail with variant picker, stock, price | `client.getProductBySlug()` + helpers | ✅ |
39
- | Buyer customization fields (engraving, uploads, select) | `product.customizationFields`, `client.uploadCustomizationFile()` | ✅ |
40
- | Cart (add, update, remove, coupon, totals) | `client.addToCart()`, `getCartTotals(cart)` | ✅ |
41
- | Inventory reservation countdown | Cart expiry timestamp from `client.getCart()` | ✅ |
42
- | Full checkout end-to-end with payment | `setShippingAddress → selectShippingMethod → getPaymentProviders → pay → handlePaymentSuccess → waitForOrder` | ✅ |
43
- | Order confirmation (clear cart + wait for real order) | `client.handlePaymentSuccess()`, `client.waitForOrder()` | ✅ |
44
- | Register + email verification flow | `client.registerCustomer()`, `client.verifyEmail()` | ✅ |
45
- | Login + verification branch | `client.loginCustomer()` | ✅ |
46
- | Forgot / reset password | `client.forgotPassword()`, `client.resetPassword()` | ✅ |
47
- | OAuth sign-in buttons + callback handler | `client.getAvailableOAuthProviders()` | ✅ |
48
- | Account area (profile + order history) | `client.getMyProfile()`, `client.getMyOrders()` | ✅ |
35
+ | Feature | SDK entry point | Mandatory |
36
+ | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------- |
37
+ | Product list with search, filter, pagination | `client.getProducts()`, `client.getSearchSuggestions(query)` | ✅ |
38
+ | Product detail with variant picker, stock, price | `client.getProductBySlug()` + helpers | ✅ |
39
+ | Buyer customization fields (engraving, uploads, select) | `product.customizationFields`, `client.uploadCustomizationFile()` | ✅ |
40
+ | Cart (add, update, remove, coupon, totals) | `client.addToCart()`, `getCartTotals(cart)` | ✅ |
41
+ | Inventory reservation countdown | Cart expiry timestamp from `client.getCart()` | ✅ |
42
+ | Full checkout end-to-end with payment | `setShippingAddress → selectShippingMethod → getPaymentProviders → pay → handlePaymentSuccess → waitForOrder` | ✅ |
43
+ | Order confirmation (clear cart + wait for real order) | `client.handlePaymentSuccess()`, `client.waitForOrder()` | ✅ |
44
+ | Register + email verification flow | `client.registerCustomer()`, `client.verifyEmail()` | ✅ |
45
+ | Login + verification branch | `client.loginCustomer()` | ✅ |
46
+ | Forgot / reset password | `client.forgotPassword()`, `client.resetPassword()` | ✅ |
47
+ | OAuth sign-in buttons + callback handler | `client.getAvailableOAuthProviders()` | ✅ |
48
+ | Account area (profile + order history) | `client.getMyProfile()`, `client.getMyOrders()` | ✅ |
49
49
  | Loyalty & rewards (points balance + tiers + redeem) | `client.getLoyaltyStatus()`, `client.getAvailableRewards()`, `client.redeemLoyaltyReward(id)`, `client.reportSocialShare()` | conditional |
50
- | Global header: cart count + search autocomplete | `client.getCart()`, `client.getSearchSuggestions(query)` | ✅ |
51
- | Discount banners + product badges | `client.getDiscountBanners()`, `client.getProductDiscountBadge(productId)` | ✅ |
52
- | Product reviews on PDP + JSON-LD aggregateRating | `client.listProductReviews(id)`, `client.submitProductReview(id, …)` | ✅ |
53
- | Site chrome (header + footer + announcement bar) | `client.content.header.get()`, `client.content.footer.get()`, `client.content.announcement.list()` | ✅ |
54
- | FAQ page | `client.content.faq.get('main', locale)` | conditional |
55
- | Static pages catch-all (`/pages/[slug]`) | `client.content.page.getBySlug(slug, locale)` | conditional |
56
- | Multi-language + RTL (when i18n enabled) | `client.setLocale()`, `client.getStoreDirection(locale)` | conditional |
50
+ | Global header: cart count + search autocomplete | `client.getCart()`, `client.getSearchSuggestions(query)` | ✅ |
51
+ | Discount banners + product badges | `client.getDiscountBanners()`, `client.getProductDiscountBadge(productId)` | ✅ |
52
+ | Product reviews on PDP + JSON-LD aggregateRating | `client.listProductReviews(id)`, `client.submitProductReview(id, …)` | ✅ |
53
+ | Site chrome (header + footer + announcement bar) | `client.content.header.get()`, `client.content.footer.get()`, `client.content.announcement.list()` | ✅ |
54
+ | FAQ page | `client.content.faq.get('main', locale)` | conditional |
55
+ | Static pages catch-all (`/pages/[slug]`) | `client.content.page.getBySlug(slug, locale)` | conditional |
56
+ | Multi-language + RTL (when i18n enabled) | `client.setLocale()`, `client.getStoreDirection(locale)` | conditional |
57
57
 
58
58
  ---
59
59
 
@@ -874,6 +874,36 @@ console.log('Order created:', orderId);
874
874
 
875
875
  > **WARNING:** Do NOT use `submitGuestOrder()` for logged-in customers! Their orders won't be linked to their account and won't appear in their order history.
876
876
 
877
+ ### Address Autocomplete (optional, for the `line1` field)
878
+
879
+ Turn the shipping address's `line1` input into a typeahead instead of free
880
+ text. Suggestions come from Google Places; each resolved address is flagged
881
+ `inZone` against the store's configured shipping zones — a soft signal for a
882
+ warning banner, never a hard block.
883
+
884
+ ```typescript
885
+ const sessionToken = crypto.randomUUID(); // one per address-entry attempt
886
+
887
+ // Debounce (~300ms) as the shopper types
888
+ const suggestions = await client.getAddressSuggestions('Rothschild 1', sessionToken);
889
+ // [{ placeId: 'ChIJ...', description: 'Rothschild Blvd 1, Tel Aviv-Yafo' }, ...]
890
+
891
+ // On picking a suggestion — reuse the SAME sessionToken to end the session
892
+ const { address, inZone } = await client.getAddressDetails(suggestions[0].placeId, sessionToken);
893
+ // address: { line1, city, region, postalCode, country, lat, lng, formattedAddress }
894
+
895
+ await client.setShippingAddress(checkout.id, {
896
+ firstName: 'John',
897
+ lastName: 'Doe',
898
+ ...address,
899
+ });
900
+
901
+ if (!inZone) {
902
+ // Non-blocking: "This address is outside our regular delivery zones.
903
+ // You can still continue — we'll confirm delivery by phone."
904
+ }
905
+ ```
906
+
877
907
  ---
878
908
 
879
909
  ## Cart (Unified for All Users)
@@ -1573,7 +1603,7 @@ await client.addToCart(cartId, {
1573
1603
  ```typescript
1574
1604
  // Works for CartItem, CheckoutLineItem, and OrderItem — same shape
1575
1605
  const summary = Object.values(item.customizations ?? {})
1576
- .map(c => `${c.label}: ${Array.isArray(c.value) ? c.value.join(', ') : c.value}`)
1606
+ .map((c) => `${c.label}: ${Array.isArray(c.value) ? c.value.join(', ') : c.value}`)
1577
1607
  .join(' | ');
1578
1608
  // e.g. "Frame color: Gold | Add-ons: Gift wrap, Engraving"
1579
1609
  ```
@@ -2262,11 +2292,11 @@ interface Checkout {
2262
2292
  subtotal: string;
2263
2293
  discountAmount: string;
2264
2294
  shippingAmount: string;
2265
- taxAmount: string; // "0" in inclusive (VAT) mode — see taxBreakdown.totalTax
2295
+ taxAmount: string; // "0" in inclusive (VAT) mode — see taxBreakdown.totalTax
2266
2296
  taxBreakdown?: TaxBreakdown | null; // { totalTax, pricesIncludeTax, breakdown[] }
2267
2297
  total: string;
2268
2298
  couponCode?: string | null;
2269
- notes?: string | null; // Order note from setCheckoutCustomer/setShippingAddress
2299
+ notes?: string | null; // Order note from setCheckoutCustomer/setShippingAddress
2270
2300
  items: CheckoutLineItem[];
2271
2301
  itemCount: number;
2272
2302
  availableShippingRates?: ShippingRate[];
@@ -2404,12 +2434,12 @@ const checkoutId = checkout.id;
2404
2434
  Use this method to get ALL enabled payment providers and build dynamic UI.
2405
2435
 
2406
2436
  **Primary vs. additive methods (Shopify-parity).** Each provider carries a
2407
- `methodType`. A `CREDIT_CARD` provider is the *primary* card processor — the
2437
+ `methodType`. A `CREDIT_CARD` provider is the _primary_ card processor — the
2408
2438
  single method that settles the order (`defaultProvider`, `isAdditive: false`,
2409
- `presentation: 'card_form'`). Everything else is *additive* (`isAdditive: true`),
2439
+ `presentation: 'card_form'`). Everything else is _additive_ (`isAdditive: true`),
2410
2440
  e.g. PayPal is a `'WALLET'` with `presentation: 'express_button'`. Render additive
2411
2441
  methods as accelerated-checkout **express buttons above the card form** — they sit
2412
- *alongside* the primary, never replace it. (Exception: a wallet-only store has no
2442
+ _alongside_ the primary, never replace it. (Exception: a wallet-only store has no
2413
2443
  card processor, so its wallet becomes the `defaultProvider` and stands alone.)
2414
2444
 
2415
2445
  ```typescript
@@ -2967,8 +2997,9 @@ const rewards = await client.getAvailableRewards();
2967
2997
  // Redeem → spends points, returns a one-time coupon code to apply to the cart.
2968
2998
  // `discountType` tells you how to interpret `discountValue` (currency amount
2969
2999
  // for FIXED_DISCOUNT, 0-100 percent for PERCENT_DISCOUNT).
2970
- const { couponCode, discountType, discountValue, pointsBalance } =
2971
- await client.redeemLoyaltyReward(rewards[0].id);
3000
+ const { couponCode, discountType, discountValue, pointsBalance } = await client.redeemLoyaltyReward(
3001
+ rewards[0].id
3002
+ );
2972
3003
  await client.applyCoupon(cartId, couponCode);
2973
3004
 
2974
3005
  // Self-reported social share — grants the SOCIAL_SHARE bonus if configured
@@ -3545,8 +3576,8 @@ client.trackEvent({ eventType: 'pageview', path: '/products/shoes' });
3545
3576
  const params = new URLSearchParams(window.location.search);
3546
3577
  client.trackEvent({
3547
3578
  path: window.location.pathname,
3548
- utmSource: params.get('utm_source') ?? undefined,
3549
- utmMedium: params.get('utm_medium') ?? undefined,
3579
+ utmSource: params.get('utm_source') ?? undefined,
3580
+ utmMedium: params.get('utm_medium') ?? undefined,
3550
3581
  utmCampaign: params.get('utm_campaign') ?? undefined,
3551
3582
  screenWidth: window.innerWidth,
3552
3583
  lang: navigator.language,
@@ -3577,6 +3608,94 @@ const admin = new BrainerceClient({
3577
3608
  });
3578
3609
  ```
3579
3610
 
3611
+ ### Product Management
3612
+
3613
+ ```typescript
3614
+ import type { Product, CreateProductDto, UpdateProductDto } from 'brainerce';
3615
+
3616
+ // Create a simple product
3617
+ const product: Product = await client.createProduct({
3618
+ name: 'Classic T-Shirt',
3619
+ slug: 'classic-t-shirt', // Optional — auto-generated from name if omitted
3620
+ sku: 'TSH-001',
3621
+ description: 'Soft cotton tee.',
3622
+ basePrice: 29.99,
3623
+ salePrice: 24.99, // Optional
3624
+ costPrice: 12.0, // Optional, internal — never synced to platforms
3625
+ status: 'active', // 'active' | 'draft' (default: 'active')
3626
+ type: 'SIMPLE', // 'SIMPLE' | 'VARIABLE' (default: 'SIMPLE')
3627
+ categories: ['cat_id'], // Existing category IDs
3628
+ categoryNames: ['Apparel'], // Or assign/auto-create by name — merged with `categories`
3629
+ brands: ['brand_id'],
3630
+ tags: ['sale', 'summer'],
3631
+ images: [
3632
+ {
3633
+ url: 'https://cdn.example.com/tshirt.jpg',
3634
+ position: 0,
3635
+ isMain: true,
3636
+ alt: 'Classic T-Shirt',
3637
+ },
3638
+ ],
3639
+
3640
+ // Cross-platform identifiers (Google Shopping / Facebook Catalog / TikTok).
3641
+ // Provide gtin when the product has one; otherwise mpn + brand.
3642
+ gtin: '012345678905', // EAN/UPC/ISBN — validated (GS1 mod-10 checksum)
3643
+ mpn: 'MFR-TSH-001', // Manufacturer Part Number — used only when gtin is absent
3644
+
3645
+ // Google Merchant Center sale_price_effective_date — bounds when `salePrice`
3646
+ // is actually active. Set both together, or omit both (sale always active).
3647
+ salePriceStartsAt: '2026-06-01T00:00:00Z',
3648
+ salePriceEndsAt: '2026-06-30T23:59:59Z',
3649
+
3650
+ // Shipping weight & dimensions — used for accurate shipping-cost
3651
+ // calculation and Google Shopping's shipping_weight/shipping_length/etc.
3652
+ shippingWeightValue: 0.3,
3653
+ shippingWeightUnit: 'kg', // 'kg' | 'lb' | 'g' | 'oz'
3654
+ shippingLengthValue: 30,
3655
+ shippingWidthValue: 20,
3656
+ shippingHeightValue: 2,
3657
+ shippingDimensionUnit: 'cm', // 'cm' | 'in' — shared by length/width/height
3658
+ });
3659
+
3660
+ // Update a product — every field is optional; only send what changed.
3661
+ // Pass `null` on gtin/mpn/salePriceStartsAt/salePriceEndsAt/shipping* to clear them.
3662
+ const updated: Product = await client.updateProduct('prod_123', {
3663
+ salePrice: null, // End the sale
3664
+ salePriceStartsAt: null,
3665
+ salePriceEndsAt: null,
3666
+ } satisfies UpdateProductDto);
3667
+
3668
+ // Delete a product (optionally also delete from synced platforms)
3669
+ await client.deleteProduct('prod_123', { platforms: ['SHOPIFY'] });
3670
+
3671
+ // VARIABLE products: create the product with type: 'VARIABLE', then add variants
3672
+ const variableProduct = await client.createProduct({
3673
+ name: 'Hoodie',
3674
+ basePrice: 0,
3675
+ type: 'VARIABLE',
3676
+ });
3677
+ await client.bulkSaveVariants(variableProduct.id, {
3678
+ variants: [
3679
+ {
3680
+ sku: 'HOOD-S-BLK',
3681
+ attributes: { Size: 'S', Color: 'Black' },
3682
+ price: 49.99,
3683
+ stock: 20,
3684
+ isEnabled: true,
3685
+ },
3686
+ {
3687
+ sku: 'HOOD-M-BLK',
3688
+ attributes: { Size: 'M', Color: 'Black' },
3689
+ price: 49.99,
3690
+ stock: 15,
3691
+ isEnabled: true,
3692
+ },
3693
+ ],
3694
+ });
3695
+ ```
3696
+
3697
+ **GTIN vs MPN:** these are two different identifiers, not interchangeable — GTIN (EAN/UPC/ISBN) is a universal barcode; MPN is manufacturer-specific and only meaningful paired with a brand. Provide GTIN when the product has one; otherwise brand + MPN. A product typically needs one or the other, not both.
3698
+
3580
3699
  ### Taxonomy Management
3581
3700
 
3582
3701
  ```typescript
@@ -3698,6 +3817,37 @@ const euZone = await client.createShippingZone({
3698
3817
  regionIds: ['reg_eu'],
3699
3818
  });
3700
3819
 
3820
+ // Polygon zone ("draw on map") — a hand-drawn delivery area. GeoJSON
3821
+ // Polygon/MultiPolygon, [lng, lat] ring coordinates. Independent of
3822
+ // `countries`/`regions`/`postalCodes`: a zone matches if the address
3823
+ // satisfies *either* coverage, so both may be set on the same zone (this
3824
+ // example omits `countries` since the polygon is its only coverage).
3825
+ const polygonZone = await client.createShippingZone({
3826
+ name: 'Tel Aviv Metro',
3827
+ countries: [],
3828
+ geometry: {
3829
+ type: 'Polygon',
3830
+ coordinates: [
3831
+ [
3832
+ [34.75, 32.05],
3833
+ [34.75, 32.1],
3834
+ [34.82, 32.1],
3835
+ [34.82, 32.05],
3836
+ [34.75, 32.05],
3837
+ ],
3838
+ ],
3839
+ },
3840
+ });
3841
+
3842
+ // Restrict a zone to specific sales channels — `SalesChannel.id` or the
3843
+ // public `vc_*` connectionId. Empty/omitted = every channel; a non-empty
3844
+ // list hides the zone unless the checkout came from one of them.
3845
+ const mobileOnlyZone = await client.createShippingZone({
3846
+ name: 'US Domestic (mobile app only)',
3847
+ countries: ['US'],
3848
+ salesChannelIds: ['vc_abc123'],
3849
+ });
3850
+
3701
3851
  // Shipping Rates
3702
3852
  const rates = await client.getZoneShippingRates('zone_id');
3703
3853
  await client.createZoneShippingRate('zone_id', {
@@ -3720,9 +3870,9 @@ const label = await admin.createShippingLabel(orderId, {
3720
3870
  shippoRateObjectId: 'rate_8f123456789abcdef', // Shippo rate.object_id from checkout rates
3721
3871
  });
3722
3872
 
3723
- console.log(label.labelUrl); // PDF label URL for printing
3873
+ console.log(label.labelUrl); // PDF label URL for printing
3724
3874
  console.log(label.trackingNumber); // Auto-populated on the order
3725
- console.log(label.carrier); // e.g. 'USPS', 'UPS', 'FedEx'
3875
+ console.log(label.carrier); // e.g. 'USPS', 'UPS', 'FedEx'
3726
3876
  ```
3727
3877
 
3728
3878
  The `trackingNumber` is stored on the `Order` automatically — customers see it in their
@@ -4047,6 +4197,43 @@ const preview = await client.previewEmailTemplate('template_id', {
4047
4197
  });
4048
4198
  ```
4049
4199
 
4200
+ ### Storefront Bot Settings
4201
+
4202
+ Requires an API key with the `bot-settings:read` / `bot-settings:write` scopes.
4203
+
4204
+ ```typescript
4205
+ // Every connection on the store + its current (or default) settings
4206
+ const { connections, settings } = await client.getBotSettings();
4207
+
4208
+ // PATCH semantics — only the fields you pass are changed
4209
+ await client.updateBotSettings({
4210
+ salesChannelId: connections[0].salesChannelId,
4211
+ enabled: true,
4212
+ displayName: 'Maya',
4213
+ personaJson: {
4214
+ greeting: 'Hi! Looking for something specific?',
4215
+ tone: 'friendly', // 'friendly' | 'formal' | 'playful' | 'concise'
4216
+ starterQuestions: ['What sizes do you carry?', "What's your return policy?"],
4217
+ avoidTopics: 'Do not discuss competitor pricing or make medical claims.',
4218
+ customInstructions: 'Always mention free shipping over $75.',
4219
+ capabilities: { productQa: true, recommendations: true, returns: false },
4220
+ },
4221
+ });
4222
+ ```
4223
+
4224
+ ### Storefront Bot Conversations
4225
+
4226
+ Studio inbox transcripts + on-demand summarization. Requires the `bot-conversations:read` / `bot-conversations:write` scopes.
4227
+
4228
+ ```typescript
4229
+ const { data: conversations } = await client.listBotConversations({ limit: 20 });
4230
+ const transcript = await client.getBotConversation(conversations[0].id);
4231
+
4232
+ // Persists a summary on the conversation; short threads (10 or fewer messages)
4233
+ // are a no-op — { summarized: false, summary: null } — no credits charged.
4234
+ const { summarized, summary } = await client.summarizeBotConversation(conversations[0].id);
4235
+ ```
4236
+
4050
4237
  ### Sync Conflict Resolution
4051
4238
 
4052
4239
  ```typescript
@@ -5129,7 +5316,7 @@ const forms = await brainerce.contactForms.list();
5129
5316
 
5130
5317
  ## Storefront Bot (AI chat widget)
5131
5318
 
5132
- Add the store's AI shopping assistant with one line. All configuration (name, avatar, colors, greeting, starter questions, guardrails) lives in the merchant dashboard — the widget renders nothing until the bot is switched Live there.
5319
+ Add the store's AI shopping assistant with one line. Configuration (name, avatar, colors, greeting, starter questions, guardrails) is normally set in the merchant dashboard — the widget renders nothing until the bot is switched Live there. Those same settings are also readable/writable via `client.getBotSettings()` / `client.updateBotSettings()`, and conversation transcripts + summarization via `client.listBotConversations()` / `client.summarizeBotConversation()` — see [Storefront Bot Settings](#storefront-bot-settings) and [Storefront Bot Conversations](#storefront-bot-conversations) in the Admin API Reference.
5133
5320
 
5134
5321
  ```html
5135
5322
  <!-- zero-code embed: keep the tag exactly this bare (no integrity/crossorigin) -->