@wocart/storefront 1.8.0 → 1.9.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 CHANGED
@@ -5,6 +5,18 @@ 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.9.0 — 2026-10-17
9
+
10
+ ### Added
11
+
12
+ - Page type `collection` at `/collections/:handle`: a collection's page,
13
+ which a merchant's menu can now link to. `storefrontPath("collection", {
14
+ handle })` builds it, `matchStorefrontPath` recognises it, and the WhatsApp
15
+ widget counts it as the catalog surface.
16
+ - `store.collections.get(handle)` → `StoreCollection` (handle, title,
17
+ description, in the client's language), `null` for an unknown handle, which
18
+ is not reported as a failure. `StoreCollectionSchema`.
19
+
8
20
  ## 1.8.0 — 2026-10-17
9
21
 
10
22
  ### Added
package/USAGE.md CHANGED
@@ -105,6 +105,7 @@ break those links.
105
105
  | `home` | `/` | the home page | required |
106
106
  | `catalog` | `/products` | every product | required |
107
107
  | `category` | `/category/:slug` | one category's products | required when the store has categories |
108
+ | `collection` | `/collections/:handle` | one collection's products, in the merchant's order | required — a menu can link to any collection |
108
109
  | `product` | `/products/:handle` | one product (its public handle, never an id) | required |
109
110
  | `page` | `/pages/:slug` | a legal or merchant page | required — checkout consent links to the store's legal pages |
110
111
  | `landing` | `/lp/:slug` | a landing page | required when the store runs landing pages |
@@ -651,6 +652,11 @@ const onSale = await store.products.list({ onSale: true, limit: 8 });
651
652
  - An empty or unknown collection lists nothing — show nothing, never the
652
653
  whole catalog instead: "Best sellers" means the merchant's picks.
653
654
  - `onSale` is computed: a product priced below its compare-at price.
655
+ - A collection has a page of its own (page type `collection`,
656
+ `/collections/:handle`) that a merchant's menu can link to. Name it with
657
+ `store.collections.get(handle)` — title and description in the client's
658
+ language, `null` for an unknown handle (answer a real 404) — and list it
659
+ with `products.list({ collection: handle })`.
654
660
  - `featured: true` and `ProductList.storeFeatured` are deprecated: they mean
655
661
  the `featured` collection.
656
662
 
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Collections Namespace - the merchant's curated product lists
3
+ *
4
+ * A collection has a page of its own (`/collections/:handle`, page type
5
+ * `collection`): `get(handle)` names it — title and description in the
6
+ * client's language — and `products.list({ collection })` lists its products
7
+ * in the merchant's order.
8
+ *
9
+ * Failure contract: `get(handle)` → null means "no such collection" or a
10
+ * failed read — the page answers a real 404.
11
+ */
12
+ import { type ClientCore } from "./core.js";
13
+ import type { StoreCollection } from "../types.js";
14
+ export interface CollectionsNamespace {
15
+ /** A collection by handle (`best-sellers`). Resolves to `null` for an unknown handle or a failed read. */
16
+ get(handle: string): Promise<StoreCollection | null>;
17
+ }
18
+ export declare function createCollectionsNamespace(core: ClientCore): CollectionsNamespace;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Collections Namespace - the merchant's curated product lists
3
+ *
4
+ * A collection has a page of its own (`/collections/:handle`, page type
5
+ * `collection`): `get(handle)` names it — title and description in the
6
+ * client's language — and `products.list({ collection })` lists its products
7
+ * in the merchant's order.
8
+ *
9
+ * Failure contract: `get(handle)` → null means "no such collection" or a
10
+ * failed read — the page answers a real 404.
11
+ */
12
+ import { StoreCollectionSchema } from "../schemas.js";
13
+ import { STOREFRONT_OPERATIONS } from "../domain.js";
14
+ import { localizedPath } from "./paths.js";
15
+ import { isNotFoundAnswer, reportDegradedRead } from "./core.js";
16
+ export function createCollectionsNamespace(core) {
17
+ return {
18
+ async get(handle) {
19
+ try {
20
+ return await core.get(localizedPath(core.config, `/store/collections/${encodeURIComponent(handle)}`), StoreCollectionSchema, { operation: STOREFRONT_OPERATIONS.collectionsGet });
21
+ }
22
+ catch (error) {
23
+ if (!isNotFoundAnswer(error))
24
+ reportDegradedRead(core.config, error, "collections.get");
25
+ return null;
26
+ }
27
+ },
28
+ };
29
+ }
@@ -2,7 +2,8 @@
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, menus, shipping, geo, cart,
5
+ * shared core: config, products, categories, collections, pages, menus,
6
+ * shipping, geo, cart,
6
7
  * orders. The core is internal — consumers see only namespaces.
7
8
  *
8
9
  * One core means one connection story for every namespace: every read is
@@ -32,6 +33,7 @@ export declare function createStoreClient(config: StoreScope): {
32
33
  categories: import("./categories.js").CategoriesNamespace;
33
34
  pages: import("./pages.js").PagesNamespace;
34
35
  menus: import("./menus.js").MenusNamespace;
36
+ collections: import("./collections.js").CollectionsNamespace;
35
37
  pageData: import("./page-data.js").PageDataNamespace;
36
38
  shipping: import("./shipping.js").ShippingNamespace;
37
39
  geo: import("./geo.js").GeoNamespace;
@@ -2,7 +2,8 @@
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, menus, shipping, geo, cart,
5
+ * shared core: config, products, categories, collections, pages, menus,
6
+ * shipping, geo, cart,
6
7
  * orders. The core is internal — consumers see only namespaces.
7
8
  *
8
9
  * One core means one connection story for every namespace: every read is
@@ -32,6 +33,7 @@ import { createProductsNamespace } from "./products.js";
32
33
  import { createCategoriesNamespace } from "./categories.js";
33
34
  import { createPagesNamespace } from "./pages.js";
34
35
  import { createMenusNamespace } from "./menus.js";
36
+ import { createCollectionsNamespace } from "./collections.js";
35
37
  import { createPageDataNamespace } from "./page-data.js";
36
38
  import { createShippingNamespace } from "./shipping.js";
37
39
  import { createGeoNamespace } from "./geo.js";
@@ -46,6 +48,7 @@ export function createStoreClient(config) {
46
48
  categories: createCategoriesNamespace(core),
47
49
  pages: createPagesNamespace(core),
48
50
  menus: createMenusNamespace(core),
51
+ collections: createCollectionsNamespace(core),
49
52
  pageData: createPageDataNamespace(core),
50
53
  shipping: createShippingNamespace(core),
51
54
  geo: createGeoNamespace(core),
package/dist/domain.d.ts CHANGED
@@ -354,6 +354,7 @@ export declare const STOREFRONT_OPERATIONS: {
354
354
  readonly pagesGet: "pages.get";
355
355
  readonly pagesLanding: "pages.landing";
356
356
  readonly menusGet: "menus.get";
357
+ readonly collectionsGet: "collections.get";
357
358
  readonly shippingRates: "shipping.rates";
358
359
  readonly shippingRate: "shipping.rate";
359
360
  readonly geoCommunes: "geo.communes";
@@ -392,6 +393,7 @@ export declare const STOREFRONT_RETRY_POLICIES: {
392
393
  readonly "pages.get": "safe-read";
393
394
  readonly "pages.landing": "safe-read";
394
395
  readonly "menus.get": "safe-read";
396
+ readonly "collections.get": "safe-read";
395
397
  readonly "products.get": "safe-read";
396
398
  readonly "products.list": "safe-read";
397
399
  readonly "products.reviews.list": "safe-read";
package/dist/domain.js CHANGED
@@ -207,6 +207,7 @@ export const STOREFRONT_OPERATIONS = {
207
207
  pagesGet: "pages.get",
208
208
  pagesLanding: "pages.landing",
209
209
  menusGet: "menus.get",
210
+ collectionsGet: "collections.get",
210
211
  shippingRates: "shipping.rates",
211
212
  shippingRate: "shipping.rate",
212
213
  geoCommunes: "geo.communes",
@@ -244,6 +245,7 @@ export const STOREFRONT_RETRY_POLICIES = {
244
245
  [STOREFRONT_OPERATIONS.pagesGet]: "safe-read",
245
246
  [STOREFRONT_OPERATIONS.pagesLanding]: "safe-read",
246
247
  [STOREFRONT_OPERATIONS.menusGet]: "safe-read",
248
+ [STOREFRONT_OPERATIONS.collectionsGet]: "safe-read",
247
249
  [STOREFRONT_OPERATIONS.productsGet]: "safe-read",
248
250
  [STOREFRONT_OPERATIONS.productsList]: "safe-read",
249
251
  [STOREFRONT_OPERATIONS.productsReviewsList]: "safe-read",
package/dist/routes.d.ts CHANGED
@@ -20,6 +20,7 @@ export declare const STOREFRONT_PAGE_PATHS: {
20
20
  readonly home: "/";
21
21
  readonly catalog: "/products";
22
22
  readonly category: "/category/:slug";
23
+ readonly collection: "/collections/:handle";
23
24
  readonly product: "/products/:handle";
24
25
  readonly page: "/pages/:slug";
25
26
  readonly landing: "/lp/:slug";
package/dist/routes.js CHANGED
@@ -3,6 +3,7 @@ export const STOREFRONT_PAGE_PATHS = {
3
3
  home: "/",
4
4
  catalog: "/products",
5
5
  category: "/category/:slug",
6
+ collection: "/collections/:handle",
6
7
  product: "/products/:handle",
7
8
  page: "/pages/:slug",
8
9
  landing: "/lp/:slug",
@@ -58,6 +59,7 @@ const WIDGET_SURFACE = {
58
59
  home: "home",
59
60
  catalog: "catalog",
60
61
  category: "catalog",
62
+ collection: "catalog",
61
63
  product: "product",
62
64
  page: "pages",
63
65
  landing: "landing",
package/dist/schemas.d.ts CHANGED
@@ -246,6 +246,12 @@ interface StoreMenuItemShape {
246
246
  type: string;
247
247
  children: StoreMenuItemShape[];
248
248
  }
249
+ /** A collection's page — GET /store/collections/{handle}. Its products come from `products.list({ collection })`. */
250
+ export declare const StoreCollectionSchema: z.ZodObject<{
251
+ handle: z.ZodString;
252
+ title: z.ZodString;
253
+ description: z.ZodNullable<z.ZodString>;
254
+ }, z.core.$strip>;
249
255
  export declare const StoreMenuSchema: z.ZodObject<{
250
256
  handle: z.ZodString;
251
257
  title: z.ZodString;
package/dist/schemas.js CHANGED
@@ -145,6 +145,12 @@ export const StoreMenuItemSchema = z.lazy(() => z.object({
145
145
  type: z.string(),
146
146
  children: z.array(StoreMenuItemSchema),
147
147
  }));
148
+ /** A collection's page — GET /store/collections/{handle}. Its products come from `products.list({ collection })`. */
149
+ export const StoreCollectionSchema = z.object({
150
+ handle: z.string(),
151
+ title: z.string(),
152
+ description: z.string().nullable(),
153
+ });
148
154
  export const StoreMenuSchema = z.object({
149
155
  handle: z.string(),
150
156
  title: z.string(),
package/dist/types.d.ts CHANGED
@@ -212,6 +212,13 @@ export interface StorePagePublic {
212
212
  metaTitle: string | null;
213
213
  metaDescription: string | null;
214
214
  }
215
+ /** A collection as its page names it — GET /store/collections/{handle}. */
216
+ export interface StoreCollection {
217
+ handle: string;
218
+ /** In the client's language when the store translated it, else the primary. */
219
+ title: string;
220
+ description: string | null;
221
+ }
215
222
  /** A navigation menu — GET /store/menus/{handle}. */
216
223
  export interface StoreMenu {
217
224
  handle: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wocart/storefront",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Typed, validated SDK for Wocart cash-on-delivery storefronts: catalog reads, basket, checkout, OTP and order tracking, with Astro adapters.",
5
5
  "keywords": [
6
6
  "wocart",