@commerce-blocks/sdk 1.3.0 → 2.0.0-alpha.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.
Files changed (4) hide show
  1. package/README.md +89 -64
  2. package/dist/index.d.ts +168 -101
  3. package/dist/index.js +1246 -1111
  4. package/package.json +7 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @commerce-blocks/sdk
2
2
 
3
- ES module SDK for Shopify storefronts with Layers API integration.
3
+ ES module SDK powered by Layers API with optional Shopify Storefront enrichment.
4
4
 
5
5
  ## Installation
6
6
 
@@ -22,7 +22,7 @@ const result = createSdk({
22
22
  ],
23
23
  facets: ['options.color', 'options.size', 'vendor'],
24
24
 
25
- // Opt in to Shopify Storefront API for richer product data
25
+ // Opt in to Storefront API for additional product data
26
26
  // enableStorefront: true,
27
27
  // shop: 'your-store.myshopify.com',
28
28
  // storefrontPublicToken: 'your-storefront-token',
@@ -38,18 +38,6 @@ if (result.error) {
38
38
 
39
39
  ## Configuration
40
40
 
41
- ### Shopify (optional)
42
-
43
- Storefront API is opt-in. Enable it for richer product data (metafields, full variant info, collection/page metadata).
44
-
45
- | Option | Type | Required | Description |
46
- | ----------------------- | ------------- | ----------------------------- | ---------------------------------------------------- |
47
- | `enableStorefront` | `boolean` | No | Enable Storefront API hydration (default: `false`) |
48
- | `shop` | `string` | When `enableStorefront: true` | Store domain |
49
- | `storefrontPublicToken` | `string` | When `enableStorefront: true` | Storefront API public access token |
50
- | `storefrontApiVersion` | `string` | No | API version (default: `2025-01`) |
51
- | `fetch` | `CustomFetch` | No | Custom fetch implementation (SSR, testing, proxying) |
52
-
53
41
  ### Layers
54
42
 
55
43
  | Option | Type | Required | Description |
@@ -61,6 +49,18 @@ Storefront API is opt-in. Enable it for richer product data (metafields, full va
61
49
  | `layersBaseUrl` | `string` | No | Custom API URL |
62
50
  | `fetch` | `CustomFetch` | No | Custom fetch implementation (SSR, testing, proxying) |
63
51
 
52
+ ### Storefront (optional)
53
+
54
+ Storefront API is opt-in. Enable it for collection/page metadata and Shopify-specific fields (metafields, variant detail).
55
+
56
+ | Option | Type | Required | Description |
57
+ | ----------------------- | ------------- | ----------------------------- | ---------------------------------------------------- |
58
+ | `enableStorefront` | `boolean` | No | Enable Storefront API hydration (default: `false`) |
59
+ | `shop` | `string` | When `enableStorefront: true` | Store domain |
60
+ | `storefrontPublicToken` | `string` | When `enableStorefront: true` | Storefront API public access token |
61
+ | `storefrontApiVersion` | `string` | No | API version (default: `2025-01`) |
62
+ | `fetch` | `CustomFetch` | No | Custom fetch implementation (SSR, testing, proxying) |
63
+
64
64
  Layers API supports identity tracking for personalization. Pass identity fields via request context:
65
65
 
66
66
  ```typescript
@@ -87,14 +87,14 @@ interface LayersIdentity {
87
87
 
88
88
  ### Extensibility
89
89
 
90
- | Option | Type | Description |
91
- | ------------------ | ------------------------------- | ---------------------------------- |
92
- | `extendProduct` | `({ base, raw, shopify }) => P` | Transform products after hydration |
93
- | `extendCollection` | `(result, raw) => result` | Transform collection results |
94
- | `extendSearch` | `(result, raw) => result` | Transform search results |
95
- | `extendBlock` | `(result, raw) => result` | Transform blocks results |
96
- | `transformFilters` | `(filters) => FilterGroup` | Custom filter transformation |
97
- | `filterMap` | `FilterMap` | URL-friendly filter key mapping |
90
+ | Option | Type | Description |
91
+ | ------------------ | ---------------------------------- | ---------------------------------- |
92
+ | `extendProduct` | `({ base, raw, storefront }) => P` | Transform products after hydration |
93
+ | `extendCollection` | `(result, raw) => result` | Transform collection results |
94
+ | `extendSearch` | `(result, raw) => result` | Transform search results |
95
+ | `extendBlock` | `(result, raw) => result` | Transform blocks results |
96
+ | `transformFilters` | `(filters) => FilterGroup` | Custom filter transformation |
97
+ | `filterMap` | `FilterMap` | URL-friendly filter key mapping |
98
98
 
99
99
  Once configured, extenders and transformers are applied automatically. You pass simple inputs and receive transformed outputs—no manual transformation needed on each call.
100
100
 
@@ -102,7 +102,7 @@ Once configured, extenders and transformers are applied automatically. You pass
102
102
  // Configure once at initialization
103
103
  const sdk = createSdk({
104
104
  // ... base config
105
- extendProduct: ({ base, raw, shopify }) => ({
105
+ extendProduct: ({ base, raw, storefront }) => ({
106
106
  ...base,
107
107
  isNew: raw.tags?.includes('new') ?? false,
108
108
  rating: raw.calculated?.average_rating,
@@ -457,7 +457,7 @@ effect(() => {
457
457
 
458
458
  | Parameter | Type | Required | Description |
459
459
  | --------------------- | ------------- | -------- | ---------------------------------------- |
460
- | `ids` | `string[]` | Yes | Shopify Product GIDs |
460
+ | `ids` | `string[]` | Yes | Product GIDs |
461
461
  | `meta.collection` | `string` | No | Collection handle to fetch metadata |
462
462
  | `meta.page` | `string` | No | Page handle to fetch metadata |
463
463
  | `meta.includeFilters` | `boolean` | No | Include available filters for collection |
@@ -503,7 +503,7 @@ interface CollectionResult {
503
503
  facets: Record<string, Record<string, number>>
504
504
  facetRanges?: Record<string, { min: number; max: number }>
505
505
  attributionToken: string
506
- collection?: ShopifyCollection
506
+ collection?: StorefrontCollection
507
507
  }
508
508
 
509
509
  interface SearchResult {
@@ -531,8 +531,8 @@ interface BlocksResult {
531
531
 
532
532
  interface StorefrontResult {
533
533
  products: Product[]
534
- collection?: ShopifyCollection
535
- page?: ShopifyPage
534
+ collection?: StorefrontCollection
535
+ page?: StorefrontPage
536
536
  }
537
537
  ```
538
538
 
@@ -551,7 +551,7 @@ if (result.error) {
551
551
  break
552
552
  case 'ApiError':
553
553
  // Server errors, rate limits
554
- console.log(result.error.source) // 'layers' | 'shopify'
554
+ console.log(result.error.source) // 'layers' | 'storefront'
555
555
  console.log(result.error.status) // HTTP status code
556
556
  break
557
557
  case 'ValidationError':
@@ -585,7 +585,7 @@ if (result.error) {
585
585
  | Field | Type | Description |
586
586
  | -------------- | -------------- | -------------------------------------------------- |
587
587
  | `code` | `ApiErrorCode` | `NOT_FOUND`, `RATE_LIMITED`, `GRAPHQL_ERROR`, etc. |
588
- | `source` | `ApiSource` | `'layers'` or `'shopify'` |
588
+ | `source` | `ApiSource` | `'layers'` or `'storefront'` |
589
589
  | `status` | `number?` | HTTP status code |
590
590
  | `retryable` | `boolean` | Whether the request can be retried |
591
591
  | `retryAfterMs` | `number?` | Suggested retry delay |
@@ -606,6 +606,8 @@ if (result.error) {
606
606
 
607
607
  ### Error Helpers
608
608
 
609
+ Always check `isRetryable()` before calling `getRetryDelay()`. `getRetryDelay()` returns `undefined` for non-retryable errors — it's not meant to be used standalone.
610
+
609
611
  ```typescript
610
612
  import { isRetryable, getRetryDelay } from '@commerce-blocks/sdk'
611
613
 
@@ -636,7 +638,7 @@ const priceFilter = filter(and(gte('price', 50), lte('price', 200)))
636
638
 
637
639
  // Use in query
638
640
  await collection.execute({
639
- filters: multiFilter.filter_group,
641
+ filters: multiFilter,
640
642
  })
641
643
  ```
642
644
 
@@ -675,57 +677,80 @@ await collection.execute({
675
677
  })
676
678
  ```
677
679
 
678
- ## Hooks
680
+ ## Product Card
679
681
 
680
- ### `useProductCard()` - Product Card Helper
682
+ ### `createProductCard()` - Reactive Product Card Controller
681
683
 
682
- Utility for building product cards with variant selection and availability logic.
684
+ Creates a reactive controller for product cards with variant selection and availability logic. All derived values are computed signals that auto-update when inputs change.
683
685
 
684
686
  ```typescript
685
- import { useProductCard } from '@commerce-blocks/sdk'
687
+ import { createProductCard, effect } from '@commerce-blocks/sdk'
686
688
 
687
- const card = useProductCard({
689
+ const card = createProductCard({
688
690
  product,
689
- selectedOptions: [{ name: 'Size', value: 'M' }],
690
- breakAwayOptions: [{ name: 'Color', value: 'Red' }],
691
+ selectedOptions: [{ name: 'Size', value: '7' }],
692
+ breakoutOptions: [{ name: 'Stone', value: 'Ruby' }],
691
693
  })
692
694
 
693
- // Access computed data
694
- card.variants // Variants filtered by breakAwayOptions
695
- card.selectedVariant // Currently selected variant
696
- card.options // Available options (excludes breakaway option names)
697
- card.images // Variant image or product images
698
- card.carouselIndex // Image index for carousel
695
+ // Subscribe to reactive state
696
+ effect(() => {
697
+ console.log('Selected variant:', card.selectedVariant.value)
698
+ console.log('Available options:', card.options.value)
699
+ })
700
+
701
+ // Access reactive signals
702
+ card.variants.value // Variants filtered by breakoutOptions
703
+ card.selectedVariant.value // Currently selected variant
704
+ card.options.value // Available options (excludes breakout option names)
705
+ card.images.value // Variant image or product images
706
+ card.carouselIndex.value // Image index for carousel
699
707
 
700
- // Helper methods
708
+ // Mutate state via actions
709
+ card.selectOption('Size', 'L') // Select a single option
710
+ card.setSelectedOptions([{ name: 'Size', value: 'L' }]) // Merge options by name
711
+ card.setBreakoutOptions([{ name: 'Stone', value: 'Emerald' }]) // Change breakout filter
712
+
713
+ // Query methods (pure)
701
714
  card.getOptionValues('Size') // ['S', 'M', 'L']
702
715
  card.getSwatches('Color') // Swatch definitions
703
716
  card.isOptionAvailable('Size', 'L') // Check if selecting 'L' results in available variant
704
717
  card.getVariantByOptions([{ name: 'Size', value: 'L' }])
718
+
719
+ // Cleanup
720
+ card.dispose()
705
721
  ```
706
722
 
707
723
  **Parameters:**
708
724
 
709
- | Parameter | Type | Required | Description |
710
- | ------------------ | ----------------- | -------- | ------------------------------------------------------------- |
711
- | `product` | `Product` | Yes | Product to display |
712
- | `selectedOptions` | `ProductOption[]` | No | Currently selected options |
713
- | `breakAwayOptions` | `ProductOption[]` | No | Options to filter visible variants (e.g., pre-selected color) |
714
-
715
- **Returns:**
716
-
717
- | Property | Type | Description |
718
- | ----------------- | ------------------------ | --------------------------------------- |
719
- | `product` | `Product` | Original product |
720
- | `variants` | `ProductVariant[]` | Variants matching breakAwayOptions |
721
- | `selectedVariant` | `ProductVariant \| null` | Variant matching all selected options |
722
- | `selectedOptions` | `ProductOption[]` | Combined breakaway + selected options |
723
- | `options` | `RichProductOption[]` | Available options from visible variants |
724
- | `optionNames` | `string[]` | Option names (excludes breakaway) |
725
- | `images` | `Image[]` | Variant image or product images |
726
- | `carouselIndex` | `number` | Index of selected variant's image |
727
-
728
- **Helper Methods:**
725
+ | Parameter | Type | Required | Description |
726
+ | ----------------- | ----------------- | -------- | -------------------------------------------------------- |
727
+ | `product` | `Product` | Yes | Product to display |
728
+ | `selectedOptions` | `ProductOption[]` | No | Initially selected options |
729
+ | `breakoutOptions` | `ProductOption[]` | No | Options to filter variants (auto-set from variant tiles) |
730
+
731
+ **Reactive State (ReadonlySignal):**
732
+
733
+ | Property | Type | Description |
734
+ | ----------------- | ---------------------------------------- | --------------------------------------- |
735
+ | `product` | `Product` | Original product (static) |
736
+ | `variants` | `ReadonlySignal<ProductVariant[]>` | Variants matching breakoutOptions |
737
+ | `selectedVariant` | `ReadonlySignal<ProductVariant \| null>` | Variant matching all selected options |
738
+ | `selectedOptions` | `ReadonlySignal<ProductOption[]>` | Combined breakout + selected options |
739
+ | `options` | `ReadonlySignal<RichProductOption[]>` | Available options from visible variants |
740
+ | `optionNames` | `ReadonlySignal<string[]>` | Option names (excludes breakout) |
741
+ | `images` | `ReadonlySignal<Image[]>` | Variant image or product images |
742
+ | `carouselIndex` | `ReadonlySignal<number>` | Index of selected variant's image |
743
+
744
+ **Actions:**
745
+
746
+ | Method | Description |
747
+ | ----------------------------- | ------------------------------- |
748
+ | `selectOption(name, value)` | Select a single option by name |
749
+ | `setSelectedOptions(options)` | Merge options by name |
750
+ | `setBreakoutOptions(options)` | Replace breakout filter options |
751
+ | `dispose()` | Cleanup controller |
752
+
753
+ **Query Methods:**
729
754
 
730
755
  | Method | Returns | Description |
731
756
  | -------------------------------- | ------------------------ | ------------------------------------------------------ |