@wocart/storefront 1.23.0 → 2.0.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 +41 -0
- package/README.md +22 -117
- package/USAGE.md +31 -70
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/schemas.d.ts +84 -35
- package/dist/schemas.js +17 -12
- package/dist/seo.js +1 -1
- package/dist/types.d.ts +7 -7
- package/dist/utils/image.d.ts +50 -169
- package/dist/utils/image.js +72 -165
- package/dist/utils/product-image.d.ts +6 -6
- package/dist/utils/product-image.js +7 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ 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
|
+
## 2.0.0 — 2026-10-30
|
|
9
|
+
|
|
10
|
+
A store's images can live on any media host. The server answers every image
|
|
11
|
+
as a `MediaImage` and the store's config says how its images are delivered;
|
|
12
|
+
the image helpers take that delivery instead of a media domain. Requires a
|
|
13
|
+
store server that answers this shape.
|
|
14
|
+
|
|
15
|
+
### Changed (breaking)
|
|
16
|
+
|
|
17
|
+
- Every image is a `MediaImage`, `{ key, url, width, height }`: a product
|
|
18
|
+
image (`ProductImageSchema`) and a landing image carry `key` and `url`
|
|
19
|
+
(`src`, `srcSm`, `srcMd`, `srcLg` are gone); a category has `image` (was
|
|
20
|
+
`imageUrl`); `StoreConfig` has `logo` (was `logoUrl`) and `ogImage` as
|
|
21
|
+
images.
|
|
22
|
+
- `StoreConfig.media` (`MediaDelivery`, `{ kind, origin }`): where every key is
|
|
23
|
+
served from, and how it is resized — `imagekit`, `cloudflare`, or `direct`.
|
|
24
|
+
- `mediaUrl(key, delivery)`, `mediaImageUrl(url, params, delivery)`,
|
|
25
|
+
`mediaImageSrcset(url, widths, { delivery })` and `pictureOf(image,
|
|
26
|
+
delivery)` take the store's delivery (was a media domain). `mediaImageUrl`
|
|
27
|
+
builds ImageKit's `?tr=` or Cloudflare's `/cdn-cgi/image/`, and answers
|
|
28
|
+
`null` for a `direct` delivery.
|
|
29
|
+
- `productImageSource` reads `url`.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `optimizeImage`, `generateSrcSet`, `IMAGE_SIZES`, `getCardImage`,
|
|
34
|
+
`getGalleryImage`, `getThumbnailImage`, `getHeroImage`, `getCategoryImage`,
|
|
35
|
+
`getLogoImage`, `createImageHelpers` and their types: each took a media
|
|
36
|
+
domain, which no longer exists. Use `mediaImageUrl` and `mediaImageSrcset`
|
|
37
|
+
with the size a page shows. They are removed without a deprecating minor
|
|
38
|
+
because no media domain is left for them to take.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `isMediaKey`, `MediaImage`, `MediaDelivery`, `MediaDeliveryKind`,
|
|
43
|
+
`MediaImageSchema` and `MediaDeliverySchema`.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- The README is short and points to the documentation at docs.wocart.store.
|
|
48
|
+
|
|
8
49
|
## 1.23.0 — 2026-10-25
|
|
9
50
|
|
|
10
51
|
One checkout, one order: a double click, a refreshed submission or a retry
|
package/README.md
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# @wocart/storefront
|
|
2
2
|
|
|
3
3
|
The storefront SDK for [Wocart](https://github.com/bighadj22/codflow), the
|
|
4
|
-
cash-on-delivery commerce platform for Algeria. It gives a storefront
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
server.
|
|
4
|
+
cash-on-delivery commerce platform for Algeria. It gives a storefront typed,
|
|
5
|
+
validated access to a store's catalog, delivery prices, basket, checkout, OTP,
|
|
6
|
+
order tracking and images — and keeps prices, rules and secrets on the server.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`{}` and report through `onError`. Writes return a result whose failure always
|
|
13
|
-
carries a structured `DomainError`.
|
|
14
|
-
- **Browser-safe by construction.** The root entry exports no client and no
|
|
15
|
-
secret; the store API key lives behind `@wocart/storefront/server`.
|
|
16
|
-
- **Framework-neutral core, Astro adapters.** Proxy routes, form actions and
|
|
17
|
-
the POST/Redirect/GET middleware decision for Astro, with no Astro import.
|
|
8
|
+
**Documentation: [docs.wocart.store/sdk/storefront](https://docs.wocart.store/sdk/storefront)**
|
|
9
|
+
— also in [French](https://docs.wocart.store/fr/sdk/storefront) and
|
|
10
|
+
[Arabic](https://docs.wocart.store/ar/sdk/storefront).
|
|
18
11
|
|
|
19
12
|
## Install
|
|
20
13
|
|
|
@@ -22,123 +15,35 @@ server.
|
|
|
22
15
|
npm install @wocart/storefront zod
|
|
23
16
|
```
|
|
24
17
|
|
|
25
|
-
`zod`
|
|
26
|
-
can validate with them, and you pass your own schemas to `createProxyEndpoint` —
|
|
27
|
-
so your app and the SDK share one copy. npm 7 and later install it
|
|
28
|
-
automatically. ESM only. Runs in browsers, Cloudflare Workers, Node and Astro server
|
|
29
|
-
runtimes; tooling needs Node 22.12 or newer.
|
|
18
|
+
`zod` 4 is a peer dependency. ESM only; Node 22.12 or newer for tooling.
|
|
30
19
|
|
|
31
20
|
## Quick start
|
|
32
21
|
|
|
33
22
|
```ts
|
|
34
23
|
import { createStoreClient } from "@wocart/storefront/server";
|
|
24
|
+
import { mediaImageUrl } from "@wocart/storefront";
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
const store = createStoreClient({
|
|
38
|
-
serverUrl: env.COD_SERVER_URL, // your store's API origin
|
|
39
|
-
apiKey: env.STORE_API_KEY, // server-only secret
|
|
40
|
-
onError: (error) => console.warn(error.operation, error.code, error.requestId),
|
|
41
|
-
});
|
|
26
|
+
const store = createStoreClient({ serverUrl: env.COD_SERVER_URL, apiKey: env.STORE_API_KEY });
|
|
42
27
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
]);
|
|
48
|
-
const product = await store.products.get("blue-t-shirt"); // Product | null
|
|
49
|
-
|
|
50
|
-
// Writes: a discriminated result, never a thrown HTTP error.
|
|
51
|
-
const result = await store.orders.create({
|
|
52
|
-
customerName: "أمين",
|
|
53
|
-
phone: "0555123456",
|
|
54
|
-
wilayaId: 16,
|
|
55
|
-
communeId: "c-16-001",
|
|
56
|
-
items: [{ productId: "p-1", productName: "Blue T-Shirt", quantity: 1 }],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
if (result.success) {
|
|
60
|
-
console.log(result.data.orderNumber, result.data.total);
|
|
61
|
-
} else {
|
|
62
|
-
console.error(result.domainError.category, result.domainError.message);
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Entry points
|
|
67
|
-
|
|
68
|
-
| Import | Runs in | What it holds |
|
|
69
|
-
| --- | --- | --- |
|
|
70
|
-
| `@wocart/storefront` | browser and server | read-model types and schemas, reference data (58 wilayas, 1,551 communes), money and image helpers, the basket and OTP state machines, language routing, Meta pixel payloads |
|
|
71
|
-
| `@wocart/storefront/server` | server only | `createStoreClient`, `StoreClient`, `ClientResult`, `StorefrontError` — the API key lives here — the preview checks (`readPreviewRequest`, `verifyPreviewToken`, `previewResponseHeaders`, `previewConfig`) and revalidation (`readRevalidateRequest`) |
|
|
72
|
-
| `@wocart/storefront/astro` | server only | `createStorefrontRoutes`, `createProxyEndpoint`, `placeOrderInput`, `executePlaceOrder`, `collectForwardedHeaders`, `runFormAction` |
|
|
73
|
-
|
|
74
|
-
Browser code never receives the key. It calls your own same-origin routes
|
|
75
|
-
(for example `src/pages/api/cart.ts`), which call the SDK on the server:
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
// In the browser: no client, no key.
|
|
79
|
-
import { createBasketStore, formatMoney } from "@wocart/storefront";
|
|
28
|
+
const config = await store.config.get();
|
|
29
|
+
const product = await store.products.get("blue-t-shirt");
|
|
30
|
+
const photo = product?.images[0];
|
|
31
|
+
const src = photo && config ? mediaImageUrl(photo.url, { width: 480, fit: "cover", format: "auto" }, config.media) ?? photo.url : null;
|
|
80
32
|
```
|
|
81
33
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
| Namespace | Use it for |
|
|
85
|
-
| --- | --- |
|
|
86
|
-
| `config` | store identity, branding, languages, checkout policy, feature flags |
|
|
87
|
-
| `products` | catalog lists, product detail, reviews |
|
|
88
|
-
| `categories` | catalog navigation |
|
|
89
|
-
| `pages` | legal and CMS pages, landing pages |
|
|
90
|
-
| `pageData` | the store's saved page document for a surface (render it with [`@wocart/theme`](https://www.npmjs.com/package/@wocart/theme)) |
|
|
91
|
-
| `shipping` | home and stop-desk delivery prices per wilaya |
|
|
92
|
-
| `geo` | wilayas and communes |
|
|
93
|
-
| `cart` | server re-pricing and validation of a browser basket |
|
|
94
|
-
| `orders` | order creation, OTP, abandoned checkout, conversion tracking |
|
|
95
|
-
|
|
96
|
-
## How errors work
|
|
34
|
+
Reads never throw: they resolve to `null`, `[]` or `{}` and report through
|
|
35
|
+
`onError`. Writes return `{ success, data }` or a structured `domainError`.
|
|
97
36
|
|
|
98
|
-
|
|
99
|
-
renders, and report the cause to `onError` as a `DomainError` with
|
|
100
|
-
`category`, `code`, `retryable`, `sideEffect`, `operation` and `requestId`.
|
|
101
|
-
- **Writes** return `ClientResult<T>`: `{ success: true, data }` or
|
|
102
|
-
`{ success: false, error, domainError }`. Branch on
|
|
103
|
-
`result.domainError.category` — `validation`, `authentication`, `business`,
|
|
104
|
-
`system`, `transport` — instead of status codes.
|
|
105
|
-
- **Setup errors are loud.** An empty `serverUrl` or `apiKey` throws
|
|
106
|
-
`MISSING_CONFIGURATION` when the client is created, instead of quietly
|
|
107
|
-
turning every read into an empty store.
|
|
108
|
-
|
|
109
|
-
## Configuration
|
|
37
|
+
## Entry points
|
|
110
38
|
|
|
111
|
-
|
|
|
39
|
+
| Import | Runs in | Holds |
|
|
112
40
|
| --- | --- | --- |
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `theme` | no | your theme's key (`defineTheme({ key })`), to receive the merchant's edits to your theme's text |
|
|
117
|
-
| `onError` | no | observability hook for degraded reads |
|
|
118
|
-
| `readTimeoutMs` | no | how long one read may take, retries included, before it degrades (default 10 000; `0` for no limit) |
|
|
119
|
-
|
|
120
|
-
A storefront deployed by Wocart receives its origin and key as the
|
|
121
|
-
`COD_SERVER_URL` and `STORE_API_KEY` environment variables. The SDK reads no
|
|
122
|
-
environment variables itself; pass the values in. In Astro, declare the key as
|
|
123
|
-
a server secret (`envField.string({ context: "server", access: "secret" })`)
|
|
124
|
-
and never expose it as a public variable.
|
|
125
|
-
|
|
126
|
-
## Documentation
|
|
127
|
-
|
|
128
|
-
- [USAGE.md](./USAGE.md) — the full guide: every namespace and the UI decisions
|
|
129
|
-
it implies, checkout and OTP, Astro actions and proxy routes, languages,
|
|
130
|
-
images, and a definition of done for a new theme.
|
|
131
|
-
- [CHANGELOG.md](./CHANGELOG.md) — what changed in each release.
|
|
132
|
-
|
|
133
|
-
## Compatibility
|
|
41
|
+
| `@wocart/storefront` | browser and server | types and schemas, reference data, money and image helpers, basket and OTP state |
|
|
42
|
+
| `@wocart/storefront/server` | server only | `createStoreClient` — the store API key lives here — preview and revalidation checks |
|
|
43
|
+
| `@wocart/storefront/astro` | server only | proxy routes, form actions, `placeOrderInput` |
|
|
134
44
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
- Response schemas ignore fields they do not know, so a newer server never
|
|
138
|
-
breaks an older SDK; a response missing a required field degrades the read
|
|
139
|
-
and reports it.
|
|
140
|
-
- Astro 7 is verified. The Astro adapters are structural and import nothing
|
|
141
|
-
from Astro.
|
|
45
|
+
[USAGE.md](./USAGE.md) ships with the package for offline reading;
|
|
46
|
+
[CHANGELOG.md](./CHANGELOG.md) lists every release.
|
|
142
47
|
|
|
143
48
|
## License
|
|
144
49
|
|
package/USAGE.md
CHANGED
|
@@ -214,10 +214,11 @@ server directly.
|
|
|
214
214
|
|
|
215
215
|
| Export | Use it for |
|
|
216
216
|
| --- | --- |
|
|
217
|
-
| `formatMoney
|
|
218
|
-
| `mediaImageUrl` |
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
217
|
+
| `formatMoney` | Money text |
|
|
218
|
+
| `mediaImageUrl(url, params, config.media)` | A store image at the size and format a page shows it, from the store's delivery (ImageKit `?tr=` or Cloudflare `/cdn-cgi/image/`); `null` for an image the delivery does not serve, or a `direct` delivery — the builder behind `@wocart/theme/image-service` |
|
|
219
|
+
| `mediaImageSrcset(url, widths, { delivery })` | The same image at several widths, for `srcset` |
|
|
220
|
+
| `mediaUrl(key, config.media)`, `isMediaKey` | The URL of a Media Key — what a theme's `image` field holds (`{ key: "products/…" }`); `null` without a delivery or for a value that is not a key |
|
|
221
|
+
| `pictureOf(image, config.media)` | A picked image ready for an `<img>`: URL, alt, size, focal point |
|
|
221
222
|
| `WILAYAS`, `ALGERIA_COMMUNES`, `communesForWilaya` | Reference data: 58 wilayas, 1,551 communes, one wilaya's rows |
|
|
222
223
|
| `isWilayaId` | Whether a value is a wilaya id — the range `WilayaIdSchema` checks on the server, without a schema in the browser |
|
|
223
224
|
| `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 |
|
|
@@ -290,7 +291,6 @@ storefront deployed by Wocart receives these:
|
|
|
290
291
|
| --- | --- | --- |
|
|
291
292
|
| `COD_SERVER_URL` | server variable | the store's API origin, e.g. `https://api.your-store.com` |
|
|
292
293
|
| `STORE_API_KEY` | server secret | the store API key |
|
|
293
|
-
| `MEDIA_DOMAIN` | server variable (optional) | media domain for `createImageHelpers(mediaDomain)`; unset → image URLs pass through unchanged |
|
|
294
294
|
|
|
295
295
|
Never put `STORE_API_KEY` in a browser-exposed variable (no `PUBLIC_*`, no
|
|
296
296
|
`import.meta.env` in client code, no HTML attribute). In Astro, declare it in
|
|
@@ -518,11 +518,13 @@ const currencySymbol = config?.currencySymbol ?? "دج";
|
|
|
518
518
|
StoreConfig contains the current store identity and presentation fields,
|
|
519
519
|
including:
|
|
520
520
|
|
|
521
|
-
- name, domain,
|
|
521
|
+
- name, domain, logo (a MediaImage) and themeId (the colors, fontFamily and fontUrl
|
|
522
522
|
fields are deprecated: colors and fonts are theme settings, read from
|
|
523
523
|
themeSettings);
|
|
524
524
|
- lang (the primary language), locales, contentOverrides, themeSettings,
|
|
525
|
-
currency, currencySymbol, metaTitle, metaDescription, and ogImage
|
|
525
|
+
currency, currencySymbol, metaTitle, metaDescription, and ogImage (a
|
|
526
|
+
MediaImage);
|
|
527
|
+
- media, the store's MediaDelivery, which every image helper takes;
|
|
526
528
|
- reviewsEnabled (announcementBar is deprecated and always null: an
|
|
527
529
|
announcement is a theme's own setting);
|
|
528
530
|
- cartEnabled, freeShippingThreshold, and cartShippingMode;
|
|
@@ -1827,53 +1829,46 @@ price presentation does not drift between cards, product pages, and checkout.
|
|
|
1827
1829
|
|
|
1828
1830
|
### Images
|
|
1829
1831
|
|
|
1832
|
+
Every image the store's server answers is a `MediaImage` — `{ key, url,
|
|
1833
|
+
width, height }` — and the store's config carries `media`, its
|
|
1834
|
+
`MediaDelivery`: the origin every key is served from and how that origin
|
|
1835
|
+
resizes (`imagekit`, `cloudflare`, or `direct` for files served as stored).
|
|
1836
|
+
Pass `config.media` to the image helpers; one theme then works on every host a
|
|
1837
|
+
store can use.
|
|
1838
|
+
|
|
1830
1839
|
~~~ts
|
|
1831
|
-
import {
|
|
1832
|
-
generateSrcSet,
|
|
1833
|
-
getCardImage,
|
|
1834
|
-
optimizeImage,
|
|
1835
|
-
} from "@wocart/storefront";
|
|
1840
|
+
import { mediaImageUrl } from "@wocart/storefront";
|
|
1836
1841
|
|
|
1837
|
-
const
|
|
1838
|
-
const
|
|
1839
|
-
const cardImage = getCardImage(originalImage, mediaDomain);
|
|
1840
|
-
const src = optimizeImage(originalImage, {
|
|
1841
|
-
width: 480,
|
|
1842
|
-
height: 480,
|
|
1843
|
-
quality: 82,
|
|
1844
|
-
format: "auto",
|
|
1845
|
-
fit: "cover",
|
|
1846
|
-
}, mediaDomain);
|
|
1847
|
-
const srcSet = generateSrcSet(originalImage, 480, 480, mediaDomain);
|
|
1842
|
+
const photo = product.images[0];
|
|
1843
|
+
const card = photo ? mediaImageUrl(photo.url, { width: 480, height: 480, fit: "cover", format: "auto", quality: 82 }, config.media) ?? photo.url : null;
|
|
1848
1844
|
~~~
|
|
1849
1845
|
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
original assets into every card.
|
|
1846
|
+
`mediaImageUrl` answers `null` for an image its delivery does not serve and
|
|
1847
|
+
for a `direct` delivery, which does not resize: show the original then.
|
|
1853
1848
|
|
|
1854
1849
|
For an image whose width changes with the screen, give the browser widths and
|
|
1855
1850
|
let `sizes` choose — a phone then downloads a phone-sized file.
|
|
1856
|
-
`mediaImageSrcset(src, widths, {
|
|
1857
|
-
|
|
1858
|
-
|
|
1851
|
+
`mediaImageSrcset(src, widths, { delivery, ratio, quality })` gives one size
|
|
1852
|
+
per width with width descriptors, cropped to `ratio` (height ÷ width) when
|
|
1853
|
+
given and never enlarged past the original otherwise:
|
|
1859
1854
|
|
|
1860
1855
|
~~~astro
|
|
1861
1856
|
<img
|
|
1862
|
-
src={mediaImageUrl(src, { width: 720, format: "auto" },
|
|
1863
|
-
srcset={mediaImageSrcset(src, [360, 540, 720, 1080], {
|
|
1857
|
+
src={mediaImageUrl(src, { width: 720, fit: "scale-down", format: "auto" }, config.media) ?? src}
|
|
1858
|
+
srcset={mediaImageSrcset(src, [360, 540, 720, 1080], { delivery: config.media, ratio: 1.25 }) ?? undefined}
|
|
1864
1859
|
sizes="(min-width: 1024px) 25vw, 50vw"
|
|
1865
1860
|
alt="" loading="lazy" />
|
|
1866
1861
|
~~~
|
|
1867
1862
|
|
|
1868
|
-
A photo a merchant picks for a section is stored as a
|
|
1869
|
-
text, size and focal point. `pictureOf(image,
|
|
1863
|
+
A photo a merchant picks for a section is stored as a Media Key with its alt
|
|
1864
|
+
text, size and focal point. `pictureOf(image, config.media)` turns it into what
|
|
1870
1865
|
an `<img>` needs — `src`, `alt` (empty when none was written), `width`,
|
|
1871
1866
|
`height` and `position`, the focal point as CSS `object-position` — or null
|
|
1872
1867
|
when none is picked:
|
|
1873
1868
|
|
|
1874
1869
|
~~~astro
|
|
1875
1870
|
---
|
|
1876
|
-
const picture = pictureOf(props.image,
|
|
1871
|
+
const picture = pictureOf(props.image, config.media);
|
|
1877
1872
|
---
|
|
1878
1873
|
{picture && <img src={picture.src} alt={picture.alt} width={picture.width} height={picture.height} style={{ objectPosition: picture.position }} />}
|
|
1879
1874
|
~~~
|
|
@@ -1886,10 +1881,10 @@ thumbnail. `productImageSource` answers which image represents the product, so
|
|
|
1886
1881
|
a cart drawer, a checkout summary and a product card cannot disagree:
|
|
1887
1882
|
|
|
1888
1883
|
~~~ts
|
|
1889
|
-
import {
|
|
1884
|
+
import { mediaImageUrl, productImageSource } from "@wocart/storefront";
|
|
1890
1885
|
|
|
1891
1886
|
const src = productImageSource(product); // Product | ProductList
|
|
1892
|
-
const thumbnail = src ?
|
|
1887
|
+
const thumbnail = src ? mediaImageUrl(src, { width: 160, height: 160, fit: "cover", format: "auto" }, config.media) ?? src : null;
|
|
1893
1888
|
~~~
|
|
1894
1889
|
|
|
1895
1890
|
It prefers the merchant's cover and falls back to the first usable gallery
|
|
@@ -1897,40 +1892,6 @@ image; `null` means the product genuinely has no photograph, so that is the
|
|
|
1897
1892
|
moment to render a placeholder. The list model carries no gallery at all, so a
|
|
1898
1893
|
card gets the cover or `null`.
|
|
1899
1894
|
|
|
1900
|
-
Available semantic helpers include:
|
|
1901
|
-
|
|
1902
|
-
- getCardImage;
|
|
1903
|
-
- getGalleryImage;
|
|
1904
|
-
- getThumbnailImage;
|
|
1905
|
-
- getHeroImage;
|
|
1906
|
-
- getCategoryImage;
|
|
1907
|
-
- getLogoImage.
|
|
1908
|
-
|
|
1909
|
-
The helper chooses the appropriate image role. The theme still owns layout,
|
|
1910
|
-
aspect ratio, alt text, lazy loading, placeholders, and art direction.
|
|
1911
|
-
|
|
1912
|
-
#### Bind the media domain once
|
|
1913
|
-
|
|
1914
|
-
Do not thread the media domain through every call site — a missed argument
|
|
1915
|
-
silently ships an unoptimized image. Bind it once and use the URL-only helper
|
|
1916
|
-
set:
|
|
1917
|
-
|
|
1918
|
-
~~~ts
|
|
1919
|
-
import { createImageHelpers } from "@wocart/storefront";
|
|
1920
|
-
|
|
1921
|
-
const images = createImageHelpers(env.MEDIA_DOMAIN);
|
|
1922
|
-
|
|
1923
|
-
const thumbnail = images.getThumbnailImage(product.images[0].src);
|
|
1924
|
-
const preview = images.getCardImage(product.images[0].src);
|
|
1925
|
-
const srcSet = images.generateSrcSet(product.images[0].src, 480, 480);
|
|
1926
|
-
~~~
|
|
1927
|
-
|
|
1928
|
-
The returned toolkit exposes the same helpers plus `optimizeImage`,
|
|
1929
|
-
`generateSrcSet`, and `IMAGE_SIZES`. With no domain bound, every helper returns
|
|
1930
|
-
the original URL unchanged, so a store without a media domain needs no branch —
|
|
1931
|
-
and `optimizeImage` / `generateSrcSet` still take a per-call domain that
|
|
1932
|
-
overrides the bound one.
|
|
1933
|
-
|
|
1934
1895
|
## WhatsApp prefill messages
|
|
1935
1896
|
|
|
1936
1897
|
The storefront's WhatsApp launcher hands the shopper to WhatsApp with a message
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { formatMoney } from "./utils/money.js";
|
|
13
13
|
export type { MoneyFormatOptions } from "./utils/money.js";
|
|
14
|
-
export {
|
|
15
|
-
export type {
|
|
14
|
+
export { isMediaKey, mediaImageSrcset, mediaImageUrl, mediaUrl, pictureOf } from "./utils/image.js";
|
|
15
|
+
export type { MediaDelivery, MediaDeliveryKind, MediaImage, MediaImageParams, MediaImageSrcsetOptions, PickedImage, Picture } from "./utils/image.js";
|
|
16
16
|
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";
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Internal modules stay reachable only through their intentional entry points.
|
|
11
11
|
*/
|
|
12
12
|
export { formatMoney } from "./utils/money.js";
|
|
13
|
-
export {
|
|
13
|
+
export { isMediaKey, mediaImageSrcset, mediaImageUrl, mediaUrl, pictureOf } 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
16
|
export { STOREFRONT_PAGE_PATHS, STOREFRONT_ROUTES, matchStorefrontPath, showsWidgetOn, storefrontPath } from "./routes.js";
|
package/dist/schemas.d.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export declare const MediaImageSchema: z.ZodObject<{
|
|
3
|
+
key: z.ZodString;
|
|
4
|
+
url: z.ZodString;
|
|
5
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
6
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare const MediaDeliverySchema: z.ZodObject<{
|
|
9
|
+
kind: z.ZodEnum<{
|
|
10
|
+
cloudflare: "cloudflare";
|
|
11
|
+
direct: "direct";
|
|
12
|
+
imagekit: "imagekit";
|
|
13
|
+
}>;
|
|
14
|
+
origin: z.ZodString;
|
|
15
|
+
}, z.core.$strip>;
|
|
2
16
|
export declare const ProductImageSchema: z.ZodObject<{
|
|
17
|
+
key: z.ZodString;
|
|
18
|
+
url: z.ZodString;
|
|
19
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
20
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
3
21
|
id: z.ZodString;
|
|
4
|
-
src: z.ZodString;
|
|
5
|
-
srcSm: z.ZodNullable<z.ZodString>;
|
|
6
|
-
srcMd: z.ZodNullable<z.ZodString>;
|
|
7
|
-
srcLg: z.ZodNullable<z.ZodString>;
|
|
8
22
|
altText: z.ZodNullable<z.ZodString>;
|
|
9
23
|
position: z.ZodNumber;
|
|
10
24
|
}, z.core.$strip>;
|
|
@@ -64,7 +78,12 @@ export declare const CategorySchema: z.ZodObject<{
|
|
|
64
78
|
slug: z.ZodString;
|
|
65
79
|
description: z.ZodNullable<z.ZodString>;
|
|
66
80
|
parentId: z.ZodNullable<z.ZodString>;
|
|
67
|
-
|
|
81
|
+
image: z.ZodNullable<z.ZodObject<{
|
|
82
|
+
key: z.ZodString;
|
|
83
|
+
url: z.ZodString;
|
|
84
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
85
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
86
|
+
}, z.core.$strip>>;
|
|
68
87
|
metaTitle: z.ZodNullable<z.ZodString>;
|
|
69
88
|
metaDescription: z.ZodNullable<z.ZodString>;
|
|
70
89
|
metaKeywords: z.ZodNullable<z.ZodString>;
|
|
@@ -114,11 +133,11 @@ export declare const ProductListSchema: z.ZodObject<{
|
|
|
114
133
|
variantOptions: z.ZodNullable<z.ZodString>;
|
|
115
134
|
tags: z.ZodNullable<z.ZodString>;
|
|
116
135
|
coverImage: z.ZodNullable<z.ZodObject<{
|
|
136
|
+
key: z.ZodString;
|
|
137
|
+
url: z.ZodString;
|
|
138
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
139
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
117
140
|
id: z.ZodString;
|
|
118
|
-
src: z.ZodString;
|
|
119
|
-
srcSm: z.ZodNullable<z.ZodString>;
|
|
120
|
-
srcMd: z.ZodNullable<z.ZodString>;
|
|
121
|
-
srcLg: z.ZodNullable<z.ZodString>;
|
|
122
141
|
altText: z.ZodNullable<z.ZodString>;
|
|
123
142
|
position: z.ZodNumber;
|
|
124
143
|
}, z.core.$strip>>;
|
|
@@ -180,7 +199,12 @@ export declare const ProductSchema: z.ZodObject<{
|
|
|
180
199
|
slug: z.ZodString;
|
|
181
200
|
description: z.ZodNullable<z.ZodString>;
|
|
182
201
|
parentId: z.ZodNullable<z.ZodString>;
|
|
183
|
-
|
|
202
|
+
image: z.ZodNullable<z.ZodObject<{
|
|
203
|
+
key: z.ZodString;
|
|
204
|
+
url: z.ZodString;
|
|
205
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
206
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
207
|
+
}, z.core.$strip>>;
|
|
184
208
|
metaTitle: z.ZodNullable<z.ZodString>;
|
|
185
209
|
metaDescription: z.ZodNullable<z.ZodString>;
|
|
186
210
|
metaKeywords: z.ZodNullable<z.ZodString>;
|
|
@@ -208,11 +232,11 @@ export declare const ProductSchema: z.ZodObject<{
|
|
|
208
232
|
updatedAt: z.ZodString;
|
|
209
233
|
}, z.core.$strip>>;
|
|
210
234
|
images: z.ZodArray<z.ZodObject<{
|
|
235
|
+
key: z.ZodString;
|
|
236
|
+
url: z.ZodString;
|
|
237
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
238
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
211
239
|
id: z.ZodString;
|
|
212
|
-
src: z.ZodString;
|
|
213
|
-
srcSm: z.ZodNullable<z.ZodString>;
|
|
214
|
-
srcMd: z.ZodNullable<z.ZodString>;
|
|
215
|
-
srcLg: z.ZodNullable<z.ZodString>;
|
|
216
240
|
altText: z.ZodNullable<z.ZodString>;
|
|
217
241
|
position: z.ZodNumber;
|
|
218
242
|
}, z.core.$strip>>;
|
|
@@ -282,11 +306,11 @@ export declare const StoreCollectionSchema: z.ZodObject<{
|
|
|
282
306
|
description: z.ZodNullable<z.ZodString>;
|
|
283
307
|
productCount: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
284
308
|
image: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
309
|
+
key: z.ZodString;
|
|
310
|
+
url: z.ZodString;
|
|
311
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
312
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
285
313
|
id: z.ZodString;
|
|
286
|
-
src: z.ZodString;
|
|
287
|
-
srcSm: z.ZodNullable<z.ZodString>;
|
|
288
|
-
srcMd: z.ZodNullable<z.ZodString>;
|
|
289
|
-
srcLg: z.ZodNullable<z.ZodString>;
|
|
290
314
|
altText: z.ZodNullable<z.ZodString>;
|
|
291
315
|
position: z.ZodNumber;
|
|
292
316
|
}, z.core.$strip>>>;
|
|
@@ -417,7 +441,12 @@ export declare const StoreConfigSchema: z.ZodObject<{
|
|
|
417
441
|
id: z.ZodString;
|
|
418
442
|
name: z.ZodString;
|
|
419
443
|
domain: z.ZodNullable<z.ZodString>;
|
|
420
|
-
|
|
444
|
+
logo: z.ZodNullable<z.ZodObject<{
|
|
445
|
+
key: z.ZodString;
|
|
446
|
+
url: z.ZodString;
|
|
447
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
448
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
449
|
+
}, z.core.$strip>>;
|
|
421
450
|
themeId: z.ZodString;
|
|
422
451
|
primaryColor: z.ZodString;
|
|
423
452
|
accentColor: z.ZodString;
|
|
@@ -440,7 +469,20 @@ export declare const StoreConfigSchema: z.ZodObject<{
|
|
|
440
469
|
currencySymbol: z.ZodString;
|
|
441
470
|
metaTitle: z.ZodNullable<z.ZodString>;
|
|
442
471
|
metaDescription: z.ZodNullable<z.ZodString>;
|
|
443
|
-
ogImage: z.ZodNullable<z.
|
|
472
|
+
ogImage: z.ZodNullable<z.ZodObject<{
|
|
473
|
+
key: z.ZodString;
|
|
474
|
+
url: z.ZodString;
|
|
475
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
476
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
477
|
+
}, z.core.$strip>>;
|
|
478
|
+
media: z.ZodObject<{
|
|
479
|
+
kind: z.ZodEnum<{
|
|
480
|
+
cloudflare: "cloudflare";
|
|
481
|
+
direct: "direct";
|
|
482
|
+
imagekit: "imagekit";
|
|
483
|
+
}>;
|
|
484
|
+
origin: z.ZodString;
|
|
485
|
+
}, z.core.$strip>;
|
|
444
486
|
announcementBar: z.ZodNullable<z.ZodString>;
|
|
445
487
|
reviewsEnabled: z.ZodBoolean;
|
|
446
488
|
cartEnabled: z.ZodBoolean;
|
|
@@ -549,12 +591,13 @@ export declare const StoreConfigSchema: z.ZodObject<{
|
|
|
549
591
|
}, z.core.$strip>>>;
|
|
550
592
|
}, z.core.$strip>;
|
|
551
593
|
export declare const LandingPageImageSchema: z.ZodObject<{
|
|
594
|
+
key: z.ZodString;
|
|
595
|
+
url: z.ZodString;
|
|
596
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
597
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
552
598
|
id: z.ZodString;
|
|
553
|
-
src: z.ZodString;
|
|
554
599
|
altText: z.ZodNullable<z.ZodString>;
|
|
555
600
|
position: z.ZodNumber;
|
|
556
|
-
width: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
557
|
-
height: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
558
601
|
}, z.core.$strip>;
|
|
559
602
|
export declare const LandingPageSchema: z.ZodObject<{
|
|
560
603
|
id: z.ZodString;
|
|
@@ -570,12 +613,13 @@ export declare const LandingPageSchema: z.ZodObject<{
|
|
|
570
613
|
metaDescription: z.ZodNullable<z.ZodString>;
|
|
571
614
|
publishedAt: z.ZodNullable<z.ZodString>;
|
|
572
615
|
images: z.ZodArray<z.ZodObject<{
|
|
616
|
+
key: z.ZodString;
|
|
617
|
+
url: z.ZodString;
|
|
618
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
619
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
573
620
|
id: z.ZodString;
|
|
574
|
-
src: z.ZodString;
|
|
575
621
|
altText: z.ZodNullable<z.ZodString>;
|
|
576
622
|
position: z.ZodNumber;
|
|
577
|
-
width: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
578
|
-
height: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
579
623
|
}, z.core.$strip>>;
|
|
580
624
|
product: z.ZodNullable<z.ZodObject<{
|
|
581
625
|
id: z.ZodString;
|
|
@@ -630,7 +674,12 @@ export declare const LandingPageSchema: z.ZodObject<{
|
|
|
630
674
|
slug: z.ZodString;
|
|
631
675
|
description: z.ZodNullable<z.ZodString>;
|
|
632
676
|
parentId: z.ZodNullable<z.ZodString>;
|
|
633
|
-
|
|
677
|
+
image: z.ZodNullable<z.ZodObject<{
|
|
678
|
+
key: z.ZodString;
|
|
679
|
+
url: z.ZodString;
|
|
680
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
681
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
682
|
+
}, z.core.$strip>>;
|
|
634
683
|
metaTitle: z.ZodNullable<z.ZodString>;
|
|
635
684
|
metaDescription: z.ZodNullable<z.ZodString>;
|
|
636
685
|
metaKeywords: z.ZodNullable<z.ZodString>;
|
|
@@ -658,11 +707,11 @@ export declare const LandingPageSchema: z.ZodObject<{
|
|
|
658
707
|
updatedAt: z.ZodString;
|
|
659
708
|
}, z.core.$strip>>;
|
|
660
709
|
images: z.ZodArray<z.ZodObject<{
|
|
710
|
+
key: z.ZodString;
|
|
711
|
+
url: z.ZodString;
|
|
712
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
713
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
661
714
|
id: z.ZodString;
|
|
662
|
-
src: z.ZodString;
|
|
663
|
-
srcSm: z.ZodNullable<z.ZodString>;
|
|
664
|
-
srcMd: z.ZodNullable<z.ZodString>;
|
|
665
|
-
srcLg: z.ZodNullable<z.ZodString>;
|
|
666
715
|
altText: z.ZodNullable<z.ZodString>;
|
|
667
716
|
position: z.ZodNumber;
|
|
668
717
|
}, z.core.$strip>>;
|
|
@@ -904,11 +953,11 @@ export declare const ProductPageEnvelopeSchema: z.ZodObject<{
|
|
|
904
953
|
variantOptions: z.ZodNullable<z.ZodString>;
|
|
905
954
|
tags: z.ZodNullable<z.ZodString>;
|
|
906
955
|
coverImage: z.ZodNullable<z.ZodObject<{
|
|
956
|
+
key: z.ZodString;
|
|
957
|
+
url: z.ZodString;
|
|
958
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
959
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
907
960
|
id: z.ZodString;
|
|
908
|
-
src: z.ZodString;
|
|
909
|
-
srcSm: z.ZodNullable<z.ZodString>;
|
|
910
|
-
srcMd: z.ZodNullable<z.ZodString>;
|
|
911
|
-
srcLg: z.ZodNullable<z.ZodString>;
|
|
912
961
|
altText: z.ZodNullable<z.ZodString>;
|
|
913
962
|
position: z.ZodNumber;
|
|
914
963
|
}, z.core.$strip>>;
|