@wocart/storefront 1.4.0 → 1.5.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 +15 -0
- package/dist/client/products.d.ts +11 -1
- package/dist/client/products.js +24 -14
- package/dist/offer-view.d.ts +1 -1
- package/dist/offer-view.js +10 -6
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.js +3 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ follows [semantic versioning](https://semver.org/): breaking changes ship only
|
|
|
5
5
|
in a new major version, and an export is marked `@deprecated` for at least one
|
|
6
6
|
minor release before a major removes it.
|
|
7
7
|
|
|
8
|
+
## 1.5.0 — 2026-10-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`products.page(filters)`** returns one page of products and whether another
|
|
13
|
+
follows (`{ items, hasNext }`). It asks the server for one row more than the
|
|
14
|
+
page, so the answer is exact without a count query. A page is 24 by default
|
|
15
|
+
and at most 99 (one below the server's 100-row cap). A failed read is an empty
|
|
16
|
+
final page, as `list` degrades. Themes stop re-deriving paging from `list`.
|
|
17
|
+
- `ProductPage` type.
|
|
18
|
+
- **`rewardUnitPrice`** on an offer: what one gift unit is worth at list price
|
|
19
|
+
(the gift variant's price, or the gift product's). `getOfferTierView` states
|
|
20
|
+
the saving at that value and the share of the list value it is. Without it,
|
|
21
|
+
the product's own price stands in, as before.
|
|
22
|
+
|
|
8
23
|
## 1.4.0 — 2026-10-15
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* half-shaped product into a theme.
|
|
13
13
|
*/
|
|
14
14
|
import { type ClientCore, type ClientResult } from "./core.js";
|
|
15
|
-
import type { Product, ProductList, ProductListFilters, Review, ReviewInput, ReviewListPagination } from "../types.js";
|
|
15
|
+
import type { Product, ProductList, ProductPage, ProductListFilters, Review, ReviewInput, ReviewListPagination } from "../types.js";
|
|
16
|
+
/** Page size when a caller names none — the storefront's default grid. */
|
|
17
|
+
export declare const PRODUCT_PAGE_DEFAULT = 24;
|
|
18
|
+
/** One below the server's 100-row cap, so the extra row that proves a next page fits. */
|
|
19
|
+
export declare const PRODUCT_PAGE_MAX = 99;
|
|
16
20
|
export interface ProductsNamespace {
|
|
17
21
|
/**
|
|
18
22
|
* Get a single product by its public URL handle
|
|
@@ -26,6 +30,12 @@ export interface ProductsNamespace {
|
|
|
26
30
|
* @returns Array of products
|
|
27
31
|
*/
|
|
28
32
|
list(filters?: ProductListFilters): Promise<ProductList[]>;
|
|
33
|
+
/**
|
|
34
|
+
* One page of products, and whether another page follows. The server is
|
|
35
|
+
* asked for one row more than the page, so the answer is exact without a
|
|
36
|
+
* count query. A failed read is an empty final page, as `list` degrades.
|
|
37
|
+
*/
|
|
38
|
+
page(filters?: ProductListFilters): Promise<ProductPage>;
|
|
29
39
|
/**
|
|
30
40
|
* Reviews sub-namespace
|
|
31
41
|
*/
|
package/dist/client/products.js
CHANGED
|
@@ -16,10 +16,29 @@ import { IdResponseSchema, ProductListSchema, ProductSchema, ReviewListEnvelopeS
|
|
|
16
16
|
import { STOREFRONT_OPERATIONS } from "../domain.js";
|
|
17
17
|
import { localizedPath } from "./paths.js";
|
|
18
18
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
19
|
+
/** Page size when a caller names none — the storefront's default grid. */
|
|
20
|
+
export const PRODUCT_PAGE_DEFAULT = 24;
|
|
21
|
+
/** One below the server's 100-row cap, so the extra row that proves a next page fits. */
|
|
22
|
+
export const PRODUCT_PAGE_MAX = 99;
|
|
19
23
|
/**
|
|
20
24
|
* Create products namespace
|
|
21
25
|
*/
|
|
22
26
|
export function createProductsNamespace(core) {
|
|
27
|
+
const list = async (filters) => {
|
|
28
|
+
try {
|
|
29
|
+
const products = await core.get(localizedPath(core.config, "/store/products", {
|
|
30
|
+
categoryId: filters?.categoryId || undefined,
|
|
31
|
+
featured: filters?.featured ? "true" : undefined,
|
|
32
|
+
limit: filters?.limit === undefined ? undefined : String(filters.limit),
|
|
33
|
+
offset: filters?.offset ? String(filters.offset) : undefined,
|
|
34
|
+
}), z.array(ProductListSchema), { operation: STOREFRONT_OPERATIONS.productsList });
|
|
35
|
+
return products ?? [];
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
reportDegradedRead(core.config, error, "products.list");
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
};
|
|
23
42
|
return {
|
|
24
43
|
async get(handle) {
|
|
25
44
|
try {
|
|
@@ -32,20 +51,11 @@ export function createProductsNamespace(core) {
|
|
|
32
51
|
return null;
|
|
33
52
|
}
|
|
34
53
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
limit: filters?.limit === undefined ? undefined : String(filters.limit),
|
|
41
|
-
offset: filters?.offset ? String(filters.offset) : undefined,
|
|
42
|
-
}), z.array(ProductListSchema), { operation: STOREFRONT_OPERATIONS.productsList });
|
|
43
|
-
return products ?? [];
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
reportDegradedRead(core.config, error, "products.list");
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
54
|
+
list,
|
|
55
|
+
async page(filters) {
|
|
56
|
+
const size = Math.min(Math.max(1, filters?.limit ?? PRODUCT_PAGE_DEFAULT), PRODUCT_PAGE_MAX);
|
|
57
|
+
const rows = await list({ ...filters, limit: size + 1 });
|
|
58
|
+
return { items: rows.slice(0, size), hasNext: rows.length > size };
|
|
49
59
|
},
|
|
50
60
|
reviews: {
|
|
51
61
|
async list(productId, pagination) {
|
package/dist/offer-view.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export interface OfferTierView {
|
|
|
6
6
|
price: number;
|
|
7
7
|
comparePrice: number | null;
|
|
8
8
|
}
|
|
9
|
-
export type OfferTierInput = Pick<Offer, "discountType" | "triggerQuantity" | "rewardQuantity">;
|
|
9
|
+
export type OfferTierInput = Pick<Offer, "discountType" | "triggerQuantity" | "rewardQuantity" | "rewardUnitPrice">;
|
|
10
10
|
export declare function getOfferTierView(offer: OfferTierInput, basePrice: number, comparePrice: number | null): OfferTierView;
|
package/dist/offer-view.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export function getOfferTierView(offer, basePrice, comparePrice) {
|
|
2
2
|
const isFreeShipping = offer.discountType === "free_shipping";
|
|
3
|
-
|
|
3
|
+
// A gift that is another product (or a variant priced apart) is worth its own
|
|
4
|
+
// price; without one, the product's price stands in for it.
|
|
5
|
+
const giftPrice = offer.rewardUnitPrice ?? basePrice;
|
|
6
|
+
const savingsAmount = isFreeShipping ? 0 : offer.rewardQuantity * giftPrice;
|
|
7
|
+
const price = basePrice * offer.triggerQuantity;
|
|
8
|
+
// The share of what the shopper would have paid for everything, gifts included.
|
|
9
|
+
const listValue = price + savingsAmount;
|
|
4
10
|
return {
|
|
5
11
|
isFreeShipping,
|
|
6
|
-
savePct: isFreeShipping ||
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
savingsAmount: isFreeShipping ? 0 : offer.rewardQuantity * basePrice,
|
|
10
|
-
price: basePrice * offer.triggerQuantity,
|
|
12
|
+
savePct: isFreeShipping || listValue <= 0 ? 0 : Math.round((savingsAmount / listValue) * 100),
|
|
13
|
+
savingsAmount,
|
|
14
|
+
price,
|
|
11
15
|
comparePrice: comparePrice === null ? null : comparePrice * offer.triggerQuantity,
|
|
12
16
|
};
|
|
13
17
|
}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare const OfferSchema: z.ZodObject<{
|
|
|
41
41
|
rewardProductName: z.ZodString;
|
|
42
42
|
rewardVariantId: z.ZodNullable<z.ZodString>;
|
|
43
43
|
rewardVariantLabel: z.ZodNullable<z.ZodString>;
|
|
44
|
+
rewardUnitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
44
45
|
}, z.core.$strip>;
|
|
45
46
|
export declare const CategorySchema: z.ZodObject<{
|
|
46
47
|
id: z.ZodString;
|
|
@@ -214,6 +215,7 @@ export declare const ProductSchema: z.ZodObject<{
|
|
|
214
215
|
rewardProductName: z.ZodString;
|
|
215
216
|
rewardVariantId: z.ZodNullable<z.ZodString>;
|
|
216
217
|
rewardVariantLabel: z.ZodNullable<z.ZodString>;
|
|
218
|
+
rewardUnitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
217
219
|
}, z.core.$strip>>;
|
|
218
220
|
}, z.core.$strip>;
|
|
219
221
|
export declare const StorePagePublicSchema: z.ZodObject<{
|
|
@@ -617,6 +619,7 @@ export declare const LandingPageSchema: z.ZodObject<{
|
|
|
617
619
|
rewardProductName: z.ZodString;
|
|
618
620
|
rewardVariantId: z.ZodNullable<z.ZodString>;
|
|
619
621
|
rewardVariantLabel: z.ZodNullable<z.ZodString>;
|
|
622
|
+
rewardUnitPrice: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
620
623
|
}, z.core.$strip>>;
|
|
621
624
|
}, z.core.$strip>>;
|
|
622
625
|
tracking: z.ZodObject<{
|
package/dist/schemas.js
CHANGED
|
@@ -38,6 +38,9 @@ export const OfferSchema = z.object({
|
|
|
38
38
|
rewardProductName: z.string(),
|
|
39
39
|
rewardVariantId: z.string().nullable(),
|
|
40
40
|
rewardVariantLabel: z.string().nullable(),
|
|
41
|
+
// What one gift unit is worth at list price. Optional: a server that predates
|
|
42
|
+
// it omits the field, and the product's own price stands in for the gift.
|
|
43
|
+
rewardUnitPrice: z.number().nullish(),
|
|
41
44
|
});
|
|
42
45
|
const productCategorySchema = z.object({
|
|
43
46
|
id: z.string(),
|
package/dist/types.d.ts
CHANGED
|
@@ -415,6 +415,12 @@ export interface ProductListFilters {
|
|
|
415
415
|
/** Products to skip — with `limit`, pages through the whole catalog. */
|
|
416
416
|
offset?: number;
|
|
417
417
|
}
|
|
418
|
+
/** One page of products from `products.page()`. */
|
|
419
|
+
export interface ProductPage {
|
|
420
|
+
items: ProductList[];
|
|
421
|
+
/** True when another page follows this one. */
|
|
422
|
+
hasNext: boolean;
|
|
423
|
+
}
|
|
418
424
|
/** `products.reviews.list()` paging — named for the same reason. */
|
|
419
425
|
export interface ReviewListPagination {
|
|
420
426
|
limit?: number;
|
package/package.json
CHANGED