@magicstoreai/hydrogen 0.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/CATALOGUE.md +931 -0
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/core.cjs +746 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +252 -0
- package/dist/core.d.ts +252 -0
- package/dist/core.js +706 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +1046 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +790 -0
- package/dist/index.d.ts +790 -0
- package/dist/index.js +1000 -0
- package/dist/index.js.map +1 -0
- package/dist/seo.cjs +101 -0
- package/dist/seo.cjs.map +1 -0
- package/dist/seo.d.cts +83 -0
- package/dist/seo.d.ts +83 -0
- package/dist/seo.js +73 -0
- package/dist/seo.js.map +1 -0
- package/dist/server.cjs +172 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +67 -0
- package/dist/server.d.ts +67 -0
- package/dist/server.js +139 -0
- package/dist/server.js.map +1 -0
- package/package.json +64 -0
package/CATALOGUE.md
ADDED
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
# `@magicstoreai/hydrogen` catalogue
|
|
2
|
+
|
|
3
|
+
<!-- Generated by `pnpm --filter @magicstoreai/hydrogen catalogue` from the TypeScript sources — do not edit. -->
|
|
4
|
+
|
|
5
|
+
Every public export, by entry point: what it is, how to call it, an example, and the Storefront
|
|
6
|
+
API operations it calls (`operationId`s of `@magicstoreai/storefront-client`). Import only from
|
|
7
|
+
these entry points — anything else is internal and may change in any release.
|
|
8
|
+
|
|
9
|
+
## `@magicstoreai/hydrogen`
|
|
10
|
+
|
|
11
|
+
React bindings (a `"use client"` module). Also re-exports everything in `/core` and `MagicStoreError`.
|
|
12
|
+
|
|
13
|
+
### Components
|
|
14
|
+
|
|
15
|
+
#### `<Image>`
|
|
16
|
+
|
|
17
|
+
An API image with lazy loading by default. Its intrinsic size is set (no layout shift) unless
|
|
18
|
+
you pass `width` / `height` to draw it at another size, e.g. a 64×64 cart thumbnail.
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Description |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `data` | `Schema<'Image'> \| null \| undefined` | |
|
|
23
|
+
| `alt?` | `string` | Used when the image has no alt text of its own. |
|
|
24
|
+
| `fallback?` | `ReactNode` | |
|
|
25
|
+
|
|
26
|
+
Any other prop is passed to the rendered element.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<Image data={product.featuredImage} alt={product.title} fallback={<div className="noimg" />} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### `<MagicStoreProvider>`
|
|
33
|
+
|
|
34
|
+
The root of a storefront: one client, and the customer, cart, wishlist and analytics built on it.
|
|
35
|
+
On sign-in the cart becomes the customer's and the guest wishlist moves into their list; on
|
|
36
|
+
sign-out both are left behind with the customer.
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Description |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `locale?` | `string` | The language the storefront renders in; the shop's default when omitted. |
|
|
41
|
+
| `shop?` | `Shop` | The shop, when the page already has it (server-rendered) — saves a `/shop` call. |
|
|
42
|
+
| `storage?` | `KeyValueStorage` | Where the session, cart id and guest wishlist are kept; `localStorage` by default. |
|
|
43
|
+
| `children` | `ReactNode` | |
|
|
44
|
+
| `shopDomain?` | `string` | The shop's storefront domain, e.g. `shop.example.uz` → `https://shop.example.uz/api/v2/storefront`. |
|
|
45
|
+
| `baseUrl?` | `string` | The full API base URL, instead of `shopDomain`. |
|
|
46
|
+
| `storefrontToken?` | `string` | `X-Storefront-Token` — identifies the shop when the host is not one of its domains. |
|
|
47
|
+
| `fetch?` | `typeof globalThis.fetch` | A `fetch` implementation; the global one by default. |
|
|
48
|
+
| `retry?` | `RetryOptions \| false` | Retry policy for `GET`s answered `429` or `5xx`, or not answered at all. Writes are never retried by the client. `false` turns retries off. |
|
|
49
|
+
|
|
50
|
+
API operations: `shop`.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// app/providers.tsx ('use client'); `shop` comes from a server-side `api.shop()`.
|
|
54
|
+
<MagicStoreProvider shopDomain="shop.example.uz" shop={shop} locale="ru">
|
|
55
|
+
{children}
|
|
56
|
+
</MagicStoreProvider>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### `<Money>`
|
|
60
|
+
|
|
61
|
+
A price as the shop shows it (`shop.moneyFormat`, the storefront's locale).
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Description |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `data` | `MoneyValue \| null \| undefined` | |
|
|
66
|
+
| `as?` | `As` | |
|
|
67
|
+
| `fallback?` | `ReactNode` | What to render for a missing price (the API sends null for "unknown", never 0). |
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<Money data={product.price} fallback={<span>—</span>} />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### `<Pagination>`
|
|
74
|
+
|
|
75
|
+
Headless pagination over `meta.pagination`: you render the links, it does the arithmetic.
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Description |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `meta` | `Pagination` | |
|
|
80
|
+
| `around?` | `number` | |
|
|
81
|
+
| `children` | `(state: PaginationState) => ReactNode` | |
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<Pagination meta={products.meta.pagination}>
|
|
85
|
+
{({ pages }) =>
|
|
86
|
+
pages.map((page, i) =>
|
|
87
|
+
page === null ? <span key={i}>…</span> : <a key={i} href={`?page=${page}`}>{page}</a>,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
</Pagination>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `<ProductProvider>`
|
|
94
|
+
|
|
95
|
+
Shares one product's variant selection with everything under it (options, price, add-to-cart).
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Description |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `product` | `P` | |
|
|
100
|
+
| `initialVariantId?` | `string \| null` | |
|
|
101
|
+
| `children` | `ReactNode` | |
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<ProductProvider product={product}>
|
|
105
|
+
<VariantPicker />
|
|
106
|
+
<AddToCart />
|
|
107
|
+
</ProductProvider>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Hooks
|
|
111
|
+
|
|
112
|
+
#### `useAnalytics`
|
|
113
|
+
|
|
114
|
+
Reports what the visitor looks at. Stable across renders.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
function useAnalytics()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
| Returns | Type | Description |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `queue` | `AnalyticsEvent[]` | |
|
|
123
|
+
| `timer` | `ReturnType<typeof setTimeout> \| null` | |
|
|
124
|
+
| `client` | `StorefrontClient` | |
|
|
125
|
+
| `sessionId` | `string` | |
|
|
126
|
+
| `options` | `{ flushAfterMs?: number; context?: AnalyticsPayload }` | |
|
|
127
|
+
| `pageView` | `void` | A page view — starts the session with its first-touch attribution (UTM, click ids, referrer). |
|
|
128
|
+
| `productView` | `void` | |
|
|
129
|
+
| `collectionView` | `void` | |
|
|
130
|
+
| `search` | `void` | |
|
|
131
|
+
| `custom` | `void` | |
|
|
132
|
+
| `track` | `void` | |
|
|
133
|
+
| `flush` | `Promise<void>` | Sends what is queued now — call it when the page is hidden. |
|
|
134
|
+
|
|
135
|
+
API operations: `analyticsEventsStore`.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
const analytics = useAnalytics();
|
|
139
|
+
useEffect(() => analytics.productView(product.id), [analytics, product.id]);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### `useCart`
|
|
143
|
+
|
|
144
|
+
The visitor's cart and every change to it. The first `addLines` creates the cart.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
function useCart()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| Returns | Type | Description |
|
|
151
|
+
| --- | --- | --- |
|
|
152
|
+
| `totalQuantity` | `number` | |
|
|
153
|
+
| `addLines` | `(lines: CartLineInput[]) => Promise<Cart \| null>` | |
|
|
154
|
+
| `addLine` | `(line: CartLineInput) => Promise<Cart \| null>` | |
|
|
155
|
+
| `updateLine` | `(lineId: string, quantity: number) => Promise<Cart \| null>` | |
|
|
156
|
+
| `removeLine` | `(lineId: string) => Promise<Cart \| null>` | |
|
|
157
|
+
| `setDiscountCodes` | `(codes: string[]) => Promise<Cart \| null>` | |
|
|
158
|
+
| `setNote` | `(note: string \| null) => Promise<Cart \| null>` | |
|
|
159
|
+
| `setAttributes` | `(attributes: CartAttribute[]) => Promise<Cart \| null>` | |
|
|
160
|
+
| `setGift` | `(promotionId: string \| null) => Promise<Cart \| null>` | |
|
|
161
|
+
| `setPoints` | `(points: number) => Promise<Cart \| null>` | |
|
|
162
|
+
| `cart` | `Cart \| null` | |
|
|
163
|
+
| `status` | `'idle' \| 'loading' \| 'updating'` | `loading` while the stored cart is fetched; `updating` while a change is on its way. |
|
|
164
|
+
| `error` | `MagicStoreError \| null` | The last change that failed — the cart is back to what the server holds. |
|
|
165
|
+
|
|
166
|
+
API operations: `cartsAttributes`, `cartsBuyerIdentity`, `cartsDiscountCodes`, `cartsGift`, `cartsLinesDestroy`, `cartsLinesStore`, `cartsLinesUpdate`, `cartsNote`, `cartsPoints`, `cartsShow`, `cartsStore`.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
const { cart, totalQuantity, status, error, addLine } = useCart();
|
|
170
|
+
await addLine({ productId: product.id, variantId: selectedVariant?.id ?? null, quantity: 1 });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `useCustomer`
|
|
174
|
+
|
|
175
|
+
The signed-in customer and every way to sign in and out.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
function useCustomer()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Returns | Type | Description |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `customer` | `{ createdAt: string \| null; deletionRequestedAt: string \| null; gender: "MALE" \| "FEMALE" \| null; id: string; locale: string \| null; name: string \| null; phone: string \| null; referralCode: string \| null; } \| null` | |
|
|
184
|
+
| `isSignedIn` | `boolean` | |
|
|
185
|
+
| `requestOtp` | `(phone: string) => Promise<Schema<'OtpChallenge'>>` | |
|
|
186
|
+
| `verifyOtp` | `(phone: string, code: string, referralCode?: string) => Promise<CustomerSession>` | |
|
|
187
|
+
| `signInWithTelegram` | `(initData: string, referralCode?: string) => Promise<CustomerSession>` | |
|
|
188
|
+
| `signInWithOq` | `(oqToken: string) => Promise<CustomerSession>` | |
|
|
189
|
+
| `signInWithClick` | `(webSession: string) => Promise<CustomerSession>` | |
|
|
190
|
+
| `signOut` | `() => Promise<void>` | |
|
|
191
|
+
| `reload` | `() => Promise<Customer \| null>` | |
|
|
192
|
+
|
|
193
|
+
API operations: `authClick`, `authOq`, `authOtp`, `authOtpVerification`, `authTelegram`, `authTokenDestroy`, `authTokenRefresh`, `customerShow`.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const { customer, isSignedIn, requestOtp, verifyOtp, signOut } = useCustomer();
|
|
197
|
+
await requestOtp('+998901234567');
|
|
198
|
+
await verifyOtp('+998901234567', code);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `useMagicStore`
|
|
202
|
+
|
|
203
|
+
Everything the provider holds: client, shop, locale and the controllers. Throws outside
|
|
204
|
+
`<MagicStoreProvider>`.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
function useMagicStore(): MagicStore
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
const { locale, sessionId } = useMagicStore();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### `useProduct`
|
|
215
|
+
|
|
216
|
+
The product and variant selection of the nearest `<ProductProvider>`; throws outside one.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
function useProduct(): ProductContextValue
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const { product, selectedVariant } = useProduct();
|
|
224
|
+
return <Money data={selectedVariant?.price ?? product.price} />;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### `useShop`
|
|
228
|
+
|
|
229
|
+
The shop (`GET /shop`): name, currency, money format, locales, features, branding…
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
function useShop(): Shop | null
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
const shop = useShop();
|
|
237
|
+
return <span>{shop?.name}</span>;
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### `useStorefrontClient`
|
|
241
|
+
|
|
242
|
+
The typed API client, for anything the hooks do not cover.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
function useStorefrontClient(): StorefrontClient
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
const client = useStorefrontClient();
|
|
250
|
+
const { data } = await client.searchSuggestions({ query: { q } });
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### `useVariantSelection`
|
|
254
|
+
|
|
255
|
+
Options → variant. `selectedVariant` is null only while a choice is missing; `isAvailable` says
|
|
256
|
+
whether a value leads to a variant for sale given the other choices.
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
function useVariantSelection(product: SelectableProduct, initialVariantId?: string | null)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Returns | Type | Description |
|
|
263
|
+
| --- | --- | --- |
|
|
264
|
+
| `selectedOptions` | `SelectedOptions` | |
|
|
265
|
+
| `selectedVariant` | `{ availableForSale: boolean; barcode: string \| null; compareAtPrice: { amount: string; currencyCode: string; } \| null; gtin: string \| null; id: string; image: { altText: string \| null; height: number \| null; url: string; width: number \| null; } \| null; ... 7 more ...; weight: { ...; } \| null; } \| null` | |
|
|
266
|
+
| `setOption` | `(name: string, value: string) => void` | |
|
|
267
|
+
| `isAvailable` | `(name: string, value: string) => boolean` | |
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
const { selectedOptions, selectedVariant, setOption, isAvailable } = useVariantSelection(product);
|
|
271
|
+
<button disabled={!isAvailable('Size', 'M')} onClick={() => setOption('Size', 'M')}>M</button>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### `useWishlist`
|
|
275
|
+
|
|
276
|
+
Saved products: a guest's in this browser, a customer's on the server (merged at sign-in).
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
function useWishlist()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
| Returns | Type | Description |
|
|
283
|
+
| --- | --- | --- |
|
|
284
|
+
| `has` | `(productId: string) => boolean` | |
|
|
285
|
+
| `add` | `(productId: string) => Promise<void>` | |
|
|
286
|
+
| `remove` | `(productId: string) => Promise<void>` | |
|
|
287
|
+
| `toggle` | `(productId: string) => Promise<void>` | |
|
|
288
|
+
| `productIds` | `string[]` | Product ids, most recent first. |
|
|
289
|
+
| `owner` | `'guest' \| 'customer'` | Whose list it is: a guest's lives in this browser, a customer's on the server. |
|
|
290
|
+
| `status` | `'idle' \| 'loading'` | |
|
|
291
|
+
|
|
292
|
+
API operations: `customerWishlistAdd`, `customerWishlistRemove`.
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
const wishlist = useWishlist();
|
|
296
|
+
<button aria-pressed={wishlist.has(product.id)} onClick={() => wishlist.toggle(product.id)}>♥</button>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Classes
|
|
300
|
+
|
|
301
|
+
#### `MagicStoreError`
|
|
302
|
+
|
|
303
|
+
Every failed call. `code` is what to branch on — never the message, which is localized.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
try {
|
|
307
|
+
await client.checkoutsCompletion({ path: { id } });
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error instanceof MagicStoreError && error.code === 'CHECKOUT_NOT_READY') { … }
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Re-exported from `@magicstoreai/storefront-client`.
|
|
314
|
+
|
|
315
|
+
### Functions
|
|
316
|
+
|
|
317
|
+
#### `paginationState`
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
function paginationState(meta: Pagination, around = 1): PaginationState
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Types
|
|
324
|
+
|
|
325
|
+
#### `MagicStore`
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
export interface MagicStore {
|
|
329
|
+
client: StorefrontClient;
|
|
330
|
+
shop: Shop | null;
|
|
331
|
+
locale: string;
|
|
332
|
+
session: CustomerSessionController;
|
|
333
|
+
cart: CartController;
|
|
334
|
+
wishlist: WishlistController;
|
|
335
|
+
analytics: AnalyticsController;
|
|
336
|
+
sessionId: string;
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### `MagicStoreProviderProps`
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
export interface MagicStoreProviderProps extends Pick<StorefrontClientOptions, 'shopDomain' | 'baseUrl' | 'storefrontToken' | 'fetch' | 'retry'> {
|
|
344
|
+
locale?: string;
|
|
345
|
+
shop?: Shop;
|
|
346
|
+
storage?: KeyValueStorage;
|
|
347
|
+
children: ReactNode;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### `PaginationState`
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
export interface PaginationState {
|
|
355
|
+
page: number;
|
|
356
|
+
totalPages: number;
|
|
357
|
+
hasPreviousPage: boolean;
|
|
358
|
+
hasNextPage: boolean;
|
|
359
|
+
previousPage: number | null;
|
|
360
|
+
nextPage: number | null;
|
|
361
|
+
pages: Array<number | null>;
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### `Shop`
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
export type Shop = Schema<'Shop'>;
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## `@magicstoreai/hydrogen/core`
|
|
372
|
+
|
|
373
|
+
Framework-free controllers and helpers, safe on the server and in any UI library.
|
|
374
|
+
|
|
375
|
+
### Classes
|
|
376
|
+
|
|
377
|
+
#### `AnalyticsController`
|
|
378
|
+
|
|
379
|
+
What the visitor looks at, sent in batches (`POST /analytics/events`). Cart and checkout steps
|
|
380
|
+
are recorded by the server — never report them. Failures are dropped: analytics must never get
|
|
381
|
+
in the way of shopping.
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
class AnalyticsController {
|
|
385
|
+
constructor(private readonly client: StorefrontClient, private readonly sessionId: string, private readonly options: {
|
|
386
|
+
flushAfterMs?: number;
|
|
387
|
+
context?: AnalyticsPayload;
|
|
388
|
+
} = {});
|
|
389
|
+
pageView(path: string, extra: AnalyticsPayload = {}): void;
|
|
390
|
+
productView(productId: string, variantId?: string | null): void;
|
|
391
|
+
collectionView(collectionId: string): void;
|
|
392
|
+
search(query: string, resultsCount?: number): void;
|
|
393
|
+
custom(name: string, properties?: Record<string, unknown>): void;
|
|
394
|
+
track(type: AnalyticsEventType, payload: AnalyticsPayload): void;
|
|
395
|
+
async flush(): Promise<void>;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
API operations: `analyticsEventsStore`.
|
|
400
|
+
|
|
401
|
+
#### `CartController`
|
|
402
|
+
|
|
403
|
+
The visitor's cart. Its id is a capability and lives only in storage; the cart itself always
|
|
404
|
+
comes from the server, so prices, discounts and availability are the server's. Quantity changes
|
|
405
|
+
show at once and roll back if the server refuses them. Changes run one after another.
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
class CartController {
|
|
409
|
+
constructor(private readonly client: StorefrontClient, private readonly storage: KeyValueStorage, private readonly key = 'magicstore.cart-id');
|
|
410
|
+
get cart(): Cart | null;
|
|
411
|
+
get cartId(): string | null;
|
|
412
|
+
load(): Promise<Cart | null>;
|
|
413
|
+
addLines(lines: CartLineInput[]): Promise<Cart | null>;
|
|
414
|
+
updateLine(lineId: string, quantity: number): Promise<Cart | null>;
|
|
415
|
+
removeLine(lineId: string): Promise<Cart | null>;
|
|
416
|
+
setDiscountCodes(discountCodes: string[]): Promise<Cart | null>;
|
|
417
|
+
setNote(note: string | null): Promise<Cart | null>;
|
|
418
|
+
setAttributes(attributes: CartAttribute[]): Promise<Cart | null>;
|
|
419
|
+
setGift(promotionId: string | null): Promise<Cart | null>;
|
|
420
|
+
setPoints(points: number): Promise<Cart | null>;
|
|
421
|
+
attachCustomer(): Promise<Cart | null>;
|
|
422
|
+
forget(): void;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
API operations: `cartsAttributes`, `cartsBuyerIdentity`, `cartsDiscountCodes`, `cartsGift`, `cartsLinesDestroy`, `cartsLinesStore`, `cartsLinesUpdate`, `cartsNote`, `cartsPoints`, `cartsShow`, `cartsStore`.
|
|
427
|
+
|
|
428
|
+
#### `CustomerSessionController`
|
|
429
|
+
|
|
430
|
+
The signed-in customer and their tokens. The access token lives an hour and is refreshed on
|
|
431
|
+
demand; the refresh token works once, so concurrent callers share one refresh.
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
class CustomerSessionController {
|
|
435
|
+
constructor(private readonly storage: KeyValueStorage, private readonly key = 'magicstore.customer-session', private readonly now: () => number = () => Date.now());
|
|
436
|
+
attach(client: StorefrontClient): void;
|
|
437
|
+
get session(): CustomerSession | null;
|
|
438
|
+
get customer(): Customer | null;
|
|
439
|
+
accessToken;
|
|
440
|
+
refresh(): Promise<string | null>;
|
|
441
|
+
async requestOtp(phone: string): Promise<Schema<'OtpChallenge'>>;
|
|
442
|
+
async verifyOtp(phone: string, code: string, referralCode?: string): Promise<CustomerSession>;
|
|
443
|
+
async signInWithTelegram(initData: string, referralCode?: string): Promise<CustomerSession>;
|
|
444
|
+
async signInWithOq(oqToken: string): Promise<CustomerSession>;
|
|
445
|
+
async signInWithClick(webSession: string): Promise<CustomerSession>;
|
|
446
|
+
async signOut(): Promise<void>;
|
|
447
|
+
async reloadCustomer(): Promise<Customer | null>;
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
API operations: `authClick`, `authOq`, `authOtp`, `authOtpVerification`, `authTelegram`, `authTokenDestroy`, `authTokenRefresh`, `customerShow`.
|
|
452
|
+
|
|
453
|
+
#### `WishlistController`
|
|
454
|
+
|
|
455
|
+
Saved products. A guest's list is kept in this browser; on sign-in it moves into the customer's
|
|
456
|
+
list on the server and the local copy is cleared. Changes show at once and roll back if refused.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
class WishlistController {
|
|
460
|
+
constructor(private readonly client: StorefrontClient, private readonly storage: KeyValueStorage, private readonly key = 'magicstore.guest-wishlist');
|
|
461
|
+
has(productId: string): boolean;
|
|
462
|
+
async add(productId: string): Promise<void>;
|
|
463
|
+
async remove(productId: string): Promise<void>;
|
|
464
|
+
toggle(productId: string): Promise<void>;
|
|
465
|
+
async signedIn(): Promise<void>;
|
|
466
|
+
signedOut(): void;
|
|
467
|
+
async reload(): Promise<void>;
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
API operations: `customerWishlistAdd`, `customerWishlistRemove`.
|
|
472
|
+
|
|
473
|
+
### Functions
|
|
474
|
+
|
|
475
|
+
#### `attributionFrom`
|
|
476
|
+
|
|
477
|
+
The first-touch attribution a landing URL carries, for the first `pageView`.
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
function attributionFrom(url: URL, referrer?: string): AnalyticsPayload
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### `browserStorage`
|
|
484
|
+
|
|
485
|
+
`localStorage` when the browser allows it; memory otherwise (server render, private mode,
|
|
486
|
+
blocked site data). Every access is guarded: storage can throw at any time.
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
function browserStorage(): KeyValueStorage
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
#### `currencySymbol`
|
|
493
|
+
|
|
494
|
+
The currency's symbol or word in a locale: `сум`, `so‘m`, `$`.
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
function currencySymbol(currencyCode: string, locale: string): string
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### `formatMoney`
|
|
501
|
+
|
|
502
|
+
Money as the shop shows it: `shop.moneyFormat.format` places the symbol or the code, and a shop
|
|
503
|
+
with no format uses the currency's own convention (`12 500 сум`, `$129.99`).
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
function formatMoney(money: Money, options: {
|
|
507
|
+
locale: string;
|
|
508
|
+
format?: MoneyFormat | null;
|
|
509
|
+
}): string
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### `groupAmount`
|
|
513
|
+
|
|
514
|
+
`12500.00` → `12 500`: groups of three separated by a space, the fraction kept only when it is
|
|
515
|
+
not zero. The amount is never rounded here — the API already applied the shop's rounding, and
|
|
516
|
+
the string is exactly what will be charged.
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
function groupAmount(amount: string): string
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
#### `initialSelection`
|
|
523
|
+
|
|
524
|
+
Where selection starts: the given variant, else the first one for sale, else the first one.
|
|
525
|
+
A product with a single variant and no options is always "selected".
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
function initialSelection(product: SelectableProduct, variantId?: string | null): SelectedOptions
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### `isOptionValueAvailable`
|
|
532
|
+
|
|
533
|
+
Whether picking `value` for `name` — keeping the other choices — lands on a variant for sale.
|
|
534
|
+
What a storefront uses to grey out a size that is sold out in the chosen colour.
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
function isOptionValueAvailable(product: SelectableProduct, selected: SelectedOptions, name: string, value: string): boolean
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### `memoryStorage`
|
|
541
|
+
|
|
542
|
+
In memory — for tests, the server, and when the browser refuses storage.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
function memoryStorage(initial: Record<string, string> = {}): KeyValueStorage
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### `moneyAmount`
|
|
549
|
+
|
|
550
|
+
The amount as a number, for arithmetic you must do on the client (sorting, a progress bar).
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
function moneyAmount(money: Money): number
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### `optionsOf`
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
function optionsOf(variant: ProductVariant): SelectedOptions
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### `selectOption`
|
|
563
|
+
|
|
564
|
+
Choosing a value keeps the other choices when that combination exists; otherwise it moves to the
|
|
565
|
+
closest variant that has the new value (for sale first), so the selection never dead-ends.
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
function selectOption(product: SelectableProduct, selected: SelectedOptions, name: string, value: string): SelectedOptions
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
#### `variantFor`
|
|
572
|
+
|
|
573
|
+
The variant whose options are exactly these, or null while the selection is incomplete.
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
function variantFor(product: SelectableProduct, selected: SelectedOptions): ProductVariant | null
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### `visitorSessionId`
|
|
580
|
+
|
|
581
|
+
The visitor's id for the shop's analytics and funnel: a UUID minted once per visitor and kept.
|
|
582
|
+
The same value goes out as `X-Session-Id`, which ties carts and orders to the visit.
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
function visitorSessionId(storage: KeyValueStorage, key = 'magicstore.session-id'): string
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Constants
|
|
589
|
+
|
|
590
|
+
#### `WISHLIST_LIMIT`
|
|
591
|
+
|
|
592
|
+
The server keeps up to this many; a guest list is capped the same.
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
const WISHLIST_LIMIT = 500
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Types
|
|
599
|
+
|
|
600
|
+
#### `AnalyticsEvent`
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
export type AnalyticsEvent = EventsBody['events'][number];
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
#### `AnalyticsEventType`
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
export type AnalyticsEventType = Schema<'StorefrontEventType'>;
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
#### `AnalyticsPayload`
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
export type AnalyticsPayload = AnalyticsEvent['payload'];
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### `Cart`
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
export type Cart = Schema<'Cart'>;
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
#### `CartAttribute`
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
export type CartAttribute = {
|
|
628
|
+
key: string;
|
|
629
|
+
value: string | null;
|
|
630
|
+
};
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
#### `CartLine`
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
export type CartLine = Schema<'CartLine'>;
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### `CartLineInput`
|
|
640
|
+
|
|
641
|
+
A line to add: the product, its variant (null for a product without variants) and how many.
|
|
642
|
+
|
|
643
|
+
```ts
|
|
644
|
+
export interface CartLineInput {
|
|
645
|
+
productId: string;
|
|
646
|
+
variantId?: string | null;
|
|
647
|
+
quantity?: number;
|
|
648
|
+
attributes?: CartAttribute[];
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
#### `CartState`
|
|
653
|
+
|
|
654
|
+
```ts
|
|
655
|
+
export interface CartState {
|
|
656
|
+
cart: Cart | null;
|
|
657
|
+
status: 'idle' | 'loading' | 'updating';
|
|
658
|
+
error: MagicStoreError | null;
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
#### `Customer`
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
export type Customer = Schema<'Customer'>;
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
#### `CustomerSession`
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
export type CustomerSession = Schema<'CustomerSession'>;
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
#### `KeyValueStorage`
|
|
675
|
+
|
|
676
|
+
Where the SDK keeps what outlives a page: the customer session, the cart id, the guest wishlist.
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
export interface KeyValueStorage {
|
|
680
|
+
get(key: string): string | null;
|
|
681
|
+
set(key: string, value: string): void;
|
|
682
|
+
remove(key: string): void;
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
#### `Money`
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
export type Money = Schema<'Money'>;
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### `MoneyFormat`
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
export type MoneyFormat = Schema<'Shop'>['moneyFormat'];
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
#### `ProductOption`
|
|
699
|
+
|
|
700
|
+
```ts
|
|
701
|
+
export type ProductOption = Schema<'ProductOption'>;
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
#### `ProductVariant`
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
export type ProductVariant = Schema<'ProductVariant'>;
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
#### `SelectableProduct`
|
|
711
|
+
|
|
712
|
+
What variant selection needs of a product — `Product` and `ProductDetail` both fit.
|
|
713
|
+
|
|
714
|
+
```ts
|
|
715
|
+
export interface SelectableProduct {
|
|
716
|
+
options: ProductOption[];
|
|
717
|
+
variants: ProductVariant[];
|
|
718
|
+
}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
#### `SelectedOptions`
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
export type SelectedOptions = Record<string, string>;
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
#### `SessionState`
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
export interface SessionState {
|
|
731
|
+
session: CustomerSession | null;
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
#### `WishlistState`
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
export interface WishlistState {
|
|
739
|
+
productIds: string[];
|
|
740
|
+
owner: 'guest' | 'customer';
|
|
741
|
+
status: 'idle' | 'loading';
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
## `@magicstoreai/hydrogen/server`
|
|
746
|
+
|
|
747
|
+
Server only: webhook verification and Next.js cache tags.
|
|
748
|
+
|
|
749
|
+
### Functions
|
|
750
|
+
|
|
751
|
+
#### `createWebhookHandler`
|
|
752
|
+
|
|
753
|
+
A route handler for the platform's webhook: `export const POST = createWebhookHandler({ secret,
|
|
754
|
+
revalidateTag })` in `app/api/magicstore/webhook/route.ts`. Answers 401 on a bad signature, 400
|
|
755
|
+
on a body that is not an event, 200 otherwise — including for a topic it does not know. Events
|
|
756
|
+
seen before (same id, this instance) are acknowledged and skipped.
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
function createWebhookHandler(options: WebhookHandlerOptions): (request: Request) => Promise<Response>
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
#### `nextCacheFetch`
|
|
763
|
+
|
|
764
|
+
A `fetch` for `createStorefrontClient` on a Next.js server: public GETs are cached under the tag
|
|
765
|
+
a webhook revalidates (and at most `revalidate` seconds); everything else is `no-store`.
|
|
766
|
+
|
|
767
|
+
```ts
|
|
768
|
+
const client = createStorefrontClient({ shopDomain, fetch: nextCacheFetch() });
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
```ts
|
|
772
|
+
function nextCacheFetch(options: {
|
|
773
|
+
revalidate?: number | false;
|
|
774
|
+
fetch?: typeof globalThis.fetch;
|
|
775
|
+
} = {}): typeof globalThis.fetch
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
#### `tagForPath`
|
|
779
|
+
|
|
780
|
+
The cache tag of an API path — what a webhook topic will invalidate. Personal and live data
|
|
781
|
+
(customer, cart, checkout, orders, search, stock-bearing listings are still catalog) get none:
|
|
782
|
+
they must never be cached across visitors.
|
|
783
|
+
|
|
784
|
+
```ts
|
|
785
|
+
function tagForPath(pathname: string): string | null
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
#### `tagsForTopic`
|
|
789
|
+
|
|
790
|
+
```ts
|
|
791
|
+
function tagsForTopic(topic: string): string[]
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
#### `verifyWebhookSignature`
|
|
795
|
+
|
|
796
|
+
Verifies `X-MagicStore-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>` over
|
|
797
|
+
the RAW body, in constant time, with Web Crypto — so it runs in Node, edge runtimes and Workers.
|
|
798
|
+
|
|
799
|
+
```ts
|
|
800
|
+
async function verifyWebhookSignature(secret: string, header: string | null, rawBody: string, nowSeconds: number = Math.floor(Date.now() / 1000)): Promise<boolean>
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### Constants
|
|
804
|
+
|
|
805
|
+
#### `CACHE_TAGS`
|
|
806
|
+
|
|
807
|
+
The cache tags a storefront gives what each topic invalidates.
|
|
808
|
+
|
|
809
|
+
```ts
|
|
810
|
+
const CACHE_TAGS = {
|
|
811
|
+
shop: 'magicstore:shop',
|
|
812
|
+
catalog: 'magicstore:catalog',
|
|
813
|
+
pages: 'magicstore:pages',
|
|
814
|
+
home: 'magicstore:home',
|
|
815
|
+
} as const
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
#### `SIGNATURE_TOLERANCE_SECONDS`
|
|
819
|
+
|
|
820
|
+
How far the signature's timestamp may be from this clock (replay window).
|
|
821
|
+
|
|
822
|
+
```ts
|
|
823
|
+
const SIGNATURE_TOLERANCE_SECONDS = 300
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Types
|
|
827
|
+
|
|
828
|
+
#### `WebhookEvent`
|
|
829
|
+
|
|
830
|
+
```ts
|
|
831
|
+
export interface WebhookEvent {
|
|
832
|
+
id: string;
|
|
833
|
+
topic: WebhookTopic | (string & {});
|
|
834
|
+
shop: string;
|
|
835
|
+
occurredAt: string;
|
|
836
|
+
data: Record<string, unknown>;
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
#### `WebhookHandlerOptions`
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
export interface WebhookHandlerOptions {
|
|
844
|
+
secret: string;
|
|
845
|
+
revalidateTag?: (tag: string, profile: 'max') => void | Promise<void>;
|
|
846
|
+
onEvent?: (event: WebhookEvent) => void | Promise<void>;
|
|
847
|
+
now?: () => number;
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
#### `WebhookTopic`
|
|
852
|
+
|
|
853
|
+
Topics the platform sends today. New ones may be added — ignore what you do not know.
|
|
854
|
+
|
|
855
|
+
```ts
|
|
856
|
+
export type WebhookTopic = 'SHOP_UPDATED' | 'CATALOG_UPDATED' | 'PAGES_UPDATED' | 'HOME_UPDATED';
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
## `@magicstoreai/hydrogen/seo`
|
|
860
|
+
|
|
861
|
+
Page meta and JSON-LD from API resources; server-safe.
|
|
862
|
+
|
|
863
|
+
### Functions
|
|
864
|
+
|
|
865
|
+
#### `breadcrumbJsonLd`
|
|
866
|
+
|
|
867
|
+
schema.org `BreadcrumbList` from `[{ name, url }]`.
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
function breadcrumbJsonLd(items: Array<{
|
|
871
|
+
name: string;
|
|
872
|
+
url: string;
|
|
873
|
+
}>): { '@context': string; '@type': string; itemListElement: { '@type': string; position: number; name: string; item: string; }[]; }
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
#### `jsonLdScript`
|
|
877
|
+
|
|
878
|
+
A JSON-LD object as a string safe inside `<script>` (no `</script>` break-out).
|
|
879
|
+
|
|
880
|
+
```ts
|
|
881
|
+
function jsonLdScript(data: unknown): string
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
#### `pageMeta`
|
|
885
|
+
|
|
886
|
+
Title and description for a page: the merchant's SEO fields first, then the page's own. Shaped so
|
|
887
|
+
a Next.js `generateMetadata` can return most of it as is.
|
|
888
|
+
|
|
889
|
+
```ts
|
|
890
|
+
function pageMeta(input: {
|
|
891
|
+
seo?: Seo | null;
|
|
892
|
+
title: string;
|
|
893
|
+
description?: string | null;
|
|
894
|
+
url?: string | null;
|
|
895
|
+
images?: Array<{
|
|
896
|
+
url: string;
|
|
897
|
+
} | null | undefined>;
|
|
898
|
+
type?: string;
|
|
899
|
+
shop?: Pick<Shop, 'name'> | null;
|
|
900
|
+
}): PageMeta
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
#### `productJsonLd`
|
|
904
|
+
|
|
905
|
+
schema.org `Product` with its offers — for a `<script type="application/ld+json">`.
|
|
906
|
+
|
|
907
|
+
```ts
|
|
908
|
+
function productJsonLd(product: Product, options: {
|
|
909
|
+
url: string;
|
|
910
|
+
shop?: Pick<Shop, 'name'> | null;
|
|
911
|
+
}): { '@context': string; '@type': string; name: string; description: string | undefined; image: string[]; sku: string | undefined; brand: { '@type': string; name: string; } | undefined; offers: { '@type': string; ... 4 more ...; url: string; }[] | undefined; aggregateRating: { ...; } | undefined; seller: { ...; } | und...
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### Types
|
|
915
|
+
|
|
916
|
+
#### `PageMeta`
|
|
917
|
+
|
|
918
|
+
```ts
|
|
919
|
+
export interface PageMeta {
|
|
920
|
+
title: string;
|
|
921
|
+
description: string | null;
|
|
922
|
+
canonical: string | null;
|
|
923
|
+
openGraph: {
|
|
924
|
+
title: string;
|
|
925
|
+
description: string | null;
|
|
926
|
+
url: string | null;
|
|
927
|
+
images: string[];
|
|
928
|
+
type: string;
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
```
|