@wocart/storefront 2.2.0 → 3.0.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/CHANGELOG.md +67 -0
- package/USAGE.md +96 -83
- package/dist/answer-readers.js +16 -43
- package/dist/astro.d.ts +2 -2
- package/dist/astro.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/products.d.ts +2 -3
- package/dist/client/products.js +2 -3
- package/dist/endpoints/storefront-routes.js +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/middleware.d.ts +6 -26
- package/dist/middleware.js +0 -9
- package/dist/product-view.d.ts +0 -8
- package/dist/product-view.js +0 -17
- package/dist/routes.d.ts +4 -13
- package/dist/routes.js +6 -9
- package/dist/schemas.d.ts +144 -222
- package/dist/schemas.js +77 -137
- package/dist/seo.js +2 -2
- package/dist/store-request.d.ts +5 -4
- package/dist/store-request.js +1 -1
- package/dist/types.d.ts +76 -111
- package/dist/unavailable-store.d.ts +23 -0
- package/dist/unavailable-store.js +58 -0
- package/dist/variant-picker.d.ts +6 -2
- package/dist/variant-picker.js +6 -9
- package/dist/whatsapp.d.ts +59 -6
- package/dist/whatsapp.js +28 -10
- package/package.json +1 -1
package/dist/schemas.js
CHANGED
|
@@ -15,24 +15,18 @@ export const ProductImageSchema = MediaImageSchema.extend({
|
|
|
15
15
|
altText: z.string().nullable(),
|
|
16
16
|
position: z.number().int().min(0),
|
|
17
17
|
});
|
|
18
|
+
/** One active variant of a product, in the merchant's order; its selection in the client's language. */
|
|
18
19
|
export const ProductVariantSchema = z.object({
|
|
19
20
|
id: z.string(),
|
|
20
|
-
productId: z.string(),
|
|
21
21
|
variations: z.record(z.string(), z.string()),
|
|
22
|
-
currency: z.string(),
|
|
23
22
|
price: z.number().int().min(0),
|
|
24
23
|
compareAtPrice: z.number().int().min(0).nullable(),
|
|
25
24
|
sku: z.string().nullable(),
|
|
26
|
-
barcode: z.string().nullable(),
|
|
27
25
|
inventory: z.number().int().min(0),
|
|
28
26
|
lowStockThreshold: z.number().int().min(0),
|
|
29
|
-
|
|
27
|
+
/** The product image this variant shows. */
|
|
30
28
|
imageId: z.string().nullable(),
|
|
31
29
|
isDefault: z.boolean(),
|
|
32
|
-
active: z.boolean(),
|
|
33
|
-
position: z.number().int().min(0),
|
|
34
|
-
createdAt: z.string().datetime(),
|
|
35
|
-
updatedAt: z.string().datetime(),
|
|
36
30
|
});
|
|
37
31
|
export const OfferSchema = z.object({
|
|
38
32
|
id: z.string(),
|
|
@@ -45,9 +39,8 @@ export const OfferSchema = z.object({
|
|
|
45
39
|
rewardProductName: z.string(),
|
|
46
40
|
rewardVariantId: z.string().nullable(),
|
|
47
41
|
rewardVariantLabel: z.string().nullable(),
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
rewardUnitPrice: z.number().nullish(),
|
|
42
|
+
/** What one gift unit is worth at list price: the reward variant's price, or its product's. */
|
|
43
|
+
rewardUnitPrice: z.number().nullable(),
|
|
51
44
|
});
|
|
52
45
|
/**
|
|
53
46
|
* Quantity pricing — price tiers by quantity ("2 for 3,500 DA"). The highest
|
|
@@ -83,6 +76,18 @@ const reviewStatsSchema = z.object({
|
|
|
83
76
|
avgRating: z.number(),
|
|
84
77
|
reviewCount: z.number().int(),
|
|
85
78
|
});
|
|
79
|
+
const variantOptionSchema = z.object({
|
|
80
|
+
name: z.string(),
|
|
81
|
+
values: z.array(z.object({
|
|
82
|
+
value: z.string(),
|
|
83
|
+
// The key may be ABSENT, not just null: rows written before the colour
|
|
84
|
+
// picker existed never carry it, and a strict `.nullable()` would reject
|
|
85
|
+
// the whole product, since a failed read degrades to null. Normalising to
|
|
86
|
+
// null keeps the public shape `string | null` for every consumer.
|
|
87
|
+
hexColor: z.string().nullable().default(null),
|
|
88
|
+
})),
|
|
89
|
+
});
|
|
90
|
+
/** A product as a storefront reads it, in the client's language — the fields a listing and a product page share. */
|
|
86
91
|
const productBaseFields = {
|
|
87
92
|
id: z.string(),
|
|
88
93
|
name: z.string(),
|
|
@@ -93,71 +98,35 @@ const productBaseFields = {
|
|
|
93
98
|
currency: z.string(),
|
|
94
99
|
price: z.number(),
|
|
95
100
|
compareAtPrice: z.number().nullable(),
|
|
96
|
-
/**
|
|
97
|
-
* @deprecated Always null: what the merchant paid is never served to a
|
|
98
|
-
* storefront. Older servers sent it; a later major drops it. Do not read it.
|
|
99
|
-
*/
|
|
100
|
-
costPrice: z.number().nullable(),
|
|
101
101
|
type: z.enum(["PHYSICAL", "DIGITAL"]),
|
|
102
102
|
hasVariants: z.boolean(),
|
|
103
|
+
/** The option axes a shopper picks from; null for a product without variants. */
|
|
104
|
+
variantOptions: z.array(variantOptionSchema).nullable(),
|
|
103
105
|
sku: z.string().nullable(),
|
|
104
106
|
inventory: z.number().int(),
|
|
105
107
|
trackInventory: z.boolean(),
|
|
106
108
|
lowStockThreshold: z.number().int(),
|
|
107
109
|
categoryId: z.string().nullable(),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* @deprecated Always null: the server never serves a removed product. The
|
|
115
|
-
* server still sends it for older releases; it will stop, and a later major
|
|
116
|
-
* drops it. Do not read it.
|
|
117
|
-
*/
|
|
118
|
-
deletedAt: z.null().optional(),
|
|
119
|
-
publishedAt: z.string().datetime().nullable(),
|
|
120
|
-
createdAt: z.string().datetime(),
|
|
121
|
-
updatedAt: z.string().datetime(),
|
|
122
|
-
shippingProfileId: z.string().nullable().optional(),
|
|
123
|
-
/** The title search engines and shared links show, in the client's language; null or absent: use `productMeta(product)`, which falls back to the name. */
|
|
124
|
-
metaTitle: z.string().nullish(),
|
|
125
|
-
/** The description search engines and shared links show; null or absent: `productMeta(product)` shortens the product's own. */
|
|
126
|
-
metaDescription: z.string().nullish(),
|
|
110
|
+
tags: z.array(z.string()),
|
|
111
|
+
/** The title search engines and shared links show; null: `productMeta(product)` falls back to the name. */
|
|
112
|
+
metaTitle: z.string().nullable(),
|
|
113
|
+
/** The description search engines and shared links show; null: `productMeta(product)` shortens the product's own. */
|
|
114
|
+
metaDescription: z.string().nullable(),
|
|
115
|
+
reviewStats: reviewStatsSchema.nullable(),
|
|
127
116
|
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
values: z.array(z.object({
|
|
131
|
-
value: z.string(),
|
|
132
|
-
// The key may be ABSENT, not just null: rows written before the colour
|
|
133
|
-
// picker existed never carry it, and a strict `.nullable()` would reject
|
|
134
|
-
// the whole product, since a failed read degrades to null. Normalising to
|
|
135
|
-
// null keeps the public shape `string | null` for every consumer.
|
|
136
|
-
hexColor: z.string().nullable().default(null),
|
|
137
|
-
})),
|
|
138
|
-
});
|
|
139
|
-
const rawProductFields = {
|
|
117
|
+
/** A product in a listing: its first photo stands for it. */
|
|
118
|
+
export const ProductListSchema = z.object({
|
|
140
119
|
...productBaseFields,
|
|
141
|
-
variantOptions: z.string().nullable(),
|
|
142
|
-
/** The tags as stored JSON text; read them with `productTags(product)`, which a product page's list also goes through. */
|
|
143
|
-
tags: z.string().nullable(),
|
|
144
120
|
coverImage: ProductImageSchema.nullable(),
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
export const ProductListSchema = z.object(rawProductFields);
|
|
121
|
+
});
|
|
122
|
+
/** A product's page: every photo, variant and offer, and its category. */
|
|
148
123
|
export const ProductSchema = z.object({
|
|
149
124
|
...productBaseFields,
|
|
150
|
-
variantOptions: z.array(variantOptionSchema).nullable(),
|
|
151
|
-
tags: z.array(z.string()),
|
|
152
125
|
category: productCategorySchema.nullable(),
|
|
153
126
|
variants: z.array(ProductVariantSchema),
|
|
154
127
|
images: z.array(ProductImageSchema),
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
* The product's live quantity pricing; null or absent when it has none
|
|
158
|
-
* (absent from a server that predates it).
|
|
159
|
-
*/
|
|
160
|
-
quantityPricing: QuantityPricingSchema.nullish(),
|
|
128
|
+
/** The product's live quantity pricing; null when it has none. */
|
|
129
|
+
quantityPricing: QuantityPricingSchema.nullable(),
|
|
161
130
|
offers: z.array(OfferSchema),
|
|
162
131
|
});
|
|
163
132
|
export const StorePagePublicSchema = z.object({
|
|
@@ -186,10 +155,10 @@ export const StoreCollectionSchema = z.object({
|
|
|
186
155
|
handle: z.string(),
|
|
187
156
|
title: z.string(),
|
|
188
157
|
description: z.string().nullable(),
|
|
189
|
-
/** How many products a shopper sees in it
|
|
190
|
-
productCount: z.number().int().min(0)
|
|
191
|
-
/** Its first listed product's first photo;
|
|
192
|
-
image: ProductImageSchema.
|
|
158
|
+
/** How many products a shopper sees in it. */
|
|
159
|
+
productCount: z.number().int().min(0),
|
|
160
|
+
/** Its first listed product's first photo; null while it lists none with one. */
|
|
161
|
+
image: ProductImageSchema.nullable(),
|
|
193
162
|
});
|
|
194
163
|
export const StoreMenuSchema = z.object({
|
|
195
164
|
handle: z.string(),
|
|
@@ -218,11 +187,10 @@ export const StorePageLinkSchema = z.object({
|
|
|
218
187
|
title: z.string(),
|
|
219
188
|
position: z.number().int(),
|
|
220
189
|
});
|
|
221
|
-
export const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
deliveryMaxDays: z.number().int(),
|
|
190
|
+
export const StoreContactSchema = z.object({
|
|
191
|
+
phone: z.string().nullable(),
|
|
192
|
+
email: z.string().nullable(),
|
|
193
|
+
address: z.string().nullable(),
|
|
226
194
|
});
|
|
227
195
|
export const StoreWhatsAppWidgetSchema = z.object({
|
|
228
196
|
href: z.string(),
|
|
@@ -234,8 +202,8 @@ export const StoreWhatsAppWidgetSchema = z.object({
|
|
|
234
202
|
ctaLabel: z.string().nullable(),
|
|
235
203
|
prefillGeneral: z.string().nullable(),
|
|
236
204
|
prefillProduct: z.string().nullable(),
|
|
237
|
-
thankYouButtonLabel: z.string().nullable()
|
|
238
|
-
prefillThankYou: z.string().nullable()
|
|
205
|
+
thankYouButtonLabel: z.string().nullable(),
|
|
206
|
+
prefillThankYou: z.string().nullable(),
|
|
239
207
|
accent: z.enum(["whatsapp", "primary"]),
|
|
240
208
|
position: z.enum(["right", "left"]),
|
|
241
209
|
attention: z.boolean(),
|
|
@@ -273,12 +241,6 @@ export const StoreConfigSchema = z.object({
|
|
|
273
241
|
name: z.string(),
|
|
274
242
|
domain: z.string().nullable(),
|
|
275
243
|
logo: MediaImageSchema.nullable(),
|
|
276
|
-
themeId: z.string(),
|
|
277
|
-
primaryColor: z.string(),
|
|
278
|
-
accentColor: z.string(),
|
|
279
|
-
bgColor: z.string(),
|
|
280
|
-
fontFamily: z.string(),
|
|
281
|
-
fontUrl: z.string().nullable(),
|
|
282
244
|
lang: z.string().min(1),
|
|
283
245
|
locale: z.string().min(1),
|
|
284
246
|
/**
|
|
@@ -286,44 +248,28 @@ export const StoreConfigSchema = z.object({
|
|
|
286
248
|
* server owns which languages exist, so a new one never fails this parse.
|
|
287
249
|
*/
|
|
288
250
|
locales: z.array(StorefrontLanguageSchema),
|
|
289
|
-
/** The requesting theme's text edits, one map per published language. */
|
|
290
|
-
contentOverrides: z.record(z.string(), z.record(z.string(), z.string())),
|
|
291
|
-
/**
|
|
292
|
-
* The store's values for the requesting theme's settings, as saved. Absent
|
|
293
|
-
* from a server older than theme settings.
|
|
294
|
-
*/
|
|
295
|
-
themeSettings: z.record(z.string(), z.unknown()).optional(),
|
|
296
251
|
currency: z.string(),
|
|
297
252
|
currencySymbol: z.string(),
|
|
298
253
|
metaTitle: z.string().nullable(),
|
|
299
254
|
metaDescription: z.string().nullable(),
|
|
300
255
|
ogImage: MediaImageSchema.nullable(),
|
|
301
256
|
media: MediaDeliverySchema,
|
|
302
|
-
announcementBar: z.string().nullable(),
|
|
303
257
|
reviewsEnabled: z.boolean(),
|
|
304
258
|
cartEnabled: z.boolean(),
|
|
305
259
|
freeShippingThreshold: z.number().int().nullable(),
|
|
306
|
-
|
|
260
|
+
/** The requesting theme's text edits, one map per published language. */
|
|
261
|
+
contentOverrides: z.record(z.string(), z.record(z.string(), z.string())),
|
|
262
|
+
/** The store's values for the requesting theme's settings, as saved. */
|
|
263
|
+
themeSettings: z.record(z.string(), z.unknown()),
|
|
264
|
+
checkoutForm: CheckoutFormPolicySchema,
|
|
265
|
+
whatsapp: StoreWhatsAppWidgetSchema.nullable(),
|
|
266
|
+
pixelId: z.string().nullable(),
|
|
267
|
+
conversionEvent: z.enum(["Purchase", "Purchase_Confirmed", "Purchase_Delivered", "Lead"]),
|
|
268
|
+
pages: z.array(StorePageLinkSchema),
|
|
269
|
+
contact: StoreContactSchema,
|
|
307
270
|
otpEnabled: z.boolean(),
|
|
308
271
|
turnstileEnabled: z.boolean(),
|
|
309
272
|
turnstileSiteKey: z.string().nullable(),
|
|
310
|
-
status: z.enum(["active", "inactive"]),
|
|
311
|
-
createdAt: z.string().optional().default(() => new Date().toISOString()),
|
|
312
|
-
updatedAt: z.string().optional().default(() => new Date().toISOString()),
|
|
313
|
-
checkoutForm: CheckoutFormPolicySchema.default({
|
|
314
|
-
address: "optional",
|
|
315
|
-
notes: "optional",
|
|
316
|
-
email: "optional",
|
|
317
|
-
deliveryOptions: { home: true, stopDesk: true },
|
|
318
|
-
customFields: [],
|
|
319
|
-
}),
|
|
320
|
-
pixelId: z.string().nullable().default(null),
|
|
321
|
-
conversionEvent: z
|
|
322
|
-
.enum(["Purchase", "Purchase_Confirmed", "Purchase_Delivered", "Lead"])
|
|
323
|
-
.default("Purchase"),
|
|
324
|
-
pages: z.array(StorePageLinkSchema).default([]),
|
|
325
|
-
legalContact: StoreLegalContactSchema.nullable().default(null),
|
|
326
|
-
whatsapp: StoreWhatsAppWidgetSchema.nullable().default(null),
|
|
327
273
|
});
|
|
328
274
|
export const LandingPageImageSchema = MediaImageSchema.extend({
|
|
329
275
|
id: z.string(),
|
|
@@ -341,7 +287,7 @@ export const LandingPageSchema = z.object({
|
|
|
341
287
|
publishedAt: z.string().nullable(),
|
|
342
288
|
images: z.array(LandingPageImageSchema),
|
|
343
289
|
product: ProductSchema.nullable(),
|
|
344
|
-
document: PageDocumentPayloadSchema.nullable().catch(null)
|
|
290
|
+
document: PageDocumentPayloadSchema.nullable().catch(null),
|
|
345
291
|
tracking: z.object({
|
|
346
292
|
pixelId: z.string().nullable(),
|
|
347
293
|
conversionEvent: z.enum([
|
|
@@ -370,24 +316,24 @@ export const CartValidationLineSchema = z.object({
|
|
|
370
316
|
quantity: z.number().int(),
|
|
371
317
|
unitPrice: z.number(),
|
|
372
318
|
lineTotal: z.number(),
|
|
373
|
-
/** The offer applied to the line
|
|
374
|
-
offerId: z.string().
|
|
375
|
-
/** That offer's name, in the shopper's language
|
|
376
|
-
offerName: z.string().
|
|
377
|
-
/** The line's share of the coupon's discount, already out of `lineTotal
|
|
378
|
-
discount: z.number()
|
|
319
|
+
/** The offer applied to the line. */
|
|
320
|
+
offerId: z.string().nullable(),
|
|
321
|
+
/** That offer's name, in the shopper's language. */
|
|
322
|
+
offerName: z.string().nullable(),
|
|
323
|
+
/** The line's share of the coupon's discount, already out of `lineTotal`. */
|
|
324
|
+
discount: z.number(),
|
|
379
325
|
maxQuantity: z.number().nullable(),
|
|
380
326
|
blocker: z.enum(["missing", "unlisted", "out_of_stock"]).nullable(),
|
|
381
327
|
});
|
|
382
328
|
export const CartRewardSchema = z.object({
|
|
383
329
|
offerId: z.string(),
|
|
384
|
-
/** The offer's name, in the shopper's language
|
|
385
|
-
offerName: z.string().
|
|
330
|
+
/** The offer's name, in the shopper's language. */
|
|
331
|
+
offerName: z.string().nullable(),
|
|
386
332
|
productId: z.string().nullable(),
|
|
387
333
|
productName: z.string().nullable(),
|
|
388
|
-
/** The variant given and its label;
|
|
389
|
-
variantId: z.string().
|
|
390
|
-
variantLabel: z.string().
|
|
334
|
+
/** The variant given and its label; null for a product without variants. */
|
|
335
|
+
variantId: z.string().nullable(),
|
|
336
|
+
variantLabel: z.string().nullable(),
|
|
391
337
|
quantity: z.number().int(),
|
|
392
338
|
});
|
|
393
339
|
/**
|
|
@@ -409,6 +355,8 @@ export const CartValidationSchema = z.object({
|
|
|
409
355
|
threshold: z.number().nullable(),
|
|
410
356
|
qualified: z.boolean(),
|
|
411
357
|
remaining: z.number(),
|
|
358
|
+
/** Why delivery is free: an offer, the coupon or the store's amount; null when it is not. */
|
|
359
|
+
by: z.enum(["offer", "coupon", "threshold"]).nullable(),
|
|
412
360
|
}),
|
|
413
361
|
delivery: z
|
|
414
362
|
.discriminatedUnion("available", [
|
|
@@ -416,8 +364,8 @@ export const CartValidationSchema = z.object({
|
|
|
416
364
|
z.object({ available: z.literal(false), fee: z.null(), total: z.null() }),
|
|
417
365
|
])
|
|
418
366
|
.optional(),
|
|
419
|
-
/** The code's outcome: null without one
|
|
420
|
-
coupon: CouponOutcomeSchema.
|
|
367
|
+
/** The code's outcome: null without one. */
|
|
368
|
+
coupon: CouponOutcomeSchema.nullable(),
|
|
421
369
|
});
|
|
422
370
|
export const CreatedOrderSchema = z.object({
|
|
423
371
|
orderId: z.string(),
|
|
@@ -425,8 +373,8 @@ export const CreatedOrderSchema = z.object({
|
|
|
425
373
|
price: z.number(),
|
|
426
374
|
deliveryFee: z.number(),
|
|
427
375
|
total: z.number(),
|
|
428
|
-
/** What the coupon took off
|
|
429
|
-
couponDiscount: z.number()
|
|
376
|
+
/** What the coupon took off. */
|
|
377
|
+
couponDiscount: z.number(),
|
|
430
378
|
});
|
|
431
379
|
export const OrderTrackingSchema = z.object({
|
|
432
380
|
pixelId: z.string().nullable(),
|
|
@@ -445,10 +393,10 @@ export const ShopperOrderStatusSchema = z.object({
|
|
|
445
393
|
total: z.number(),
|
|
446
394
|
tracking: z.object({ number: z.string(), carrier: z.string().nullable() }).nullable(),
|
|
447
395
|
});
|
|
448
|
-
/** A page of products with the whole listing's size
|
|
396
|
+
/** A page of products with the whole listing's size. */
|
|
449
397
|
export const ProductPageEnvelopeSchema = z.object({
|
|
450
398
|
data: z.array(ProductListSchema),
|
|
451
|
-
total: z.number().int().min(0)
|
|
399
|
+
total: z.number().int().min(0),
|
|
452
400
|
});
|
|
453
401
|
/**
|
|
454
402
|
* The `{ id }` success payload of the write endpoints that only mint a row:
|
|
@@ -483,25 +431,17 @@ export const ReviewMediaSchema = z.object({
|
|
|
483
431
|
height: z.number().int().nullable(),
|
|
484
432
|
durationMs: z.number().int().nullable(),
|
|
485
433
|
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const obj = raw;
|
|
489
|
-
return {
|
|
490
|
-
...obj,
|
|
491
|
-
customerName: obj.customerName ?? obj.authorName ?? "Customer",
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
return raw;
|
|
495
|
-
}, z.object({
|
|
434
|
+
/** An approved review, newest first: who wrote it, and whether a delivered order proves the purchase. */
|
|
435
|
+
export const ReviewSchema = z.object({
|
|
496
436
|
id: z.string(),
|
|
497
437
|
customerName: z.string(),
|
|
498
438
|
rating: z.number(),
|
|
499
|
-
title: z.string().nullable()
|
|
439
|
+
title: z.string().nullable(),
|
|
500
440
|
body: z.string(),
|
|
501
441
|
createdAt: z.string(),
|
|
502
|
-
verified: z.boolean()
|
|
503
|
-
media: z.array(ReviewMediaSchema)
|
|
504
|
-
})
|
|
442
|
+
verified: z.boolean(),
|
|
443
|
+
media: z.array(ReviewMediaSchema),
|
|
444
|
+
});
|
|
505
445
|
export const ReviewEligibilitySchema = z.object({
|
|
506
446
|
orderNumber: z.string(),
|
|
507
447
|
customerName: z.string(),
|
|
@@ -527,7 +467,7 @@ export const ReviewUploadSchema = z.object({
|
|
|
527
467
|
});
|
|
528
468
|
export const ReviewSubmitResultSchema = z.object({
|
|
529
469
|
id: z.string(),
|
|
530
|
-
visibility: z.enum(["published", "after_review"])
|
|
470
|
+
visibility: z.enum(["published", "after_review"]),
|
|
531
471
|
});
|
|
532
472
|
/**
|
|
533
473
|
* GET /store/reviews answers `{ success, data: rows, count, total }` — the
|
package/dist/seo.js
CHANGED
|
@@ -44,7 +44,7 @@ const AVAILABILITY = { inStock: "https://schema.org/InStock", outOfStock: "https
|
|
|
44
44
|
export function productStructuredData(product, options) {
|
|
45
45
|
const meta = productMeta(product);
|
|
46
46
|
const view = getProductView(product);
|
|
47
|
-
const variants = product.hasVariants ? product.variants
|
|
47
|
+
const variants = product.hasVariants ? product.variants : [];
|
|
48
48
|
const prices = variants.map((variant) => variant.price);
|
|
49
49
|
const sellable = product.hasVariants
|
|
50
50
|
? variants.some((variant) => !product.trackInventory || variant.inventory > 0)
|
|
@@ -55,7 +55,7 @@ export function productStructuredData(product, options) {
|
|
|
55
55
|
? { "@type": "AggregateOffer", url: options.url, priceCurrency: product.currency, lowPrice: Math.min(...prices), highPrice: Math.max(...prices), offerCount: prices.length, availability }
|
|
56
56
|
: { "@type": "Offer", url: options.url, priceCurrency: product.currency, price: view.basePrice, availability, itemCondition: "https://schema.org/NewCondition" };
|
|
57
57
|
const sku = product.sku ?? (product.hasVariants ? initialVariant(product)?.sku : null) ?? null;
|
|
58
|
-
const images =
|
|
58
|
+
const images = product.images.map((image) => image.url);
|
|
59
59
|
const rating = product.reviewStats && product.reviewStats.reviewCount > 0 ? product.reviewStats : null;
|
|
60
60
|
return {
|
|
61
61
|
"@context": "https://schema.org",
|
package/dist/store-request.d.ts
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* language the draft asks for when the store serves it.
|
|
15
15
|
*
|
|
16
16
|
* The theme keeps what is its own: the client it creates (one per request),
|
|
17
|
-
* the config it falls back to when the store cannot be read
|
|
18
|
-
* it builds from what these
|
|
17
|
+
* the config it falls back to when the store cannot be read
|
|
18
|
+
* (`unavailableStoreConfig`), and the context it builds from what these
|
|
19
|
+
* return.
|
|
19
20
|
*/
|
|
20
21
|
import type { StoreClient } from "./client/index.js";
|
|
21
22
|
import { type LocaleRouting } from "./locale-routing.js";
|
|
@@ -24,7 +25,7 @@ import type { StoreConfig } from "./types.js";
|
|
|
24
25
|
export interface StoreRequestOptions {
|
|
25
26
|
/** The request's client in `locale`, or in the store's primary language when absent. */
|
|
26
27
|
client: (locale?: string) => StoreClient;
|
|
27
|
-
/** What renders when the store's config cannot be read:
|
|
28
|
+
/** What renders when the store's config cannot be read: `unavailableStoreConfig(…)`. */
|
|
28
29
|
fallback: StoreConfig;
|
|
29
30
|
/** Whether a path serves part of a page rather than a page. Default: `/api/` and `/_server-islands/`. */
|
|
30
31
|
embedded?: (pathname: string) => boolean;
|
|
@@ -60,7 +61,7 @@ export type StoreRequestResolution = ({
|
|
|
60
61
|
* ```ts
|
|
61
62
|
* const request = await resolveStoreRequest(
|
|
62
63
|
* { url: context.url, referer: context.request.headers.get("Referer") },
|
|
63
|
-
* { client: createStore, fallback:
|
|
64
|
+
* { client: createStore, fallback: unavailableStoreConfig({ serverUrl: env.COD_SERVER_URL }) },
|
|
64
65
|
* );
|
|
65
66
|
* if (request.kind === "redirect") return context.redirect(request.location, request.status);
|
|
66
67
|
* context.locals.store = buildContext(request);
|
package/dist/store-request.js
CHANGED
|
@@ -38,7 +38,7 @@ function askingPage(url, referer) {
|
|
|
38
38
|
* ```ts
|
|
39
39
|
* const request = await resolveStoreRequest(
|
|
40
40
|
* { url: context.url, referer: context.request.headers.get("Referer") },
|
|
41
|
-
* { client: createStore, fallback:
|
|
41
|
+
* { client: createStore, fallback: unavailableStoreConfig({ serverUrl: env.COD_SERVER_URL }) },
|
|
42
42
|
* );
|
|
43
43
|
* if (request.kind === "redirect") return context.redirect(request.location, request.status);
|
|
44
44
|
* context.locals.store = buildContext(request);
|