@wocart/storefront 1.3.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 +30 -0
- package/dist/client/cart.d.ts +9 -1
- package/dist/client/cart.js +7 -4
- package/dist/client/products.d.ts +11 -1
- package/dist/client/products.js +24 -14
- package/dist/domain.d.ts +42 -0
- package/dist/domain.js +25 -0
- package/dist/endpoints/storefront-routes.d.ts +3 -1
- package/dist/endpoints/storefront-routes.js +13 -6
- 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,36 @@ 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
|
+
|
|
23
|
+
## 1.4.0 — 2026-10-15
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`cart.quote(lines, { destination })`** quotes what an order would cost for
|
|
28
|
+
a basket, or for one product exactly as its order form posts it: a quantity,
|
|
29
|
+
a variant per unit (`variantSelections`), an offer tier (`offerId`). The
|
|
30
|
+
server builds the lines the way the order does, so a product page's delivery
|
|
31
|
+
and total are the order's own. `validate(items, options)` is now its basket
|
|
32
|
+
form.
|
|
33
|
+
- `ProductQuoteInputSchema`, `CartQuoteLinesSchema` and the `ProductQuoteInput`
|
|
34
|
+
and `CartQuoteLines` types.
|
|
35
|
+
- The `cartValidate` route accepts a product form as well as a basket, each
|
|
36
|
+
with an optional `destination`.
|
|
37
|
+
|
|
8
38
|
## 1.3.0 — 2026-10-15
|
|
9
39
|
|
|
10
40
|
### Added
|
package/dist/client/cart.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { ClientCore, ClientResult } from "./core.js";
|
|
15
15
|
import type { CartValidation } from "../types.js";
|
|
16
|
-
import type { BasketLineInput, DeliveryDestination } from "../domain.js";
|
|
16
|
+
import type { BasketLineInput, CartQuoteLines, DeliveryDestination } from "../domain.js";
|
|
17
17
|
export interface CartNamespace {
|
|
18
18
|
/**
|
|
19
19
|
* Re-price and re-check a basket against the live catalog.
|
|
@@ -29,6 +29,14 @@ export interface CartNamespace {
|
|
|
29
29
|
validate(items: readonly BasketLineInput[], options?: {
|
|
30
30
|
destination?: DeliveryDestination;
|
|
31
31
|
}): Promise<ClientResult<CartValidation>>;
|
|
32
|
+
/**
|
|
33
|
+
* Quote what an order would cost, for a basket or for one product exactly as
|
|
34
|
+
* its order form posts it (quantity, variants per unit, offer tier). The lines
|
|
35
|
+
* are built the way the order builds them, so the quote and the order agree.
|
|
36
|
+
*/
|
|
37
|
+
quote(lines: CartQuoteLines, options?: {
|
|
38
|
+
destination?: DeliveryDestination;
|
|
39
|
+
}): Promise<ClientResult<CartValidation>>;
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
42
|
* Create cart namespace
|
package/dist/client/cart.js
CHANGED
|
@@ -18,10 +18,13 @@ import { localizedPath } from "./paths.js";
|
|
|
18
18
|
* Create cart namespace
|
|
19
19
|
*/
|
|
20
20
|
export function createCartNamespace(core) {
|
|
21
|
+
// A closure rather than `this`: a theme may destructure `store.cart`.
|
|
22
|
+
const quote = async (lines, options) => {
|
|
23
|
+
const destination = options?.destination;
|
|
24
|
+
return core.post(localizedPath(core.config, "/store/cart/validate"), destination ? { ...lines, destination } : lines, { schema: CartValidationSchema, operation: STOREFRONT_OPERATIONS.cartValidate });
|
|
25
|
+
};
|
|
21
26
|
return {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return core.post(localizedPath(core.config, "/store/cart/validate"), destination ? { items, destination } : { items }, { schema: CartValidationSchema, operation: STOREFRONT_OPERATIONS.cartValidate });
|
|
25
|
-
},
|
|
27
|
+
validate: (items, options) => quote({ items: [...items] }, options),
|
|
28
|
+
quote,
|
|
26
29
|
};
|
|
27
30
|
}
|
|
@@ -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/domain.d.ts
CHANGED
|
@@ -175,6 +175,48 @@ export declare const AbandonedCheckoutInputSchema: z.ZodObject<{
|
|
|
175
175
|
* `items[]` line: absent — not null — is the common wire shape.
|
|
176
176
|
*/
|
|
177
177
|
export type AbandonedCheckoutInput = z.infer<typeof AbandonedCheckoutInputSchema>;
|
|
178
|
+
/**
|
|
179
|
+
* One product as a product page's order form posts it: a quantity, or a
|
|
180
|
+
* variant chosen per unit, and an offer tier. The quote reads these the way the
|
|
181
|
+
* order does, so the page asks with exactly what its form will submit.
|
|
182
|
+
*/
|
|
183
|
+
export declare const ProductQuoteInputSchema: z.ZodObject<{
|
|
184
|
+
productId: z.ZodString;
|
|
185
|
+
productName: z.ZodString;
|
|
186
|
+
quantity: z.ZodOptional<z.ZodNumber>;
|
|
187
|
+
variantId: z.ZodOptional<z.ZodString>;
|
|
188
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
189
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
190
|
+
variantSelections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
191
|
+
variantId: z.ZodString;
|
|
192
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
193
|
+
}, z.core.$strip>>>;
|
|
194
|
+
}, z.core.$strip>;
|
|
195
|
+
export type ProductQuoteInput = z.infer<typeof ProductQuoteInputSchema>;
|
|
196
|
+
/** What a quote is asked about: a basket, or one product as its form posts it. */
|
|
197
|
+
export declare const CartQuoteLinesSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
198
|
+
items: z.ZodArray<z.ZodObject<{
|
|
199
|
+
productId: z.ZodString;
|
|
200
|
+
productName: z.ZodString;
|
|
201
|
+
variantId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
202
|
+
variantLabel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
203
|
+
quantity: z.ZodNumber;
|
|
204
|
+
pricePerUnit: z.ZodOptional<z.ZodNumber>;
|
|
205
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
206
|
+
}, z.core.$strip>>;
|
|
207
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
208
|
+
productId: z.ZodString;
|
|
209
|
+
productName: z.ZodString;
|
|
210
|
+
quantity: z.ZodOptional<z.ZodNumber>;
|
|
211
|
+
variantId: z.ZodOptional<z.ZodString>;
|
|
212
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
213
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
214
|
+
variantSelections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
215
|
+
variantId: z.ZodString;
|
|
216
|
+
variantLabel: z.ZodOptional<z.ZodString>;
|
|
217
|
+
}, z.core.$strip>>>;
|
|
218
|
+
}, z.core.$strip>]>;
|
|
219
|
+
export type CartQuoteLines = z.infer<typeof CartQuoteLinesSchema>;
|
|
178
220
|
/**
|
|
179
221
|
* Where a basket would be delivered, for a delivery quote. The commune is
|
|
180
222
|
* optional — without it the wilaya's rate applies; with it, the commune's own
|
package/dist/domain.js
CHANGED
|
@@ -126,6 +126,31 @@ export const AbandonedCheckoutInputSchema = z.object({
|
|
|
126
126
|
fbc: z.string().max(500).optional(),
|
|
127
127
|
fbp: z.string().max(500).optional(),
|
|
128
128
|
});
|
|
129
|
+
/**
|
|
130
|
+
* One product as a product page's order form posts it: a quantity, or a
|
|
131
|
+
* variant chosen per unit, and an offer tier. The quote reads these the way the
|
|
132
|
+
* order does, so the page asks with exactly what its form will submit.
|
|
133
|
+
*/
|
|
134
|
+
export const ProductQuoteInputSchema = z.object({
|
|
135
|
+
productId: z.string().min(1).max(200),
|
|
136
|
+
productName: z.string().min(1).max(200),
|
|
137
|
+
quantity: z.number().int().min(1).max(MAX_BASKET_LINE_QUANTITY).optional(),
|
|
138
|
+
variantId: z.string().min(1).max(200).optional(),
|
|
139
|
+
variantLabel: z.string().max(200).optional(),
|
|
140
|
+
offerId: z.string().min(1).max(200).optional(),
|
|
141
|
+
variantSelections: z
|
|
142
|
+
.array(z.object({
|
|
143
|
+
variantId: z.string().min(1).max(200),
|
|
144
|
+
variantLabel: z.string().max(200).optional(),
|
|
145
|
+
}))
|
|
146
|
+
.max(MAX_BASKET_LINE_QUANTITY)
|
|
147
|
+
.optional(),
|
|
148
|
+
});
|
|
149
|
+
/** What a quote is asked about: a basket, or one product as its form posts it. */
|
|
150
|
+
export const CartQuoteLinesSchema = z.union([
|
|
151
|
+
z.object({ items: z.array(BasketLineInputSchema).min(1).max(MAX_BASKET_LINES) }),
|
|
152
|
+
ProductQuoteInputSchema,
|
|
153
|
+
]);
|
|
129
154
|
/**
|
|
130
155
|
* Where a basket would be delivered, for a delivery quote. The commune is
|
|
131
156
|
* optional — without it the wilaya's rate applies; with it, the commune's own
|
|
@@ -33,7 +33,9 @@ export interface StorefrontRoutes<C extends AstroAPIContext> {
|
|
|
33
33
|
/** POST `{ phone, requestId, code }` → `{ data: OtpVerifyResult }`. */
|
|
34
34
|
readonly otpVerify: StorefrontApiRoute<C>;
|
|
35
35
|
/**
|
|
36
|
-
* POST `{ items
|
|
36
|
+
* POST a basket `{ items }` or one product `{ productId, productName, quantity,
|
|
37
|
+
* variantSelections, offerId, … }`, each with an optional `destination` →
|
|
38
|
+
* `{ success: true, data: CartValidation }`.
|
|
37
39
|
* With a destination, `data.delivery` is the fee and total the order will charge.
|
|
38
40
|
*/
|
|
39
41
|
readonly cartValidate: StorefrontApiRoute<C>;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* (see `createProxyEndpoint`), and the server's diagnostic fields ride along.
|
|
22
22
|
*/
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import { AbandonedCheckoutInputSchema, BasketLineInputSchema, DeliveryDestinationSchema, MAX_BASKET_LINES, WilayaIdSchema, } from "../domain.js";
|
|
24
|
+
import { AbandonedCheckoutInputSchema, BasketLineInputSchema, DeliveryDestinationSchema, ProductQuoteInputSchema, MAX_BASKET_LINES, WilayaIdSchema, } from "../domain.js";
|
|
25
25
|
import { createProxyEndpoint, } from "./proxy.js";
|
|
26
26
|
export const REVIEWS_PAGE_DEFAULT = 20;
|
|
27
27
|
export const REVIEWS_PAGE_MAX = 50;
|
|
@@ -31,10 +31,17 @@ const otpVerifyInput = z.object({
|
|
|
31
31
|
requestId: z.string().min(1).max(200),
|
|
32
32
|
code: z.string().min(1).max(16),
|
|
33
33
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
/**
|
|
35
|
+
* A basket, or one product as its order form posts it — the same two shapes the
|
|
36
|
+
* server's quote takes — each with an optional destination to quote to.
|
|
37
|
+
*/
|
|
38
|
+
const cartValidateInput = z.union([
|
|
39
|
+
z.object({
|
|
40
|
+
items: z.array(BasketLineInputSchema).min(1).max(MAX_BASKET_LINES),
|
|
41
|
+
destination: DeliveryDestinationSchema.optional(),
|
|
42
|
+
}),
|
|
43
|
+
ProductQuoteInputSchema.extend({ destination: DeliveryDestinationSchema.optional() }),
|
|
44
|
+
]);
|
|
38
45
|
const abandonedConvertInput = z.object({
|
|
39
46
|
sessionId: z.string().uuid(),
|
|
40
47
|
orderId: z.string().min(1).max(200),
|
|
@@ -88,7 +95,7 @@ export function createStorefrontRoutes(options) {
|
|
|
88
95
|
method: "POST",
|
|
89
96
|
inputSchema: cartValidateInput,
|
|
90
97
|
successBody: (data) => ({ success: true, data }),
|
|
91
|
-
call: (client, {
|
|
98
|
+
call: (client, { destination, ...lines }) => client.cart.quote(lines, destination ? { destination } : undefined),
|
|
92
99
|
}),
|
|
93
100
|
abandonedUpsert: route({
|
|
94
101
|
method: "POST",
|
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