@wocart/storefront 1.6.0 → 1.8.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 +121 -34
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +3 -1
- package/dist/client/menus.d.ts +22 -0
- package/dist/client/menus.js +30 -0
- package/dist/client/products.d.ts +1 -1
- package/dist/client/products.js +2 -0
- package/dist/domain.d.ts +2 -0
- package/dist/domain.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/routes.d.ts +60 -28
- package/dist/routes.js +80 -51
- package/dist/schemas.d.ts +19 -0
- package/dist/schemas.js +22 -0
- package/dist/types.d.ts +63 -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.8.0 — 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Collections.** `products.list({ collection: "best-sellers" })` lists a
|
|
13
|
+
collection — a list of products the merchant curates by hand — in the
|
|
14
|
+
merchant's order; an unknown handle lists nothing. `products.list({ onSale:
|
|
15
|
+
true })` lists the products priced below their compare-at price. Both need
|
|
16
|
+
a server with collections.
|
|
17
|
+
|
|
18
|
+
### Deprecated
|
|
19
|
+
|
|
20
|
+
- `products.list({ featured: true })` and `ProductList.storeFeatured`: they
|
|
21
|
+
mean the `featured` collection; removed in 2.0.
|
|
22
|
+
- `StoreConfig.primaryColor`, `accentColor`, `bgColor`, `fontFamily` and
|
|
23
|
+
`fontUrl`. A store's colors and font are its theme's settings: a theme
|
|
24
|
+
declares them (`defineTheme({ settings })` in `@wocart/theme`) and reads
|
|
25
|
+
`themeSettings`. The fields still arrive — the values saved before, no
|
|
26
|
+
longer editable (`fontUrl` is always null) — and are removed in 2.0.
|
|
27
|
+
- `StoreConfig.announcementBar`: always null. An announcement is a theme's
|
|
28
|
+
own content, declared as a theme setting; removed in 2.0.
|
|
29
|
+
|
|
30
|
+
## 1.7.0 — 2026-10-17
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **Page types and their URLs.** `STOREFRONT_PAGE_PATHS` is the platform's
|
|
35
|
+
table of public paths (`home`, `catalog`, `category`, `product`, `page`,
|
|
36
|
+
`landing`, `checkout`, `thankYou`); `storefrontPath(type, params)` builds
|
|
37
|
+
one (encoded; a missing parameter throws) and `matchStorefrontPath(pathname)`
|
|
38
|
+
recognises one, exactly. Types `StorefrontPageType`, `StorefrontPathParams`,
|
|
39
|
+
`StorefrontPageMatch`.
|
|
40
|
+
- **Navigation menus.** `store.menus.get(handle)` reads a menu as a shopper
|
|
41
|
+
sees it: labels in the client's language, every `href` resolved by the
|
|
42
|
+
server, items leading nowhere public already left out; `null` for an
|
|
43
|
+
unknown handle, which is not reported to `onError`. Types `StoreMenu`,
|
|
44
|
+
`StoreMenuItem`, schemas `StoreMenuSchema`, `StoreMenuItemSchema`.
|
|
45
|
+
- `StoreConfig.themeSettings`: the merchant's values for the requesting
|
|
46
|
+
theme's settings, as saved — resolve them with `resolveThemeSettings` from
|
|
47
|
+
`@wocart/theme` 1.1. Optional: absent from a server that predates them.
|
|
48
|
+
|
|
49
|
+
### Deprecated
|
|
50
|
+
|
|
51
|
+
- `STOREFRONT_ROUTES` lists six of the paths, without the home and the
|
|
52
|
+
catalog; use `STOREFRONT_PAGE_PATHS` and `matchStorefrontPath`.
|
|
53
|
+
|
|
8
54
|
## 1.6.0 — 2026-10-17
|
|
9
55
|
|
|
10
56
|
### Added
|
package/USAGE.md
CHANGED
|
@@ -90,34 +90,55 @@ allowlists, delivery availability, stock, discounts, final totals, OTP
|
|
|
90
90
|
acceptance, or tracking eligibility. Those rules are platform data or platform
|
|
91
91
|
decisions. The theme presents them and gives the shopper a clear next action.
|
|
92
92
|
|
|
93
|
-
##
|
|
93
|
+
## Page types and their URLs
|
|
94
94
|
|
|
95
|
-
A
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
A URL *means* something to the platform — a product, a category, a store
|
|
96
|
+
page — and that meaning is a **page type**. Each page type lives at one
|
|
97
|
+
public path. The paths are a platform contract, not a theme preference: the
|
|
98
|
+
platform builds them without running your theme (the dashboard prints a page's
|
|
99
|
+
public link, the server names the landing page in the URL Meta is told about,
|
|
100
|
+
the AI tools hand out landing-page links), so a theme that moved one would
|
|
101
|
+
break those links.
|
|
101
102
|
|
|
102
|
-
|
|
|
103
|
-
| --- | --- |
|
|
104
|
-
|
|
|
105
|
-
| `/
|
|
106
|
-
| `/
|
|
107
|
-
| `/
|
|
108
|
-
| `/
|
|
109
|
-
| `/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
103
|
+
| Page type | Path | Renders | Your theme |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| `home` | `/` | the home page | required |
|
|
106
|
+
| `catalog` | `/products` | every product | required |
|
|
107
|
+
| `category` | `/category/:slug` | one category's products | required when the store has categories |
|
|
108
|
+
| `product` | `/products/:handle` | one product (its public handle, never an id) | required |
|
|
109
|
+
| `page` | `/pages/:slug` | a legal or merchant page | required — checkout consent links to the store's legal pages |
|
|
110
|
+
| `landing` | `/lp/:slug` | a landing page | required when the store runs landing pages |
|
|
111
|
+
| `checkout` | `/checkout` | the cart's checkout | required when the store turns the cart on |
|
|
112
|
+
| `thankYou` | `/thank-you` | the order confirmation | required |
|
|
113
|
+
|
|
114
|
+
How a theme implements them, in Astro's own terms:
|
|
115
|
+
|
|
116
|
+
- **Render each page type at its path with a file route**:
|
|
117
|
+
`src/pages/products/[handle].astro`, `src/pages/lp/[slug].astro` … The
|
|
118
|
+
parameter's name in your file is yours; the path is the platform's.
|
|
119
|
+
- **Build every link with `storefrontPath`**, never a hand-written string:
|
|
120
|
+
`storefrontPath("product", { handle: product.handle })` → `/products/robe`,
|
|
121
|
+
`storefrontPath("checkout")` → `/checkout`. Parameters are encoded for you;
|
|
122
|
+
a missing one throws, so a link to nowhere fails loudly. Add the language
|
|
123
|
+
prefix with `localizeHref(storefrontPath(…), locale, routing)`.
|
|
124
|
+
- **Recognise a page with `matchStorefrontPath(pathname)`** →
|
|
125
|
+
`{ type: "product", params: { handle: "robe" } }`, or `null` for any other
|
|
126
|
+
path. It matches exactly — `/productsomething`, `/products/a/b` and
|
|
127
|
+
`/checkout-success` are nothing — and ignores the query, fragment and a
|
|
128
|
+
trailing slash. Pass the path without its language prefix.
|
|
129
|
+
- **Answer a missing resource with a real 404** (`return Astro.rewrite("/404")`),
|
|
130
|
+
never a redirect to the home page: a redirect turns a dead link into a
|
|
131
|
+
"soft 404" for search engines and ad reviews.
|
|
132
|
+
- **Every other route is yours.** `/about`, `/offers/summer`, a store
|
|
133
|
+
locator — add them as Astro file routes; nothing in the contract restricts
|
|
134
|
+
them. Avoid the platform's paths above and `/api/*` (your same-origin
|
|
135
|
+
endpoints, see "Storefront API routes").
|
|
136
|
+
|
|
137
|
+
`showsWidgetOn(surfaces, pathname)` is the platform's half of "should the
|
|
138
|
+
WhatsApp widget show here": the merchant switches surfaces on; the page type
|
|
139
|
+
says which surface a pathname is. `STOREFRONT_PAGE_PATHS` is the table above
|
|
140
|
+
as data. `STOREFRONT_ROUTES` is deprecated: it lists six of the paths, without
|
|
141
|
+
the home and the catalog.
|
|
121
142
|
|
|
122
143
|
## The five rules to remember
|
|
123
144
|
|
|
@@ -196,7 +217,8 @@ server directly.
|
|
|
196
217
|
| `WILAYAS`, `ALGERIA_COMMUNES`, `communesForWilaya` | Reference data: 58 wilayas, 1,551 communes, one wilaya's rows |
|
|
197
218
|
| `normalizeAlgerianPhone`, `toLocalAlgerianMobile` | The one Algerian phone rule (accept/format) |
|
|
198
219
|
| `resolvePrefillText`, `buildWhatsAppHref`, `stripTrackingParams`, `truncateAtWordBoundary`, `PREFILL_TOKENS`, `MAX_RESOLVED_PREFILL_LENGTH` | WhatsApp prefill text and the `wa.me` link |
|
|
199
|
-
| `
|
|
220
|
+
| `storefrontPath`, `matchStorefrontPath`, `STOREFRONT_PAGE_PATHS` | The page types and their public paths: build a link, recognise a page |
|
|
221
|
+
| `showsWidgetOn`, `STOREFRONT_ROUTES` | Which widget surface a pathname is (`STOREFRONT_ROUTES` deprecated) |
|
|
200
222
|
| `productImageSource` | Which photo represents a product (cover → gallery fallback) |
|
|
201
223
|
| `getProductView`, `getProductListView` | Pure price, discount, default-variant, and stock values for detail and list projections |
|
|
202
224
|
| `getOfferTierView` | Pure offer-tier price, compare-price, savings, and free-shipping projection; display-only, never order authority |
|
|
@@ -295,6 +317,7 @@ carries the key, which the package's own contract test asserts.
|
|
|
295
317
|
| products | Product catalog and reviews | Product, ProductList, or empty/null |
|
|
296
318
|
| categories | Catalog navigation | Category array |
|
|
297
319
|
| pages | Legal and CMS pages, landing pages | StorePagePublic, LandingPage, or null |
|
|
320
|
+
| menus | The store's navigation menus | StoreMenu or null |
|
|
298
321
|
| pageData | The store's saved page document | PageDocumentPayload or null |
|
|
299
322
|
| shipping | Delivery prices | ShippingRates or one rate/null |
|
|
300
323
|
| geo | Wilayas and communes | Static wilayas or Commune array |
|
|
@@ -460,29 +483,46 @@ store.
|
|
|
460
483
|
## Store configuration
|
|
461
484
|
|
|
462
485
|
Use config.get() near the root of every page. It is the theme's starting point
|
|
463
|
-
for
|
|
486
|
+
for store identity and feature flags:
|
|
464
487
|
|
|
465
488
|
~~~ts
|
|
466
489
|
const config = await store.config.get();
|
|
467
490
|
|
|
468
491
|
const cartEnabled = config?.cartEnabled === true;
|
|
469
492
|
const currencySymbol = config?.currencySymbol ?? "دج";
|
|
470
|
-
const announcement = config?.announcementBar ?? null;
|
|
471
493
|
~~~
|
|
472
494
|
|
|
473
495
|
StoreConfig contains the current store identity and presentation fields,
|
|
474
496
|
including:
|
|
475
497
|
|
|
476
|
-
- name, domain, logoUrl
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
-
|
|
498
|
+
- name, domain, logoUrl and themeId (the colors, fontFamily and fontUrl
|
|
499
|
+
fields are deprecated: colors and fonts are theme settings, read from
|
|
500
|
+
themeSettings);
|
|
501
|
+
- lang (the primary language), locales, contentOverrides, themeSettings,
|
|
502
|
+
currency, currencySymbol, metaTitle, metaDescription, and ogImage;
|
|
503
|
+
- reviewsEnabled (announcementBar is deprecated and always null: an
|
|
504
|
+
announcement is a theme's own setting);
|
|
480
505
|
- cartEnabled, freeShippingThreshold, and cartShippingMode;
|
|
481
506
|
- checkoutForm policy;
|
|
482
507
|
- OTP and Turnstile settings;
|
|
483
508
|
- pixelId and conversionEvent;
|
|
484
509
|
- public page links, legal contact information, and WhatsApp widget settings.
|
|
485
510
|
|
|
511
|
+
`themeSettings` holds the merchant's values for your theme's settings (the
|
|
512
|
+
ones it declares with `defineTheme({ settings })` in `@wocart/theme`), by
|
|
513
|
+
setting key, exactly as saved. It is sent only to a client created with
|
|
514
|
+
`theme`, is `{}` when the store saved none, and is absent from a server that
|
|
515
|
+
predates theme settings — `resolveThemeSettings` takes either. Never read it raw: resolve it
|
|
516
|
+
against your declaration, which checks every value and falls back to your
|
|
517
|
+
default for one it no longer accepts:
|
|
518
|
+
|
|
519
|
+
~~~ts
|
|
520
|
+
import { resolveThemeSettings } from "@wocart/theme";
|
|
521
|
+
|
|
522
|
+
const { values } = resolveThemeSettings(theme, config?.themeSettings, { locales: [locale] });
|
|
523
|
+
const headerMenu = values.headerMenu; // a menu handle, for store.menus.get()
|
|
524
|
+
~~~
|
|
525
|
+
|
|
486
526
|
The checkout policy is not a suggestion. Use it to decide whether address,
|
|
487
527
|
email, notes, delivery options, and custom fields are required, optional, or
|
|
488
528
|
hidden. The policy includes:
|
|
@@ -592,11 +632,28 @@ decides whether a product is publicly available.
|
|
|
592
632
|
~~~ts
|
|
593
633
|
const products = await store.products.list({
|
|
594
634
|
categoryId: selectedCategoryId,
|
|
595
|
-
featured: true,
|
|
596
635
|
limit: 12,
|
|
597
636
|
});
|
|
598
637
|
~~~
|
|
599
638
|
|
|
639
|
+
**Collections** are lists of products the merchant curates by hand in the
|
|
640
|
+
dashboard (Products → Collections): every store starts with `featured`,
|
|
641
|
+
`best-sellers` and `new-arrivals`, and may add any other. A collection lists
|
|
642
|
+
in the merchant's order:
|
|
643
|
+
|
|
644
|
+
~~~ts
|
|
645
|
+
const bestSellers = await store.products.list({ collection: "best-sellers", limit: 8 });
|
|
646
|
+
const onSale = await store.products.list({ onSale: true, limit: 8 });
|
|
647
|
+
~~~
|
|
648
|
+
|
|
649
|
+
- Never hardcode which collection a section shows: declare a `collection`
|
|
650
|
+
field (`@wocart/theme`) with a default handle, so the merchant picks it.
|
|
651
|
+
- An empty or unknown collection lists nothing — show nothing, never the
|
|
652
|
+
whole catalog instead: "Best sellers" means the merchant's picks.
|
|
653
|
+
- `onSale` is computed: a product priced below its compare-at price.
|
|
654
|
+
- `featured: true` and `ProductList.storeFeatured` are deprecated: they mean
|
|
655
|
+
the `featured` collection.
|
|
656
|
+
|
|
600
657
|
Product lists return ProductList objects. ProductList and Product are
|
|
601
658
|
different shapes: a list item is not a detail object and must not be cast to
|
|
602
659
|
Product. ProductList carries list-oriented fields such as coverImage and
|
|
@@ -716,6 +773,36 @@ Do not hardcode the legal navigation list. Use config.pages so the store's
|
|
|
716
773
|
available public pages determine the links. If a page is null, render the
|
|
717
774
|
theme's not-found or fallback state.
|
|
718
775
|
|
|
776
|
+
### Navigation menus
|
|
777
|
+
|
|
778
|
+
A store's menus are store data, like its products: the merchant builds them in
|
|
779
|
+
the dashboard (Navigation) — a handle such as `main-menu`, a title, and up to
|
|
780
|
+
three levels of items, each pointing at a product, a category, a store page, a
|
|
781
|
+
landing page, the home, the catalog, or a URL. Your theme decides *where* a
|
|
782
|
+
menu shows; the merchant decides *what* it holds.
|
|
783
|
+
|
|
784
|
+
~~~ts
|
|
785
|
+
const menu = await store.menus.get(settings.headerMenu); // "main-menu"
|
|
786
|
+
|
|
787
|
+
for (const item of menu?.items ?? []) {
|
|
788
|
+
const href = item.type === "url" ? item.href : localizeHref(item.href, locale, routing);
|
|
789
|
+
// item.label, item.children (same shape, at most three levels)
|
|
790
|
+
}
|
|
791
|
+
~~~
|
|
792
|
+
|
|
793
|
+
- Name the menu through a `menu` theme setting rather than a hard-coded
|
|
794
|
+
handle, so the merchant picks which menu each place shows.
|
|
795
|
+
- Every link is resolved by the server: `href` is a path of the store without
|
|
796
|
+
a language prefix (pass it through `localizeHref`) or, for `type: "url"`,
|
|
797
|
+
the address the merchant entered — render it as is.
|
|
798
|
+
- An item whose destination is deleted or not public (a draft page, an
|
|
799
|
+
inactive product) is already left out, with its children. You never filter.
|
|
800
|
+
- Labels arrive in the client's language when the store translated them.
|
|
801
|
+
- `null` means the store has no menu with that handle, or the read failed:
|
|
802
|
+
render your own fallback navigation (for example the home and the catalog
|
|
803
|
+
from `storefrontPath`). A missing menu is not a failure and is not reported
|
|
804
|
+
to `onError`.
|
|
805
|
+
|
|
719
806
|
### Landing pages
|
|
720
807
|
|
|
721
808
|
~~~ts
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Store Client Factory — the SDK's front door.
|
|
3
3
|
*
|
|
4
4
|
* One server-side call builds the whole read/write surface a theme needs, over one
|
|
5
|
-
* shared core: config, products, categories, pages, shipping, geo, cart,
|
|
5
|
+
* shared core: config, products, categories, pages, menus, shipping, geo, cart,
|
|
6
6
|
* orders. The core is internal — consumers see only namespaces.
|
|
7
7
|
*
|
|
8
8
|
* One core means one connection story for every namespace: every read is
|
|
@@ -31,6 +31,7 @@ export declare function createStoreClient(config: StoreScope): {
|
|
|
31
31
|
products: import("./products.js").ProductsNamespace;
|
|
32
32
|
categories: import("./categories.js").CategoriesNamespace;
|
|
33
33
|
pages: import("./pages.js").PagesNamespace;
|
|
34
|
+
menus: import("./menus.js").MenusNamespace;
|
|
34
35
|
pageData: import("./page-data.js").PageDataNamespace;
|
|
35
36
|
shipping: import("./shipping.js").ShippingNamespace;
|
|
36
37
|
geo: import("./geo.js").GeoNamespace;
|
package/dist/client/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Store Client Factory — the SDK's front door.
|
|
3
3
|
*
|
|
4
4
|
* One server-side call builds the whole read/write surface a theme needs, over one
|
|
5
|
-
* shared core: config, products, categories, pages, shipping, geo, cart,
|
|
5
|
+
* shared core: config, products, categories, pages, menus, shipping, geo, cart,
|
|
6
6
|
* orders. The core is internal — consumers see only namespaces.
|
|
7
7
|
*
|
|
8
8
|
* One core means one connection story for every namespace: every read is
|
|
@@ -31,6 +31,7 @@ import { createConfigNamespace } from "./config.js";
|
|
|
31
31
|
import { createProductsNamespace } from "./products.js";
|
|
32
32
|
import { createCategoriesNamespace } from "./categories.js";
|
|
33
33
|
import { createPagesNamespace } from "./pages.js";
|
|
34
|
+
import { createMenusNamespace } from "./menus.js";
|
|
34
35
|
import { createPageDataNamespace } from "./page-data.js";
|
|
35
36
|
import { createShippingNamespace } from "./shipping.js";
|
|
36
37
|
import { createGeoNamespace } from "./geo.js";
|
|
@@ -44,6 +45,7 @@ export function createStoreClient(config) {
|
|
|
44
45
|
products: createProductsNamespace(core),
|
|
45
46
|
categories: createCategoriesNamespace(core),
|
|
46
47
|
pages: createPagesNamespace(core),
|
|
48
|
+
menus: createMenusNamespace(core),
|
|
47
49
|
pageData: createPageDataNamespace(core),
|
|
48
50
|
shipping: createShippingNamespace(core),
|
|
49
51
|
geo: createGeoNamespace(core),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menus Namespace - the store's navigation menus
|
|
3
|
+
*
|
|
4
|
+
* A menu is store data the merchant edits in the dashboard; a theme names it
|
|
5
|
+
* by handle (usually through a `menu` theme setting) and renders what comes
|
|
6
|
+
* back. Every link is resolved by the server: an item's `href` is a path of
|
|
7
|
+
* the store without a language prefix, or the merchant's URL, and items
|
|
8
|
+
* leading nowhere public are already left out. Fetched fresh every request.
|
|
9
|
+
*
|
|
10
|
+
* Failure contract: `get(handle)` → null means "no such menu" or a failed
|
|
11
|
+
* read — the theme renders its own fallback navigation.
|
|
12
|
+
*/
|
|
13
|
+
import { type ClientCore } from "./core.js";
|
|
14
|
+
import type { StoreMenu } from "../types.js";
|
|
15
|
+
export interface MenusNamespace {
|
|
16
|
+
/**
|
|
17
|
+
* A menu by handle (`main-menu`), labels in the client's language.
|
|
18
|
+
* Resolves to `null` for an unknown handle or a failed read.
|
|
19
|
+
*/
|
|
20
|
+
get(handle: string): Promise<StoreMenu | null>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createMenusNamespace(core: ClientCore): MenusNamespace;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menus Namespace - the store's navigation menus
|
|
3
|
+
*
|
|
4
|
+
* A menu is store data the merchant edits in the dashboard; a theme names it
|
|
5
|
+
* by handle (usually through a `menu` theme setting) and renders what comes
|
|
6
|
+
* back. Every link is resolved by the server: an item's `href` is a path of
|
|
7
|
+
* the store without a language prefix, or the merchant's URL, and items
|
|
8
|
+
* leading nowhere public are already left out. Fetched fresh every request.
|
|
9
|
+
*
|
|
10
|
+
* Failure contract: `get(handle)` → null means "no such menu" or a failed
|
|
11
|
+
* read — the theme renders its own fallback navigation.
|
|
12
|
+
*/
|
|
13
|
+
import { StoreMenuSchema } from "../schemas.js";
|
|
14
|
+
import { STOREFRONT_OPERATIONS } from "../domain.js";
|
|
15
|
+
import { localizedPath } from "./paths.js";
|
|
16
|
+
import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
|
|
17
|
+
export function createMenusNamespace(core) {
|
|
18
|
+
return {
|
|
19
|
+
async get(handle) {
|
|
20
|
+
try {
|
|
21
|
+
return await core.get(localizedPath(core.config, `/store/menus/${encodeURIComponent(handle)}`), StoreMenuSchema, { operation: STOREFRONT_OPERATIONS.menusGet });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!isNotFoundAnswer(error))
|
|
25
|
+
reportDegradedRead(core.config, error, "menus.get");
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -26,7 +26,7 @@ export interface ProductsNamespace {
|
|
|
26
26
|
get(handle: string): Promise<Product | null>;
|
|
27
27
|
/**
|
|
28
28
|
* List products with optional filters
|
|
29
|
-
* @param filters - Optional category,
|
|
29
|
+
* @param filters - Optional collection, category, on-sale flag, limit and offset
|
|
30
30
|
* @returns Array of products
|
|
31
31
|
*/
|
|
32
32
|
list(filters?: ProductListFilters): Promise<ProductList[]>;
|
package/dist/client/products.js
CHANGED
|
@@ -28,6 +28,8 @@ export function createProductsNamespace(core) {
|
|
|
28
28
|
try {
|
|
29
29
|
const products = await core.get(localizedPath(core.config, "/store/products", {
|
|
30
30
|
categoryId: filters?.categoryId || undefined,
|
|
31
|
+
collection: filters?.collection || undefined,
|
|
32
|
+
onSale: filters?.onSale ? "true" : undefined,
|
|
31
33
|
featured: filters?.featured ? "true" : undefined,
|
|
32
34
|
limit: filters?.limit === undefined ? undefined : String(filters.limit),
|
|
33
35
|
offset: filters?.offset ? String(filters.offset) : undefined,
|
package/dist/domain.d.ts
CHANGED
|
@@ -353,6 +353,7 @@ export declare const STOREFRONT_OPERATIONS: {
|
|
|
353
353
|
readonly productsReviewsUpload: "products.reviews.upload";
|
|
354
354
|
readonly pagesGet: "pages.get";
|
|
355
355
|
readonly pagesLanding: "pages.landing";
|
|
356
|
+
readonly menusGet: "menus.get";
|
|
356
357
|
readonly shippingRates: "shipping.rates";
|
|
357
358
|
readonly shippingRate: "shipping.rate";
|
|
358
359
|
readonly geoCommunes: "geo.communes";
|
|
@@ -390,6 +391,7 @@ export declare const STOREFRONT_RETRY_POLICIES: {
|
|
|
390
391
|
readonly "pageData.get": "safe-read";
|
|
391
392
|
readonly "pages.get": "safe-read";
|
|
392
393
|
readonly "pages.landing": "safe-read";
|
|
394
|
+
readonly "menus.get": "safe-read";
|
|
393
395
|
readonly "products.get": "safe-read";
|
|
394
396
|
readonly "products.list": "safe-read";
|
|
395
397
|
readonly "products.reviews.list": "safe-read";
|
package/dist/domain.js
CHANGED
|
@@ -206,6 +206,7 @@ export const STOREFRONT_OPERATIONS = {
|
|
|
206
206
|
productsReviewsUpload: "products.reviews.upload",
|
|
207
207
|
pagesGet: "pages.get",
|
|
208
208
|
pagesLanding: "pages.landing",
|
|
209
|
+
menusGet: "menus.get",
|
|
209
210
|
shippingRates: "shipping.rates",
|
|
210
211
|
shippingRate: "shipping.rate",
|
|
211
212
|
geoCommunes: "geo.communes",
|
|
@@ -242,6 +243,7 @@ export const STOREFRONT_RETRY_POLICIES = {
|
|
|
242
243
|
[STOREFRONT_OPERATIONS.pageDataGet]: "safe-read",
|
|
243
244
|
[STOREFRONT_OPERATIONS.pagesGet]: "safe-read",
|
|
244
245
|
[STOREFRONT_OPERATIONS.pagesLanding]: "safe-read",
|
|
246
|
+
[STOREFRONT_OPERATIONS.menusGet]: "safe-read",
|
|
245
247
|
[STOREFRONT_OPERATIONS.productsGet]: "safe-read",
|
|
246
248
|
[STOREFRONT_OPERATIONS.productsList]: "safe-read",
|
|
247
249
|
[STOREFRONT_OPERATIONS.productsReviewsList]: "safe-read",
|
package/dist/index.d.ts
CHANGED
|
@@ -17,11 +17,11 @@ export { productImageSource } from "./utils/product-image.js";
|
|
|
17
17
|
export type { ProductImageSourceInput } from "./utils/product-image.js";
|
|
18
18
|
export { MAX_RESOLVED_PREFILL_LENGTH, PREFILL_TOKENS, stripTrackingParams, truncateAtWordBoundary, resolvePrefillText, buildWhatsAppHref, } from "./whatsapp.js";
|
|
19
19
|
export type { PrefillToken, ResolvePrefillParams } from "./whatsapp.js";
|
|
20
|
-
export { STOREFRONT_ROUTES, showsWidgetOn } from "./routes.js";
|
|
20
|
+
export { STOREFRONT_PAGE_PATHS, STOREFRONT_ROUTES, matchStorefrontPath, showsWidgetOn, storefrontPath } from "./routes.js";
|
|
21
21
|
export { servedLanguage } from "./locales.js";
|
|
22
22
|
export { alternateLinks, localeRouting, localizeHref, resolveLocalePath } from "./locale-routing.js";
|
|
23
23
|
export type { LocalePath, LocaleRouting } from "./locale-routing.js";
|
|
24
|
-
export type { StorefrontRoute } from "./routes.js";
|
|
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
26
|
export type { BasketStore, BasketStorageAdapter, BasketStoreOptions, BasketListener, } from "./basket.js";
|
|
27
27
|
export { createOtpMachine, createOtpTransport, mapOtpResponse, otpTransportFailure, } from "./otp.js";
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export { formatMoney } from "./utils/money.js";
|
|
|
13
13
|
export { optimizeImage, generateSrcSet, IMAGE_SIZES, getCardImage, getGalleryImage, getThumbnailImage, getHeroImage, getCategoryImage, getLogoImage, createImageHelpers, } from "./utils/image.js";
|
|
14
14
|
export { productImageSource } from "./utils/product-image.js";
|
|
15
15
|
export { MAX_RESOLVED_PREFILL_LENGTH, PREFILL_TOKENS, stripTrackingParams, truncateAtWordBoundary, resolvePrefillText, buildWhatsAppHref, } from "./whatsapp.js";
|
|
16
|
-
export { STOREFRONT_ROUTES, showsWidgetOn } from "./routes.js";
|
|
16
|
+
export { STOREFRONT_PAGE_PATHS, STOREFRONT_ROUTES, matchStorefrontPath, showsWidgetOn, storefrontPath } from "./routes.js";
|
|
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";
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,20 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The storefront's page types and their public paths.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the dashboard shows
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* A page type is what a URL *means* — a product, a category, a store page, a
|
|
5
|
+
* landing page — and the path is where it lives. The paths are a platform
|
|
6
|
+
* contract, not a theme decision: the platform builds them without running
|
|
7
|
+
* the theme (the dashboard shows a page's public link, the server names the
|
|
8
|
+
* landing page in a conversion's source URL), so a theme renders each page
|
|
9
|
+
* type it supports at its path and builds every link to one with
|
|
10
|
+
* `storefrontPath` — never a hand-written string. Any other route is the
|
|
11
|
+
* theme's own (`/about`, `/offers/summer` …): Astro's file routing owns those,
|
|
12
|
+
* and nothing here constrains them.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* surface gate below handles them alongside the six frozen patterns.
|
|
14
|
+
* Language prefixes are applied on top: `localizeHref(storefrontPath(…),
|
|
15
|
+
* locale, routing)`.
|
|
15
16
|
*/
|
|
16
17
|
import type { WidgetSurfaces } from "./types.js";
|
|
17
|
-
/**
|
|
18
|
+
/** Each page type's public path; `:handle` / `:slug` mark its one parameter. */
|
|
19
|
+
export declare const STOREFRONT_PAGE_PATHS: {
|
|
20
|
+
readonly home: "/";
|
|
21
|
+
readonly catalog: "/products";
|
|
22
|
+
readonly category: "/category/:slug";
|
|
23
|
+
readonly product: "/products/:handle";
|
|
24
|
+
readonly page: "/pages/:slug";
|
|
25
|
+
readonly landing: "/lp/:slug";
|
|
26
|
+
readonly checkout: "/checkout";
|
|
27
|
+
readonly thankYou: "/thank-you";
|
|
28
|
+
};
|
|
29
|
+
/** What a storefront URL means. */
|
|
30
|
+
export type StorefrontPageType = keyof typeof STOREFRONT_PAGE_PATHS;
|
|
31
|
+
type ParamOf<P extends string> = P extends `${string}:${infer Name}` ? Name : never;
|
|
32
|
+
/** The parameter a page type's path needs: `{ handle }` for a product, `{ slug }` for a category, page or landing page, nothing otherwise. */
|
|
33
|
+
export type StorefrontPathParams<T extends StorefrontPageType> = ParamOf<(typeof STOREFRONT_PAGE_PATHS)[T]> extends never ? Record<string, never> : {
|
|
34
|
+
[K in ParamOf<(typeof STOREFRONT_PAGE_PATHS)[T]>]: string;
|
|
35
|
+
};
|
|
36
|
+
/** A pathname recognised as one of the page types, with its parameter. */
|
|
37
|
+
export interface StorefrontPageMatch {
|
|
38
|
+
readonly type: StorefrontPageType;
|
|
39
|
+
readonly params: Readonly<Record<string, string>>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The public path of a page type, its parameter encoded:
|
|
43
|
+
* `storefrontPath("product", { handle: "robe" })` → `/products/robe`.
|
|
44
|
+
* Throws when the parameter is missing — a link to nowhere is a bug to see,
|
|
45
|
+
* not a path to render.
|
|
46
|
+
*/
|
|
47
|
+
export declare function storefrontPath<T extends StorefrontPageType>(type: T, params?: StorefrontPathParams<T>): string;
|
|
48
|
+
/**
|
|
49
|
+
* Which page type a pathname is, and its parameter (decoded) — or null for
|
|
50
|
+
* any other path. Exact matches only: `/products/` is the catalog,
|
|
51
|
+
* `/productsomething` and `/products/a/b` are nothing. The query, the
|
|
52
|
+
* fragment and a trailing slash are ignored. Pass a pathname without its
|
|
53
|
+
* language prefix (`resolveLocalePath` gives it).
|
|
54
|
+
*/
|
|
55
|
+
export declare function matchStorefrontPath(rawPathname: string): StorefrontPageMatch | null;
|
|
56
|
+
/** One route with a parameter or a fixed path of its own, and the widget surface that renders it. */
|
|
18
57
|
export interface StorefrontRoute {
|
|
19
58
|
/** Path pattern; `:handle` and `:slug` mark the single path parameter. */
|
|
20
59
|
readonly path: string;
|
|
@@ -22,24 +61,17 @@ export interface StorefrontRoute {
|
|
|
22
61
|
readonly surface: keyof WidgetSurfaces;
|
|
23
62
|
}
|
|
24
63
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
64
|
+
* @deprecated Use `STOREFRONT_PAGE_PATHS` (every page type, the home and the
|
|
65
|
+
* catalog index included) with `storefrontPath` and `matchStorefrontPath`.
|
|
66
|
+
* Kept equal to what it always listed.
|
|
28
67
|
*/
|
|
29
68
|
export declare const STOREFRONT_ROUTES: readonly StorefrontRoute[];
|
|
30
69
|
/**
|
|
31
|
-
* Whether the widget belongs on a pathname
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* - `/` → the home surface;
|
|
38
|
-
* - `/products` (the catalog index) and `/category/:slug` → the catalog surface;
|
|
39
|
-
* - `/products/:handle` → the product surface;
|
|
40
|
-
* - `/pages/:slug`, `/lp/:slug`, `/checkout`, `/thank-you` → their own surfaces;
|
|
41
|
-
* - anything else — a 404, `/productsomething`, `/checkout-success` — → no
|
|
42
|
-
* surface at all, never a prefix match that leaks a widget onto a page the
|
|
43
|
-
* merchant switched off.
|
|
70
|
+
* Whether the widget belongs on a pathname: the merchant answers "which
|
|
71
|
+
* surfaces" (`surfaces[surface]`); this answers which surface a pathname *is*,
|
|
72
|
+
* from the page types above. Any other path — a 404, `/productsomething`,
|
|
73
|
+
* `/checkout-success` — is no surface at all, never a prefix match that leaks
|
|
74
|
+
* the widget onto a page the merchant switched off.
|
|
44
75
|
*/
|
|
45
76
|
export declare function showsWidgetOn(surfaces: WidgetSurfaces | null | undefined, rawPathname: string): boolean;
|
|
77
|
+
export {};
|
package/dist/routes.js
CHANGED
|
@@ -1,59 +1,88 @@
|
|
|
1
|
+
/** Each page type's public path; `:handle` / `:slug` mark its one parameter. */
|
|
2
|
+
export const STOREFRONT_PAGE_PATHS = {
|
|
3
|
+
home: "/",
|
|
4
|
+
catalog: "/products",
|
|
5
|
+
category: "/category/:slug",
|
|
6
|
+
product: "/products/:handle",
|
|
7
|
+
page: "/pages/:slug",
|
|
8
|
+
landing: "/lp/:slug",
|
|
9
|
+
checkout: "/checkout",
|
|
10
|
+
thankYou: "/thank-you",
|
|
11
|
+
};
|
|
1
12
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
13
|
+
* The public path of a page type, its parameter encoded:
|
|
14
|
+
* `storefrontPath("product", { handle: "robe" })` → `/products/robe`.
|
|
15
|
+
* Throws when the parameter is missing — a link to nowhere is a bug to see,
|
|
16
|
+
* not a path to render.
|
|
5
17
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
export function storefrontPath(type, params) {
|
|
19
|
+
return STOREFRONT_PAGE_PATHS[type].replace(/:([a-z]+)/g, (_, name) => {
|
|
20
|
+
const value = params?.[name];
|
|
21
|
+
if (!value)
|
|
22
|
+
throw new Error(`storefrontPath("${type}") needs "${name}"`);
|
|
23
|
+
return encodeURIComponent(value);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const MATCHERS = Object.entries(STOREFRONT_PAGE_PATHS).map(([type, path]) => {
|
|
27
|
+
const names = [];
|
|
28
|
+
const source = path.replace(/:([a-z]+)/g, (_, name) => {
|
|
29
|
+
names.push(name);
|
|
30
|
+
return "([^/]+)";
|
|
31
|
+
});
|
|
32
|
+
return { type, names, pattern: new RegExp(`^${source}$`) };
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Which page type a pathname is, and its parameter (decoded) — or null for
|
|
36
|
+
* any other path. Exact matches only: `/products/` is the catalog,
|
|
37
|
+
* `/productsomething` and `/products/a/b` are nothing. The query, the
|
|
38
|
+
* fragment and a trailing slash are ignored. Pass a pathname without its
|
|
39
|
+
* language prefix (`resolveLocalePath` gives it).
|
|
40
|
+
*/
|
|
41
|
+
export function matchStorefrontPath(rawPathname) {
|
|
42
|
+
const pathname = rawPathname.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
|
|
43
|
+
for (const { type, names, pattern } of MATCHERS) {
|
|
44
|
+
const match = pattern.exec(pathname);
|
|
45
|
+
if (!match)
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
return { type, params: Object.fromEntries(names.map((name, i) => [name, decodeURIComponent(match[i + 1])])) };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/** The WhatsApp widget surface each page type belongs to (the home surface is the root). */
|
|
57
|
+
const WIDGET_SURFACE = {
|
|
58
|
+
home: "home",
|
|
59
|
+
catalog: "catalog",
|
|
60
|
+
category: "catalog",
|
|
61
|
+
product: "product",
|
|
62
|
+
page: "pages",
|
|
63
|
+
landing: "landing",
|
|
64
|
+
checkout: "checkout",
|
|
65
|
+
thankYou: "thankYou",
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated Use `STOREFRONT_PAGE_PATHS` (every page type, the home and the
|
|
69
|
+
* catalog index included) with `storefrontPath` and `matchStorefrontPath`.
|
|
70
|
+
* Kept equal to what it always listed.
|
|
71
|
+
*/
|
|
72
|
+
export const STOREFRONT_ROUTES = ["product", "category", "page", "landing", "checkout", "thankYou"].map((type) => ({
|
|
73
|
+
path: STOREFRONT_PAGE_PATHS[type],
|
|
74
|
+
surface: WIDGET_SURFACE[type],
|
|
75
|
+
}));
|
|
14
76
|
/**
|
|
15
|
-
* Whether the widget belongs on a pathname
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - `/` → the home surface;
|
|
22
|
-
* - `/products` (the catalog index) and `/category/:slug` → the catalog surface;
|
|
23
|
-
* - `/products/:handle` → the product surface;
|
|
24
|
-
* - `/pages/:slug`, `/lp/:slug`, `/checkout`, `/thank-you` → their own surfaces;
|
|
25
|
-
* - anything else — a 404, `/productsomething`, `/checkout-success` — → no
|
|
26
|
-
* surface at all, never a prefix match that leaks a widget onto a page the
|
|
27
|
-
* merchant switched off.
|
|
77
|
+
* Whether the widget belongs on a pathname: the merchant answers "which
|
|
78
|
+
* surfaces" (`surfaces[surface]`); this answers which surface a pathname *is*,
|
|
79
|
+
* from the page types above. Any other path — a 404, `/productsomething`,
|
|
80
|
+
* `/checkout-success` — is no surface at all, never a prefix match that leaks
|
|
81
|
+
* the widget onto a page the merchant switched off.
|
|
28
82
|
*/
|
|
29
83
|
export function showsWidgetOn(surfaces, rawPathname) {
|
|
30
84
|
if (!surfaces)
|
|
31
85
|
return false;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (pathname === "/") {
|
|
35
|
-
return Boolean(surfaces.home);
|
|
36
|
-
}
|
|
37
|
-
if (pathname === "/products" || pathname.startsWith("/category/")) {
|
|
38
|
-
return Boolean(surfaces.catalog);
|
|
39
|
-
}
|
|
40
|
-
if (pathname.startsWith("/products/")) {
|
|
41
|
-
const slug = pathname.slice("/products/".length);
|
|
42
|
-
return slug.length > 0 ? Boolean(surfaces.product) : Boolean(surfaces.catalog);
|
|
43
|
-
}
|
|
44
|
-
if (pathname === "/checkout") {
|
|
45
|
-
return Boolean(surfaces.checkout);
|
|
46
|
-
}
|
|
47
|
-
if (pathname === "/thank-you") {
|
|
48
|
-
return Boolean(surfaces.thankYou);
|
|
49
|
-
}
|
|
50
|
-
if (pathname.startsWith("/pages/")) {
|
|
51
|
-
const slug = pathname.slice("/pages/".length);
|
|
52
|
-
return slug.length > 0 ? Boolean(surfaces.pages) : false;
|
|
53
|
-
}
|
|
54
|
-
if (pathname.startsWith("/lp/")) {
|
|
55
|
-
const slug = pathname.slice("/lp/".length);
|
|
56
|
-
return slug.length > 0 ? Boolean(surfaces.landing) : false;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
86
|
+
const match = matchStorefrontPath(rawPathname);
|
|
87
|
+
return match ? Boolean(surfaces[WIDGET_SURFACE[match.type]]) : false;
|
|
59
88
|
}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -234,6 +234,23 @@ export declare const StorePagePublicSchema: z.ZodObject<{
|
|
|
234
234
|
metaTitle: z.ZodNullable<z.ZodString>;
|
|
235
235
|
metaDescription: z.ZodNullable<z.ZodString>;
|
|
236
236
|
}, z.core.$strip>;
|
|
237
|
+
/**
|
|
238
|
+
* A navigation menu as a shopper sees it — GET /store/menus/{handle}.
|
|
239
|
+
* `type` is an open string: the server owns which destinations exist, so a
|
|
240
|
+
* new one never fails this parse.
|
|
241
|
+
*/
|
|
242
|
+
export declare const StoreMenuItemSchema: z.ZodType<StoreMenuItemShape>;
|
|
243
|
+
interface StoreMenuItemShape {
|
|
244
|
+
label: string;
|
|
245
|
+
href: string;
|
|
246
|
+
type: string;
|
|
247
|
+
children: StoreMenuItemShape[];
|
|
248
|
+
}
|
|
249
|
+
export declare const StoreMenuSchema: z.ZodObject<{
|
|
250
|
+
handle: z.ZodString;
|
|
251
|
+
title: z.ZodString;
|
|
252
|
+
items: z.ZodArray<z.ZodType<StoreMenuItemShape, unknown, z.core.$ZodTypeInternals<StoreMenuItemShape, unknown>>>;
|
|
253
|
+
}, z.core.$strip>;
|
|
237
254
|
/**
|
|
238
255
|
* The persisted theme page document (PageData): the
|
|
239
256
|
* TRANSPORT shape only. `sections` stays `unknown[]` so instances of a newer
|
|
@@ -373,6 +390,7 @@ export declare const StoreConfigSchema: z.ZodObject<{
|
|
|
373
390
|
}>;
|
|
374
391
|
}, z.core.$strip>>;
|
|
375
392
|
contentOverrides: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
393
|
+
themeSettings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
376
394
|
currency: z.ZodString;
|
|
377
395
|
currencySymbol: z.ZodString;
|
|
378
396
|
metaTitle: z.ZodNullable<z.ZodString>;
|
|
@@ -832,3 +850,4 @@ export declare const ReviewListEnvelopeSchema: z.ZodObject<{
|
|
|
832
850
|
}, z.core.$strip>, unknown>>;
|
|
833
851
|
total: z.ZodNumber;
|
|
834
852
|
}, z.core.$strip>;
|
|
853
|
+
export {};
|
package/dist/schemas.js
CHANGED
|
@@ -82,6 +82,7 @@ const productBaseFields = {
|
|
|
82
82
|
visibility: z.boolean(),
|
|
83
83
|
status: z.enum(["DRAFT", "ACTIVE", "ARCHIVED"]),
|
|
84
84
|
showInStore: z.boolean(),
|
|
85
|
+
/** @deprecated Whether the product is in the `featured` collection; list it with `collection: "featured"`. Removed in 2.0. */
|
|
85
86
|
storeFeatured: z.boolean(),
|
|
86
87
|
/**
|
|
87
88
|
* @deprecated Always null: the server never serves a removed product. The
|
|
@@ -133,6 +134,22 @@ export const StorePagePublicSchema = z.object({
|
|
|
133
134
|
metaTitle: z.string().nullable(),
|
|
134
135
|
metaDescription: z.string().nullable(),
|
|
135
136
|
});
|
|
137
|
+
/**
|
|
138
|
+
* A navigation menu as a shopper sees it — GET /store/menus/{handle}.
|
|
139
|
+
* `type` is an open string: the server owns which destinations exist, so a
|
|
140
|
+
* new one never fails this parse.
|
|
141
|
+
*/
|
|
142
|
+
export const StoreMenuItemSchema = z.lazy(() => z.object({
|
|
143
|
+
label: z.string(),
|
|
144
|
+
href: z.string(),
|
|
145
|
+
type: z.string(),
|
|
146
|
+
children: z.array(StoreMenuItemSchema),
|
|
147
|
+
}));
|
|
148
|
+
export const StoreMenuSchema = z.object({
|
|
149
|
+
handle: z.string(),
|
|
150
|
+
title: z.string(),
|
|
151
|
+
items: z.array(StoreMenuItemSchema),
|
|
152
|
+
});
|
|
136
153
|
/**
|
|
137
154
|
* The persisted theme page document (PageData): the
|
|
138
155
|
* TRANSPORT shape only. `sections` stays `unknown[]` so instances of a newer
|
|
@@ -225,6 +242,11 @@ export const StoreConfigSchema = z.object({
|
|
|
225
242
|
locales: z.array(StorefrontLanguageSchema),
|
|
226
243
|
/** The requesting theme's text edits, one map per published language. */
|
|
227
244
|
contentOverrides: z.record(z.string(), z.record(z.string(), z.string())),
|
|
245
|
+
/**
|
|
246
|
+
* The store's values for the requesting theme's settings, as saved. Absent
|
|
247
|
+
* from a server older than theme settings.
|
|
248
|
+
*/
|
|
249
|
+
themeSettings: z.record(z.string(), z.unknown()).optional(),
|
|
228
250
|
currency: z.string(),
|
|
229
251
|
currencySymbol: z.string(),
|
|
230
252
|
metaTitle: z.string().nullable(),
|
package/dist/types.d.ts
CHANGED
|
@@ -64,10 +64,31 @@ export interface StoreConfig {
|
|
|
64
64
|
domain: string | null;
|
|
65
65
|
logoUrl: string | null;
|
|
66
66
|
themeId: string;
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated A store's colors and font are its theme's settings: declare
|
|
69
|
+
* them in `defineTheme({ settings })` and read `themeSettings`. This is the
|
|
70
|
+
* value saved before that, no longer editable; removed in 2.0.
|
|
71
|
+
*/
|
|
67
72
|
primaryColor: string;
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated A store's colors and font are its theme's settings: declare
|
|
75
|
+
* them in `defineTheme({ settings })` and read `themeSettings`. This is the
|
|
76
|
+
* value saved before that, no longer editable; removed in 2.0.
|
|
77
|
+
*/
|
|
68
78
|
accentColor: string;
|
|
79
|
+
/**
|
|
80
|
+
* @deprecated A store's colors and font are its theme's settings: declare
|
|
81
|
+
* them in `defineTheme({ settings })` and read `themeSettings`. This is the
|
|
82
|
+
* value saved before that, no longer editable; removed in 2.0.
|
|
83
|
+
*/
|
|
69
84
|
bgColor: string;
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated A store's colors and font are its theme's settings: declare
|
|
87
|
+
* them in `defineTheme({ settings })` and read `themeSettings`. This is the
|
|
88
|
+
* value saved before that, no longer editable; removed in 2.0.
|
|
89
|
+
*/
|
|
70
90
|
fontFamily: string;
|
|
91
|
+
/** @deprecated Always null; removed in 2.0. */
|
|
71
92
|
fontUrl: string | null;
|
|
72
93
|
/** The store's primary language: a BCP 47 code such as `ar`. */
|
|
73
94
|
lang: string;
|
|
@@ -85,11 +106,24 @@ export interface StoreConfig {
|
|
|
85
106
|
* language — never another language's map.
|
|
86
107
|
*/
|
|
87
108
|
contentOverrides: Record<string, Record<string, string>>;
|
|
109
|
+
/**
|
|
110
|
+
* The merchant's values for this theme's settings, by setting key, exactly
|
|
111
|
+
* as saved — `{}` when there are none or the client was created without
|
|
112
|
+
* `theme`, absent from a server that predates theme settings. Read them
|
|
113
|
+
* against the theme's declaration with `resolveThemeSettings` from
|
|
114
|
+
* `@wocart/theme` (which takes either): a value the theme no longer accepts
|
|
115
|
+
* falls back to its default there.
|
|
116
|
+
*/
|
|
117
|
+
themeSettings?: Record<string, unknown> | undefined;
|
|
88
118
|
currency: string;
|
|
89
119
|
currencySymbol: string;
|
|
90
120
|
metaTitle: string | null;
|
|
91
121
|
metaDescription: string | null;
|
|
92
122
|
ogImage: string | null;
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Always null. An announcement is a theme's own content: declare
|
|
125
|
+
* it as a theme setting and read `themeSettings`. Removed in 2.0.
|
|
126
|
+
*/
|
|
93
127
|
announcementBar: string | null;
|
|
94
128
|
reviewsEnabled: boolean;
|
|
95
129
|
/** When true the storefront renders the cart alongside the direct order form. */
|
|
@@ -178,6 +212,25 @@ export interface StorePagePublic {
|
|
|
178
212
|
metaTitle: string | null;
|
|
179
213
|
metaDescription: string | null;
|
|
180
214
|
}
|
|
215
|
+
/** A navigation menu — GET /store/menus/{handle}. */
|
|
216
|
+
export interface StoreMenu {
|
|
217
|
+
handle: string;
|
|
218
|
+
title: string;
|
|
219
|
+
/** Only items a shopper can follow, in the merchant's order; at most three levels. */
|
|
220
|
+
items: StoreMenuItem[];
|
|
221
|
+
}
|
|
222
|
+
export interface StoreMenuItem {
|
|
223
|
+
/** In the client's language when the store translated it, else the primary. */
|
|
224
|
+
label: string;
|
|
225
|
+
/**
|
|
226
|
+
* A path of the store without a language prefix (`/products/robe`) — pass
|
|
227
|
+
* it through `localizeHref` — or the absolute URL the merchant entered.
|
|
228
|
+
*/
|
|
229
|
+
href: string;
|
|
230
|
+
/** The page type it opens (`home`, `catalog`, `category`, `product`, `page`, `landing`), or `url`. */
|
|
231
|
+
type: string;
|
|
232
|
+
children: StoreMenuItem[];
|
|
233
|
+
}
|
|
181
234
|
export interface ShippingRates {
|
|
182
235
|
[wilayaId: string]: WilayaRates;
|
|
183
236
|
}
|
|
@@ -466,6 +519,16 @@ export interface ReviewSubmitResult {
|
|
|
466
519
|
*/
|
|
467
520
|
export interface ProductListFilters {
|
|
468
521
|
categoryId?: string;
|
|
522
|
+
/**
|
|
523
|
+
* Only this collection's products, by handle (`best-sellers`), in the order
|
|
524
|
+
* the merchant gave them. A collection is a list the merchant curates in the
|
|
525
|
+
* dashboard; a theme names it through a `collection` theme setting. An
|
|
526
|
+
* unknown handle lists nothing.
|
|
527
|
+
*/
|
|
528
|
+
collection?: string;
|
|
529
|
+
/** Only products priced below their compare-at price. */
|
|
530
|
+
onSale?: boolean;
|
|
531
|
+
/** @deprecated Use `collection: "featured"`. */
|
|
469
532
|
featured?: boolean;
|
|
470
533
|
/** Server-side cap; the server clamps anything larger. */
|
|
471
534
|
limit?: number;
|
package/package.json
CHANGED