@wocart/storefront 1.17.0 → 1.19.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 +46 -0
- package/USAGE.md +27 -7
- package/dist/answer-readers.d.ts +16 -0
- package/dist/answer-readers.js +41 -0
- package/dist/basket-line.d.ts +15 -0
- package/dist/basket-line.js +17 -0
- package/dist/basket.d.ts +1 -1
- package/dist/basket.js +5 -1
- package/dist/client/cart.js +1 -1
- package/dist/client/categories.js +1 -1
- package/dist/client/collections.js +1 -1
- package/dist/client/config.js +1 -1
- package/dist/client/geo.js +1 -1
- package/dist/client/menus.js +1 -1
- package/dist/client/orders.js +1 -1
- package/dist/client/page-data.js +1 -1
- package/dist/client/pages.js +1 -1
- package/dist/client/products.js +1 -1
- package/dist/client/shipping.js +1 -1
- package/dist/domain.d.ts +2 -70
- package/dist/domain.js +6 -70
- package/dist/endpoints/storefront-routes.js +2 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/operations.d.ts +68 -0
- package/dist/operations.js +66 -0
- package/dist/otp.d.ts +8 -1
- package/dist/otp.js +3 -0
- package/dist/wilaya-id.d.ts +1 -0
- package/dist/wilaya-id.js +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ 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.19.0 — 2026-10-21
|
|
9
|
+
|
|
10
|
+
What a theme's browser code takes from the package brings no Zod. Validation
|
|
11
|
+
stays where data enters — a shopper's request at a route, the platform's answer
|
|
12
|
+
in the client — on the server; the browser reads what those checked.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `isWilayaId(value)`: whether a value is a wilaya id, the range
|
|
17
|
+
`WilayaIdSchema` checks on the server, read from the same `WILAYAS` — for the
|
|
18
|
+
browser, which checks a selected wilaya without a schema.
|
|
19
|
+
- `readShippingRates(value)` and `readCouponOutcome(value)`: the delivery rates
|
|
20
|
+
a page carries and a quote's coupon outcome, read in the browser. Each accepts
|
|
21
|
+
exactly what `ShippingRatesSchema` / `CouponOutcomeSchema` accept and returns
|
|
22
|
+
what they would, without Zod — a page cached apart from its scripts can still
|
|
23
|
+
hold another version's shape. A property test holds each to its schema.
|
|
24
|
+
- `mapOtpResponse` takes no `schema` for an answer of the SDK's own
|
|
25
|
+
`otpSend` / `otpVerify` routes, whose client already checked it on the
|
|
26
|
+
server. An answer with no `data` object is still a `MALFORMED_RESPONSE`
|
|
27
|
+
failure. Passing a schema works as before.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- The basket's limits and line identity (`MAX_BASKET_LINES`,
|
|
32
|
+
`MAX_BASKET_LINE_QUANTITY`, `basketLineKey`) and the operation tables
|
|
33
|
+
(`STOREFRONT_OPERATIONS`, `STOREFRONT_RETRY_POLICIES`) moved out of the
|
|
34
|
+
module that builds the schemas. The exports are the same; a browser bundle
|
|
35
|
+
that uses the basket or names an operation no longer carries Zod.
|
|
36
|
+
- A test bundles every browser-facing export through the package entry the
|
|
37
|
+
way a theme's Vite build does and fails, naming the export, if Zod appears.
|
|
38
|
+
|
|
39
|
+
## 1.18.0 — 2026-10-21
|
|
40
|
+
|
|
41
|
+
The basket carries the offer a shopper chose for a line, so a quantity tier
|
|
42
|
+
that two offers share is priced as the shopper picked it.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- `BasketLine.offerId` (optional): the offer chosen for the line. `addLine`
|
|
47
|
+
keeps it; a plain add to the line keeps the chosen offer, and a later explicit
|
|
48
|
+
choice replaces it. A stored offer survives a reload; a value that is not a
|
|
49
|
+
string is dropped.
|
|
50
|
+
- `toOrderItems` and `BasketStore.toOrderItems()` send `offerId` with the line,
|
|
51
|
+
so checkout and cart validation receive the choice. A line that chose no offer
|
|
52
|
+
sends none.
|
|
53
|
+
|
|
8
54
|
## 1.17.0 — 2026-10-18
|
|
9
55
|
|
|
10
56
|
### Added
|
package/USAGE.md
CHANGED
|
@@ -218,6 +218,8 @@ server directly.
|
|
|
218
218
|
| `mediaUrl(key, mediaDomain)` | The public URL of a stored media key — what a theme's `image` field holds (`{ key: "products/…" }`): `mediaUrl(image.key, MEDIA_DOMAIN)`; `null` with no media domain or a key that cannot name an object. The key is the identity; the URL is derived at render |
|
|
219
219
|
| `createImageHelpers(mediaDomain)`, `getCardImage`, `getGalleryImage`, `getThumbnailImage`, `getHeroImage`, `getCategoryImage`, `getLogoImage` | The bound image toolkit (`IMAGE_SIZES` holds the presets) |
|
|
220
220
|
| `WILAYAS`, `ALGERIA_COMMUNES`, `communesForWilaya` | Reference data: 58 wilayas, 1,551 communes, one wilaya's rows |
|
|
221
|
+
| `isWilayaId` | Whether a value is a wilaya id — the range `WilayaIdSchema` checks on the server, without a schema in the browser |
|
|
222
|
+
| `readShippingRates`, `readCouponOutcome` | Read in the browser what the server handed it — the rates a page carries, a quote's coupon outcome — accepting exactly what `ShippingRatesSchema` / `CouponOutcomeSchema` accept, without Zod |
|
|
221
223
|
| `normalizeAlgerianPhone`, `toLocalAlgerianMobile` | The one Algerian phone rule (accept/format) |
|
|
222
224
|
| `resolvePrefillText`, `buildWhatsAppHref`, `stripTrackingParams`, `truncateAtWordBoundary`, `PREFILL_TOKENS`, `MAX_RESOLVED_PREFILL_LENGTH` | WhatsApp prefill text and the `wa.me` link |
|
|
223
225
|
| `storefrontPath`, `matchStorefrontPath`, `STOREFRONT_PAGE_PATHS` | The page types and their public paths: build a link, recognise a page |
|
|
@@ -314,6 +316,20 @@ Browser code reaches the platform only through same-origin routes you own
|
|
|
314
316
|
browser-safe by construction: it exports no client factory and no function that
|
|
315
317
|
carries the key, which the package's own contract test asserts.
|
|
316
318
|
|
|
319
|
+
**Validation runs where data enters, on the server.** The SDK checks a
|
|
320
|
+
shopper's request at its routes and the platform's answer in its client, with
|
|
321
|
+
the schemas this package exports. What its routes and server islands hand the
|
|
322
|
+
browser has passed those checks, so browser code reads it by its type and never
|
|
323
|
+
imports a schema: a schema in a browser script ships Zod — about 27 KB
|
|
324
|
+
compressed — to every shopper's page. The browser-facing values (the basket,
|
|
325
|
+
the variant picker, the OTP machine, tracking, routes, phones, wilayas) are
|
|
326
|
+
free of schemas, and a test bundles them the way a theme's build does to keep
|
|
327
|
+
them so. A page can be cached apart from its scripts, so where the browser
|
|
328
|
+
reads a value another version may have shaped, it uses a reader —
|
|
329
|
+
`readShippingRates`, `readCouponOutcome` — that accepts exactly what the
|
|
330
|
+
schema accepts, held to it by a property test, and Zod stays on the server. A theme with an endpoint of its own still validates it — on the
|
|
331
|
+
server, where the request arrives.
|
|
332
|
+
|
|
317
333
|
### The namespaces
|
|
318
334
|
|
|
319
335
|
| Namespace | Use it for | Important result shapes |
|
|
@@ -964,6 +980,10 @@ const communes = await store.geo.communes(wilayaId); // server rows, cached
|
|
|
964
980
|
// zero-network rows for the checkout's locality field:
|
|
965
981
|
import { communesForWilaya } from "@wocart/storefront";
|
|
966
982
|
const rows = communesForWilaya(wilayaId); // Commune[] — [] if unknown
|
|
983
|
+
|
|
984
|
+
// a selected wilaya, checked in the browser before its communes are asked for:
|
|
985
|
+
import { isWilayaId } from "@wocart/storefront";
|
|
986
|
+
if (isWilayaId(Number(select.value))) loadCommunes(Number(select.value));
|
|
967
987
|
~~~
|
|
968
988
|
|
|
969
989
|
Wilayas are local and do not use the network. `geo.communes()` reads the
|
|
@@ -1182,12 +1202,12 @@ The SDK exports `createOtpMachine` to manage the complete interactive WhatsApp
|
|
|
1182
1202
|
verification lifecycle without managing timers and attempts manually. It talks
|
|
1183
1203
|
to your own same-origin OTP routes through a transport; `mapOtpResponse` turns
|
|
1184
1204
|
each route's response into the SDK's typed result, and `otpTransportFailure`
|
|
1185
|
-
covers a request that never completed
|
|
1205
|
+
covers a request that never completed. The routes are the SDK's own
|
|
1206
|
+
(`createStorefrontRoutes`), whose client checked the answer on the server, so
|
|
1207
|
+
`mapOtpResponse` takes no schema here; pass one only for an endpoint of your own:
|
|
1186
1208
|
|
|
1187
1209
|
~~~ts
|
|
1188
1210
|
import {
|
|
1189
|
-
OtpSendResultSchema,
|
|
1190
|
-
OtpVerifyResultSchema,
|
|
1191
1211
|
STOREFRONT_OPERATIONS,
|
|
1192
1212
|
createOtpMachine,
|
|
1193
1213
|
createOtpTransport,
|
|
@@ -1195,14 +1215,14 @@ import {
|
|
|
1195
1215
|
otpTransportFailure,
|
|
1196
1216
|
} from "@wocart/storefront";
|
|
1197
1217
|
|
|
1198
|
-
async function post(path, body,
|
|
1218
|
+
async function post(path, body, operation) {
|
|
1199
1219
|
try {
|
|
1200
1220
|
const response = await fetch(path, {
|
|
1201
1221
|
method: "POST",
|
|
1202
1222
|
headers: { "Content-Type": "application/json" },
|
|
1203
1223
|
body: JSON.stringify(body),
|
|
1204
1224
|
});
|
|
1205
|
-
return await mapOtpResponse(response, {
|
|
1225
|
+
return await mapOtpResponse(response, { operation, fallbackError: "Please try again" });
|
|
1206
1226
|
} catch (error) {
|
|
1207
1227
|
return otpTransportFailure(error, operation, "Please try again");
|
|
1208
1228
|
}
|
|
@@ -1211,9 +1231,9 @@ async function post(path, body, schema, operation) {
|
|
|
1211
1231
|
const otp = createOtpMachine({
|
|
1212
1232
|
transport: createOtpTransport({
|
|
1213
1233
|
send: (phone) =>
|
|
1214
|
-
post("/api/otp/send", { phone },
|
|
1234
|
+
post("/api/otp/send", { phone }, STOREFRONT_OPERATIONS.ordersOtpSend),
|
|
1215
1235
|
verify: (phone, requestId, code) =>
|
|
1216
|
-
post("/api/otp/verify", { phone, requestId, code },
|
|
1236
|
+
post("/api/otp/verify", { phone, requestId, code }, STOREFRONT_OPERATIONS.ordersOtpVerify),
|
|
1217
1237
|
}),
|
|
1218
1238
|
initialPhone: "0551234567",
|
|
1219
1239
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readers for what this package's server parts hand the browser: the delivery
|
|
3
|
+
* rates a page carries and the coupon outcome of a cart quote.
|
|
4
|
+
*
|
|
5
|
+
* The server built both with `ShippingRatesSchema` / `CouponOutcomeSchema`, so
|
|
6
|
+
* the browser need not run those schemas. A page can still be cached apart
|
|
7
|
+
* from its scripts, so a value of another version's shape is possible; these
|
|
8
|
+
* accept exactly what the schemas accept and return what they would — extra
|
|
9
|
+
* keys left out — without bringing Zod into a shopper's page. A property test
|
|
10
|
+
* holds each reader to its schema.
|
|
11
|
+
*/
|
|
12
|
+
import type { CouponOutcome, ShippingRates } from "./types.js";
|
|
13
|
+
/** The delivery rates of a value (`ShippingRates`), or null when it is not a rate table. */
|
|
14
|
+
export declare function readShippingRates(value: unknown): ShippingRates | null;
|
|
15
|
+
/** What a coupon did in a quote (`CouponOutcome`), or null when the value is not one. */
|
|
16
|
+
export declare function readCouponOutcome(value: unknown): CouponOutcome | null;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2
|
+
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
3
|
+
/** The delivery rates of a value (`ShippingRates`), or null when it is not a rate table. */
|
|
4
|
+
export function readShippingRates(value) {
|
|
5
|
+
if (!isObject(value))
|
|
6
|
+
return null;
|
|
7
|
+
const rates = {};
|
|
8
|
+
for (const [wilaya, entry] of Object.entries(value)) {
|
|
9
|
+
if (wilaya === "__proto__")
|
|
10
|
+
continue;
|
|
11
|
+
if (!isObject(entry) || !isFiniteNumber(entry.home) || !isFiniteNumber(entry.stopDesk))
|
|
12
|
+
return null;
|
|
13
|
+
rates[wilaya] = { home: entry.home, stopDesk: entry.stopDesk };
|
|
14
|
+
}
|
|
15
|
+
return rates;
|
|
16
|
+
}
|
|
17
|
+
/** What a coupon did in a quote (`CouponOutcome`), or null when the value is not one. */
|
|
18
|
+
export function readCouponOutcome(value) {
|
|
19
|
+
if (!isObject(value) || typeof value.code !== "string")
|
|
20
|
+
return null;
|
|
21
|
+
const { code } = value;
|
|
22
|
+
if (value.applied === true) {
|
|
23
|
+
if (!isFiniteNumber(value.discount) || typeof value.freeShipping !== "boolean")
|
|
24
|
+
return null;
|
|
25
|
+
return { code, applied: true, discount: value.discount, freeShipping: value.freeShipping };
|
|
26
|
+
}
|
|
27
|
+
if (value.applied !== false || typeof value.reason !== "string")
|
|
28
|
+
return null;
|
|
29
|
+
const { reason, shortBy, startsAt } = value;
|
|
30
|
+
if (shortBy !== undefined && !isFiniteNumber(shortBy))
|
|
31
|
+
return null;
|
|
32
|
+
if (startsAt !== undefined && typeof startsAt !== "string")
|
|
33
|
+
return null;
|
|
34
|
+
return {
|
|
35
|
+
code,
|
|
36
|
+
applied: false,
|
|
37
|
+
reason,
|
|
38
|
+
...(shortBy === undefined ? {} : { shortBy }),
|
|
39
|
+
...(startsAt === undefined ? {} : { startsAt }),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A basket line's identity and the server's limits for one basket: what the
|
|
3
|
+
* browser's basket needs to keep itself well-formed. Plain values and no
|
|
4
|
+
* schema — this module runs in every shopper's browser that holds a basket,
|
|
5
|
+
* and the schemas that check a basket on the server (`domain.ts`) read the
|
|
6
|
+
* same limits from here.
|
|
7
|
+
*/
|
|
8
|
+
/** The server's hard limits for one canonical browser basket. */
|
|
9
|
+
export declare const MAX_BASKET_LINES: 20;
|
|
10
|
+
export declare const MAX_BASKET_LINE_QUANTITY: 100;
|
|
11
|
+
/**
|
|
12
|
+
* Product + variant is the identity of a basket line. A null variant is a
|
|
13
|
+
* real identity, not the same thing as any selected variant.
|
|
14
|
+
*/
|
|
15
|
+
export declare function basketLineKey(productId: string, variantId: string | null): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A basket line's identity and the server's limits for one basket: what the
|
|
3
|
+
* browser's basket needs to keep itself well-formed. Plain values and no
|
|
4
|
+
* schema — this module runs in every shopper's browser that holds a basket,
|
|
5
|
+
* and the schemas that check a basket on the server (`domain.ts`) read the
|
|
6
|
+
* same limits from here.
|
|
7
|
+
*/
|
|
8
|
+
/** The server's hard limits for one canonical browser basket. */
|
|
9
|
+
export const MAX_BASKET_LINES = 20;
|
|
10
|
+
export const MAX_BASKET_LINE_QUANTITY = 100;
|
|
11
|
+
/**
|
|
12
|
+
* Product + variant is the identity of a basket line. A null variant is a
|
|
13
|
+
* real identity, not the same thing as any selected variant.
|
|
14
|
+
*/
|
|
15
|
+
export function basketLineKey(productId, variantId) {
|
|
16
|
+
return `${productId}\u0000${variantId ?? ""}`;
|
|
17
|
+
}
|
package/dist/basket.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (or memory fake in server/tests). The server always validates and re-prices
|
|
10
10
|
* every line at checkout.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import type { BasketLine, BasketLineInput } from "./domain.js";
|
|
13
13
|
/** Default key used in browser storage, versioned to prevent schema drift crashes. */
|
|
14
14
|
export declare const DEFAULT_BASKET_STORAGE_KEY = "cod_cart_v1";
|
|
15
15
|
/**
|
package/dist/basket.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (or memory fake in server/tests). The server always validates and re-prices
|
|
10
10
|
* every line at checkout.
|
|
11
11
|
*/
|
|
12
|
-
import { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey
|
|
12
|
+
import { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey } from "./basket-line.js";
|
|
13
13
|
/** Default key used in browser storage, versioned to prevent schema drift crashes. */
|
|
14
14
|
export const DEFAULT_BASKET_STORAGE_KEY = "cod_cart_v1";
|
|
15
15
|
/**
|
|
@@ -107,6 +107,7 @@ function sanitizeLine(raw, maxQuantity) {
|
|
|
107
107
|
unitPrice: Number.isFinite(unitPrice) && unitPrice >= 0 ? unitPrice : 0,
|
|
108
108
|
href: typeof line.href === "string" ? line.href : undefined,
|
|
109
109
|
image: typeof line.image === "string" ? line.image : undefined,
|
|
110
|
+
offerId: typeof line.offerId === "string" && line.offerId ? line.offerId : undefined,
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
function normalizeLines(lines, maxLines, maxQuantity) {
|
|
@@ -119,6 +120,8 @@ function normalizeLines(lines, maxLines, maxQuantity) {
|
|
|
119
120
|
existing.variantLabel ??= line.variantLabel;
|
|
120
121
|
existing.image ??= line.image;
|
|
121
122
|
existing.href ??= line.href;
|
|
123
|
+
// The newest explicit choice of offer wins; a plain add keeps the one chosen.
|
|
124
|
+
existing.offerId = line.offerId ?? existing.offerId;
|
|
122
125
|
}
|
|
123
126
|
else {
|
|
124
127
|
merged.set(key, { ...line });
|
|
@@ -143,6 +146,7 @@ export function toOrderItems(lines) {
|
|
|
143
146
|
variantLabel: line.variantLabel ?? undefined,
|
|
144
147
|
quantity: line.quantity,
|
|
145
148
|
pricePerUnit: line.unitPrice,
|
|
149
|
+
...(line.offerId !== undefined ? { offerId: line.offerId } : {}),
|
|
146
150
|
}));
|
|
147
151
|
}
|
|
148
152
|
/**
|
package/dist/client/cart.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* keeps its optimistic view and checkout re-validates everything anyway.
|
|
13
13
|
*/
|
|
14
14
|
import { CartValidationSchema } from "../schemas.js";
|
|
15
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
15
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
16
16
|
import { localizedPath } from "./paths.js";
|
|
17
17
|
/**
|
|
18
18
|
* Create cart namespace
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { CategorySchema } from "../schemas.js";
|
|
15
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
15
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
16
16
|
import { localizedPath } from "./paths.js";
|
|
17
17
|
import { reportDegradedRead } from "./core.js";
|
|
18
18
|
/**
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* failed read — the page answers a real 404.
|
|
11
11
|
*/
|
|
12
12
|
import { StoreCollectionSchema } from "../schemas.js";
|
|
13
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
13
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
14
14
|
import { localizedPath } from "./paths.js";
|
|
15
15
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
16
16
|
export function createCollectionsNamespace(core) {
|
package/dist/client/config.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* outage recovers on the next request.
|
|
16
16
|
*/
|
|
17
17
|
import { StoreConfigSchema } from "../schemas.js";
|
|
18
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
18
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
19
19
|
import { localizedPath } from "./paths.js";
|
|
20
20
|
import { reportDegradedRead } from "./core.js";
|
|
21
21
|
/**
|
package/dist/client/geo.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
import { CommuneSchema } from "../schemas.js";
|
|
17
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
17
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
18
18
|
import { reportDegradedRead } from "./core.js";
|
|
19
19
|
import { WILAYAS } from "../data/wilayas.js";
|
|
20
20
|
/**
|
package/dist/client/menus.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* read — the theme renders its own fallback navigation.
|
|
12
12
|
*/
|
|
13
13
|
import { StoreMenuSchema } from "../schemas.js";
|
|
14
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
14
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
15
15
|
import { localizedPath } from "./paths.js";
|
|
16
16
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
17
17
|
export function createMenusNamespace(core) {
|
package/dist/client/orders.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* is recoverable; reporting it to the wrong ad account is not.
|
|
13
13
|
*/
|
|
14
14
|
import { CreatedOrderSchema, IdResponseSchema, OrderTrackingSchema, OtpSendResultSchema, OtpVerifyResultSchema, } from "../schemas.js";
|
|
15
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
15
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
16
16
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
17
17
|
/**
|
|
18
18
|
* Create orders namespace
|
package/dist/client/page-data.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* the next render, subject only to the theme's route cache).
|
|
18
18
|
*/
|
|
19
19
|
import { PageDocumentPayloadSchema } from "../schemas.js";
|
|
20
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
20
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
21
21
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
22
22
|
/**
|
|
23
23
|
* Create the page-data namespace.
|
package/dist/client/pages.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* redirects to the store home instead of a dead end.
|
|
13
13
|
*/
|
|
14
14
|
import { LandingPageSchema, StorePagePublicSchema } from "../schemas.js";
|
|
15
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
15
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
16
16
|
import { localizedPath } from "./paths.js";
|
|
17
17
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
18
18
|
/**
|
package/dist/client/products.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { ProductListSchema, ProductSchema, ReviewEligibilitySchema, ReviewListEnvelopeSchema, ReviewSubmitResultSchema, ReviewUploadSchema } from "../schemas.js";
|
|
16
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
16
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
17
17
|
import { localizedPath } from "./paths.js";
|
|
18
18
|
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
19
19
|
/** Page size when a caller names none — the storefront's default grid. */
|
package/dist/client/shipping.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* treated as unsupported.
|
|
14
14
|
*/
|
|
15
15
|
import { ShippingRatesSchema } from "../schemas.js";
|
|
16
|
-
import { STOREFRONT_OPERATIONS } from "../
|
|
16
|
+
import { STOREFRONT_OPERATIONS } from "../operations.js";
|
|
17
17
|
import { reportDegradedRead } from "./core.js";
|
|
18
18
|
/**
|
|
19
19
|
* Create shipping namespace
|
package/dist/domain.d.ts
CHANGED
|
@@ -46,9 +46,6 @@ export interface StoreScope {
|
|
|
46
46
|
*/
|
|
47
47
|
readonly readTimeoutMs?: number;
|
|
48
48
|
}
|
|
49
|
-
/** The server's hard limits for one canonical browser basket. */
|
|
50
|
-
export declare const MAX_BASKET_LINES: 20;
|
|
51
|
-
export declare const MAX_BASKET_LINE_QUANTITY: 100;
|
|
52
49
|
/**
|
|
53
50
|
* A wilaya id, bounded by the reference dataset itself rather than a literal.
|
|
54
51
|
*
|
|
@@ -57,11 +54,6 @@ export declare const MAX_BASKET_LINE_QUANTITY: 100;
|
|
|
57
54
|
* range in step.
|
|
58
55
|
*/
|
|
59
56
|
export declare const WilayaIdSchema: z.ZodNumber;
|
|
60
|
-
/**
|
|
61
|
-
* Product + variant is the identity of a basket line. A null variant is a
|
|
62
|
-
* real identity, not the same thing as any selected variant.
|
|
63
|
-
*/
|
|
64
|
-
export declare function basketLineKey(productId: string, variantId: string | null): string;
|
|
65
57
|
/**
|
|
66
58
|
* A browser-owned basket line. `unitPrice` is an optimistic display value;
|
|
67
59
|
* the server re-prices every line during validation and order creation.
|
|
@@ -75,6 +67,7 @@ export declare const BasketLineSchema: z.ZodObject<{
|
|
|
75
67
|
unitPrice: z.ZodNumber;
|
|
76
68
|
href: z.ZodOptional<z.ZodString>;
|
|
77
69
|
image: z.ZodOptional<z.ZodString>;
|
|
70
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
78
71
|
}, z.core.$strip>;
|
|
79
72
|
export type BasketLine = z.infer<typeof BasketLineSchema>;
|
|
80
73
|
/**
|
|
@@ -106,6 +99,7 @@ export declare const BasketSchema: z.ZodObject<{
|
|
|
106
99
|
unitPrice: z.ZodNumber;
|
|
107
100
|
href: z.ZodOptional<z.ZodString>;
|
|
108
101
|
image: z.ZodOptional<z.ZodString>;
|
|
102
|
+
offerId: z.ZodOptional<z.ZodString>;
|
|
109
103
|
}, z.core.$strip>>;
|
|
110
104
|
}, z.core.$strip>;
|
|
111
105
|
export type Basket = z.infer<typeof BasketSchema>;
|
|
@@ -345,65 +339,3 @@ export type ClientResult<T> = {
|
|
|
345
339
|
readonly domainError: DomainError;
|
|
346
340
|
readonly [key: string]: unknown;
|
|
347
341
|
};
|
|
348
|
-
export type RetryPolicy = "safe-read" | "never";
|
|
349
|
-
export declare const STOREFRONT_OPERATIONS: {
|
|
350
|
-
readonly configGet: "config.get";
|
|
351
|
-
readonly categoriesList: "categories.list";
|
|
352
|
-
readonly productsGet: "products.get";
|
|
353
|
-
readonly productsList: "products.list";
|
|
354
|
-
readonly productsReviewsList: "products.reviews.list";
|
|
355
|
-
readonly productsReviewsSubmit: "products.reviews.submit";
|
|
356
|
-
readonly productsReviewsEligibility: "products.reviews.eligibility";
|
|
357
|
-
readonly productsReviewsUpload: "products.reviews.upload";
|
|
358
|
-
readonly pagesGet: "pages.get";
|
|
359
|
-
readonly pagesLanding: "pages.landing";
|
|
360
|
-
readonly menusGet: "menus.get";
|
|
361
|
-
readonly collectionsGet: "collections.get";
|
|
362
|
-
readonly shippingRates: "shipping.rates";
|
|
363
|
-
readonly shippingRate: "shipping.rate";
|
|
364
|
-
readonly geoCommunes: "geo.communes";
|
|
365
|
-
readonly cartValidate: "cart.validate";
|
|
366
|
-
readonly pageDataGet: "pageData.get";
|
|
367
|
-
readonly ordersCreate: "orders.create";
|
|
368
|
-
readonly ordersTracking: "orders.tracking";
|
|
369
|
-
readonly ordersAbandonedUpsert: "orders.abandoned.upsert";
|
|
370
|
-
readonly ordersAbandonedConvert: "orders.abandoned.convert";
|
|
371
|
-
readonly ordersOtpSend: "orders.otp.send";
|
|
372
|
-
readonly ordersOtpVerify: "orders.otp.verify";
|
|
373
|
-
};
|
|
374
|
-
export type StorefrontOperation = (typeof STOREFRONT_OPERATIONS)[keyof typeof STOREFRONT_OPERATIONS];
|
|
375
|
-
/**
|
|
376
|
-
* The retry policy per operation, as a contract a caller can read.
|
|
377
|
-
*
|
|
378
|
-
* The transport enforces it by kind: every `safe-read` goes through the
|
|
379
|
-
* caching/deduplicating GET path, which repeats a transport or 5xx failure,
|
|
380
|
-
* while every `never` goes through the POST/PATCH path, which returns a result
|
|
381
|
-
* and never retries (a replayed write is how a shopper ends up with two
|
|
382
|
-
* orders). A test asserts this table covers every public operation and agrees
|
|
383
|
-
* with that classification, so a new operation cannot ship unclassified.
|
|
384
|
-
*/
|
|
385
|
-
export declare const STOREFRONT_RETRY_POLICIES: {
|
|
386
|
-
readonly "cart.validate": "safe-read";
|
|
387
|
-
readonly "categories.list": "safe-read";
|
|
388
|
-
readonly "config.get": "safe-read";
|
|
389
|
-
readonly "geo.communes": "safe-read";
|
|
390
|
-
readonly "orders.abandoned.convert": "never";
|
|
391
|
-
readonly "orders.abandoned.upsert": "never";
|
|
392
|
-
readonly "orders.create": "never";
|
|
393
|
-
readonly "orders.otp.send": "never";
|
|
394
|
-
readonly "orders.otp.verify": "never";
|
|
395
|
-
readonly "orders.tracking": "safe-read";
|
|
396
|
-
readonly "pageData.get": "safe-read";
|
|
397
|
-
readonly "pages.get": "safe-read";
|
|
398
|
-
readonly "pages.landing": "safe-read";
|
|
399
|
-
readonly "menus.get": "safe-read";
|
|
400
|
-
readonly "collections.get": "safe-read";
|
|
401
|
-
readonly "products.get": "safe-read";
|
|
402
|
-
readonly "products.list": "safe-read";
|
|
403
|
-
readonly "products.reviews.list": "safe-read";
|
|
404
|
-
readonly "products.reviews.submit": "never";
|
|
405
|
-
readonly "products.reviews.eligibility": "never";
|
|
406
|
-
readonly "products.reviews.upload": "never";
|
|
407
|
-
readonly "shipping.rate": "safe-read";
|
|
408
|
-
readonly "shipping.rates": "safe-read";
|
|
409
|
-
};
|
package/dist/domain.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { WILAYAS } from "./data/wilayas.js";
|
|
3
|
-
|
|
4
|
-
export const MAX_BASKET_LINES = 20;
|
|
5
|
-
export const MAX_BASKET_LINE_QUANTITY = 100;
|
|
3
|
+
import { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey } from "./basket-line.js";
|
|
6
4
|
/**
|
|
7
5
|
* A wilaya id, bounded by the reference dataset itself rather than a literal.
|
|
8
6
|
*
|
|
@@ -11,13 +9,6 @@ export const MAX_BASKET_LINE_QUANTITY = 100;
|
|
|
11
9
|
* range in step.
|
|
12
10
|
*/
|
|
13
11
|
export const WilayaIdSchema = z.number().int().min(1).max(WILAYAS.length);
|
|
14
|
-
/**
|
|
15
|
-
* Product + variant is the identity of a basket line. A null variant is a
|
|
16
|
-
* real identity, not the same thing as any selected variant.
|
|
17
|
-
*/
|
|
18
|
-
export function basketLineKey(productId, variantId) {
|
|
19
|
-
return `${productId}\u0000${variantId ?? ""}`;
|
|
20
|
-
}
|
|
21
12
|
/**
|
|
22
13
|
* A browser-owned basket line. `unitPrice` is an optimistic display value;
|
|
23
14
|
* the server re-prices every line during validation and order creation.
|
|
@@ -31,6 +22,11 @@ export const BasketLineSchema = z.object({
|
|
|
31
22
|
unitPrice: z.number().finite().nonnegative(),
|
|
32
23
|
href: z.string().min(1).optional(),
|
|
33
24
|
image: z.string().min(1).optional(),
|
|
25
|
+
/**
|
|
26
|
+
* The offer the shopper chose for this line, when they picked a tier. The
|
|
27
|
+
* server still applies it only if it holds for the quantity.
|
|
28
|
+
*/
|
|
29
|
+
offerId: z.string().min(1).max(200).optional(),
|
|
34
30
|
});
|
|
35
31
|
/**
|
|
36
32
|
* The server-facing projection of a basket line. UI-only fields are omitted,
|
|
@@ -196,63 +192,3 @@ export const TrackingDecisionSchema = z.object({
|
|
|
196
192
|
pixelId: z.string().nullable(),
|
|
197
193
|
event: z.enum(["Purchase", "Lead"]).nullable(),
|
|
198
194
|
});
|
|
199
|
-
export const STOREFRONT_OPERATIONS = {
|
|
200
|
-
configGet: "config.get",
|
|
201
|
-
categoriesList: "categories.list",
|
|
202
|
-
productsGet: "products.get",
|
|
203
|
-
productsList: "products.list",
|
|
204
|
-
productsReviewsList: "products.reviews.list",
|
|
205
|
-
productsReviewsSubmit: "products.reviews.submit",
|
|
206
|
-
productsReviewsEligibility: "products.reviews.eligibility",
|
|
207
|
-
productsReviewsUpload: "products.reviews.upload",
|
|
208
|
-
pagesGet: "pages.get",
|
|
209
|
-
pagesLanding: "pages.landing",
|
|
210
|
-
menusGet: "menus.get",
|
|
211
|
-
collectionsGet: "collections.get",
|
|
212
|
-
shippingRates: "shipping.rates",
|
|
213
|
-
shippingRate: "shipping.rate",
|
|
214
|
-
geoCommunes: "geo.communes",
|
|
215
|
-
cartValidate: "cart.validate",
|
|
216
|
-
pageDataGet: "pageData.get",
|
|
217
|
-
ordersCreate: "orders.create",
|
|
218
|
-
ordersTracking: "orders.tracking",
|
|
219
|
-
ordersAbandonedUpsert: "orders.abandoned.upsert",
|
|
220
|
-
ordersAbandonedConvert: "orders.abandoned.convert",
|
|
221
|
-
ordersOtpSend: "orders.otp.send",
|
|
222
|
-
ordersOtpVerify: "orders.otp.verify",
|
|
223
|
-
};
|
|
224
|
-
/**
|
|
225
|
-
* The retry policy per operation, as a contract a caller can read.
|
|
226
|
-
*
|
|
227
|
-
* The transport enforces it by kind: every `safe-read` goes through the
|
|
228
|
-
* caching/deduplicating GET path, which repeats a transport or 5xx failure,
|
|
229
|
-
* while every `never` goes through the POST/PATCH path, which returns a result
|
|
230
|
-
* and never retries (a replayed write is how a shopper ends up with two
|
|
231
|
-
* orders). A test asserts this table covers every public operation and agrees
|
|
232
|
-
* with that classification, so a new operation cannot ship unclassified.
|
|
233
|
-
*/
|
|
234
|
-
export const STOREFRONT_RETRY_POLICIES = {
|
|
235
|
-
[STOREFRONT_OPERATIONS.cartValidate]: "safe-read",
|
|
236
|
-
[STOREFRONT_OPERATIONS.categoriesList]: "safe-read",
|
|
237
|
-
[STOREFRONT_OPERATIONS.configGet]: "safe-read",
|
|
238
|
-
[STOREFRONT_OPERATIONS.geoCommunes]: "safe-read",
|
|
239
|
-
[STOREFRONT_OPERATIONS.ordersAbandonedConvert]: "never",
|
|
240
|
-
[STOREFRONT_OPERATIONS.ordersAbandonedUpsert]: "never",
|
|
241
|
-
[STOREFRONT_OPERATIONS.ordersCreate]: "never",
|
|
242
|
-
[STOREFRONT_OPERATIONS.ordersOtpSend]: "never",
|
|
243
|
-
[STOREFRONT_OPERATIONS.ordersOtpVerify]: "never",
|
|
244
|
-
[STOREFRONT_OPERATIONS.ordersTracking]: "safe-read",
|
|
245
|
-
[STOREFRONT_OPERATIONS.pageDataGet]: "safe-read",
|
|
246
|
-
[STOREFRONT_OPERATIONS.pagesGet]: "safe-read",
|
|
247
|
-
[STOREFRONT_OPERATIONS.pagesLanding]: "safe-read",
|
|
248
|
-
[STOREFRONT_OPERATIONS.menusGet]: "safe-read",
|
|
249
|
-
[STOREFRONT_OPERATIONS.collectionsGet]: "safe-read",
|
|
250
|
-
[STOREFRONT_OPERATIONS.productsGet]: "safe-read",
|
|
251
|
-
[STOREFRONT_OPERATIONS.productsList]: "safe-read",
|
|
252
|
-
[STOREFRONT_OPERATIONS.productsReviewsList]: "safe-read",
|
|
253
|
-
[STOREFRONT_OPERATIONS.productsReviewsSubmit]: "never",
|
|
254
|
-
[STOREFRONT_OPERATIONS.productsReviewsEligibility]: "never",
|
|
255
|
-
[STOREFRONT_OPERATIONS.productsReviewsUpload]: "never",
|
|
256
|
-
[STOREFRONT_OPERATIONS.shippingRate]: "safe-read",
|
|
257
|
-
[STOREFRONT_OPERATIONS.shippingRates]: "safe-read",
|
|
258
|
-
};
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
* (see `createProxyEndpoint`), and the server's diagnostic fields ride along.
|
|
23
23
|
*/
|
|
24
24
|
import { z } from "zod";
|
|
25
|
-
import { AbandonedCheckoutInputSchema, BasketLineInputSchema, DeliveryDestinationSchema, ProductQuoteInputSchema,
|
|
25
|
+
import { AbandonedCheckoutInputSchema, BasketLineInputSchema, DeliveryDestinationSchema, ProductQuoteInputSchema, WilayaIdSchema, } from "../domain.js";
|
|
26
|
+
import { MAX_BASKET_LINES } from "../basket-line.js";
|
|
26
27
|
import { createProxyEndpoint, } from "./proxy.js";
|
|
27
28
|
export const REVIEWS_PAGE_DEFAULT = 20;
|
|
28
29
|
export const REVIEWS_PAGE_MAX = 50;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,16 +23,20 @@ export { alternateLinks, localeRouting, localizeHref, resolveLocalePath } from "
|
|
|
23
23
|
export type { LocalePath, LocaleRouting } from "./locale-routing.js";
|
|
24
24
|
export type { StorefrontPageMatch, StorefrontPageType, StorefrontPathParams, StorefrontRoute } from "./routes.js";
|
|
25
25
|
export { createBasketStore, createMemoryStorageAdapter, createLocalStorageAdapter, DEFAULT_BASKET_STORAGE_KEY, toOrderItems, } from "./basket.js";
|
|
26
|
+
export { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey } from "./basket-line.js";
|
|
26
27
|
export type { BasketStore, BasketStorageAdapter, BasketStoreOptions, BasketListener, } from "./basket.js";
|
|
27
28
|
export { createOtpMachine, createOtpTransport, mapOtpResponse, otpTransportFailure, } from "./otp.js";
|
|
28
29
|
export type { OtpMachine, OtpMachineOptions, OtpFailureContext, OtpResponseOptions, OtpState, OtpStatus, OtpTransport, OtpTransportResult, OtpStateListener, } from "./otp.js";
|
|
29
30
|
export { WILAYAS } from "./data/wilayas.js";
|
|
30
31
|
export { ALGERIA_COMMUNES, communesForWilaya } from "./data/communes.js";
|
|
32
|
+
export { isWilayaId } from "./wilaya-id.js";
|
|
33
|
+
export { readCouponOutcome, readShippingRates } from "./answer-readers.js";
|
|
31
34
|
export type { CommuneTuple } from "./data/communes.js";
|
|
32
35
|
export { normalizeAlgerianPhone, toLocalAlgerianMobile } from "./phone.js";
|
|
33
36
|
export * from "./types.js";
|
|
34
37
|
export * from "./schemas.js";
|
|
35
38
|
export * from "./domain.js";
|
|
39
|
+
export * from "./operations.js";
|
|
36
40
|
export * from "./product-view.js";
|
|
37
41
|
export { getOfferTierView, getQuantityTierViews } from "./offer-view.js";
|
|
38
42
|
export { COUPON_NOT_APPLICABLE, COUPON_URL_PARAM, couponFromUrl, couponRefusalOf } from "./coupon.js";
|
package/dist/index.js
CHANGED
|
@@ -17,13 +17,17 @@ export { STOREFRONT_PAGE_PATHS, STOREFRONT_ROUTES, matchStorefrontPath, showsWid
|
|
|
17
17
|
export { servedLanguage } from "./locales.js";
|
|
18
18
|
export { alternateLinks, localeRouting, localizeHref, resolveLocalePath } from "./locale-routing.js";
|
|
19
19
|
export { createBasketStore, createMemoryStorageAdapter, createLocalStorageAdapter, DEFAULT_BASKET_STORAGE_KEY, toOrderItems, } from "./basket.js";
|
|
20
|
+
export { MAX_BASKET_LINES, MAX_BASKET_LINE_QUANTITY, basketLineKey } from "./basket-line.js";
|
|
20
21
|
export { createOtpMachine, createOtpTransport, mapOtpResponse, otpTransportFailure, } from "./otp.js";
|
|
21
22
|
export { WILAYAS } from "./data/wilayas.js";
|
|
22
23
|
export { ALGERIA_COMMUNES, communesForWilaya } from "./data/communes.js";
|
|
24
|
+
export { isWilayaId } from "./wilaya-id.js";
|
|
25
|
+
export { readCouponOutcome, readShippingRates } from "./answer-readers.js";
|
|
23
26
|
export { normalizeAlgerianPhone, toLocalAlgerianMobile } from "./phone.js";
|
|
24
27
|
export * from "./types.js";
|
|
25
28
|
export * from "./schemas.js";
|
|
26
29
|
export * from "./domain.js";
|
|
30
|
+
export * from "./operations.js";
|
|
27
31
|
export * from "./product-view.js";
|
|
28
32
|
export { getOfferTierView, getQuantityTierViews } from "./offer-view.js";
|
|
29
33
|
export { COUPON_NOT_APPLICABLE, COUPON_URL_PARAM, couponFromUrl, couponRefusalOf } from "./coupon.js";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform operations a storefront calls, and the retry policy each one
|
|
3
|
+
* keeps. Plain values and no schema: browser code reads the operation names
|
|
4
|
+
* too — an OTP route's failure names the operation it was — so this module
|
|
5
|
+
* stays free of anything that would bring a validator into a shopper's page.
|
|
6
|
+
*/
|
|
7
|
+
export type RetryPolicy = "safe-read" | "never";
|
|
8
|
+
export declare const STOREFRONT_OPERATIONS: {
|
|
9
|
+
readonly configGet: "config.get";
|
|
10
|
+
readonly categoriesList: "categories.list";
|
|
11
|
+
readonly productsGet: "products.get";
|
|
12
|
+
readonly productsList: "products.list";
|
|
13
|
+
readonly productsReviewsList: "products.reviews.list";
|
|
14
|
+
readonly productsReviewsSubmit: "products.reviews.submit";
|
|
15
|
+
readonly productsReviewsEligibility: "products.reviews.eligibility";
|
|
16
|
+
readonly productsReviewsUpload: "products.reviews.upload";
|
|
17
|
+
readonly pagesGet: "pages.get";
|
|
18
|
+
readonly pagesLanding: "pages.landing";
|
|
19
|
+
readonly menusGet: "menus.get";
|
|
20
|
+
readonly collectionsGet: "collections.get";
|
|
21
|
+
readonly shippingRates: "shipping.rates";
|
|
22
|
+
readonly shippingRate: "shipping.rate";
|
|
23
|
+
readonly geoCommunes: "geo.communes";
|
|
24
|
+
readonly cartValidate: "cart.validate";
|
|
25
|
+
readonly pageDataGet: "pageData.get";
|
|
26
|
+
readonly ordersCreate: "orders.create";
|
|
27
|
+
readonly ordersTracking: "orders.tracking";
|
|
28
|
+
readonly ordersAbandonedUpsert: "orders.abandoned.upsert";
|
|
29
|
+
readonly ordersAbandonedConvert: "orders.abandoned.convert";
|
|
30
|
+
readonly ordersOtpSend: "orders.otp.send";
|
|
31
|
+
readonly ordersOtpVerify: "orders.otp.verify";
|
|
32
|
+
};
|
|
33
|
+
export type StorefrontOperation = (typeof STOREFRONT_OPERATIONS)[keyof typeof STOREFRONT_OPERATIONS];
|
|
34
|
+
/**
|
|
35
|
+
* The retry policy per operation, as a contract a caller can read.
|
|
36
|
+
*
|
|
37
|
+
* The transport enforces it by kind: every `safe-read` goes through the
|
|
38
|
+
* caching/deduplicating GET path, which repeats a transport or 5xx failure,
|
|
39
|
+
* while every `never` goes through the POST/PATCH path, which returns a result
|
|
40
|
+
* and never retries (a replayed write is how a shopper ends up with two
|
|
41
|
+
* orders). A test asserts this table covers every public operation and agrees
|
|
42
|
+
* with that classification, so a new operation cannot ship unclassified.
|
|
43
|
+
*/
|
|
44
|
+
export declare const STOREFRONT_RETRY_POLICIES: {
|
|
45
|
+
readonly "cart.validate": "safe-read";
|
|
46
|
+
readonly "categories.list": "safe-read";
|
|
47
|
+
readonly "config.get": "safe-read";
|
|
48
|
+
readonly "geo.communes": "safe-read";
|
|
49
|
+
readonly "orders.abandoned.convert": "never";
|
|
50
|
+
readonly "orders.abandoned.upsert": "never";
|
|
51
|
+
readonly "orders.create": "never";
|
|
52
|
+
readonly "orders.otp.send": "never";
|
|
53
|
+
readonly "orders.otp.verify": "never";
|
|
54
|
+
readonly "orders.tracking": "safe-read";
|
|
55
|
+
readonly "pageData.get": "safe-read";
|
|
56
|
+
readonly "pages.get": "safe-read";
|
|
57
|
+
readonly "pages.landing": "safe-read";
|
|
58
|
+
readonly "menus.get": "safe-read";
|
|
59
|
+
readonly "collections.get": "safe-read";
|
|
60
|
+
readonly "products.get": "safe-read";
|
|
61
|
+
readonly "products.list": "safe-read";
|
|
62
|
+
readonly "products.reviews.list": "safe-read";
|
|
63
|
+
readonly "products.reviews.submit": "never";
|
|
64
|
+
readonly "products.reviews.eligibility": "never";
|
|
65
|
+
readonly "products.reviews.upload": "never";
|
|
66
|
+
readonly "shipping.rate": "safe-read";
|
|
67
|
+
readonly "shipping.rates": "safe-read";
|
|
68
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform operations a storefront calls, and the retry policy each one
|
|
3
|
+
* keeps. Plain values and no schema: browser code reads the operation names
|
|
4
|
+
* too — an OTP route's failure names the operation it was — so this module
|
|
5
|
+
* stays free of anything that would bring a validator into a shopper's page.
|
|
6
|
+
*/
|
|
7
|
+
export const STOREFRONT_OPERATIONS = {
|
|
8
|
+
configGet: "config.get",
|
|
9
|
+
categoriesList: "categories.list",
|
|
10
|
+
productsGet: "products.get",
|
|
11
|
+
productsList: "products.list",
|
|
12
|
+
productsReviewsList: "products.reviews.list",
|
|
13
|
+
productsReviewsSubmit: "products.reviews.submit",
|
|
14
|
+
productsReviewsEligibility: "products.reviews.eligibility",
|
|
15
|
+
productsReviewsUpload: "products.reviews.upload",
|
|
16
|
+
pagesGet: "pages.get",
|
|
17
|
+
pagesLanding: "pages.landing",
|
|
18
|
+
menusGet: "menus.get",
|
|
19
|
+
collectionsGet: "collections.get",
|
|
20
|
+
shippingRates: "shipping.rates",
|
|
21
|
+
shippingRate: "shipping.rate",
|
|
22
|
+
geoCommunes: "geo.communes",
|
|
23
|
+
cartValidate: "cart.validate",
|
|
24
|
+
pageDataGet: "pageData.get",
|
|
25
|
+
ordersCreate: "orders.create",
|
|
26
|
+
ordersTracking: "orders.tracking",
|
|
27
|
+
ordersAbandonedUpsert: "orders.abandoned.upsert",
|
|
28
|
+
ordersAbandonedConvert: "orders.abandoned.convert",
|
|
29
|
+
ordersOtpSend: "orders.otp.send",
|
|
30
|
+
ordersOtpVerify: "orders.otp.verify",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The retry policy per operation, as a contract a caller can read.
|
|
34
|
+
*
|
|
35
|
+
* The transport enforces it by kind: every `safe-read` goes through the
|
|
36
|
+
* caching/deduplicating GET path, which repeats a transport or 5xx failure,
|
|
37
|
+
* while every `never` goes through the POST/PATCH path, which returns a result
|
|
38
|
+
* and never retries (a replayed write is how a shopper ends up with two
|
|
39
|
+
* orders). A test asserts this table covers every public operation and agrees
|
|
40
|
+
* with that classification, so a new operation cannot ship unclassified.
|
|
41
|
+
*/
|
|
42
|
+
export const STOREFRONT_RETRY_POLICIES = {
|
|
43
|
+
[STOREFRONT_OPERATIONS.cartValidate]: "safe-read",
|
|
44
|
+
[STOREFRONT_OPERATIONS.categoriesList]: "safe-read",
|
|
45
|
+
[STOREFRONT_OPERATIONS.configGet]: "safe-read",
|
|
46
|
+
[STOREFRONT_OPERATIONS.geoCommunes]: "safe-read",
|
|
47
|
+
[STOREFRONT_OPERATIONS.ordersAbandonedConvert]: "never",
|
|
48
|
+
[STOREFRONT_OPERATIONS.ordersAbandonedUpsert]: "never",
|
|
49
|
+
[STOREFRONT_OPERATIONS.ordersCreate]: "never",
|
|
50
|
+
[STOREFRONT_OPERATIONS.ordersOtpSend]: "never",
|
|
51
|
+
[STOREFRONT_OPERATIONS.ordersOtpVerify]: "never",
|
|
52
|
+
[STOREFRONT_OPERATIONS.ordersTracking]: "safe-read",
|
|
53
|
+
[STOREFRONT_OPERATIONS.pageDataGet]: "safe-read",
|
|
54
|
+
[STOREFRONT_OPERATIONS.pagesGet]: "safe-read",
|
|
55
|
+
[STOREFRONT_OPERATIONS.pagesLanding]: "safe-read",
|
|
56
|
+
[STOREFRONT_OPERATIONS.menusGet]: "safe-read",
|
|
57
|
+
[STOREFRONT_OPERATIONS.collectionsGet]: "safe-read",
|
|
58
|
+
[STOREFRONT_OPERATIONS.productsGet]: "safe-read",
|
|
59
|
+
[STOREFRONT_OPERATIONS.productsList]: "safe-read",
|
|
60
|
+
[STOREFRONT_OPERATIONS.productsReviewsList]: "safe-read",
|
|
61
|
+
[STOREFRONT_OPERATIONS.productsReviewsSubmit]: "never",
|
|
62
|
+
[STOREFRONT_OPERATIONS.productsReviewsEligibility]: "never",
|
|
63
|
+
[STOREFRONT_OPERATIONS.productsReviewsUpload]: "never",
|
|
64
|
+
[STOREFRONT_OPERATIONS.shippingRate]: "safe-read",
|
|
65
|
+
[STOREFRONT_OPERATIONS.shippingRates]: "safe-read",
|
|
66
|
+
};
|
package/dist/otp.d.ts
CHANGED
|
@@ -46,7 +46,14 @@ export declare function createOtpTransport(options: {
|
|
|
46
46
|
verify: OtpTransport["verify"];
|
|
47
47
|
}): OtpTransport;
|
|
48
48
|
export interface OtpResponseOptions<T> {
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Checks the answer's `data`. Leave it out to read an answer of this
|
|
51
|
+
* package's own `otpSend` / `otpVerify` routes: their client already checked
|
|
52
|
+
* it on the server with `OtpSendResultSchema` / `OtpVerifyResultSchema`, and
|
|
53
|
+
* a schema here would only ship Zod to every shopper's browser. Pass one for
|
|
54
|
+
* an endpoint of your own.
|
|
55
|
+
*/
|
|
56
|
+
schema?: z.Schema<T>;
|
|
50
57
|
operation: string;
|
|
51
58
|
fallbackError: string;
|
|
52
59
|
}
|
package/dist/otp.js
CHANGED
|
@@ -77,6 +77,9 @@ export async function mapOtpResponse(response, options) {
|
|
|
77
77
|
if (!response.ok) {
|
|
78
78
|
return mapFailure(body, options, `HTTP_${response.status}`);
|
|
79
79
|
}
|
|
80
|
+
if (options.schema === undefined) {
|
|
81
|
+
return isRecord(body.data) ? { success: true, data: body.data } : mapFailure(body, options, "MALFORMED_RESPONSE");
|
|
82
|
+
}
|
|
80
83
|
const parsed = options.schema.safeParse(body.data);
|
|
81
84
|
if (!parsed.success) {
|
|
82
85
|
return mapFailure(body, options, "SCHEMA_VALIDATION_FAILED");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isWilayaId(value: unknown): value is number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a value is a wilaya id: an integer from 1 to the number of wilayas
|
|
3
|
+
* in the reference dataset. It is the range `WilayaIdSchema` checks on the
|
|
4
|
+
* server, read from the same `WILAYAS`, so the two cannot disagree — for the
|
|
5
|
+
* browser, which checks a selected wilaya before asking for its communes.
|
|
6
|
+
*/
|
|
7
|
+
import { WILAYAS } from "./data/wilayas.js";
|
|
8
|
+
export function isWilayaId(value) {
|
|
9
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= WILAYAS.length;
|
|
10
|
+
}
|
package/package.json
CHANGED