@asstio-dev/storefront 0.24.0 → 0.27.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
@@ -191,14 +191,39 @@ const { data: listing } = await storefront.GET("/v1/products", {
191
191
  });
192
192
  ```
193
193
 
194
+ ### Variant links
195
+
196
+ A link to one variant is the product URL plus `?variant={variantId}` (the id from `variants[].id`); `variant.url` is
197
+ that link ready-made, `null` when the variant has no price on the market. Read the parameter on the server, pass it
198
+ to the API, and render by `selectedVariantStatus`:
199
+
200
+ ```ts
201
+ const raw = (await headers()).get("x-search") ?? ""; // the raw search string, e.g. set by middleware
202
+ const param = readVariantParam(raw); // absent | id | invalid
203
+ const { data: product } = await storefront.GET("/v1/products/{slugOrId}", {
204
+ params: { path: { slugOrId: slug }, query: { variant: variantQueryForApi(param) } },
205
+ });
206
+ if (product?.redirectTo) permanentRedirect(redirectTarget(product.url!, raw)); // 308; keeps ?variant= and UTM as received
207
+ // product.selectedVariantStatus: null (absent: your default), "matched" (preselect product.selectedVariant),
208
+ // "notSoldHere" (show it as unavailable here, preselect nothing), "unknown" (note that it is gone, preselect nothing).
209
+ if (!product) notFound();
210
+ const ld = productJsonLd(product, { site, market: "se" });
211
+ ```
212
+
213
+ Pass the **raw search string** to `readVariantParam` and `redirectTarget` when you can (for example from a
214
+ middleware header): a Next.js `searchParams` record is accepted too, but it loses the interleaving of different keys.
215
+ Canonical stays the product `url`. Never a 404 while the product resolves. If you have variant ids but no `v.url` (for example a cart line), `variantUrl(product.url, product.id, variantId)` builds the same link; the parameter name is exported as `VARIANT_PARAM`. The full contract is in the onboarding
216
+ guide, "Variant links".
217
+
194
218
  ### SEO
195
219
 
196
220
  Pure helpers that compute schema.org JSON-LD and `<head>` data from the DTOs. They render nothing: your frontend
197
221
  writes the markup. No backend call, no DOM, no dependency.
198
222
 
199
223
  **Product JSON-LD.** `productJsonLd` returns a `Product` (one variant) or a `ProductGroup` with one `Product` and
200
- `Offer` per variant sold on the market. `variantUrl` is required: Google needs a distinct URL per variant, and opening
201
- that URL **must preselect the variant** on your page. The format is yours; `?variant=` below is only an example.
224
+ `Offer` per variant sold on the market. Each variant's link is `v.url` from the API (see Variant links above), so no
225
+ `variantUrl` hook is needed; pass one only to override it. Google needs a distinct URL per variant, and opening that
226
+ URL **must preselect the variant** on your page. `productJsonLd` reads `product.selectedVariant` itself.
202
227
  `variesBy` maps your option names to `size`, `color`, `material` or `pattern`; unmapped options still name the
203
228
  variants. A product with more than 100 variants arrives with `variants: null`: pass the first `/variants` page as
204
229
  `variants`, or the product gets no offer (valid schema.org, but not eligible for product rich results).
@@ -206,7 +231,6 @@ variants. A product with more than 100 variants arrives with `variants: null`: p
206
231
  ```tsx
207
232
  const ld = productJsonLd(product, {
208
233
  site, market: "se",
209
- variantUrl: (v) => `${product.url}?variant=${v.id}`,
210
234
  variesBy: { Storlek: "size", Färg: "color" },
211
235
  });
212
236
  <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: jsonLdScript(ld) }} />
@@ -237,7 +261,7 @@ export async function generateMetadata(): Promise<Metadata> {
237
261
  Titles come out as `{ absolute }` on purpose, so a layout's `title.template` does not apply a second time.
238
262
 
239
263
  **Errors.** `SeoError` means a caller or configuration error: `redirectTo` is set (answer 301 instead), an unknown
240
- market, a bad `variesBy` value, a `variantUrl` that is not an absolute http(s) URL or repeats for two variants, or
264
+ market, a bad `variesBy` value, a variant URL (`v.url` or the optional `variantUrl` hook) that is missing, not an absolute http(s) URL or repeated for two variants, or
241
265
  two markets with the same `language-COUNTRY` and different URLs. Catch it at your SEO boundary and fix the market
242
266
  config. Holes in the data never throw: a missing image, description or price only leaves that part out.
243
267
 
package/dist/index.d.ts CHANGED
@@ -61,3 +61,4 @@ export { longestCategory, toGa4Ecommerce, toGa4Item } from "./analytics/ga4.js";
61
61
  export { trackSearchEvents, type SearchEvent, type SearchEventsResponse } from "./analytics/events.js";
62
62
  export { AnalyticsError, type Ga4Ecommerce, type Ga4Item, type Ga4ItemOptions, type Ga4ItemSource, type Ga4Purchase, type Ga4SharedOptions, type ProductFacts, type SelectedVariant, } from "./analytics/types.js";
63
63
  export { SeoError, type BreadcrumbLink, type CollectionHeadOptions, type HeadOptions, type JsonLd, type NextMetadataLike, type ProductJsonLdOptions, type SchemaProperty, type SeoContext, type SeoHead, type SeoMeta, type VariantAvailability, } from "./seo/types.js";
64
+ export { readVariantParam, redirectTarget, VARIANT_PARAM, variantQueryForApi, variantUrl, type QueryInput, type VariantParam, } from "./variant.js";
package/dist/index.js CHANGED
@@ -50,3 +50,4 @@ export { longestCategory, toGa4Ecommerce, toGa4Item } from "./analytics/ga4.js";
50
50
  export { trackSearchEvents } from "./analytics/events.js";
51
51
  export { AnalyticsError, } from "./analytics/types.js";
52
52
  export { SeoError, } from "./seo/types.js";
53
+ export { readVariantParam, redirectTarget, VARIANT_PARAM, variantQueryForApi, variantUrl, } from "./variant.js";
package/dist/schema.d.ts CHANGED
@@ -546,6 +546,7 @@ export interface components {
546
546
  selectedOptions: {
547
547
  [key: string]: string;
548
548
  };
549
+ url: null | string;
549
550
  };
550
551
  CategoryNodeDto: {
551
552
  children: components["schemas"]["CategoryNodeDto"][];
@@ -923,6 +924,7 @@ export interface components {
923
924
  };
924
925
  OrderLineVariantDto: {
925
926
  brand: null | components["schemas"]["BrandDto"];
927
+ url: null | string;
926
928
  };
927
929
  OrderShippingDto: {
928
930
  carrier: string;
@@ -1011,6 +1013,8 @@ export interface components {
1011
1013
  redirectTo: null | string;
1012
1014
  related: components["schemas"]["RelatedGroupDto"][];
1013
1015
  responsible: components["schemas"]["ResponsibleDto"];
1016
+ selectedVariant: null | components["schemas"]["VariantDto"];
1017
+ selectedVariantStatus: null | components["schemas"]["SelectedVariantStatus"];
1014
1018
  seo: components["schemas"]["SeoDto"];
1015
1019
  shortDescription: null | string;
1016
1020
  sku: string;
@@ -1099,6 +1103,8 @@ export interface components {
1099
1103
  name: string;
1100
1104
  slug: string;
1101
1105
  };
1106
+ /** @enum {unknown} */
1107
+ SelectedVariantStatus: "matched" | "notSoldHere" | "unknown";
1102
1108
  SellabilityDto: {
1103
1109
  mode: components["schemas"]["SiteSellabilityMode"];
1104
1110
  };
@@ -1255,6 +1261,7 @@ export interface components {
1255
1261
  };
1256
1262
  sku: string;
1257
1263
  stock: string;
1264
+ url: null | string;
1258
1265
  };
1259
1266
  VariantOptionDto: {
1260
1267
  name: string;
@@ -3314,7 +3321,10 @@ export interface operations {
3314
3321
  };
3315
3322
  GetProduct: {
3316
3323
  parameters: {
3317
- query?: never;
3324
+ query?: {
3325
+ /** @description A variant id (`VariantDto.id`) to preselect. Answered in `selectedVariantStatus`; a value that is not one of this product's variants is `unknown`, never an error. */
3326
+ variant?: string;
3327
+ };
3318
3328
  header: {
3319
3329
  /** @description Publishable, preview or secret site key. Required on every request; secret keys are server-side only. */
3320
3330
  "X-Site-Key": string;
@@ -1,6 +1,7 @@
1
1
  import { type JsonLd, type ProductDto, type ProductJsonLdOptions } from "./types.js";
2
2
  /**
3
3
  * schema.org `Product` / `ProductGroup` for a product page (spec §6.2). Decision order: no offer → single (only for a
4
- * proven singleton) → group. Never an `AggregateOffer`, never a guessed price.
4
+ * proven singleton) → group. Never an `AggregateOffer`, never a guessed price. A priced `p.selectedVariant` is always
5
+ * in the group, appended when the list does not hold it (variant URLs spec §6).
5
6
  */
6
7
  export declare function productJsonLd(p: ProductDto, ctx: ProductJsonLdOptions): JsonLd;
@@ -13,11 +13,14 @@ function availabilityOf(v, ctx) {
13
13
  return SCHEMA_AVAILABILITY[state] ?? OUT_OF_STOCK;
14
14
  }
15
15
  const brandNode = (label) => ({ "@type": "Brand", name: label });
16
- /** Spec §4.12: absolute http(s), distinct per variant; checked for every emitted variant, single path included. */
17
- function checkVariantUrls(variants, variantUrl) {
16
+ /** Spec §4.12: absolute http(s), distinct per variant; checked for every emitted variant, single path included. The
17
+ * caller's hook wins, else the variant's own `url` (variant URLs spec §6); neither is an error. */
18
+ function checkVariantUrls(variants, ctx) {
18
19
  const seen = new Set();
19
20
  return variants.map((v) => {
20
- const u = variantUrl(v);
21
+ const u = ctx.variantUrl?.(v) ?? v.url;
22
+ if (u == null)
23
+ throw new SeoError(`variant ${v.sku} has no URL: neither the variantUrl hook nor v.url (from the API) gave one`);
21
24
  let parsed = null;
22
25
  if (typeof u === "string") {
23
26
  try {
@@ -57,7 +60,8 @@ function offerExtras(ctx) {
57
60
  }
58
61
  /**
59
62
  * schema.org `Product` / `ProductGroup` for a product page (spec §6.2). Decision order: no offer → single (only for a
60
- * proven singleton) → group. Never an `AggregateOffer`, never a guessed price.
63
+ * proven singleton) → group. Never an `AggregateOffer`, never a guessed price. A priced `p.selectedVariant` is always
64
+ * in the group, appended when the list does not hold it (variant URLs spec §6).
61
65
  */
62
66
  export function productJsonLd(p, ctx) {
63
67
  rejectRedirect(p, `product ${p.sku}`);
@@ -66,19 +70,24 @@ export function productJsonLd(p, ctx) {
66
70
  for (const [axis, prop] of Object.entries(variesBy))
67
71
  if (!PROPERTIES.includes(prop))
68
72
  throw new SeoError(`variesBy['${axis}'] = '${prop}' is not one of ${PROPERTIES.join(", ")}`);
69
- if (typeof ctx.variantUrl !== "function")
70
- throw new SeoError("variantUrl is required: Google needs a distinct URL that preselects each variant");
73
+ if (ctx.variantUrl !== undefined && typeof ctx.variantUrl !== "function")
74
+ throw new SeoError("variantUrl must be a function");
71
75
  const list = p.variants ?? ctx.variants ?? null;
72
76
  const complete = p.variants !== null;
73
- const sellable = (list ?? []).filter((v) => v.price !== null);
77
+ // Variant URLs spec §6: a priced selection is always emitted, exactly once (deduplicated by id against the list).
78
+ const sel = p.selectedVariant?.price != null ? p.selectedVariant : null;
79
+ const merged = sel !== null && !(list ?? []).some((v) => v.id === sel.id) ? [...(list ?? []), sel] : (list ?? []);
80
+ const sellable = merged.filter((v) => v.price !== null);
74
81
  const description = jsonLdDescription(productDescription(p));
75
82
  const images = pickImages(ctx.site, p.images, market.language, p.name).map((i) => i.url);
76
- const common = (type) => ({
83
+ // Variant URLs spec §5.4: effective images = the variant's own set when it owns one (an owned empty set stays empty).
84
+ const effectiveImages = (v, alt) => v.hasOwnImages ? pickImages(ctx.site, v.images, market.language, alt).map((x) => x.url) : images;
85
+ const common = (type, image = images) => ({
77
86
  "@context": "https://schema.org",
78
87
  "@type": type,
79
88
  name: p.name,
80
89
  ...(description !== null ? { description } : {}),
81
- ...(images.length > 0 ? { image: images } : {}),
90
+ ...(image.length > 0 ? { image } : {}),
82
91
  url: p.url,
83
92
  });
84
93
  const offer = (v, url) => ({
@@ -98,14 +107,16 @@ export function productJsonLd(p, ctx) {
98
107
  const keyed = gtinJsonLd(g);
99
108
  return Object.keys(keyed).length > 0 ? keyed : { gtin: g };
100
109
  };
101
- if (list === null || sellable.length === 0)
110
+ if (sellable.length === 0)
102
111
  return { ...common("Product"), sku: p.sku, ...(p.brand ? { brand: brandNode(p.brand.label) } : {}) };
103
- if (list.length === 1 && p.options.length === 0 && sellable.length === 1 && (complete || Number(p.variantCount) === 1)) {
104
- const v = sellable[0];
105
- const [url] = checkVariantUrls([v], ctx.variantUrl);
106
- return { ...common("Product"), sku: v.sku, ...gtin(v), ...(v.brand ? { brand: brandNode(v.brand.label) } : {}), offers: offer(v, url) };
112
+ // The singleton is decided on the list alone: a selection never turns a proven singleton into a group (§6).
113
+ const listSellable = (list ?? []).filter((v) => v.price !== null);
114
+ if (list !== null && list.length === 1 && p.options.length === 0 && listSellable.length === 1 && (complete || Number(p.variantCount) === 1)) {
115
+ const v = listSellable[0];
116
+ const [url] = checkVariantUrls([v], ctx);
117
+ return { ...common("Product", effectiveImages(v, p.name)), sku: v.sku, ...gtin(v), ...(v.brand ? { brand: brandNode(v.brand.label) } : {}), offers: offer(v, url) };
107
118
  }
108
- const urls = checkVariantUrls(sellable, ctx.variantUrl);
119
+ const urls = checkVariantUrls(sellable, ctx);
109
120
  // Spec §4.14: hoist only from the DTO's own list, and only when every emitted variant has the same non-null label.
110
121
  const first = sellable[0].brand?.label;
111
122
  const hoisted = complete && first != null && sellable.every((v) => v.brand?.label === first) ? first : null;
@@ -113,7 +124,8 @@ export function productJsonLd(p, ctx) {
113
124
  const props = [...new Set(axes.map((a) => a.prop))];
114
125
  return {
115
126
  ...common("ProductGroup"),
116
- productGroupID: p.sku,
127
+ // VU8: the product id, which never changes and matches the feeds' item_group_id; a SKU can be renamed.
128
+ productGroupID: p.id,
117
129
  ...(props.length > 0 ? { variesBy: props.map((x) => `https://schema.org/${x}`) } : {}),
118
130
  ...(hoisted !== null ? { brand: brandNode(hoisted) } : {}),
119
131
  hasVariant: sellable.map((v, i) => {
@@ -124,13 +136,13 @@ export function productJsonLd(p, ctx) {
124
136
  if (value !== undefined && !Object.hasOwn(own, a.prop))
125
137
  own[a.prop] = value;
126
138
  }
127
- const vImages = v.hasOwnImages ? pickImages(ctx.site, v.images, market.language, name).map((x) => x.url) : [];
139
+ const vImages = effectiveImages(v, name);
128
140
  return {
129
141
  "@type": "Product",
130
142
  sku: v.sku,
131
143
  ...gtin(v),
132
144
  name,
133
- inProductGroupWithID: p.sku,
145
+ inProductGroupWithID: p.id,
134
146
  ...own,
135
147
  ...(vImages.length > 0 ? { image: vImages } : {}),
136
148
  ...(hoisted === null && v.brand ? { brand: brandNode(v.brand.label) } : {}),
@@ -36,8 +36,9 @@ export type SeoHead = {
36
36
  meta: SeoMeta[];
37
37
  };
38
38
  export type ProductJsonLdOptions = SeoContext & {
39
- /** Required: a distinct absolute http(s) URL that opens the page with this variant preselected (spec §4.12). */
40
- variantUrl: (v: VariantDto) => string;
39
+ /** A distinct absolute http(s) URL that opens the page with this variant preselected (spec §4.12). Optional: defaults
40
+ * to the variant's own `url` (variant URLs spec §6); a hook given here wins. */
41
+ variantUrl?: (v: VariantDto) => string;
41
42
  /** Option name (`options[].name`) → schema.org property, e.g. `{ Storlek: "size", Färg: "color" }`. */
42
43
  variesBy?: Record<string, SchemaProperty>;
43
44
  /** Used only when `p.variants` is `null` (more than 100 variants), e.g. the first page of `/variants`. */
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Variant URL helpers (spec 2026-10-16 §4, §7, §8 step 2). The selector is the query parameter `variant`; its key is the
3
+ * variant id exactly as `VariantDto.id` returns it. A frontend builds, reads and redirects with these, never by hand.
4
+ */
5
+ /** The fixed name of the variant selector parameter (VU3). */
6
+ export declare const VARIANT_PARAM = "variant";
7
+ /** What a request's `variant` parameter says: nothing, one numeric id, or anything else (empty, text, repeated). */
8
+ export type VariantParam = {
9
+ kind: "absent";
10
+ } | {
11
+ kind: "invalid";
12
+ raw: string[];
13
+ } | {
14
+ kind: "id";
15
+ value: string;
16
+ };
17
+ /**
18
+ * A received query. A `string` is a raw search (`"?a=1&b=2"` or `"a=1&b=2"`) and is the only form that keeps the
19
+ * interleaving of different keys; a record is the Next.js `searchParams` shape.
20
+ */
21
+ export type QueryInput = string | URLSearchParams | Record<string, string | string[] | undefined>;
22
+ /** The URL of one variant: the product URL with `variant=<id>` set by query handling (VU6: the product URL when the ids match).
23
+ * Prefer `v.url` when you have it: equal in meaning, not always byte-equal (the query is re-serialized). */
24
+ export declare function variantUrl(productUrl: string, productId: string, variantId: string): string;
25
+ /** Read the selector: every `variant` value in order; exactly one all-digit value is an id, anything else is invalid. */
26
+ export declare function readVariantParam(q: QueryInput): VariantParam;
27
+ /** The value to send as the API's `variant` query parameter: the id, `"-"` for an invalid selector, none when absent. */
28
+ export declare function variantQueryForApi(parsed: VariantParam): string | undefined;
29
+ /**
30
+ * Where a 301 to `destinationUrl` should point: the destination's path and query, plus every received parameter the
31
+ * destination does not set itself (destination wins), in incoming order and multiplicity. `variant` is always carried
32
+ * as received. Values are decoded and re-encoded (`a%20b` becomes `a+b`): names, values, multiplicity and order are kept.
33
+ */
34
+ export declare function redirectTarget(destinationUrl: string, received: QueryInput): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Variant URL helpers (spec 2026-10-16 §4, §7, §8 step 2). The selector is the query parameter `variant`; its key is the
3
+ * variant id exactly as `VariantDto.id` returns it. A frontend builds, reads and redirects with these, never by hand.
4
+ */
5
+ /** The fixed name of the variant selector parameter (VU3). */
6
+ export const VARIANT_PARAM = "variant";
7
+ // The API's key policy: a key equals `variant` ASCII case-insensitively.
8
+ const isVariantKey = (k) => k.toLowerCase() === VARIANT_PARAM;
9
+ function entries(q) {
10
+ if (typeof q === "string")
11
+ return [...new URLSearchParams(q.startsWith("?") ? q.slice(1) : q)];
12
+ if (q instanceof URLSearchParams)
13
+ return [...q];
14
+ const out = [];
15
+ for (const [k, v] of Object.entries(q)) {
16
+ if (v === undefined)
17
+ continue;
18
+ for (const item of Array.isArray(v) ? v : [v])
19
+ out.push([k, item]);
20
+ }
21
+ return out;
22
+ }
23
+ /** The URL of one variant: the product URL with `variant=<id>` set by query handling (VU6: the product URL when the ids match).
24
+ * Prefer `v.url` when you have it: equal in meaning, not always byte-equal (the query is re-serialized). */
25
+ export function variantUrl(productUrl, productId, variantId) {
26
+ if (variantId === productId)
27
+ return productUrl;
28
+ const url = new URL(productUrl);
29
+ for (const k of [...new Set([...url.searchParams.keys()])])
30
+ if (isVariantKey(k))
31
+ url.searchParams.delete(k);
32
+ url.searchParams.append(VARIANT_PARAM, variantId);
33
+ return url.toString();
34
+ }
35
+ /** Read the selector: every `variant` value in order; exactly one all-digit value is an id, anything else is invalid. */
36
+ export function readVariantParam(q) {
37
+ const raw = entries(q).filter(([k]) => isVariantKey(k)).map(([, v]) => v);
38
+ if (raw.length === 0)
39
+ return { kind: "absent" };
40
+ if (raw.length === 1 && /^\d{1,19}$/.test(raw[0]))
41
+ return { kind: "id", value: raw[0] };
42
+ return { kind: "invalid", raw };
43
+ }
44
+ /** The value to send as the API's `variant` query parameter: the id, `"-"` for an invalid selector, none when absent. */
45
+ export function variantQueryForApi(parsed) {
46
+ switch (parsed.kind) {
47
+ case "id": return parsed.value;
48
+ case "invalid": return "-";
49
+ default: return undefined;
50
+ }
51
+ }
52
+ /**
53
+ * Where a 301 to `destinationUrl` should point: the destination's path and query, plus every received parameter the
54
+ * destination does not set itself (destination wins), in incoming order and multiplicity. `variant` is always carried
55
+ * as received. Values are decoded and re-encoded (`a%20b` becomes `a+b`): names, values, multiplicity and order are kept.
56
+ */
57
+ export function redirectTarget(destinationUrl, received) {
58
+ const dest = new URL(destinationUrl);
59
+ const own = new Set(dest.searchParams.keys());
60
+ const out = new URLSearchParams();
61
+ for (const [k, v] of dest.searchParams)
62
+ if (!isVariantKey(k))
63
+ out.append(k, v);
64
+ for (const [k, v] of entries(received))
65
+ if (isVariantKey(k) || !own.has(k))
66
+ out.append(k, v);
67
+ const search = out.toString();
68
+ // A path starting with `//` would read as protocol-relative; keep one slash.
69
+ const path = dest.pathname.replace(/^\/{2,}/, "/");
70
+ return path + (search ? "?" + search : "");
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asstio-dev/storefront",
3
- "version": "0.24.0",
3
+ "version": "0.27.0",
4
4
  "description": "Typed client for the Asstio Storefront API (generated from OpenAPI).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,7 @@
21
21
  "test": "vitest run",
22
22
  "test:browser": "playwright test -c browser/playwright.config.ts",
23
23
  "check": "npm run generate && npm run typecheck-examples && npm test && git diff --exit-code -- src/schema.d.ts && test -z \"$(git status --porcelain -- src/schema.d.ts)\"",
24
- "smoke": "node --input-type=module -e \"import(\\\"./dist/index.js\\\").then(m => { for (const f of [\\\"createStorefrontClient\\\", \\\"filterQuery\\\", \\\"encodeFilterValues\\\", \\\"decodeFilterValues\\\", \\\"pictureSources\\\", \\\"mediaUrl\\\", \\\"formatImage\\\", \\\"styleToCss\\\", \\\"startCheckout\\\", \\\"syncCheckout\\\", \\\"completeCheckout\\\", \\\"getCheckoutStatus\\\", \\\"liveShippingFor\\\", \\\"getOrder\\\", \\\"awaitCompletion\\\", \\\"mountCheckout\\\", \\\"productJsonLd\\\", \\\"gtinJsonLd\\\", \\\"breadcrumbJsonLd\\\", \\\"productHead\\\", \\\"collectionHead\\\", \\\"brandHead\\\", \\\"toNextMetadata\\\", \\\"jsonLdScript\\\", \\\"SeoError\\\", \\\"productFacts\\\", \\\"toGa4Item\\\", \\\"toGa4Ecommerce\\\", \\\"longestCategory\\\", \\\"AnalyticsError\\\", \\\"trackSearchEvents\\\"]) if (typeof m[f] !== \\\"function\\\") throw new Error(\\\"missing export \\\" + f) })\""
24
+ "smoke": "node --input-type=module -e \"import(\\\"./dist/index.js\\\").then(m => { for (const f of [\\\"createStorefrontClient\\\", \\\"filterQuery\\\", \\\"encodeFilterValues\\\", \\\"decodeFilterValues\\\", \\\"pictureSources\\\", \\\"mediaUrl\\\", \\\"formatImage\\\", \\\"styleToCss\\\", \\\"startCheckout\\\", \\\"syncCheckout\\\", \\\"completeCheckout\\\", \\\"getCheckoutStatus\\\", \\\"liveShippingFor\\\", \\\"getOrder\\\", \\\"awaitCompletion\\\", \\\"mountCheckout\\\", \\\"productJsonLd\\\", \\\"gtinJsonLd\\\", \\\"breadcrumbJsonLd\\\", \\\"productHead\\\", \\\"collectionHead\\\", \\\"brandHead\\\", \\\"toNextMetadata\\\", \\\"jsonLdScript\\\", \\\"SeoError\\\", \\\"productFacts\\\", \\\"toGa4Item\\\", \\\"toGa4Ecommerce\\\", \\\"longestCategory\\\", \\\"AnalyticsError\\\", \\\"trackSearchEvents\\\", \\\"variantUrl\\\", \\\"readVariantParam\\\", \\\"variantQueryForApi\\\", \\\"redirectTarget\\\"]) if (typeof m[f] !== \\\"function\\\") throw new Error(\\\"missing export \\\" + f) })\""
25
25
  },
26
26
  "dependencies": {
27
27
  "openapi-fetch": "0.17.0"
package/src/schema.d.ts CHANGED
@@ -546,6 +546,7 @@ export interface components {
546
546
  selectedOptions: {
547
547
  [key: string]: string;
548
548
  };
549
+ url: null | string;
549
550
  };
550
551
  CategoryNodeDto: {
551
552
  children: components["schemas"]["CategoryNodeDto"][];
@@ -923,6 +924,7 @@ export interface components {
923
924
  };
924
925
  OrderLineVariantDto: {
925
926
  brand: null | components["schemas"]["BrandDto"];
927
+ url: null | string;
926
928
  };
927
929
  OrderShippingDto: {
928
930
  carrier: string;
@@ -1011,6 +1013,8 @@ export interface components {
1011
1013
  redirectTo: null | string;
1012
1014
  related: components["schemas"]["RelatedGroupDto"][];
1013
1015
  responsible: components["schemas"]["ResponsibleDto"];
1016
+ selectedVariant: null | components["schemas"]["VariantDto"];
1017
+ selectedVariantStatus: null | components["schemas"]["SelectedVariantStatus"];
1014
1018
  seo: components["schemas"]["SeoDto"];
1015
1019
  shortDescription: null | string;
1016
1020
  sku: string;
@@ -1099,6 +1103,8 @@ export interface components {
1099
1103
  name: string;
1100
1104
  slug: string;
1101
1105
  };
1106
+ /** @enum {unknown} */
1107
+ SelectedVariantStatus: "matched" | "notSoldHere" | "unknown";
1102
1108
  SellabilityDto: {
1103
1109
  mode: components["schemas"]["SiteSellabilityMode"];
1104
1110
  };
@@ -1255,6 +1261,7 @@ export interface components {
1255
1261
  };
1256
1262
  sku: string;
1257
1263
  stock: string;
1264
+ url: null | string;
1258
1265
  };
1259
1266
  VariantOptionDto: {
1260
1267
  name: string;
@@ -3314,7 +3321,10 @@ export interface operations {
3314
3321
  };
3315
3322
  GetProduct: {
3316
3323
  parameters: {
3317
- query?: never;
3324
+ query?: {
3325
+ /** @description A variant id (`VariantDto.id`) to preselect. Answered in `selectedVariantStatus`; a value that is not one of this product's variants is `unknown`, never an error. */
3326
+ variant?: string;
3327
+ };
3318
3328
  header: {
3319
3329
  /** @description Publishable, preview or secret site key. Required on every request; secret keys are server-side only. */
3320
3330
  "X-Site-Key": string;