@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.
- package/README.md +342 -7
- package/STOREFRONT_CLARITY.md +140 -0
- package/STOREFRONT_CONSTRUCTION.md +92 -0
- package/STOREFRONT_LEADS.md +130 -0
- package/STOREFRONT_LEGAL.md +66 -0
- package/STOREFRONT_MARKETING.md +286 -0
- package/STOREFRONT_PERFORMANCE.md +265 -0
- package/STOREFRONT_PREVIEW.md +474 -0
- package/client.d.ts +7 -1
- package/errors.d.ts +9 -1
- package/graphql.cjs +63 -0
- package/graphql.d.ts +2 -2
- package/graphql.js +63 -0
- package/index.cjs +1542 -192
- package/index.d.ts +8 -6
- package/index.js +1542 -192
- package/marketing/attribution.d.ts +51 -0
- package/marketing/clarity.d.ts +3 -0
- package/marketing/domain.d.ts +1 -0
- package/marketing/ga4.d.ts +2 -0
- package/marketing/index.d.ts +5 -0
- package/marketing/runtime.d.ts +2 -0
- package/marketing/sanitize.d.ts +2 -0
- package/marketing/types.d.ts +109 -0
- package/media.d.ts +45 -2
- package/modules/auth.d.ts +3 -2
- package/modules/availability.d.ts +2 -2
- package/modules/cart-session.d.ts +2 -2
- package/modules/cart.d.ts +3 -3
- package/modules/catalog.d.ts +15 -3
- package/modules/content.d.ts +2 -2
- package/modules/marketing.d.ts +5 -0
- package/modules/orders.d.ts +2 -2
- package/modules/profile.d.ts +1 -1
- package/modules/static-catalog.d.ts +13 -0
- package/modules/website.d.ts +7 -1
- package/package.json +14 -3
- package/react/context-registry.d.ts +9 -0
- package/react/context.d.ts +1 -1
- package/react/hooks.d.ts +23 -2
- package/react/marketing-context.d.ts +3 -0
- package/react/marketing-provider.d.ts +10 -0
- package/react/preview/cache.d.ts +23 -0
- package/react/preview/context.d.ts +5 -0
- package/react/preview/navigation.d.ts +9 -0
- package/react/preview/provider.d.ts +3 -0
- package/react/preview/shell.d.ts +3 -0
- package/react/preview/toolbar.d.ts +3 -0
- package/react/preview/types.d.ts +60 -0
- package/react/preview.cjs +742 -0
- package/react/preview.d.ts +14 -0
- package/react/preview.js +730 -0
- package/react/website-gate.d.ts +22 -0
- package/react.cjs +3432 -1780
- package/react.d.ts +8 -4
- package/react.js +3428 -1772
- package/skills/bacano-web/SKILL.md +109 -0
- package/skills/bacano-web/agents/openai.yaml +4 -0
- package/skills/bacano-web/references/common.md +91 -0
- package/skills/bacano-web/references/informational.md +39 -0
- package/skills/bacano-web/references/report.md +52 -0
- package/skills/bacano-web/references/storefront.md +116 -0
- package/skills/bacano-web/scripts/audit-http.mjs +247 -0
- package/skills/bacano-web/scripts/cli.mjs +222 -0
- package/skills/bacano-web/scripts/postinstall.mjs +8 -0
- package/transport/documents.d.ts +8 -6
- package/transport/graphql.d.ts +13 -1
- 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.
|