@bacano/sdk 1.0.0 → 1.1.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 (68) hide show
  1. package/README.md +342 -7
  2. package/STOREFRONT_CLARITY.md +140 -0
  3. package/STOREFRONT_CONSTRUCTION.md +92 -0
  4. package/STOREFRONT_LEADS.md +130 -0
  5. package/STOREFRONT_LEGAL.md +66 -0
  6. package/STOREFRONT_MARKETING.md +286 -0
  7. package/STOREFRONT_PERFORMANCE.md +265 -0
  8. package/STOREFRONT_PREVIEW.md +474 -0
  9. package/client.d.ts +7 -1
  10. package/errors.d.ts +9 -1
  11. package/graphql.cjs +63 -0
  12. package/graphql.d.ts +2 -2
  13. package/graphql.js +63 -0
  14. package/index.cjs +1542 -192
  15. package/index.d.ts +8 -6
  16. package/index.js +1542 -192
  17. package/marketing/attribution.d.ts +51 -0
  18. package/marketing/clarity.d.ts +3 -0
  19. package/marketing/domain.d.ts +1 -0
  20. package/marketing/ga4.d.ts +2 -0
  21. package/marketing/index.d.ts +5 -0
  22. package/marketing/runtime.d.ts +2 -0
  23. package/marketing/sanitize.d.ts +2 -0
  24. package/marketing/types.d.ts +109 -0
  25. package/media.d.ts +45 -2
  26. package/modules/auth.d.ts +3 -2
  27. package/modules/availability.d.ts +2 -2
  28. package/modules/cart-session.d.ts +2 -2
  29. package/modules/cart.d.ts +3 -3
  30. package/modules/catalog.d.ts +15 -3
  31. package/modules/content.d.ts +2 -2
  32. package/modules/marketing.d.ts +5 -0
  33. package/modules/orders.d.ts +2 -2
  34. package/modules/profile.d.ts +1 -1
  35. package/modules/static-catalog.d.ts +13 -0
  36. package/modules/website.d.ts +7 -1
  37. package/package.json +14 -3
  38. package/react/context-registry.d.ts +9 -0
  39. package/react/context.d.ts +1 -1
  40. package/react/hooks.d.ts +23 -2
  41. package/react/marketing-context.d.ts +3 -0
  42. package/react/marketing-provider.d.ts +10 -0
  43. package/react/preview/cache.d.ts +23 -0
  44. package/react/preview/context.d.ts +5 -0
  45. package/react/preview/navigation.d.ts +9 -0
  46. package/react/preview/provider.d.ts +3 -0
  47. package/react/preview/shell.d.ts +3 -0
  48. package/react/preview/toolbar.d.ts +3 -0
  49. package/react/preview/types.d.ts +60 -0
  50. package/react/preview.cjs +742 -0
  51. package/react/preview.d.ts +14 -0
  52. package/react/preview.js +730 -0
  53. package/react/website-gate.d.ts +22 -0
  54. package/react.cjs +3432 -1780
  55. package/react.d.ts +8 -4
  56. package/react.js +3428 -1772
  57. package/skills/bacano-web/SKILL.md +109 -0
  58. package/skills/bacano-web/agents/openai.yaml +4 -0
  59. package/skills/bacano-web/references/common.md +91 -0
  60. package/skills/bacano-web/references/informational.md +39 -0
  61. package/skills/bacano-web/references/report.md +52 -0
  62. package/skills/bacano-web/references/storefront.md +116 -0
  63. package/skills/bacano-web/scripts/audit-http.mjs +247 -0
  64. package/skills/bacano-web/scripts/cli.mjs +222 -0
  65. package/skills/bacano-web/scripts/postinstall.mjs +8 -0
  66. package/transport/documents.d.ts +8 -6
  67. package/transport/graphql.d.ts +13 -1
  68. package/types.d.ts +202 -0
@@ -0,0 +1,265 @@
1
+ # Storefront performance contract
2
+
3
+ This is the canonical performance contract for storefronts built with
4
+ `@bacano/sdk`. It applies to static, hybrid and server-rendered storefronts.
5
+
6
+ ## Required architecture
7
+
8
+ 1. Create one `BacanoClient` and mount one `BacanoProvider` at the application
9
+ root. Do not create independent clients per component or route.
10
+ 2. Generate catalog routes and their page data from one build snapshot. Never
11
+ call `getProductBySlug()` once for every product during the same build.
12
+ 3. Render useful catalog and product HTML during the static/server render.
13
+ Loading skeletons are fallbacks, not the initial production document.
14
+ 4. Use the SDK for every Bacano request. Storefronts must not duplicate GraphQL
15
+ documents, gateway limits, retries or storage transform rules.
16
+ 5. Keep build data separate from volatile state. Prices and availability may be
17
+ refreshed after hydration with the lightweight live-state contract.
18
+ 6. A live refresh must preserve the static content when it fails. A public page
19
+ must not become empty because Bacano is temporarily unavailable.
20
+
21
+ ## Static catalog bootstrap
22
+
23
+ Use one bootstrap per website build:
24
+
25
+ ```ts
26
+ const bootstrap = await client.catalog.getStaticCatalogBootstrap({
27
+ categoryListKey: "categorias-web",
28
+ attributeListKey: "atributos",
29
+ pageSize: 100,
30
+ initialPageSize: 24,
31
+ maxProducts: 10_000,
32
+ sortBy: "name_asc",
33
+ });
34
+
35
+ // Render this in the initial catalog HTML.
36
+ const initialCatalogPage = bootstrap.initialPage;
37
+
38
+ // Reuse the same objects for routes, metadata, JSON-LD and sitemap.
39
+ const productByRoute = new Map(
40
+ bootstrap.products.map((product) => [product.slug || product.id, product]),
41
+ );
42
+ ```
43
+
44
+ The SDK paginates internally, respects `Retry-After`, rejects inconsistent
45
+ snapshots and avoids repeating facets on every page. Builds must fail clearly
46
+ if this contract fails; they must not silently publish an empty catalog.
47
+
48
+ ### Listing variants and detail variants
49
+
50
+ The static bootstrap deliberately exposes two projections on each product:
51
+
52
+ - `product.variants` contains only variants that were saleable when Bacano
53
+ generated the catalog page. Cards, price ranges, filters and listing
54
+ visibility must use this projection.
55
+ - `product.detailVariants` also contains enabled, published variants with a
56
+ public price that are currently sold out. Product detail selectors must use
57
+ this projection so a customer can see unavailable sizes instead of assuming
58
+ they do not exist.
59
+
60
+ Use the public helper so cached snapshots from older SDK versions remain safe:
61
+
62
+ ```ts
63
+ import { getProductDetailVariants } from "@bacano/sdk";
64
+
65
+ const detailVariants = getProductDetailVariants(product);
66
+ const liveState = await client.catalog.getProductLiveState(
67
+ detailVariants.map((variant) => variant.id),
68
+ );
69
+ ```
70
+
71
+ `getProduct()` and `getProductBySlug()` expose the complete detail projection
72
+ through `variants` as well. A sold-out variant returns `inStock: false` and
73
+ `maxPurchasableQuantity: 0` from live state. Never use `detailVariants` to
74
+ calculate a card's minimum price or to make a completely sold-out product
75
+ appear in listings.
76
+
77
+ ## Live price and availability
78
+
79
+ After hydration, refresh only the variants visible on screen:
80
+
81
+ ```ts
82
+ const liveState = await client.catalog.getProductLiveState(visibleVariantIds);
83
+ ```
84
+
85
+ One call accepts at most 100 unique variant IDs. Debounce filter/pagination
86
+ changes and do not refresh the whole static catalog. The response only contains
87
+ variant ID, current pricing, availability and `maxPurchasableQuantity`.
88
+
89
+ For a local cart, batch all distinct variant IDs when the cart opens and use
90
+ `maxPurchasableQuantity` as the total cap per variant, including units already
91
+ in the cart. Disable quantity increases at the cap and explain that the maximum
92
+ available was reached. Do not poll after every click or persist the limit as
93
+ catalog data: another checkout may consume stock, and Bacano remains
94
+ authoritative when checkout starts and finishes.
95
+
96
+ ## Responsive media
97
+
98
+ Every SDK product/content media object can expose `responsiveSources`. Use it
99
+ with a realistic `sizes` value so mobile devices do not download desktop media:
100
+
101
+ ```tsx
102
+ import { formatMediaSrcSet } from "@bacano/sdk";
103
+
104
+ <img
105
+ src={media.cardUrl || media.url}
106
+ srcSet={formatMediaSrcSet(media.responsiveSources ?? []) || undefined}
107
+ sizes="(max-width: 640px) 50vw, (max-width: 1200px) 33vw, 320px"
108
+ width={320}
109
+ height={400}
110
+ loading="lazy"
111
+ decoding="async"
112
+ alt={media.altText || product.name}
113
+ />;
114
+ ```
115
+
116
+ The first visible product image may use eager loading/high priority. Images
117
+ below the fold and recommendation blocks must remain lazy. Storefronts must not
118
+ append storage transformation query parameters manually.
119
+
120
+ ### Banners and full-width heroes
121
+
122
+ Editorial `ContentMedia` images additionally expose:
123
+
124
+ ```ts
125
+ type ContentMedia = {
126
+ src?: string | null;
127
+ url?: string | null;
128
+ originalUrl?: string | null;
129
+ thumbnailUrl?: string | null;
130
+ cardUrl?: string | null;
131
+ detailUrl?: string | null;
132
+ heroUrl?: string | null;
133
+ responsiveSources?: Array<{ width: number; url: string }>;
134
+ originalWidth?: number | null;
135
+ originalHeight?: number | null;
136
+ };
137
+ ```
138
+
139
+ `src` and `url` remain available as compatibility aliases. New storefront code
140
+ must select the semantic URL that matches its rendering context.
141
+
142
+ `heroUrl` is an optimized WebP fallback for wide editorial media. Bacano uses
143
+ the candidates `640`, `960`, `1400`, `1920` and `2560`, but removes every size
144
+ larger than the original file and adds the exact intrinsic width as the final
145
+ candidate. A 1920 px source therefore ends at 1920 px; a 1180 px source ends at
146
+ 1180 px. When Bacano cannot determine the dimensions, `heroUrl` safely falls
147
+ back to `originalUrl` and no speculative `srcset` is emitted.
148
+
149
+ For separate mobile and desktop artwork, keep the breakpoint in the storefront
150
+ and let the browser choose density within each source:
151
+
152
+ ```tsx
153
+ import { formatMediaSrcSet, type ContentMedia } from "@bacano/sdk";
154
+
155
+ export function EditorialHero({
156
+ desktop,
157
+ mobile,
158
+ }: {
159
+ desktop: ContentMedia;
160
+ mobile: ContentMedia;
161
+ }) {
162
+ return (
163
+ <picture>
164
+ <source
165
+ media="(max-width: 767px)"
166
+ srcSet={formatMediaSrcSet(mobile.responsiveSources ?? []) || undefined}
167
+ sizes="100vw"
168
+ />
169
+ <img
170
+ src={desktop.heroUrl ?? desktop.originalUrl ?? ""}
171
+ srcSet={formatMediaSrcSet(desktop.responsiveSources ?? []) || undefined}
172
+ sizes="100vw"
173
+ width={desktop.originalWidth ?? undefined}
174
+ height={desktop.originalHeight ?? undefined}
175
+ alt={desktop.alt ?? ""}
176
+ decoding="async"
177
+ />
178
+ </picture>
179
+ );
180
+ }
181
+ ```
182
+
183
+ This contract works in static exports: all URLs are deterministic, cacheable
184
+ gateway URLs and the browser selects the appropriate candidate at runtime for
185
+ device-pixel ratios 1 and 2. Do not use `detailUrl` as the fallback for a
186
+ full-width hero and do not append `w`, `q` or `f` parameters in a storefront.
187
+
188
+ ## Request ownership
189
+
190
+ - Website initialization: obtain one public configuration and use it to seed the
191
+ SDK context. Do not resolve the same website again through GraphQL.
192
+ - Catalog build: one paginated bootstrap.
193
+ - Initial catalog navigation: zero mandatory catalog requests after hydration;
194
+ the static page is already usable.
195
+ - Live state: one batched request for visible variants.
196
+ - Recommendations: load independently and lazily; they must not delay product
197
+ title, price, selected variant or primary media.
198
+ - Shipping policy and public configuration: read once in the root data layer and
199
+ share the result.
200
+
201
+ ### Shared website bootstrap
202
+
203
+ The public configuration is the canonical bootstrap shared by buyer accounts,
204
+ marketing and the SDK client. A static or server-rendered storefront should
205
+ fetch it once, cache it for the build/render window and pass its context into
206
+ the root provider:
207
+
208
+ ```tsx
209
+ import {
210
+ getWebsiteContextFromPublicConfig,
211
+ getWebsitePublicConfig,
212
+ } from "@bacano/sdk";
213
+ import { BacanoMarketingProvider, BacanoProvider } from "@bacano/sdk/react";
214
+
215
+ const publicConfig = await getWebsitePublicConfig({
216
+ apiUrl,
217
+ websiteSlug,
218
+ origin: canonicalOrigin,
219
+ });
220
+
221
+ <BacanoProvider
222
+ config={{
223
+ apiUrl,
224
+ websiteSlug,
225
+ initialContext: getWebsiteContextFromPublicConfig(publicConfig),
226
+ }}
227
+ >
228
+ <BacanoMarketingProvider
229
+ publicConfig={publicConfig}
230
+ scriptLoadStrategy="idle"
231
+ >
232
+ {children}
233
+ </BacanoMarketingProvider>
234
+ </BacanoProvider>;
235
+ ```
236
+
237
+ `scriptLoadStrategy="idle"` prepares the GA4, Meta, and Clarity command queues
238
+ immediately, so events are not lost, but postpones downloading their vendor
239
+ scripts until browser idle time or the first interaction. The default remains
240
+ `"immediate"` for backward compatibility.
241
+
242
+ If the storefront refreshes public configuration in the browser, it must keep
243
+ the static bootstrap while the refresh is pending and only replace it when the
244
+ payload actually changes. Configuration failure must not blank the storefront.
245
+
246
+ Mount third-party authentication only on routes that require it, such as
247
+ checkout, sign-in and customer account pages. Public catalog and product pages
248
+ must not download an authentication runtime merely because buyer accounts are
249
+ enabled for the website.
250
+
251
+ ## Acceptance checklist for storefront agents
252
+
253
+ - [ ] View source contains real product cards/details, not only skeletons.
254
+ - [ ] Routes, sitemap, metadata and JSON-LD reuse the same build bootstrap.
255
+ - [ ] No direct GraphQL and no per-product build fetch loop.
256
+ - [ ] One `BacanoProvider` and no duplicated website/config requests.
257
+ - [ ] The provider receives `initialContext` from the shared public config.
258
+ - [ ] Marketing commands queue before scripts and idle loading loses no events.
259
+ - [ ] Authentication vendor scripts are absent from public catalog routes.
260
+ - [ ] `srcset` and `sizes` are present on catalog and product media.
261
+ - [ ] Live refresh is batched, debounced and failure-safe.
262
+ - [ ] Recommendations and below-fold media do not block the primary content.
263
+ - [ ] Mobile and desktop Lighthouse runs show no material layout shift.
264
+ - [ ] A production trace confirms no duplicated catalog, shipping or website
265
+ bootstrap calls during the first navigation.