@managesales/storefront 1.0.1 → 1.1.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 +58 -0
- package/README.md +230 -103
- package/dist/index.cjs +179 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.js +179 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@managesales/storefront`. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org).
|
|
5
|
+
|
|
6
|
+
## 1.1.0 — 2026-07-11
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Stateful cart** — `client.cart.create()` / `client.cart.current()` return a `StorefrontCart` with
|
|
10
|
+
`addLineItem` (auto-enriches price/name from `products.get`, merges duplicate product+variant
|
|
11
|
+
lines), `updateLineItem`, `removeLineItem`, `clear`, and `checkout()` to materialize the cart
|
|
12
|
+
straight into a checkout session. Persisted to `localStorage` by default (`persistCart: false` to
|
|
13
|
+
opt out); safe to import under SSR/Node with no DOM. `client.cart.recover(token)` is unchanged.
|
|
14
|
+
- **`orders` and `customers` alias namespaces** — `client.orders.list()` and
|
|
15
|
+
`client.customers.{login,register,loginWithGoogle,logout,isAuthenticated,setTokens,me,orders,addresses}`
|
|
16
|
+
are convenience references onto the existing `auth`/`customer` methods (no duplicated logic, no new
|
|
17
|
+
backend calls).
|
|
18
|
+
- **Configurable request retries** — network errors and 5xx/408/425/429 responses on idempotent
|
|
19
|
+
(`GET`/`HEAD`) requests are now retried automatically with exponential backoff, honoring a
|
|
20
|
+
`Retry-After` response header as a delay floor. Configure via
|
|
21
|
+
`createStorefrontClient({ retry: { retries, baseDelayMs, maxDelayMs, retryOnStatus } })`, or pass
|
|
22
|
+
`retry: false` to disable. Non-idempotent requests (`POST`/`PUT`/`PATCH`/`DELETE`) are never
|
|
23
|
+
retried once a response is received from the server; a network failure that never reached the
|
|
24
|
+
server (e.g. `fetch` throwing) is retried regardless of method, since no side effect could have
|
|
25
|
+
occurred yet.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- `checkout.create` now sends line items under the `lines` key (matching the `/checkout` endpoint's
|
|
29
|
+
actual request shape) instead of `items`. Note this only corrects the request envelope — direct
|
|
30
|
+
callers must still supply every field the backend requires (see next item).
|
|
31
|
+
- `CheckoutLineItem` now includes the backend-required `productName` (string) and `unitPrice`
|
|
32
|
+
(number) fields, plus optional `imageUrl`. The previous `{ productId, variantId?, quantity }`
|
|
33
|
+
shape type-checked but was always rejected by the real `/checkout` endpoint's validation for
|
|
34
|
+
direct `client.checkout.create()` callers; `cart.checkout()` was unaffected since `CartLine`
|
|
35
|
+
already carried both fields. Callers who don't want to supply `productName`/`unitPrice` by hand
|
|
36
|
+
should use the stateful cart (`cart.addLineItem`), which auto-enriches them from `products.get`.
|
|
37
|
+
|
|
38
|
+
## 1.0.2 — 2026-07-11
|
|
39
|
+
|
|
40
|
+
### Docs
|
|
41
|
+
- Rewrote the README as a full developer landing page: human-readable title and tagline, feature
|
|
42
|
+
overview, all-package-manager install, a copy-paste quick start, a complete **API organization**
|
|
43
|
+
reference, framework examples (React / Next.js / Node.js), server-side auth (SSR) guide, error
|
|
44
|
+
handling, TypeScript guide, a **Versioning** policy, and this changelog.
|
|
45
|
+
|
|
46
|
+
## 1.0.1 — 2026-07-11
|
|
47
|
+
|
|
48
|
+
### Docs
|
|
49
|
+
- Expanded the README with per-resource examples and browser/Node runtime support notes.
|
|
50
|
+
|
|
51
|
+
## 1.0.0 — 2026-07-11
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- Initial public release. Resource-based Storefront SDK with namespaces for products, collections,
|
|
55
|
+
checkout, cart recovery, auth, customer, blog, bookings, and forms.
|
|
56
|
+
- Built-in customer auth with automatic access-token refresh on `401`.
|
|
57
|
+
- `StorefrontError` class exposing `status`, `code`, `message`, and `details`.
|
|
58
|
+
- Dual ESM + CommonJS builds with bundled TypeScript declarations. Zero runtime dependencies.
|
package/README.md
CHANGED
|
@@ -1,84 +1,116 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ManageSales Storefront SDK
|
|
2
|
+
|
|
3
|
+
**Build custom storefronts on top of the ManageSales Commerce Platform.**
|
|
2
4
|
|
|
3
5
|
[](https://www.npmjs.com/package/@managesales/storefront)
|
|
4
6
|
[](./LICENSE)
|
|
5
7
|
[](#typescript-support)
|
|
6
8
|
[](#browser--node-support)
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
manage customer accounts with a typed, ergonomic client instead of hand-rolled `fetch` calls.
|
|
10
|
+
`@managesales/storefront` is a typed, resource-based client for the ManageSales Storefront API —
|
|
11
|
+
like Shopify's Storefront client, for ManageSales. Everything you need to ship a storefront:
|
|
11
12
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
13
|
+
- 🛍️ **Products** — list, search, and fetch details
|
|
14
|
+
- 🗂️ **Collections** — browse categories and their products
|
|
15
|
+
- 👤 **Customers** — registration, login, profile, order history
|
|
16
|
+
- 🛒 **Checkout** — line items, coupons, complete purchases (+ abandoned-cart recovery)
|
|
17
|
+
- 🔎 **Search** — full-text product search
|
|
18
|
+
- 🟦 **TypeScript** — first-class types, no `@types` needed
|
|
19
|
+
- ⚡ **SSR-ready** — inject a custom `fetch`; server-side auth pattern built in
|
|
20
|
+
- 📦 **Zero dependencies** — one small file, dual ESM + CommonJS
|
|
17
21
|
|
|
18
22
|
---
|
|
19
23
|
|
|
20
24
|
## Table of contents
|
|
21
25
|
|
|
22
|
-
- [What is this?](#what-is-this)
|
|
23
26
|
- [Installation](#installation)
|
|
24
|
-
- [Quick start](#quick-start
|
|
27
|
+
- [Quick start](#quick-start)
|
|
28
|
+
- [API organization](#api-organization)
|
|
25
29
|
- [Create a client](#create-a-client)
|
|
26
|
-
- [Products](#products)
|
|
30
|
+
- [Products & search](#products--search)
|
|
27
31
|
- [Collections](#collections)
|
|
28
32
|
- [Cart & checkout](#cart--checkout)
|
|
33
|
+
- [Stateful cart](#stateful-cart)
|
|
29
34
|
- [Authentication](#authentication)
|
|
30
35
|
- [Customer account](#customer-account)
|
|
36
|
+
- [Retries](#retries)
|
|
31
37
|
- [Error handling](#error-handling)
|
|
32
|
-
- [More resources](#more-resources)
|
|
33
38
|
- [TypeScript support](#typescript-support)
|
|
39
|
+
- [Framework examples](#framework-examples)
|
|
34
40
|
- [Browser & Node support](#browser--node-support)
|
|
41
|
+
- [Versioning](#versioning)
|
|
42
|
+
- [Changelog](#changelog)
|
|
35
43
|
- [No-npm alternative](#no-npm-alternative)
|
|
36
44
|
- [License](#license)
|
|
37
45
|
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## What is this?
|
|
41
|
-
|
|
42
|
-
`@managesales/storefront` is the official client SDK for the **ManageSales Storefront API** — the
|
|
43
|
-
public, customer-facing API behind a ManageSales store. Use it to build a custom storefront
|
|
44
|
-
(product pages, search, cart, checkout, customer login) in any JavaScript/TypeScript framework,
|
|
45
|
-
while the SDK handles request headers, workspace scoping, error parsing, pagination, and token
|
|
46
|
-
refresh for you.
|
|
47
|
-
|
|
48
|
-
It talks to the same API you can also reach directly over HTTP (`/storefront-api/*`) — the SDK is
|
|
49
|
-
just the typed, batteries-included way to do it.
|
|
50
|
-
|
|
51
46
|
## Installation
|
|
52
47
|
|
|
53
48
|
```bash
|
|
54
49
|
npm install @managesales/storefront
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
pnpm add @managesales/storefront
|
|
51
|
+
yarn add @managesales/storefront
|
|
52
|
+
bun add @managesales/storefront
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
## Quick start
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
Copy-paste, add your workspace ID, and you're fetching live catalog data — no auth required:
|
|
61
58
|
|
|
62
59
|
```ts
|
|
63
60
|
import { createStorefrontClient } from "@managesales/storefront";
|
|
64
61
|
|
|
65
62
|
const client = createStorefrontClient({
|
|
66
|
-
workspaceId: "ws_123", // your store
|
|
63
|
+
workspaceId: "ws_123", // your store — Settings → API
|
|
67
64
|
});
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log(`${total} products`, products);
|
|
66
|
+
const { data: products } = await client.products.list({ limit: 12 });
|
|
67
|
+
console.log(products);
|
|
72
68
|
```
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
customers log in.
|
|
70
|
+
## API organization
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
Every resource is a namespace on the client. Public resources need no auth; customer resources
|
|
73
|
+
require a logged-in session.
|
|
78
74
|
|
|
79
75
|
```ts
|
|
80
|
-
|
|
76
|
+
// Catalog (public)
|
|
77
|
+
client.products.list(params?) // paginated list
|
|
78
|
+
client.products.get(idOrSlug) // product details
|
|
79
|
+
client.products.search(query, params?) // full-text search
|
|
80
|
+
client.collections.list(params?)
|
|
81
|
+
client.collections.get(idOrSlug) // collection + its products
|
|
82
|
+
|
|
83
|
+
// Cart & checkout (public)
|
|
84
|
+
client.checkout.create(lineItems) // start a checkout from line items
|
|
85
|
+
client.checkout.get(checkoutId)
|
|
86
|
+
client.checkout.applyCoupon(checkoutId, code)
|
|
87
|
+
client.checkout.complete(checkoutId, payment?)
|
|
88
|
+
client.cart.create() / client.cart.current() // stateful cart — see Stateful cart
|
|
89
|
+
client.cart.recover(token) // rebuild a checkout from a recovery link
|
|
90
|
+
client.orders.list(params?) // alias for client.customer.orders(params?)
|
|
91
|
+
|
|
92
|
+
// Auth & customer (customer session)
|
|
93
|
+
client.auth.register({ email, password, name })
|
|
94
|
+
client.auth.login(email, password)
|
|
95
|
+
client.auth.loginWithGoogle(idToken)
|
|
96
|
+
client.auth.logout()
|
|
97
|
+
client.auth.isAuthenticated()
|
|
98
|
+
client.auth.setTokens(access, refresh) // restore a session (e.g. from cookies)
|
|
99
|
+
client.customer.me()
|
|
100
|
+
client.customer.orders(params?) // order history
|
|
101
|
+
client.customer.addresses()
|
|
102
|
+
// client.customers.* — alias namespace bundling auth + customer under one name
|
|
103
|
+
// (client.customers.login === client.auth.login, client.customers.me === client.customer.me, ...)
|
|
104
|
+
|
|
105
|
+
// Content (public)
|
|
106
|
+
client.blog.list(params?) / client.blog.get(slug)
|
|
107
|
+
client.bookings.services() / client.bookings.availability(params) / client.bookings.book(data)
|
|
108
|
+
client.forms.get(idOrSlug) / client.forms.submit(formId, data) / client.forms.uploadFile(formId, file)
|
|
109
|
+
```
|
|
81
110
|
|
|
111
|
+
## Create a client
|
|
112
|
+
|
|
113
|
+
```ts
|
|
82
114
|
const client = createStorefrontClient({
|
|
83
115
|
workspaceId: "ws_123",
|
|
84
116
|
});
|
|
@@ -87,12 +119,11 @@ const client = createStorefrontClient({
|
|
|
87
119
|
| Option | Type | Required | Description |
|
|
88
120
|
|--------|------|----------|-------------|
|
|
89
121
|
| `workspaceId` | `string` | ✅ | Identifies your store. Found in **Settings → API**. |
|
|
90
|
-
| `apiUrl` | `string` | — | Backend base URL **including** the `/api/v1` prefix. Defaults to ManageSales cloud; override for self-hosted/staging/local. |
|
|
91
|
-
| `customFetch` | `typeof fetch` | — | Inject a `fetch`
|
|
122
|
+
| `apiUrl` | `string` | — | Backend base URL **including** the `/api/v1` prefix. Defaults to ManageSales cloud; override for self-hosted / staging / local. |
|
|
123
|
+
| `customFetch` | `typeof fetch` | — | Inject a `fetch` for SSR / edge runtimes or Node < 18. |
|
|
92
124
|
| `locale` | `string` | — | Default locale for localized content (e.g. `"en"`). |
|
|
93
125
|
|
|
94
126
|
```ts
|
|
95
|
-
// Point at your own backend and inject a custom fetch:
|
|
96
127
|
const client = createStorefrontClient({
|
|
97
128
|
workspaceId: "ws_123",
|
|
98
129
|
apiUrl: "https://api.mystore.com/api/v1",
|
|
@@ -101,7 +132,7 @@ const client = createStorefrontClient({
|
|
|
101
132
|
});
|
|
102
133
|
```
|
|
103
134
|
|
|
104
|
-
## Products
|
|
135
|
+
## Products & search
|
|
105
136
|
|
|
106
137
|
```ts
|
|
107
138
|
// List with pagination, search, and sorting
|
|
@@ -124,32 +155,30 @@ const { data: results } = await client.products.search("organic cotton");
|
|
|
124
155
|
## Collections
|
|
125
156
|
|
|
126
157
|
```ts
|
|
127
|
-
// List collections
|
|
128
158
|
const { data: collections } = await client.collections.list();
|
|
129
159
|
|
|
130
|
-
// Get a collection with its products
|
|
131
160
|
const summer = await client.collections.get("summer-sale");
|
|
132
|
-
console.log(summer.name, summer.products.length);
|
|
161
|
+
console.log(summer.name, summer.products.length); // collection + its products
|
|
133
162
|
```
|
|
134
163
|
|
|
135
164
|
## Cart & checkout
|
|
136
165
|
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
Assemble line items in your UI, create a checkout session, optionally apply a coupon, then complete
|
|
167
|
+
it:
|
|
139
168
|
|
|
140
169
|
```ts
|
|
141
|
-
// 1. Create a checkout from
|
|
170
|
+
// 1. Create a checkout from line items
|
|
142
171
|
const session = await client.checkout.create([
|
|
143
|
-
{ productId: "prod_123", variantId: "var_456", quantity: 2 },
|
|
144
|
-
{ productId: "prod_789", quantity: 1 },
|
|
172
|
+
{ productId: "prod_123", variantId: "var_456", productName: "Blue T-Shirt", quantity: 2, unitPrice: 24.99 },
|
|
173
|
+
{ productId: "prod_789", productName: "Canvas Tote", quantity: 1, unitPrice: 18 },
|
|
145
174
|
]);
|
|
146
175
|
|
|
147
|
-
// 2. Re-fetch a session (e.g. after a
|
|
176
|
+
// 2. Re-fetch a session (e.g. after a reload)
|
|
148
177
|
const current = await client.checkout.get(session.id);
|
|
149
178
|
|
|
150
179
|
// 3. Apply a coupon
|
|
151
180
|
const discounted = await client.checkout.applyCoupon(session.id, "SAVE10");
|
|
152
|
-
console.log(
|
|
181
|
+
console.log(discounted.discount, discounted.total);
|
|
153
182
|
|
|
154
183
|
// 4. Complete the purchase
|
|
155
184
|
const order = await client.checkout.complete(session.id, {
|
|
@@ -159,47 +188,73 @@ const order = await client.checkout.complete(session.id, {
|
|
|
159
188
|
console.log("Order placed:", order.orderNumber);
|
|
160
189
|
```
|
|
161
190
|
|
|
191
|
+
> `productName` and `unitPrice` are required on every line item passed directly to
|
|
192
|
+
> `client.checkout.create()` — the backend validates them. If you'd rather not look these up
|
|
193
|
+
> yourself, use the [stateful cart](#stateful-cart) instead: `cart.addLineItem` auto-enriches
|
|
194
|
+
> `productName`/`unitPrice` (and `imageUrl`) via `products.get`, so cart callers only ever supply
|
|
195
|
+
> `productId`/`variantId`/`quantity`.
|
|
196
|
+
|
|
162
197
|
**Abandoned-cart recovery** — turn a recovery link back into a checkout session:
|
|
163
198
|
|
|
164
199
|
```ts
|
|
165
|
-
// URL: https://yourstore.com/recover?token=abc123
|
|
166
200
|
const token = new URLSearchParams(location.search).get("token")!;
|
|
167
201
|
const recovered = await client.cart.recover(token);
|
|
168
202
|
```
|
|
169
203
|
|
|
170
|
-
##
|
|
204
|
+
## Stateful cart
|
|
171
205
|
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
For a persistent, imperative cart — add/update/remove lines, then check out in one call — use
|
|
207
|
+
`client.cart` instead of assembling line items by hand:
|
|
174
208
|
|
|
175
209
|
```ts
|
|
176
|
-
//
|
|
177
|
-
const
|
|
178
|
-
email: "jane@example.com",
|
|
179
|
-
password: "SecurePass123!",
|
|
180
|
-
name: "Jane Doe",
|
|
181
|
-
});
|
|
210
|
+
// Create a fresh cart, or resume one already persisted for this workspace
|
|
211
|
+
const cart = client.cart.create(); // client.cart.current() resumes a persisted cart instead
|
|
182
212
|
|
|
183
|
-
//
|
|
184
|
-
await
|
|
213
|
+
// Add items — price, name, and image are looked up automatically via products.get()
|
|
214
|
+
await cart.addLineItem({ productId: "prod_123", variantId: "var_456", quantity: 2 });
|
|
215
|
+
await cart.addLineItem({ productId: "prod_123", variantId: "var_456", quantity: 1 }); // merges into the existing line
|
|
185
216
|
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
console.log(cart.itemCount); // 3
|
|
218
|
+
console.log(cart.subtotal); // sum of unitPrice * quantity across all lines
|
|
188
219
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
220
|
+
cart.updateLineItem(cart.lines[0].id, 5); // set a line's quantity (quantity <= 0 removes it)
|
|
221
|
+
cart.removeLineItem(cart.lines[0].id);
|
|
222
|
+
cart.clear();
|
|
223
|
+
|
|
224
|
+
// Turn the cart directly into a checkout session
|
|
225
|
+
const session = await cart.checkout({ clearOnSuccess: true });
|
|
226
|
+
console.log(session.id, session.total);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
By default the cart is persisted to `localStorage` under a per-workspace key, so `client.cart.current()`
|
|
230
|
+
resumes it after a page reload. Pass `persistCart: false` to `createStorefrontClient` to keep it
|
|
231
|
+
in-memory only (tests, SSR, multi-cart UIs). All storage access is guarded, so the SDK still imports
|
|
232
|
+
and runs fine under Node/SSR where `localStorage` doesn't exist — the cart simply won't persist.
|
|
233
|
+
|
|
234
|
+
> `cart.checkout()` forwards the cart's line items to `client.checkout.create()` under the hood, so
|
|
235
|
+
> coupons/payment on the returned session work exactly as in [Cart & checkout](#cart--checkout).
|
|
236
|
+
|
|
237
|
+
## Authentication
|
|
238
|
+
|
|
239
|
+
Most pages need **no auth**. When a customer logs in, the SDK stores the access + refresh tokens and
|
|
240
|
+
**auto-refreshes on `401`** — you never handle tokens by hand.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
await client.auth.register({ email: "jane@example.com", password: "SecurePass123!", name: "Jane Doe" });
|
|
244
|
+
await client.auth.login("jane@example.com", "SecurePass123!");
|
|
245
|
+
await client.auth.loginWithGoogle(googleIdToken); // pass a Google ID token
|
|
246
|
+
|
|
247
|
+
if (client.auth.isAuthenticated()) { /* show account menu */ }
|
|
193
248
|
await client.auth.logout();
|
|
194
249
|
```
|
|
195
250
|
|
|
196
251
|
### Server-side (recommended)
|
|
197
252
|
|
|
198
|
-
For production, run the SDK on your server
|
|
199
|
-
|
|
253
|
+
For production, run the SDK on your server with httpOnly cookies so tokens never reach the browser.
|
|
254
|
+
Restore a session with `setTokens`:
|
|
200
255
|
|
|
201
256
|
```ts
|
|
202
|
-
// app/api/storefront/[...path]/route.ts
|
|
257
|
+
// app/api/storefront/[...path]/route.ts (Next.js)
|
|
203
258
|
import { createStorefrontClient } from "@managesales/storefront";
|
|
204
259
|
import { cookies } from "next/headers";
|
|
205
260
|
|
|
@@ -217,18 +272,48 @@ export async function GET() {
|
|
|
217
272
|
|
|
218
273
|
## Customer account
|
|
219
274
|
|
|
220
|
-
Requires a logged-in session
|
|
275
|
+
Requires a logged-in session.
|
|
221
276
|
|
|
222
277
|
```ts
|
|
223
|
-
const me = await client.customer.me();
|
|
278
|
+
const me = await client.customer.me(); // profile
|
|
224
279
|
const { data: orders } = await client.customer.orders({ page: 1 }); // order history
|
|
225
|
-
const addresses = await client.customer.addresses();
|
|
280
|
+
const addresses = await client.customer.addresses(); // saved addresses
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`client.customers` mirrors `auth` + `customer` under one namespace, and `client.orders.list()` is a
|
|
284
|
+
shorthand for `client.customer.orders()` — same functions, same requests, just organized differently:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
await client.customers.login("jane@example.com", "SecurePass123!"); // === client.auth.login
|
|
288
|
+
const { data: orders } = await client.orders.list({ page: 1 }); // === client.customer.orders({ page: 1 })
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Retries
|
|
292
|
+
|
|
293
|
+
Network failures and `408` / `425` / `429` / `5xx` responses to idempotent (`GET`/`HEAD`) requests are
|
|
294
|
+
retried automatically with exponential backoff, honoring a `Retry-After` response header as a delay
|
|
295
|
+
floor when the server sends one. Non-idempotent requests (`POST`/`PUT`/`PATCH`/`DELETE`) are retried
|
|
296
|
+
**only** when the request never reached the server (e.g. `fetch` itself throws) — never after a server
|
|
297
|
+
response, since the side effect may already have happened.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
const client = createStorefrontClient({
|
|
301
|
+
workspaceId: "ws_123",
|
|
302
|
+
retry: {
|
|
303
|
+
retries: 2, // default: 2 additional attempts after the first
|
|
304
|
+
baseDelayMs: 300, // default: 300 — base for exponential backoff
|
|
305
|
+
maxDelayMs: 4000, // default: 4000 — cap on any single backoff delay
|
|
306
|
+
retryOnStatus: [408, 425, 429, 500, 502, 503, 504], // default shown
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Disable retries entirely
|
|
311
|
+
const noRetryClient = createStorefrontClient({ workspaceId: "ws_123", retry: false });
|
|
226
312
|
```
|
|
227
313
|
|
|
228
314
|
## Error handling
|
|
229
315
|
|
|
230
|
-
Every non-2xx response throws a `StorefrontError` with structured fields
|
|
231
|
-
precisely:
|
|
316
|
+
Every non-2xx response throws a `StorefrontError` with structured fields:
|
|
232
317
|
|
|
233
318
|
```ts
|
|
234
319
|
import { StorefrontError } from "@managesales/storefront";
|
|
@@ -247,30 +332,13 @@ try {
|
|
|
247
332
|
}
|
|
248
333
|
```
|
|
249
334
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
const { data: posts } = await client.blog.list({ limit: 10 });
|
|
256
|
-
const post = await client.blog.get("how-to-style-leather");
|
|
257
|
-
|
|
258
|
-
const services = await client.bookings.services();
|
|
259
|
-
const slots = await client.bookings.availability({ serviceId: "svc_1", date: "2026-08-15" });
|
|
260
|
-
const booking = await client.bookings.book({
|
|
261
|
-
serviceId: "svc_1",
|
|
262
|
-
date: "2026-08-15",
|
|
263
|
-
startTime: "10:00",
|
|
264
|
-
customer: { name: "Jane Doe", email: "jane@example.com" },
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const form = await client.forms.get("contact-us");
|
|
268
|
-
await client.forms.submit(form.id, { name: "Jane", email: "jane@example.com", message: "Hi!" });
|
|
269
|
-
```
|
|
335
|
+
> **Resilience:** the client automatically retries a request **once** with a refreshed token when a
|
|
336
|
+
> `401` indicates an expired access token, and separately retries transient network/5xx failures per
|
|
337
|
+
> the [Retries](#retries) config.
|
|
270
338
|
|
|
271
339
|
## TypeScript support
|
|
272
340
|
|
|
273
|
-
Written in TypeScript and shipped with declaration files — no `@types/*` package
|
|
341
|
+
Written in TypeScript and shipped with declaration files — no `@types/*` package. Params and
|
|
274
342
|
responses are fully typed, including generics like `PaginatedResponse<Product>`:
|
|
275
343
|
|
|
276
344
|
```ts
|
|
@@ -288,18 +356,77 @@ const client = createStorefrontClient(config);
|
|
|
288
356
|
const page: PaginatedResponse<Product> = await client.products.list();
|
|
289
357
|
```
|
|
290
358
|
|
|
359
|
+
## Framework examples
|
|
360
|
+
|
|
361
|
+
**React**
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import { useEffect, useState } from "react";
|
|
365
|
+
import { createStorefrontClient, type Product } from "@managesales/storefront";
|
|
366
|
+
|
|
367
|
+
const client = createStorefrontClient({ workspaceId: "ws_123" });
|
|
368
|
+
|
|
369
|
+
export function ProductGrid() {
|
|
370
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
client.products.list({ limit: 12 }).then((res) => setProducts(res.data));
|
|
373
|
+
}, []);
|
|
374
|
+
return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Next.js (Server Component)**
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
// app/page.tsx
|
|
382
|
+
import { createStorefrontClient } from "@managesales/storefront";
|
|
383
|
+
|
|
384
|
+
const client = createStorefrontClient({ workspaceId: "ws_123" });
|
|
385
|
+
|
|
386
|
+
export default async function Home() {
|
|
387
|
+
const { data: products } = await client.products.list({ limit: 12 });
|
|
388
|
+
return <ProductGrid products={products} />;
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Node.js**
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
import { createStorefrontClient } from "@managesales/storefront";
|
|
396
|
+
|
|
397
|
+
const client = createStorefrontClient({ workspaceId: "ws_123" });
|
|
398
|
+
|
|
399
|
+
const { data } = await client.products.list({ page: 1, limit: 20 });
|
|
400
|
+
console.log(data);
|
|
401
|
+
```
|
|
402
|
+
|
|
291
403
|
## Browser & Node support
|
|
292
404
|
|
|
293
405
|
- **Zero dependencies** — uses the platform's global `fetch`.
|
|
294
406
|
- **Node.js ≥ 18** (global `fetch`). On older Node, pass `customFetch` (e.g. `undici`).
|
|
295
407
|
- **Browsers** — all modern evergreen browsers.
|
|
296
|
-
- **Deno, Bun,
|
|
297
|
-
|
|
408
|
+
- **Deno, Bun, edge runtimes** (Cloudflare Workers, Vercel Edge) — supported; inject `customFetch`
|
|
409
|
+
where a global isn't available.
|
|
298
410
|
- **Module formats** — ships both **ESM** (`import`) and **CommonJS** (`require`).
|
|
299
411
|
|
|
412
|
+
## Versioning
|
|
413
|
+
|
|
414
|
+
Follows [Semantic Versioning](https://semver.org):
|
|
415
|
+
|
|
416
|
+
- **patch** (`x.y.Z`) — docs and bug fixes, no API change
|
|
417
|
+
- **minor** (`x.Y.z`) — additive, backward-compatible API
|
|
418
|
+
- **major** (`X.y.z`) — breaking changes to the client surface
|
|
419
|
+
|
|
420
|
+
The SDK version tracks the ManageSales Storefront API contract it targets. See the
|
|
421
|
+
[Changelog](#changelog).
|
|
422
|
+
|
|
423
|
+
## Changelog
|
|
424
|
+
|
|
425
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release notes.
|
|
426
|
+
|
|
300
427
|
## No-npm alternative
|
|
301
428
|
|
|
302
|
-
Prefer
|
|
429
|
+
Prefer no dependency? The identical client is served as a single file — download it from
|
|
303
430
|
**Developers → SDKs** in the app, or curl it:
|
|
304
431
|
|
|
305
432
|
```bash
|
|
@@ -308,7 +435,7 @@ curl -o managesales-storefront.ts \
|
|
|
308
435
|
```
|
|
309
436
|
|
|
310
437
|
Then `import { createStorefrontClient } from "./managesales-storefront"`. The npm package, the curl
|
|
311
|
-
endpoint, and the in-app download are
|
|
438
|
+
endpoint, and the in-app download are generated from one source, so they're always identical.
|
|
312
439
|
|
|
313
440
|
## License
|
|
314
441
|
|